I'm struggling to understand what is going on with the ContextMenu. I know it is rendered as a separate window, with a separate visual tree, so we can't use relative binding to bind a command exposed as a property of the user control. e.g. the following does not work:
<MenuItem Command="{Binding RelativeSource={RelativeSource AncestorType=UserControl}, Path=TestCommand}" Header="Test" />
But, if you set the data context of the user control to a view model that exposes a command as a property, the following will work:
<MenuItem Command="{Binding TestCommand}" Header="Test" />
What I don't understand is, how is the ContextMenu inheriting the value of the DataContext if it is not part of the visual tree. I would expect both of these examples to behave the same (i.e. both work or both fail).
The second binding works because of so called "inheritance context". You can read about it here: http://blogs.msdn.com/b/nickkramer/archive/2006/08/18/705116.aspx. Basically this is a kind of special case where some properties inherit data context of the owner object. Thus, for example, the inheritance context always works on properties of type Freezable (another interesting article about Freezables: http://drwpf.com/blog/category/freezables/).
Actually the article says that the inheritance context doesn't work on ContextMenu, but in version 4 they added it, so it actually works now as you've shown it in your example.
Related
I'm using MVVM and custom ICommand objects are provided by ViewModel layer. One ViewModel object at the same time can be attached via DataContext property to many View objects (windows, pages, etc). In ICommand.CanExecute() I want to check absence of validation errors for some controls in View (which attached to ViewModel props, significant for a particular VM command). One ViewModel can provide many commands, each of them has own set of controls for errors validation verification. So, pseudo-XAML is:
<Button.CommandParameter>
<x:Array Type="sys_win:DependencyObject">
<sys_win:DependencyObject>
<reference_to_textbox_or_other_control/>
</sys_win:DependencyObject>
<sys_win:DependencyObject>
<reference_to_textbox_or_other_control/>
</sys_win:DependencyObject>
</x:Array>
</Button.CommandParameter>
The second problem is that the particular command may be invoked by control, which itself is the part of the DataTemplate for collection item (in my case - part of ListBoxItem data template). My templated listbox item has two text boxes (binded to two props of corresponding ViewModel) and button, which invoke the ViewModel command. So, in command CanExecute() I need to check for validation errors for some window controls & two text boxes, which belongs to this listitem, not other items. The code below works fine if I want to pass ListBoxItem.IsSelected property as CommandParameter:
<Button DataContext="{Binding}"
Command="{Binding Path=SwitchCommand}"
CommandParameter="{Binding Path=IsSelected, RelativeSource={
RelativeSource
Mode=FindAncestor,
AncestorType={x:Type ListBoxItem}}}"/>
But how can I pass whole (DependencyObject)ListBoxItem as the CommandParameter? And how this ListBoxItem, passed via {Binding RelativeSource} can be mixed with other current window controls in the first code example?
I'm very sorry, but how can I add the references to controls in xaml?
<Button.CommandParameter>
<x:Array Type="sys_win:DependencyObject">
<sys_win:DependencyObject>
<reference_to_textbox_or_other_control/>
</sys_win:DependencyObject>
<sys_win:DependencyObject>
<reference_to_textbox_or_other_control/>
</sys_win:DependencyObject>
</x:Array>
</Button.CommandParameter>
Just use a binding with no Path :
<Button DataContext="{Binding}"
Command="{Binding Path=SwitchCommand}"
CommandParameter="{Binding RelativeSource=
{RelativeSource
Mode=FindAncestor,
AncestorType={x:Type ListBoxItem}}}"/>
I'm not sure if I'm reading your example correctly, but it seems to violate a bit of the MVVM principle. (My apologies if I read it incorrectly).
The idea behind MVVM is to decouple the viewmodel from any dependency on a XAML / View entity. You're breaking that by having the CommandParameter dependent on the usercontrol. What I would do is create state properties in the ViewModel and bind the usercontrol validations to those states, then in CanExecute you can test the values of those properties rather than trying to bind to a usercontrol.
I thought {Binding DataContext.foo} and {Binding foo} were identical until I came across an issue binding a selection changed event from a ComboBox to a command in my ViewModel.
I was doing it like so...
<i:EventTrigger EventName="SelectionChanged">
<cmd:EventToCommand Command="{Binding DataContext.TestCommand, RelativeSource={RelativeSource FindAncestor, AncestorType={x:Type Page}}}" />
</i:EventTrigger>
Using DataContext.TestCommand works while just specifying TestCommand appears to fail. I've never encountered a difference between the two can anyone explain it?
They're binding to two subtly different things:
"Binding DataContext.TestCommand" is binding to the TestCommand property of the Datacontext (presumably the context defines that property) of your Page.
"Binding TestCommand" is binding to the TestCommand property of the Page itself, which in this case probably doesn't exist, which is why it doesn't work.
There's a neat program called WPF Snoop that you can use to inspect the bindings whilst things are running (often helps me make sense of things when I'm, stuck).
Almost each element in visual tree (View) is linked to the data layer (ViewModel) per DataContext property. Of course the data layer tree is much more simpler because most controls just inherit from their parent.
By default a Binding is looking for the Path in data layer. But if you specify a RelativeSource (like RelativeSource.Self or with AncestorType) or an ElementName the Binding switches to visual layer and searches in controls for the bound property. With DataContext you can go back to data layer.
In my MVVM Light application I do a search in a customer list. The search narrows the customer list which are displayed in a master/detail view with a datagrid (the master CustomerSearchResultView) and a separately defined usercontrol with FirstName, Lastname, Address etc, etc (the detail - CustomerSearchDetailView). Here are the main content of the master/detail view:
<StackPanel MinWidth="150" >
<TextBlock Text="Customer Search Result List" />
<Grid>
<DataGrid Name="CustomerList" ItemsSource="{Binding SearchResult}" SelectedItem="{Binding SelectedRow, Mode=TwoWay}" >
.....
</DataGrid>
</Grid>
<Grid Grid.Column="2">
<TextBlock Text="Customer Details" Style="{StaticResource Heading2}" Margin="30,-23,0,0"/>
<content:CustomerSearchDetail DataContext="{Binding SelectedRow}" />
</Grid>
</Grid>
</StackPanel>
Both have their corresponding ViewModels. Please remark the DC for the CustomerSearchDetail, SelectedRow - it is a property on the CustomerSearchResultViewModel and is defined like this:
private Customer _selectedRow;
...
public Customer SelectedRow
{
get { return _selectedRow; }
set
{
_selectedRow = value;
RaisePropertyChanged("SelectedRow");
}
}
...
Because of this I have not defined any DC on the CustomerSearchDetailView - it is set in the Binding on the "Master" view (as shown above) and it seems to work ok.
In my Model folder I have created the Customer class that is in use here. It implements ObservableObject and IDataErrorInfo and have public properties that raisepropertychanged events.
I run the application and everything seems to be ok. Note: the ViewModel for the CustomerSearchDetailView (that is CustomerSearchDetailViewModel.cs) is at this stage just an empty shell and not in use (as far as I can see ... the constructor is never accessed)
Now I want to add Save/Update functionality to my customer in the detail view. Ok, I add a Save button to the CustomerSearchDetailView like this:
<Button Content="Save" Command="{Binding Path = SaveCommand}" Width="80" Margin="0,0,15,0"/>
I create my "SaveCommand" RelayCommand property in my CustomerSearchDetailViewModel - but it is never accessed.
Hmmmmm ... well after some googling back and forth I come up with this:
<Button Content="Save" Command="{Binding Source={StaticResource MyCustDetails}, Path = SaveCommand}" Width="80" Margin="0,0,15,0"/>
I defined the "MyCustDetails" as a resource in this view pointing to the CustomerSearchDetailViewModel. And voila! I now hit the method when debugging ... but alas, my customer was of course "null". (In fact I spent 2 hours implementing the CommandParameter here and binding it to the "SelectedRow" Property on the master view - but the customer was still "null").
More googling and searching for mvvm examples, and I implemented my "SaveCommand" on the Customer class (the model object). And guess what? The edited customer got passed along - I could send it to my EF layer and everything seems to be ok ....
And - If you are still with me - here comes my questions:
1.) I would like - and thought that was the "proper MVVM way" of doing things - to have my CRUD/Repository accessing in the ViewModel. How can I do that in my scenario?
2.) Now that I have my CRUD in place via the Model class (Customer) - should i bother with question 1? In fact I have deleted the CustomerSearchDetailViewModel and everything runs ok. I feel I have invented the View - Model (MV) framework ... :-P
I would very much like feedback on this - and I apologize for this "wall of text".
Assuming DC means DataContext
Just my opinion:
First question is are you doing anything special with SelectedRow in CustomerSearchResultViewModel?
If the answer is no, just get rid of that property and have your CustomSearchDetailView bind directly to the DataGrid using {Binding ElementName=CustomerList, Path=SelectedItem}
Now your Save / update Commands need to be used by Button's in CustomerSearchDetailView. So instantly I'd be inclined to using a separate VM for that View and have these Command's defined there.
Now you mentioned these Commands were not accessed. Well the answer for that is because in your program you're never actually creating the CustomerSearchDetailViewModel.
Normal operation is your View's DataContext is it's VM(If it requires one. In your case you do imo cos you need it to hold your Commands)
looking at your code I'd guess your using MVVM Light. So in ViewModelLocator you have your Main property and in your Main View, you got the DataContext set using that Main property and Source={StaticResource Locator} where Locator is the ViewModelLocator created in App.xaml Resources. This thereby creates that ViewModel for that view defining that DataContext. You can ofcourse do the same in code-behind but let's not go off topic.
So in your case you got the DataContext set as SelectedRow which is of type Customer and Binding's are resolved using DataContext and that's why when your command's are defined in Customer it works fine but when it's in the VM it did not.
So why did it work when you had the commands in your VM and used
<Button Content="Save" Command="{Binding Source={StaticResource MyCustDetails}, Path = SaveCommand}" Width="80" Margin="0,0,15,0"/>
^^ That worked because the DataContext was not used since Source has been specified explicitly. and where-ever MyCustDetails was defined in resources, there the VM got created.
So it worked what's wrong with that?
Well it's quite a big mess. Also just like you mentioned Customer details in that VM was null. Well I hope you can guess why that was by now. It's because your VM was created in resources via x:Key="MyCustDetails" but nothing in it was ever used or set apart from when the Binding's referred to it explicitly
In this system we got commands that refer either to the Model which is plain wrong or the VM which is created as a resource just for this purpose. The DataContext is heavily linked to the "SearchResults" view making it not so easy for future extensions or layout updates.
If we keep the View <-> VM a 1 <-> 1 relattion we can avoid all this confusion. So in summary we can answer both your question's together. While this works, please don't let your code be like this and tweak it to better help expansion for future and comply with some basic guidelines.
So how do we do that?
Approach 1:
In your CustomerSearchDetail View, add a DependencyProperty of type Customer lets call this say SelectedCustomer.
Now replace DataContext="{Binding SelectedRow}" with SelectedCustomer="{Binding SelectedRow}" in CustomerSearchResultView
Now set the DataContext of your CustomerSerachDetailView as it's VM similar to how CustomerSerachResultsView links to it's VM(guessing through DataContext Binding in xaml using the ViewModelLocator)
Now you can have your commands in Button's of CustomerSerachDetailView just as <Button Command="{Binding SaveCommand}" ...
Finally because SelectedRow is no longer the DataContext of the CustomerSerachDetailsView, your Bindings for FirstName, Lastname, Address will all appear to stop working.
We got plenty of options to address this.
First is to in each Binding use a RelativeSource FindAncestor binding pointing to CustomerSerachDetailsView and there via the CurrentCustomer DP(DependencyProperty) we created before get the appropriate field.
eg:
<TextBlock Text={Binding RelativeSource={RelativeSource FindAncestor, AncestorType={x:Type local:CustomerDetailsView}}, Path=CurrentCustomer.FirstName}" />
now if you have multiple properties this is gonna soon start getting annoying to type. So then pick a common ancestor(say 3 of these TextBlocks are grouped under a StackPanel) and apply it's DataContext as the CurrentCustomer element via a similar binding to ^^. Now the StackPanel's children DataContext will be the Customer element so in each of their binding's you don't have to do the whole RelativeSource thing and can just mention {Binding Path=FirstName} and so on.
That's it. Now you got two view's with their own respective VM and a Model(Customer) and each have their respective tasks.
Great, we done? err not quite yet.
While Approach 1 is better than what we started with it's still just "meh". We could do better.
Approach 2
MVVMLight has a Messenger class that will allow you to communicate between different classes in a weak dependent format. You need to look into this if you haven't already.
So what do we do with Messenger?
pretty simple:
In the setter of SelectedRow in CustomerSearchResultsViewModel we'll send a message with the new incoming value to CustomerSearchDetailsViewModel.
Now in CustomerSearchResultsViewModel we'll add a property CurrentCustomer and assign it this incoming value.
In the CustomerSerachDetailsView we no longer create a DP. Which means we no longer set SelectedRow to anything(DataContext or DP) in the CustomerSerachDetailsView from CustomerSearchResultsView ( sweet less work :) )
As for the way we assign DataContext of CustomerSerachDetailsView or way we bind the Button.Command - They remain same as Approach 1
Finally the actual "FirstName" and so Binding's. Well now CurrentCustomer is a property of the CustomerSearchDetailsViewModel. So binding to it just like how the Button bind's to it's commands
^^ this works fine now cos DataContext for the TextBlock is the VM and the property CurrentCustomer exists in it.
This dialog makes no sense to me
http://img576.imageshack.us/img576/4223/50709706.gif
And I'm having trouble finding good tutorials on it. Most of the examples aren't detailed enough, or do stuff via code, but I'd like to take advantage of the IDE as much as possible.
Whats the difference between ItemsSource and DataContext?
I'd like to bind it to just a List for starters. I don't need SQL or databases or anything fancy. Where would I declare my list? In MainWindow.xaml.cs? How do I get it to appear in that dialog?
Think of "DataContext" as the default value for "Source" in a binding.
When you create a binding, you can specify the path and source, like this (I'll use TextBox as an example):
<TextBox Text="{Binding Path=Foo,Source={StaticResource Bar}}" />
So my TextBox.Text property is bound to a Foo property on an object called Bar (a resource somewhere in the application).
However, if you have a whole bunch of things that you want to bind to properties on Bar, it's easier to set Bar as the DataContext of the parent container. A Binding without a Source will just use the DataContext by default, and DataContext flows through to child controls from the parent. So:
<StackPanel DataContext="{StaticResource Bar}">
<TextBox Text="{Binding Path=Foo}" />
<TextBox Text="{Binding Path=Fizz}" />
<TextBox Text="{Binding Path=Buzz}" />
</StackPanel>
All of the TextBoxes are still binding to properties on Bar, but they're doing it without setting it as a Source explicitly.
So let's have another look at the dialog you posted. It's giving you several options for the "source" of the ItemsSource binding. When you choose "DataContext", you're telling Visual Studio that the ItemsControl doesn't need to know the source - it'll pick it up from the DataContext of the parent container (maybe even the Window itself).
If you chose one of the other options (ElementName, RelativeSource or StaticResource) then you'd be setting the binding's source explicitly for that ItemsControl.
Once you've told it that it's binding to the DataContext, you'll need to drop into the "Path" section of the dialog and tell it which property to bind the items of the control to. In the end, the markup would look something like this (assuming it's a ListBox):
<ListBox ItemsSource="{Binding Path=Foos}" />
So the items in the ListBox are coming from a property called "Foos", and that property is on an object that's set in the DataContext somewhere higher in the logical tree (perhaps on the Window itself).
You rarely need to use the data context of a control outside of the control. The most common use case for setting DataContext(DataContext = this;) is within UserControl's code-behind to make all controls within the UserControl to bind to the control's properties.
When you use a ListBox, setting ItemsSource is sufficient, unless you are doing something funky.
This is a pretty good walkthrough: http://windowsclient.net/learn/video.aspx?v=315275
Specifically, you need to set the DataContext first to tell it where to look for the ItemsSource. The easiest way is to set this on the Window through the XAML:
<Window.DataContext>
<controllers:DownloadManager />
</Window.DataContext>
I have several MenuItems whose Commands are bound to my ViewModel. Until today, all of them execute properly.
Now I have added a MenuItem whose ItemsSource is bound to an ObservableCollection. The point of this MenuItem is to enumerate a list of plugins so that the name of all plugins show up. Then when the user clicks on a plugin name, it should call a function to display properties for audio filters.
In my current implementation, which doesn't work, I tried to databind like this:
<MenuItem Header="Filters" ItemsSource="{Binding FilterPluginNames}">
<MenuItem.ItemContainerStyle>
<Style>
<Setter Property="MenuItem.Command" Value="{Binding ShowFilterDialogCommand}" />
</Style>
</MenuItem.ItemContainerStyle>
</MenuItem>
The problem is that I get a BindingExpression path error because it's trying to use a String as the MenuItem's DataContext.
This leads me to believe that the DataContext for a MenuItem's MenuItems is automatically set to type of objects in the ItemsSource. Is this true?
If I need to change the DataContext, then I'd like to change it to the ViewModel that handles all of my other Commands. But if I do that, how in the world am I able to tell which plugin I want to display filter properties for? I'd need to pass in a CommandParameter at the very least, but binding this value to the filter name isn't my most favorite option. Are there any other ways to do this?
If the DataContext is indeed automatically set to the object type in the ObservableCollection, then I'd rather just call my interface method ShowFilterProperties() directly. I bet that I can't do this without Command binding. If that is the case, how do you all deal with this sort of application? Do you make all of the plugins expose a command handler, which will then show the dialog?
EDIT -- I modified my code to change the ObservableCollection type, and sure enough, WPF wants to databind to the type T. So I guess one option is to have the plugin expose the ICommand, but I don't know if this is a weird approach or not?
EDIT -- ok, I just learned something new. Interfaces can't have fields, so is it not possible to databind with plugins, period?
You are likely not quite binding like you think you are. You might want to just put some diagnostics on your bindings and see what object they are binding to. Here is a good link for debugging bindings:
http://www.beacosta.com/blog/?p=52
Here's a sample:
<Window …
xmlns:diagnostics="clr-namespace:System.Diagnostics;assembly=WindowsBase"
/>
<TextBlock Text="{Binding Path=Caption, diagnostics:PresentationTraceSources.TraceLevel=High}" … />
I think your approach is correct... it probably just needs to be debugged a little.