DataTrigger in Style in HierarchicalDataTemplate not working -- TreeView - wpf

I have a DataTrigger to set a TextBox's Background based on a bound property.
Here's a streamlined version of the xaml:
<TreeView >
<TreeViewItem Header="Things" >
<TreeViewItem.Resources>
<Style TargetType="{x:Type TextBlock}">
<Style.Triggers>
<DataTrigger Binding="{Binding IsDirty}" Value="True">
<Setter Property="Background" Value="LightGray" />
</DataTrigger>
</Style.Triggers>
</Style>
<HierarchicalDataTemplate DataType="{x:Type local:Type1}" ItemsSource="{Binding Children, Mode=OneWay}">
<StackPanel Orientation="Horizontal">
<TextBlock Text="{Binding Name}" Margin="6,0,6,0" />
<TextBlock Text="{Binding IsDirty}" Margin="6,0,6,0" />
<i:Interaction.Behaviors>
<dragDrop:FrameworkElementDropBehavior DragEffect="Move" />
</i:Interaction.Behaviors>
</StackPanel>
</HierarchicalDataTemplate>
</TreeViewItem.Resources>
</TreeViewItem>
I added a TextBlock to display the value of the IsDirty property; when that is true, the Background remains unchanged.
I have tried moving the Style to the HierarchicalDataTemplate.Resources, but that made no difference.
What am I overlooking?
Thanks --

That's because implicit styles targeting types not derived from Control do not cross the template boundary, i.e. are not applied inside templates unless they're defined within that template's scope. Here's a good post explaining how it works and why does it work this way.
In order to cross the template boundary, you should use a type deriving from Control (e.g. a Label) instead of a TextBlock and define implicit style targeting that type.
Otherwise, you could put your style in scope of the template in question by moving it into the template's resources dictionary:
<HierarchicalDataTemplate (...)>
<HierarchicalDataTemplate.Resources>
<Style TargetType="{x:Type TextBlock}">
(...)
</Style>
</HierarchicalDataTemplate.Resources>
(...)
</HierarchicalDataTemplate>

Related

Excess border selection in WPF's Lisbox [duplicate]

I have a ListBox in which each item is a StackPanel. The StackPanel consist of an Image and a TextBlock below it:
<ListBox.ItemTemplate>
<DataTemplate>
<StackPanel Margin="10">
<Image>
<Image.Source>
<BitmapImage UriSource="{Binding Path=ImageFilePath}"/>
</Image.Source>
</Image>
<TextBlock Text="Title" TextAlignment="Center"/>
</StackPanel>
</DataTemplate>
</ListBox.ItemTemplate>
It looks like this:
When the user select an item, I get the default blue rectangle that surround the StackPanel:
Now, I want to make a different border for the selected-item, but I want it to surround only the image.
I know how to make a control template and put a custom border around the ContentPresenter, but this, of course, will surround the whole StackPanel, not only the Image.
I don’t know if making changes to the ContentPresenter is possible, and if it is a good idea at all. If there is other way to achieve the look I want, it will be fine as well.
Right, the ListBox's own ContentPresenter isn't helpful for what you're doing. You want to a) eliminate the ListBox's own selection visuals and b) replace them with something more suitable in the DataTemplate for your items.
The default selection visual is applied by the default template for ListBoxItem. So replace that template. Using a Style in the resources for your ListBox, apply your own control template to ListBoxItem. Not much to it, just present the content and don't provide a selection background. Then you handle the selection visuals with a trigger in your data template, where your image and your label are defined and you can apply changes to one and not the other. The below example works for me.
Note that there's some fiddling with the HorizontalAlignment on the Border element to make it cling to the Image element within it. Also, I wrote a quickie test viewmodel whose Items property is called Items; I assume this is not the name of the collection member you're using to populate your own ListBox.
<ListBox
Margin="8"
ItemsSource="{Binding Items}"
>
<ListBox.Resources>
<Style TargetType="{x:Type ListBoxItem}">
<Setter Property="HorizontalContentAlignment" Value="Stretch" />
<Setter Property="Template">
<Setter.Value>
<ControlTemplate TargetType="{x:Type ListBoxItem}">
<Grid>
<ContentPresenter />
</Grid>
</ControlTemplate>
</Setter.Value>
</Setter>
</Style>
</ListBox.Resources>
<ListBox.ItemTemplate>
<DataTemplate>
<Grid>
<Border
x:Name="HighlightBorder"
BorderThickness="4"
HorizontalAlignment="Left"
VerticalAlignment="Top"
Margin="10"
>
<Border.Style>
<Style TargetType="Border">
<!-- MUST set default BorderBrush via a style, if you set it at all.
As an attribute on the Border tag, it would override the effects of
the trigger below.
-->
<Setter Property="BorderBrush" Value="Transparent" />
</Style>
</Border.Style>
<Image Source="{Binding ImageFilePath}" />
</Border>
</Grid>
<DataTemplate.Triggers>
<DataTrigger
Binding="{Binding IsSelected, RelativeSource={RelativeSource AncestorType=ListBoxItem}}"
Value="True">
<Setter TargetName="HighlightBorder" Property="BorderBrush" Value="Orange" />
</DataTrigger>
</DataTemplate.Triggers>
</DataTemplate>
</ListBox.ItemTemplate>
</ListBox>

TreeView HierarchicalDataTemplate does not apply ItemContainerStyle

I try do display hierarchical data with a TreeView and I would like to set different DataTemplates for my different Children types.
But the thing is, that my style does not get applied.
Maybe its a very simple mistake but i really do not find it.
<TreeView ItemsSource="{Binding List}">
<TreeView.Resources>
<HierarchicalDataTemplate DataType="{x:Type local:Main}" ItemsSource="{Binding Children}">
<TextBlock Text="{Binding Property1}"/>
</HierarchicalDataTemplate>
<HierarchicalDataTemplate DataType="{x:Type local:Type2}">
<HierarchicalDataTemplate.ItemContainerStyle>
<Style TargetType="TreeViewItem">
<Setter Property="IsExpanded" Value="True"/>
</Style>
</HierarchicalDataTemplate.ItemContainerStyle>
<TextBlock Text="{Binding Property2}"/>
</HierarchicalDataTemplate>
<HierarchicalDataTemplate DataType="{x:Type local:Type3}">
<HierarchicalDataTemplate.ItemContainerStyle>
<Style TargetType="TreeViewItem">
<Setter Property="IsExpanded" Value="False"/>
</Style>
</HierarchicalDataTemplate.ItemContainerStyle>
</HierarchicalDataTemplate>
</TreeView.Resources>
</TreeView>
Ok, I know what is going wrong. HierarchicalDataTemplate.ItemContainerStyle contains a style which is applied to the ItemsContainer where the children for the current node are stored. Try this as an experiment, change your style to:
<HierarchicalDataTemplate.ItemContainerStyle>
<Style TargetType="{x:Type TreeViewItem}">
<Setter Property="IsExpanded" Value="True" />
<Setter Property="Foreground" Value="Navy" />
</Style>
</HierarchicalDataTemplate.ItemContainerStyle>
You will notice that the node that you put this style onto continues to have a black foreground, but all it's children will now have a foreground of Navy.
It is a little counter-intuitive, but when you think about it, I guess it makes sense. So, bearing this in mind, I think the best solution is to bind IsExpanded for all TreeViewItems to a variable in the VM and then pick different values based on types there.
I had a similar issue maybe. In case Main, Type2 and Type3 are interfaces the selection in XAML won't work, I had to use classes. If you want to use interfaces you could implement a template selector.

Different views / data template based on member variable

I have a view model called
ViewModelClass
which contains a boolean.
I have another view model which contains
ObservableCollection<ViewModelClass> m_allProjects;
Then I have this in my view:
<DataTemplate>
<views:ProjectInfoView x:Key="ProjectInfoDetailTemplate"/>
</DataTemplate>
<ItemsControl Grid.Row="1" Grid.Column="0"
ItemsSource="{Binding AllProjects}"
ItemTemplate="{StaticResource ProjectInfoDetailTemplate}"
Margin="10,28.977,10,10">
</ItemsControl >
I want, based on the boolean in the AllProjects-collection, to use a different datatemplate. What is the best way to do this?
I know I can do this with different ViewModels and use a kind of ViewModel-base object, but I prefer just to use 1 view model.
EDIT:
I want to do this with data triggers. Can someone provide me with some code please?
I usually use a ContentControl to display the data, and swap out the ContentTemplate in a trigger based on the property that changes.
Here's an example I have posted on my blog that swaps a template based on a bound property
<DataTemplate x:Key="PersonTemplate" DataType="{x:Type local:ConsumerViewModel}">
<TextBlock Text="I'm a Person" />
</DataTemplate>
<DataTemplate x:Key="BusinessTemplate" DataType="{x:Type local:ConsumerViewModel}">
<TextBlock Text="I'm a Business" />
</DataTemplate>
<DataTemplate DataType="{x:Type local:ConsumerViewModel}">
<ContentControl Content="{Binding }">
<ContentControl.Style>
<Style TargetType="{x:Type ContentControl}">
<Setter Property="ContentTemplate" Value="{StaticResource PersonTemplate}" />
<Style.Triggers>
<DataTrigger Binding="{Binding ConsumerType}" Value="Business">
<Setter Property="ContentTemplate" Value="{StaticResource BusinessTemplate}" />
</DataTrigger>
</Style.Triggers>
</Style>
</ContentControl.Style>
</ContentControl>
</DataTemplate>
A DataTemplateSelector will also work, but only if the property that determines which template to show doesn't change since DataTemplateSelectors don't respond to change notifications. I usually avoid them if possible since I also prefer my view selection logic in my view so I can see whats going on.

XAML trigger template to set visibilty based on another element

I have several StackPanels that change visibility based on ToggleButtons. The code below works if I replace Tag with btn1 on the DataTrigger-lines.
How do I use the value of the Tag property?
<Window x:Class="MyTestApp.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
Title="TestApp">
<Window.Resources>
<Style x:Key="panelStyle" TargetType="{x:Type StackPanel}">
<Style.Triggers>
<DataTrigger Binding="{Binding ElementName=Tag, Path=IsChecked}" Value="False">
<Setter Property="StackPanel.Visibility" Value="Collapsed" />
</DataTrigger>
<DataTrigger Binding="{Binding ElementName=Tag, Path=IsChecked}" Value="True">
<Setter Property="StackPanel.Visibility" Value="Visible" />
</DataTrigger>
</Style.Triggers>
</Style>
</Window.Resources>
<WrapPanel>
<ToggleButton Content="One" Name="btn1" />
<ToggleButton Content="Two" Name="btn2" />
<StackPanel Style="{StaticResource panelStyle}" Tag="{Binding btn1}">
<Label Content="Data to panel 1" />
</StackPanel>
<StackPanel Style="{StaticResource panelStyle}" Tag="{Binding btn2}">
<Label Content="Data to panel 2" />
</StackPanel>
</WrapPanel>
</Window>
This question is very similar, but I'm missing details on how to pass an element name.
XAML - Generic textbox stylewith triggers / parameters?
Your bindings are incorrect.
In your DataTemplate the bindings should be:
<DataTrigger Binding="{Binding Path=Tag.IsChecked, RelativeSource={RelativeSource Self}}" Value="False">
<Setter Property="StackPanel.Visibility" Value="Collapsed" />
</DataTrigger>
Here the RelativeSource with a mode of Self tells the binding engine that the object to bind against is object to which the style is being applied (e.g. your StackPanel). The PropertyPath of Tag.IsChecked tells the binding engine to look for a property called IsChecked from the object stored in Tag.
Finally the bindings in your StackPanel should be:
<StackPanel Style="{StaticResource panelStyle}" Tag="{Binding ElementName=btn1}">
<Label Content="Data to panel 1" />
</StackPanel>
Here ElementName creates a binding to another element in the logical tree. If you do not explicitly assign to any properties in a Binding as in your original example:
Tag="{Binding btn1}"
The value specified is assigned to the Path property. So this would be the same as:
Tag="{Binding Path=btn1}"
Also note, that using Tag is not considered best practice since it's type is of object and its use is unrestricted, and hence can take on any number of different meanings throughout your project (which often makes it difficult to understand, especially when used in Templates that are located far away from their actual use).
Hope this helps!
Use Converter: set the visibility of StackPanel:
<StackPanel Visivility="{Binding IsChecked, ElementName=btn1, Converter={StaticResource BooleanToVisibilityConverter}}">
...
</StackPanel>

WPF: TreeView unable to apply Hierachical template and style at the same time

Here is what I want to do:
<TreeView>
<TreeView.Resources>
<Style TargetType="TreeViewItem" >
<Setter Property="dz:VirtualListItemBase.AutoLoad" Value="true" />
</Style>
</TreeView.Resources>
<TreeView.ItemTemplate>
<HierarchicalDataTemplate ItemsSource="{Binding Data.Nodes}" >
<Label Grid.Column="1" Grid.Row="0" Content="{Binding Data.Name}" dz:VirtualListItemBase.AutoLoad="true" />
</HierarchicalDataTemplate>
</TreeView.ItemTemplate>
The style part never gets applied. The HierachicalDataTemplate seems to override the initial style. I cannot use the type to apply the template since multiple types are involved. Any ideas?
Thanks
DataTemplate has higher precedence over Style. Try moving the DataTemplate into the Style as:
<Style TargetType="TreeViewItem" >
<Setter Property="dz:VirtualListItemBase.AutoLoad" Value="true" />
<Setter Property="ItemTemplate">
<Setter.Value>
<HierarchicalDataTemplate ...
</HierarchicalDataTemplate>
</Setter.Value>
</Setter>
</Style>

Resources