I have a dependency property on a user control called "RefreshCommand" (ICommand type) that I want to bind to a Command in my main window.
If I write this in code, it works...
MainToolbar.RefreshCommand = (ICommand)this.CommandBindings[0].Command;
.. but how can I express that in XAML?
I would additionally like to refer to the command by its name as opposed to its index.
Thanks,
you can bind in XAML to other XAML Element through e.g.
The Element name:
RefreshCommand="{Binding ElementName=window, Path=CommandBindings[0].Command}"
On Properties of "yourself"
RefreshCommand="{Binding RelativeSource={x:Static RelativeSource.Self}, Path=CommandBindings[0].Command}"
Upwards along the tree with AncestorType
RefreshCommand="{Binding Path=CommandBindings[0].Command, RelativeSource={RelativeSource AncestorType={x:Type Window}}}"
If the toolbar is a child from window, I suppose the third should work well.
I would recommend doing this then.
3 Classes
MainWindow.xaml (Window)
MainViewModel.cs (Class)
UserControl.xaml (UserControl)
Set the data context of the MainWindow to the ViewModel. The UserControl will inheret this data context unless you change it explicitly. Set up the controls on the user control / main window to bind to relay commands on the Main View Model.
This will give you the context you need without hard coding indexes (xxx.[0]) and still maintain a degree of separation.
Related
I need to click on an TreeViewItem and open an dialog window with the data of that TreeViewItem, later based on that data I will run another command.
My actual problem is: I can't click on it because treeviewitem doesn't have the command property.
My scenario: I have 2 Models with 2 properties that will be used to create my TreeViewItems. On my ViewModel I create them, and organize them inside each other based on their properties and then store them inside One Collection.
Here's my xaml to bind the elements:
<TreeView ItemsSource="{Binding Local}">
<TreeView.DataContext>
<data:ItemViewModel/>
</TreeView.DataContext>
</Treeview>
//In my "Local" property I have 3 TreeViewItems with other items inside them which I want to execute the commands
I couldn't find a way to create a datatemplate for that specific scenario. Even tried to create a datatemplate with a Hyperlink (thought it would be a temporary solution) inside it, but would not execute any command.
MVVM pattern is to use one of the many "Event to Command" implementations out there. You basically bind the "Event to Command" object to the Click event and then a command in your VM gets bound to the "Event to Command" object and it gets mapped behind the scenes for you and handles all the enabled / disabled stuff.
You can see an example of one of the implementations here:
WPF Binding UI events to commands in ViewModel
You should be binding to a collection whose objects have a collection as a public property and templating by type into whatever you want to see in each treeviewitem.
Like this sample:
https://learn.microsoft.com/en-us/dotnet/api/system.windows.hierarchicaldatatemplate?view=netframework-4.7.2
Technically, you could have a button whose template was a textblock or something and that would then have the behaviour of a button such as click and command.
But I'd be more likely to use an inputbinding.
Here's an example:
<DataTemplate DataType="{x:Type local:LineTerrainVM}">
<Grid Background="Transparent">
<Grid.InputBindings>
<MouseBinding MouseAction="RightClick" Command="{Binding FixLineCommand}"/>
</Grid.InputBindings>
You can give that a commandparameter="{Binding .}" and it'll pass the viewmodel as a parameter.
You could also use relativesource to the datacontext of the treeview to get at a parent viewmodel and define a command in that to do your stuff.
Since that stuff you want to do is a view responsibility you could rely on routed events without "breaking" mvvm. A click in any treeviewitem could be handled at the treeview level and use the originalsource to get to the treeviewitem clicked. Then grab it's datacontext for the viewmodel of whatever that is.
Rough idea:
<TreeView Name="tv" ItemsSource="{Binding Families}" FrameworkElement.PreviewMouseLeftButtonDown="Tv_PreviewMouseLeftButtonDown"
And the handler:
private void Tv_PreviewMouseLeftButtonDown(object sender, MouseButtonEventArgs e)
{
var vm = ((FrameworkElement)e.OriginalSource).DataContext;
// do something with viewmodel
}
You could then do something like new up your dialog window, set it's datacontext to that viewmodel you just got and showdialog it.
I have ListView control in my view with it's own viewmodel A. I have made a seperate UserControl to use as ListViewItem, because it's styling takes a lot of space. In this ListViewItem I have a button, which is binded to viewmodel A and it works fine.
As the context menu has it's own visual tree and cannot bind via ancestor, I have used binding proxy, to solve this issue. I have tweaked it a little so it worked for my particular case, because if it just used {Binding} it would bind to item's model, not listview's viewmodel.
<helpers:BindingProxy x:Key="proxy" Data="{Binding RelativeSource={RelativeSource Mode=FindAncestor, AncestorType=ListView}}"/>
To check if the binding is correct I've used a converter as a way just to have a breakpoint to check source. Everything was good and I was getting my viewmodel right there.
Now, when I try to bind to this in my context menu
<UserControl.ContextMenu>
<ContextMenu>
<MenuItem Header="Open"
Command="{Binding DataContext.OpenChatCommand, Source={StaticResource proxy}, Converter={StaticResource DataBindingDebugConverter}}"
CommandParameter="{Binding}"/>
</ContextMenu>
</UserControl.ContextMenu>
The command never gets called. I added converter to see if something is wrong, but it turns out, I never get to my converter, which in turn means this code never gets executed.
Anyone with any ideas why this is happening and how to solve this is welcome.
I think it's the compiler malfunctioning though
I just did a brief readup on that "binding proxy" you mentioned, but as far as I know, DataGridTextColumn is in the same Visual Tree as its DataGrid, just that its DataContext is bound to its data.
For ContextMenu, it's totally different. This one really has a separate tree from its parent. There is no point in using a proxy object in resources, because it is from a different visual tree. When you use StaticResource, WPF will search upwards through its visual tree, level by level, inside those elements' Resource property (which is a ResourceDictionary).
One way is to make that proxy into a singleton, and use Source={x:Static helpers:BindingProxy.Instance}. Of course using this means that your proxy can only be used by a single View, or else something unexpected would happen.
The other way is to make use of PlacementTarget property of the ContextMenu.
<ContextMenu DataContext="{Binding Path=PlacementTarget.DataContext,
RelativeSource={RelativeSource Self}}">
This is the preferred way, but you need to make sure the parent's DataContext is really the VM that you need.
Edit
There is no super elegant way to do it the MVVM way. The best way is probably through the use of Tag property.
<ContextMenu DataContext="{Binding Path=PlacementTarget.Tag,
RelativeSource={RelativeSource Self}}">
ListView Control:
<MyControl:MyListViewItem .... Tag="{Binding Path=DataContext, RelativeSource={RelativeSource AncestorType={x:Type MyControl:MyListViewView}}}"}" ...>
I have a button in a UserControl that I want to call a method in another class (which happens to be my main window's view model).
<ToggleButton cal:Message.Attach="[Event Click] = [Action ToggleWatch]">
The user control's DataContext is tied to a DataTemplate. This makes everything beautiful, unless I run into cases like this where implementing the ToggleWatch method in the DataTemplate class doesn't make much sense, since the DataTemplate should only contain data.
Is the best way to get around this is by setting the DataContext of this control to the MainWindowViewModel? That workaround fails when I want to bind a value from the DataTemplate to the same button, since the DataContext modification will then make it look for the value in the MainWindowViewModel.
In case I've over complicated the question, I will summarise. I have a UserControl whose DataContext is a seperate DataTemplate. I want to call a method from a button when it is clicked, but I want the method seperated from the DataTemplate. I want the method in a completely different class. What is the best way to solve this?
Here is how I'm setting the DataTemplate:
<WrapPanel>
<ItemsControl ItemsSource="{Binding Devices}">
<ItemsControl.ItemTemplate>
<DataTemplate>
<local:DeviceCleanerBox/>
...
UserControl must exist as child of your MainWindow and if i am right you must have set DataContext of MainWindow to be MainWindowViewModel.
So, what you can do is declare ICommand in MainWindowViewModel and bind to button's Command using RelativeSource to search for window's DataContext:
<ToggleButton Command="{Binding DataContext.CommandName,
RelativeSource={RelativeSource Mode=FindAncestor,
AncestorType=Window}}"/>
My application looks like the following
The black is my MainWindow, the red is a tab control and the yellow is a UserControl.
The UserControl has many Dependency Properties defined and they bind to the DataContext (Which is set in the MainWindow's code behind, using this.DataContext = this).
To bind my UserControl to the same DataContext as my MainWindow, in my UserControl xaml I have the following
DataContext="{Binding RelativeSource={RelativeSource Mode=FindAncestor, AncestorLevel=1,AncestorType=Window}}"
This works great, and when I interact with my UserControl, due to the two way binding, it updates the Properties of my MainWindow, which in turn updates my TabControl!
The issue is, my UserControl now has some extra functionality and as such, needs to bind to the UserControl's code behind (such as values for the GUI).
This is where I'm stuck. I can't bind from my UserControl to my code behind because I've already created a DataContext.
I know I could use the WinForms approach, and name each control with x:Name="MyControl" like
MyControl.Text = "This value";
or
MyControl.DataContext = this;
Yeuk I think!!
My question is, is this the only way to go, or can I still use binding.
First of all you don't need to manually set DataContext on UserControl. DataContext is an inheritable property so it will inherit DataContext from its parent unless you have explicitly set it.
Get rid of DataContext="{Binding RelativeSource={RelativeSource Mode=FindAncestor, AncestorLevel=1,AncestorType=Window}}" from your UserControl.
And now, in case you want to bind to code behind for some controls in your UserControl, you can bind using RelativeSource or can set DataContext on control:
DataContext="{Binding RelativeSource={RelativeSource Mode=FindAncestor,
AncestorType=UserControl}}"
If controls can be clubbed together under one panel, set DataContext on parent panel say Grid and child controls will inherit from it:
<Grid DataContext="{Binding RelativeSource={RelativeSource Mode=FindAncestor,
AncestorType=UserControl}}">
..Child Controls here will inherit DataContext
</Grid>
And to answer your question:
MyControl.DataContext = this; <-- Is this possible
Yes, it is possible like i mentioned above.
You can just use another RelativeSource Binding as you have for the MainWindow properties... to access the properties defined in the UserControl, try the following XAML in your UserControl:
<TextBlock Text="{Binding UserControlProperty, RelativeSource={RelativeSource
AncestorType={x:Type YourXmlNamespacePrefix:YourUserControl}}}" />
Obviously, you'll need to update YourXmlNamespacePrefix:YourUserControl to a valid XML Namespace and control type to get this to work.
I'm not saying either that you should set the DataContext anywhere, or change any properties. This is a RelativeSource Binding... you do not need to set any DataContext to make it work. I thought you would have known that seeing as you're already using one. Just try the example out.
I have a MainWindowView(Window) with a Canvas in which I add my Views(UserControls).
The Canvas in the MainWindow is a Custom Canvas derived from Canvas so that Views inside this can be moved here and there, and can bringtofront or sendback.
I add Views to MainWindowView's Canvas by Binding a Command to a Button. So when I click a Button, a View gets added in the Canvas.
But, my problem is, I want to add another View to the same Canvas of MainViewModel from the ViewModel of my Views which are already in the Canvas of MainViewModel.
Since the ObservableCollection, which I used to bind Canvas, is in MainViewModel, I can add View from the MainViewModel only.
When I try to use the MainViewModel from other ViewModel, I have to create a new object of it, which makes the old View in the Canvas being replaced by the new one.
Is there a solution for this. If not what's the use of using MVVM framework.
Please help...
Use Calibrum Micro, which will help you in this
Am I getting this right : Your controls' DataContext is a Different one than that of the Window and you need to access it from there?
Basically that could have been avoided by design (use Dependency Injection to get the MainViewModel instance into the Command), but in fact there is a WPF/MVVM friendly way of solving this:
Use Commands to Add Controls to the MainViewModels ObservableCollection
<Button Command="{Binding Path=CreateViewCommand}" CommandParameter="{Binding}" />
From your Control (what you called View), you must use Ancestor Binding:
<Button Command="{Binding Path=DataContext.CreateViewCommand, RelativeSource={RelativeSource Mode=FindAncestor, AncestorType={x:Type Window}}}"
CommandParameter="{Binding Path=DataContext, RelativeSource={RelativeSource Mode=FindAncestor, AncestorType={x:Type Window}}}"/>
In your Command, you can cast the parameter to its original Type (MainViewModel) and work with it as you wish.