ContextMenu based on object binding wpf - c#

So I have a list view that displays post items (Delivery date, type, tracking number etc) and I have a context menu set up that either opens up the tracking website or copies the tracking number to the clipboard.
What I want is for the contextmenu only to appear for listitems that have a tracking number. I've got the idea of changing the visibility of the contextmenu but it's the binding to the tracking number I'm having the trouble with.
<ContextMenu x:Key="MyElementMenu">
<MenuItem Header="Track Item" Click="MenuItem_Click"></MenuItem>
<MenuItem Header="Copy to Clipboard" Click="MenuItem_CopyToClipboard"></MenuItem>
</ContextMenu>
<!--Sets a context menu for each ListBoxItem in the current ListBox-->
<Style TargetType="{x:Type ListViewItem}">
<Setter Property="ContextMenu" Value="{StaticResource MyElementMenu}"/>
</Style>
This is what I have currently.

<MyControl.Resources>
<BooleanToVisibilityConverter x:Key="BoolToVis"/>
</MyControl.Resources>
<!--Sets a context menu for each ListBoxItem in the current ListBox-->
<Style TargetType="{x:Type ListViewItem}">
<Setter Property="ContextMenu">
<Setter.Value>
<ContextMenu IsEnabled="{Binding HasTrackingNumber}" Visibility="{Binding HasTrackingNumber, Converter={StaticResource BoolToVis}">
<MenuItem Header="Track Item" Click="MenuItem_Click"></MenuItem>
<MenuItem Header="Copy to Clipboard" Click="MenuItem_CopyToClipboard"></MenuItem>
</ContextMenu>
</Setter.Value>
</Setter>
</Style>
This should give you what you need. Not sure if you use the ContextMenu elsewhere, but if you dont you can always just set it in the style of the ListViewItem Style. Then you dont need to have it referenced from elsewhere. Either way its more about adding the Binding from the item. In your ListItem Viewmodel you could add something like:
public bool HasTrackingNumber => TrackingNumber == 0 || TrackingNumber == null;
(I dont know what type your tracking number is so you can do your own logic checks to know if it "has" a valid tracking number)

That seems like a case for a trigger:
<Style TargetType="{x:Type ListViewItem}">
<Setter Property="ContextMenu" Value="{StaticResource MyElementMenu}"/>
<Style.Triggers>
<!--
Maybe the tracking number property is called something else, maybe it's 0
instead of null when absent. You didn't say.
-->
<DataTrigger Binding="{Binding TrackingNumber}" Value="{x:Null}">
<Setter Property="ContextMenu" Value="{x:Null}"/>
</DataTrigger>
</Style.Triggers>
</Style>

Related

WPF: ContextMenu in templated ListBox Item (InvalidCastException)

I'm trying to add a context menu to a ListBoxItem. I'm using ListBox.ItemTemplate and DataTemplate (with a Grid) to define the layout of the item and the ListBoxItem is styled.
In a search that this should be the way to go:
<ListBox.ItemContainerStyle>
<Style TargetType="ListBoxItem">
<Setter Property="ContextMenu">
<Setter.Value>
<ContextMenu>
<MenuItem Header="Rename" Click="Rename_Click" />
</ContextMenu>
</Setter.Value>
</Setter>
</Style>
</ListBox.ItemContainerStyle>
But this throws an XamlParseException/InvalidCastException saying
Couldn't cast an object of the type
System.Windows.Controls.MenuItem to the type
System.Windows.Controls.Grid
I tried adding the context menu to the Grid in the ItemTemplate, but then it only works when you click on one of the elements in the Grid (there is some empty space) (or if I add a background to the Grid, but that overrides/"covers" the styling, for hover & selected, of the Item itself)
I can't find any similar issues when searching, and I can't figure out the logic of the Exception..
You could overcome this by defining the ContextMenu as a resource:
<ListBox>
<ListBox.Resources>
<ContextMenu x:Key="cm">
<MenuItem Header="Rename" Click="Rename_Click" />
</ContextMenu>
</ListBox.Resources>
<ListBox.ItemTemplate>
...
</ListBox.ItemTemplate>
<ListBox.ItemContainerStyle>
<Style TargetType="ListBoxItem">
<Setter Property="ContextMenu" Value="{StaticResource cm}" />
</Style>
</ListBox.ItemContainerStyle>
</ListBox>
Use the TargetType="ListBoxItem" string of code. Because the coding langauge needs to know the Listbox!

Caliburn Micro Action in ContextMenu with ControlTemplate

I have a custom component with a ContextMenu in a ControlTemplate. I've read a couple of posts with people having trouble getting their actions to work in a ContextMenu. I've tried the workarounds posted there but I cannot get it to work. I keep getting No target found for method Open. None of these solutions used a ControlTemplate, however. I've tried to bind the model to the contextmenu, to the menuitems, to use TargetWithoutContext property, but none seem to work.
<components2:ImageBlock Background="Transparent" x:Name="ShareButton" Margin="0,0,7,0" >
<components2:ImageBlock.Style>
<Style TargetType="{x:Type components2:ImageBlock }">
<Setter Property="ToolTipService.IsEnabled" Value="False"/>
<Setter Property="Cursor" Value="Hand"/>
<Setter Property="FocusVisualStyle" Value="{x:Null}"/>
<Setter Property="Template">
<Setter.Value>
<ControlTemplate TargetType="{x:Type components2:ImageBlock }">
<Grid x:Name="ContentGrid" Background="{TemplateBinding Background}" MinHeight="30" ToolTip="{TemplateBinding ToolTip}">
<Grid.ContextMenu>
<ContextMenu>
<MenuItem Header="Facebook" cal:Message.Attach="Open(0)"/>
<MenuItem Header="Twitter" cal:Message.Attach="Open(1)]" />
<MenuItem Header="Tumblr" cal:Message.Attach="Open(2)]" />
</ContextMenu>
</Grid.ContextMenu>
</Grid>
</ControlTemplate>
</Setter.Value>
</Setter>
</Style>
</components2:ImageBlock.Style>
</components2:ImageBlock>
How can I get the Open to work at my viewmodel behind the view?
Got it to work through a DependencyObject that is added to the ImageBlock, where I had to set the PlacementTarget of the ContextMenu to the ImageBlock. Strangely, setting the PlacementTarget of the ContextMenu through {Binding ...} to ImageBlock directly didn't work.

ContextMenu bound to ObservableCollection<string> - show item if count == 0

I've got an observable collection of strings to thats data bound to my XAML contextmenu:
The ViewModel-Property:
public ObservableCollection<string> Indexes
{
get { return _Indexes; }
private set
{
if (value != _Indexes)
{
_Indexes = value;
OnPropertyChanged("Indexes");
}
}
}
The XAML code:
<viewmodel:IndexViewModel x:Key="IndexViewModel" />
<ContextMenu x:Key="ContextMenu_Index" Placement="Mouse" IsOpen="False">
<ContextMenu.ItemsSource>
<CompositeCollection>
<MenuItem Header="No items!" IsEnabled="False" Visibility="Collapsed">
<MenuItem.Style>
<Style TargetType="{x:Type MenuItem}">
<Style.Triggers>
<DataTrigger Binding="{Binding Source={StaticResource IndexViewModel}, Path=Indexes.Count}" Value="0">
<Setter Property="Visibility" Value="Visible" />
</DataTrigger>
</Style.Triggers>
</Style>
</MenuItem.Style>
</MenuItem>
<CollectionContainer Collection="{Binding Path=Indexes, Source={StaticResource IndexViewModel}}" />
</CompositeCollection>
</ContextMenu.ItemsSource>
<ContextMenu.Style>
<Style TargetType="ContextMenu"></Style>
</ContextMenu.Style>
<ContextMenu.ItemTemplate>
<DataTemplate DataType="string">
<TextBlock Text="{Binding}" MouseDown="TextBlock_Index_MouseDown"></TextBlock>
</DataTemplate>
</ContextMenu.ItemTemplate>
</ContextMenu>
Now I want to show the "No items" menu item if the count of Indexes is 0. But unfortunately it doesn't work this way, the "No items!" menu item is not shown. Do you have some hints?
There is a Dependency Property Setting Precedence List and because of that when you manually set Visibility it has priority over style trigger. Bring default value as setter into your Style instead of setting it against MenuItem and then Style.Trigger will be able to change that value:
<MenuItem Header="No items!" IsEnabled="False">
<MenuItem.Style>
<Style TargetType="{x:Type MenuItem}">
<Setter Property="Visibility" Value="Collapsed" />
<Style.Triggers>
<DataTrigger Binding="{Binding Source={StaticResource IndexViewModel}, Path=Indexes.Count}" Value="0">
<Setter Property="Visibility" Value="Visible" />
</DataTrigger>
</Style.Triggers>
</Style>
</MenuItem.Style>
</MenuItem>
In my opinion, displaying a MenuItem to say 'No items!' is unhelpful and incorrect... surely your users can tell when there are no MenuItems without being told that. Even if you feel that you absolutely do have to do that, then why don't you simply add an actual item into your data bound collection?:
Indexes.Add("No items!");
In your AddItem method, you'd just need to check for the existence of this item before adding a new item:
if (Indexes.Contains("No items!")) Indexes.Remove("No items!");
Indexes.Add(newItem);
In your comment, you said that you couldn't Style this item differently... I don't know why you'd want to do that anyway, but you could just use the DataTemplateSelector Class to do that for you. It would easier for you to implement your requirements this way.

Reuse context menu

I have created a context menu that I (at the moment) use for some items in my treeview. For that I have created a TreeItem class that holds all the relevant information like header, icon, children, execute target, etc. This is what it looks like:
<ContextMenu DataContext="{Binding PlacementTarget.DataContext, RelativeSource={RelativeSource Self}}"
Visibility="{Binding ShowContextMenu}"
ItemsSource="{Binding ContextMenu}">
<ContextMenu.ItemTemplate>
<HierarchicalDataTemplate ItemsSource="{Binding Children}">
<TextBlock Text="{Binding Header}" />
<HierarchicalDataTemplate.ItemContainerStyle>
<Style TargetType="MenuItem">
<Setter Property="Command"
Value="{Binding Execute}" />
<Setter Property="Icon"
Value="{StaticResource cmIcon}" />
<Setter Property="ToolTip"
Value="{Binding ToolTip}" />
</Style>
</HierarchicalDataTemplate.ItemContainerStyle>
</HierarchicalDataTemplate>
</ContextMenu.ItemTemplate>
<ContextMenu.ItemContainerStyle>
<Style TargetType="MenuItem">
<Setter Property="Command"
Value="{Binding Execute}" />
<Setter Property="Icon"
Value="{StaticResource cmIcon}" />
<Setter Property="ToolTip"
Value="{Binding ToolTip}" />
</Style>
</ContextMenu.ItemContainerStyle>
</ContextMenu>
When I used the context menu only in the tree view, it was attached to the TextBlock in the ItemTemplate. But now I want to use the same context menu for a different control. As I don't want to copy the same code to a different location and maintain it multiple times, I want to reuse it as template. I tried 2 things:
I put the context menu in the resources of the user control (just for testing) and call it like this: <TextBlock Text="{Binding Header}" ContextMenu="{StaticResource myContextMenu}">. It will be displayed, but not be closed and not move. Also this is not really helpful anyway as I want to use the context menu on a different user control.
Then I put the context menu inside a control template in the App.xaml: <ControlTemplate x:Key="TreeContextMenu" TargetType="ContextMenu">. And I call it like this:
<TextBlock.ContextMenu>
<ContextMenu Template="{StaticResource TreeContextMenu}"/>
</TextBlock.ContextMenu>
The program starts, but when I want to open the context menu, I get an exception: 'ContextMenu' cannot have a logical or visual parent.
I have tried to google for a solution, but couldn't find anything helpful.
You are trying to create a context menu inside a context menu. Remove the ControlTemplate tag from the App.xaml and move the x:Key attribute directly to the ContextMenu tag.
Also, delete the TextBlock.ContextMenu and add ContextMenu="{StaticResource TreeContextMenu}" attribute to the TextBlocktag.

How to approach item count checker

I'm trying to create a trigger to disable the combobox drop down button if there are no items. This is the XAML code I've tried so far, however I am unsure of how to detect whether there are no items contained in the ComboBox, and how to disable the button which drops down the list specifically.
<Style TargetType="ComboBox">
<Setter Property="Template">
<Setter.Value>
<ControlTemplate TargetType="{x:Type ComboBox}">
<ControlTemplate.Triggers>
<Trigger Property="Items.Count" Value="0">
</Trigger>
</ControlTemplate.Triggers>
</ControlTemplate>
</Setter.Value>
</Setter>
</Style>
This worked for me:
<ComboBox IsEnabled="{Binding RelativeSource=
{RelativeSource Mode=Self}, Path=ItemsSource.Count}"/>
Assuming whatever you have bound to your ItemsSource property has a count method (it worked for ObservableCollection). It's actually kind of interesting that count being 0 resolves to false in xaml, however this wouldn't be the case in C#.
You can add it to a style if you need to add it to a control programatically
<Style TargetType="ComboBox" x:Key="ComboStyle">
<Setter Property="IsEnabled" Value="{Binding RelativeSource=
{RelativeSource Mode=Self}, Path=ItemsSource.Count}"/>
</Style>
ComboBox cbo = new ComboBox();
cbo.ItemsSource = MyData;
cbo.Style = Resources["ComboStyle"] as Style;

Categories

Resources