I've been doing a lot of research and I don't understand exaclty how to properly bind an ICommand to my TreeView item.
I have a VM that holds a Data Object.
This data object holds an ObservableCollection object of TreeNode item objects.
This TreeNode Item has properties such as Tag, IsSelected, Header and even a ContextMenu.
However I can't figure out how to bind an ICommand to a single TreeView item.
Here is my XAML of my TreeView
<TreeView Grid.Column="0" Grid.Row="0"
Grid.ColumnSpan="1" Grid.RowSpan="5"
x:Name="TestPlanTreeView"
ItemsSource="{Binding Data.TestPlanCollection}"
dd:DragDrop.IsDragSource="True"
dd:DragDrop.IsDropTarget="True">
<TreeView.ItemContainerStyle>
<Style TargetType="TreeViewItem">
<Setter Property="Focusable" Value="{Binding Focusable, Mode=TwoWay}"/>
<Setter Property="IsSelected" Value="{Binding IsSelected, Mode=TwoWay}"/>
<Setter Property="IsExpanded" Value="{Binding IsExpanded, Mode=TwoWay}"/>
<Style.Triggers>
<Trigger Property="IsSelected" Value="True">
<Setter Property="FontWeight" Value="Bold"/>
<Setter Property="FontStyle" Value="Italic"/>
</Trigger>
</Style.Triggers>
</Style>
</TreeView.ItemContainerStyle>
<TreeView.ItemTemplate>
<HierarchicalDataTemplate ItemsSource="{Binding Children}">
<TextBlock Text="{Binding Header}" Margin="2">
<TextBlock.ContextMenu>
<ContextMenu ItemsSource="{Binding MenuCollection}">
<ContextMenu.ItemContainerStyle>
<Style TargetType="MenuItem">
<Setter Property="Header" Value="{Binding Header}"/>
<Setter Property="Command" Value="{Binding Command}"/>
<Setter Property="CommandParameter" Value="{Binding CommandParameter}"/>
</Style>
</ContextMenu.ItemContainerStyle>
</ContextMenu>
</TextBlock.ContextMenu>
</TextBlock>
</HierarchicalDataTemplate>
</TreeView.ItemTemplate>
</TreeView>
Related
I am trying to create a Combobox with updatable collection. Than I would to display special content inside combobox if collection which binding to combobox in null.
I had try to set TargetNullValue parameter but it's display content not as I expected.
<ComboBox ItemsSource="{Binding MyCollection, UpdateSourceTrigger=PropertyChanged, TargetNullValue='No Items'}"/>
What I Have Now
What I Expect
You may replace the ComboBox's Template when it has no items, e.g. with a TextBlock or any other visual element.
<ComboBox>
<ComboBox.Style>
<Style TargetType="ComboBox">
<Style.Triggers>
<Trigger Property="HasItems" Value="False">
<Setter Property="Template">
<Setter.Value>
<ControlTemplate TargetType="ComboBox">
<TextBlock Text="No Items"/>
</ControlTemplate>
</Setter.Value>
</Setter>
</Trigger>
</Style.Triggers>
</Style>
</ComboBox.Style>
</ComboBox>
Try this (I can’t check it myself - I don’t have a computer “at hand”):
<ComboBox>
<ComboBox.Resources>
<CompositeCollection x:Key="noItems">
<sys:String>No Items</sys:String>
</CompositeCollection>
</ComboBox.Resources>
<ComboBox.Style>
<Style TergetType="ComboBox">
<Setter Property="ItemsSource" Value="{Binding MyCollection}"/>
<Style.Triggers>
<DataTrigger Binding="{Binding MyCollection.Count}"
Value="0">
<Setter Property="ItemsSource" Value="{DynamicResource noItems}"/>
<Setter Property="SelectedIndex" Value="0"/>
</DataTrigger>
</Style.Triggers>
</Style>
<ComboBox.Style>
</ComboBox>
Or such variant for TargetNullValue:
<ComboBox>
<ComboBox.ItemsSource>
<Binding Path="MyCollection">
<Binding.TargetNullValue>
<CompositeCollection>
<sys:String>No Items</sys:String>
</CompositeCollection>
</Binding.TargetNullValue>
</Binding>
</ComboBox.ItemsSource>
</ComboBox>
I am using the following code for the Treeview object:
<TreeView x:Name="RegisteredServer" ItemsSource="{Binding RegisteredServerList}" Grid.Row="1">
<TreeView.ItemTemplate>
<HierarchicalDataTemplate ItemsSource="{Binding Children}">
<StackPanel Orientation="Horizontal">
<Image Margin="0,0,4,0" Source="{Binding Icon}" Width="16" Height="16" />
<TextBlock Text="{Binding Name}" FontWeight="SemiBold" />
</StackPanel>
</HierarchicalDataTemplate>
</TreeView.ItemTemplate>
<TreeView.ItemContainerStyle>
<Style TargetType="{x:Type TreeViewItem}">
<Setter Property="Foreground" Value="{x:Reference Name=LabelForeground}"></Setter>
<Setter Property="IsExpanded" Value="True"/>
<EventSetter Event="PreviewMouseRightButtonDown"
Handler="OnPreviewMouseRightButtonDown" />
<EventSetter Event="Selected" Handler="Tree_SelectedItemChanged"/>
<EventSetter Event="MouseDoubleClick" Handler="OnItemMouseDoubleClick"/>
</Style>
</TreeView.ItemContainerStyle>
</TreeView>
And I have the following code in the style file named Style.xaml:
<Style TargetType="TreeView">
<Setter Property="Control.Background" Value="Red"></Setter>
<Style.Resources>
<Style TargetType="TreeViewItem">
<Setter Property="Background" Value="Orange"></Setter>
<Setter Property="Foreground" Value="White"></Setter>
</Style>
</Style.Resources>
</Style>
Since I use in the part where I assign the event, the code in my style file does not work.
I cannot write event and style codes together in because I use theme structure.
How should I proceed here? Thank you from now.
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.
I have the following WPF ListView where I change the template depending on the property ChangeView in my class MyItemsClass.
<ListView x:Name="MyListView" ItemsSource="{Binding}">
<ListView.Style>
<Style>
<Style.Triggers>
<DataTrigger Binding="{Binding RelativeSource={RelativeSource FindAncestor, AncestorType={x:Type myClass:MyItemsClass}}, Path=ChangeView}" Value="True">
<Setter Property="ListView.ItemsPanel" Value="{StaticResource largeTemplate}"/>
</DataTrigger>
<DataTrigger Binding="{Binding RelativeSource={RelativeSource FindAncestor, AncestorType={x:Type myClass:MyItemsClass}}, Path=ChangeView}" Value="False">
<Setter Property="ListView.ItemsPanel" Value="{StaticResource smallTemplate }" />
</DataTrigger>
</Style.Triggers>
</Style>
</ListView.Style>
<ItemsPanelTemplate x:Key="largeTemplate">
<WrapPanel Orientation="Horizontal" IsItemsHost="True">
<WrapPanel.Resources>
<BooleanToVisibilityConverter x:Key="BoolToVisibility"/>
<HierarchicalDataTemplate DataType="{x:Type local:MyData}">
<Button Command="{Binding}" Visibility="{Binding RelativeSource={RelativeSource Self}, Path=IsEnabled, Converter={StaticResource BoolToVisibility}}">
<Image Height="100" Width="100" Source="{Binding Path=MyImage}"/>
</Button>
</HierarchicalDataTemplate>
</WrapPanel.Resources>
</WrapPanel>
</ItemsPanelTemplate>
smallTemplate is the same as largeTemplate XAML only with different sizes of iamge.
Now I want to 'skin' my ListView with an existing style of ListView so if I do the following it works
<ListView x:Name="MyListView" ItemsSource="{Binding}" ItemContainerStyle="{DynamicResource MyStyle}">
</ListView>
<Style x:Key="MyStyle" TargetType="{x:Type ListViewItem}" BasedOn="{StaticResource ListViewItemStyle}">
<Setter Property="Template">
<Setter.Value>
<ControlTemplate TargetType="ListViewItem">
<ContentPresenter />
</ControlTemplate>
</Setter.Value>
</Setter>
</Style>
So now I am attempting to add in the original triggers like so:
<ListView x:Name="MyListView" ItemsSource="{Binding}" ItemContainerStyle="{DynamicResource MyStyle}">
</ListView>
<Style x:Key="MyStyle" TargetType="{x:Type ListViewItem}" BasedOn="{StaticResource ListViewItemStyle}">
<Setter Property="Template">
<Setter.Value>
<ControlTemplate TargetType="ListViewItem">
<ContentPresenter />
</ControlTemplate>
</Setter.Value>
</Setter>
<Style.Triggers>
<DataTrigger Binding="{Binding RelativeSource={RelativeSource FindAncestor, AncestorType={x:Type myClass:MyItemsClass}}, Path=ChangeView}" Value="True">
<Setter Property="ListView.ItemsPanel" Value="{StaticResource largeTemplate}"/>
</DataTrigger>
<DataTrigger Binding="{Binding RelativeSource={RelativeSource FindAncestor, AncestorType={x:Type myClass:MyItemsClass}}, Path=ChangeView}" Value="False">
<Setter Property="ListView.ItemsPanel" Value="{StaticResource smallTemplate }" />
</DataTrigger>
</Style.Triggers>
</Style>
But now my DataTriggers do not work - the ListView.ItemsPanel does not change when my class variable is changed. How canI get this to work?
Try this instead:
<ListView x:Name="MyListView" ItemsSource="{Binding}" Style="{DynamicResource MyStyle}">
</ListView>
<Style x:Key="MyStyle" TargetType="{x:Type ListView}" BasedOn="{StaticResource ListViewStyle}">
<Setter Property="ItemTemplate">
<Setter.Value>
<ControlTemplate TargetType="ListViewItem">
<ContentPresenter />
</ControlTemplate>
</Setter.Value>
</Setter>
<Style.Triggers>
<DataTrigger Binding="{Binding RelativeSource={RelativeSource FindAncestor, AncestorType={x:Type myClass:MyItemsClass}}, Path=ChangeView}" Value="True">
<Setter Property="ListView.ItemsPanel" Value="{StaticResource largeTemplate}"/>
</DataTrigger>
<DataTrigger Binding="{Binding RelativeSource={RelativeSource FindAncestor, AncestorType={x:Type myClass:MyItemsClass}}, Path=ChangeView}" Value="False">
<Setter Property="ListView.ItemsPanel" Value="{StaticResource smallTemplate }" />
</DataTrigger>
</Style.Triggers>
</Style>
I changed your style to target the Style of the ListView itself, rather than its ItemContainerStyle property, and then I changed <Setter Property="Template"> to <Setter Property="ItemTemplate"> so that it will only override the template for your items, rather then overriding the template for the whole ListView.
So you have a working ListView style? Why don't you simply set the ItemContainerStyle property of it to your "MyStyle"?:
<ListView x:Name="MyListView" ItemsSource="{Binding}" ItemContainerStyle="{DynamicResource MyStyle}">
<ListView.Style>
<Style>
<Style.Triggers>
<DataTrigger Binding="{Binding RelativeSource={RelativeSource FindAncestor, AncestorType={x:Type myClass:MyItemsClass}}, Path=ChangeView}" Value="True">
<Setter Property="ListView.ItemsPanel" Value="{StaticResource largeTemplate}"/>
</DataTrigger>
<DataTrigger Binding="{Binding RelativeSource={RelativeSource FindAncestor, AncestorType={x:Type myClass:MyItemsClass}}, Path=ChangeView}" Value="False">
<Setter Property="ListView.ItemsPanel" Value="{StaticResource smallTemplate }" />
</DataTrigger>
</Style.Triggers>
</Style>
</ListView.Style>
</ListView>
MyStyle applies to a ListViewItem and not to a ListView so this should work.
Or what is the "skin" that you are trying to apply?
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>