WPF Gridview ContextMenu - c#

Does anybody know how to add a ContextMenu to each row in a GridView? The following example works but adds the contextmenu to the whole control, also on the empty space, scrollbar, header, etc.. Thanks for any help.
<GridView ContextMenu="{StaticResource DisplayedContextMenu}" ItemsSource="{Binding Data}" >
<GridView.View>
<GridView>
<GridViewColumn Header="Position" Width="0" DisplayMemberBinding="{Binding Position}" />
...

<DataGrid Name="grid">
<DataGrid.Resources>
<Style TargetType="DataGridRow">
<Setter Property="ContextMenu">
<Setter.Value>
<ContextMenu>
<MenuItem Header="Copy Row" />
<MenuItem Header="Paste Row" />
</ContextMenu>
</Setter.Value>
</Setter>
</Style>
</DataGrid.Resources>
<DataGrid.ContextMenu>
<ContextMenu>
<MenuItem Header="Copy Grid" />
<MenuItem Header="Paste Grid" />
</ContextMenu>
</DataGrid.ContextMenu>
</DataGrid>
In case of ListView you should override style for ListViewItem and etc.

Related

Hiding wpf ContextMenu when the Data Grid is Empty?

I am trying to make a simple TODO App with WPF using MVVM.
I have the tasks in Datagrid and a right click context menu with Delete option. I don't want the context menu to show when there are no elements in Data Grid. What can I do to solve it?
<Grid.RowDefinitions>
<RowDefinition Height="auto"/>
<RowDefinition Height="*"/>
</Grid.RowDefinitions>
<DataGrid ItemsSource="{Binding Path=TaskList}" SelectedItem="{Binding SelectedTask}" AutoGenerateColumns="False">
<DataGrid.ContextMenu >
<ContextMenu>
<MenuItem Header="Delete" Command="{Binding OpenDialogCommand}" CommandParameter="{Binding PlacementTarget.SelectedItem, RelativeSource={RelativeSource FindAncestor, AncestorType={x:Type ContextMenu}}}"></MenuItem>
</ContextMenu>
</DataGrid.ContextMenu>
<DataGrid.Columns>
<DataGridTextColumn Header="Priority" Width="auto" Binding="{Binding Path=Id}"/>
<DataGridTextColumn Header="Task" Width="auto" Binding="{Binding Path=TaskName}"/>
</DataGrid.Columns>
</DataGrid>
You could set the ContextMenu property conditionally using a Style with a Trigger:
<DataGrid ItemsSource="{Binding Path=TaskList}" SelectedItem="{Binding SelectedTask}" AutoGenerateColumns="False">
<DataGrid.Style>
<Style TargetType="DataGrid">
<Setter Property="ContextMenu">
<Setter.Value>
<ContextMenu>
<MenuItem Header="Delete" Command="{Binding OpenDialogCommand}"
CommandParameter="{Binding PlacementTarget.SelectedItem, RelativeSource={RelativeSource FindAncestor, AncestorType={x:Type ContextMenu}}}"></MenuItem>
</ContextMenu>
</Setter.Value>
</Setter>
<Style.Triggers>
<Trigger Property="HasItems" Value="False">
<Setter Property="ContextMenu" Value="{x:Null}" />
</Trigger>
</Style.Triggers>
</Style>
</DataGrid.Style>
<DataGrid.Columns>
<DataGridTextColumn Header="Priority" Width="auto" Binding="{Binding Path=Id}"/>
<DataGridTextColumn Header="Task" Width="auto" Binding="{Binding Path=TaskName}"/>
</DataGrid.Columns>
</DataGrid>
You could bind visibility and use the built in booleantovisibility converter
You need the converter. This could be in a resource dictionary you merge in app.xaml to use across the app.
<Window.Resources>
<BooleanToVisibilityConverter x:Key="booleanToVisibilityConverter"/>
</Window.Resources>
Then just bind visibility on the context menu
<DataGrid ...... >
<DataGrid.ContextMenu>
<ContextMenu Visibility="{Binding PlacementTarget.HasItems, RelativeSource={RelativeSource Self},
Converter={StaticResource booleanToVisibilityConverter}}">
<MenuItem Header="Delete"
Command="{Binding OpenDialogCommand}"
CommandParameter="{Binding PlacementTarget.SelectedItem, RelativeSource={RelativeSource FindAncestor, AncestorType={x:Type ContextMenu}}}"/>
</ContextMenu>
</DataGrid.ContextMenu>
As one of variations, you can use ContextMenuService.IsEnabled attached property and bind it with ItemsControl.HasItems.
<DataGrid ...
ContextMenuService.IsEnabled="{Binding RelativeSource={RelativeSource Self}, Path=HasItems}">
I got a simple hack to solve this. I changed the row definition from * to auto for row 2; This will stop context menu from showing when you click anywhere other than Datagrid Elements.
<RowDefinition Height="auto"/>
Also to remove one extra row which always shows(and display context menu when right clicked)
This worked.
<DataGrid Margin ="10" x:Name="dgTaks" Grid.Row="1" Grid.ColumnSpan="2" ItemsSource="{Binding Path=TaskList}" SelectedItem="{Binding SelectedTask}" AutoGenerateColumns="False" **CanUserAddRows="False"**>

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>

`ContextMenu` Command binding inside `ListView.ItemContainerStyle` does not work

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>

How to enable sorting for programmatically added DataGridTextColumn in WPF?

I have a DataGrid with 2 fixed columns that can be sorted without any problem.
<DataGrid Name="SelectedObjectsGrid"
Visibility="{Binding ShowSelectedObjectsInfoPanel, Converter={StaticResource BoolNegationToVisibilityConverter}}"
ItemsSource="{Binding SelectedObjectItems}"
SelectionMode="Extended"
CanUserAddRows="False"
AutoGenerateColumns="False"
VirtualizingPanel.IsVirtualizing="True"
VirtualizingPanel.IsVirtualizingWhenGrouping="True"
VirtualizingPanel.VirtualizationMode="Standard"
IsReadOnly="True"
Grid.Row="0" Margin="0,0,4,0">
<DataGrid.RowStyle>
<Style TargetType="DataGridRow" BasedOn="{StaticResource Theme.DataGrid.Row.Style}">
<Setter Property="ContextMenu" Value="{StaticResource SelectedObjectRowContextMenu}" />
<EventSetter Event="MouseDoubleClick" Handler="SelectedObjectsRow_DoubleClick" />
</Style>
</DataGrid.RowStyle>
<i:Interaction.Triggers>
<i:EventTrigger EventName="SelectionChanged">
<i:InvokeCommandAction Command="{ui:CommandHandler ObjectsGridSelectionChangedCommand}" CommandParameter="{Binding SelectedItems,ElementName=SelectedObjectsGrid}">
</i:InvokeCommandAction>
</i:EventTrigger>
</i:Interaction.Triggers>
<DataGrid.GroupStyle>
<!-- Style for groups at top level. -->
<GroupStyle>
<GroupStyle.ContainerStyle>
<Style TargetType="{x:Type GroupItem}">
<Setter Property="Margin" Value="0,0,0,5"/>
<Setter Property="Template">
<Setter.Value>
<ControlTemplate TargetType="{x:Type GroupItem}">
<Expander IsExpanded="{Binding Path=Name.IsExpanded}"
Tag="{Binding Path=Name}"
Background="#FF112255" BorderBrush="#FF002255"
Foreground="#FFEEEEEE" BorderThickness="1,1,1,5">
<Expander.ContextMenu>
<ContextMenu DataContext="{Binding PlacementTarget.DataContext, RelativeSource={RelativeSource Self}}">
<MenuItem Header="{DynamicResource XpStrKeepSelectionTableShort}" Command="{ui:CommandHandler KeepTable}" CommandParameter="{Binding Name}" />
<MenuItem Header="{DynamicResource XpStrRemoveSelectionTableShort}" Command="{ui:CommandHandler RemoveTable}" CommandParameter="{Binding Name}" />
<Separator/>
<MenuItem Header="{DynamicResource XpStrList}" Command="{ui:CommandHandler ToggleSelectedObjectsGrouping}" CommandParameter="False" >
<MenuItem.Icon>
<Image>
<Image.Source>
<DrawingImage>
<DrawingImage.Drawing>
<GeometryDrawing Brush="Black" Geometry="{Binding DataContext.AreSelectedObjectsGroupedByTable,
Source={x:Reference SelectedObjectsGrid}, Converter={StaticResource BoolNegationToGeometryConverter}}" />
</DrawingImage.Drawing>
</DrawingImage>
</Image.Source>
</Image>
</MenuItem.Icon>
</MenuItem>
<MenuItem Header="{DynamicResource XpStrGroups}" Command="{ui:CommandHandler ToggleSelectedObjectsGrouping}" CommandParameter="True" >
<MenuItem.Icon>
<Image>
<Image.Source>
<DrawingImage>
<DrawingImage.Drawing>
<GeometryDrawing Brush="Black" Geometry="{Binding DataContext.AreSelectedObjectsGroupedByTable,
Source={x:Reference SelectedObjectsGrid}, Converter={StaticResource BoolToGeometryConverter}}" />
</DrawingImage.Drawing>
</DrawingImage>
</Image.Source>
</Image>
</MenuItem.Icon>
</MenuItem>
<Separator Visibility="{Binding DataContext.AreSelectedObjectsGroupedByTable, Source={x:Reference SelectedObjectsGrid}, Converter={StaticResource BoolToVisibilityConverter}}" />
<MenuItem Header="{DynamicResource XpStrHintTreeExpandAll}" Command="{ui:CommandHandler ExpandOrCollapseAll}" CommandParameter="True"
Visibility="{Binding DataContext.SelectedObjectsGroupHeaders.DoCollapsedGroupsExist,
Source={x:Reference SelectedObjectsGrid}, Converter={StaticResource BoolToVisibilityConverter}}">
<MenuItem.Icon>
<Image Source="{Binding Converter={StaticResource nameToBitmapSource}}" DataContext="BmpTreeExpandAll"/>
</MenuItem.Icon>
</MenuItem>
<MenuItem Header="{DynamicResource XpStrHintTreeCollapseAll}" Command="{ui:CommandHandler ExpandOrCollapseAll}" CommandParameter="False"
Visibility="{Binding DataContext.SelectedObjectsGroupHeaders.DoExpandedGroupsExist,
Source={x:Reference SelectedObjectsGrid}, Converter={StaticResource BoolToVisibilityConverter}}">
<MenuItem.Icon>
<Image Source="{Binding Converter={StaticResource nameToBitmapSource}}" DataContext="BmpTreeCollapseAll"/>
</MenuItem.Icon>
</MenuItem>
</ContextMenu>
</Expander.ContextMenu>
<Expander.Header>
<DockPanel>
<TextBlock FontWeight="Bold" Margin="5,0,0,0" IsHitTestVisible="False">
<Run Text="{Binding Path=Name.Description, Mode=OneWay}" />
<Run Text=" ("/>
<Run Text="{Binding Path=Name.GroupedCount, Mode=OneWay}" />
<Run Text=")"/>
</TextBlock>
</DockPanel>
</Expander.Header>
<Expander.Content>
<ItemsPresenter/>
</Expander.Content>
</Expander>
</ControlTemplate>
</Setter.Value>
</Setter>
</Style>
</GroupStyle.ContainerStyle>
</GroupStyle>
</DataGrid.GroupStyle>
<DataGrid.Columns>
<DataGridTextColumn Header="{DynamicResource XpStrLabel}" Binding="{Binding Data.Label}" SortDirection="Ascending"/>
<DataGridTextColumn Header="{DynamicResource XpStrClass}" Binding="{Binding Data.Class.Name}" />
</DataGrid.Columns>
</DataGrid>
When program is properly initialized I call method below to add dynamic columns that can't be sorted:
I have piece of code that adds:
private void AddDynamicColumns()
{
var separator = new char[] {';', ':'};
foreach (var parameter in Parameters.Instance.GetParameters("SelectedObjectsDynField"))
{
var values = parameter.StringValue.Split(separator);
var fieldName = values[0];
var fieldDescr = fieldName;
if (values.Length > 1)
fieldDescr = values[1];
var col = new DataGridTextColumn();
col.CanUserSort = true;
col.Header = fieldDescr;
var bind = new MultiBinding();
bind.Converter = new NetObjectToPrintableFieldValueConverter();
bind.Bindings.Add(new Binding("Data"));
bind.Bindings.Add(new Binding() { Source = fieldName });
col.Binding = bind;
SelectedObjectsGrid.Columns.Add(col);
}
}
What could be the reason for this?
EDIT: Adding
col.SortMemberPath = fieldName
makes it somewhat work, but it breaks virtualization. Populating DataGrid takes now 20 times longer. Also the sorting does not sort rows correctly. Clicking 2nd time dynamic column to sort it descending does nothing.
Based on this answer, setting the RowHeight makes the WPF DataGrid not lose virtualization when is sorted by specific columns.

Categories

Resources