ContextMenu items Visibility - wpf

I have some ContextMenu With some menuItems.
One of the menuItems is "Add Item...". when the user will selects this item, he should see submenu with list of available items;
here is description of my contextMenu:
Add Item
Item 1
Item 2
Item 3
Item 4
Delete
Copy
the ItemsSource of 'Add Item' menuItem is binded to some observable collection. Each model in the collecion has 'Name' and 'IsEnabled' (I have converter which convert bool to visibility). Everything works fine except the fact that the items which has 'IsEnable' = false, are not visible, but I can see there space.
for example: let say that Item 3 has IsEnable = false:
Add Item
Item 1
Item 2
Item 4
My bool2Vis converter return 'Collapsed' in case of false value.
Whay I'm doing wrong?
Here is the ContextMenu Code:
<ContextMenu x:Key="mainContextMenu" DataContext="{Binding Source={x:Static fw:UIMainManager.Instance},Path=layoutManager}">
<MenuItem Header="Add Item" Name="addItemMenu" ItemsSource="{Binding ControlBoxItems}" >
<MenuItem.ItemTemplate>
<DataTemplate>
<MenuItem Header="{Binding ControlName}" Visibility="{Binding IsEnabled,Converter={StaticResource boolToVisibilityConverter}}"
Command="{Binding Source={x:Static fw:ApplicationCommands.AddControlToScene}}" CommandParameter="{Binding}"/>
</DataTemplate>
</MenuItem.ItemTemplate>
</MenuItem>
<MenuItem Header="Copy" Command="{Binding Source={x:Static fw:ApplicationCommands.Copy}}" />
<MenuItem Header="Paste" Command="{Binding Source={x:Static fw:ApplicationCommands.Paste}}" />
<MenuItem Header="Cut" Command="{Binding Source={x:Static fw:ApplicationCommands.Cut}}" />
<MenuItem Header="Duplicated" Command="{Binding Source={x:Static fw:ApplicationCommands.DuplicateControl}}" />
<MenuItem Header="Delete" Command="{Binding Source={x:Static fw:ApplicationCommands.DeleteControl}}" />
</ContextMenu>

I think the MenuItem is collapsed but not the ItemContainer of MenuItem. Try this:
<MenuItem Header="Add Item" Name="addItemMenu" ItemsSource="{Binding ControlBoxItems}">
<MenuItem.ItemContainerStyle>
<Style>
<Setter Property="Visibility" Value="{Binding IsEnabled, Converter={StaticResource boolToVisibilityConverter}}" />
</Style>
</MenuItem.ItemContainerStyle>
<MenuItem.ItemTemplate>
<DataTemplate>
<MenuItem Header="{Binding ControlName}"
Command="{Binding Source={x:Static fw:ApplicationCommands.AddControlToScene}}"
CommandParameter="{Binding}" />
</DataTemplate>
</MenuItem.ItemTemplate>
</MenuItem>

Related

MenuItem Icon not clickable? WPF

I have a menuitem that contains a sub menu of menuitems. For some reason, to set off the command i have to click the actual menuitem and not the menuitem icon as well. i.e. clicking the menuitem icon doesn't trigger the command. Here is my XAML. Is there any easy fix for this as i want to be able to click both the text and the icon for the command to run.
<MenuItem Header="Choose Shoe Colour"
ItemsSource="{Binding DataContext.ShoeModels, Source={x:Reference sItemControl}}"
Command="{Binding DataContext.ChooseShoe_Click, Source={x:Reference sItemControl}}">
<MenuItem.ItemTemplate>
<DataTemplate>
<MenuItem Header="{Binding ShoeColour}"
Command="{Binding DataContext.ChooseShoe_Click, Source={x:Reference sItemControl}}"
CommandParameter="{Binding ShoeId}">
<MenuItem.Icon>
<Ellipse Fill="{Binding ShoeId, Converter={StaticResource ShoeIdToColorConverter}}"/>
</MenuItem.Icon>
</MenuItem>
</DataTemplate>
</MenuItem.ItemTemplate>
</MenuItem>

context menu for removing items in listview

I have a ListView which displays a list of string values. I want to add a context menu entry for each item in the list to remove the selected item. My XAML looks like this:
<ListView x:Name="itemsListView" ItemsSource="{Binding MyItems}">
<ListView.ContextMenu>
<ContextMenu>
<MenuItem Header="Remove"
Command="{Binding RemoveItem}"
CommandParameter="{Binding ElementName=itemsListView, Path=SelectedItem}" />
</ContextMenu>
</ListView.ContextMenu>
</ListView>
The problem is that the CommandParameter value is always null. I've added an additional button to remove the selected item to check if my command works. The button has exactly the same binding and removing items via the button works. The button looks like this:
<Button Content="Remove selected item"
Command="{Binding RemoveItem}"
CommandParameter="{Binding ElementName=itemsListView, Path=SelectedItem}"/>
The command looks like this:
private ICommand _removeItem;
public ICommand RemoveItem
{
get { return _removeItem ?? (_removeItem = new RelayCommand(p => RemoveItemCommand((string)p))); }
}
private void RemoveItemCommand(string item)
{
if(!string.IsNullOrEmpty(item))
MyItems.Remove(item);
}
Any ideas why the selected item is null when opening the context menu? Maybe a focus problem of the listview?
H.B. is right. but you can also use RelativeSource Binding
<ListView x:Name="itemsListView" ItemsSource="{Binding MyItems}">
<ListView.ContextMenu>
<ContextMenu>
<MenuItem Header="Remove"
Command="{Binding RemoveItem}"
CommandParameter="{Binding RelativeSource={RelativeSource AncestorType=ContextMenu}, Path=PlacementTarget.SelectedItem}" />
</ContextMenu>
</ListView.ContextMenu>
</ListView>
ContextMenus are disconnected, you cannot use ElementName bindings. One workaround would be using Binding.Source and x:Reference which requires you to extract parts that use it to be in the resources (due to cyclical dependency errors). You can just put the whole context menu there.
An example:
<ListBox Name="lb" Height="200">
<ListBox.Resources>
<ContextMenu x:Key="cm">
<MenuItem Header="{Binding ActualHeight, Source={x:Reference lb}}" />
</ContextMenu>
</ListBox.Resources>
<ListBox.ContextMenu>
<StaticResource ResourceKey="cm" />
</ListBox.ContextMenu>
</ListBox>
This work for me CommandParameter="{Binding}"

Context Menu for listboxitem bound to observablecollection MVVM style

I have 3 listboxes all bound two 3 separate observable collections of the same type. My ViewModel has the observable collections exposed via properties. This is for some drag and drop grouping, source list box can have items dragged onto two different lists. But I want to give the user the ability to right click on a listboxitem and set the item's properties. Things like Type, Name, etc. I am using a data template in the since I want all three boxes to be the same in functionality. This works well, and I can get the context menu to pop up when I click on individual items with no problem. My trouble is that I have one propery called FieldType. It is an enum that has 4 potential values. I can't, for the life of me, figure out how to bind the IsChecked property of the MenuItem to that property... functionally anyway. Here is what I have tried....
<DataTemplate x:Key="SFTemplateWithContextMenu">
<TextBlock Text="{Binding Path=FieldName}" ><!--Tag="{Binding DataContext, ElementName=Window}"-->
<TextBlock.ContextMenu>
<ContextMenu >
<ContextMenu.Resources>
<Configurator:EnumToBooleanConverter x:Key="EnumToBooleanConverterc" />
</ContextMenu.Resources>
<MenuItem Header="Rename..." />
<MenuItem Header="Field Type">
<MenuItem.Resources>
<Configurator:EnumToBooleanConverter x:Key="EnumToBooleanConverter" />
</MenuItem.Resources>
<MenuItem Header="String" IsCheckable="True" IsChecked="{Binding RelativeSource={RelativeSource AncestorType={x:Type ContextMenu}}, Path=DataContext.FieldType, Mode=TwoWay, UpdateSourceTrigger=PropertyChanged, Converter={StaticResource EnumToBooleanConverterc}, ConverterParameter={x:Static Configurator:TypeDesc.String}, PresentationTraceSources.TraceLevel=High}"/>
<MenuItem Header="Date" IsCheckable="True" IsChecked="{Binding RelativeSource={RelativeSource AncestorType={x:Type ContextMenu}}, Path=DataContext.FieldType, Mode=TwoWay, UpdateSourceTrigger=PropertyChanged, Converter={StaticResource EnumToBooleanConverter}, ConverterParameter={x:Static Configurator:TypeDesc.Date}}"/>
<MenuItem Header="Barcode" IsCheckable="True" IsChecked="{Binding RelativeSource={RelativeSource AncestorType={x:Type ContextMenu}}, Path=DataContext.FieldType, Mode=TwoWay, UpdateSourceTrigger=PropertyChanged, Converter={StaticResource EnumToBooleanConverter}, ConverterParameter={x:Static Configurator:TypeDesc.BarCode}}" />
</MenuItem>
</ContextMenu>
</TextBlock.ContextMenu>
</TextBlock>
</DataTemplate>
In the code above you can see on the String, Date, and Barcode MenuItems what I was trying to do (gotta love code that is a work in process). My issue is the exposed property that it should call. I don't know how, in my ViewModel property, to get to the item in the observable collection that corresponds to the item clicked. I have a value converter EnumToBoolean that will sit on the binding to get the checked or not. The problem is the property that is setting/getting that particular item in the observable collection.
Any thoughts? Need more code? Need me to clarify anything? How close am I? By the way, the ViewModel code is written in VB 2010.
Thanks
Bryce
EDIT:
I have tried the following using Angel's suggestion...
<DataTemplate x:Key="SFTemplateWithContextMenu">
<TextBlock x:Name="Field" Text="{Binding Path=FieldName}" >
<TextBlock.ContextMenu PlacementTarget="{Binding ElementName=Field}">
<MenuItem Header="Rename..." />
<MenuItem Header="Field Type">
<MenuItem.Resources>
<Configurator:EnumToBooleanConverter x:Key="EnumToBooleanConverter" />
</MenuItem.Resources>
<MenuItem Header="Date" IsCheckable="True" IsChecked="{Binding PlacementTarget.DataContext.FieldType, RelativeSource={RelativeSource AncestorType={x:Type ContextMenu}}, Mode=TwoWay, UpdateSourceTrigger=PropertyChanged, Converter={StaticResource EnumToBooleanConverter}, ConverterParameter={x:Static Configurator:TypeDesc.String}, PresentationTraceSources.TraceLevel=High}"/>
</MenuItem>
</TextBlock.ContextMenu>
</TextBlock>
</DataTemplate>
But this gives me an error that says...Cannot set properties on property elements. Not sure if this is a TextBlox vs TextBox issue? You used TextBox in your example... my guess is that your code would do the same. So I then tried the following...
<DataTemplate x:Key="SFTemplateWithContextMenu">
<TextBlock x:Name="Field" Text="{Binding Path=FieldName}" ><!--Tag="{Binding DataContext, ElementName=Window}"-->
<TextBlock.ContextMenu>
<ContextMenu PlacementTarget="{Binding ElementName=Field}" >
<MenuItem Header="Rename..." />
<MenuItem Header="Field Type">
<MenuItem.Resources>
<Configurator:EnumToBooleanConverter x:Key="EnumToBooleanConverter" />
</MenuItem.Resources>
<MenuItem Header="Date" IsCheckable="True" IsChecked="{Binding PlacementTarget.DataContext.FieldType, RelativeSource={RelativeSource AncestorType={x:Type ContextMenu}}, Mode=TwoWay, UpdateSourceTrigger=PropertyChanged, Converter={StaticResource EnumToBooleanConverter}, ConverterParameter={x:Static Configurator:TypeDesc.String}, PresentationTraceSources.TraceLevel=High}"/>
</MenuItem>
</ContextMenu>
</TextBlock.ContextMenu>
</TextBlock>
</DataTemplate>
But this causes binding errors...
System.Windows.Data Error: 4 : Cannot find source for binding with reference 'ElementName=Field'. BindingExpression:(no path); DataItem=null; target element is 'ContextMenu' (Name=''); target property is 'PlacementTarget' (type 'UIElement')
So it appears that the binding is not working. Any thoughts?
ContextMenu is not part of visual tree. So it wont, by default, connect \ bind to the datacontext of the TextBlock on which it is applied...
So 2 ways to do this...
Set ContextMenu.PlacementTarget and refer that as the Path in individual MenuItem's Binding.
e.g.
<TextBox x:Name="MyTextBlock">
<TextBox.ContextMenu PlacementTarget="{Binding ElementName=MyTextBlock}">
<MenuItem
Header="{Binding PlacementTarget.DataContext.MyHeader,
RelativeSource={RelativeSource
AncestorType={x:Type ContextMenu}}}"
</TextBox.ContextMenu>
</TextBox>
So in the example above... you want to connect menu item with the data context of the text box. So you define PlacementTarget on the ContextMenu. This placement target can only be set with 2 types of bindings... ElementName or StaticResource. And once the context menu is connected to the visual element via PlacementTarget, use the Path in the binding of the meuitem to resolve the data context property i.e. MyHeader.
OR
Use proxy element approach...
Bind datagrid column visibility MVVM

XAML and Binding Submenu items in a ContextMenu?

I have a ContextMenu defined on a Datagrid but want to bind submenu items to a collection on my viewmodel. Can anybody suggest how this should be done?
The following is a simple example of what I'm trying to achieve, BUT I want "Test1", "Test2" to come from a collection on my viewmodel, not hardcoded. I know how to bind my collection to the whole ContextMenu, but not how to bind it to just the one submenu...
<ContextMenu>
<MenuItem Header="Add to">
<MenuItem Header="Test1" />
<MenuItem Header="Test2" />
</MenuItem>
<MenuItem Header="Remove from All" />
</ContextMenu>
I'm using 3.5 SP1 and the WPF Toolkit.
Guess I should have experimented more. Turns out this was relatively simple:
<my:DataGrid.ContextMenu>
<ContextMenu>
<MenuItem Header="Add to" ItemsSource="{Binding MyItems}">
<MenuItem.ItemTemplate>
<DataTemplate>
<MenuItem CommandTarget="{Binding}" Click="AddClick">
<MenuItem.Header>
<TextBlock>
<TextBlock.Text><Binding StringFormat="Add to {0}" /></TextBlock.Text>
</TextBlock>
</MenuItem.Header>
</MenuItem>
</DataTemplate>
</MenuItem.ItemTemplate>
</MenuItem>
<MenuItem Header="Remove from All" />
</ContextMenu>
</my:DataGrid.ContextMenu>
There is a bug when using MenuItem.ItemTemplate. The color when do mouse over on the sub menu make user misunderstand that they can click to select the menu but it doesn't work for all area even if it's highlighted. See the picture
Then I used this code instead and it worked fine for me.
<ContextMenu>
<MenuItem Header="Add to" ItemsSource="{Binding MyItems}"
DisplayMemberPath="{Binding ItemName}">
<MenuItem.ItemContainerStyle>
<Style>
<EventSetter Event="MenuItem.Click" Handler="Menu_Click"/>
</Style>
</MenuItem.ItemContainerStyle>
</MenuItem>
<MenuItem Header="Remove from All" />
</ContextMenu>

Bind Command for MenuItem in DataTemplate without Tag

I want to bind a command in my ViewModel to a menuItem which is in DataTemplate. I can do that with using Tag. Is there any method which can do the same task but without using tag.
<Window.Resources>
<DataTemplate x:Key="StudentListBoxItemTemplate">
<StackPanel Tag="{Binding DataContext, RelativeSource={RelativeSource FindAncestor, AncestorType={x:Type ListBox}}}">
<TextBlock Text="{Binding Name}"/>
<StackPanel.ContextMenu>
<ContextMenu>
<MenuItem Header="Trigger" Command="{Binding PlacementTarget.Tag.TriggerCommand, RelativeSource={RelativeSource AncestorType=ContextMenu}}"/>
</ContextMenu>
</StackPanel.ContextMenu>
</StackPanel>
</DataTemplate>
</Window.Resources>
<StackPanel>
<ListBox
ItemsSource="{Binding StudentList}"
ItemTemplate="{StaticResource StudentListBoxItemTemplate}">
</ListBox>
</StackPanel>
My ViewModel
public class MainViewModel {
public ICommand TriggerCommand { ... }
public ObservableList<Student> StudentList { ... }
}
With your current design, you need to get from the ContextMenu through the StackPanel and back to the DataContext of the containing ListBox. What makes this awkward is that the DataContext of the StackPanel is already narrowed down to a particular student.
There are at least two ways to make this easier:
Provide a TriggerCommand property in Student so the command is right there where you need it
Provide a Parent property in the Student to escape the narrowed scope
You can try to add click event to the menuItem like following
<Menu Style="{StaticResource bellRingersFontStyle}" Height="23" Name="menu1" Width="Auto" DockPanel.Dock="Top" VerticalAlignment="Top">
<MenuItem Header="_File">
<MenuItem Header="_New Member" Name="newMember" Click="newMember_Click" >
<MenuItem.Icon>
<Image Source="Face.bmp" />
</MenuItem.Icon>
</MenuItem>
<MenuItem Header="_Save Member Details" Name="saveMember" IsEnabled="False" Click="saveMember_Click">
<MenuItem.Icon>
<Image Source="Note.bmp" />
</MenuItem.Icon>
</MenuItem>
<Separator />
<MenuItem Header="E_xit" Name="exit" Click="exit_Click" />
</MenuItem>
<MenuItem Header="_Help">
<MenuItem Header="_About Middleshire Bell Ringers" Name="about" Click="about_Click" >
<MenuItem.Icon>
<Image Source="Ring.bmp" />
</MenuItem.Icon>
</MenuItem>
</MenuItem>
</Menu>
Try binding the command to Click. My VS is down so can't check at this moment.
One way would be to get the context menu collection presentation defined in your viewmodel, which will contain the header string and command action (maybe with predicate).
The viewmodel creates an observable collection of the contextmenu items and the view binds that to ContextMenu itemssource and sets the displaymember path to header string.

Resources