How to use Relative Source binding to get the Parent class? - c#

I have a TreeView with a HierarchicalDataTemplate like this:
<TreeView ItemsSource="{Binding Situation.TopLevelSituationList}">
<TreeView.Resources>
<HierarchicalDataTemplate DataType="{x:Type local:GroupSubGroup}" ItemsSource="{Binding SubGroups}"/>
<HierarchicalDataTemplate DataType="{x:Type local:GroupDecision}" ItemsSource="{Binding Decisions}">
<HierarchicalDataTemplate.ItemTemplate>
<DataTemplate>
<TextBlock Style="{StaticResource TextBlockBaseStyling}" Text="{Binding}"/>
</DataTemplate>
</HierarchicalDataTemplate.ItemTemplate>
</HierarchicalDataTemplate>
</TreeView.Resources>
In my base, abstract class, it is set up like this:
public abstract class Group
{
private bool isSelected;
public bool IsSelected
{
get
{
return this.isSelected;
}
set
{
this.isSelected = value;
OnPropertyChanged("IsSelected");
}
}
The two classes that derive from this class are references in my TreeView, each with their own HierarchicalDataTemplate (GroupSubGroup, GroupDecision).
This is my problem:
<HierarchicalDataTemplate DataType="{x:Type local:GroupDecision}" ItemsSource="{Binding Decisions}">
This ItemsSource is a collection of Strings. I know I need to use RelativeBinding to reference other properties in the GroupDecision class.
My TreeView is something like this:
GroupSubGroups' ItemsSource is a Collection of type Group so I will always have a reference to the IsSelected property directly.
For a visual aid, this is what my TreeView and TreeView Items look like:
------------------------------------
| This row is a GroupDecision |
| -------------------------------- |
| This row is a Decision (str) |
| This row is also a Decision (str) |
| |
-------------------------------------
When I select the top row, the GroupDecision row, I am able to fire a PropertyChange on the IsSelected property. However, if I were to select a Decision row, I do not get the same firing because it is actually a String.
The whole purpose of this is to make the border of the TreeViewItem thicker when it is selected but I can only do that if I select the top row but I want to have the same functionality if I select a Decision string since it is contained within as well..
I hope that was clear. Please let me know if I can further explain.
Edit: I applied a style to the Border but it does not increase the size of the border thickness when clicking a String row:
<Border.Style>
<Style TargetType="Border">
<Setter Property="BorderBrush" Value="{Binding GroupColor}"/>
<Setter Property="Grid.Column" Value="1"/>
<Setter Property="BorderThickness" Value="1"/>
<Style.Triggers>
<DataTrigger Binding="{Binding IsSelected, UpdateSourceTrigger=PropertyChanged}" Value="True">
<Setter Property="BorderThickness" Value="3"/>
</DataTrigger>
</Style.Triggers>
</Style>
</Border.Style>

Since you need to define a border in each of the templates anyhow, it's fairly simple to explicitly give it a style you define as a resource.
Define the style in TreeView.Resources:
<TreeView.Resources>
<Style TargetType="Border" x:Key="ExpandingBorder">
<Setter Property="BorderBrush" Value="Black"/>
<Setter Property="BorderThickness" Value="1"/>
<Style.Triggers>
<DataTrigger Binding="{Binding IsSelected
, RelativeSource={RelativeSource AncestorType=TreeViewItem}}"
Value="True">
<Setter Property="BorderThickness" Value="3"/>
</DataTrigger>
</Style.Triggers>
</Style>
You can then use that on any borders in your templates. For example:
<HierarchicalDataTemplate DataType="{x:Type local:Family}" ItemsSource="{Binding Members}">
<Border Style="{StaticResource ExpandingBorder}">
<StackPanel Orientation="Horizontal"
Height="32"
>
<Label VerticalAlignment="Center" FontFamily="WingDings" Content="1"/>
<TextBlock Text="{Binding Name}" />
</StackPanel>
</Border>
</HierarchicalDataTemplate>

Related

Command binding to top-level MenuItem of Menu doesn't work

I'm learning WPF and developing a dynamic Menu which is driven by data binding of it's ItemsSource to an ObservableCollection. To do this I have a simple MenuItemViewModel and a HierarchicalDataTemplate for automatic binding of MenuItems to it.
The problem I have is that Command property doesn't work for top level menu items. Despite it is set, a MenuItem doesn't react on mouse click and doesn't get disabled if Command cannot be executed. Simply it's like it's not getting bound.
For lower level menu items though, it works as intended. I think this should be a problem of my HierarchicalDataTemplate but I can't find it, because as I see there's no code in template which might affect command binding of only top-level MenuItems.
MenuItemViewModel implements INotifyPropertyChanged and contains following public properties:
string Text
Uri ImageSource
ICommand Command
ObservableCollection<MenuItemViewModel> Children
HierarchicalDataTemplate for MenuItem in my Window.Resources is as follows:
<HierarchicalDataTemplate DataType="{x:Type common:MenuItemViewModel}"
ItemsSource="{Binding Path=Children}">
<HierarchicalDataTemplate.ItemContainerStyle>
<Style TargetType="MenuItem">
<Setter Property="Command"
Value="{Binding Command}" />
</Style>
</HierarchicalDataTemplate.ItemContainerStyle>
<StackPanel Orientation="Horizontal">
<Image Source="{Binding ImageSource}" />
<TextBlock Text="{Binding Text}" VerticalAlignment="Center"/>
</StackPanel>
</HierarchicalDataTemplate>
Can you please point me on my mistake?
EDIT: Top-level MenuItem doesn't contain any children (i.e. Children collection of associated ViewModel is empty).
Thanks to #sTrenat comment I've come to solution below.
<Menu.Resources>
<!-- cancel sharing of image so Icon will work properly -->
<Image x:Key="MenuIcon" Source="{Binding ImageSource}" x:Shared="False"/>
</Menu.Resources>
<Menu.ItemContainerStyle>
<Style TargetType="{x:Type MenuItem}"
BasedOn="{StaticResource {x:Type MenuItem}}">
<Setter Property="Header" Value="{Binding Text}" />
<Setter Property="Icon" Value="{StaticResource MenuIcon}"/>
<Setter Property="Command" Value="{Binding Command}"/>
<Setter Property="ItemsSource" Value="{Binding Children}" />
<!-- centering MenuItem's Header -->
<Setter Property="HeaderTemplate">
<Setter.Value>
<DataTemplate>
<ContentPresenter HorizontalAlignment="Center" VerticalAlignment="Center" Content="{Binding}" />
</DataTemplate>
</Setter.Value>
</Setter>
<!-- setting Icon to null when ImageSource isn't specified -->
<Style.Triggers>
<DataTrigger Binding="{Binding ImageSource}"
Value="{x:Null}">
<Setter Property="Icon" Value="{x:Null}"/>
</DataTrigger>
</Style.Triggers>
</Style>
</Menu.ItemContainerStyle>

Indenting top hierarchical items in a wpf treeview

I am trying to provide a separation (margin) between the top hierarchical items in a WPF TreeView. The problem is that I cannot figure out how to write the Style for it to apply only to the top items and not to every item.
The code for my TreeView looks like this:
<TreeView ItemContainerStyle="{StaticResource treeViewItemStyle}"
ItemsSource="{Binding Container.RootRules}"
KeyUp="treeView_KeyUp"
SelectedItemChanged="TreeView_SelectedItemChanged">
<TreeView.Resources>
<HierarchicalDataTemplate DataType="{x:Type me:HybridForecastRulesViewModel}"
ItemsSource="{Binding Children}">
<Border Name="bd"
...
</Border>
</HierarchicalDataTemplate>
<HierarchicalDataTemplate DataType="{x:Type me:RootRulesViewModel}"
ItemsSource="{Binding Rules}">
<Grid>
...
</Grid>
</HierarchicalDataTemplate>
</TreeView.Resources>
</TreeView>
I have a style for the treeViewItems like this:
<Style x:Key="treeViewItemStyle"
BasedOn="{StaticResource {x:Type TreeViewItem}}"
TargetType="{x:Type TreeViewItem}">
<Setter Property="Margin" Value="0,10,0,0" />
<Style.Triggers>
<DataTrigger Binding="{Binding IsVisible}" Value="False">
<Setter Property="Visibility" Value="Collapsed" />
</DataTrigger>
</Style.Triggers>
</Style>
But this style applies to items of both type (RootRulesViewModel and HybridForecastRulesViewModel), when I would like for it to only apply to items of type RootRulesViewModel. How can this be done?
And the icing on the cake would be for all RootRulesViewModel items to have a top Margin of 10, except or the first one.
TreeViewItem has no Level property to use.
So you have 2 options:
1) this. look at the TreeLevelConverter, it's interesting. They bind the control itself and use the converter to retrieve the Level. In your case, you can extend the converter so after it retrieves the Level, converts it in a Thickness instance to use as Margin.
2) you can create a Level property on your ViewModels (possibly in their base class in order to avoid code duplication). Then, every time that you add a child to a ViewModel node, you set the Level on that child. In the xaml, you bind the Margin property to the Level on the ViewModel, using a converter that returns different Thickness depending if the Level is 1 or not.
EDIT:
This is how you set a common Style for all the TreeViewItem:
<TreeView>
<TreeView.ItemContainerStyle>
<Style TargetType="{x:Type TreeViewItem}">
<Setter 1 .../>
<Setter 2 .../>
<Setter Property="Margin"
Value="{Binding Level, Converter={StaticResource LevelToMarginConverter}}"/>
</Style>
</TreeView.ItemContainerStyle>
<TreeView.Resources>
<!-- here your hierarchical DataTemplate... -->
<HierarchicalDataTemplate ... />
</TreeView.Resources>
</TreeView>
I've actually ended up with a solution for this. I'm not sure how good it is, so would be open to suggestions to improve.
I've written an object converter from this answer, to get the type of object in the Header of the TreeViewItem.
The code looks like this:
<Style.Triggers>
<DataTrigger Binding="{Binding RelativeSource={x:Static RelativeSource.Self}, Path=Header, Converter={StaticResource ObjectTypeConverter}}"
Value="RootRulesViewModel">
<Setter Property="Margin"
Value="0,0,0,10" />
</DataTrigger>
<DataTrigger Binding="{Binding IsVisible}"
Value="False">
<Setter Property="Visibility"
Value="Collapsed" />
</DataTrigger>
</Style.Triggers>
</Style>
Where ObjectTypeConverter is my custom converter which converts the object type to a string value.

How do I create a reusable TextBlock binding in WPF?

I've been teaching myself WPF and I'm still learning the basic concepts and terminology. So please forgive me if the title of this question isn't worded correctly.
I have the following XAML that's part of a HierarchicalDataTemplate bound to an object of type ViewModelBase:
<StackPanel Orientation="Horizontal">
<TextBlock Text="{Binding PrefixStyle.Text}"
FontWeight="{Binding PrefixStyle.Weight}"
Foreground ="{Binding PrefixStyle.Foreground}"
Margin="0,0,3,0"/>
<TextBlock Text="{Binding ValueStyle.Text}"
FontWeight="{Binding ValueStyle.Weight}"
Foreground ="{Binding ValueStyle.Foreground}"
Margin="0,0,3,0"/>
<TextBlock Text="{Binding SuffixStyle.Text}"
FontWeight="{Binding SuffixStyle.Weight}"
Foreground ="{Binding SuffixStyle.Foreground}"
Margin="0,0,3,0"/>
...
</StackPanel>
ViewModelBase has the corresponding properties referenced in the XAML:
public TextBlockStyle PrefixStyle...
public TextBlockStyle ValueStyle...
public TextBlockStyle SuffixStyle...
public class TextBlockStyle : INotifyPropertyChanged
{
public string Text...
public FontWieght Weight...
public Brush Foreground
}
How can I define the binding for TextBlock to TextBlockStyle only once in the XAML and save myself from having to explicitly bind each TextBlock property as above? So I can just have one line for each TextBlock:
<StackPanel Orientation="Horizontal">
<TextBlock Source="{Binding PrefixStyle}" />
<TextBlock Source="{Binding ValueStyle}" />
<TextBlock Source="{Binding SuffixStyle}" />
...
</StackPanel>
I just don't know where to start. Do I need to subclass TextBlock? Use a BindingGroup?
This must be common issue programmers run into - so I apologize if this question has been asked before. I've tried searching, but I'm so new to WPF I don't know how to properly express my question.
You use implicit styling to set a "global" style across your application. This is usually done in your App.xaml file inside of the ResourceDictionary.
<Style TargetType="{x:Type TextBlock}"
BasedOn="{StaticResource TextBlockStyle}">
<Setter Property="FontWeight"
Value="Bold">
</Setter>
<Setter Property="Foreground"
Value="Red">
</Setter>
</Style>
If you continue to use the method you're following (I wouldn't), you can change your setters to something like:
<Setter Property="FontWeight"
Value="{Binding Weight}">
</Setter>
Then, all of the TextBlocks in your application would use that style unless you define an explicit/implicit style more local to the control.
Edit to elaborate the comments:
I am also using a TreeView and HierarchicalDataTemplate. Thinking in MVVM fashion, each item in the TreeView (parent/child) should represent some sort of Model. For instance, think of Windows Explorer. Each item in it would be a Folder or Drive in MVVM world. Folder and Drive wouldn't have different font weight/color/size characteristics because that's all view related.
In our example, you would have something like this:
public class BaseItem : ViewModel
{
public ObservableCollection<BaseItem> Children { .... }
public bool IsSelected { .... }
public string Title { .... }
}
Since a folder can hold more folders, and a drive can holder folders you would something like:
public class DriveVM : BaseItem { }
public class FolderVM : BaseItem { }
Which would all you to do Children.Add(new FolderVM(folder)); inside of DriveVM, and that would show a folder under a drive. The problem is this can get pretty complex. In short though, I think inheritance is the key to using a TreeView.
Another option is something like this:
<Style x:Name="PrefixTextBlockStyle"
TargetType="{x:Type TextBox}">
<Setter Property="FontWeight"
Value="Bold" />
<Setter Property="Foreground"
Value="Red" />
<Setter Property="Text"
Value="{Binding Text}"
<Setter Property="Margin"
Value="0 0 3 0" />
</Style>
<Style x:Name="SuffixTextBlockStyle"
TargetType="{x:Type TextBox}">
<Setter Property="FontWeight"
Value="Italic" />
<Setter Property="Foreground"
Value="Orange" />
<Setter Property="Text"
Value="{Binding Text}" />
<Setter Property="Margin"
Value="0 0 3 0" />
</Style>
and then in your HierarchicalDataTemplate do:
<StackPanel Orientation="Horizontal">
<TextBlock DataContext="{Binding Prefix}"
Style="{StaticResource PrefixTextBlockStyle} ">
....
</StackPanel>
<StackPanel Orientation="Horizontal">
<StackPanel.Resources>
<Style TargetType="TextBlock">
<Setter Property="Text" Value="{Binding Text}" />
<Setter Property="FontWeight" Value="{Binding Weight}" />
<Setter Property="Foreground " Value="{Binding Foreground}" />
<Setter Property="Margin" Value="0,0,3,0" />
</Style>
</StackPanel.Resources>
<TextBlock DataContext="{Binding PrefixStyle}"/>
<TextBlock DataContext="{Binding ValueStyle}"/>
<TextBlock DataContext="{Binding SuffixStyle}"/>
...
</StackPanel>
Or using a global named style in app.xaml:
<Application>
<Application.Resources>
<Style x:Key="MyTextBlockStyle" TargetType="TextBlock">
<Setter Property="Text" Value="{Binding Text}" />
<Setter Property="FontWeight" Value="{Binding Weight}" />
<Setter Property="Foreground " Value="{Binding Foreground}" />
<Setter Property="Margin" Value="0,0,3,0" />
</Style>
</Application.Resources>
</Application>
Usage elsewhere:
<Window>
<StackPanel>
<!-- Single textblock with explicit style -->
<TextBlock DataContext="Blah" Style="{StaticResource MyTextBlockStyle}" />
<!-- Multiple textblocks with implicit style -->
<StackPanel Orientation="Horizontal">
<StackPanel.Resources>
<Style TargetType="TextBlock" BasedOn={StaticResource MyTextBlockStyle}" />
</StackPanel.Resources>
<TextBlock DataContext="{Binding PrefixStyle}"/>
<TextBlock DataContext="{Binding ValueStyle}"/>
<TextBlock DataContext="{Binding SuffixStyle}"/>
...
</StackPanel>
</Window>

How do I hide expander in Hierarchical Data Template when all children are collapsed or hidden?

I Have a tree view set up like so:
<TreeView ItemsSource="{Binding TreeRoot}" x:Name="HierarchyTreeView">
<TreeView.ItemContainerStyle>
<Style TargetType="{x:Type TreeViewItem}>
<Setter Property="Visibility" Value="{Binding IsVisible, Converter={...}}" />
...
</Style>
</TreeView.ItemContainerStyle>
<TreeView.ItemTemplate>
<HierarchicalDataTemplate ItemsSource="{Binding Children}">
<StackPanel Orientation="Horizontal">
<TextBlock Text="{Binding Name}" ... />
</StackPanel>
</HierarchicalDataTemplate>
</TreeView.ItemTemplate>
</TreeView>
Users have the ability to hide items in the treeview. However, when all items' visibility under a node has been set to Collapsed or Hidden, the expander remains.
Is there any way to hide the expander when every child's visibility under an item is set to hidden or collapsed?
I was facing this issue and came across this question. I saw there were some answers where you needed to make this change in the treeview item style which required you to edit the default style.
However, like the case above I too just needed the expander to be not visible when the node items are hidden.
A simple way to do it is to expand all your nodes by default and use a property in the datatrigger for the treeviewItem style to set the treeview item visibility.
This is how I was able to achieve it.
<Style x:Key="myTreeItemtyle" TargetType="{x:Type TreeViewItem}">
<Setter Property="IsExpanded" Value="True"/>
<Style.Triggers>
<DataTrigger Binding="{Binding IsVisible}" Value="True">
<Setter Property="Visibility" Value="Visible"/>
</DataTrigger>
<DataTrigger Binding="{Binding IsVisible}" Value="False">
<Setter Property="Visibility" Value="Collapsed"/>
</DataTrigger>
</Style.Triggers>
</Style>
Now, use this style as the treeview item container style:
<TreeView Name="treeView" ItemsSource="{Binding TreeItems}" ItemContainerStyle="{StaticResource myTreeItemtyle}" BorderThickness="0"/>

Setting ItemTemplate based on CheckBox value

I have a DataTemplate which contains a CheckBox and ListBox. When the CheckBox is checked, I want to change the ItemTemplate property on the ListBox to change the appearance of each item.
Right now, it looks like this:
<DataTemplate DataType={x:Type MyViewModel}>
<DockPanel>
<CheckBox DockPanel.Dock="Bottom"
Content="Show Details"
HorizontalAlignment="Right"
IsChecked="{Binding ShowDetails}"
Margin="0 5 10 5" />
<ListBox ItemsSource="{Binding Items}"
ItemTemplate="{StaticResource SimpleItemTemplate}"
Margin="10 0 10 5">
<ListBox.Triggers>
<DataTrigger Binding="{Binding ShowDetails}" Value="True">
<Setter Property="ItemTemplate"
Value="{StaticResource DetailedItemTemplate}" />
</DataTrigger>
</ListBox.Triggers>
</ListBox>
</DockPanel>
</DataTemplate>
However, when I try to compile, I get the following error messages:
Value 'ItemTemplate' cannot be assigned to property 'Property'. Invalid PropertyDescriptor value.
and
Cannot find the static member 'ItemTemplateProperty' on the type 'ContentPresenter'.
I'm still fairly new to WPF, so perhaps there is something I'm not quite understanding?
You need to do this through the ListBox Style rather than directly through its Triggers collection. A FrameworkElement's Triggers collection can only contain EventTriggers (so I'm surprised your sample got as far as complaining about the properties!). Here's what you need to do:
<ListBox ItemsSource="{Binding Items}">
<ListBox.Style>
<Style TargetType="ListBox">
<Setter Property="ItemTemplate" Value="{StaticResource SimpleItemTemplate}" />
<Style.Triggers>
<DataTrigger Binding="{Binding ShowDetails}" Value="True">
<Setter Property="ItemTemplate"
Value="{StaticResource DetailedItemTemplate}" />
</DataTrigger>
</Style.Triggers>
</Style>
</ListBox.Style>
</ListBox>

Resources