Change ListView.ItemTemplate on subelement change - wpf

let's say we have simple data class:
public class Ex {
public string Prop1 {...} // notify property
public string Prop2 {...} // notify property
}
and an ObservableCollection of objects of this class. I want to have this collection displayed in a ListView with seperated DataTemplated which is distinguished by Ex.Prop2 (if it is null or empty then template01 is used, otherwise template02). This property can be changed in runtime so simple "trick" with ListView.ItemTemplateSelector does not work :(
How to achieve this functionality? Is it possible to achieve it any other way than listening to NotifyPropertyChanged on each object of the collection and than changing manually the template?
Thanks for your help.
Below piece of code which I already have:
<ListView x:Name="lstTerms"
ItemsSource="{Binding Game.Words}"
HorizontalContentAlignment="Stretch"
Grid.IsSharedSizeScope="True">
<ListView.ItemContainerStyle>
<Style>
<Setter Property="Control.Padding" Value="0" />
</Style>
</ListView.ItemContainerStyle>
<!-- checks if element is null or its Prop2 is null or empty. If so, uses NullTemplate -->
<ListView.ItemTemplateSelector>
<local:MySelectTemplate
NormalTemplate="{StaticResource NormalItemTemplate}"
NullTemplate="{StaticResource NullItemTemplate}" />
</ListView.ItemTemplateSelector>
</ListView>

Instead of using a TemplateSelector, you can have a single DataTemplate containing two Grids, which switch visibility dependent on the property values.
Here is an example:
<ListView.ItemTemplate>
<DataTemplate>
<Grid>
<Grid Background="LightBlue" Name="normalGrid">
<Grid.Style>
<Style>
<Style.Triggers>
<DataTrigger Binding="{Binding Path=Prop1}" Value="{x:Null}">
<Setter Property="Grid.Visibility" Value="Hidden"></Setter>
</DataTrigger>
</Style.Triggers>
</Style>
</Grid.Style>
<TextBlock Text="{Binding Prop1}"></TextBlock>
</Grid>
<Grid Background="Green" Name="nullGrid">
<Grid.Style>
<Style>
<Style.Triggers>
<DataTrigger Binding="{Binding ElementName=normalGrid, Path=Visibility}" Value="Visible">
<Setter Property="Grid.Visibility" Value="Hidden"></Setter>
</DataTrigger>
</Style.Triggers>
</Style>
</Grid.Style>
<TextBlock Text="{Binding Prop2}"></TextBlock>
</Grid>
</Grid>
</DataTemplate>
</ListView.ItemTemplate>
Obviously you could replace the TextBlock elements with UserControls representing your two DataTemplates.
If you want, you can also remove the need for the bulky Styles by binding Grid.Visibility to a property (named, for example, IsVisible) on your ViewModel and using a VisibilityConverter.

I usually just use a ContentControl which changes its ContentTemplate based on a DataTrigger. DataTriggers respond to the value getting changed, while DataTemplateSelectors do not
<Style x:Key="SomeStyleKey" TargetType="{x:Type ContentControl}">
<Setter Property="ContentTemplate" Value="{StaticResource DefaultTemplate}" />
<Style.Triggers>
<DataTrigger Binding="{Binding Prop2}" Value="{x:Null}">
<Setter Property="ContentTemplate" Value="{StaticResource NullTemplate}" />
</DataTrigger>
<DataTrigger Binding="{Binding Prop2}" Value="">
<Setter Property="ContentTemplate" Value="{StaticResource NullTemplate}" />
</DataTrigger>
</Style.Triggers>
</Style>
...
<ListView.ItemTemplate>
<DataTemplate>
<ContentControl Style="{StaticResource SomeStyleKey}" />
</DataTemplate>
</ListView.ItemTemplate>
You could also use a Converter that returns String.IsNullOrEmpty(value) if you wanted a single DataTrigger

Related

WPF override property specified on element from style trigger

I have two TextBlock Run elements with different colors (one is explicitly set on the element). I want them both to change color to red when the value Foo is zero, using the same style. Is this possible somehow? I would rather not duplicate the Style. This is what I want to work:
<Style x:Key="ForegroundStyleTrigger" TargetType="Run">
<Style.Triggers>
<DataTrigger Binding="{Binding Foo}" Value="0">
<Setter Property="Foreground" Value="{StaticResource RedBrush}"></Setter>
</DataTrigger>
</Style.Triggers>
</Style>
<TextBlock>
<Run Text="{Binding Foo, Mode=OneWay}"
Style="{StaticResource ForegroundStyleTrigger}"/>
<Run Text="{Binding Bar, Mode=OneWay}"
Foreground="Blue"
Style="{StaticResource ForegroundStyleTrigger}"/>
</TextBlock>
But since the locally defined color (the one defined on the element) takes precedence over style triggers, nothing happens and the text stays blue for that text run.
Question: Can I override a TextBlocks runs color from a style resource?
If not, how can achieve the expected result without duplicating the style resource?
Can I override a TextBlocks runs color from a style resource?
No, you cannot override a local value using a style setter.
If not, how can achieve the expected result without duplicating the style resource?
If you want the Foreground of the second Run element to be Blue by default and Red only if the Foo source property returns "0" you could create another style and base this one on your existing one:
<Style x:Key="ForegroundStyleTrigger" TargetType="Run">
<Style.Triggers>
<DataTrigger Binding="{Binding Foo}" Value="0">
<Setter Property="Foreground" Value="Red"></Setter>
</DataTrigger>
</Style.Triggers>
</Style>
<Style x:Key="BlueByDefault" BasedOn="{StaticResource ForegroundStyleTrigger}" TargetType="Run">
<Setter Property="Foreground" Value="Blue" />
</Style>
<TextBlock>
<Run Text="{Binding Foo, Mode=OneWay}" Style="{StaticResource ForegroundStyleTrigger}"/>
<Run Text="{Binding Bar, Mode=OneWay}" Style="{StaticResource BlueByDefault}"/>
</TextBlock>
But you cannot set the Foreground property to a local value if you want your style setters to apply.
You would use Style inheritance to achieve what you're asking. The trick then is that all properties must be in the Style as local explicitly set values override implicitly set Style values:
<Style x:Key="ForegroundStyleTrigger" TargetType="Run">
<Style.Triggers>
<DataTrigger Binding="{Binding Foo}" Value="0">
<Setter Property="Foreground" Value="{StaticResource RedBrush}"></Setter>
</DataTrigger>
</Style.Triggers>
</Style>
<Style x:Key="BlueForegroundStyleTrigger" BasedOn="{StaticResource ForegroundStyleTrigger}" TargetType="Run">
<Setter Property="Foreground" Value="Blue"/>
</Style>
<TextBlock>
<Run Text="{Binding Foo, Mode=OneWay}"
Style="{StaticResource ForegroundStyleTrigger}"/>
<Run Text="{Binding Bar, Mode=OneWay}"
Style="{StaticResource BlueForegroundStyleTrigger}"/>
</TextBlock>

How to apply style to Listbox Item based on Property value in WPF

<ListBox ItemsSource="{Binding Customers}">
<ListBox.ItemTemplate>
<DataTemplate>
<Grid Margin="1" x:Name="BackgroundGrid">
<TextBlock Text="{Binding CustomerName}" />
<TextBlock Text="{Binding CustomerId}" />
</Grid>
<DataTemplate.Triggers>
<DataTrigger Binding="{Binding CustomerId}" Value="9-3453"><Setter TargetName="BackgroundGrid" Property="Background" Value="Red"/>
</DataTrigger>
</DataTemplate.Triggers>
</DataTemplate>
</ListBox.ItemTemplate>
</ListBox>
In the above code, I need to provide DataTrigger's value based on property value instead of hard code the value.
Thanks in advance,
Dinesh
Supposed your DataTrigger's value based on property CustomerNumber. Then you add another property for binding, like this:
public bool CustomerIdMatchs
{
get{
return CustomerNumber==CustomerId;
}
}
Then you bind CustomerIdMatchs instead of CustomerId, like this:
<DataTrigger Binding="{Binding CustomerIdMatchs}" Value="true">
<Setter TargetName="BackgroundGrid" Property="Background" Value="Red"/>
</DataTrigger>

Dynamically set image of each node

So I have 3 levels of nodes in my TreeView:
A single Root Node (should display Image1.png)
Some second level nodes (should display Image2.png)
Each second level node has some third-level nodes (should display Image3.png)
I'm trying to use a DataTemplate to dynamically assign the display image to each node, depending upon its level. Since level is not as easily available in WPF as it is in WinForms, I simply resorted to using Tag property of TreeViewItems to store their level. Then I wrote this following Style for assigning display images:
<Style TargetType="{x:Type TreeViewItem}">
<Setter Property="HeaderTemplate">
<Setter.Value>
<DataTemplate>
<StackPanel Orientation="Horizontal">
<Image Name="img" Width="20" Height="20" Stretch="Fill">
<Image.Style>
<Style TargetType="{x:Type Image}">
<Style.Triggers>
<DataTrigger Binding="{Binding Tag}" Value="0">
<Setter Property="Source" Value="Icons\Image1.png"/>
</DataTrigger>
<DataTrigger Binding="{Binding Tag}" Value="1">
<Setter Property="Source" Value="Icons\Image2.png"/>
</DataTrigger>
</Style.Triggers>
</Style>
</Image.Style>
</Image>
<TextBlock VerticalAlignment="Center" Text="{Binding}" Margin="5,0" />
</StackPanel>
</DataTemplate>
</Setter.Value>
</Setter>
</Style>
Yes, you guessed it; it doesn't work. Can someone please identify where the problem lies? Or am I doing it just the wrong way?
Right now, your {Binding Tag} will try to find the Tag property of the TreeViewItem's DataContext, not the DependencyProperty. And since, i'm guessing, there is no Tag property in the DataContext, it won't work. If you look at your output Windows in VS, you should see binding errors all over the place.
What you need to do is add the relative source to your binding so it looks at the TreeViewItem instead of the DataContext. Here is an example :
<DataTrigger Binding="{Binding Path=Tag, RelativeSource={RelativeSource AncestorType={x:Type TreeViewItem}}}" Value="0">

Datatrigger on contentpresenter.content not working

I am trying to switch the content of a contentpresenter based on a datatrigger.I want to display a usercontrol in the contentpresenter.content, if i have a value set or else i need to display an error message.But the binding on my datatrigger fails stating that the property is not found.I cant get the datacontext to inherit for the datatrigger checking.I can make it work by using the commented out code.But i am confused why it doesn't work the normal way.
<ContentPresenter.Style>
<Style TargetType="{x:Type ContentPresenter}">
<Setter Property="Content" Value="{Binding UC}"/>
<Style.Triggers>
<!--<DataTrigger Binding="{Binding DataContext.HasValue,RelativeSource={RelativeSource AncestorType={x:Type ContentPresenter}}}" Value="false">
<Setter Property="Content" Value="No preview"/>
</DataTrigger>-->
<DataTrigger Binding="{Binding HasValue}" Value="false">
<Setter Property="Content" Value="No value"/>
</DataTrigger>
</Style.Triggers>
</Style>
</ContentPresenter.Style>
</ContentPresenter>
If you want to use triggers to display UserControl, you should use ContentControl not ContentPresenter.
I prefer to use ContentPresenter for CustomControls and When I am using the UserControl for views of Custom Data Types in my system and Allow to give dynamic behavior.
Example: To switch templates for ContentPresenter you need to set ContentTemplateSelector like this
<ContentPresenter Content="{Binding MyContent}"
ContentTemplate="{Binding MyContentTemplate}"
ContentTemplateSelector="{Binding MyContentTemplateSelector}"/>
MyContent, MyContentTemplate & MyContentTemplateSelector are Dependency Properties and can be binded wherever you are using its instance.
READ :
Usage of ContentPresenter
What is the difference between ContentControl and ContentPresenter
The binding mentioned in the question won't work as
ContentPresenter’s DataContext is automatically set to the value of
its Content property, while ContentControl’s DataContext is not.
Bindings are resolved relatively to the value of the DataContext property. If you declare a binding on the ContentPresenter, the moment its content is set, the binding would be re-evaluated.
ContentControl.Content Property can be changed on any trigger based on your requirement. If you want to use it to change on PropertyChanged Event of a property of ViewModel, DataTrigger can be used by binding it with a DataTemplate with UserControl instance in it or using static resource of that UserControl.
<ContentControl>
<ContentControl.Style>
<Style TargetType="{x:Type ContentControl}">
<Setter Value="{StaticResource UnSelectedDataTemplate}" Property="ContentTemplate" />
<Style.Triggers>
<DataTrigger Binding="{Binding Path=IsSelected}" Value="True">
<Setter Value="{StaticResource SelectedDataTemplate}" Property="ContentTemplate" />
</DataTrigger>
</Style.Triggers>
</Style>
</ContentContro.Style>
</ContentControl>
READ How to use triggers for content template, more details here
Difference in DataTemplate and StaticResource scope is DataTemplate creates a new instance of the template every time its applied.
Whereas, StaticResource is using the same instance of UserControl again (Static Instance).
You can also use EventTriggers to change content base don Control Events like MouseOver etc.
Alternate approach
Very similar to the above with slight difference. Defining as a data template in resources. Triggering for the content change is essentially identical.
...in <x.Resources /> tag:
<DataTemplate x:Key="DesignerTemplate" DataType="{x:Type vm:SolutionViewModel}">
<vw:SolutionDesignerView />
</DataTemplate>
<DataTemplate DataType="{x:Type vm:SolutionViewModel}">
<ContentControl Content="{Binding }">
<ContentControl.Style>
<Style TargetType="{x:Type ContentControl}">
<Style.Triggers>
<DataTrigger Binding="{Binding IsLoaded}" Value="True">
<Setter Property="ContentTemplate" Value="{StaticResource DesignerTemplate}" />
</DataTrigger>
</Style.Triggers>
</Style>
</ContentControl.Style>
</ContentControl>
</DataTemplate>
...then:
<ContentControl Content="{Binding Solution}" />
I usually use trigger Like this...
<UserControl>
<UserControl.Resources>
<DataTemplate x:Key="normalTemplate" >
<!-Fav UserControl->
</DataTemplate >
<DataTemplate x:Key="overWriteTempalte">
<!-Fav UserControl-> </DataTemplate>
</UserControl.Resources>
<ContentPresenter x:Name="ContentField"
Content="{Binding}"
ContentTemplate="{StaticResource ResourceKey=normalTemplate}" />
<UserControl.Triggers>
<DataTrigger Binding="{Binding Path=MyProperty}" Value="True">
<Setter TargetName="ContentField" Property="ContentTemplate" Value="{StaticResource ResourceKey=overWriteTempalte}" />
</DataTrigger>
</UserControl.Triggers>
</UserControl>
If Bindings are a problem Use Snoop to Detect binding errors

Binding to the Items properties of an ItemsControl

I'm looking to a way to bind a Button Text or IsEnabled property for example to the IsChecked property in an ItemsControl
Here is my simplified source code :
<StackPanel>
<ItemsControl ItemsSource="{Binding Tasks}">
<ItemsControl.ItemTemplate>
<DataTemplate>
<StackPanel Orientation="Horizontal">
<TextBlock Margin="10,0,10,0" Text="{Binding Name}"/>
<CheckBox IsChecked="{Binding InProgress}"/>
<CheckBox IsChecked="{Binding Done}"/>
</StackPanel>
</DataTemplate>
</ItemsControl.ItemTemplate>
</ItemsControl>
<Button/>
</StackPanel>
I would like (for example) if all the "Done" CheckBoxes are Checked, to set Button Text to some value or to enable it. I thought doing this with Data Binding in Xaml using DataTriggers but I don't know how to do it.
Ca anyone give me a full xaml solution ?
New Answer
Sorry, I misunderstood the question. I would expose another property from your DataContext that simply returns true/false if all the items in the collection are checked or not, and base your Button's Text/IsEnabled off that property using a DataTrigger
<Style TargetType="{x:Type Button}">
<Setter Property="IsEnabled" Value="False" />
<Style.Triggers>
<DataTrigger Binding="{Binding IsAllChecked}" Value="True">
<Setter Property="IsEnabled" Value="True" />
</DataTrigger>
</Style.Triggers>
</Style>
Old Answer
DataTriggers simply take a binding, and check if the result is equal to some value.
<Style TargetType="{x:Type TextBox}">
<Setter Property="IsEnabled" Value="False" />
<Style.Triggers>
<DataTrigger Binding="{Binding Done}" Value="True">
<Setter Property="IsEnabled" Value="True" />
</DataTrigger>
</Style.Triggers>
</Style>
If you need to test more than one condition, you need a MultiDataTrigger
<Style TargetType="{x:Type TextBox}">
<Setter Property="IsEnabled" Value="False" />
<Style.Triggers>
<MultiDataTrigger>
<MultiDataTrigger.Conditions>
<Condition Binding="{Binding Path=InProgress}" Value="True" />
<Condition Binding="{Binding Path=Done}" Value="True" />
</MultiDataTrigger.Conditions>
<Setter Property="IsEnabled" Value="True" />
</MultiDataTrigger>
</Style.Triggers>
</Style>
Note that the default value of setter is part of the style. This is important because if you set the default value on the <TextBox> then it has a higher priority than triggered values, so triggered values are unable to change the current value.
You can change this line...
<Button/>
To this
<Button Content={Binding ButtonContent}/>
And set the 'ButtonContent' property in your ViewModel whenever your 'Done' check box gets checked.
Usually, the best way to do that is to synthesize the state you want to apply to your button into a property of your ViewModel.
The WPFy way in that case is for your ViewModel to expose a command that implements the action of your button.
The command in turns can react to changes in your data to signal if it is available or not, and thus will automaticaally disable/enable your button. An added bonus is that you can easily implement a menu for example, that will be properly enabled/disabled at no cost once you have your command.
It's been a long time since I had any chance to touch WPF, so I do not have any really useful links at hand, but if I were you I would learn about commands in WPF, they are a very important part of the MVVM architecture.

Resources