Button ContextMenu Image only shown at last item - c#

I have a problem with a dynamic ContextMenu on a Button.
The Button is inside a DataGrid with the following Columndefinition in a ResourceDictionary:
<DataGridTemplateColumn Width="16" x:Key="ShowContextMenuColumn" x:Shared="False" KeyboardNavigation.IsTabStop="False">
<DataGridTemplateColumn.CellTemplate>
<DataTemplate>
<Button Style="{StaticResource ShowContextMenuButtonStyle}">
<Button.ContextMenu>
<ContextMenu ItemsSource="{Binding Data.ContextActionItems, Source={StaticResource proxy}}" DisplayMemberPath="DisplayText">
<ContextMenu.Resources>
<Style TargetType="{x:Type MenuItem}">
<Setter Property="Icon">
<Setter.Value>
<Image Source="{Binding Image}"/>
</Setter.Value>
</Setter>
<Setter Property="Command" Value="{Binding Command}"/>
</Style>
</ContextMenu.Resources>
</ContextMenu>
</Button.ContextMenu>
</Button>
</DataTemplate>
</DataGridTemplateColumn.CellTemplate>
<DataGridTemplateColumn.CellStyle>
<Style TargetType="{markup:Type DataGridCell}" BasedOn="{StaticResource BaseDataGridCellStyle}">
<Setter Property="AutomationProperties.Name" Value="ContextMenu"/>
</Style>
</DataGridTemplateColumn.CellStyle>
</DataGridTemplateColumn>
The ObservableCollection<DisplayListActionItem> for the ContextActionItems is defined in an abstract generic base-class which is inherited by several ViewModels.
The DisplayListActionItem-Class looks like:
public class DisplayListActionItem : NotifyBase
{
public string DisplayText
{
get { return Get<string>(); }
set { Set(value); }
}
public ICommand Command
{
get { return Get<ICommand>(); }
set { Set(value); }
}
public ImageSource Image
{
get { return Get<ImageSource>(); }
set { Set(value); }
}
}
Everything just works fine. The only problem is, that the Image is only shown at the last item of the ContextMenu. If I remove the last item, then the image of the new last item is shown. I don't understand what's the reason for this.
I've already checked the Debug-Output for Binding-Errors, but there are none.
Any ideas what could cause this problem?

Solved it by myself.
The solution was to modify the ContextMenu-ItemContainerStyle instead doing the needed stuff in the Resources of the ContextMenu.
The new xaml for the Button looks like:
<Button Style="{StaticResource ShowContextMenuButtonStyle}">
<Button.ContextMenu>
<ContextMenu ItemsSource="{Binding Data.ContextActionItems, Source={StaticResource proxy}}" >
<ContextMenu.ItemContainerStyle>
<Style TargetType="{x:Type MenuItem}">
<Setter Property="Icon" Value="{Binding Image, Converter={converter:ImageSourceToImageConverter}}"/>
<Setter Property="Header" Value="{Binding DisplayText}"/>
<Setter Property="Command" Value="{Binding Command}"/>
</Style>
</ContextMenu.ItemContainerStyle>
</ContextMenu>
</Button.ContextMenu>
</Button>

Related

WPF Datagrid Grouping using a parent object as the expander header

I am using WPF Datagrid to group an observable collection by Parent. I have been following the example here and other examples showing a parent child relationship. So far I have the following:
And I want to get something like this:
Where the Header of a group is just a row as well but still able to collapse/expand child rows. I have tried to make a sub-datagrid without luck. So the underlying type of my collection looks something like this:
public class Task : INotifyPropertyChanged, IEditableObject
{
// memebers
...
public string ProjectName
{
get { return this.m_ProjectName; }
set
{
if (value != this.m_ProjectName)
{
this.m_ProjectName = value;
NotifyPropertyChanged("ProjectName");
}
}
}
public string TaskName
{
get { return this.m_TaskName; }
set
{
if (value != this.m_TaskName)
{
this.m_TaskName = value;
NotifyPropertyChanged("TaskName");
}
}
}
public DateTime DueDate
{
get { return this.m_DueDate; }
set
{
if (value != this.m_DueDate)
{
this.m_DueDate = value;
NotifyPropertyChanged("DueDate");
}
}
}
public bool Complete
{
get { return this.m_Complete; }
set
{
if (value != this.m_Complete)
{
this.m_Complete = value;
NotifyPropertyChanged("Complete");
}
}
}
public Task Parent
{
get { return m_Parent; }
set
{
m_Parent = value;
}
}
So my object type can either by a child or parent, where a child has a reference to its parent. So I am grouping by parent, I just haven't figured out how to make the expandable group headers rows of the same type. Any help is appreciated.
Here is the xaml:
<DataGrid x:Name="dataGrid1"
ItemsSource="{Binding Source={StaticResource cvsTasks}}"
CanUserAddRows="False"
ColumnWidth="*"
RowHeaderWidth="0">
<DataGrid.GroupStyle>
<!-- Style for groups at top level. -->
<GroupStyle>
<GroupStyle.ContainerStyle>
<Style TargetType="{x:Type GroupItem}">
<Setter Property="Margin" Value="0,0,0,2"/>
<Setter Property="Template">
<Setter.Value>
<ControlTemplate TargetType="{x:Type GroupItem}">
<Expander IsExpanded="True" Background="#FF112255" BorderBrush="#FF002255" Foreground="#FFEEEEEE" BorderThickness="1,1,1,5">
<Expander.Header>
<DockPanel HorizontalAlignment="Stretch" >
<TextBlock FontWeight="Bold" Text="{Binding Path=Name, Converter={StaticResource completeConverter}}" Margin="5,0,0,0" Width="200" HorizontalAlignment="Stretch"/>
<TextBlock FontWeight="Bold" Text="{Binding Path=ItemCount}" Width="Auto"/>
</DockPanel>
<!--<DataGrid x:Name="dataGrid2"
ItemsSource="{Binding Source={StaticResource vsParentTasks}}"
CanUserAddRows="False"
ColumnWidth="*"
RowHeaderWidth="0"
HeadersVisibility="None" >
<DataGrid.RowStyle>
<Style TargetType="DataGridRow">
<Setter Property="Foreground" Value="#FFEEEEEE" />
<Setter Property="Background" Value="#FF112255" />
<Setter Property="HorizontalAlignment" Value="Stretch" />
</Style>
</DataGrid.RowStyle>
</DataGrid>-->
</Expander.Header>
<Expander.Content>
<ItemsPresenter />
</Expander.Content>
</Expander>
</ControlTemplate>
</Setter.Value>
</Setter>
</Style>
</GroupStyle.ContainerStyle>
</GroupStyle>
</DataGrid.GroupStyle>
<DataGrid.RowStyle>
<Style TargetType="DataGridRow">
<Setter Property="Foreground" Value="Black" />
<Setter Property="Background" Value="White" />
</Style>
</DataGrid.RowStyle>
</DataGrid>

Data Binding to menu using multiple data types and being able to set the icon

Before you mark as duplicate, I have read the other SO entries, none seem to apply
I have two classes, one for menu item groups which will comprise my root menu items, and one for the sub items as such
public class RootItem
{
public string name {get;set;}
public image icon {get;set;}
public List<SubItem> subItems {get;set;}
}
public class SubItem
{
public string name {get;set;}
public image icon {get;set;}
}
The rest of what I am trying to do is probibly obvious but ill outline it anyway:
<MenuItem ItemsSource="{Binding Path=RootItems}" Header="Menu">
<MenuItem.Resources>
<DataTemplate DataType="{x:Type root:RootItem}">
<DataTemplate.Resources>
<Style TargetType="{x:Type MenuItem}">
<Setter Property="Header" Value="{Binding name}"/>
<Setter Property="ItemsSource" Value="{Binding subItems}"/>
<Setter Property="Icon" Value="{Binding icon}"/>
</Style>
</DataTemplate.Resources>
</DataTemplate>
<DataTemplate DataType="{x:Type root:SubItem}">
<DataTemplate.Resources>
<Style TargetType="{x:Type MenuItem}">
<Setter Property="Header" Value="{Binding name}"/>
<Setter Property="Icon" Value="{Binding icon}"/>
</Style>
</DataTemplate.Resources>
</DataTemplate>
</MenuItem.Resources>
</MenuItem>
This is just one of the methods I tried, in this one it doesn't seem like the style is used at all as only the root items show but they are blank, no icon or name.
I must be able to use 2 separate classes and the icon must be set-able and there must be 2 data templates or styles to handle/show the 2 classes differently.
I have also tried this, which while a mess, gets me 90% of the way, I just have issues where the icon is only visible on the last menu item.
<MenuItem ItemsSource="{Binding Path=RootItems}" Header="Menu">
<MenuItem.Resources>
<imico:InputMapperIcon x:Key="ico" Icon="{Binding icon}" x:Shared="false"/>
<HierarchicalDataTemplate DataType="{x:Type root:RootItem}" x:Shared="false" ItemsSource="{Binding subItems}">
<HierarchicalDataTemplate.ItemContainerStyle>
<Style>
<Setter Property="MenuItem.Header" Value="{Binding name}"/>
<Setter Property="MenuItem.Icon" Value="{StaticResource ico}"/>
<Setter Property="MenuItem.Tag" Value="{Binding RelativeSource={RelativeSource FindAncestor, AncestorType={x:Type Menu}}, Path=DataContext}"/>
<EventSetter Event="MenuItem.Click" Handler="BtnAddCommand_Click"/>
</Style>
</HierarchicalDataTemplate.ItemContainerStyle>
</HierarchicalDataTemplate>
</MenuItem.Resources>
<MenuItem.ItemContainerStyle>
<Style>
<Setter Property="MenuItem.Header" Value="{Binding name}"/>
<Setter Property="MenuItem.Icon" Value="{StaticResource ico}"/>
</Style>
</MenuItem.ItemContainerStyle>
</MenuItem>
here is a working example, copied from your second xaml snippet:
.xaml
<Grid Background="Transparent">
<Grid.ContextMenu>
<ContextMenu>
<MenuItem ItemsSource="{Binding Path=RootItems}" Header="Menu">
<MenuItem.Resources>
<HierarchicalDataTemplate DataType="{x:Type root:RootItem}" x:Shared="false" ItemsSource="{Binding subItems}">
<HierarchicalDataTemplate.ItemContainerStyle>
<Style>
<Setter Property="MenuItem.Header" Value="{Binding name}"/>
<Setter Property="MenuItem.Icon" Value="{Binding icon}"/>
</Style>
</HierarchicalDataTemplate.ItemContainerStyle>
</HierarchicalDataTemplate>
</MenuItem.Resources>
<MenuItem.ItemContainerStyle>
<Style>
<Setter Property="MenuItem.Header" Value="{Binding name}"/>
<Setter Property="MenuItem.Icon" Value="{Binding icon}"/>
</Style>
</MenuItem.ItemContainerStyle>
</MenuItem>
</ContextMenu>
</Grid.ContextMenu>
</Grid>
.cs
public partial class MainWindow : Window
{
public ObservableCollection<RootItem> RootItems { get; set; }
public MainWindow()
{
InitializeComponent();
RootItems = new ObservableCollection<RootItem>();
var rootitem = new RootItem() { name = "rootitem" };
rootitem.subItems = new List<SubItem>();
var subitem = new SubItem() { name = "subitem" };
subitem.icon = new Image();
subitem.icon.Source = new BitmapImage(new Uri(#"C:\Users\username\source\repos\WpfApp3\WpfApp3\New Bitmap Image.bmp"));
var subitem2 = new SubItem() { name = "subitem2" };
subitem2.icon = new Image();
subitem2.icon.Source = new BitmapImage(new Uri(#"C:\Users\username\source\repos\WpfApp3\WpfApp3\New Bitmap Image.bmp"));
rootitem.subItems.Add(subitem);
rootitem.subItems.Add(subitem2);
RootItems.Add(rootitem);
this.DataContext = this;
}
}
public class RootItem
{
public string name { get; set; }
public Image icon { get; set; }
public List<SubItem> subItems { get; set; }
}
public class SubItem
{
public string name { get; set; }
public Image icon { get; set; }
}
so the mistake i imagine must be in how you assign image to your SubItem(which i removed in my example, i use a staticresource instead), or in your InputMapperIcon

Struggling to template a WPF MenuItem's Icon with control

Following on from a previous question here, I'm struggling to style a MenuItem's Icon with a control I have that inserts icon images based upon a string dependency property.
Initially I started with:
<ContextMenu ItemsSource="{Binding MenuItems}">
<ContextMenu.Resources>
<Style TargetType="MenuItem">
...
<Setter Property="Icon">
<local:StringToIcon IconName="{Binding IconName}" />
</Setter>
</Style>
</ContextMenu.Resources>
</ContextMenu>
This had the predictable effect of only displaying one of the icons in the menu, usually the last one, as the instance was shared around.
I then tried the non-shared resource approach:
<ContextMenu ItemsSource="{Binding MenuItems}">
<ContextMenu.Resources>
<local:StringToIcon x:Key="MenuIcon" x:Shared="False" IconName="{Binding IconName}" />
<Style TargetType="MenuItem">
...
<Setter Property="Icon" Value="{StaticResource MenuIcon} />
</Style>
</ContextMenu.Resources>
</ContextMenu>
This had no effect. It didn't offer me x:Shared in Intellisense, so I wonder if that's an invalid property here.
Out of desperation, I threw the thing into a template:
<Setter Property="Icon">
<Setter.Value>
<ContentControl>
<ContentControl.Template>
<ControlTemplate>
<local:StringToIcon IconName="{Binding IconName}" />
</ControlTemplate>
</ContentControl.Template>
</ContentControl>
</Setter.Value>
</Setter>
Again, no effect. My StringToIcon looks like this at the moment, hard-coded with a single image to check the problem doesn't lie there. (Or does it?)
<UserControl x:Class="RAP.Admin3.Components.StringToIcon"
...
>
<Image DataContext="{Binding ElementName=StringIconControl}" Source="pack://application:,,,/Resources/Icons/lorry.png"/>
</UserControl>
How do I get this darn thing to template and allow multiple uses? It's probably something basic I'm overlooking.
I've looked at various similar questions, and most seem to have success with the non-shared resource method.
Edit: Let me add substantially more code as requested. I've come up with a minimal replication of the problem:
The context menu is part of a TreeView resource.
<UserControl x:Class="MyApp.ItemHierarchy"
...
Name="ItemHierarchyControl">
<Grid>
<TreeView ItemsSource="{Binding ElementName=ItemHierarchyControl, Path=Items}">
<TreeView.Resources>
<HierarchicalDataTemplate DataType="{x:Type local:HierarchyItem}" ItemsSource="{Binding Subitems}">
<StackPanel Orientation="Horizontal" Margin="0,1,4,1">
<TextBlock Text="My text" VerticalAlignment="Center" />
<StackPanel.ContextMenu>
<ContextMenu ItemsSource="{Binding MenuItems}">
<ContextMenu.Resources>
<local:StringToIcon x:Key="MenuIcon" x:Shared="False" IconName="{Binding IconName}" />
<Style TargetType="MenuItem">
<Setter Property="Header" Value="{Binding Path=Name}" />
<Setter Property="Icon" Value="{StaticResource MenuIcon}" />
</Style>
</ContextMenu.Resources>
</ContextMenu>
</StackPanel.ContextMenu>
</StackPanel>
</HierarchicalDataTemplate>
</TreeView.Resources>
</TreeView>
</Grid>
</UserControl>
This is backed by a dependency property for the items.
public ObservableCollection<HierarchyItem> Items
{
get { return (ObservableCollection<HierarchyItem>)GetValue(ItemsProperty); }
set { SetValue(ItemsProperty, value); }
}
public static readonly DependencyProperty ItemsProperty = DependencyProperty.Register("Items", typeof(ObservableCollection<HierarchyItem>), typeof(ItemHierarchy), new PropertyMetadata(new ObservableCollection<HierarchyItem>()));
StringToIcon is also backed by a string dependency property for the icon name, which is summarily ignored because of the hard-coded image at the moment.
HierarchyItems are simple for the example:
public ObservableCollection<HierarchyItem> Subitems { get; set; }
public ObservableCollection<BindableMenuItem> MenuItems { get; set; }
Just to get this proof working, I attached the ItemHierarchy to some properties of the main window:
public ObservableCollection<BindableMenuItem> MenuItems { get; set; }
public ObservableCollection<HierarchyItem> IHItems { get; set; }
public MainWindow()
{
MenuItems = new ObservableCollection<BindableMenuItem>();
MenuItems.Add(new BindableMenuItem("Item", null));
MenuItems.Add(new BindableMenuItem("Item", null));
MenuItems.Add(new BindableMenuItem("Item", null));
MenuItems.Add(new BindableMenuItem("Item", null));
IHItems = new ObservableCollection<HierarchyItem>();
IHItems.Add(new HierarchyItem() { MenuItems = this.MenuItems });
InitializeComponent();
}
Edit 2: Here's BindableMenuItem also:
public class BindableMenuItem
{
public BindableMenuItem(string name, ICommand command)
{
this.Name = name;
this.Command = command;
}
public string Name { get; set; }
public ICommand Command { get; set; }
public string IconName { get; set; }
public ObservableCollection<BindableMenuItem> Children { get; set; }
}
Try to move the StringToIcon to <TreeView.Resources>:
<TreeView ItemsSource="{Binding ElementName=ItemHierarchyControl, Path=Items}">
<TreeView.Resources>
<local:StringToIcon x:Key="MenuIcon" x:Shared="False" IconName="{Binding IconName}" />
<HierarchicalDataTemplate DataType="{x:Type local:HierarchyItem}" ItemsSource="{Binding Subitems}">
<StackPanel Orientation="Horizontal" Margin="0,1,4,1">
<TextBlock Text="My text" VerticalAlignment="Center" />
<StackPanel.ContextMenu>
<ContextMenu ItemsSource="{Binding MenuItems}">
<ContextMenu.Resources>
<Style TargetType="MenuItem">
<Setter Property="Header" Value="{Binding Path=Name}" />
<Setter Property="Icon" Value="{StaticResource MenuIcon}" />
</Style>
</ContextMenu.Resources>
</ContextMenu>
</StackPanel.ContextMenu>
</StackPanel>
</HierarchicalDataTemplate>
</TreeView.Resources>
</TreeView>

Why changing DataGrid ComboBox value does not update the bound property at all?

I have DataGridComboBoxColumn that is supposed to show integers or text "Default". When I add row the combobox gets correct value from viewmodel's bound property, but when I change value in user interface, the property's set is not called. I tried both SelectedValueBinding and SelectedItemBinding. Converter's ConvertBack is never called. I don't event know should it be called.
Things that work:
DataGrid SelectedItem binding
Text column binding both ways (omitted here for shortness)
Here is my code:
XAML:
<DataGrid Name="SelectionSetsGrid" CanUserAddRows="False" CanUserResizeColumns="True" CanUserSortColumns="True"
ItemsSource="{Binding SelectionSets}" AutoGenerateColumns="False"
SelectedItem="{Binding SelectedSelectionSet}">
<DataGrid.Columns>
<DataGridComboBoxColumn Header="Width" SelectedValueBinding="{Binding LineWidthIndex}">
<DataGridComboBoxColumn.ElementStyle>
<Style TargetType="ComboBox" BasedOn="{StaticResource Theme.ComboBox.Style}">
<Setter Property="ItemsSource" Value="{Binding RelativeSource={RelativeSource FindAncestor, AncestorType={x:Type UserControl}}, Path=DataContext.LineWidths}"/>
<Setter Property="IsReadOnly" Value="True"/>
<Setter Property="ItemTemplate">
<Setter.Value>
<DataTemplate>
<WrapPanel>
<TextBlock Text="{Binding Converter={StaticResource IntToIntTextOrDefaultConverter}}" VerticalAlignment="Center"/>
</WrapPanel>
</DataTemplate>
</Setter.Value>
</Setter>
</Style>
</DataGridComboBoxColumn.ElementStyle>
<DataGridComboBoxColumn.EditingElementStyle>
<Style TargetType="ComboBox" BasedOn="{StaticResource Theme.ComboBox.Style}">
<Setter Property="ItemsSource" Value="{Binding RelativeSource={RelativeSource FindAncestor, AncestorType={x:Type UserControl}}, Path=DataContext.LineWidths}"/>
<Setter Property="ItemTemplate">
<Setter.Value>
<DataTemplate>
<WrapPanel>
<TextBlock Text="{Binding Converter={StaticResource IntToIntTextOrDefaultConverter}}" VerticalAlignment="Center"/>
</WrapPanel>
</DataTemplate>
</Setter.Value>
</Setter>
</Style>
</DataGridComboBoxColumn.EditingElementStyle>
</DataGridComboBoxColumn>
</DataGrid.Columns>
</DataGrid>
ViewModel (ViewModel implements INotifyPropertyChanged and SetValue raises PropertyChanged):
public class SelectedObjectsViewModel : ViewModel
{
private int[] _lineWidths = { -1, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10 };
public ObservableCollection<int> LineWidths { get; private set; };
private ObservableCollection<SelectionSetViewModel> _selectionSets;
public ObservableCollection<SelectionSetViewModel> SelectionSets
{
get { return _selectionSets; }
set { this.SetValue(ref _selectionSets, value); }
}
private SelectionSetViewModel _selectedSelectionSet;
public SelectionSetViewModel SelectedSelectionSet
{
get { return this._selectedSelectionSet; }
set { this.SetValue(ref _selectedSelectionSet, value); }
}
}
ViewModel for DataGrid row (ViewModel implements INotifyPropertyChanged and SetValue raises PropertyChanged):
public class SelectionSetViewModel : ViewModel
{
public SelectionSetViewModel()
{
LineWidthIndex = -1;
}
private int _lineWidthIndex;
public int LineWidthIndex
{
get { return _lineWidthIndex; }
set { SetValue(ref _lineWidthIndex, value); }
}
Converter:
public class IntToIntTextOrDefaultConverter : IValueConverter
{
public object Convert(object value, Type targetType, object parameter,
System.Globalization.CultureInfo culture)
{
if ((int)value == -1)
return Fusion.App.Current.Resources["StrDefault"].ToString();
return value.ToString();
}
public object ConvertBack(object value, Type targetType, object parameter,
System.Globalization.CultureInfo culture)
{
return value.Equals(true) ? parameter : Binding.DoNothing;
}
}
It seems that on some occasions like after editing the text column and pressing enter or adding new row the property WAS actually updated (set called) after changing combobox value. So I just added UpdateSourceTrigger=PropertyChanged to binding and the update to source property happened immediately (and not after some random operation). Note that changing focus from ComboBox was not enough to update source property so I thought it was never updated.
<DataGrid Name="SelectionSetsGrid" CanUserAddRows="False" CanUserResizeColumns="True" CanUserSortColumns="True"
ItemsSource="{Binding SelectionSets}" AutoGenerateColumns="False"
SelectedItem="{Binding SelectedSelectionSet}">
<DataGridComboBoxColumn Header="{StaticResource XpStrTopologyWidth}" SelectedItemBinding="{Binding LineWidthIndex, UpdateSourceTrigger=PropertyChanged}">
<DataGridComboBoxColumn.ElementStyle>
<Style TargetType="ComboBox" BasedOn="{StaticResource Theme.ComboBox.Style}">
<Setter Property="ItemsSource" Value="{Binding RelativeSource={RelativeSource FindAncestor, AncestorType={x:Type UserControl}}, Path=DataContext.LineWidths}"/>
<Setter Property="IsReadOnly" Value="True"/>
<Setter Property="ItemTemplate">
<Setter.Value>
<DataTemplate>
<WrapPanel>
<TextBlock Text="{Binding Converter={StaticResource IntToIntTextOrDefaultConverter}}" VerticalAlignment="Center"/>
</WrapPanel>
</DataTemplate>
</Setter.Value>
</Setter>
</Style>
</DataGridComboBoxColumn.ElementStyle>
<DataGridComboBoxColumn.EditingElementStyle>
<Style TargetType="ComboBox" BasedOn="{StaticResource Theme.ComboBox.Style}">
<Setter Property="ItemsSource" Value="{Binding RelativeSource={RelativeSource FindAncestor, AncestorType={x:Type UserControl}}, Path=DataContext.LineWidths}"/>
<Setter Property="ItemTemplate">
<Setter.Value>
<DataTemplate>
<WrapPanel>
<TextBlock Text="{Binding Converter={StaticResource IntToIntTextOrDefaultConverter}}" VerticalAlignment="Center"/>
</WrapPanel>
</DataTemplate>
</Setter.Value>
</Setter>
</Style>
</DataGridComboBoxColumn.EditingElementStyle>
</DataGridComboBoxColumn>
</DataGrid.Columns>
</DataGrid>

How to make TreeView node selected/expanded when ItemsSource = custom object collection?

I have a TreeView intended to operate on two levels. For his I have two HierarchicalDataTemplate and two custom types. The ItemsSource is linked to a ObservableCollection and everything works fine. I just can't figure how to make a node selected or expanded from codebehind. Somewhere was mentioned a very good idea of binding IsExpanded and IsSelected properties to the corresponding properties in my custom types. The only problem is that the HierarchicalDataTemplate does not implement a TreeViewItem directly, so how can I access these properties in the following code?
<TreeView Name="treeViewNotes" AllowDrop="True" PreviewMouseLeftButtonDown="treeViewNotes_PreviewMouseLeftButtonDown" PreviewMouseMove="treeViewNotes_PreviewMouseMove" Drop="treeViewNotes_Drop" DragEnter="treeViewNotes_DragEnter">
<TreeView.Resources>
<HierarchicalDataTemplate DataType="{x:Type dataclasses:NoteFolder}" ItemsSource="{Binding Notes}">
<StackPanel Orientation="Horizontal">
<Image Height="16" Source="{Binding TreeViewIcon}" Tag="{Binding Self}"/>
<TextBlock Text="{Binding Title}" Tag="{Binding Self}" Margin="3"/>
</StackPanel>
</HierarchicalDataTemplate>
<HierarchicalDataTemplate DataType="{x:Type dataclasses:Note}">
<StackPanel Orientation="Horizontal">
<Image Height="16" Source="{Binding TreeViewIcon}" Tag="{Binding Self}"/>
<TextBlock Text="{Binding Title}" Tag="{Binding Self}" Margin="3"/>
</StackPanel>
</HierarchicalDataTemplate>
</TreeView.Resources>
</TreeView>
My goal is when creating and adding a new Note to some NoteFolder, to make this Note selected and the Folder expanded. The same would be needed to further improve the UI response on drag&drop.
You could try changing the TreeView's ItemContainerStyle in the following way, so that its IsExpanded and IsSelected properties would bind to the DataContext's IsExpanded and IsSelected:
<TreeView x:Name="..." ItemsSource="{Binding RootNode}">
<TreeView.ItemContainerStyle>
<Style TargetType="{x:Type TreeViewItem}">
<!-- Items in the ItemsSource need to have these properties for the binding to work -->
<Setter Property="IsExpanded" Value="{Binding IsExpanded, Mode=TwoWay, UpdateSourceTrigger=PropertyChanged}" />
<Setter Property="IsSelected" Value="{Binding IsSelected, Mode=TwoWay, UpdateSourceTrigger=PropertyChanged}" />
<!-- You can also optionally change some style values based on IsSelected and IsExpanded values -->
<Style.Triggers>
<DataTrigger Binding="{Binding IsSelected}" Value="True">
<Setter Property="BorderThickness" Value="4 0 0 1"/>
<Setter Property="BorderBrush" Value="DeepSkyBlue"/>
</DataTrigger>
<DataTrigger Binding="{Binding IsSelected}" Value="False">
<Setter Property="BorderThickness" Value="4 0 0 1 "/>
<Setter Property="BorderBrush" Value="Transparent"/>
</DataTrigger>
</Style.Triggers>
</Style>
</TreeView.ItemContainerStyle>
<TreeView.Resources>
<HierarchicalDataTemplate>
...
</HierarchicalDataTemplate>
</TreeView.Resources>
</TreeView>
Of course, then each item coming from the ItemSource and present in the hierarchy needs to have these properties.
The only problem is that the HierarchicalDataTemplate does not
implement a TreeViewItem directly, so how can I access these
properties in the following code?
You can do that in TreeView.ItemContainerStyle:
<TreeView ItemTemplate="{StaticResource ResourceKey=treeViewDataTemplate}"
ItemsSource="{Binding Data}"
Name="trvTreeView">
<TreeView.ItemContainerStyle>
<Style TargetType="{x:Type TreeViewItem}">
<Setter Property="IsSelected" Value="{Binding Path=IsSelected}" />
<Setter Property="IsExpanded" Value="{Binding Path=IsExpanded}" />
<EventSetter Event="Expanded" Handler="TreeViewItem_Expanded" />
<EventSetter Event="Collapsed" Handler="TreeViewItem_Collapsed" />
<EventSetter Event="PreviewMouseRightButtonDown" Handler="TreeViewItem_PreviewMouseRightButtonDown" />
</Style>
</TreeView.ItemContainerStyle>
</TreeView>
In the code behind:
private void TreeViewItem_Expanded(object sender, RoutedEventArgs e)
{
TreeViewItem treeViewItem = e.OriginalSource as TreeViewItem;
if (treeViewItem != null)
{
BaseObjectExplorerNode baseObjectExplorerNode = treeViewItem.Header as BaseObjectExplorerNode;
if (baseObjectExplorerNode != null)
{
baseObjectExplorerNode.IsExpanded = true;
}
}
}
private void TreeViewItem_Collapsed(object sender, RoutedEventArgs e)
{
TreeViewItem treeViewItem = e.OriginalSource as TreeViewItem;
if (treeViewItem != null)
{
BaseObjectExplorerNode baseObjectExplorerNode = treeViewItem.Header as BaseObjectExplorerNode;
if (baseObjectExplorerNode != null)
{
baseObjectExplorerNode.IsExpanded = false;
}
}
}
And then for example:
root.IsExpanded = true;
I don't see any problem. You can make a StyleSelector, two Style's and bind to properties.
XAML:
<TreeView Name="treeViewNotes" AllowDrop="True" PreviewMouseLeftButtonDown="treeViewNotes_PreviewMouseLeftButtonDown" PreviewMouseMove="treeViewNotes_PreviewMouseMove" Drop="treeViewNotes_Drop" DragEnter="treeViewNotes_DragEnter">
<TreeView.Resources>
<!-- StyleSelector for containers -->
<notdataclasses:NoteStyleSelector x:Key="NoteStyleSelector" />
<!-- Style for a NoteFolder's container -->
<Style x:Key="NoteFolderStyle" TargetType="{x:Type TreeViewItem}">
<Setter Property="IsSelected" Value="{Binding Path=IsSelected}" />
<Setter Property="IsExpanded" Value="{Binding Path=IsExpanded}" />
<Setter Property="ItemContainerStyleSelector" Value="{StaticResource NoteFolderStyle}" />
</Style>
<!-- Style for a Note's container -->
<Style x:Key="NoteStyle" TargetType="{x:Type TreeViewItem}">
<Setter Property="IsSelected" Value="{Binding Path=IsSelected}" />
</Style>
<!-- ... -->
</TreeView.Resources>
<TreeView.ItemContainerStyleSelector>
<StaticResource ResourceKey="NoteStyleSelector" />
</TreeView.ItemContainerStyleSelector>
</TreeView>
NoteStyleSelector:
public sealed class NoteStyleSelector : StyleSelector
{
public override Style SelectStyle(object item, DependencyObject container)
{
FrameworkElement fe = container as FrameworkElement;
if (fe!= null)
{
if (item is Note)
{ return (Style)fe.FindResource("NoteStyle"); }
if (item is NoteFolder)
{ return (Style)fe.FindResource("NoteFolderStyle"); }
}
return base.SelectStyle(item, container);
}
}
In a drop handler:
currentFolder.Nodes.Add(pastedNode);
currentFolder.IsExpanded = true;
currentNode.IsSelected = true;
Data casses:
public class Note : INotifyPropertyChanged
{
// Only the IsSelected property because a Note can not be expanded
public bool IsSelected { get { /* ... */ } set { /* ... */ } }
}
public class NoteFolder : INotifyPropertyChanged
{
public bool IsSelected { get { /* ... */ } set { /* ... */ } }
public bool IsExpanded { get { /* ... */ } set { /* ... */ } }
}

Categories