How to bind usercontrol to applications viewmodel - data-binding

I am using the standard pivot template in my WP7 app.
I have the MainViewModel class defined with a few extra properties:
public class MainViewModel : INotifyPropertyChanged
{
...
private MyClass selectedKey_m;
public MyClass SelectedKey
{
get
{
...
}
set
{
if (value != this.selectedKey_m)
{
this.selectedKey_m = value;
NotifyPropertyChanged("SelectedKey");
}
}
}
}
The App class has a view model instance:
private static MainViewModel viewModel = null;
public static MainViewModel ViewModel
{
get
{
// Delay creation of the view model until necessary
if (viewModel == null)
viewModel = new MainViewModel();
return viewModel;
}
}
My MainPage.xaml.cs sets the DataContext:
DataContext = App.ViewModel;
From here, I can set up two way bindings on ListBoxes and I know it works because if I put a breakpoint on the SelecetdKey property in my viewmodel I can see the setter get called.
My problem is that I have my own user control, with a bindable property, bound to the SelectedKey property of the view model, but the property in my user control never gets set when the viewmodel gets updated and I can't figure out why.
Here is my user control:
public partial class MyUserControl : UserControl
{
public static readonly DependencyProperty SelectedKeyProperty = DependencyProperty.Register(
"SelectedKey", typeof(MyClass), typeof(MyUserControl), new PropertyMetadata(null));
public MyClass SelectedKey
{
get { return (MyClass)this.GetValue(SelectedKeyProperty); }
set { this.SetValue(SelectedKeyProperty, value); }
}
}
And here is the xaml in my main page:
<local:MyUserControl x:Name="myUC" SelectedKey="{Binding Path=SelectedKey}">
I would expect that the setter for the SelectedKey property of my user control to get called when the SelectedKey property of the view model gets changed, but it doesn't.
I've also tried setting the datacontext of my user control in the xaml:
DataContext="{Binding Path=App.ViewModel}"

The debugger does not step into the setter, don't know why.
Try adding a callback invoked on property value changes :
public static readonly DependencyProperty SelectedKeyProperty = DependencyProperty.Register(
"SelectedKey", typeof(MyClass), typeof(MyUserControl), new PropertyMetadata(MyPropertyChanged));
private static void MyPropertyChanged( object sender, DependencyPropertyChangedEventArgs args)
{
}

Solved. I had to add the static method as ptauzen suggested, but also remove the DataContext binding statement from my xaml :
DataContext="{Binding Path=App.ViewModel}"
Because the MainPage sets the datacontext in the constructor, so because my user control is a child of the main page, it inherits the data context. All I needed was to ensure the binding of my user controls properties were set up:
SelectedKey="{Binding SelectedKey}"

Related

Navigating to views inside CarouselView with Prism

I have a MyPage with a CarouselView and two buttons below it. The buttons are for navigating between ContentView views inside the CarouselView:
[CarouselView]
[Prev] [Next]
ContentViewA and ContentViewB are inside of CarouselView
The MyPageViewModel has commands for the previous and next buttons:
class MyPageViewModel : BindableBase
{
public ICommand ShowPrevCommand { get; private set;}
public ICommand ShowNextCommand { get; private set;}
}
How do I implement the commands to make the CarouselView show the views?
According to documentation here
In Prism, the concept of navigating to a View or navigating to a
ViewModel does not exist. Instead, you simply navigate to an
experience, or a unique identifier, which represents the target view
you wish to navigate to in your application
so I was thinking I could use INavigationService.
I was thinking I could implement my own NavigationService and on NavigateAsync I could check if current page is MyPage. If it is, I could set the view inside of CarouselView to the view based on the navigation name parameter.
I am however not sure how to implement and override Prism's navigation service.
Can Prism for Xamarin Forms do something like this?
It does not have to be that complicated. CarouselView has the bindable property Position, which you can bind to a property of your viewmodel
<ContentPage xmlns="http://xamarin.com/schemas/2014/forms"
xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
xmlns:forms="clr-namespace:Xamarin.Forms;assembly=Xamarin.Forms.CarouselView">
<ContentPage.Content>
<forms:CarouselView Position="{Binding CarouselPosition}">
<!-- whatever to display in the CarouselView -->
</form:CarouselView>
</ContentPage.Content>
</ContentPage>
In your viewmodel you can implement th navigation the following way:
class MyPageViewModel : BindableBase
{
public MyPageViewModel()
{
ShowPrevCommand = new Command(ShowPrev);
ShowNextCommand = new Command(ShowNext);
}
public ICommand ShowPrevCommand { get; private set;}
public ICommand ShowNextCommand { get; private set;}
void OnShowPrev()
{
CarouselPosition--;
}
void OnShowNext()
{
CarouselPosition++;
}
public int CarouselPosition
{
get => _carouselPosition;
set
{
if(value == _carouselPosition)
{
return;
}
this._carouselPosition = value;
PropertyChanges?.Invoke(this, new PropertyChangedEventArgs(CarouselPosition));
}
}
}
Just to get the gist. Of course you'll have to handle cases like overflows (i.e. CarouselPosition exceeds the number of views in the carousel), etc.

Xamarin forms - Passing data between pages & views

I have a xamarin forms app.
There are 2 classes with data, one of the pages is filling the data.
The problem is: I'm creating new view, that should use data from both classes.
The only way i'm familiar with is to set a class as a bindingContext to pass data between pages, and it's working fine with ONE class, because apparently there couldn't be 2 bindingContext at the same time.
EXAMPLE:
1st class (all the classes are filled on the previous page. just accept that they are filled)
public class Buildings : INotifyPropertyChanged
{
public event PropertyChangedEventHandler PropertyChanged;
private string _id;
public string Id
{
get { return _id; }
set
{
_id = value;
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs("Id"));
}
}
}
2nd class
public class Flats : INotifyPropertyChanged
{
public event PropertyChangedEventHandler PropertyChanged;
private string _num;
public string Num
{
get { return _num; }
set
{
_num = value;
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs("Num"));
}
}
}
new view:
public partial class HouseView
{
private Flats _flats;
private Buildings _buildings;
public HouseView()
{
InitializeComponent();
}
private void HouseView_OnBindingContextChanged(object sender, EventArgs e)
{
var building = BindingContext as Building;
//var flat = BindingContext as Flat;
//_flat = flat;
_building = building;
var buildingInfo = await Rest.GetHouseInfo(_building.Id, _flat.Num); //function that will return info on a current house;
// rest code
}
}
Maybe there is no need for binding context, because i'm just passing the parameters, not changing them in a view? I guess the solution can be pretty simple, and i cant figure it out....
What you are missing is understanding the concept of ViewModel, and it's relation with the views.. In this case what you need is a 3rd class (ViewModel) that handles your 2 previous class:
public class HouseViewModel : INotifyPropertyChanged
{
public Flats Flats { get; set; }
private Buildings Buildings { get; set; }
}
Also using OnBindingContextChanged is just messy and will take some performance from your app .. try to prepare your data before on your VM, so the view knows as little as possible in how to get/handle data.
There is simple way to transfer data between pages in Xamarin forms.
Add new class to the main project called Transporter.cs, and this class should be static.
Inside this class, add the variables to transfer data between other pages; then you can simply access any variable by using Transporter.Variable.
Example:
public static Transporter
{
public static string x;
}
> Now, in each page, you can simply access (set or get) the value:
Transporter.x=MyName.Text;
>In another page:
MySecondName.Text=Transporter.x;
Note: MyName is an entry field in the first page, and MySecondName is an entry field in the second page.
Also, you can define any type of variables like (Lists, int, object... etc).

AutoPostBack Interface

I'm creating a set of Web User Controls which will share a common base class. The base will always contain a WebControl (either a Textbox, DropDownList, CheckBoxList, RadioButtonList, ListBox, and maybe others) and a RequiredFieldValidator. All is well, except that my abstract InputControl property (defined as type WebControl) doesn't seem to have the AutoPostBack property which I want them all to have (I assume this is because not all WebControls support AutoPostBack). I would love to put this functionality in completely in the base class and not have to define it in each child. It would look like:
public abstract WebControl InputControl { get; set; }
public bool AutoPostBack {
get { return InputControl.AutoPostBack; }
set { InputControl.AutoPostBack = value; }
}
But this obviously won't work because AutoPostBack is not a property of WebControl. Is there another interface that I could specify that would support this, or do I need to create my own?
autopostback is not in any base class because only some controls need autopostback property. what you can do is to use generics
public class MyBaseUC<T> : System.Web.UI.UserControl
{
public abstract T InputControl { get; set; }
}
public partial class WebUserControl1 : MyBaseUC<TextBox>
{
protected void Page_Load(object sender, EventArgs e)
{
InputControl.AutoPostBack = false;
}
}
Regards.

Using castle windsor with interceptors and asp.net

I'm trying to add logging with aspect orientated programming using castle windsor in plain asp.net, i.e. not MVC
I've added a class that implements the IInterceptor interface and an attribute that inherits from Attribute.
public class LogAttribute : Attribute
{
public Level LogLevel { get; set; }
public LogAttribute(Level level)
{
LogLevel = level;
}
}
public class LoggingInterceptor : IInterceptor
{
public void Intercept(IInvocation invocation)
{
MethodInfo mi = invocation.Method;
LogAttribute[] atts = (LogAttribute[])mi.GetCustomAttributes(typeof(LogAttribute), true);
// if method not marked with InternalUseRestricted attribute, then pass on call
if (atts.Length == 0)
{
invocation.Proceed();
}
else
{
ISeiLogger log = LoggerFactory.GetLogger(mi.DeclaringType.ToString());
//assume only one logging attribute
//log on entry
log.LogEnter(atts[0].LogLevel);
//allow code to continue
invocation.Proceed();
//log on exit
log.LogExit(atts[0].LogLevel);
}
}
}
Now in the global.asax.cs I've added the following:
public partial class Global : System.Web.HttpApplication, IoCProvider
{
private void InitializeIoC()
{
container = new WindsorContainer();
container.Install(new Sei.Aspect.AspectInstaller());
}
public IWindsorContainer Container
{
get { return container; }
}
private static Sei.Logging.ISeiLogger log;
private IWindsorContainer container;
public override void Init()
{
base.Init();
InitializeIoC();
}
and I've created an installer class:
public class AspectInstaller : IWindsorInstaller
{
public void Install(IWindsorContainer container, IConfigurationStore store)
{
//container.Register(AllTypes.FromAssembly(Assembly.GetExecutingAssembly()).BasedOn<IInterceptor>().Configure(component => component.LifeStyle.PerWebRequest));
container.Register(Component.For<IInterceptor>().ImplementedBy<LoggingInterceptor>().LifeStyle.PerWebRequest);
container.Register(Component.For<IInterceptor>().ImplementedBy<InternalUseRestrictedInterceptor>().LifeStyle.PerWebRequest);
container.Register(Component.For<IInterceptor>().ImplementedBy<CachingInterceptor>().LifeStyle.PerWebRequest);
}
}
I now want to add the attribute to some arbitary page's code behind class and some arbitary virtual method, as in
[Log(Level.Info)]
protected string Login(string username, string password)
{
DoSomething();
}
This obviously doesn't work. Do I need to change the way I'm instantiating the page (its a page's code-behind class) to use a container? Or is it the way I'm registering the interceptors? I want to be able to use the interceptors on any class going forward and not have to tell the container about each and every class that I have in my application.
Short answer: it's not possible.
Long answer: due to the way ASP.NET Web Forms works, it doesn't let anyone interfere with the page instantiation. Some claim that using a custom PageHandlerFactory lets you do IoC, but this only lets you set properties after the page has been instantiated, which is simply not enough for proxying.
So runtime proxy libraries such as DynamicProxy or LinFu can't do anything about this. But you may be able to use compile-time aspect weavers, such as PostSharp.
Alternatively, make your code-behind as slim as possible, deferring actual logic to Windsor-managed components.

problem in xbap mvvm implementation

I am trying to implement a mvvm design pattern for xbap application But unable to carry out simple text binding.
Following is the definition of my DemoViewModel.cs,
class DemoViewModel : INotifyPropertyChanged
{
string name;
public event PropertyChangedEventHandler PropertyChanged;
public string Name
{
get
{
return name;
}
set
{
name = value;
OnPropertyChanged("Name");
}
}
public DemoViewModel()
{
Name = "test";
}
protected virtual void OnPropertyChanged(string propertyName)
{
if (this.PropertyChanged != null)
{
this.PropertyChanged(this, new PropertyChangedEventArgs(propertyName));
}
}
}
I am binding the view to viewmodel using code behind of view,
public DemoView()
{
InitializeComponent();
DataContext = new DemoViewModel();
}
Following is the binding definition for text box present in view,
I appears that you have everything hooked up correctly. During execution, take a look at you 'Output' window and see if it gives you any warnings on you Binding. Also, try to simplify your xaml a bit to the following and see if this helps:
<TextBox Text="{Binding Name, Mode=TwoWay}"/>
Based on your comment, to JSPrang's answer, I know whats wrong =)
XBAP is missing permissions to use reflection, and can therefore only bind to public classes, unless run in full trust.

Resources