How to get TreeViewItem's ContextMenu, having TreeView as a DataTemplate - wpf

I have a TreeView in a data template like this :
<DataTemplate DataType="{x:Type MainFavoriteItems}">
<TreeView BorderThickness="0">
<TreeViewItem Header="First Header" ItemsSource="{Binding FavItems}" ItemTemplate="{StaticResource item_template}"/>
<TreeViewItem Header="Second Header" ../>
<TreeViewItem Header="Third Header" ../>
<TreeView.ItemContainerStyle>
<Style TargetType="{x:Type TreeViewItem}" BasedOn="{StaticResource {x:Type TreeViewItem}}">
<Setter Property="ContextMenu" Value="{Binding GetMenu}}"/> // DO not work
</TreeView.ItemContainerStyle>
</TreeView>
</DataTemplate>
internal class MainFavoriteItems{
public IReadOnlyList<Info> FavItems
{
get
{
List<Info> items = new List<Info>();
items.Add(new InnerViewModel("1"));
items.Add(new InnerViewModel("2"));
NotifyPropertyChanged(() => FavItems);
return items;
}
}
}
public class InnerViewModel
{
public string Id;
public System.Windows.Controls.ContextMenu GetMenu => new CustomContextMenu();
...
}
When i run it, headers and its children appears fine, but inner item's right-click ContextMenu doesn't showup, and its xaml output error says .. GetMenu property not found on object 'MainFavoriteItems' .. What am i doing wrong !? Thanks.

Your sample code doesn't even compile but you should set the ItemContainerStyle of the TreeViewItem with the ItemsSource for the binding to work:
<TreeViewItem Header="First Header" ItemsSource="{Binding FavItems}"
ItemTemplate="{StaticResource item_template}">
<TreeViewItem.ItemContainerStyle>
<Style TargetType="{x:Type TreeViewItem}" BasedOn="{StaticResource {x:Type TreeViewItem}}">
<Setter Property="ContextMenu" Value="{Binding GetMenu}"/>
</Style>
</TreeViewItem.ItemContainerStyle>
</TreeViewItem>
<TreeViewItem Header="Second Header" />
<TreeViewItem Header="Third Header" />

Related

WPF Custom Control library : trying to bind to templatedparent property from a keyed resource, ancestor way fails

How can I make this custom control library binding work? The code is heavily simplified for the sake of brevity, the Listview chooses its current View among several Views thank to a Datatrigger in its style, I'm only showing one for the sake of brevity. The failing binding is the GridViewColumn.Width one
Themes/Generic.Xaml :
<ResourceDictionary
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:local="clr-namespace:WpfCustomControlLibrary1">
<DataTemplate x:Key ="myDataTemplate">
<!--This visibility binding works-->
<CheckBox Width="16" Height="16" x:Name="ItemCheckbox"
Visibility="{Binding CheckBoxVisibility,
RelativeSource={RelativeSource Mode=FindAncestor,
AncestorType={x:Type local:CustomControl1}}}" />
</DataTemplate>
<GridView x:Name="myView" x:Key="myView" x:Shared="False">
<!--this width binding fails : Cannot find source for binding-->
<GridViewColumn x:Name="NameColumn" Header="Name"
CellTemplate="{StaticResource myDataTemplate}"
Width="{Binding NameColumnWidth,
RelativeSource={RelativeSource Mode=FindAncestor,
AncestorType={x:Type local:CustomControl1}}}" />
</GridView>
<Style TargetType="{x:Type local:CustomControl1}">
<Setter Property="Template">
<Setter.Value>
<ControlTemplate TargetType="{x:Type local:CustomControl1}">
<ListView ItemsSource="{Binding
RelativeSource={RelativeSource TemplatedParent},
Path=ItemsSource}" >
<ListView.Style>
<Style TargetType="{x:Type ListView}">
<Style.Triggers>
<DataTrigger Binding="{Binding
RelativeSource={RelativeSource TemplatedParent},
Path=ViewDetails}" Value="True">
<Setter Property="View"
Value="{StaticResource myView}" />
</DataTrigger>
<DataTrigger Binding="{Binding
RelativeSource={RelativeSource TemplatedParent},
Path=ViewDetails}" Value="False ">
<Setter Property="View"
Value="{StaticResource myView2}" />
</DataTrigger>
</Style.Triggers>
</Style>
</ListView.Style>
</ListView>
</ControlTemplate>
</Setter.Value>
</Setter>
</Style>
</ResourceDictionary>
CustomControl1.cs
public class CustomControl1 : Control
{
//these properties should be DependencyProperties, simplified here for the sake of brevity
ObservableCollection<Item> items;
public object ItemsSource { get => items; }
//this is the property I fail to bind to
public double NameColumnWidth { get => this.Width; }
//this one works
public Visibility CheckBoxVisibility { get => Visibility.Visible; }
static CustomControl1()
{
DefaultStyleKeyProperty.OverrideMetadata(typeof(CustomControl1),
new FrameworkPropertyMetadata(typeof(CustomControl1)));
}
}
Edit:
I actually ended up changing my approach and instead of binding the column width to a Property in the templated parent I made it auto resizing to its content by subclassing GridView. This is an overall better solution. If anyone finds a solution to this binding puzzle I'll still be happy to test and accept it.
public class AutoAdjustingGridView: GridView
{
protected override void PrepareItem(ListViewItem item)
{
base.PrepareItem(item);
foreach(var column in Columns)
{
if (double.IsNaN(column.Width))
column.Width = column.ActualWidth;
column.Width = double.NaN;
}
}
}

Bind to HierarchicalDataTemplate DataContext from nested DataTemplate

I have my treeview with 2 level nested levels.
<TreeView ItemsSource="{Binding Project.Tables}">
<TreeView.Resources>
<HierarchicalDataTemplate DataType="{x:Type TableModel}" ItemsSource="{Binding Columns}">
</HierarchicalDataTemplate>
<DataTemplate DataType="{x:Type ColumnModel}">
</DataTemplate>
</TreeView.Resources>
</TreeView>
My binding source with tables:
public class ProjectModel : PropertyChangedBase
{
private BindableCollection<TableModel> _tables;
public BindableCollection<TableModel> Tables
{
get { return _tables; }
set
{
if (_tables != value)
{
_tables = value;
NotifyOfPropertyChange();
}
}
}
}
public class TableModel
{
public BindableCollection<ColumnModel> Columns { get; set; }
public bool IsSelected
{
get;
}
}
I tried to use next binding:
<DataTemplate DataType="{x:Type projectModels:ColumnModel}">
<StackPanel Orientation="Horizontal">
<StackPanel.Resources>
<Style TargetType="TextBlock">
<Style.Triggers>
<DataTrigger Binding="{Binding RelativeSource={RelativeSource FindAncestor, AncestorType={x:Type TreeViewItem}}, Path=DataContext.IsSelected }" Value="True">
<Setter Property="Foreground" Value="{StaticResource GreyBrush3}"></Setter>
</DataTrigger>
</Style.Triggers>
</Style>
</StackPanel.Resources>
<TextBlock Text="{Binding ColumnName}">
</TextBlock>
</StackPanel>
</DataTemplate>
Inside DataTemplate I want to have binding to DataContext of HierarchicalDataTemplate. I already tried different AncestorLevels and it did not work, any ideas how can I do it?

Binding to DataGridColumnHeader.Command

I need to use Command on DataGridColumnHeader to sort my data. Standard DataGrid sorting feature is not enough for me because there are not all data displayed in DataGrid. I don't even have all data present in my VM. (It's not possible... it's too much) I just request concrete page of data from server. And now I would also like to get concrete page from sorted data.
So I did this:
<DataGrid ItemsSource="{Binding Path=Entities, Mode=OneWay}" CanUserSortColumns="False" SelectionMode="Single" SelectedItem="{Binding Path=SelectedEntity}">
<DataGrid.ColumnHeaderStyle>
<Style TargetType="{x:Type DataGridColumnHeader}">
<Setter Property="Command" Value="{Binding Path=MyCommand}"/>
<Setter Property="CommandParameter" Value="{Binding Path=Content, RelativeSource={RelativeSource Self}}"/>
</Style>
</DataGrid.ColumnHeaderStyle>
<DataGrid.Columns>
<DataGridTemplateColumn >
<DataGridTemplateColumn.Header>
<DataGridColumnHeader Content="Column1" />
</DataGridTemplateColumn.Header>
<DataGridTemplateColumn.CellTemplate>
<DataTemplate>
<TextBox Text="{Binding Path=Property1}"/>
</DataTemplate>
</DataGridTemplateColumn.CellTemplate>
</DataGridTemplateColumn>
</DataGrid.Columns>
</DataGrid>
It works great. And now I just want to have AutoGenerateColumns="True" unfortunately it works no more. Is there anybody who can explain why it doesn't work for autogenerated columns and provide me with some solution? Thank you in advance!
EDIT
It has probably something to do with the fact that following doesn't work either.
<DataGrid ItemsSource="{Binding Path=Entities, Mode=OneWay}" CanUserSortColumns="False" SelectionMode="Single" SelectedItem="{Binding Path=SelectedEntity}">
<DataGrid.ColumnHeaderStyle>
<Style TargetType="{x:Type DataGridColumnHeader}">
<Setter Property="Command" Value="{Binding Path=MyCommand}"/>
<Setter Property="CommandParameter" Value="{Binding Path=Content, RelativeSource={RelativeSource Self}}"/>
</Style>
</DataGrid.ColumnHeaderStyle>
<DataGrid.Columns>
<DataGridTemplateColumn>
<!-- HERE IS THE CHANGE -->
<DataGridTemplateColumn.Header>Column1</DataGridTemplateColumn.Header>
<DataGridTemplateColumn.CellTemplate>
<DataTemplate>
<TextBox Text="{Binding Path=Property1}"/>
</DataTemplate>
</DataGridTemplateColumn.CellTemplate>
</DataGridTemplateColumn>
</DataGrid.Columns>
But style is applied. I know this because I tried:
<Style TargetType="{x:Type DataGridColumnHeader}">
<Setter Property="Background" Value="Tomato"/> <!-- beautiful tomato background -->
<Setter Property="Command" Value="{Binding Path=MyCommand}"/>
<Setter Property="CommandParameter" Value="{Binding Path=Content, RelativeSource={RelativeSource Self}}"/>
</Style>
Column header has Tomato background, but command doesn't work.
EDIT2
Following is the solution. Apparently DataGridColumnHeaders doesn't inherit DataContext, because when I change the command binding everything works again.
<Setter Property="Command" Value="{Binding RelativeSource={RelativeSource AncestorType={x:Type DataGrid}},
Path=DataContext.MyCommand}"/>
But still I don't quite understand why. So answer with detailed explanation will get the bounty.
DataGridColumnHeader is container item for DataGridColumn.Header same as, for example, ListBoxItem is container item for each item in ListBox's ItemsSource.
Since ListBoxItem's DataContext will be set to an item from ListBox's ItemsSource, it is natural that DataGridColumnHeader's DataContext will be set to DataGridColumn.Header object.
As your findings suggest, autogenerated columns will have a DataGridColumn.Header with its DataContext set to the string header object.
If you do not use autogenerated columns, and instead specify a DataGridTemplateColumn and the DataGridTemplateColumn.Header, standard inheritance for the DataContext still applies, so that's why it worked for you the first time.
Edit: A quick search didn't provide immediate results where this behavior is described. However, verifying the fact is not too hard:
Xaml:
<DataGrid ItemsSource="{Binding MyClasses}" AutoGenerateColumns="True" MouseRightButtonUp="DataGrid_MouseRightButtonUp_1" >
<DataGrid.Columns>
<DataGridTemplateColumn>
<DataGridTemplateColumn.Header>
<DataGridColumnHeader Content="Name (Manual Column)" />
</DataGridTemplateColumn.Header>
<DataGridTemplateColumn.CellTemplate>
<DataTemplate>
<TextBlock Text="{Binding Path=Name}"/>
</DataTemplate>
</DataGridTemplateColumn.CellTemplate>
</DataGridTemplateColumn>
</DataGrid.Columns>
</DataGrid>
ViewModel:
public class ViewModel : INotifyPropertyChanged
{
public event PropertyChangedEventHandler PropertyChanged;
private void OnPropertyChanged(string propertyName)
{
if (this.PropertyChanged != null)
PropertyChanged(this, new PropertyChangedEventArgs(propertyName));
}
private ObservableCollection<Class1> _myClasses;
public ObservableCollection<Class1> MyClasses { get { return _myClasses; } set { _myClasses = value; OnPropertyChanged("MyClasses"); } }
public ViewModel()
{
MyClasses = new ObservableCollection<Class1>()
{
new Class1() { Name = "Andy" },
new Class1() { Name = "Mark" },
new Class1() { Name = "Peter" },
new Class1() { Name = "Gregor" },
new Class1() { Name = "Jenny" }
};
}
}
Code behind (for illustration purpose only):
private void DataGrid_MouseRightButtonUp_1(object sender, MouseButtonEventArgs e)
{
if (e.OriginalSource is Microsoft.Windows.Themes.DataGridHeaderBorder)
{
object dataContext = ((Microsoft.Windows.Themes.DataGridHeaderBorder)e.OriginalSource).DataContext;
}
}
Right-click on the manual column header (outside the textblock) produces the following dataContext:
Right-click on the autogenerated column header:

How to conditionally insert a Checkbox in a list item of a customized ListView?

I try to make a customized ListView which fills each list item with some stuff and an initial Checkbox if desired. Currently no Checkbox is displayed so I guess my code of the ContentControl stuff is somehow erroneous.
<ListView xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml">
<ListView.View>
<GridView>
<GridViewColumn>
<GridViewColumn.CellTemplate>
<DataTemplate>
<!-- Each list item: [Checkbox] Label -->
<StackPanel Orientation="Horizontal">
<!-- The code for the optional check box -->
<ContentControl>
<ContentControl.Style>
<Style TargetType="ContentControl">
<Style.Triggers>
<DataTrigger Binding="{Binding IsCheckable}" Value="True">
<Setter Property="Template">
<Setter.Value>
<ControlTemplate>
<CheckBox IsChecked="{Binding Path=SomeProperty}" />
</ControlTemplate>
</Setter.Value>
</Setter>
</DataTrigger>
</Style.Triggers>
</Style>
</ContentControl.Style>
</ContentControl>
<!-- The non-optional test label -->
<Label Content="Test Content" />
</StackPanel>
</DataTemplate>
</GridViewColumn.CellTemplate>
</GridViewColumn>
</ListView.View>
</ListView>
The Code behind:
public partial class MyListView : ListView {
public MyListView () {
InitializeComponent();
}
public bool IsCheckable
{
get { return (bool)GetValue(IsCheckableProperty); }
set { SetValue(IsCheckableProperty, value); }
}
public static readonly DependencyProperty IsCheckableProperty =
DependencyProperty.Register(
"IsCheckable",
typeof(bool),
typeof(AppropriatenessWidget),
new UIPropertyMetadata(false));
}
{Binding IsCheckable} binds to the DataContext not the control, use for example:
{Binding IsCheckable,
RelativeSource={RelativeSource AncestorType=local:MyListView}}
Also i doubt that this subclassing approach is such a good idea, this could easily be handled via an underlying DataContext.

WPF: TreeView unable to apply Hierachical template and style at the same time

Here is what I want to do:
<TreeView>
<TreeView.Resources>
<Style TargetType="TreeViewItem" >
<Setter Property="dz:VirtualListItemBase.AutoLoad" Value="true" />
</Style>
</TreeView.Resources>
<TreeView.ItemTemplate>
<HierarchicalDataTemplate ItemsSource="{Binding Data.Nodes}" >
<Label Grid.Column="1" Grid.Row="0" Content="{Binding Data.Name}" dz:VirtualListItemBase.AutoLoad="true" />
</HierarchicalDataTemplate>
</TreeView.ItemTemplate>
The style part never gets applied. The HierachicalDataTemplate seems to override the initial style. I cannot use the type to apply the template since multiple types are involved. Any ideas?
Thanks
DataTemplate has higher precedence over Style. Try moving the DataTemplate into the Style as:
<Style TargetType="TreeViewItem" >
<Setter Property="dz:VirtualListItemBase.AutoLoad" Value="true" />
<Setter Property="ItemTemplate">
<Setter.Value>
<HierarchicalDataTemplate ...
</HierarchicalDataTemplate>
</Setter.Value>
</Setter>
</Style>

Resources