`ContextMenu` Command binding inside `ListView.ItemContainerStyle` does not work - c#

I have a list view, where I wanted to Hide ContextMenuItems based on some value in the ListView Column. I could achieve this by moving ContextMenu to ListView.ItemContainerStyle. Now my problem is that I am unable to fire Commands on MenuItem. This used to work when ContextMenu was set directly to ListView.
Here is what I have tried and failed. (Used ElementType, ElementName, Direct call etc.).
<UserControl Name="RootElement" DataContext=.Some Context.....>
<ListView ItemsSource="{Binding LicensesView}">
<ListView.ItemContainerStyle>
<Style TargetType="{x:Type ListViewItem}">
<Setter Property="ContextMenu">
<Setter.Value>
<ContextMenu>
<MenuItem Header="Refresh 1" Command="{Binding Path=DataContext.LicenseListRefreshCommand, RelativeSource={RelativeSource AncestorType={x:Type UserControl}}}" />
<MenuItem Header="Refresg 2" Command="{Binding Path=DataContext.LicenseListRefreshCommand, RelativeSource={RelativeSource AncestorType=Window, Mode=FindAncestor}}"/>
<MenuItem Header="Refresh 3" Command="{Binding Path= DataContext.LicenseListRefreshCommand, ElementName=RootElement}" Visibility="{Binding Path=Aktif,Converter= {StaticResource BoolToVisibility}}"/>
<MenuItem Header="Refresh 4" Command="{Binding LicenseListRefreshCommand}"/>
</ContextMenu>
</Setter.Value>
</Setter>
</Style>
</ListView.ItemContainerStyle>
<ListView.View>
<GridView>
<GridViewColumn Header="Code" DisplayMemberBinding="{Binding Code}" Width="Auto"/>
<GridViewColumn Header="Active" DisplayMemberBinding="{Binding Active}" Width="150" />
<GridViewColumn Header="Company" DisplayMemberBinding="{Binding Company}" Width="60" />
<GridViewColumn Header="Demo" DisplayMemberBinding="{Binding Demo}" Width="Auto" />
</GridView>
</ListView.View>
</ListView>
</UserControl>

The reason it doesn't work is because Style is a Disconnected property and hence it doesn' respect the visual tree.
I resolved this by using binding Proxy as shown in this answer.
Key is to add DataContext reference as StaticResource.
<UserControl Name="RootElement" DataContext=.Some Context.....>
<UserControl.Resources>
<BooleanToVisibilityConverter x:Key="BoolToVisibility" />
<helper:BindingProxy x:Key="Proxy" Data="{Binding}" />
</UserControl.Resources>
<ListView ItemsSource="{Binding LicensesView}">
<ListView.ItemContainerStyle>
<Style TargetType="{x:Type ListViewItem}">
<Setter Property="ContextMenu">
<Setter.Value>
<ContextMenu>
<MenuItem Header="Refresh 1" Command="{Binding Path=Data.LicenseListRefreshCommand, Source={StaticResource Proxy}}" />
</ContextMenu>
</Setter.Value>
</Setter>
</Style>
</ListView.ItemContainerStyle>
<ListView.View>
<GridView>
<GridViewColumn Header="Code" DisplayMemberBinding="{Binding Code}" Width="Auto"/>
<GridViewColumn Header="Active" DisplayMemberBinding="{Binding Active}" Width="150" />
<GridViewColumn Header="Company" DisplayMemberBinding="{Binding Company}" Width="60" />
<GridViewColumn Header="Demo" DisplayMemberBinding="{Binding Demo}" Width="Auto" />
</GridView>
</ListView.View>
</ListView>

Related

gridview column auto size not working on loading except with hot reload

When I load my list, my column size is set to "auto". I would like my column to automatically take the size of my longest string, so as not to "cut" the strings.
For the first column, we see that everything is fine. For the second one, which is exactly the same except for the font style, it doesn't work, we see that the names are cut.
However, if I change "auto" to a value, for example 10, and then back to "auto", everything works with an horizontal scrollbar:
If it works with hot reload and for the first column, what can explain that my second column does not fit the size of my strings?
My list is a collection of objects composed with 2 strings. My code :
<ListView
BorderBrush="SteelBlue"
ItemsSource="{Binding ListeAgentsAAfficher}"
SelectedItem="{Binding AgentSelected}">
<ListView.ContextMenu>
<ContextMenu>
<MenuItem
Command="{Binding AfficherSupprimerCommand}"
Header="Afficher agents supprimés"
IsCheckable="True"
IsChecked="{Binding IsCheckedSupprimer}" />
<MenuItem
Command="{Binding TriAgentsMatriculesCommand}"
Header="Trier par matricules"
IsCheckable="True"
IsChecked="{Binding IsCheckedTriMatricule}" />
<MenuItem
Command="{Binding TriAgentsNomCommand}"
Header="Trier par noms"
IsCheckable="True"
IsChecked="{Binding IsCheckedTriNom}" />
</ContextMenu>
</ListView.ContextMenu>
<ListView.Resources>
<Style TargetType="ListViewItem">
<Setter Property="Background" Value="White" />
<Style.Triggers>
<DataTrigger Binding="{Binding Supprimer}" Value="true">
<Setter Property="Foreground" Value="DarkOrange" />
</DataTrigger>
</Style.Triggers>
</Style>
</ListView.Resources>
<ListView.View>
<GridView>
<GridView.ColumnHeaderContainerStyle>
<Style TargetType="GridViewColumnHeader">
<Setter Property="Visibility" Value="Collapsed" />
</Style>
</GridView.ColumnHeaderContainerStyle>
<GridViewColumn Width="auto">
<GridViewColumnHeader />
<GridViewColumn.CellTemplate>
<DataTemplate>
<TextBlock
HorizontalAlignment="center"
VerticalAlignment="center"
FontSize="10"
FontWeight="Bold"
Text="{Binding Matricule}" />
</DataTemplate>
</GridViewColumn.CellTemplate>
</GridViewColumn>
<GridViewColumn Width="auto">
<GridViewColumn.CellTemplate>
<DataTemplate>
<TextBlock
HorizontalAlignment="center"
VerticalAlignment="center"
Text="{Binding Nom}" />
</DataTemplate>
</GridViewColumn.CellTemplate>
</GridViewColumn>
</GridView>
</ListView.View>
</ListView>

Bind Command To ContextMenu Item

I am getting this error/warning:
System.Windows.Data Error: 4 : Cannot find source for binding with reference 'ElementName=DrivesListView'
When I press 'Refresh', the command does not fire. I am guessing since it is a ContextMenu, I need to somehow access the parent control in the binding path and then I can use MouseDownCommand.
MouseDownCommand is located in my tab viewmodel, TabItemViewModel. My MainWindowViewModel contains a list of TabItemViewModels, and that is the source of the TabControl's items.
What I've tried:
1)
I've tried setting the ContextMenu Opened event to set the DataContext manually like this to see if it would fix the DataContext somehow:
private void ContextMenu_Opened(object sender, RoutedEventArgs e)
{
ContextMenu menu = sender as ContextMenu;
ListView listView = menu.PlacementTarget as ListView;
Grid grid = listView.Parent as Grid;
TabControl tabControl = grid.Parent as TabControl;
menu.DataContext = (TabItemViewModel)tabControl.SelectedItem;
}
The problem with this is the fact that I cannot seem to get the tabControl from the grid. Doing .Parent just returns null for some unknown reason.
2)
I also tried setting the Tag of the control, which did not work either:
<ListView Grid.Row="1" Name="DrivesListView" ItemsSource="{Binding Drives}"
Tag="{Binding DataContext,RelativeSource={RelativeSource Mode=FindAncestor,AncestorType=Window}}">>
<ListView.ContextMenu>
<ContextMenu>
<MenuItem Header="Refresh">
<i:Interaction.Triggers>
<i:EventTrigger EventName="MouseDown">
<i:InvokeCommandAction
Command="{Binding Path=PlacementTarget.Tag.MouseDownCommand, RelativeSource={RelativeSource Mode=FindAncestor, AncestorType=ContextMenu}}"
CommandParameter="{Binding ElementName=DrivesListView}"/>
</i:EventTrigger>
</i:Interaction.Triggers>
</MenuItem>
</ContextMenu>
</ListView.ContextMenu>
<ListView.Style>
<Style TargetType="{x:Type ListView}" BasedOn="{StaticResource MahApps.Styles.ListView}">
<Style.Triggers>
<DataTrigger Binding="{Binding DrivesListViewEnabled}" Value="true">
<Setter Property="Visibility" Value="Visible" />
</DataTrigger>
<DataTrigger Binding="{Binding DrivesListViewEnabled}" Value="false">
<Setter Property="Visibility" Value="Hidden" />
</DataTrigger>
</Style.Triggers>
</Style>
</ListView.Style>
<i:Interaction.Triggers>
<i:EventTrigger EventName="MouseDoubleClick">
<i:InvokeCommandAction Command="{Binding Path=MouseDoubleClickCommand}"
CommandParameter="{Binding ElementName=DrivesListView}"/>
</i:EventTrigger>
</i:Interaction.Triggers>
<ListView.View>
<GridView>
<GridViewColumn x:Name="NameHeader" Header="Name" DisplayMemberBinding="{Binding Name}"/>
<GridViewColumn x:Name="TypeHeader" Header="Type" DisplayMemberBinding="{Binding Type}"/>
<GridViewColumn x:Name="TotalSizeHeader" Header="Total Size" DisplayMemberBinding="{Binding TotalSize}"/>
<GridViewColumn x:Name="FreeSpaceHeader" Header="Free Space" DisplayMemberBinding="{Binding FreeSpace}"/>
</GridView>
</ListView.View>
</ListView>
Here is my XAML
<UserControl x:Class="FileExplorerModuleServer.Views.FileBrowserTabView"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:i="http://schemas.microsoft.com/expression/2010/interactivity" xmlns:mah="http://metro.mahapps.com/winfx/xaml/controls"
mc:Ignorable="d"
d:DesignHeight="450" d:DesignWidth="800">
<Grid>
<Grid.RowDefinitions>
<RowDefinition Height="Auto"/>
<RowDefinition Height="*"/>
</Grid.RowDefinitions>
<!--ItemsSource is bound to the 'Tabs' property on the view-
model, while DisplayMemeberPath tells TabControl
which property on each tab has the tab's name -->
<TabControl Name="SubTabControl" Grid.Row="1"
ItemsSource="{Binding Tabs}"
DisplayMemberPath="Header" SelectedIndex="{Binding SelectedIndex}">
<i:Interaction.Triggers>
<i:EventTrigger EventName="SelectionChanged">
<i:InvokeCommandAction Command="{Binding Path=TabChangedCommand}"
CommandParameter="{Binding ElementName=SubTabControl}"/>
</i:EventTrigger>
</i:Interaction.Triggers>
<TabControl.ContentTemplate>
<DataTemplate>
<Grid>
<Grid.RowDefinitions>
<RowDefinition Height="Auto"/>
<RowDefinition Height="*"/>
</Grid.RowDefinitions>
<TextBox Grid.Row="0" HorizontalAlignment="Left" Width="300" Text="{Binding Path}">
</TextBox>
<mah:ProgressRing Grid.Row="1" Height="1">
<mah:ProgressRing.Style>
<Style TargetType="{x:Type mah:ProgressRing}">
<Style.Triggers>
<DataTrigger Binding="{Binding IsLoading}" Value="true">
<Setter Property="Visibility" Value="Visible" />
</DataTrigger>
<DataTrigger Binding="{Binding IsLoading}" Value="false">
<Setter Property="Visibility" Value="Hidden" />
</DataTrigger>
</Style.Triggers>
</Style>
</mah:ProgressRing.Style>
</mah:ProgressRing>
<ListView Grid.Row="1" Name="DrivesListView" ItemsSource="{Binding Drives}">
<ListView.ContextMenu>
<ContextMenu Opened="ContextMenu_Opened">
<MenuItem Header="Refresh">
<i:Interaction.Triggers>
<i:EventTrigger EventName="MouseDown">
<i:InvokeCommandAction
Command="{Binding Path=MouseDownCommand}"
CommandParameter="{Binding ElementName=DrivesListView}"/>
</i:EventTrigger>
</i:Interaction.Triggers>
</MenuItem>
</ContextMenu>
</ListView.ContextMenu>
<ListView.Style>
<Style TargetType="{x:Type ListView}" BasedOn="{StaticResource MahApps.Styles.ListView}">
<Style.Triggers>
<DataTrigger Binding="{Binding DrivesListViewEnabled}" Value="true">
<Setter Property="Visibility" Value="Visible" />
</DataTrigger>
<DataTrigger Binding="{Binding DrivesListViewEnabled}" Value="false">
<Setter Property="Visibility" Value="Hidden" />
</DataTrigger>
</Style.Triggers>
</Style>
</ListView.Style>
<i:Interaction.Triggers>
<i:EventTrigger EventName="MouseDoubleClick">
<i:InvokeCommandAction Command="{Binding Path=MouseDoubleClickCommand}"
CommandParameter="{Binding ElementName=DrivesListView}"/>
</i:EventTrigger>
</i:Interaction.Triggers>
<ListView.View>
<GridView>
<GridViewColumn x:Name="NameHeader" Header="Name" DisplayMemberBinding="{Binding Name}"/>
<GridViewColumn x:Name="TypeHeader" Header="Type" DisplayMemberBinding="{Binding Type}"/>
<GridViewColumn x:Name="TotalSizeHeader" Header="Total Size" DisplayMemberBinding="{Binding TotalSize}"/>
<GridViewColumn x:Name="FreeSpaceHeader" Header="Free Space" DisplayMemberBinding="{Binding FreeSpace}"/>
</GridView>
</ListView.View>
</ListView>
Here is the ViewModel.cs
public ICommand MouseDownCommand
=> new RelayCommand<object>(e => MouseDown(e));
private void MouseDown(object commandParameter)
{
}
As stated in the documentation, [the] menu [...] is specific to the context of the control.
In other words, the ContextMenu has the same data context as it's parent control (here the ListView).
To follow the MVVM pattern, you then only need to add a ICommand in the data context of the ListView and bind it to the MenuItem.Command property.
The View:
<ListView ItemsSource="{Binding Drives}">
<ListView.ContextMenu>
<ContextMenu>
<MenuItem Header="Refresh" Command="{Binding RefreshCommand}" />
</ContextMenu>
</ListView.ContextMenu>
<ListView.View>
<GridView>
<GridViewColumn Header="Name" DisplayMemberBinding="{Binding Name}"/>
<GridViewColumn Header="Type" DisplayMemberBinding="{Binding Type}"/>
<GridViewColumn Header="Total Size" DisplayMemberBinding="{Binding TotalSize}"/>
<GridViewColumn Header="Free Space" DisplayMemberBinding="{Binding FreeSpace, StringFormat='{}{0:0.00}'}"/>
</GridView>
</ListView.View>
</ListView>
The ViewModel:
public class ViewModel
{
public ViewModel()
{
RefreshCommand = new ActionCommand(Refresh);
}
public IReadOnlyList<DriveViewModel> Drives { get; }
public ICommand RefreshCommand { get; }
private void Refresh()
{
// ...
}
}
Working demo available here.
First of all, MouseDown event doesn't seem to work well in the case of MenuItem. So, you need to use PreviewMouseDown or probaby Click event instread. Also, you cannot use ElementName for referring elements outside of ContextMenu.
Then, assuming DataContext of the ListView is implicitly bound to TabItemViewModel and you want to specify that ListView as CommandParameter, it could be modified as follows:
<MenuItem Header="Refresh">
<i:Interaction.Triggers>
<i:EventTrigger EventName="Click">
<i:InvokeCommandAction
Command="{Binding PlacementTarget.DataContext.MouseDownCommand, RelativeSource={RelativeSource AncestorType={x:Type ContextMenu}}}"
CommandParameter="{Binding PlacementTarget, RelativeSource={RelativeSource AncestorType={x:Type ContextMenu}}}"/>
</i:EventTrigger>
</i:Interaction.Triggers>
</MenuItem>

WPF How to change the color of a gridviewcolumn

To preface the bulk of the question, I've found several answers regarding CellTemplate and DataTemplate, as well as one setting HeaderContainerStyle, and none of them worked; results ranged from no change to a crash.
I'm trying to change the color of the first entry in each column for my listview (basically so the header for each column sticks out). Here's the most recent thing I've tried with no success. Any suggestions or advice are greatly appreciated.
<ListView Height="380" VerticalAlignment="Top" HorizontalContentAlignment="Stretch">
<ListView.View>
<GridView>
<GridViewColumn Header="First" Width="60" >
<GridViewColumn.CellTemplate>
<DataTemplate>
<TextBlock x:Name="Txt" Text="{Binding First}" Background="DarkBlue"/>
</DataTemplate>
</GridViewColumn.CellTemplate>
</GridViewColumn>
<GridViewColumn Header="Last" Width="60" DisplayMemberBinding="{Binding Last}" />
<GridViewColumn Header="Group" Width="80" DisplayMemberBinding="{Binding Group}" />
</GridView>
</ListView.View>
</ListView>
Is the following code what you are looking for?
For the first row:
<GridViewColumn.CellTemplate>
<DataTemplate>
<TextBlock x:Name="Txt" Text="{Binding First}"/>
<DataTemplate.Triggers>
<DataTrigger Binding="{Binding RelativeSource={RelativeSource PreviousData}}" Value="{x:Null}">
<Setter TargetName="Txt" Property="Background" Value="DarkBlue"/>
</DataTrigger>
</DataTemplate.Triggers>
</DataTemplate>
</GridViewColumn.CellTemplate>
For the header:
<GridViewColumn Header="First" Width="60" >
<GridViewColumn.HeaderContainerStyle>
<Style TargetType="{x:Type GridViewColumnHeader}">
<Setter Property="Background" Value="LightSkyBlue"/>
</Style>
</GridViewColumn.HeaderContainerStyle>
</GridViewColumn>

How to get ListView SelectedItem while editing?

I'm trying to save record when combination of keys pressed Ctrl + S.
Below code works fine only when i exit TextBox and select line then press Ctrl + S.
How to SelectItem without exiting TextBox? For example: typing text >> Ctrl + S >> continue typing.
Here is my code:
if (Keyboard.IsKeyDown(Key.LeftCtrl) && Keyboard.IsKeyDown(Key.S))
{
sql.saveSoftwareChanges(_list.SelectedItem as Software);
e.Handled = true;
}
and XAML:
<ListView x:Name="_list" Visibility="Visible" KeyDown="_list_KeyDown">
<!--RESOURCES-->
<ListView.Resources>
<Style TargetType="ListViewItem">
<Style.Triggers>
<Trigger Property="IsKeyboardFocusWithin" Value="true">
<Setter Property="IsSelected" Value="true" />
</Trigger>
<Trigger Property="IsSelected" Value="true">
<Setter Property="Background" Value="#FFFFFF9A" />
</Trigger>
</Style.Triggers>
</Style>
</ListView.Resources>
<!--/RESOURCES-->
<ListView.View>
<GridView>
<GridViewColumn>
<GridViewColumn.CellTemplate>
<DataTemplate>
<Button x:Name="_save" Content="Save" MinWidth="20" Width="AUTO" Click="_save_Click" />
</DataTemplate>
</GridViewColumn.CellTemplate>
</GridViewColumn>
<GridViewColumn Header="Product ID">
<GridViewColumn.CellTemplate>
<DataTemplate>
<TextBox Text="{Binding productId}" MinWidth="20" Width="AUTO" Padding="2"/>
</DataTemplate>
</GridViewColumn.CellTemplate>
</GridViewColumn>
<GridViewColumn Header="Product">
<GridViewColumn.CellTemplate>
<DataTemplate>
<TextBox Text="{Binding product}" MinWidth="20" Width="AUTO" Padding="2"/>
</DataTemplate>
</GridViewColumn.CellTemplate>
</GridViewColumn>
<GridViewColumn Header="Path">
<GridViewColumn.CellTemplate>
<DataTemplate>
<TextBox Text="{Binding path}" MinWidth="20" Width="AUTO" Padding="2"/>
</DataTemplate>
</GridViewColumn.CellTemplate>
</GridViewColumn>
<GridViewColumn Header="Master Path">
<GridViewColumn.CellTemplate>
<DataTemplate>
<TextBox Text="{Binding master_path}" MinWidth="20" Width="AUTO" Padding="2"/>
</DataTemplate>
</GridViewColumn.CellTemplate>
</GridViewColumn>
<GridViewColumn Header="Parameters">
<GridViewColumn.CellTemplate>
<DataTemplate>
<TextBox Text="{Binding parameters}" MinWidth="20" Width="AUTO" Padding="2"/>
</DataTemplate>
</GridViewColumn.CellTemplate>
</GridViewColumn>
<GridViewColumn Header="Windows Version">
<GridViewColumn.CellTemplate>
<DataTemplate>
<TextBox Text="{Binding windowsVersion}" MinWidth="20" Width="AUTO" Padding="2"/>
</DataTemplate>
</GridViewColumn.CellTemplate>
</GridViewColumn>
</GridView>
</ListView.View>
</ListView>
After a lot of research and trial and error I've got the solution.
It's as simple as moving out of that field, which can be achieved by:
MoveFocus(new TraversalRequest(FocusNavigationDirection.Next));
and entire method looks like:
if (Keyboard.IsKeyDown(Key.LeftCtrl) && Keyboard.IsKeyDown(Key.S))
{
var uie = e.OriginalSource as UIElement;
uie.MoveFocus(new TraversalRequest(FocusNavigationDirection.Next));
sql.saveSoftwareChanges(_list.SelectedItem as Software);
uie.Focus();
e.Handled = true;
}
I hope that will help others as well.
You have to place your save code in a function then call this function from any control that may have the focus at that time. (i.e. the text box as well).
Remember that keyboard events only fire when the corresponding control has focus.
Test this:
<ListView x:Name="_list" Visibility="Visible" KeyDown="_list_KeyDown">
<!--RESOURCES-->
<ListView.Resources>
<Style TargetType="ListViewItem">
<Style.Triggers>
<Trigger Property="IsKeyboardFocusWithin" Value="true">
<Setter Property="IsSelected" Value="true" />
</Trigger>
<Trigger Property="IsSelected" Value="true">
<Setter Property="Background" Value="#FFFFFF9A" />
</Trigger>
</Style.Triggers>
</Style>
</ListView.Resources>
<!--/RESOURCES-->
<ListView.View>
<GridView>
<GridViewColumn>
<GridViewColumn.CellTemplate>
<DataTemplate>
<Button x:Name="_save" Content="Save" MinWidth="20" Width="AUTO" Click="_save_Click" />
</DataTemplate>
</GridViewColumn.CellTemplate>
</GridViewColumn>
<GridViewColumn Header="Product ID">
<GridViewColumn.CellTemplate>
<DataTemplate>
<TextBox Text="{Binding productId}" MinWidth="20" Width="AUTO" Padding="2" KeyDown="_list_KeyDown"/>
</DataTemplate>
</GridViewColumn.CellTemplate>
</GridViewColumn>
<GridViewColumn Header="Product">
<GridViewColumn.CellTemplate>
<DataTemplate>
<TextBox Text="{Binding product}" MinWidth="20" Width="AUTO" Padding="2" KeyDown="_list_KeyDown"/>
</DataTemplate>
</GridViewColumn.CellTemplate>
</GridViewColumn>
<GridViewColumn Header="Path">
<GridViewColumn.CellTemplate>
<DataTemplate>
<TextBox Text="{Binding path}" MinWidth="20" Width="AUTO" Padding="2" KeyDown="_list_KeyDown"/>
</DataTemplate>
</GridViewColumn.CellTemplate>
</GridViewColumn>
<GridViewColumn Header="Master Path">
<GridViewColumn.CellTemplate>
<DataTemplate>
<TextBox Text="{Binding master_path}" MinWidth="20" Width="AUTO" Padding="2" KeyDown="_list_KeyDown"/>
</DataTemplate>
</GridViewColumn.CellTemplate>
</GridViewColumn>
<GridViewColumn Header="Parameters">
<GridViewColumn.CellTemplate>
<DataTemplate>
<TextBox Text="{Binding parameters}" MinWidth="20" Width="AUTO" Padding="2" KeyDown="_list_KeyDown"/>
</DataTemplate>
</GridViewColumn.CellTemplate>
</GridViewColumn>
<GridViewColumn Header="Windows Version">
<GridViewColumn.CellTemplate>
<DataTemplate>
<TextBox Text="{Binding windowsVersion}" MinWidth="20" Width="AUTO" Padding="2" KeyDown="_list_KeyDown"/>
</DataTemplate>
</GridViewColumn.CellTemplate>
</GridViewColumn>
</GridView>
</ListView.View>
</ListView>

GridViewColumn Header Binding Fails When In A Style

I have a ListView where the View contains a GridView and a number of column definitions.
As Below:
<ListView Name="SampleListView" ItemsSource="{Binding SomeSource}" >
<ListView.View>
<GridView AllowsColumnReorder="False">
<GridViewColumn Header="{Binding ColumnHeader1,Mode=OneWay}" CellTemplate="{StaticResource Column1Template}" />
<GridViewColumn Header="{Binding ColumnHeader2,Mode=OneWay}" CellTemplate="{StaticResource Column2Template}" />
<GridViewColumn Header="{Binding ColumnHeader3,Mode=OneWay}" CellTemplate="{StaticResource Column3Template}" />
<GridViewColumn Header="{Binding ColumnHeader4,Mode=OneWay}" CellTemplate="{StaticResource Column4Template}" />
<GridViewColumn Header="{Binding ColumnHeader5,Mode=OneWay}" CellTemplate="{StaticResource Column5Template}" />
</GridView>
</ListView.View>
</ListView>
(This works perfectly)
I have a requirement to hide the first column on a user preference so I created a Style Trigger to accomplish this. The code with the style trigger is as below
<ListView Name="SampleListView" ItemsSource="{Binding SomeSource}" >
<ListView.Resources>
<Style x:Key="{x:Type ListView}" TargetType="ListView">
<Style.Triggers>
<DataTrigger Binding="{Binding Source={x:Static p:User.Default},Path=ShowColumn1,Mode=OneWay}" Value="False">
<Setter Property="View">
<Setter.Value>
<GridView x:Name="WithOutColumn1" AllowsColumnReorder="False">
<GridViewColumn Header="{Binding ColumnHeader2,Mode=OneWay}" CellTemplate="{StaticResource Column2Template}" />
<GridViewColumn Header="{Binding ColumnHeader3,Mode=OneWay}" CellTemplate="{StaticResource Column3Template}" />
<GridViewColumn Header="{Binding ColumnHeader4,Mode=OneWay}" CellTemplate="{StaticResource Column4Template}" />
<GridViewColumn Header="{Binding ColumnHeader5,Mode=OneWay}" CellTemplate="{StaticResource Column5Template}" />
</GridView>
</Setter.Value>
</Setter>
</DataTrigger>
<DataTrigger Binding="{Binding Source={x:Static p:User.Default},Path=ShowColumn1,Mode=OneWay}" Value="True">
<Setter Property="View">
<Setter.Value>
<GridView x:Name="WithColumn1" AllowsColumnReorder="False">
<GridViewColumn Header="{Binding ColumnHeader1,Mode=OneWay}" CellTemplate="{StaticResource Column1Template}" />
<GridViewColumn Header="{Binding ColumnHeader2,Mode=OneWay}" CellTemplate="{StaticResource Column2Template}" />
<GridViewColumn Header="{Binding ColumnHeader3,Mode=OneWay}" CellTemplate="{StaticResource Column3Template}" />
<GridViewColumn Header="{Binding ColumnHeader4,Mode=OneWay}" CellTemplate="{StaticResource Column4Template}" />
<GridViewColumn Header="{Binding ColumnHeader5,Mode=OneWay}" CellTemplate="{StaticResource Column5Template}" />
</GridView>
</Setter.Value>
</Setter>
</DataTrigger>
</Style.Triggers>
</Style>
</ListView.Resources>
</ListView>
When this style is used the Binding for the GridViewColumn Header property does not work.
But the Binding for the CellTemplate does and displays correctly.
The Output Window in VS Shows this Error
System.Windows.Data Error: 2 : Cannot find governing FrameworkElement or FrameworkContentElement for target element. BindingExpression:Path=ColumnHeader1; DataItem=null; target element is 'GridViewColumn' (HashCode=56585823); target property is 'Header' (type 'Object
Can anyone shed some light on why this occurs and how to resolve it?
Things Tried:
Using a RelativePath binding to ensure the DataContext is valid.
(Suspect this doesn't work as i think the GridViewColumn definition
exists outside the visual tree from this post Here)
Creating a DataTemplate for the HeaderTemplate : Same result.
Using an FrameWorkElement as a Proxy as per this post Here
In Case anyone else is having an issue like this i have found a resolution.
The GridViewColumn Header property must be set and bound within XAML tags for that item as below.
<Setter Property="View">
<Setter.Value>
<GridView x:Name="WithOutColumn1" AllowsColumnReorder="False">
<GridViewColumn CellTemplate="{StaticResource Column2Template}">
<GridViewColumnHeader Content="{Binding ColumnHeader2}" />
</GridViewColumn>
<GridViewColumn CellTemplate="{StaticResource Column3Template}">
<GridViewColumnHeader Content="{Binding ColumnHeader3}" />
</GridViewColumn>
<GridViewColumn CellTemplate="{StaticResource Column4Template}">
<GridViewColumnHeader Content="{Binding ColumnHeader3}" />
</GridViewColumn>
<GridViewColumn CellTemplate="{StaticResource Column5Template}">
<GridViewColumnHeader Content="{Binding ColumnHeader3}" />
</GridViewColumn>
</GridView>
</Setter.Value>
Hope fully this will be helpful if you encounter a similar issue.

Categories

Resources