Im working on a WPF project in which my view model has more than 90 properties. I have a "save" button on clicking of which I save the properties to DB. Im required to disable the Save button until at lease one of the properties has been modified. I cannot go and put OnPropertyChanged event handling for each property(simply because it is too cumbersome for 90+ properties). Is there that I can do such that even if one property is changed I should get notified so that I can enable the Save button?
<StackPanel>
<StackPanel>
<StackPanel Orientation="Horizontal" HorizontalAlignment="Right">
<Button Content="{x:Static res:Resources.SaveToDB}" Command="{Binding Save}" IsEnabled="{Binding CanSaveToDB}" Margin="0,0,4,0" HorizontalAlignment="Center" Style="{StaticResource OverlayDialogButtonCentered}" FontSize="14"/>
<Button Content="{x:Static res:Resources.DiscardDBSave}" Command="{Binding DiscardSave}" Margin="0,0,4,0" HorizontalAlignment="Center" Style="{StaticResource OverlayDialogButtonCentered}" FontSize="14"/>
</StackPanel>
<StackPanel>
<views:MySampleAppView1 DataContextChanged="MySampleApp_DataContextChanged" DataContext="{Binding MySampleApp.View1}"/>
</StackPanel>
<StackPanel>
<views:MySampleAppView2 DataContextChanged="MySampleApp_DataContextChanged" DataContext="{Binding MySampleApp.View2}"/>
</StackPanel>
<StackPanel>
<views:MySampleAppView3 DataContextChanged="MySampleApp_DataContextChanged" DataContext="{Binding MySampleApp.View3}"/>
</StackPanel>
<StackPanel>
<views:MySampleAppView4 DataContextChanged="MySampleApp_DataContextChanged" DataContext="{Binding MySampleApp.View4}"/>
</StackPanel>
<StackPanel>
<views:MySampleAppView5 DataContextChanged="MySampleApp_DataContextChanged" DataContext="{Binding MySampleApp.View5}"/>
</StackPanel>
<StackPanel>
<views:MySampleAppView6 DataContextChanged="MySampleApp_DataContextChanged" DataContext="{Binding MySampleApp.View6}"/>
</StackPanel>
<StackPanel>
<views:MySampleAppView7 DataContextChanged="MySampleApp_DataContextChanged" DataContext="{Binding MySampleApp.View7}"/>
</StackPanel>
</StackPanel>
Then each of the above View has WPF controls.I have viewmodels for each of the above View. Then there is a main view model which represents above mentioned code.
A typical solution is to implement it just once (usually on a base class) and call it from the setter on each property passing the name of the property that has changed. This is still relatively cumbersome. Another approach is to use a tool such as Fody to automatically add this logic.
If all your properties are calling a single OnPropertyChanged implementation then the logic to set the dirty flag (and so enable the Save button) can be implemented there.
As per the Documentation,
The PropertyChanged event can indicate all properties on the object
have changed by using either null or String.Empty as the property name
in the PropertyChangedEventArgs.
Two ways come to mind.
First, you don't really need to trigger a unique property change notice in each property setter because your buttons' IsEnabled properties are not gonna be binding to any one of those 90-some odd properties in your view model. What you really want I think is to have some kind of IsDirty property that gets set whenever one of the other properties change, bind that to the IsEnabled property of the buttons, and execute the OnPropertyChanged notifier when IsDirty is changed.
If you still don't want to have to do ANYTHING special in your 90 property setters, however, the other option is to handle the change event in your view controls themselves. In all TextBoxes, for example, subscribe to TextChanged. And in your event handlers, set the IsDirty property on your view model. (You can subscribe to those handlers in the controls' styles so you don't have to set the event handler 90 times.)
Barring those two ideas, I don't think there's any way to do what you want. There's no built in mechanism in C# to get notified when a class property has changed. You're gonna have to intercept the change yourself, if not in the properties' setters, then using event handlers on the controls they're bound to.
Related
I'm trying to extend the app from a WPF MVVM tutorial as an exercise. I've found no solution on the net for this specific problem I'm facing here.
I have a ViewModel with an ObservableCollection called "StudentsToAdd". This collection is bound to an ItemsControl. Outside the ItemsControl I have a Button with a binding to the "AddCommand" command in the ViewModel. The relevant extract form my XAML looks as follows:
<StackPanel Orientation="Vertical">
<StackPanel Orientation="Horizontal">
<Button Content="Add" Command="{Binding AddCommand}" HorizontalAlignment="Left" VerticalAlignment="Top" Width="75"/>
<Button Content="+" Command="{Binding AddToAddListCommand}" HorizontalAlignment="Center" VerticalAlignment="Center" Padding="3,0,3,0" Margin="50,0,0,0"/>
<Button Content="-" Command="{Binding RemoveFromAddListCommand}" HorizontalAlignment="Center" VerticalAlignment="Center" Padding="5,0,5,0" Margin="5,0,0,0"/>
</StackPanel>
<ItemsControl x:Name="AddList" ItemsSource="{Binding Path=StudentsToAdd}">
<ItemsControl.ItemTemplate>
<DataTemplate>
<StackPanel Orientation="Horizontal">
<TextBox Text="{Binding Path=FirstName, Mode=TwoWay, UpdateSourceTrigger=PropertyChanged}" Width="100" Margin="0 5 3 5">
<TextBox.InputBindings>
<KeyBinding Command="{Binding ElementName=AddList, Path=DataContext.AddCommand}" Key="Return"/>
</TextBox.InputBindings>
</TextBox>
<TextBox Text="{Binding Path=LastName, Mode=TwoWay, UpdateSourceTrigger=PropertyChanged}" Width="100" Margin="0 5 3 5">
<TextBox.InputBindings>
<KeyBinding Command="{Binding ElementName=AddList, Path=DataContext.AddCommand}" Key="Return"/>
</TextBox.InputBindings>
</TextBox>
</StackPanel>
</DataTemplate>
</ItemsControl.ItemTemplate>
</ItemsControl>
</StackPanel>
The + and - buttons will add or remove students from the StudentsToAdd collection. The "AddCommand" moves all entries from StudentsToAdd to another collection called "Students" when executed.
Now what I can't get to work is this: whenever a Student in StudentsToAdd is modified (after any keystroke: UpdateSourceTrigger=PropertyChanged). I want the Add Button to evaluate the CanExecute of AddCommand in the ViewModel so its IsEnabled property is automatically set accordingly. The command methods in the ViewModel currently look as follows:
private void OnAdd()
{
foreach (Student s in StudentsToAdd)
{
Students.Add(s);
}
StudentsToAdd.Clear();
StudentsToAdd.Add(new Student { FirstName = string.Empty, LastName = string.Empty });
}
private bool CanAdd()
{
if (StudentsToAdd != null && StudentsToAdd.Count > 0)
{
return StudentsToAdd.All(x => !string.IsNullOrWhiteSpace(x.FirstName) && !string.IsNullOrWhiteSpace(x.LastName));
}
return false;
}
Does anybody know how I can achieve this without coupling parts of the MVVM?
Does anybody know how I can achieve this without coupling parts of the MVVM?
There's not much context in your question. But, it seems that you are asking how to accomplish this entirely within the view model/model layer, without involving the view layer. At least, that's what you should be asking.
If so, it should be relatively simple, assuming the code you didn't show is written reasonably. That is, since your CanAdd() method depends on property values of the Student objects, you'll need to subscribe to the Student.PropertyChanged event, and raise the ICommand.CanExecuteChanged event any time any of the Student objects' PropertyChanged event is raised.
For what it's worth, I would also encapsulate the "can be added" logic in the Student class, rather than the ViewModel class. Expose that state as a single property that the ViewModel class can check. This will address a couple of things:
Your Student class seems like the more logical place to put code that determines whether the class is ready to be added to a list of Students, and
The ViewModel class can check to make sure it's that property that is changing, so it doesn't bother to go to all the work to check all the other Student objects every time any Student property changes, and each Student object will effectively be caching the "can be added" value, so that that work to check all the other Student objects is a simple property retrieval, instead of having to re-evaluate the state every single time.
I assume you already understand how to raise the ICommand.CanExecuteChanged event, but if not, here are a couple of posts that should help you with that:
CanExecuteChanged event of ICommand
ICommand CanExecuteChanged not updating
(You'll see that there are two basic strategies: implement something in the ICommand object that will explicitly raise the ICommand.CanExecuteChanged event, or call InputManager.InvalidateRequerySuggested() to force the Input Manager to call all the CanExecute() methods it knows about. IMHO, the latter is a pretty heavy-weight and less-desirable approach, hence my suggestion to use the ICommand.CanExecuteChanged event.)
So I'm developing a Windows Phone 8 app with the Caliburn.Micro framework. I'm trying to create a grid where I, at runtime add/remove elements such as TextBlock's at runtime. I've tried a few things to bind my code to the x:Name but nothing has worked so far.
So one of the things i tried was having a placeholder grid in my xaml aka View:
<Grid x:Name="ContentPanel" Margin="0,97,0,0" Grid.RowSpan="2">
</Grid>
And then i my ViewModel i use the following to bind my ContentPanel Grid:
private Grid contentPanel;
public Grid ContentPanel
{
get
{
return contentPanel;
}
set
{
contentPanel = value;
NotifyOfPropertyChange(() => ContentPanel);
}
}
I then created a TextBlock to add to the grid:
TextBlock txt1 = new TextBlock();
txt1.Text = "2005 Products Shipped";
txt1.FontSize = 20;
txt1.FontWeight = FontWeights.Bold;
Grid.SetRow(txt1, 1);
And finally i added the TextBlock to my Grid:
ContentPanel.Children.Add(txt1);
When i run this code ContentPanel turn out to be equals null, why is that? Shouldn't Caliburn auto bind ContentPanel x:Name="ContentPanel" with the property ContentPanel?
I would appreciate your help in this matter.
My core problem, that i need solved is this:
I got a login page in my app where i show some pictures and text loaded from a server. As you can see below this is done with Image and a TextBlock When that server is offline or the wi-fi simply aren't enabled i want to replace this picture+text with a static image. Aka i want to remove the TextBlock from the StackPanel.
The part where i load and show the stuff form my server works great and looks like this in my xaml:
<StackPanel Orientation="Horizontal" Background="White" DataContext="{Binding FeedItemsAnnounce,Mode=TwoWay}" >
<Image delay:LowProfileImageLoader.UriSource="{Binding ImagePath,Mode=TwoWay}" Margin="5" Width="170" Height="138">
<i:Interaction.Triggers>
<i:EventTrigger
EventName="Tap">
<cm:ActionMessage
MethodName="LoadAnnouncement">
<cm:Parameter Value="{Binding Link}"></cm:Parameter>
</cm:ActionMessage>
</i:EventTrigger>
</i:Interaction.Triggers>
</Image>
<TextBlock Text="{Binding Title}" TextWrapping="Wrap" Width="160" Foreground="Black" FontSize="24" VerticalAlignment="Center" Margin="25,0,0,0"></TextBlock>
<i:Interaction.Triggers>
<i:EventTrigger
EventName="Tap">
<cm:ActionMessage
MethodName="LoadAnnouncement">
<cm:Parameter Value="{Binding Link}"></cm:Parameter>
</cm:ActionMessage>
</i:EventTrigger>
</i:Interaction.Triggers>
So when the server is offline/wifi disabled i want to replace that with. so that the TextBlock is no longer there:
<Image delay:LowProfileImageLoader.UriSource="{Binding ImagePath,Mode=TwoWay}" DataContext="{Binding FeedItemsAdvertisement,Mode=TwoWay}" Margin="0,20,0,39" Width="380" Height="128">
<i:Interaction.Triggers>
<i:EventTrigger
EventName="Tap">
<cm:ActionMessage
MethodName="LoadAdvertisement" >
<cm:Parameter Value="{Binding Link}"></cm:Parameter>
</cm:ActionMessage>
</i:EventTrigger>
</i:Interaction.Triggers>
Is this even possible? If not what would the best semi-solution be?
EDIT 1: I've managed to setup the flow following the instructions from the accepted answer. But my BooleanToVisibilityConverter is not called, though my NotifyOfPropertyChange(() => IsConnectionAvailable); is getting called.
My Property:
private bool _isConnectionAvailable;
public bool IsConnectionAvailable
{
get { return _isConnectionAvailable; }
set
{
if (_isConnectionAvailable != value)
{
_isConnectionAvailable = value;
NotifyOfPropertyChange(() => IsConnectionAvailable);
}
}
}
How i change the bool: This code is called in my constructor for my ViewModel(just as a test to see if it was working):
IsConnectionAvailable = false;
TextBlock (without trigger code cause its the same as previous):
<TextBlock Text="{Binding Title}" Visibility="{Binding IsConnectionAvailable, Converter={StaticResource BoolToVisibility}}" TextWrapping="Wrap" Width="160" Foreground="Black" FontSize="24" VerticalAlignment="Center" Margin="25,0,0,0"></TextBlock>
It's like the Binding IsConnectionAvailable isn't working because i can change the name IsConnectionAvailable in my Xaml to anything and my NotifyOfPropertyChange(() => IsConnectionAvailable); will still be called.
Any ideas?
I can't even do a normal bind Visibility="{Binding Path=IsVisibil,Mode=TwoWay} to a public Visibility IsVisibil property. I've done this in other classes, but even this won't work??
EDIT 2: The problem that course the binding not to work, seems to lie somewhere in this code:
<StackPanel Orientation="Horizontal" Background="White" DataContext="{Binding FeedItemsAnnounce,Mode=TwoWay}" >
<Image delay:LowProfileImageLoader.UriSource="{Binding ImagePath,Mode=TwoWay}" Margin="5" Width="170" Height="138">
<i:Interaction.Triggers>
<i:EventTrigger
EventName="Tap">
<cm:ActionMessage
MethodName="LoadAnnouncement">
<cm:Parameter Value="{Binding Link}"></cm:Parameter>
</cm:ActionMessage>
</i:EventTrigger>
</i:Interaction.Triggers>
</Image>
<TextBlock Text="{Binding Title}" Visibility="{Binding Path=IsVisibil,Mode=TwoWay}" TextWrapping="Wrap" Width="160" Foreground="Black" FontSize="24" VerticalAlignment="Center" Margin="25,0,0,0"></TextBlock>
<i:Interaction.Triggers>
<i:EventTrigger
EventName="Tap">
<cm:ActionMessage
MethodName="LoadAnnouncement">
<cm:Parameter Value="{Binding Link}"></cm:Parameter>
</cm:ActionMessage>
</i:EventTrigger>
</i:Interaction.Triggers>
</StackPanel>
Solution to EDIT 1 and 2: I created an x:Name"Root" at the top of my xaml structure. Then changed the binding to:
ElementName=Root, Path=DataContext.IsVisibil
This is needed because the binding to visibility that I'm trying to set is inside another DataContxt.
This isn't the correct way to use CM, there are a number of areas where you are confusing the model and viewmodel and the binding functionality in CM.
What you are doing currently
You are attempting to have the CM framework look for a property called ContentPanel on your ViewModel and automatically figure out what properties on Grid to bind it to...
This won't work because of a few reasons:
I don't think there is a convention for Grid in CM - it's not really bindable in an obvious way (it's a layout container)
Grid is not a data enabled control - it doesn't know how to consume a collection and display dynamic rows out the box (it's a layout container)
What you are doing doesn't really make any sense (you have an instance of a grid in your UserControl and you have also instantiated a grid in your ViewModel - these are two separate instances of a control - you can't 'bind' them together - that's not how it all works)
CM and Bindings
When you using element name bindings e.g. x:Name with CM, it attempts to find a property on the ViewModel which matches the element name. At this point, depending on the conventions setup for the source control in question, CM will attempt to automagically wire up all the bits and pieces.
There are default conventions contained in ConventionManager which determine which properties to bind when you use element name bindings - e.g. for TextBlock, the Text property on the TextBlock is bound to the target property on the ViewModel.
http://caliburnmicro.codeplex.com/SourceControl/latest#src/Caliburn.Micro.Platform/ConventionManager.cs - look at the class constructor on ConventionManager to see the out of the box conventions - there isn't one for Grid
Once a target property is found, CM will bind it up.
(As an aside: it's worth noting that if the control type is a ContentControl CM will do some composition magic so you can have viewmodels that contain other viewmodels and have a composition all bound up at runtime - great for screens which have multiple sub-windows etc)
The problem you have is that there is no convention setup for Grid out of the box - this is most likely because a Grid in SL/WPF is primarily used for layout, and is not really a 'data container' or data aware in any way (apart from the few dependency properties you can bind to) - i.e. I don't think it's possible to bind to a grid and get a dynamic number of columns/rows without some customisation to the control, hence the omission of any conventions
(think about it - if you are binding a grid to a collection, what should the grid do... add rows or columns? It can't really be supported in a sensible way)
Now bringing it back to SL/WPF for a sec:
Usually if you want a variable list of items you will need to bind to the ItemsSource property of a control which inherits from ItemsControl (or ItemsControl itself).
Many controls do this: if they need to display a dynamic number of items they will usually inherit from ItemsControl.
How does this tie in with CM?
Caliburn Micro knows how to bind up ItemsControl out of the box. This means you can have a property on your ViewModel containing a collection of items and after binding you get a dynamic view of these at runtime
For example - a CM bound ItemsControl might look like this:
<ItemsControl x:Name="TextItems">
<!-- host the items generated by this ItemsControl in a grid -->
<ItemsControl.ItemsPanel>
<ItemsPanelTemplate>
<Grid/>
</ItemsPanelTemplate>
</ItemsControl.ItemsPanel>
<!-- render each bound item using a TextBlock-->
<ItemsControl.ItemTemplate>
<DataTemplate>
<TextBlock Text="{Binding SomeTextualProperty}"/>
</DataTemplate>
</ItemsControl.ItemTemplate>
</ItemsControl>
Now you just need a collection of objects to bind this to - each item in the collection becomes a new item in the control with its DataContext pointing to the bound item. I've made the assumption that you would want each item to be a ViewModel which contained the property SomeTextualProperty - I've defined that here...
// Provides a viewmodel for a textual item
public class TextItemViewModel
{
public string SomeTextualProperty { get; set;}
}
The VM that should contain the list of items would need to have a collection to bind against.
(Note: Since you are adding items to it at runtime you need to tell the UI when the collection changes - ObservableCollection gives you this for free as it implements collection changed notification events)
// This is the viewmodel that contains the list of text items
public class ScreenViewModel
{
public ObservableCollection<TextItemViewModel> TextItems { get; set; }
}
What else I would consider the incorrect approach
Your ViewModels shouldn't know about your View implementation i.e. they shouldn't reference any type of controls unless absolutely necessary (I can't think of a time when I had to put a control in a VM). ViewModels should model the view - but they shouldn't really need to know any specifics about what that view contains - this way they are more easily testable and they are easily reused
If you follow the above approach, you can get away with providing an application which re-uses the set of viewmodels, but provides different views for each. You can try this by replacing ItemsControl with another type of control in the view (as long as it's data aware such as a datagrid) and the VM will still work - the VM is view agnostic.
Your use of Grid in your VM is not ideal because Grid is a visual control, it is not data. Remember that the visuals are your View and the ViewModel should just contain data and events which notify the view of things happening
If I was doing this - the code would look more like the code I posted above.
To sum up
Model the information you wanted to show in a ViewModel (TextItemViewModel)
Add a collection of these objects to the main ViewModel (ScreenViewModel) using a change aware collection such as ObservableCollection
Add/remove items from the collection using the standard add/remove
Bind the ItemsControl in the view using x:Name bindings to the collection on your ScreenViewModel
Adding/removing items in the VM will fire property changed notifications. ItemsControl will watch for these events and update itself accordingly
Addendum
You could get away with just using an ObservableCollection<string> instead of a TextBlockViewModel but it's not clear if you want to add more properties to the items you are binding to the grid (such as IsHeading property for headings which you could then make bold/italic in the view)
If you want to just use strings just modify the DataTemplate to bind directly to the DataContext rather than a property on the DataContext
<ItemsControl x:Name="TextItems">
<!-- host the items generated by this ItemsControl in a grid -->
<ItemsControl.ItemsPanel>
<ItemsPanelTemplate>
<Grid/>
</ItemsPanelTemplate>
</ItemsControl.ItemsPanel>
<!-- render each bound item using a TextBlock-->
<ItemsControl.ItemTemplate>
<DataTemplate>
**<TextBlock Text="{Binding}"/> <!-- Bind direct -->**
</DataTemplate>
</ItemsControl.ItemTemplate>
</ItemsControl>
Edit:
Ok in your case it's quite simple - your ViewModel should simply model the state of the server:
public class LoginPageViewModel
{
public bool IsConnectionAvailable { get; set; } // or whatever your variable should be called
}
Then bind the visibility of the textblock to this using a converter:
<TextBlock Visibility="{Binding IsConnectionAvailable, Converter={StaticResource BooleanToVisibilityConverter}}">
You will need to declare the static resource for the converter somewhere (in the control itself or your main resources dictionary for example)
It looks like there is a converter already defined in System.Windows.Controls somewhere, but in case you can't find it the implementation is pretty simple (you could probably do this a bit better to guard against invalid input but for brevity I've kept it tiny):
public class BooleanToVisibilityConverter : IValueConverter
{
public object Convert(object value, Type targetType, object parameter, CultureInfo culture)
{
return (bool) value ? Visibility.Visible : Visibility.Collapsed;
}
public object ConvertBack(object value, Type targetType, object parameter,CultureInfo culture)
{
throw new NotImplementedException();
}
}
You may also want to change the state from available/unavailable during the views lifecycle, so in that case you probably want to use the property changed events built in to PropertyChangedBase (which Screen also inherits) to let the view know when the property changes
private bool _isConnectionAvailable;
public bool IsConnectionAvailable
{
get { return _isConnectionAvailable; }
set
{
if (_isConnectionAvailable != value)
{
_isConnectionAvailable = value;
NotifyOfPropertyChange(() => IsConnectionAvailable);
}
}
}
Addendum 2
I prefer the terse CM syntax instead of being explicit when binding action messages - so your XAML would change from:
<Image delay:LowProfileImageLoader.UriSource="{Binding ImagePath,Mode=TwoWay}" DataContext="{Binding FeedItemsAdvertisement,Mode=TwoWay}" Margin="0,20,0,39" Width="380" Height="128">
<i:Interaction.Triggers>
<i:EventTrigger
EventName="Tap">
<cm:ActionMessage
MethodName="LoadAdvertisement" >
<cm:Parameter Value="{Binding Link}"></cm:Parameter>
</cm:ActionMessage>
</i:EventTrigger>
</i:Interaction.Triggers>
</Image>
To
<Image delay:LowProfileImageLoader.UriSource="{Binding ImagePath,Mode=TwoWay}" DataContext="{Binding FeedItemsAdvertisement,Mode=TwoWay}" Margin="0,20,0,39" Width="380" Height="128" cal:Message.Attach="[Tap] = [LoadAdvertisement($dataContext.Link)]"></Image>
(actually that might not be right with the $dataContext.Link part ... but then again it might be... see here: http://caliburnmicro.codeplex.com/wikipage?title=All%20About%20Actions&referringTitle=Documentation)
I have a GridView that has several buttons. One of them is defined by the following template:
<DataTemplate x:Name="SubjectItemTemplate">
<Canvas Width="340" Height="170" VerticalAlignment="Top">
<Controls:ThreeImageButton HorizontalAlignment="Center" VerticalAlignment="Top" Margin="0,0,0,0"
NormalStateImageSource="{Binding NormalImage}"
HoverStateImageSource="{Binding HoverImage}"
PressedStateImageSource="{Binding PressedImage}" Command="{Binding Path=NavigateToUnitsPage}"
CommandParameter="{Binding}" Canvas.Left="0" Canvas.Top="0">
</Controls:ThreeImageButton>
</Canvas>
</DataTemplate>
Now I have a custom control as you can see, called ThreeImageButton. The button works fine when I use it on its own. But when I have it in the DataTemplate it won't bind properties to the code behind.
Right now, I have
x:Name="MyThreeImageButton"
in the custom button definition. And I connect to the code-behind like this:
<TextBlock Text="{Binding ElementName=MyThreeImageButton, Path=NormalStateImageSource}"/>
(This is just a test to display the text, in the actual code I would assign an image source to another property that is referred to by an element).
Right now, nothing is displayed in the TextBlock. What is the correct binding syntax I'm supposed to use to reach my properties?
Thanks!
Edit: I am setting the variable in the InitializeComponent function and I am using SetValue on the DependencyProperty.
Edit: Let me add the following information to be more clear
Scenario I:
In DataTemplate for GridView:
<UserControl CustomParameter="Literal Text">
In UserControl:
<TextBlock Text="{Binding CustomParameter}">
in UserControl .cs: this.DataContext = this
works!
Scenario II:
In DataTemplate for GridView:
<UserControl CustomParameter="{Binding ValueFromDataItem">
In UserControl:
<TextBlock Text="{Binding CustomParameter}">
in UserControl .cs: this.DataContext = this
nope!
I see,
So setting up a two-way binding to a custom property in a user control can be tricky because a user control cannot bind to a CLR property. Not only that but setting the data context on a user control has an unexpected effect on the binding inside it.
You can solve these problems with a little slight of code. Basically back your CLR properties with dependency properties and set the data context on a child element instead of the root user control.
Take a look at this sample. Let's pretend you have the following MainPage. That MainPage will eventually use our custom user control. So let's set the stage.
Here's the code-behind:
public sealed partial class MainPage : Page
{
public MainPage()
{
this.InitializeComponent();
this.DataContext = new /* your view model */
{
Title = Guid.NewGuid().ToString(),
};
}
}
In the code above I am simulating a complex view model with a simple anonymous class. It would be silly for you to implement your own like this, but at the same time it is silly for me to build a simple sample with the complete scaffolding. I bring this up only so it does not confuse you - as it could look like I am suggesting this approach in prod.
Here's the XAML:
<Grid Background="{StaticResource ApplicationPageBackgroundThemeBrush}">
<local:MyUserControl Text="{Binding Title}" />
</Grid>
In the XAML above, there is absolutely nothing special. I already have reference to the user control in the local namespace and I simply declare it here.
Okay, now that we have a consumer of the control, it's worth pointing out that in testing developers can mistakenly think that their binding is working because they test with literal values. Literal values bind fine. It's binding from the underlying view model that hick-ups.
Let's say another thing, some developers tend to avoid dependency properties because the require a little more typing. People remember that [kbd]propdp[/kbd] is a handy Visual Studio snippet that stubs out a dependency property for you.
Take a look at this user control. It has two controls, a TextBox and a TextBlock which are there to demonstrate the OneWay and TwoWay functionality of this binding approach. We also implement INotifyPropertyChanged on the user control. For the most part, adding a view model in the case of a user control is overkill because the user control already acts like a view model. It's up to the developer, but it seems dumb to me.
Here's the code behind:
public sealed partial class MyUserControl : UserControl, INotifyPropertyChanged
{
public MyUserControl()
{
this.InitializeComponent();
}
// text property
public string Text
{
get { return (string)GetValue(TextProperty); }
set { SetValueDp(TextProperty, value); }
}
public static readonly DependencyProperty TextProperty =
DependencyProperty.Register("Text", typeof(string), typeof(MyUserControl), null);
// bindable
public event PropertyChangedEventHandler PropertyChanged;
void SetValueDp(DependencyProperty property, object value,
[System.Runtime.CompilerServices.CallerMemberName] String propertyName = null)
{
SetValue(property, value);
if (PropertyChanged != null)
PropertyChanged(this, new PropertyChangedEventArgs(propertyName));
}
}
In the ode above, I have create a "Text" property and backed it with a dependency property. For a matter of reuse I have also implemented SetValueDp() which could be used again and again if I had more than a single property. Even though this demo has but one, I wanted to include this because the repetitive logic should certainly be abstracted out like this.
Here's the XAML:
<Grid Background="Black" DataContext="{Binding ElementName=userControl}">
<StackPanel>
<TextBox Text="{Binding Text, Mode=TwoWay}"
MinHeight="100" Padding="15" FontWeight="Light" FontSize="50" />
<TextBlock Text="{Binding Text}"
MinHeight="100" Padding="15" FontWeight="Light" FontSize="50" />
</StackPanel>
</Grid>
In the XAML above, I do nothing special insofar as binding. The syntax simply binds to the Text property using the Mode appropriate to the control. Just like you would do normally. However, what's worth noticing is that the DataContext is NOT set on the user control. Instead, it is set on the Grid. As a point of fact, any control in the tree other than the user control could be used like this. Just don't set the data context of the user control.
That is it by the way.
I have tested it to make sure it works. Demonstrating both one and two way binding is pretty handy here. I might even turn this into a blog in case other developers want to find it and don't discover this question. Thanks for your question!
Best of luck!
As the comments alluded to, your DataTemplate is placing the datacontext of the items to whatever object you are adding to your list. This is not the same as the surrounding user control's data context. If you want to reference that datacontext's commands, do the following in the DataTemplate's bindings:
{Binding RelativeSource={RelativeSource FindAncestor, AncestorType={x:Type UserControl}}, Path=DataContext.NormalImage}
What this is saying is to go out and find the user control ancestor and use its datacontext and then look for the NormalImage property. If you run into problems, check your output window for binding errors. It is very helpful in finding binding problems.
I have a TreeView that I am binding to an ItemsSource that creates a CheckBox for each item. Here is the xaml:
<TreeView x:Name="ReasonTreeView" Height="Auto" Background="Transparent"
BorderThickness="0" IsTabStop="False"
ItemsSource="{Binding Path=AnswerOptions}">
<TreeView.ItemTemplate>
<HierarchicalDataTemplate DataType="{x:Type QSB:Answer}" ItemsSource="{Binding Path=AnswerOptions}">
<StackPanel Orientation="Horizontal">
<CheckBox Margin="0,5"
IsChecked="{Binding Path=IsSelected}"
IsEnabled="{Binding Path=Value,
Converter={StaticResource ReasonValueToEnabledConverter}}"
Visibility="{Binding Path=AnswerOptions,
Converter={StaticResource ParentNodeVisConverter}}" />
</StackPanel>
</HierarchicalDataTemplate>
</TreeView.ItemTemplate>
In my application I then create multiple instances of these. Depending on the instance of the TreeView, certain CheckBoxes need to be disabled so the user can not select them, however I'm uncertain of how I can access the individual items in the HierarchicalDataTemplate in the code.
After looking around for a while the only thing I can think of is to build the whole TreeView in the code behind instead of the xaml, but I would rather not have to resort to that. Is there anything else that I can do?
To help clarify my point and for illustrative purposes, this is essentially what I want to be able to do (in pseudocode): ReasonTreeView.ItemsSource[5].IsEnabled = false;
Which would disable the CheckBox (and any other controls in that HierarchicalDataTemplateItem) at index 5 of the TreeView's ItemsSource
Let me know if more information is needed
I meant that binding on the checkbox's isenabled property Path=Value. That Value member has to be bool and implement INotifyPropertyChanged then you can control IsEnabled from your model. Dont forget to add Mode=Twoway to your binding
Instead of accessing the CheckBox through Control.ItemsSource property you should make the change in your underlying collection (that is itemssource of your control). After making the change notify the View (your Control) that data has been changed so update the control.
Implement INotifyPropertyChanged in your underlying class and after changing the Property (which is responsible for Enabled/Disabled) value Notify the View.
If you are not familiar with concepts of Data Binding and INotifyPropertyChanged, I would suggest you to read some basic tutorials about it. It is one of the major feature of WPF which makes life very easy for doing things like yours
My problem is similar to the one described in this question:
WPF MVVM Button Control Binding in DataTemplate
Here is my XAML:
<Window x:Class="MissileSharp.Launcher.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
Title="MissileSharp Launcher" Height="350" Width="525">
<Grid>
<!-- when I put the button here (outside the list), the binding works -->
<!--<Button Content="test" Command="{Binding Path=FireCommand}" />-->
<ListBox ItemsSource="{Binding CommandSets}">
<ListBox.ItemTemplate>
<DataTemplate>
<!-- I need the button here (inside the list), and here the binding does NOT work -->
<Button Content="{Binding}" Command="{Binding Path=FireCommand}" />
</DataTemplate>
</ListBox.ItemTemplate>
</ListBox>
</Grid>
</Window>
It's just a ListBox, bound to an ObservableCollection<string> named CommandSets (which is in the ViewModel).
This binding works (it displays a button for each item in the collection).
Now I want to bind the button to a command (FireCommand), which is also in the ViewModel.
Here's the relevant part of the ViewModel:
public class MainWindowViewModel : INotifyPropertyChanged
{
public ICommand FireCommand { get; set; }
public ObservableCollection<string> CommandSets { get; set; }
public MainWindowViewModel()
{
this.FireCommand = new RelayCommand(new Action<object>(this.FireMissile));
}
private void FireMissile(Object obj)
{
System.Windows.MessageBox.Show("fire");
}
}
The binding of this button does NOT work.
From what I've understood from the question I linked above, the binding doesn't work because:
(correct me if I'm wrong)
The button is inside the ListBox, so it only "knows" the binding of the ListBox (the ObservableCollection, in this case), but not the binding of the main window
I'm trying to bind to a command in the main ViewModel of the main window (which the button doesn't "know")
The command itself is definitely correct, because when I put the button outside the ListBox (see the XAML above for an example), the binding works and the command is executed.
Apparently, I "just" need to tell the button to bind to the main ViewModel of the form.
But I'm not able to figure out the right XAML syntax.
I tried several approaches that I found after some googling, but none of them worked for me:
<Button Content="{Binding}" Command="{Binding RelativeSource={RelativeSource Window}, Path=DataContext.FireCommand}" />
<Button Content="{Binding}" Command="{Binding Path=FireCommand, Source={StaticResource MainWindow}}" />
<Button Content="{Binding}" Command="{Binding Path=FireCommand, RelativeSource={RelativeSource AncestorType={x:Type Window}}}" />
Could someone please:
give me the proper XAML to bind the button inside the ListBox to a command in the form's MainViewModel?
point me to a link where this advanced binding stuff is explained in a way that a WPF/MVVM beginner can understand?
I'm feeling like I'm just copying and pasting arcane XAML incantations, and so far I don't have any clue (and can't find any good documentation) how I would figure out by myself in which cases I'd need RelativeSource or StaticResource or whatever instead of a "normal" binding.
It's:
{Binding DataContext.FireCommand,
RelativeSource={RelativeSource AncestorType=ListBox}}
No need to walk up to the root unless you actually change the DataContext along the way, but as the ListBox seems to bind to a property on the main VM this should be enough.
The only thing i recommend reading is the Data Binding Overview, and the Binding class documentation (including its properties).
Also here is a short explanation on how bindings are constructed: A binding consists of a source and a Path relative to that source, by default the source is the current DataContext. Sources that can be set explicitly are: Source, ElementName & RelativeSource. Setting any of those will override the DataContext as source.
So if you use a source like RelativeSource and want to access something in the DataContext on that level the DataContext needs to appear in the Path.
This may be considered unrelated by most, but this search is only 1 of 3 results that you'll find searching for data binding commands to controls inside a data template--as it relates to Xamarin Forms. So, maybe it'll help someone now-a-days.
Like me you may wonder how to bind commands inside a BindableLayout. Credit jesulink2514 for answering this at Xamarin Forums, where it's probably overlooked by many because of all the comments. Here's his solution, but I'm including the link below:
<ContenPage x:Name="MainPage">
<ListView Grid.Row="1"
ItemsSource="{Binding Customers}"
VerticalOptions="Fill"
x:Name="ListviewCustomer">
<ListView.ItemTemplate>
<DataTemplate>
<Label Text="{Binding Property}"/>
<Button Command="{Binding BindingContext.ItemCommand, Source={x:Reference MainPage}}"
CommandParameter="{Binding .}">Click me</Button>
</DataTemplate>
</ListView.ItemTemplate>
</ListView>
</ContentPage>
https://forums.xamarin.com/discussion/comment/217355/#Comment_217355