How do I bind to the parent's DataContext in a HierarchicalDataTemplate - wpf

I have a situation in which I am using a HierarchicalDataTemplate in which most display fields are bound to the items represented in this template (like you'd expect), but I also need one Binding to the data context of the UserControl itself. I fail to manage this last bit.
So I currently have:
<UserControl x:Class="MyProject.ProjectTreeView">
<UserControl.Resources>
<HierarchicalDataTemplate DataType="{x:Type StreetViewModel}"
ItemsSource="{Binding Houses}">
<!-- This Binding works fine (binds to local:StreetViewModel.Street.StreetName) -->
<TextBlock Text="{Binding Street.StreetName}">
<TextBlock.Style>
<Style TargetType="{x:Type TextBlock}">
<Style.Triggers>
<!-- THIS BINDING DOESN'T WORK (I want it to bind to local:ProjectTreeView.SelectedStreet) -->
<DataTrigger Binding="{Binding Path=SelectedStreet, RelativeSource={RelativeSource Self}, UpdateSourceTrigger=PropertyChanged}" Value="Main Street">
<Setter Property="FontWeight" Value="Bold" />
</DataTrigger>
<!-- This one works again (binds to local:StreetViewModel.Street.ConstructionWorkGoingOn) -->
<DataTrigger Binding="{Binding Street.ConstructionWorkGoingOn, UpdateSourceTrigger=PropertyChanged}" Value="true">
<Setter Property="Foreground" Value="Red" />
</DataTrigger>
</Style.Triggers>
</Style>
</TextBlock.Style>
</TextBlock>
</HierarchicalDataTemplate>
So the problematic thing is that I want to access data in ProjectTreeView but can't reach it within this code. I've tried all sort of things with RelativeSource but that doesn't work. How can this be done?

Try :
<DataTrigger Binding="{Binding Path=DataContext.SelectedStreet, RelativeSource={RelativeSource AncestorType=UserControl}}, UpdateSourceTrigger=PropertyChanged}" Value="Main Street">
<Setter Property="FontWeight" Value="Bold" />
</DataTrigger>

Related

How to set a DataTemplate with one x:Key and multiple DataType?

I want to define multiple DataTemplate that can be selected based on DataType, and switch in Style. How can I get this effect using only XAML code? Or impossible?
UPDATE: Sorry, my fault, I didn't write clearly. I can't set default because I want to be able to switch views.
<ListView.Resources>
<DataTemplate x:Key="xkey">
<...... for DataType="{x:Type local:typea}" />
<...... for DataType="{x:Type local:typeb}" />
</DataTemplate>
<DataTemplate x:Key="xkey2">
<...... for DataType="{x:Type local:typea}" />
<...... for DataType="{x:Type local:typeb}" />
</DataTemplate>
</ListView.Resources>
<ListView.Style>
<Style TargetType="ListView">
<Style.Triggers>
<DataTrigger ....>
<Setter Property="ItemTemplate" Value="{StaticResource xkey}"/>
</DataTrigger>
<DataTrigger ....>
<Setter Property="ItemTemplate" Value="{StaticResource xkey2}"/>
</DataTrigger>
<DataTrigger ...>
<Setter Property="View" Value=... />
</DataTrigger>
</Style.Triggers>
</Style>
</ListView.Style>

How to change TreeViewItem background color according to property of bound data?

I have a TreeView where the data is bound to generic derived wrapper classes over my data hierarchy.
My bound wrapper classes include added fields like "IsHilighted" and "IsExpanded".
I would like to change the background of any TreeViewItem according to its bound data property "IsHiglighted". I would like to set the color of Hilighted item to the same (or lighter) color as the default Selected item background color.
Ideally, I would like to not modify existing XAML... I mean being able to eventually add the behavior through code.
UPDATE
I have found a partial solution: I had to add triggers as defined below. Code included below.
<Style TargetType="TreeViewItem">
<Setter Property="IsExpanded" Value="{Binding Path=IsExpanded}"/>
<Style.Triggers>
<DataTrigger Binding="{Binding Path=IsHilighted}" Value="true">
<Setter Property="Background" Value="SlateBlue"></Setter>
<Setter Property="Opacity" Value="160"></Setter>
</DataTrigger>
</Style.Triggers>
</Style>
Still not resolved: How could I bind the color of Hilighted item (see partial solution above) to the "Selected" TreeViewItem background color, i.e. replace "SlateBlue" on partial solution to binding to existing selected item style background color ?
Original TreeView XAML code:
<TreeView Name="TreeViewSelectScopeStudy" MinHeight="24" Margin="7" ItemsSource="{Binding Path=TvItemRootPssTreeViewRoot.ChildsView}" Height="Auto"
VerticalAlignment="Stretch"
VirtualizingStackPanel.IsVirtualizing="True"
VirtualizingStackPanel.VirtualizationMode="Recycling">
<TreeView.Resources>
<Style TargetType="TreeViewItem">
<Setter Property="IsExpanded" Value="{Binding Path=IsExpanded}"/>
</Style>
<SolidColorBrush x:Key="{x:Static SystemColors.HighlightBrushKey}" Color="Red" />
<SolidColorBrush x:Key="{x:Static SystemColors.ControlBrushKey}" Color="Green" />
<HierarchicalDataTemplate DataType="{x:Type scopeSelection:WrapperSimulatedInfoStudy}" ItemsSource="{Binding Path=Childs}">
<StackPanel Orientation="Horizontal">
<CheckBox IsChecked="{Binding Path=IsSelected}"></CheckBox>
<TextBlock Text="{Binding Path=TvItemName}" Margin="5,0,0,0"></TextBlock>
</StackPanel>
</HierarchicalDataTemplate>
<HierarchicalDataTemplate DataType="{x:Type scopeSelection:WrapperSimulatedInfoSimulation}">
<StackPanel Orientation="Horizontal" ToolTip="{Binding Path=Item.InvalidityReason}">
<StackPanel.Style>
<Style TargetType="{x:Type StackPanel}">
<Style.Triggers>
<DataTrigger Binding="{Binding Path=Item.IsValid}" Value="false">
<Setter Property="Opacity" Value="160"></Setter>
</DataTrigger>
</Style.Triggers>
</Style>
</StackPanel.Style>
<CheckBox IsChecked="{Binding Path=IsSelected}" IsEnabled="{Binding Path=Item.IsValid}" ToolTip="{Binding Path=Item.InvalidityReason}"></CheckBox>
<CheckBox IsChecked="{Binding Path=IsHilighted}"></CheckBox>
<TextBlock Text="{Binding Path=TvItemName}" Margin="5,0,0,0" ToolTip="{Binding Path=Item.InvalidityReason}">
<TextBlock.Style>
<Style TargetType="{x:Type TextBlock}">
<Style.Triggers>
<DataTrigger Binding="{Binding Path=Item.IsValid}" Value="false">
<Setter Property="Background" Value="LightPink"></Setter>
</DataTrigger>
</Style.Triggers>
</Style>
</TextBlock.Style>
</TextBlock>
</StackPanel>
</HierarchicalDataTemplate>
</TreeView.Resources>
</TreeView>
You could define one more property called IsItemSelected and bind it to TreeViewItems IsSelected property (similar to how you have done for IsExpanded).
<Style TargetType="TreeViewItem">
<Setter Property="IsExpanded" Value="{Binding Path=IsExpanded}"/>
<Setter Property="IsSelected" Value="{Binding Path=IsItemSelected}"/>
</Style>
Then you could define a DataTrigger for the IsItemSelected property and set the background color.
<DataTrigger Binding="{Binding Path=IsItemSelected}" Value="true">
<Setter Property="Background" Value="Blue"></Setter>
</DataTrigger>

Treeview ContextMenu binding Cut and Paste to logical tree

This seems like a pretty common scenario but I can't figure out how to bind the menu items to disable when there is nothing in the clipboard.
I've decided against using the Windows clipboard and instead store the actual object in a reference variable on the UserControl called NodeClipboard. Since it is strongly typed and implements INotifyProperty it is a lot more useful to me than the Windows clipboard.
Binding to the individual item works fine though it is extremely verbose because you can't set EventHandlers within resources without using the Style Event Setters.
It sort of looks like this...
<UserControl x:Name="PART_Root">
<TreeView x:Name="PART_Tree" ItemsSource="{Binding ElementName=PART_Root, Path=RootItemContainer}">
<TreeView.Resources>
<ContextMenu x:Key="ContextMenu">
<ContextMenu.Style>
<Style TargetType="ContextMenu">
<!-- I use this event to select the tree view item otherwise it is actually pretty difficult to know what item you right clicked on -->
<EventSetter Event="Opened" Handler="ContextMenu_Opened"/>
</Style>
</ContextMenu.Style>
<MenuItem Header="Cut">
<MenuItem.Style>
<Style TargetType="MenuItem">
<EventSetter Event="Click" Handler="CutNode_Click"/>
<Style.Triggers>
<!-- This binding is fine because it binds to the item that was right clicked on -->
<DataTrigger Binding="{Binding Path=IsRoot}" Value="True">
<Setter Property="IsEnabled" Value="False"/>
</DataTrigger>
</Style.Triggers>
</Style>
</MenuItem.Style>
</MenuItem>
<MenuItem Header="Paste">
<MenuItem.Style>
<Style TargetType="MenuItem">
<EventSetter Event="Click" Handler="PasteNode_Click"/>
<!-- This binding always fails because ContextMenu lives outside of the logical tree -->
<Style.Triggers>
<DataTrigger Binding="{Binding ElementName=PART_Root, Path=NodeClipboard" Value="{x:Null}">
<Setter Property="IsEnabled" Value="False"/>
</DataTrigger>
</Style.Triggers>
</Style>
</MenuItem.Style>
</MenuItem>
</ContextMenu>
<Style TargetType="TreeViewItem">
<Setter Property="ContextMenu" Value="{StaticResource ContextMenu}"/>
</Style>
<HierarchicalDataTemplate DataType="{x:Type local:Node}" ItemsSource="{Binding Path=Children}">
<TextBlock Text="{Binding Path=Id}"/>
</HierarchicalDataTemplate>
</TreeView.Resources>
</TreeView>
</UserControl>
The key part that doesn't work is this here:
<!-- This binding always fails because ContextMenu lives outside of the logical tree -->
<Style.Triggers>
<DataTrigger Binding="{Binding ElementName=PART_Root, Path=NodeClipboard" Value="{x:Null}">
<Setter Property="IsEnabled" Value="False"/>
</DataTrigger>
</Style.Triggers>
I've tried using relative source which results in the same problem. The only solution I've thought of so far is making two context menus, one with Paste enabled and one without, and switching the context menu on the style on the TreeViewItem style's ContextMenu setter like so...
<TreeView.Resources>
<Style TargetType="TreeViewItem">
<Setter Property="ContextMenu" Value="{StaticResource ContextMenu_PasteEnabled}"/>
<Style.Triggers>
<DataTrigger Binding="{Binding ElementName=PART_Root, Path=NodeClipboard" Value="{x:Null}">
<Setter Property="ContextMenu" Value="{StaticResource ContextMenu_PasteDisabled}"/>
</DataTrigger>
</Style.Triggers>
</Style>
</TreeView.Resources>
Use the Clipboard class. Then you can use the ContainsText method to determine if there is any data on the clipboard.
http://msdn.microsoft.com/en-us/library/system.windows.forms.clipboard.aspx

Setting different styles for Dual level grouping with DataTrigger

I have a dual level grouping and thought I could define different styles with DataTriggers.
Thinking that GroupStyles.HeaderTemplate would bind to CollectionViewGroup I tried DataBinding to IsBottomLevel property.
<GroupStyle>
<GroupStyle.HeaderTemplate>
<DataTemplate>
<TextBlock x:Name="GroupName"
Text="{Binding Path=Name}"
Foreground="Red" />
<DataTemplate.Triggers>
<DataTrigger Binding="IsBottomLevel" Value="True" >
<Setter TargetName="GroupName" Property="Foreground" Value="Blue" />
</DataTrigger>
</DataTemplate.Triggers>
</DataTemplate>
</GroupStyle.HeaderTemplate>
</GroupStyle>
Can I get this to work somehow?
Define your trigger in the Style of the TextBlock itself, TargetName normally is for ControlTemplates, then you can just drop that.
This is not a binding:
Binding="IsBottomLevel"
You should replace it with the following of course:
Binding="{Binding IsBottomLevel}"
<TextBlock Text="{Binding Name}">
<TextBlock.Style>
<Style TargetType="{x:Type TextBlock}">
<Setter Property="Foreground" Value="Red" />
<Style.Triggers>
<DataTrigger Binding="{Binding IsBottomLevel}" Value="True">
<Setter Property="Foreground" Value="Blue" />
</DataTrigger>
</Style.Triggers>
</Style>
</TextBlock.Style>
</TextBlock>
If you set the red Foreground directly in the TextBlock declaration the trigger will have no effect due to precedence.

WPF: Can not find the Trigger target 'cc'. The target must appear before any Setters, Triggers

what is wrong about the following code?
I get this error during compilation:
The property 'TargetName' does not represent a valid target for the 'Setter' because an element named 'cc' was not found. Make sure that the target is declared before any Setters, Triggers or Conditions that use it.
How do I have to refactor my code so I can compile it without error?
I just want to switch a datatemplate with DataTrigger bound to a value in my PersonViewModel!
<ContentControl x:Name="cc" Grid.Column="1">
<DataTemplate>
<DataTemplate.Triggers>
<DataTrigger Binding="{Binding Path=CurrentPersonViewModel.IsNew}" Value="True">
<Setter TargetName="cc" Property="ContentTemplate" Value="{DynamicResource NewPersonId}" />
</DataTrigger>
<DataTrigger Binding="{Binding Path=CurrentPersonViewModel.IsNew}" Value="False">
<Setter TargetName="cc" Property="ContentTemplate" Value="{DynamicResource SelectedPersonId}" />
</DataTrigger>
</DataTemplate.Triggers>
</DataTemplate>
</ContentControl>
Update
You can use a Style for the ContentControl and change the ContentTemplate from there
<ContentControl Name="cc" Grid.Column="1">
<ContentControl.Style>
<Style TargetType="ContentControl">
<Setter Property="ContentTemplate" Value="{DynamicResource SelectedPersonId}"/>
<Style.Triggers>
<DataTrigger Binding="{Binding Path=CurrentPersonViewModel.IsNew}" Value="True">
<Setter Property="ContentTemplate" Value="{DynamicResource NewPersonId}" />
</DataTrigger>
</Style.Triggers>
</Style>
</ContentControl.Style>
</ContentControl>
UPDATE
I don't understand why the View's in the DataTemplate doesn't inherit the DataContext. Got it working by using this but I can't see why this is necessary
<DataTemplate x:Key="NewPersonId">
<local:NewPersonView DataContext="{Binding RelativeSource={RelativeSource AncestorType={x:Type ContentControl}}, Path=DataContext.CurrentPersonViewModel}" />
</DataTemplate>
<DataTemplate x:Key="SelectedPersonId">
<local:SelectedPersonView DataContext="{Binding RelativeSource={RelativeSource AncestorType={x:Type ContentControl}}, Path=DataContext.SelectedPersonViewModel}"/>
</DataTemplate>
You do not need the whole DataTrigger stuff.
Just read this to make your DataTemplateSelector work properly:
http://joshsmithonwpf.wordpress.com/2007/03/18/updating-the-ui-when-binding-directly-to-business-objects-that-are-modified/

Resources