Datatrigger to change a ListView ItemsSource not firing - wpf

I am trying to change the ItemsSource Property of a ListView, depending on if a collection contains elements or is null.
The ListView currently looks like this:
<Control.Resources>
<Style TargetType="{x:Type GridViewColumnHeader}">
<Setter Property="HorizontalContentAlignment" Value="Left" />
</Style>
<Style TargetType="{x:Type ListViewItem}">
<EventSetter Event="MouseDoubleClick" Handler="GridFiles_MouseDoubleClick" />
</Style>
<CollectionViewSource x:Key="DirectoryFiles" Source="{Binding Path=CurrentDirectory.Files}"/>
<CollectionViewSource x:Key="DirectorySubDirs" Source="{Binding Path=CurrentDirectory.SubDirectories}"/>
<CompositeCollection x:Key="CombinedCollection">
<CollectionContainer Collection="{Binding Source={StaticResource DirectorySubDirs}}" />
<CollectionContainer Collection="{Binding Source={StaticResource DirectoryFiles}}" />
</CompositeCollection>
</Control.Resources>
<Grid>
<ListView x:Name="gridFiles" Grid.Row="2" Grid.Column="1" Margin="2"
SelectedItem="{Binding Path=SelectedFileGridItem}"
ItemsSource="{StaticResource CombinedCollection}">
<ListView.View>
<GridView>
<GridViewColumn Header="Name" DisplayMemberBinding="{Binding Name}" />
<GridViewColumn Header="Extension" DisplayMemberBinding="{Binding Extension}" />
<GridViewColumn Header="Checksum" DisplayMemberBinding="{Binding Checksum}" />
</GridView>
</ListView.View>
</ListView>
<Grid>
In my viewmodel there is an IEnumerable<FileNodeViewModel> property called SearchResults which is filled with results after performing a search, otherwise its null.
If I manually set the ItemsSource of the ListView property to {Binding SearchResults} the search results are displayed as intended, so I am sure that the data is correctly formated and available to the Listview.
In this thread I found a similar question and appended the xaml code:
<Control.Resources>
<Style TargetType="{x:Type GridViewColumnHeader}">
<Setter Property="HorizontalContentAlignment" Value="Left" />
</Style>
<Style TargetType="{x:Type ListViewItem}">
<EventSetter Event="MouseDoubleClick" Handler="GridFiles_MouseDoubleClick" />
</Style>
<Style TargetType="ListView">
<Style.Triggers>
<DataTrigger Binding="{Binding SearchResults}" Value="All">
<Setter Property="ItemsSource" Value="{Binding SearchResults}" />
</DataTrigger>
</Style.Triggers>
</Style>
<CollectionViewSource x:Key="DirectoryFiles" Source="{Binding Path=CurrentDirectory.Files}"/>
<CollectionViewSource x:Key="DirectorySubDirs" Source="{Binding Path=CurrentDirectory.SubDirectories}"/>
<CompositeCollection x:Key="CombinedCollection">
<CollectionContainer Collection="{Binding Source={StaticResource DirectorySubDirs}}" />
<CollectionContainer Collection="{Binding Source={StaticResource DirectoryFiles}}" />
</CompositeCollection>
</Control.Resources>
<Grid>
<ListView x:Name="gridFiles" Grid.Row="2" Grid.Column="1" Margin="2"
SelectedItem="{Binding Path=SelectedFileGridItem}"
ItemsSource="{StaticResource CombinedCollection}">
<ListView.View>
<GridView>
<GridViewColumn Header="Name" DisplayMemberBinding="{Binding Name}" />
<GridViewColumn Header="Extension" DisplayMemberBinding="{Binding Extension}" />
<GridViewColumn Header="Checksum" DisplayMemberBinding="{Binding Checksum}" />
</GridView>
</ListView.View>
</ListView>
<Grid>
However, nothing changes. No matter if the SearchResults property is null or not, the ListView always displays the combined collection. I also tried to create a boolan property to indicate if the second collection should be displayed or not, with the same result:
<Style TargetType="ListView">
<Style.Triggers>
<DataTrigger Binding="{Binding ShowSearchResults}" Value="True">
<Setter Property="ItemsSource" Value="{Binding SearchResults}" />
</DataTrigger>
</Style.Triggers>
</Style>
Does anybody know how what I should do to correctly change the ItemsSource property?

So you want to bind to SearchResults only if it's not null, or else you wan to bind to CombinedCollection? You could then use a DataTrigger with a value of {x:Null}:
<ListView x:Name="gridFiles" Grid.Row="2" Grid.Column="1" Margin="2"
SelectedItem="{Binding Path=SelectedFileGridItem}">
<ListView.Style>
<Style TargetType="ListView">
<Setter Property="ItemsSource" Value="{Binding SearchResults}" />
<Style.Triggers>
<DataTrigger Binding="{Binding SearchResults}" Value="{x:Null}">
<Setter Property="ItemsSource" Value="{StaticResource CombinedCollection}" />
</DataTrigger>
</Style.Triggers>
</Style>
</ListView.Style>
<ListView.View>
<GridView>
<GridViewColumn Header="Name" DisplayMemberBinding="{Binding Name}" />
<GridViewColumn Header="Extension" DisplayMemberBinding="{Binding Extension}" />
<GridViewColumn Header="Checksum" DisplayMemberBinding="{Binding Checksum}" />
</GridView>
</ListView.View>
</ListView>
Make sure that you only set the ItemsSource property in the Style Setter.

So, after some trial and error I found the issue. Everything was pretty much set up correctly, but I was not aware that styles can only append properties to an element, not replace existing ones.
To test this, I created a new ListView:
<ListView Grid.Row="2" Grid.Column="1" Margin="2"
ItemsSource="{StaticResource CombinedCollection}"
Foreground="Blue">
<ListView.Style>
<Style TargetType="ListView">
<Style.Triggers>
<DataTrigger Binding="{Binding ShowSearchResults}" Value="True">
<Setter Property="Foreground" Value="Red" />
</DataTrigger>
</Style.Triggers>
</Style>
</ListView.Style>
<ListView.View>
<GridView>
<GridViewColumn Header="Name" DisplayMemberBinding="{Binding Name}" />
</GridView>
</ListView.View>
</ListView>
The data was displayed correctly, with blue foreground color. If I changed ShowSearchResults to True, nothing happened, the color still was blue. So I removed the foreground property:
<ListView Grid.Row="2" Grid.Column="1" Margin="2"
ItemsSource="{StaticResource CombinedCollection}">
I retried the test, and suddenly the ListView items would turn red. Knowing this, I removed the explicit data source from the ListView definition and used two data triggers instead.
For reference, here is the working markup. Thanks a lot to mm8 for providing some great input about the formatting!
<ListView Grid.Row="2" Grid.Column="1" x:Name="gridFiles" Margin="2"
SelectedItem="{Binding Path=SelectedFileGridItem}">
<ListView.Style>
<Style TargetType="ListView">
<Setter Property="ItemsSource" Value="{StaticResource CombinedCollection}" />
<Style.Triggers>
<DataTrigger Binding="{Binding ShowSearchResults}" Value="True">
<Setter Property="ItemsSource" Value="{Binding SearchResults}" />
</DataTrigger>
</Style.Triggers>
</Style>
</ListView.Style>
<ListView.View>
<GridView>
<GridViewColumn Header="Name" DisplayMemberBinding="{Binding Name}" />
<GridViewColumn Header="Extension" DisplayMemberBinding="{Binding Extension}" />
<GridViewColumn Header="Checksum" DisplayMemberBinding="{Binding Checksum}" />
</GridView>
</ListView.View>
</ListView>

Related

WPF Listview - Change format of a row depending on content

I have a WPF listview with 3 columns. Name, cost, and sell. Cost and Sell are editable textboxes formatted as currency.
2 issues:
- I might have specific items listed where the cost and sell need to be formatted as a percentage instead of currency. So if Name="x" then cost and sell should have percentage stringformt.
Some items only require a sell option. so if Name="y", do not display textbox in Cost column.
How would I implement this in WPF? I was looking into DataTriggers but couldn't figure out how to implement correctly.
<ListView x:Name="Pricing_LV" HorizontalAlignment="Left" Height="335" Margin="10,41,0,0" VerticalAlignment="Top" Width="350" TabIndex="22">
<ListView.View>
<GridView>
<GridViewColumn Header="Surcharge" Width="185" DisplayMemberBinding="{Binding Path=Name}"/>
<GridViewColumn Header="Cost" Width="70">
<GridViewColumn.CellTemplate>
<DataTemplate>
<TextBox Width="55" Text="{Binding Path=Cost, StringFormat='c'}" />
</DataTemplate>
</GridViewColumn.CellTemplate>
</GridViewColumn>
<GridViewColumn Header="Sell" Width="70">
<GridViewColumn.CellTemplate>
<DataTemplate>
<TextBox Width="55" Text="{Binding Path=Sell, StringFormat='c'}" />
</DataTemplate>
</GridViewColumn.CellTemplate>
</GridViewColumn>
</GridView>
</ListView.View>
</ListView>
You may have tried putting your DataTriggers direct inside TextBox.Triggers but then getting the following error
Error: Triggers collection members must be of type EventTrigger
See also this question
The solution is to simply wrap your Triggers inside a Style
<ListView x:Name="Pricing_LV" HorizontalAlignment="Left" Height="335" Margin="10,41,0,0" VerticalAlignment="Top" Width="350" TabIndex="22">
<ListView.View>
<GridView>
<GridViewColumn Header="Surcharge" Width="185" DisplayMemberBinding="{Binding Path=Name}"/>
<GridViewColumn Header="Cost" Width="70">
<GridViewColumn.CellTemplate>
<DataTemplate>
<TextBox Width="55">
<TextBox.Style>
<Style TargetType="TextBox">
<Setter Property="Text" Value="{Binding Path=Cost, StringFormat='c'}"></Setter>
<Style.Triggers>
<DataTrigger Binding="{Binding Name}" Value="x">
<Setter Property="Text" Value="{Binding Path=Cost, StringFormat=#.00\\%}"></Setter>
</DataTrigger>
<DataTrigger Binding="{Binding Name}" Value="y">
<Setter Property="Text" Value=""></Setter>
<Setter Property="Visibility" Value="Hidden"></Setter>
</DataTrigger>
</Style.Triggers>
</Style>
</TextBox.Style>
</TextBox>
</DataTemplate>
</GridViewColumn.CellTemplate>
</GridViewColumn>
<GridViewColumn Header="Sell" Width="70">
<GridViewColumn.CellTemplate>
<DataTemplate>
<TextBox Width="55">
<TextBox.Style>
<Style TargetType="TextBox">
<Setter Property="Text" Value="{Binding Path=Sell, StringFormat='c'}"></Setter>
<Style.Triggers>
<DataTrigger Binding="{Binding Name}" Value="x">
<Setter Property="Text" Value="{Binding Path=Sell, StringFormat=#.00\\%}"></Setter>
</DataTrigger>
</Style.Triggers>
</Style>
</TextBox.Style>
</TextBox>
</DataTemplate>
</GridViewColumn.CellTemplate>
</GridViewColumn>
</GridView>
</ListView.View>
</ListView>
Note: I don't use p for percentage format because I think this won't suit in your case. See also here

different alignments for different columns and items

Got a WPF app.
it uses a listview control.
I have 3 columns.
I want the 1st column header to be left aligned
I want the other 2 to be centred aligned
I want one the items to be right aligned (just so I know how to do it).
With my markup everything is Left aligned so what am I doing wrong?
This is my markup:
<ResourceDictionary>
<Style TargetType="GridViewColumnHeader">
<Setter Property="HorizontalContentAlignment" Value="Left"/>
</Style>
<DataTemplate x:Key="value1Template">
<TextBlock TextAlignment="Right" Text="{Binding Path=Description}"/>
</DataTemplate>
<DataTemplate x:Key="value2Template">
<TextBlock TextAlignment="Center" Text="{Binding Path=IncludesVATCaption}"/>
</DataTemplate>
<DataTemplate x:Key="value3Template">
<TextBlock TextAlignment="Center" Text="{Binding Path=Cost}"/>
</DataTemplate>
</ResourceDictionary>
<ListView x:Name="lvWorkItems">
<ListView.View>
<GridView>
<GridViewColumn Header="Description" CellTemplate="{StaticResource value1Template}" Width="190">
</GridViewColumn>
<GridViewColumn Header="IsVat" Width="50" CellTemplate="{StaticResource value2Template}"/>
<GridViewColumn Header="Cost" Width="65" CellTemplate="{StaticResource value3Template}" />
</GridView>
</ListView.View>
</ListView>
You saw that behaviour cause you set style for GridViewColumnHeader and it is applied for all columns. You can change this Style by adding DataTrigger for Center alignment columns.
<ResourceDictionary>
<Style TargetType="GridViewColumnHeader">
<Setter Property="HorizontalContentAlignment" Value="Left"/>
<Style.Triggers>
<DataTrigger Binding="{Binding RelativeSource={RelativeSource Mode=Self}, Path=Column.HeaderStringFormat}" Value="Center">
<Setter Property="HorizontalContentAlignment" Value="Center"/>
</DataTrigger>
</Style.Triggers>
</Style>
</ResourceDictionary>
And you should set HeaderStringFormat="Center" property for them. I know it's a little tricky.
<GridViewColumn Header="IsVat" Width="50" CellTemplate="{StaticResource value2Template}" HeaderStringFormat="Center"/>
<GridViewColumn Header="Cost" Width="65" CellTemplate="{StaticResource value3Template}" HeaderStringFormat="Center" />
You should set the HorizontalContentAligment property to Stretch so that the column/cell inside a row can take the required space. This can be achieved by setting the property through ItemContainerStyle.
<ListView.ItemContainerStyle>
<Style TargetType="ListViewItem">
<Setter Property="HorizontalContentAlignment" Value="Stretch" />
</Style>
</ListView.ItemContainerStyle>

Applying a ControlTemplate Trigger to a CheckBox in a ListView

I am trying to override the appearance of a CheckBox in a ListView. The CheckBox is bound to a nullable bool (bool?).
The style does not seem to be applied at all. What am I doing wrong here?
<ListView Grid.Row="1" Margin="10" VerticalAlignment="Top" ItemsSource="{Binding Prerequisites}" d:DataContext="{d:DesignInstance Type=viewModels:MockPrerequisiteViewModel, IsDesignTimeCreatable=True}">
<ListView.Resources>
<Style x:Key="StyleCustomCheckBox" TargetType="{x:Type CheckBox}">
<Setter Property="FontSize" Value="14" />
<Setter Property="Margin" Value="10,0,0,0" />
<Setter Property="VerticalAlignment" Value="Center" />
<Setter Property="Template">
<Setter.Value>
<ControlTemplate TargetType="{x:Type CheckBox}">
<StackPanel Orientation="Horizontal">
<Path x:Name="MyIcon" Width="18" Height="18" Stretch="Fill" Fill="#FF00FF00"
Data="M-150.204,626.126C-152.317,626.126 -154.429,626.126 -156.541,626.126 -167.642,633.42 -180.629,646.047 -189.668,657.238 -190.916,658.782 -192.945,662.362 -193.701,662.422 -194.041,662.448 -198.024,659.719 -198.614,659.297 -202.818,656.279 -205.779,653.709 -209.257,650.899 -211.248,652.172 -212.879,653.805 -214.153,655.797 -206.627,665.074 -200.283,675.534 -193.124,685.18 -181.491,665.11 -168.473,644.683 -152.796,629.006 -151.735,627.946 -149.817,626.933 -150.204,626.126z"/>
<ContentPresenter VerticalAlignment="Center" Margin="10,0,0,0" />
</StackPanel>
<ControlTemplate.Triggers>
<Trigger Property="IsChecked" Value="False">
<Setter TargetName="MyIcon" Property="Data" Value="F1M-1774.05,-6263.81L-1787.51,-6277.27 -1773.22,-6291.56C-1769.23,-6295.55 -1769.24,-6302.03 -1773.21,-6306.01 -1777.19,-6309.98 -1783.67,-6309.99 -1787.66,-6305.99L-1801.95,-6291.71 -1816.79,-6306.55C-1820.79,-6310.55 -1827.26,-6310.54 -1831.24,-6306.55 -1835.22,-6302.58 -1835.23,-6296.11 -1831.24,-6292.12L-1816.39,-6277.27 -1830.4,-6263.25C-1834.4,-6259.26 -1834.38,-6252.8 -1830.4,-6248.82 -1826.42,-6244.84 -1819.96,-6244.82 -1815.96,-6248.82L-1801.95,-6262.83 -1788.49,-6249.37C-1784.5,-6245.38 -1778.03,-6245.39 -1774.06,-6249.37 -1770.07,-6253.35 -1770.06,-6259.82 -1774.05,-6263.81" />
<Setter TargetName="MyIcon" Property="Fill" Value="#FFFF0000" />
</Trigger>
</ControlTemplate.Triggers>
</ControlTemplate>
</Setter.Value>
</Setter>
</Style>
</ListView.Resources>
<ListView.View>
<GridView>
<GridViewColumn Width="Auto" Header="Machine" DisplayMemberBinding="{Binding Context.Hostname}"/>
<GridViewColumn Width="Auto" Header="Prerequisite" DisplayMemberBinding="{Binding Label}"/>
<GridViewColumn Width="Auto" Header="Status" DisplayMemberBinding="{Binding Status}"/>
<GridViewColumn Width="Auto" Header="Result" DisplayMemberBinding="{Binding Result}">
<GridViewColumn.CellTemplate>
<DataTemplate>
<CheckBox IsChecked="{Binding Result}" IsThreeState="True" IsEnabled="False" Style="{StaticResource StyleCustomCheckBox}"/>
</DataTemplate>
</GridViewColumn.CellTemplate>
</GridViewColumn>
</GridView>
</ListView.View>
</ListView>
You need to remove the DisplayMemberBinding="{Binding Result}" from the GridViewColumn.
As you are binding the checkbox to result already, and when you set DisplayMemberBinding property, GridView automatically uses a string representation.
<GridViewColumn Width="Auto" Header="Result">
<GridViewColumn.CellTemplate>
<DataTemplate>
<CheckBox IsChecked="{Binding Result}" IsThreeState="True" IsEnabled="False" Style="{StaticResource StyleCustomCheckBox}"/>
</DataTemplate>
</GridViewColumn.CellTemplate>
</GridViewColumn>

Cannot select ListView item when clicking on some parts

I create a ListView having a GridView like below:
<ListView ItemsSource="{Binding Orders}" SelectedItem="{Binding SelectedOrder}">
<ListView.Resources>
<Style TargetType="{x:Type ListViewItem}" BasedOn="{StaticResource {x:Type ListViewItem}}">
<Setter Property="Template">
<Setter.Value>
<ControlTemplate TargetType="{x:Type ListViewItem}">
<Border BorderBrush="SteelBlue" BorderThickness="1" x:Name="Border" Padding="10">
<GridViewRowPresenter />
</Border>
<ControlTemplate.Triggers>
<Trigger Property="IsSelected" Value="True">
<Setter TargetName="Border" Property="Background" Value="LightBlue"></Setter>
</Trigger>
</ControlTemplate.Triggers>
</ControlTemplate>
</Setter.Value>
</Setter>
</Style>
</ListView.Resources>
<ListView.View>
<GridView d:DataContext="{d:DesignInstance entities:ExecReport}">
<GridViewColumn Header="Symbol" DisplayMemberBinding="{Binding Instrument.Symbol}" />
<GridViewColumn Header="Side" DisplayMemberBinding="{Binding Side}" />
<GridViewColumn Header="Price" DisplayMemberBinding="{Binding Price}" />
<GridViewColumn Header="Total Size" DisplayMemberBinding="{Binding Qty}" />
<GridViewColumn Header="Open Size" DisplayMemberBinding="{Binding OpenQty}" />
<GridViewColumn Header="Status" DisplayMemberBinding="{Binding State}" />
<GridViewColumn Header="Validity" DisplayMemberBinding="{Binding Validity}" />
<GridViewColumn Header="Order Seq" DisplayMemberBinding="{Binding OrderSeq}" />
</GridView>
</ListView.View>
</ListView>
I can select items by clicking on them in most places. But when I click on some places they do not get selected. The backing field is not set either. This problem does not happen if I remove the ListViewItem Style.
The below image points out the sort of place where an item does not get selected.
How do I fix this?
Set the Background property of your Border(named "Border") to Transparent. Since controls without background will be considered as hollow and hit test may not be possible. More information on HitTesting.

Unable to apply trigger on TextBlock within ListView/GridView

Unable apply the Style on GridViewDataTemplate field.
I just need to change the Forground of the TextBlock based on its contect.
If pull this textblock outside the list view it works perfectly. But inside the ListView it's not working.
Please let me how to fix this issue.
<ListView Margin="5,15,0,5"
ItemsSource="{Binding}" Background="Transparent" Foreground="{DynamicResource {x:Static SystemColors.WindowBrushKey}}" BorderThickness="0"
ScrollViewer.HorizontalScrollBarVisibility="Disabled">
<ListView.Resources>
<Style TargetType="{x:Type GridViewColumnHeader}">
<Setter Property="Visibility" Value="Collapsed"/>
</Style>
<DataTemplate x:Key="DataColumnStyle" DataType="GridViewColumn.CellTemplate">
<StackPanel>
<TextBlock Text="{Binding}" Foreground="White" FontSize="16" TextWrapping="Wrap"/>
</StackPanel>
</DataTemplate>
<DataTemplate x:Key="StatusColumnStyle" DataType="GridViewColumn.CellTemplate">
<StackPanel>
<TextBlock Text="{Binding}" FontSize="16" TextWrapping="Wrap">
<TextBlock.Style>
<Style TargetType="TextBlock">
<Style.Triggers>
<Trigger Property="Text" Value="In Progress">
<Setter Property="Foreground" Value="Black"/>
</Trigger>
<Trigger Property="Text" Value="Complete">
<Setter Property="Foreground" Value="Green"/>
</Trigger>
<Trigger Property="Text" Value="Failed">
<Setter Property="Foreground" Value="Red"/>
</Trigger>
</Style.Triggers>
</Style>
</TextBlock.Style>
</TextBlock>
</StackPanel>
</DataTemplate>
</ListView.Resources>
<ListView.View>
<GridView>
<GridViewColumn DisplayMemberBinding="{Binding Activity}" CellTemplate="{StaticResource DataColumnStyle}" x:Name="ActivityColumn"/>
<GridViewColumn DisplayMemberBinding="{Binding Status}" CellTemplate="{StaticResource StatusColumnStyle}" Width="100" x:Name="StatusColumn"/>
</GridView>
</ListView.View>
</ListView>
I believe if you set the DisplayMemberBinding property, the CellTemplate property will be ignored; DisplayMemberBinding is fine if you're only looking to specify text, rather than a template.
So you'd need to change your code to something along these lines:
Altering the Text binding within the `DataTemplate':
Text="{Binding}" => Text="{Binding Activity}"
Removing the DisplayMemberBinding from the ListView:
<GridViewColumn DisplayMemberBinding="{Binding Activity}" CellTemplate="{StaticResource DataColumnStyle}" x:Name="ActivityColumn"/> => <GridViewColumn CellTemplate="{StaticResource DataColumnStyle}" x:Name="ActivityColumn"/>
Roughly:
<!-- Your Code -->
<GridView>
<GridViewColumn CellTemplate="{StaticResource DataColumnStyle}" x:Name="ActivityColumn"/>
<GridViewColumn CellTemplate="{StaticResource StatusColumnStyle}" Width="100" x:Name="StatusColumn"/>
</GridView>
<!-- Your Code -->
If you have a look at the MSDN info for DisplayMemberInfo (also CellTemplate / CellTemplateSelector), you can see that there is an order of precedence:
The following properties are all used to define the content and style
of a column cell, and are listed here in their order of precedence,
from highest to lowest:
DisplayMemberBinding
CellTemplate
CellTemplateSelector

Resources