I'm having some issues trying to get a filter box, which is added to the header of a datagrid column through styling, working. When I do not sort the column everything is working fine, no issues there, but the moment I sort, I don't see what's typed anymore and I can only filter on one char (can't clear the filter either as there're no characters to delete and trigger the TextChanged event).
What's also odd is that the TextBox is nowhere to be found in the visual tree, starting from the DataGridColumnHeader instance, when the column is sorted. If I could find it, I could reset the text at the end of the cycle and the issue would probably be solved.
Visually it looks like this, I typed a "P" in all 3 cases:
The relevant styling that's behind this is the following:
<Page.Resources>
<Style x:Key="Filter" TargetType="TextBox">
<EventSetter Event="TextChanged" Handler="Filter_TextBox_TextChanged"/>
</Style>
<Style TargetType="{x:Type DataGrid}">
<!-- Some visual styling like margins and colors on the grid -->
<Style.Resources>
<Style TargetType="{x:Type DataGridColumnHeader}">
<!-- Some visual styling like margins and colors on the header -->
<Setter Property="ContentTemplate">
<Setter.Value>
<DataTemplate>
<StackPanel>
<Grid>
<Grid.RowDefinitions>
<RowDefinition Height="Auto"/>
<RowDefinition Height="Auto"/>
</Grid.RowDefinitions>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="*"/>
<ColumnDefinition Width="Auto"/>
<ColumnDefinition Width="Auto"/>
</Grid.ColumnDefinitions>
<TextBlock Grid.Column="0" Grid.Row="0" Margin="10,0,10,0" TextWrapping="WrapWithOverflow" Text="{Binding}"/>
<fa:FontAwesome Grid.Column="1" Grid.Row="0" Icon="LongArrowUp" Foreground="#dbdbdb" HorizontalAlignment="Right" VerticalAlignment="Bottom"/>
<fa:FontAwesome Grid.Column="2" Grid.Row="0" Margin="0,0,10,0" Icon="LongArrowDown" Foreground="#dbdbdb" HorizontalAlignment="Right" VerticalAlignment="Bottom"/>
<StackPanel Grid.Column="0" Grid.ColumnSpan="3" Grid.Row="1" Margin="0,10,0,0" HorizontalAlignment="Stretch" VerticalAlignment="Bottom" Background="#dbdbdb">
<TextBox Style="{StaticResource Filter}" />
</StackPanel>
</Grid>
</StackPanel>
</DataTemplate>
</Setter.Value>
</Setter>
<Style.Triggers>
<Trigger Property="SortDirection" Value="Ascending">
<Setter Property="Background" Value="#F0F0F0"/>
<Setter Property="ContentTemplate">
<Setter.Value>
<DataTemplate>
<StackPanel>
<Grid>
<Grid.RowDefinitions>
<RowDefinition Height="Auto"/>
<RowDefinition Height="Auto"/>
</Grid.RowDefinitions>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="*"/>
<ColumnDefinition Width="Auto"/>
</Grid.ColumnDefinitions>
<TextBlock Grid.Column="0" Grid.Row="0" Margin="5,0,10,0" TextWrapping="WrapWithOverflow" Text="{Binding}"/>
<fa:FontAwesome Grid.Column="1" Grid.Row="0" Margin="0,0,10,0" Icon="SortAmountAsc" Foreground="#919191" HorizontalAlignment="Right" VerticalAlignment="Bottom"/>
<StackPanel Grid.Column="0" Grid.ColumnSpan="3" Margin="0,10,0,0" Grid.Row="1" HorizontalAlignment="Stretch" VerticalAlignment="Stretch" Background="#dbdbdb">
<TextBox Style="{StaticResource Filter}" />
</StackPanel>
</Grid>
</StackPanel>
</DataTemplate>
</Setter.Value>
</Setter>
</Trigger>
<Trigger Property="SortDirection" Value="Descending">
<Setter Property="Background" Value="#F0F0F0"/>
<Setter Property="ContentTemplate">
<Setter.Value>
<DataTemplate>
<StackPanel>
<Grid>
<Grid.RowDefinitions>
<RowDefinition Height="Auto"/>
<RowDefinition Height="Auto"/>
</Grid.RowDefinitions>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="*"/>
<ColumnDefinition Width="Auto"/>
</Grid.ColumnDefinitions>
<TextBlock Grid.Row="0" Grid.Column="0" Margin="5,0,10,0" TextWrapping="WrapWithOverflow" Text="{Binding}"/>
<fa:FontAwesome Grid.Row="0" Grid.Column="1" Margin="0,0,10,0" Icon="SortAmountDesc" Foreground="#919191" HorizontalAlignment="Right" VerticalAlignment="Bottom"/>
<StackPanel Grid.Column="0" Grid.ColumnSpan="2" Grid.Row="1" Margin="0,10,0,0" HorizontalAlignment="Stretch" VerticalAlignment="Stretch" Background="#dbdbdb">
<TextBox Style="{StaticResource Filter}" />
</StackPanel>
</Grid>
</StackPanel>
</DataTemplate>
</Setter.Value>
</Setter>
</Trigger>
</Style.Triggers>
</Style>
Nothing special in the definition of the DataGrid:
<DataGrid x:Name="History_DataGrid" Grid.Row="0" Grid.Column="0" Grid.ColumnSpan="3" HeadersVisibility="Column" GridLinesVisibility="None" ItemsSource="{Binding history}" AutoGenerateColumns="False" HorizontalAlignment="Stretch" VerticalAlignment="Stretch" SelectionMode="Single" SelectionUnit="FullRow" CanUserAddRows="false" SelectionChanged="History_DataGrid_SelectionChanged" Sorting="History_DataGrid_Sorting">
<DataGrid.Columns>
<DataGridTextColumn Header="{x:Static resx:Resources.Product}" Binding="{Binding Path=Product}" Width="*" ElementStyle="{StaticResource Wrap}" />
<DataGridTextColumn Header="{x:Static resx:Resources.Label}" Binding="{Binding Path=Label}" Width="*" ElementStyle="{StaticResource Wrap}" />
<DataGridTextColumn Header="{x:Static resx:Resources.Color}" Binding="{Binding Path=Color}" Width="*" ElementStyle="{StaticResource Wrap}" />
<DataGridTextColumn Header="{x:Static resx:Resources.Volume}" Binding="{Binding Path=Volume}" Width="Auto" ElementStyle="{StaticResource Wrap}" />
<DataGridTextColumn Header="{x:Static resx:Resources.LastPrinted}" Binding="{Binding Path=LastPrinted, StringFormat=\{0:yyyy-MM-dd\}}" Width="Auto" ElementStyle="{StaticResource Wrap}" />
<DataGridTextColumn Header="{x:Static resx:Resources.TimesPrinted}" Binding="{Binding Path=TimesPrinted}" Width="Auto" ElementStyle="{StaticResource Wrap}" />
</DataGrid.Columns>
</DataGrid>
Possible ways to bypass the issue, though I have no idea how to accomplish them:
Create a second header row with the filter boxes that doesn't changes
after sorting.
Setting the value of the filter textbox at the end of
the event cycle, I have a function where I prepare the paging
controls that is triggered after the datagrid is rebound, though I
need to be able to access the textbox instances for that. I can if
it's unsorted, but I can't find the box when the colums is sorted.
Any idea how to get this fixed, it's really a breaking issue and none of the search results have come up with anything usefull.
EDIT: Some scenarios for clarification
How it should work and how it works as long as I don't sort:
I type 'A' in the textbox.
The source of the datagrid is filtered on the the text "A" and rebound.
The textbox shows "A" as the filter.
I type 'B' in the textbox.
The source of the datagrid is filtered on the the text "AB" and rebound.
The textbox shows "AB" as the filter.
When the column is sorted, it behaves quite differently:
I sort the column and it loses the text that was already in the textbox.
I type 'A' in the textbox.
The source of the datagrid is filtered on the the text "A" and rebound.
The textbox stays empty and does not show "A" as the filter.
I type 'B' in the textbox.
The source of the datagrid is filtered on the the text "B", not "AB" (it lost the "A"), and rebound.
The textbox stays empty and does not show "A", "B" or "AB" as the filter.
As a result removing the filter is quite hard as well as there's no text to delete and trigger the TextChanged event.
I got into contact with some WPF specialists in my company and they helped me reach a workable solution.
The edited Style looks as followed:
<Page.Resources>
<Style x:Key="Filter" TargetType="TextBox">
<EventSetter Event="TextChanged" Handler="Filter_TextBox_TextChanged"/>
</Style>
<Style TargetType="{x:Type DataGrid}">
<!-- Some visual styling like margins and colors on the grid -->
<Style.Resources>
<Style TargetType="{x:Type DataGridColumnHeader}">
<!-- Some visual styling like margins and colors on the header -->
<Setter Property="ContentTemplate">
<Setter.Value>
<DataTemplate>
<StackPanel>
<Grid>
<Grid.RowDefinitions>
<RowDefinition Height="Auto"/>
<RowDefinition Height="Auto"/>
</Grid.RowDefinitions>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="*"/>
<ColumnDefinition Width="Auto"/>
<ColumnDefinition Width="Auto"/>
</Grid.ColumnDefinitions>
<TextBlock Grid.Column="0" Grid.Row="0" Margin="10,0,10,0" TextWrapping="WrapWithOverflow" Text="{Binding}"/>
<fa:FontAwesome Grid.Column="1" Grid.Row="0" x:Name="SortIcon1" Icon="LongArrowUp" Foreground="#dbdbdb" HorizontalAlignment="Right" VerticalAlignment="Bottom"/>
<fa:FontAwesome Grid.Column="2" Grid.Row="0" x:Name="SortIcon2" Margin="0,0,10,0" Icon="LongArrowDown" Foreground="#dbdbdb" HorizontalAlignment="Right" VerticalAlignment="Bottom"/>
<StackPanel Grid.Column="0" Grid.ColumnSpan="3" Grid.Row="1" Margin="0,10,0,0" HorizontalAlignment="Stretch" VerticalAlignment="Bottom" Background="#dbdbdb">
<TextBox Style="{StaticResource Filter}" />
</StackPanel>
</Grid>
</StackPanel>
<DataTemplate.Triggers>
<DataTrigger Binding="{Binding SortDirection, RelativeSource={RelativeSource AncestorType=DataGridColumnHeader}}" Value="Ascending">
<Setter TargetName="SortIcon1" Property="Icon" Value="SortAmountAsc" />
<Setter TargetName="SortIcon2" Property="Visibility" Value="Hidden" />
</DataTrigger>
<DataTrigger Binding="{Binding SortDirection, RelativeSource={RelativeSource AncestorType=DataGridColumnHeader}}" Value="Descending">
<Setter TargetName="SortIcon1" Property="Icon" Value="SortAmountDesc" />
<Setter TargetName="SortIcon2" Property="Visibility" Value="Hidden" />
</DataTrigger>
</DataTemplate.Triggers>
</DataTemplate>
</Setter.Value>
</Setter>
<Style.Triggers>
<Trigger Property="SortDirection" Value="Ascending">
<Setter Property="Background" Value="#F0F0F0"/>
</Trigger>
<Trigger Property="SortDirection" Value="Descending">
<Setter Property="Background" Value="#F0F0F0"/>
</Trigger>
</Style.Triggers>
</Style>
Basically my main mistake was to have a DataTemplate per sort state. They pointed out that I could add a trigger to the Datatemplate bound to the Header's event. Adding a name to my sort icons made me able to change them with the TargetName attribute in the new trigger sections.
As there's now only one instance of the textbox, the issue is resolved.
An image is better than a thousand of words.
I have a list view Left image, and want to force my inner object (TextBlock) to use the full space of my ListViewItem, Right image.
How can i do to style it?
Here is my xaml
<ListView
x:Name="ListProperties"
Grid.Row="1"
ItemsSource="{Binding ImportarConfig.PropertiesList}">
<ListView.ItemTemplate>
<DataTemplate>
<Grid HorizontalAlignment="Center" VerticalAlignment="Center" Width="{Binding ElementName=ListProperties.Width}">
<Grid.ColumnDefinitions>
<ColumnDefinition Width="*"/>
</Grid.ColumnDefinitions>
<CheckBox IsChecked="{Binding IsOptional}" Grid.Column="1"/>
<TextBlock Text="{Binding ColumnName}" Style="{StaticResource BlockHeaderWarning}" AllowDrop="True" Drop="TextBlock_Drop" Width="{Binding ElementName=ListProperties.Width}"/>
</Grid>
</DataTemplate>
</ListView.ItemTemplate>
</ListView>
I tryed Changing Grid to Stackpanel but nothing happens
You need to use ItemContainerStyle and set your HorizontalAlignment and HorizontalContentAlignment to Stretch.
Something like:
<ListView
x:Name="ListProperties"
Grid.Row="1"
ItemsSource="{Binding ImportarConfig.PropertiesList}">
<ListView.ItemTemplate>
<DataTemplate>
<Grid HorizontalAlignment="Center" VerticalAlignment="Center" Width="{Binding ElementName=ListProperties.Width}">
<Grid.ColumnDefinitions>
<ColumnDefinition Width="*"/>
</Grid.ColumnDefinitions>
<CheckBox IsChecked="{Binding IsOptional}" Grid.Column="1"/>
<TextBlock Text="{Binding ColumnName}" Style="{StaticResource BlockHeaderWarning}" AllowDrop="True" Drop="TextBlock_Drop" Width="{Binding ElementName=ListProperties.Width}"/>
</Grid>
</DataTemplate>
</ListView.ItemTemplate>
<ListView.ItemContainerStyle>
<Style TargetType="ListViewItem">
<Setter Property="HorizontalAlignment" Value="Stretch" />
<Setter Property="HorizontalContentAlignment" Value="Stretch" />
</Style>
</ListView.ItemContainerStyle>
</ListView>
Why TextWrapping for the TextBlock in the Grid works only if I specify TexBlock Width?
When I remove TextBlock MaxWidth Property TextWrapping is not working.
Full code for ComboBox
<Window Height="600" Width="480">
<Grid>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="4*"></ColumnDefinition>
<ColumnDefinition Width="*"></ColumnDefinition>
</Grid.ColumnDefinitions>
<Grid.RowDefinitions>
<RowDefinition></RowDefinition>
<RowDefinition></RowDefinition>
</Grid.RowDefinitions>
<ComboBox Grid.Row="0" Grid.Column="0" ItemsSource="{Binding MyList}" SelectedIndex="0" x:Name="comboBox" HorizontalAlignment="Stretch" VerticalAlignment="Top">
<ComboBox.ItemTemplate>
<DataTemplate>
<StackPanel Orientation="Horizontal">
<TextBlock Margin="2" Text="{Binding Code}"></TextBlock>
<TextBlock Margin="2" Text="{Binding Name}"></TextBlock>
</StackPanel>
</DataTemplate>
</ComboBox.ItemTemplate>
<ComboBox.ItemContainerStyle>
<Style TargetType="ComboBoxItem">
<Setter Property="Template">
<Setter.Value>
<ControlTemplate>
<Grid ShowGridLines="False">
<Grid.ColumnDefinitions>
<ColumnDefinition MaxWidth="50"/>
<ColumnDefinition Name="Column1" MaxWidth="300"/>
</Grid.ColumnDefinitions>
<TextBlock Grid.Column="0" Text="{Binding Code}"/>
<TextBlock Grid.Column="1" MaxWidth="{Binding ElementName=Column1, Path=MaxWidth}" Text="{Binding Name}" TextWrapping="Wrap"/>
</Grid>
</ControlTemplate>
</Setter.Value>
</Setter>
</Style>
</ComboBox.ItemContainerStyle>
</ComboBox>
</Grid>
</Window>
The reason is by defining your ColumnDefinition as Auto or * or not setting it at all you have nothing to limit the size available for your TextBlock to consume. So it would be expected behavior for the Text to not Wrap. So you'll have to define a Width or MaxWidth on either the ColumnDefinition or the TextBlock directly in order for wrapping to work.
Why TextWrapping for the TextBlock in the Grid works only if I specify TexBlock Width?
Because the Grid gets added to an invisible ScrollViewer when the ComboBox is opened. You could fix this and get the text to wrap by simply setting the ScrollViewer.HorizontalScrollBarVisibility attached property of the ComboBox to Disabled:
<ComboBox Grid.Row="0" Grid.Column="0" ItemsSource="{Binding MyList}"
SelectedIndex="0" x:Name="comboBox" HorizontalAlignment="Stretch"
VerticalAlignment="Top" ScrollViewer.HorizontalScrollBarVisibility="Disabled">
<ComboBox.ItemTemplate>
<DataTemplate>
<StackPanel Orientation="Horizontal">
<TextBlock Margin="2" Text="{Binding Code}"></TextBlock>
<TextBlock Margin="2" Text="{Binding Name}"></TextBlock>
</StackPanel>
</DataTemplate>
</ComboBox.ItemTemplate>
<ComboBox.ItemsPanel>
<ItemsPanelTemplate>
<StackPanel Width="100" />
</ItemsPanelTemplate>
</ComboBox.ItemsPanel>
<ComboBox.ItemContainerStyle>
<Style TargetType="ComboBoxItem">
<Setter Property="Template">
<Setter.Value>
<ControlTemplate>
<Grid ShowGridLines="False">
<Grid.ColumnDefinitions>
<ColumnDefinition MaxWidth="50"/>
<ColumnDefinition MaxWidth="300"/>
</Grid.ColumnDefinitions>
<TextBlock Grid.Column="0" Text="Code..."/>
<TextBlock Grid.Column="1"
Text="some long text some long text some long text some long text some long text"
TextWrapping="Wrap"/>
</Grid>
</ControlTemplate>
</Setter.Value>
</Setter>
</Style>
</ComboBox.ItemContainerStyle>
</ComboBox>
I have a comobox defined as follows:
<ComboBox Grid.Row="1" Grid.Column="1" Grid.ColumnSpan="3" SelectedValue="{Binding Path=CurrentLineItem.Location, Mode=TwoWay}" SelectedValuePath="Location" DisplayMemberPath="Location" IsEnabled="{Binding Path=CurrentLineItem.Editing, Mode=OneWay}" ItemsSource="{Binding CurrentLineItem.LocationDetails}">
<ComboBox.ItemContainerStyle>
<Style TargetType="{x:Type ComboBoxItem}">
<Setter Property="Template">
<Setter.Value>
<ControlTemplate>
<Grid>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="40"/>
<ColumnDefinition Width="70"/>
<ColumnDefinition Width="70"/>
<ColumnDefinition Width="70"/>
</Grid.ColumnDefinitions>
<TextBlock Margin="5" Grid.Column="0" Text="{Binding Location, Mode=OneWay}"/>
<TextBlock Margin="5" Grid.Column="1" Text="{Binding OnHand, Mode=OneWay, StringFormat=\{0:N4\}}"/>
<TextBlock Margin="5" Grid.Column="2" Text="{Binding Allocated, Mode=OneWay, StringFormat=\{0:N4\}}"/>
<TextBlock Margin="5" Grid.Column="3" Text="0.0000"/>
</Grid>
</ControlTemplate>
</Setter.Value>
</Setter>
</Style>
</ComboBox.ItemContainerStyle>
</ComboBox>
This is working fine except that I want the selected item to be highlighted with a blue background. How do I do this?
<ComboBox Grid.Row="1" Grid.Column="1" Grid.ColumnSpan="3" SelectedValue="{Binding Path=CurrentLineItem.Location, Mode=TwoWay}" SelectedValuePath="Location" DisplayMemberPath="Location" IsEnabled="{Binding Path=CurrentLineItem.Editing, Mode=OneWay}" ItemsSource="{Binding CurrentLineItem.LocationDetails}">
<ComboBox.ItemContainerStyle>
<Style TargetType="{x:Type ComboBoxItem}">
<Setter Property="Template">
<Setter.Value>
<ControlTemplate>
<Grid>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="40"/>
<ColumnDefinition Width="70"/>
<ColumnDefinition Width="70"/>
<ColumnDefinition Width="70"/>
</Grid.ColumnDefinitions>
<TextBlock Margin="5" Grid.Column="0" Text="{Binding Location, Mode=OneWay}"/>
<TextBlock Margin="5" Grid.Column="1" Text="{Binding OnHand, Mode=OneWay, StringFormat=\{0:N4\}}"/>
<TextBlock Margin="5" Grid.Column="2" Text="{Binding Allocated, Mode=OneWay, StringFormat=\{0:N4\}}"/>
<TextBlock Margin="5" Grid.Column="3" Text="0.0000"/>
</Grid>
</ControlTemplate>
</Setter.Value>
</Setter>
<Style.Triggers>
<DataTrigger Binding="{Binding RelativeSource={RelativeSource Mode=TemplatedParent}, Path=IsSelected}" Value="True">
<Setter Property="Background" Value="Blue"/>
</DataTrigger>
</Style.Triggers>
</Style>
</ComboBox.ItemContainerStyle>
</ComboBox>
I am using DataTrigger to change the DataTemplate based on a enum.
<TreeView >
<TreeView.Resources>
<HierarchicalDataTemplate DataType="{x:Type model:Card}" ItemsSource="{Binding Reqs}">
<StackPanel VerticalAlignment="Center">
<TextBlock Text="{Binding Label}" FontWeight="Bold" Foreground="Gray" HorizontalAlignment="Stretch" VerticalAlignment="Center" FontSize="15"/>
<TextBlock Text="{Binding Reqs.Count}" FontWeight="UltraLight" Foreground="Gray" HorizontalAlignment="Stretch" VerticalAlignment="Center" FontSize="10"/>
</StackPanel>
</HierarchicalDataTemplate>
<DataTemplate DataType="{x:Type model:ReqCard}">
<ContentControl>
<ContentControl.Style>
<Style TargetType="{x:Type ContentControl}">
<Setter Property="ContentTemplate">
<Setter.Value>
<DataTemplate >
<Border Width="340" Background="LightBlue">
<Grid Name="PGrid" Margin="5">
<Grid.ColumnDefinitions>
<ColumnDefinition Width="*"/>
<ColumnDefinition Width="Auto"/>
</Grid.ColumnDefinitions>
<TextBlock Margin="2" Text="{Binding Label}" Foreground="Gray" FontSize="15" FontWeight="Normal"/>
<CheckBox Grid.Column="1" Margin="2" IsChecked="{Binding IsMeet}" Click="IsMeet_OnClick"/>
</Grid>
</Border>
</DataTemplate>
</Setter.Value>
</Setter>
<Style.Triggers>
<DataTrigger Binding="{Binding DemandType}" Value="{x:Static enums:DemandType.Condition}">
<Setter Property="ContentTemplate">
<Setter.Value>
<DataTemplate >
<Border Width="340" Background="LightBlue" >
<Grid Name="KGrid" Margin="5">
<Grid.ColumnDefinitions>
<ColumnDefinition Width="*"/>
<ColumnDefinition Width="Auto"/>
</Grid.ColumnDefinitions>
<TextBlock Margin="2" Text="{Binding Label}" Foreground="Gray" FontSize="15" FontWeight="Normal"/>
<TextBox Grid.Column="1" Width="20" Margin="2" Text="{Binding Condition}" Click="Condition_OnClick"/>
</Grid>
</Border>
</DataTemplate>
</Setter.Value>
</Setter>
</DataTrigger>
</Style.Triggers>
</Style>
</ContentControl.Style>
</ContentControl>
</DataTemplate>
</TreeView.Resources>
</TreeView>
It displays no data.