ListBoxItem ContextMenu only when ListBox Multiple Items Selected - c#

How can I disable the listboxitem context menu when none or only one item is selected?
ListBox has a SelectedItems property, but it is read only and you cannot bind to it.
<ListBox ItemsSource="{Binding Items}" SelectionMode="Extended">
<ListBox.ItemContainerStyle>
<Style TargetType="ListBoxItem">
<Setter Property="ContextMenu">
<Setter.Value>
<ContextMenu>
<MenuItem Header="GOGO" />
</ContextMenu>
</Setter.Value>
</Setter>
</Style>
</ListBox.ItemContainerStyle>
<ListBox.ItemTemplate>
<DataTemplate>
<TextBlock Text="{Binding}" HorizontalAlignment="Center" VerticalAlignment="Center" />
</DataTemplate>
</ListBox.ItemTemplate>
</ListBox>

This should work:
<ListBox ItemsSource="{Binding Items}" SelectionMode="Extended">
<ListBox.ItemContainerStyle>
<Style TargetType="ListBoxItem">
<Setter Property="ContextMenu">
<Setter.Value>
<ContextMenu>
<MenuItem Header="GOGO" />
</ContextMenu>
</Setter.Value>
</Setter>
<Style.Triggers>
<DataTrigger Binding="{Binding SelectedItems.Count, RelativeSource={RelativeSource AncestorType={x:Type ListBox}}}" Value="0">
<Setter Property="ContextMenu" Value="{x:Null}" />
</DataTrigger>
<DataTrigger Binding="{Binding SelectedItems.Count, RelativeSource={RelativeSource AncestorType={x:Type ListBox}}}" Value="1">
<Setter Property="ContextMenu" Value="{x:Null}" />
</DataTrigger>
</Style.Triggers>
</Style>
</ListBox.ItemContainerStyle>
<ListBox.ItemTemplate>
<DataTemplate>
<TextBlock Text="{Binding}" HorizontalAlignment="Center" VerticalAlignment="Center" />
</DataTemplate>
</ListBox.ItemTemplate>
</ListBox>
Adding two DataTriggers checking whether the SelectedItems.Count is 0 or 1 in which case it sets the ContextMenu to {x:Null}.

Related

WPF INotifyDataErrorInfo highlight ListBoxItem

I've got a ListBox as such:
<ListBox Margin="5" ItemsSource="{Binding NetworkAdapters, Mode=OneWay}" SelectedItem="{Binding SelectedNetworkAdapter}" SelectionChanged="{s:Action SelectedNetworkAdapterChanged}">
<ListBox.ItemsPanel>
<ItemsPanelTemplate>
<UniformGrid Columns="2" VerticalAlignment="Top"/>
</ItemsPanelTemplate>
</ListBox.ItemsPanel>
<ListBox.ItemTemplate>
<DataTemplate>
<StackPanel Orientation="Horizontal">
<Ellipse Width="15" Height="15" Margin="5">
<Ellipse.Style>
<Style TargetType="Ellipse">
<Setter Property="Fill" Value="Gray"></Setter>
<Style.Triggers>
<DataTrigger Binding="{Binding Status}" Value="{x:Static wpf:NetworkAdapterStatus.Up}">
<Setter Property="Fill" Value="Green"></Setter>
</DataTrigger>
<DataTrigger Binding="{Binding Status}" Value="{x:Static wpf:NetworkAdapterStatus.Down}">
<Setter Property="Fill" Value="Red"></Setter>
</DataTrigger>
</Style.Triggers>
</Style>
</Ellipse.Style>
</Ellipse>
<StackPanel Margin="5,0,0,0">
<TextBlock Text="{Binding Name}" FontWeight="Bold"/>
<TextBlock Text="{Binding Description}"></TextBlock>
<TextBlock Text="{Binding Speed, StringFormat='Speed: {0}'}" FontSize="10"/>
<TextBlock Text="{Binding Status}" FontSize="10"/>
</StackPanel>
</StackPanel>
</DataTemplate>
</ListBox.ItemTemplate>
</ListBox>
NetworkAdapters is a collection of View Models that implement INotifyDataErrorInfo.
With the current XAML, if there is an error in any of the View Models the whole ListBox will be highlighted red, but I would like just the single ListBoxItems that contains errors to be highlighted.
I had a look at similar questions such as:
WPF ListBox ErrorTemplate and
Validating a ListBoxItem rather than a ListBox
But I still can't make this work. Any help would be appreciated.
UPDATE:
As per Krzysztof's advice, I tried wrapping the StackPanel around a border and using a DataTrigger as such:
<ListBox.ItemTemplate>
<DataTemplate>
<Border BorderThickness="1">
<Border.Resources>
<Style TargetType="Border">
<Style.Triggers>
<DataTrigger Binding="{Binding HasErrors}" Value="True">
<Setter Property="BorderBrush" Value="Red"/>
</DataTrigger>
</Style.Triggers>
</Style>
</Border.Resources>
<StackPanel> ... </StackPanel>
</Border>
</DataTemplate>
</ListBox.ItemTemplate>
However, what this produces is the following:
Which is slightly different from what I expected. I would like to have the highlight around the whole ListBoxItem not just part of it as per the image.
You need to implement an ItemContainerStyle as below:
<ListBox.ItemContainerStyle>
<Style TargetType="ListBoxItem">
<Setter Property="BorderThickness" Value="1" />
<Setter Property="BorderBrush" Value="Transparent" />
<Style.Triggers>
<DataTrigger Binding="{Binding Validation.HasErrors}" Value="True">
<Setter Property="BorderBrush" Value="Red"/>
</DataTrigger>
</Style.Triggers>
</Style>
</ListBox.ItemContainerStyle>
This will enable you to change the border of the ListBoxItem itself, so the whole things as you want.
You can forget about ErrorTemplate and just use DataTrigger to bind to Validation.HasErrors:
<StackPanel>
<StackPanel.Resources>
<Style TargetType="{x:Type StackPanel}" BasedOn="{StaticResource {x:Type StackPanel}}">
<Style.Triggers>
<DataTrigger Binding="{Binding Validation.HasErrors}" Value="True"> <!-- change all text to red -->
<Setter Property="Foreground" Value="Red"/>
</DataTrigger>
</Style.Triggers>
</Style>
</StackPanel.Resources>
</StackPanel>
If you want a highlight, you can wrap StackPanel with a Border and set its color to red in a style.

How to bind text alignment in GridViewColumn

I'm using MVVM in a WPF application. I've a UserControl whose DataContext is set to a ViewModel. The UserControl is a table with 3 columns.
This is the following ListView/GridView:
<DockPanel>
<ListView DockPanel.Dock="Top" ItemsSource="{Binding Items}" ScrollViewer.HorizontalScrollBarVisibility="Disabled"
ctrl:ListViewLayoutManager.Enabled="True">
<ListView.View>
<GridView>
<GridViewColumn Header="{Binding ColumnHeader1}"
CellTemplate="{StaticResource cellTemplateColumn1}">
<GridViewColumn.HeaderContainerStyle>
<Style BasedOn="{StaticResource {x:Type GridViewColumnHeader}}" TargetType="GridViewColumnHeader">
<Setter Property="TextBlock.TextAlignment" Value="{Binding Column1HAlignment}" />
</Style>
</GridViewColumn.HeaderContainerStyle>
</GridViewColumn>
<GridViewColumn Header="{Binding ColumnHeader2}"
CellTemplate="{StaticResource cellTemplateColumn2}">
<GridViewColumn.HeaderContainerStyle>
<Style BasedOn="{StaticResource {x:Type GridViewColumnHeader}}" TargetType="GridViewColumnHeader">
<Setter Property="TextBlock.TextAlignment" Value="{Binding Column2HAlignment}" />
</Style>
</GridViewColumn.HeaderContainerStyle>
</GridViewColumn>
<GridViewColumn Header="{Binding ColumnHeader3}"
CellTemplate="{StaticResource cellTemplateColumn3}"
ctrl:RangeColumn.IsFillColumn="True">
<GridViewColumn.HeaderContainerStyle>
<Style BasedOn="{StaticResource {x:Type GridViewColumnHeader}}" TargetType="GridViewColumnHeader">
<Setter Property="TextBlock.TextAlignment" Value="{Binding Column3HAlignment}" />
</Style>
</GridViewColumn.HeaderContainerStyle>
</GridViewColumn>
</GridView>
</ListView.View>
</ListView>
</DockPanel>
This is achieved by the following resources:
<Style TargetType="GridViewColumnHeader">
<Setter Property="Template">
<Setter.Value>
<ControlTemplate TargetType="GridViewColumnHeader">
<TextBlock Text="{TemplateBinding Content}" Margin="5" Width="{TemplateBinding Width}">
</TextBlock>
</ControlTemplate>
</Setter.Value>
</Setter>
<Setter Property="FontWeight" Value="Bold" />
<Setter Property="FontSize" Value="14" />
</Style>
<Style TargetType="ListViewItem">
<Setter Property="HorizontalContentAlignment" Value="Stretch" />
<Setter Property="VerticalAlignment" Value="Stretch" />
<Setter Property="VerticalContentAlignment" Value="Center" />
</Style>
<DataTemplate x:Key="cellTemplateColumn1">
<fa:ImageAwesome Icon="{Binding IconType}" Foreground="{Binding Status}" Width="10" />
</DataTemplate>
<DataTemplate x:Key="cellTemplateColumn2">
<TextBlock Text="{Binding SerialNumber, StringFormat='0'}" />
</DataTemplate>
<DataTemplate x:Key="cellTemplateColumn3">
<TextBlock Text="{Binding Action}" TextWrapping="Wrap" />
</DataTemplate>
The text alignments of each column is settable through the view model. I can do this easily for the headers:
<GridViewColumn.HeaderContainerStyle>
<Style BasedOn="{StaticResource {x:Type GridViewColumnHeader}}" TargetType="GridViewColumnHeader">
<Setter Property="TextBlock.TextAlignment" Value="{Binding Column1HAlignment}" />
</Style>
</GridViewColumn.HeaderContainerStyle>
But how can I pass the same parameter (Column1HAlignment) to align the cell's TextBlock contents?
Note the alignment of the 2nd column (where header is #) in the image above. The header is right aligned through data binding. I want the same binding for each cell content.
You could bind the HorizontalAlignment property of the element in the CellTemplate to the Column1HAlignment source property using a {RelativeSource}:
<DataTemplate x:Key="cellTemplateColumn2">
<TextBlock Text="{Binding SerialNumber, StringFormat='0'}"
HorizontalAlignment="{Binding DataContext.Column1HAlignment,
RelativeSource={RelativeSource AncestorType=ListView}}"/>
</DataTemplate>

ListBox contextMenu SubItem Click

I have ListBox with ContextMenu, I have configured MenuItem to works only if I click in selected item.
This is XAML code:
<ListBox x:Name="MessagesLb" Grid.Column="1" Margin="241,100,22.4,50" Grid.Row="1" BorderThickness="0" FontSize="14" FontWeight="SemiBold" ItemsSource="{Binding Items}" SelectionMode="Extended">
<ListBox.ItemContainerStyle>
<Style TargetType="ListBoxItem">
<Setter Property="ContextMenu">
<Setter.Value>
<ContextMenu>
<MenuItem Header="Copia" Click="MessagesLbCopySubMi_Click" />
<Separator/>
<MenuItem Header="Dettagli" />
</ContextMenu>
</Setter.Value>
</Setter>
<Style.Triggers>
<DataTrigger Binding="{Binding SelectedItems.Count, RelativeSource={RelativeSource AncestorType={x:Type ListBox}}}" Value="0">
<Setter Property="ContextMenu" Value="{x:Null}" />
</DataTrigger>
</Style.Triggers>
</Style>
</ListBox.ItemContainerStyle>
<ListBox.ItemTemplate>
<DataTemplate>
<TextBlock Text="{Binding}" HorizontalAlignment="Center" VerticalAlignment="Center" />
</DataTemplate>
</ListBox.ItemTemplate>
</ListBox>
I tried to add Click event in my MenuItem but it doesn't work.
Example:
<MenuItem Header="Copia" Click="MessagesLbCopySubMi_Click" />
private void MessagesLbCopySubMi_Click(object sender, RoutedEventArgs e)
{
MessageBox.Show("Click event done");
}
How can I solve this?
Define the ContextMenu as a resource:
<ListBox x:Name="MessagesLb" Grid.Column="1" Margin="241,100,22.4,50" Grid.Row="1" BorderThickness="0" FontSize="14" FontWeight="SemiBold"
ItemsSource="{Binding Items}" SelectionMode="Extended">
<ListBox.Resources>
<ContextMenu x:Key="cm">
<MenuItem Header="Copia" Click="MessagesLbCopySubMi_Click" />
<Separator/>
<MenuItem Header="Dettagli" />
</ContextMenu>
</ListBox.Resources>
<ListBox.ItemContainerStyle>
<Style TargetType="ListBoxItem">
<Setter Property="ContextMenu" Value="{StaticResource cm}" />
<Style.Triggers>
<DataTrigger Binding="{Binding SelectedItems.Count, RelativeSource={RelativeSource AncestorType={x:Type ListBox}}}" Value="0">
<Setter Property="ContextMenu" Value="{x:Null}" />
</DataTrigger>
</Style.Triggers>
</Style>
</ListBox.ItemContainerStyle>
<ListBox.ItemTemplate>
<DataTemplate>
<TextBlock Text="{Binding}" HorizontalAlignment="Center" VerticalAlignment="Center" />
</DataTemplate>
</ListBox.ItemTemplate>
</ListBox>

WPF ListBoxItem detect IsMouseOver inside a custom item

i have a ListBox which I fill with custom items. I want to detect a MouseOver event from a ListBoxItem inside the item in order to change visibility of a button. I have checked most of the answers on StackOverflow, the following solution was what I was looking for, but it doesn't work.
This is a code snippet from my ContactsView:
<ListBox ScrollViewer.CanContentScroll="False" VerticalContentAlignment="Top" ScrollViewer.ScrollChanged="ListBox_OnScrollChanged" BorderThickness="0,0,0,0" Margin="0,0,0,0" Padding="0" BorderBrush="{StaticResource ResourceKey=PrimaryColor}" Name="ListBox" ItemsSource="{Binding ListBoxItemsSource}" HorizontalContentAlignment="Stretch">
<i:Interaction.Triggers>
<events:RoutedEventTrigger RoutedEvent="ScrollViewer.ScrollChanged">
<i:InvokeCommandAction Command="{Binding Path=ListBoxScrollChangedCommand}" />
</events:RoutedEventTrigger>
<i:EventTrigger EventName="Loaded">
<i:InvokeCommandAction Command="{Binding Path=ListBoxLoadedCommand}" />
</i:EventTrigger>
</i:Interaction.Triggers>
<ListBox.ItemContainerStyle>
<Style TargetType="ListBoxItem">
<Setter Property="Padding" Value="0"/>
<Setter Property="BorderThickness" Value="0"/>
<Style.Triggers>
<Trigger Property="ListBoxItem.IsMouseOver" Value="True">
<Setter Property="Background" Value="{StaticResource PrimaryColor}"/>
</Trigger>
<Trigger Property="ListBoxItem.IsMouseOver" Value="False">
<Setter Property="Background" Value="Transparent"/>
</Trigger>
</Style.Triggers>
</Style>
</ListBox.ItemContainerStyle>
<ListBox.ItemTemplate>
<DataTemplate>
<StackPanel>
<!-- Custom item -->
<items:ItemCorporateContact Value="{Binding Path=., Mode=TwoWay}" HorizontalAlignment="Stretch" VerticalAlignment="Top" />
<Separator Height="1" Margin="0" Background="#ececec" />
</StackPanel>
</DataTemplate>
</ListBox.ItemTemplate>
</ListBox>
And I have been trying to detect the event this way (code from a custom item that I add to a ListBox):
<Button Name="StartCallButton" VerticalAlignment="Center" Background="Red" Margin="10" HorizontalAlignment="Left">
<Button.Content>
<Image Source="{StaticResource PhoneIconBitmap}"></Image>
</Button.Content>
<Button.Style>
<Style TargetType="{x:Type Button}">
<Setter Property="Visibility" Value="Hidden" />
<Style.Triggers>
<DataTrigger Binding="{Binding RelativeSource={RelativeSource Mode=FindAncestor, AncestorType={x:Type ListBoxItem}},Path=IsMouseOver}" Value="True">
<Setter Property="Visibility" Value="Visible" />
</DataTrigger>
</Style.Triggers>
</Style>
</Button.Style>
</Button>
Any help will be greatly appreciated.
I had been searching for the same thing. Although the answer is provided in the question, but to specify the answer more clearly, following is the code that works for me.
<ListBox.ItemTemplate>
<DataTemplate>
<StackPanel x:Name="ViewTypeStackPanel" Orientation="Horizontal">
<Border BorderThickness="2,0,0,0" Visibility="{Binding Path=IsSelected, Converter={StaticResource BooleanToVisibilityConverterInstance}}" BorderBrush="Blue"/>
<Image Height="32" Width="32">
<Image.Style>
<Style TargetType="Image">
<Setter Property="Source" Value="{Binding Path=ImagePath}"/>
<Style.Triggers>
<DataTrigger Binding="{Binding RelativeSource={RelativeSource Mode=FindAncestor, AncestorType={x:Type ListBoxItem}}, Path=IsMouseOver}" Value="True">
<Setter Property="Source" Value="{Binding Path=ImagePathHover}"/>
</DataTrigger>
</Style.Triggers>
</Style>
</Image.Style>
</Image>
</StackPanel>
</DataTemplate>
</ListBox.ItemTemplate>

Context menu on Treeview

How can I have a context menu in a DataTrigger on treeview? The code below does not trigger the context menu eg I want the menu on "Symbols" as well. Although I have a context menu on HierarchicalDataTemplate which works fine but only on child elements. The root on the treeview does not have a menu
<HierarchicalDataTemplate x:Key="NameTemplate" ItemsSource="{Binding Path=ChildPlanner}">
<HierarchicalDataTemplate.ItemContainerStyle>
<Style>
<Style.Triggers>
<DataTrigger Binding="{Binding IsFolder}" Value="True">
<Setter Property="TreeViewItem.ContextMenu" Value="{StaticResource AddNewSymbol}"/>
</DataTrigger>
</Style.Triggers>
</Style>
</HierarchicalDataTemplate.ItemContainerStyle>
<StackPanel Orientation="Horizontal" Margin="2">
<TextBlock Text="{Binding Path=Name}" FontWeight="Bold">
</TextBlock>
</StackPanel>
</HierarchicalDataTemplate>
<TreeView Name="SymbolsTreeView">
<TreeView.ItemContainerStyle>
<Style TargetType="{x:Type TreeViewItem}">
<Style.Triggers>
<DataTrigger Binding="{Binding IsFolder}" Value="True">
<Setter Property="ContextMenu" Value="{StaticResource AddNewSymbol}"/>
</DataTrigger>
</Style.Triggers>
</Style>
</TreeView.ItemContainerStyle>
<TreeViewItem Header="Symbols" IsExpanded="True" ItemsSource="{Binding PlannerTreeList}" ItemTemplate="{StaticResource NameTemplate}"/>
</TreeView>
Imagine my tree is
Symbols
Current
Menu1Folder
Menu2Folder
Menu2Item
Menu2AnotherItem
Current1Item
The HierarchicalDataTemplate's menu works for menu1folder onwards which is ok. But I want it to work for Current1Item, Current and Symbols. Since Current1Item is not a folder, there should be no menu for it but Current and Symbols are folders
<TreeView.Resources>
<Style TargetType="{x:Type TreeViewItem}">
<Setter Property="ContextMenu" Value="{StaticResource AddNewSymbol}"/>
<Style.Triggers>
<DataTrigger Binding="{Binding IsFolder,RelativeSource={RelativeSource Self}}" Value="False">
<Setter Property="ContextMenu" Value="{x:Null}"/>
</DataTrigger>
</Style.Triggers>
</Style>
</TreeView.Resources>
Edit - Try this new code. I am using a converter to show and hide the contextmenu based on your property. It works with my sample code. Let me know if you want my sample code.
<Grid>
<Grid.Resources>
<BooleanToVisibilityConverter x:Key="VisibilityConverter" />
<ContextMenu x:Key="MenuOne" Visibility="{Binding IsFolder,Converter={StaticResource VisibilityConverter}}">
<MenuItem Header="Add Folder" Command="{Binding AddFolderCommand}"/>
<MenuItem Header="Add Item" Command="{Binding AddItemCommand}"/>
</ContextMenu>
</Grid.Resources>
<TreeView Name="SymbolsTreeView" ItemsSource="{Binding Items}">
<TreeView.Resources>
<HierarchicalDataTemplate DataType="{x:Type local:MyTreeViewItem}" ItemsSource="{Binding Items}">
<ContentControl>
<TextBlock Text="{Binding Name}"/>
</ContentControl>
</HierarchicalDataTemplate>
<Style TargetType="TreeViewItem">
<Setter Property="ContextMenu" Value="{StaticResource MenuOne}"/>
<Setter Property="IsExpanded" Value="True"/>
</Style>
</TreeView.Resources>
</TreeView>
</Grid>

Categories

Resources