C# wpf Databinding command not working in contextmenu - c#

I am learning wpf and mvvm and I decided to create a Soundboard for myself to practice and so far it's going pretty well.
Now I have made a datatemplate where for every file that the program finds in the specified directory it will create a button with the name of the file in it and I can click it to play. So far so good.
However I now tried to make a ContextMenu so that when I want to remove a file from the list I can right click and select remove, but this command doesn't work even though I have the exact same command structure for the regular button.
I am really quite confused with the whole RelativeSource thing and was already happy my regular 'play' command worked in the button.
If someone could point me in the right direction that would be great. I really could use an explanation on my specific problem as that always seems to help me more then a generic example somehow. I have tried to read on all the related questions but just don't seem to figure it out from there.
My ItemsControl:
<ItemsControl x:Name="MySounds" ItemsSource="{Binding Sounds}">
ItemTemplate:
<ItemsControl.ItemTemplate>
<DataTemplate>
<StackPanel>
<Button Style="{StaticResource mainButton}"
Command="{Binding RelativeSource={RelativeSource Mode=FindAncestor, AncestorType={x:Type ItemsControl}}, Path=DataContext.PlaySound}"
CommandParameter="{Binding Path=Tag, RelativeSource={RelativeSource Self}}"
Tag="{Binding Path=Name}">
<TextBlock Text="{Binding Path=NormalizedName}" TextWrapping="Wrap" Height="auto" />
<Button.ContextMenu>
<ContextMenu>
<MenuItem Header="{Binding Path=Name}"
Command="{Binding RelativeSource={RelativeSource Mode=FindAncestor, AncestorType={x:Type ItemsControl}}, Path=DataContext.RemoveSound}"
CommandParameter="{Binding Path=Tag, RelativeSource={RelativeSource Self}}">
<MenuItem.Icon>
<Image Source="\WpfPractice;component\Images\CoffeeArt.png" Width="20" VerticalAlignment="Center"/>
</MenuItem.Icon>
</MenuItem>
</ContextMenu>
</Button.ContextMenu>
</Button>
</StackPanel>
</DataTemplate>
</ItemsControl.ItemTemplate>
I have a generic RelayCommand in my viewmodel and that all works, the problem really is just with the binding.

If you bind the Tag property of the Button to the ItemsControl, you could bind to the command using the PlacementTarget property of the ContextMenu:
<ItemsControl.ItemTemplate>
<DataTemplate>
<StackPanel>
<Button Style="{StaticResource mainButton}"
Command="{Binding RelativeSource={RelativeSource Mode=FindAncestor, AncestorType={x:Type ItemsControl}}, Path=DataContext.PlaySound}"
CommandParameter="{Binding Path=Name}"
Tag="{Binding RelativeSource={RelativeSource Mode=FindAncestor, AncestorType={x:Type ItemsControl}}}">
<TextBlock Text="{Binding Path=NormalizedName}" TextWrapping="Wrap" Height="auto" />
<Button.ContextMenu>
<ContextMenu>
<MenuItem Header="{Binding Path=Name}"
Command="{Binding RelativeSource={RelativeSource Mode=FindAncestor, AncestorType={x:Type ContextMenu}}, Path=PlacementTarget.Tag.DataContext.RemoveSound}"
CommandParameter="{Binding Path=Name}">
<MenuItem.Icon>
<Image Source="\WpfPractice;component\Images\CoffeeArt.png" Width="20" VerticalAlignment="Center"/>
</MenuItem.Icon>
</MenuItem>
</ContextMenu>
</Button.ContextMenu>
</Button>
</StackPanel>
</DataTemplate>
</ItemsControl.ItemTemplate>

You can try to replace your command string in your MenuItem by this :
Command="{Binding RelativeSource={RelativeSource AncestorType=Window}, Path=DataContext.RemoveSound}"

Related

How to get PlacementTarget.Content in ContextMenu?

I have a situation, where I have to get Content from ContextMenu's Button. Something like this:
<Button Content="Test" HorizontalAlignment="Left" Margin="10,10,0,0" VerticalAlignment="Top" Width="75">
<Button.ContextMenu>
<ContextMenu>
<MenuItem Header="{Binding RelativeSource={RelativeSource Self}, Path=PlacementTarget.Content}"/>
</ContextMenu>
</Button.ContextMenu>
</Button>
But.. that doesn't work. The problem can be easily solved with the button's Tag, but the Tag is in use already:
<Button Content="Test" HorizontalAlignment="Left" Margin="10,10,0,0" VerticalAlignment="Top" Width="75"
Tag="{Binding DataContext, RelativeSource={RelativeSource Mode=FindAncestor, AncestorType={x:Type StackPanel}, AncestorLevel=2}}">
<Button.ContextMenu>
<ContextMenu DataContext="{Binding Path=PlacementTarget.Tag, RelativeSource={RelativeSource Self}}">
<MenuItem Header="{Binding RelativeSource={RelativeSource Self}, Path=PlacementTarget.Content}"/>
<MenuItem Header="Option 2" />
</ContextMenu>
</Button.ContextMenu>
</Button>
I'm using Tag to get main DataContext. Yet I still need the content of the button.
Why getting "Tag" from Placement target works, but "Content" does not?
How can I get it?
MenuItem doesn't have "PlacementTarget" property, bidning to Self doesn't work. There should be "System.Windows.Data Error: 40 : BindingExpression path error: 'PlacementTarget' property not found on 'object' 'MenuItem'" warning in Output.
Change the path:
<ContextMenu DataContext="{Binding Path=PlacementTarget.Tag, RelativeSource={RelativeSource Self}}">
<MenuItem Header="{Binding Path=Parent.PlacementTarget.Content, RelativeSource={RelativeSource Self}}"/>
<MenuItem Header="Option 2" />
</ContextMenu>
or RelativeSource:
<ContextMenu DataContext="{Binding Path=PlacementTarget.Tag, RelativeSource={RelativeSource Self}}">
<MenuItem Header="{Binding Path=PlacementTarget.Content, RelativeSource={RelativeSource AncestorType=ContextMenu}}"/>
<MenuItem Header="Option 2" />
</ContextMenu>

How to bind a command to navigate between pages?

I am learning navigation with MVVM as seen here:
Navigation with MVVM
In this tutorial, binding a command for button control is implemented by using ItemsControl like this:
<Window.Resources>
<DataTemplate DataType="{x:Type local:HomeViewModel}">
<local:HomeView />
</DataTemplate>
<DataTemplate DataType="{x:Type local:ProductsViewModel}">
<local:ProductsView />
</DataTemplate>
</Window.Resources>
<DockPanel>
<Border DockPanel.Dock="Left" BorderBrush="Black" BorderThickness="0,0,1,0">
<ItemsControl ItemsSource="{Binding PageViewModels}">
<ItemsControl.ItemTemplate>
<DataTemplate>
<Button Content="{Binding Name}"
Command="{Binding DataContext.ChangePageCommand, RelativeSource={RelativeSource AncestorType={x:Type Window}}}"
CommandParameter="{Binding }"
Margin="2,5"/>
</DataTemplate>
</ItemsControl.ItemTemplate>
</ItemsControl>
</Border>
<ContentControl Content="{Binding CurrentPageViewModel}" />
</DockPanel>
If I understand it correctly, it iterates through collection of PageViewModels and binds a button command for every VIewModel. This example works fine and I am able to switch between pages with no problem.
However, I don't want to create buttons iteratively. I want to have two buttons inside a grid with the same command that switches views as in example above. Something like this:
<Window.Resources>
<DataTemplate DataType="{x:Type local:HomeViewModel}">
<local:HomeView />
</DataTemplate>
<DataTemplate DataType="{x:Type local:ProductsViewModel}">
<local:ProductsView />
</DataTemplate>
</Window.Resources>
<Grid>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="*"/>
<ColumnDefinition Width="*"/>
<ColumnDefinition Width="5*"/>
</Grid.ColumnDefinitions>
<Button Grid.Column="0"
Content="{Binding HomeView.Name}"
Command="{Binding DataContext.ChangePageCommand, RelativeSource={RelativeSource AncestorType={x:Type Window}}}"
CommandParameter="{Binding }"/>
<Button Grid.Column="1"
Content="{Binding ProductsView.Name}"
Command="{Binding DataContext.ChangePageCommand, RelativeSource={RelativeSource AncestorType={x:Type Window}}}"
CommandParameter="{Binding }"/>
</Grid>
But this doesn't work as I expected. I am able to bind View's names but not the Command. Clicking buttons is doing nothing. DataContext is set to an instance of my ApplicationViewModel. What could be a problem here?
The problem is in the CommandParameter="{Binding }", in the first example that binding is pointing to ItemsSource="{Binding PageViewModels}". The second example doesn't have that part, so that binding is failing.
EDIT>>>>
To clarify, you must bind the command parameter to an instanciated viewModel in your datacontext.
ViewModel:
public IPageViewModel homeViewModel{get; set;}
public IPageViewModel productsViewModel{get; set;}
...
IPageViewModel homeViewModel = new HomeViewModel();
IPageViewModel productsViewModel = new ProductsViewModel();
View:
CommandParameter="{Binding DataContext.homeViewModel, RelativeSource={RelativeSource AncestorType={x:Type Window}}}"
I dont have a code editor right now, but hope it helps.

Bind to command from within a treeview

I'm building a treeview with HierarchicalDataTemplates and would like to bind the nodes to a command from my MainViewModel. I guess there is some conflict with the scopes, since the binding works if I e.g. use a button and define it outside of the treeview. If I define it inside, however, it does not work.
I've searched through Stackoverflow and found several solutions but none that worked for me. Jehof e.g. suggested here to use
<Button Command="{Binding DataContext.Command,
RelativeSource={RelativeSource AncestorLevel=2, AncestorType=TreeViewItem}}"
CommandParameter="{Binding}" />
but that did not work. Thank you for any suggestions!
<TreeView ItemsSource="{Binding _questions}" Grid.Row="0" Margin="10" BorderThickness="1">
<TreeView.Resources>
<HierarchicalDataTemplate DataType="{x:Type local:Question}" ItemsSource="{Binding Converter={StaticResource QuestionConverter}}">
<StackPanel Orientation="Horizontal">
<TextBlock Text="{Binding Path=Name}" />
</StackPanel>
</HierarchicalDataTemplate>
<HierarchicalDataTemplate DataType="{x:Type local:MainOption}" ItemsSource="{Binding MainOptions}">
<StackPanel Orientation="Horizontal">
<CheckBox Content="{Binding Path=Name}" />
/////////////////////////////////////
<Button Command="{Binding ThisIsMyCommand}" Content="Click Me"/>
/////////////////////////////////////
</StackPanel>
</HierarchicalDataTemplate>
</TreeView.Resources>
Is your TreeView-Control inside a Window or UserControl?
If you are inside a Window:
<Button Command="{Binding DataContext.Command, RelativeSource={RelativeSource FindAncestor, AncestorType=Window}}" CommandParameter="{Binding}" />
and for UserControl
<Button Command="{Binding DataContext.Command, RelativeSource={RelativeSource FindAncestor, AncestorType=UserControl}}" CommandParameter="{Binding}" />

How to access dynamic control WPF in DataTemplate

I created a tabcontrol with TabItem dynamic, and each TabItem with a button to close it, but just want that button visible when the TabItem is selected.
But I can not access the control inside the DataTemplate
<TabControl Name="dynamicTab" ItemsSource="{Binding}" Margin="0,85,0,0">
<TabControl.Resources>
<DataTemplate x:Key="TabHeader" DataType="TabItem">
<DockPanel>
<Button
Focusable="False"
BorderThickness="0"
Background="Transparent"
BorderBrush="Transparent"
Padding="-4"
Height="10"
Width="10"
Name="btnDelete" Visibility="Hidden" DockPanel.Dock="Right" Margin="5,0,0,0" Click="btnDelete_Click"
CommandParameter="{Binding RelativeSource={RelativeSource AncestorType={x:Type TabItem}}, Path=Name}">
<Image Name="imgButtonClose" Source="/Recursos;component/Imagens/close16x16.png" Height="10" Width="10"/>
</Button>
<TextBlock Text="{Binding RelativeSource={RelativeSource AncestorType={x:Type TabItem}}, Path=Header}" />
</DockPanel>
</DataTemplate>
</TabControl.Resources>
</TabControl>
Just use the binding on the IsSelected property of ancestoral TabItem:
<BooleanToVisibilityConverter x:Key="boolToVisibilityConverter"/>
...
<Button ...
Name="btnDelete"
Visibility="{Binding RelativeSource={RelativeSource AncestorType={x:Type TabItem}}, Path=IsSelected, Converter={StaticResource boolToVisibilityConverter}">
...
</Button>
If you have no problems with this binding:
CommandParameter="{Binding RelativeSource={RelativeSource AncestorType={x:Type TabItem}}, Path=Name}"
then the proposed code should work.

binding error to hide menu item

I want to hide a menuItem regarding user's rights. The menu item is placed into a context menu (displayed with a right click) into a userControl. Rights are passed to the user control trough the main window. I have an error 40 : Binding error. VS can't find my property declared in the xaml document.
MainWindow.xaml
<MyUC:myUC
...
MainOptionsVisibility="False" />
myUc.xaml
<GMap_NET_WindowsPresentation:GMapControl.ContextMenu>
<ContextMenu Opened="ContextMenu_Opened">
<MenuItem
Header="{x:Static Internationalization:Resources.VIEWPORT_ADDOBJECT}"
Command="{x:Static local:Viewport.CreateGraphicObjectRequestCommand}"
CommandTarget="{Binding Path=PlacementTarget,RelativeSource={RelativeSource AncestorType={x:Type ContextMenu}}}"
Visibility="{Binding RelativeSource={RelativeSource Self}, Path=IsEnabled, Converter={StaticResource BooleanToVisibilityCollapsedConverter}}">
<MenuItem.Icon>
<Image Source="{DynamicResource EditIcon}" Width="32" Height="32" />
</MenuItem.Icon>
</MenuItem>
<MenuItem Header="{x:Static Internationalization:Resources.OPTIONS}"
Visibility="{Binding Path=MainOptionsVisibility, RelativeSource={RelativeSource Self}, Converter={StaticResource BooleanToVisibilityCollapsedConverter}}" >
...
/>
</GMap_NET_WindowsPresentation:GMapControl.ContextMenu>
</GMap_NET_WindowsPresentation:GMapControl>
And MainOptionsVisibility is well declared as dependency property in code-behind. I have checked, it is well initialized. The visibility of the other item is OK (I didn't do it).
EDIT : new XAML code after Flo's answer :
<UserControl
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:local="clr-namespace:Main.Client.MyProject.Implementation.UIs.StandardViewports.Viewports"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:GMap_NET_WindowsPresentation="clr-namespace:GMap.NET.WindowsPresentation;assembly=GMap.NET.WindowsPresentation"
xmlns:Internationalization="clr-namespace:Main.Client.MyProject.Library.Resources;assembly=MyProjectLibrary"
xmlns:Main_Client_MyProject_Library_Converters="clr-namespace:Main.Client.MyProject.Library.Converters;assembly=MyProjectLibrary"
x:Name="baseViewport"
x:Class="Main.Client.MyProject.Implementation.UIs.StandardViewports.Viewports.Viewport"
MouseEnter="baseViewport_MouseEnter"
mc:Ignorable="d"
d:DesignHeight="300" d:DesignWidth="300" Loaded="BaseViewport_Loaded">
<UserControl.Resources>
<ResourceDictionary>
<ResourceDictionary.MergedDictionaries>
<ResourceDictionary Source="/MyLibs;component/ResourceDictionnary/ResourceDictionnary.xaml" />
</ResourceDictionary.MergedDictionaries>
<Main_Client_MyProject_Library_Converters:BooleanToVisibilityCollapsedConverter x:Key="BooleanToVisibilityCollapsedConverter" />
<Main_Client_MyProject_Library_Converters:BooleanToVisibilityCollapsedConverter x:Key="BooleanToVisibilityTestConverter" />
</ResourceDictionary>
</UserControl.Resources>
<UserControl.CommandBindings>
...
</UserControl.CommandBindings>
<Grid>
<GMap_NET_WindowsPresentation:GMapControl
x:Name="gMapControl"
MaxZoom="18"
MinZoom="1"
MouseDown="gMapControl_MouseDown"
OnMapZoomChanged="gMapControl_OnMapZoomChanged"
OnCurrentPositionChanged="gMapControl_OnCurrentPositionChanged"
MouseMove="gMapControl_MouseMove"
Loaded="gMapControl_Loaded"
Drop="gMapControl_Drop"
AllowDrop="True"
IsEnabled="{Binding IsEnabled, ElementName=baseViewport}" MapType="OpenStreetMap">
<GMap_NET_WindowsPresentation:GMapControl.ContextMenu>
<ContextMenu Opened="ContextMenu_Opened">
<MenuItem
Header="{x:Static Internationalization:Resources.VIEWPORT_ADDOBJECT}"
Command="{x:Static local:Viewport.CreateGraphicObjectRequestCommand}"
CommandTarget="{Binding Path=PlacementTarget,RelativeSource={RelativeSource AncestorType={x:Type ContextMenu}}}"
Visibility="{Binding RelativeSource={RelativeSource Self}, Path=IsEnabled, Converter={StaticResource BooleanToVisibilityTestConverter}}">
<MenuItem.Icon>
<Image Source="{DynamicResource EditIcon}" Width="32" Height="32" />
</MenuItem.Icon>
</MenuItem>
<MenuItem Header="{x:Static Internationalization:Resources.VIEWPORT_OPTIONS}"
Visibility="{Binding Path=PlacementTarget.DataContext.MainOptionsVisibility, RelativeSource={RelativeSource AncestorType={x:Type ContextMenu}}, Converter={StaticResource BooleanToVisibilityTestConverter}}" >
<MenuItem Header="{x:Static Internationalization:Resources.VIEWPORT_LOCKSUPERIORLEFTCORNER}" Command="{x:Static local:Viewport.LockSuperiorLeftCornerRequestCommand}" CommandTarget="{Binding Path=PlacementTarget,RelativeSource={RelativeSource AncestorType={x:Type ContextMenu}}}">
</MenuItem>
<MenuItem Header="{x:Static Internationalization:Resources.VIEWPORT_LOCKINFERIORRIGHTCORNER}" Command="{x:Static local:Viewport.LockInferiorRightCornerRequestCommand}" CommandTarget="{Binding Path=PlacementTarget,RelativeSource={RelativeSource AncestorType={x:Type ContextMenu}}}">
</MenuItem>
<MenuItem Header="{x:Static Internationalization:Resources.VIEWPORT_LOCKZOOMMAXONMAP}" Command="{x:Static local:Viewport.LockMaxZoomLevelRequestCommand}" CommandTarget="{Binding Path=PlacementTarget,RelativeSource={RelativeSource AncestorType={x:Type ContextMenu}}}" >
</MenuItem>
<MenuItem Header="{x:Static Internationalization:Resources.VIEWPORT_LOCKZOOMMINONMAP}" Command="{x:Static local:Viewport.LockMinZoomLevelRequestCommand}" CommandTarget="{Binding Path=PlacementTarget,RelativeSource={RelativeSource AncestorType={x:Type ContextMenu}}}" >
</MenuItem>
</MenuItem>
</ContextMenu>
</GMap_NET_WindowsPresentation:GMapControl.ContextMenu>
</GMap_NET_WindowsPresentation:GMapControl>
</Grid>
The converter was changed for both menuItems for tests. It is never called.
The problem is your MenuItem has no property which is called MainOptionsVisibility, only your Window has. Through RelativeSource={RelativeSource Self} your Binding to the MenuItem.
RelativeSource={RelativeSource AncestorType={x:Type Window}} wont work either, because your ContextMenu isnt part of the Windows logical or visual Tree.
What you could do is set the DataContext of the ContextMenus PlacementTarget (GMap_NET_WindowsPresentation:GMapControl) or one of its ancestors to your Window (e.g. through setting the Windows DataContext to itself (<Window ...DataContext={Binding RelativeSource={RelativeSource Self}}.../>) and the do something like:
<MenuItem Header="{x:Static Internationalization:Resources.OPTIONS}" Visibility="{Binding Path=PlacementTarget.DataContext.MainOptionsVisibility, RelativeSource={RelativeSource AncestorType={x:Type ContextMenu}}, Converter={StaticResource BooleanToVisibilityCollapsedConverter}}" >

Categories