WPF usercontrol command binding to window viewmodel - wpf

I have a Window which contains UserControl1 and UserControl2. These user controls have their own viewmodels. Also, these user controls use UserControl3 to display data. So, when UserControl1 uses UserControl3 the UserControl3 has the same viewmodel as UserControl1.
I have a binding in UserControl3 which I wish to call the command which is on the viewmodel of UserControl1.
But I can't find a way to make it work. Any help is welcomed. Thank you very much.
Here is my binding which does not work:
<UserControl x:Class="MyNamespace.UserControl3"
xmlns:local="clr-namespace:MyNamespace">
<UserControl.Resources>
<DataTemplate DataType="{x:Type g:GraphNode}">
<StackPanel>
<StackPanel.ContextMenu>
<ContextMenu>
<MenuItem Header="My Command" Command="{Binding Path=DataContext.MyCommand, RelativeSource={RelativeSource Mode=FindAncestor, AncestorType={x:Type local:UserControl3}}}"/>
</ContextMenu>
</StackPanel.ContextMenu>
<Grid>
<ContentControl Content="{Binding Data}"/>
</Grid>
</StackPanel>
</DataTemplate>
</UserControl.Resources>

This works in my app:
<DataTemplate DataType="{x:Type g:GraphNode}">
<StackPanel Tag="{Binding}">
<StackPanel.ContextMenu>
<ContextMenu>
<MenuItem Header="My Command" Command="{Binding Path=PlacementTarget.Tag.MyCommand, RelativeSource={RelativeSource Mode=FindAncestor, AncestorType=ContextMenu}}" />
</ContextMenu>
</StackPanel.ContextMenu>
</StackPanel>
</DataTemplate>
The key is that ContextMenus are on a different window, so you can't access datacontexts like you usually do.
You will have to adapt this so that the object containing the Command you seek is set as the Tag of the StackPanel (which is the PlacementTarget of your ContextMenu).

Related

How can I track which element of ItemsControl caused ContexMenu to open?

In my book editor app I need to display themes and subthemes in an hierarchical order, so I made such an markup:
<StackPanel>
<ScrollViewer>
<Grid ColumnDefinitions="auto, *" RowDefinitions="auto">
<ItemsControl Grid.Column="0" Grid.Row="0" Items="{Binding Book.Themes}">
<ItemsControl.ItemTemplate>
<DataTemplate>
<StackPanel>
<Button Content="{Binding Name}" Command="{Binding RelativeSource={RelativeSource FindAncestor, AncestorType={x:Type Window}}, Path=DataContext.UpdateSubthemesVisibilityCommand}" CommandParameter="{Binding Name}"/>
<ItemsControl Items="{Binding Subthemes}" IsVisible="{Binding AreSubthemesVisible}">
<ItemsControl.ItemTemplate>
<DataTemplate>
<Button Content="{Binding Name}"/>
</DataTemplate>
</ItemsControl.ItemTemplate>
<ItemsControl.ContextMenu>
<ContextMenu>
<MenuItem Header="Rename" />
<MenuItem Header="Delete"/>
</ContextMenu>
</ItemsControl.ContextMenu>
</ItemsControl>
</StackPanel>
</DataTemplate>
</ItemsControl.ItemTemplate>
<ItemsControl.ContextMenu>
<ContextMenu>
<MenuItem Header="Rename" Command="{Binding RenameThemeCommand}" CommandParameter="...How to bind it to button which cause menu to open"/>
<MenuItem Header="Delete"/>
</ContextMenu>
</ItemsControl.ContextMenu>
</ItemsControl>
</Grid>
</ScrollViewer>
</StackPanel>
Maybe markup isn't quite clear, but the point is that I have external ItemsControl and internal one. External one's items have structure: button (theme name) + internal ItemsControl which presents subthemes' names as buttons too. Here is Screenshot:
Each ItemsControl has its own ContextMenu, but I can't bind them properly. I want "Rename" MenuItems to be bind to Button.Content of Button which right click caused menu to open. How should I do this here?
I am using MVVM Avalonia, but I hope in WPF it's working the same way so that I could add WPF hashtag to this question.
You don't need to add ContextMenu to ItemsControl. It needs to be added to the items ItemsControl.
This is done in ItemContainerStyle.
In such a case, in ContextMenu DataContext will contain the element on which the ContextMenu is called.
Example:
<Window.Resources>
<spec:StringCollection x:Key="strings">
<sys:String>First</sys:String>
<sys:String>Second</sys:String>
<sys:String>Third</sys:String>
</spec:StringCollection>
<Style x:Key="ItemStyle" TargetType="ContentPresenter">
<Setter Property="ContextMenu">
<Setter.Value>
<ContextMenu>
<TextBlock Text="{Binding}"/>
</ContextMenu>
</Setter.Value>
</Setter>
</Style>
</Window.Resources>
<ItemsControl ItemsSource="{StaticResource strings}"
ItemContainerStyle="{StaticResource ItemStyle}"/>

Unable to use FindAncestor to bind to parent properties with a ContextMenu

I have a ListBox with a ContextMenu and I am trying to bind to the Tag.
I am able to use RelativeSource/FindAncestor to bind to the Tag from the ItemTemplate but this same approach doesn't work for the ContextMenu.
I looked through the Live Visual Tree in Visual Studio and I see the ListBox Items but I don't see the ContextMenu. What is the proper way to do this binding if the ListBox is not an Ancestor of the ContextMenu in the Visual Tree?
Note: I intend to create a ContextMenu in the Page.Resources that I can use in more than one ListBox so I do not want to use ElementName to bind to specific controls.
<ListBox Grid.Row="1"
x:Name="SetupStepsList"
VerticalAlignment="Stretch"
KeyDown="ListBox_KeyDown"
Tag="This is the tag"
Style="{StaticResource GenericListBox}"
SelectedValue="{Binding ActiveStep, Mode=TwoWay}"
ItemContainerStyle="{StaticResource TightListBox}"
ItemsSource="{Binding SelectedStation.SetupSteps, Mode=OneWay}">
<ListBox.ContextMenu>
<ContextMenu>
<MenuItem Header="{Binding RelativeSource={RelativeSource Mode=FindAncestor, AncestorType={x:Type ListBox}}, Path=Tag}" >
<MenuItem.Icon>
<iconPacks:PackIconMaterial Kind="Plus"
Style="{StaticResource MenuIconStyle}"/>
</MenuItem.Icon>
</MenuItem>
</ContextMenu>
</ListBox.ContextMenu>
<ListBox.ItemTemplate>
<DataTemplate>
<StackPanel>
<TextBlock AllowDrop="False"
Text="{Binding RelativeSource={RelativeSource Mode=FindAncestor, AncestorType={x:Type ListBox}}, Path=Tag}"/>
</StackPanel>
</DataTemplate>
</ListBox.ItemTemplate>
</ListBox>
A ContextMenu is not part of the same visual tree as the ListBox, therefore RelativeSource bindings do not work. You can do this instead:
<MenuItem Header="{Binding Path=PlacementTarget.Tag, RelativeSource={RelativeSource FindAncestor, AncestorType={x:Type ContextMenu}}}">
This works, because the PlacementTarget of the ContextMenu is the parent ListBox.

How to bind Command to ParentControl's ViewModel in DataTemplate(bind for ListBox's ItemsTemplate)

UserControl's DataContext bind to a NotesViewModel(ViewModel) instance which has a ICommand named AddNote, so the ListBox's ContextMenu works.
Now I want to the ContextMenu in the DataTemplate works as ListBox's, how to bind {??????}?
Part of code below:
<DataTemplate x:Key="contentTemplate">
<Border BorderThickness="0,0,0,1">
<Border.ContextMenu>
<ContextMenu>
<MenuItem Header="AddNote" Command="{??????}"></MenuItem> <!-- here -->
</ContextMenu>
</Border.ContextMenu>
<Grid>
<TextBlock Text="{Binding NoteContent}"></TextBlock>
</Grid>
</Border>
</DataTemplate>
<ListBox DockPanel.Dock="Top" x:Name="noteListBox"
ItemTemplate="{StaticResource contentTemplate}"
ItemsSource="{Binding Source={StaticResource notesViewSource}}">
<ListBox.ContextMenu>
<ContextMenu>
<MenuItem x:Name="AddNoteMenu"
Header="AddNote"
Command="{Binding AddNote}"/> <!-- here works -->
</ContextMenu>
</ListBox.ContextMenu>
</ListBox>
You can bind using x:Reference markup extension.
Set x:Name on your listBox:
<ListBox x:Name="myListBox"/>
and bind using x:Reference:
<MenuItem Header="AddNote" Command="{Binding DataContext.AddNote,
Source={x:Reference myListBox}}"/>
PS - Please not binding with ElementName won't work because ContextMenu doesn't lie in same Visual Tree as that of ListBox. Hence, we need to use x:Reference here.

binding a command inside a listbox item to a property on the viewmodel parent

I've been working on this for about an hour and looked at all related SO questions.
My problem is very simple:
I have HomePageVieModel:
HomePageVieModel
+IList<NewsItem> AllNewsItems
+ICommand OpenNews
My markup:
<Window DataContext="{Binding HomePageViewModel../>
<ListBox ItemsSource="{Binding Path=AllNewsItems}">
<ListBox.ItemTemplate>
<DataTemplate>
<StackPanel>
<TextBlock>
<Hyperlink Command="{Binding Path=OpenNews}">
<TextBlock Text="{Binding Path=NewsContent}" />
</Hyperlink>
</TextBlock>
</StackPanel>
</DataTemplate>
</ListBox.ItemTemplate>
The list shows fine with all the items, but for the life of me whatever I try for the Command won't work:
<Hyperlink Command="{Binding Path=OpenNewsItem, RelativeSource={RelativeSource AncestorType=vm:HomePageViewModel, AncestorLevel=1}}">
<Hyperlink Command="{Binding Path=OpenNewsItem, RelativeSource={RelativeSource AncestorType=vm:HomePageViewModel,**Mode=FindAncestor}**}">
<Hyperlink Command="{Binding Path=OpenNewsItem, RelativeSource={RelativeSource AncestorType=vm:HomePageViewModel,**Mode=TemplatedParent}**}">
I just always get :
System.Windows.Data Error: 4 : Cannot find source for binding with reference .....
Update
I am setting my ViewModel like this? Didn't think this would matter:
<Window.DataContext>
<Binding Path="HomePage" Source="{StaticResource Locator}"/>
</Window.DataContext>
I use the ViewModelLocator class from the MVVMLight toolkit which does the magic.
Slightly different example but,
I found that by referencing the parent container (using ElementName) in the binding you can get to it's DataContext and its subsequent properties using the Path syntax. As shown below:
<ItemsControl x:Name="lstDevices" ItemsSource="{Binding DeviceMappings}">
<ItemsControl.ItemTemplate>
<DataTemplate>
<Grid>
<ComboBox Text="{Binding Device}" ItemsSource="{Binding ElementName=lstDevices, Path=DataContext.AvailableDevices}" />
...
</Grid>
</DataTemplate>
</ItemsControl.ItemTemplate>
</ItemsControl>
There's two issue working against you here.
The DataContext for the DataTemplate is set to the item the template is displaying. This means you can't just use binding without setting a source.
The other issue is that the template means the item is not technically part of the logical tree, so you can't search for ancestors beyond the DataTemplate node.
To solve this you need to have the binding reach outside the logical tree. You can use a DataContextSpy defined here.
<ListBox ItemsSource="{Binding Path=AllNewsItems}">
<ListBox.Resources>
<l:DataContextSpy x:Key="dataContextSpy" />
</ListBox.Resources>
<ListBox.ItemTemplate>
<DataTemplate>
<StackPanel>
<TextBlock>
<Hyperlink Command="{Binding Source={StaticResource dataContextSpy}, Path=DataContext.OpenNews}" CommandParameter="{Binding}">
<TextBlock Text="{Binding Path=NewsContent}" />
</Hyperlink>
</TextBlock>
</StackPanel>
</DataTemplate>
</ListBox.ItemTemplate>
</ListBox>
try something like this
<Button Command="{Binding DataContext.YourCommand,RelativeSource={RelativeSource AncestorType={x:Type ListBox}}}"
he can't find your command binding inside the listbox because you set a diffrent datacontext than the viewmodel for that listbox
So looks like you are trying to give the proper DataContext to the HyperLink so as to trigger ICommand.
I think a simple element name binding can solve this.
<Window x:Name="window" DataContext="{Binding HomePageViewModel../>
<ListBox ItemsSource="{Binding Path=AllNewsItems}">
<ListBox.ItemTemplate>
<DataTemplate>
<StackPanel>
<TextBlock>
<Hyperlink DataContext="{Binding DataContext ,ElementName=window}" Command="{Binding Path=OpenNews}">
<TextBlock Text="{Binding Path=NewsContent}" />
</Hyperlink>
</TextBlock>
</StackPanel>
</DataTemplate>
The AncestorType checks only for Visual-Types not for ViewModel types.
Well, it's a little bit late, I know. But I have only recently faced the same problem. Due to architectural reasons I decided to use a static viewmodel locator instead of the dataContextSpy.
<UserControl x:Class="MyView"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:locator="clr-namespace: MyNamespace"
DataContext="{Binding Source={x:Static locator:ViewModelLocator.MyViewModel}}" >
<ListBox ItemsSource="{Binding Path=AllNewsItems}">
<ListBox.ItemTemplate>
<DataTemplate>
<StackPanel>
<TextBlock>
<Hyperlink Command="{Binding Source={x:Static locator:ViewModelLocator.MyViewModel},
Path=OpenNews}"
CommandParameter="{Binding}">
<TextBlock Text="{Binding Path=NewsContent}" />
</Hyperlink>
</TextBlock>
</StackPanel>
</DataTemplate>
</ListBox.ItemTemplate>
</ListBox>
</UserControl>
The static viewmodel locator instantiates the view model:
namespace MyNamespace
{
public static class ViewModelLocator
{
private static MyViewModelType myViewModel = new MyViewModelType();
public static MyViewModelType MyViewModel
{
get
{
return myViewModel ;
}
}
}
}
Using this workaround is another way to bind from a data template to a command that is in the viewmodel.
The answer from #Darren works well in most cases, and should be the preferred method if possible. However, it is not a working solution where the following (niche) conditions all occur:
DataGrid with DataGridTemplateColumn
.NET 4
Windows XP
...and possibly in other circumstances too. In theory it should fail on all versions of Windows; but in my experience the ElementName approach works in a DataGrid on Windows 7 upwards but not XP.
In the following fictional example, we are trying to bind to an ICommand called ShowThingCommand on the UserControl.DataContext (which is the ViewModel):
<UserControl x:Name="ThisUserControl" DataContext="whatever...">
<DataGrid ItemsSource="{Binding Path=ListOfThings}">
<DataGrid.Columns>
<DataGridTemplateColumn Header="Thing">
<DataGridTemplateColumn.CellTemplate>
<DataTemplate>
<Button
Command="{Binding ElementName=ThisUserControl, Path=ShowThingCommand}"
CommandParameter="{Binding Path=ThingId}"
Content="{Binding Path=ThingId}" />
</DataTemplate>
</DataGridTemplateColumn.CellTemplate>
</DataGridTemplateColumn>
</DataGrid.Columns>
</DataGrid>
</UserControl>
Due to the DataTemplate not being in the same VisualTree as the main control, it's not possible to reference back up to the control by ElementName.
To solve this, the little known .NET 4 and above {x:Reference} can be used. Modifying the above example:
<UserControl x:Name="ThisUserControl" DataContext="whatever...">
<DataGrid ItemsSource="{Binding Path=ListOfThings}">
<DataGrid.Columns>
<DataGridTemplateColumn Header="Thing">
<DataGridTemplateColumn.CellTemplate>
<DataTemplate>
<Button
Command="{Binding Source={x:Reference ThisUserControl}, Path=ShowThingCommand}"
CommandParameter="{Binding Path=ThingId}"
Content="{Binding Path=ThingId}" />
</DataTemplate>
</DataGridTemplateColumn.CellTemplate>
</DataGridTemplateColumn>
</DataGrid.Columns>
</DataGrid>
</UserControl>
For reference, see the following stackoverflow posts:
Question 19244111
Question 5834336
...and for an explanation of why ElementName doesn't work in this circumstance, see this blog post which contains a pre-.NET 4 workaround.
<ListBox xmlns:model="clr-namespace:My.Space;assembly=My.Assembly"
ItemsSource="{Binding Path=AllNewsItems, Mode=OneWay}">
<ListBox.ItemTemplate>
<DataTemplate>
<StackPanel>
<TextBlock>
<Hyperlink Command="{Binding RelativeSource={RelativeSource Mode=FindAncestor, AncestorType={x:Type ListBox}}, Path=DataContext.(model:MyNewsModel.OpenNews), Mode=OneWay}"
CommandParameter="{Binding Path=., Mode=OneWay}">
<TextBlock Text="{Binding Path=NewsContent, Mode=OneWay}" />
</Hyperlink>
</TextBlock>
</StackPanel>
</DataTemplate>
</ListBox.ItemTemplate>
</ListBox>

How can I tell my DataTemplate to bind to a property in the PARENT ViewModel?

I've got the following MainView.xaml file that works well as a MVVM menu switcher. I've got these pairs:
Page1View / Page1ViewModel
Page2View / Page2ViewModel
in my MainViewModel I fill an ObservableCollection with both ViewModels, then when the user clicks the Next button, it calls NextPageCommand in MainViewModel which switches out CurrentPageViewModel with a new ViewModel which is then displayed with an appropriate View, works nicely.
I also have a Menu being filled with all the titles from the ViewModels in the Observable collection, which also works nicely.
However, each MenuItem has a Command="{Binding SwitchPageCommand}" which SHOULD call SwitchPageCommand on the MainViewModel and not on e.g. Page1ViewModel or Page2ViewModel.
So how can I indicate in the template not to bind to the current ViewModel but the ViewModel which contains that ViewModel, e.g. something like this:
PSEUDO-CODE:
<DataTemplate x:Key="CodeGenerationMenuTemplate">
<MenuItem
Command="{Binding <parentViewModel>.SwitchPageCommand}"
Header="{Binding Title}"
CommandParameter="{Binding Title}"/>
</DataTemplate>
Here is MainViewModel:
<Window x:Class="TestMenu234.Views.MainView"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:c="clr-namespace:TestMenu234.Commands"
xmlns:vm="clr-namespace:TestMenu234.ViewModels"
xmlns:v="clr-namespace:TestMenu234.Views"
Title="Main Window" Height="400" Width="800">
<Window.Resources>
<DataTemplate x:Key="CodeGenerationMenuTemplate">
<MenuItem Header="{Binding Title}" Command="{Binding SwitchPageCommand}" CommandParameter="{Binding Title}"/>
</DataTemplate>
<DataTemplate DataType="{x:Type vm:Page1ViewModel}">
<v:Page1View/>
</DataTemplate>
<DataTemplate DataType="{x:Type vm:Page2ViewModel}">
<v:Page2View/>
</DataTemplate>
</Window.Resources>
<DockPanel>
<Menu DockPanel.Dock="Top">
<MenuItem Header="Code _Generation" ItemsSource="{Binding AllPageViewModels}"
ItemTemplate="{StaticResource CodeGenerationMenuTemplate}"/>
</Menu>
<StackPanel DockPanel.Dock="Top" Orientation="Horizontal">
<Button Margin="5" Content="Next Page" Command="{Binding NextPageCommand}"/>
</StackPanel>
<ContentControl
Content="{Binding CurrentPageViewModel}"/>
</DockPanel>
</Window>
The answer is this:
<DataTemplate x:Key="CodeGenerationMenuTemplate">
<MenuItem
Header="{Binding Title}"
Command="{Binding DataContext.SwitchPageCommand,
RelativeSource={RelativeSource FindAncestor, AncestorType={x:Type Menu}}}"
CommandParameter="{Binding Title}"/>
</DataTemplate>
I just saw that Nir had given me the syntax to solve the above issue on this question: What is the best way in MVVM to build a menu that displays various pages?.

Resources