How to change the toggle of a TreeView - c#

I have a treeview working, it has a toggle button that can expand it's contents. When I do, each column is too far to the right from where I want it due to the toggle expansion width. I'd like to override that behaviour but I don't know how, I understand it's something to do with defining the control template of the TreeView?
This is my code
c#
Class MyList
{
double someDouble;
double somestring;
souble anotherString;
bool thisOnesABool;
}
Class MyContainer
{
string headerText1;
string headerText2;
List<MyList> SomeList;
}
List<MyContainer> SomeContainerInCodeBehind = new List<MyContainer>();
WPF
<UserControl.Resources>
<DataTemplate x:Key="level2">
<Grid>
//the content of 'MyList'
</Grid>
</DataTemplate>
<HierarchicalDataTemplate x:Key="level1"
ItemsSource="{Binding SomeListWithinContainer}"
ItemTemplate="{StaticResource level2}">
<Grid>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="100"/>
<ColumnDefinition Width="100"/>
</Grid.ColumnDefinitions>
<TextBox Grid.Column="0" Text="{Binding HeaderText}" />
<TextBox Grid.Column="1" Text="{Binding MoreHeaderText}" />
</Grid>
</HierarchicalDataTemplate>
</UserControl.Resources>
<Grid>
<TreeView Name="TheTreeView"
Grid.Row="1"
ItemTemplate="{StaticResource level1}"
ItemsSource="{Binding SomeContainerInCodeBehind}">
</TreeView>
</Grid>

In order to change the position of the expander (or expanded child items), you have to override the ControlTemplate of the TreeViewItem.
The following Style is taken from Microsoft Docs: TreeView Styles and Templates and shortened to show the relevant code. Visit the link to get the full code.
<Style x:Key="{x:Type TreeViewItem}"
TargetType="{x:Type TreeViewItem}">
<Setter Property="Template">
<Setter.Value>
<ControlTemplate TargetType="{x:Type TreeViewItem}">
<Grid>
<Grid.ColumnDefinitions>
<ColumnDefinition MinWidth="19"
Width="Auto" />
<ColumnDefinition Width="Auto" />
<ColumnDefinition Width="*" />
</Grid.ColumnDefinitions>
<Grid.RowDefinitions>
<RowDefinition Height="Auto" />
<RowDefinition />
</Grid.RowDefinitions>
<VisualStateManager.VisualStateGroups>
...
</VisualStateManager.VisualStateGroups>
<!-- Use Margin to modify the position of the "Expander" ToggleButton -->
<ToggleButton x:Name="Expander"
Style="{StaticResource ExpandCollapseToggleStyle}"
ClickMode="Press"
IsChecked="{Binding IsExpanded,
RelativeSource={RelativeSource TemplatedParent}}"/>
<Border x:Name="Bd"
Grid.Column="1"
Background="{TemplateBinding Background}"
BorderBrush="{TemplateBinding BorderBrush}"
BorderThickness="{TemplateBinding BorderThickness}"
Padding="{TemplateBinding Padding}">
<!-- Modify e.g. Margin to change the position of the Header (parent item) -->
<ContentPresenter x:Name="PART_Header"
ContentSource="Header"
HorizontalAlignment="{TemplateBinding HorizontalContentAlignment}"/>
</Border>
<!-- Modify e.g. using Margin to reposition the expanded child items -->
<ItemsPresenter x:Name="ItemsHost"
Grid.Row="1"
Grid.Column="1"
Grid.ColumnSpan="2"
Visibility="Collapsed" />
</Grid>
...
</ControlTemplate>
</Setter.Value>
</Setter>
</Style>

Related

Applying border style on certain grid cells in WPF

I want to apply the following style to certain cells of my grid:
<Border BorderBrush="#808080" BorderThickness="1,1,0,0" Grid.Column="3" >
<Border BorderBrush="#404040" BorderThickness="1,1,0,0" Width="Auto">
<Border BorderBrush="#FFFFFF" BorderThickness="0,0,1,1" Width="Auto">
<Border BorderBrush="#DCE6F4" BorderThickness="0,0,1,1" Width="Auto">
</Border>
</Border>
</Border>
</Border>
Right now i specify the cell in the first line Grid.Colum="3". However to do this for all cells I have to copy paste this like 20 times. Is there any short way to apply my style to all controls or cells of my choice (for example cell 1, , 7, 9 or label1, label 9 and label10?
I am a newbie in wpf, please be gentle :)
Assuming you want to place labels inside the grid you could create a ControlTemplate and assign it to the labels you want with Template="{StaticResource BorderLabel}":
<Grid Margin="5">
<Grid.ColumnDefinitions>
<ColumnDefinition Width="*" />
<ColumnDefinition Width="*" />
</Grid.ColumnDefinitions>
<Grid.Resources>
<ControlTemplate TargetType="Label" x:Key="BorderLabel">
<Border BorderBrush="#808080" BorderThickness="1,1,0,0" >
<Border BorderBrush="#404040" BorderThickness="1,1,0,0" Width="Auto">
<Border BorderBrush="#FFFFFF" BorderThickness="0,0,1,1" Width="Auto">
<Border BorderBrush="#DCE6F4" BorderThickness="0,0,1,1" Width="Auto">
<ContentControl Content="{TemplateBinding Content}" />
</Border>
</Border>
</Border>
</Border>
</ControlTemplate>
</Grid.Resources>
<Label Grid.Column="0" Content="MyText" />
<Label Grid.Column="1" Content="MyText" Template="{StaticResource BorderLabel}" />
</Grid>
And this is how it looks:

Styled Expander Control Template and not able to view Expanded Content on Click

I am just styling the Expander Control in WPF. I have defined only the styles in Expander Control Template, but I am not able to view the content of expander when I click on it.
I guess I have to define the expander trigers also ? but I don't know which triger and how to define it.
Also why I have to define triggers when I am just styling the expander.
<Window x:Class="ExpanderControl.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
Title="MainWindow" Height="350" Width="525">
<Window.Resources>
<Style TargetType="Expander">
<Setter Property="Template">
<Setter.Value>
<ControlTemplate TargetType="{x:Type Expander}">
<Grid>
<Grid.RowDefinitions>
<RowDefinition Height="30"></RowDefinition>
<RowDefinition Height="0" Name="contentRow"></RowDefinition>
</Grid.RowDefinitions>
<!--Expander Header-->
<Border Background="AliceBlue"
Grid.Row="0"
>
<Grid>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="*"></ColumnDefinition>
<ColumnDefinition Width="20"></ColumnDefinition>
</Grid.ColumnDefinitions>
<ContentPresenter Grid.Column="0" ContentSource="Header"
RecognizesAccessKey="True"
VerticalAlignment="Center"
HorizontalAlignment="Left"
Margin="5"
></ContentPresenter>
<ToggleButton Grid.Column="1">
<TextBlock>x</TextBlock>
</ToggleButton>
</Grid>
</Border>
<!--Expander Content-->
<Border Background="Aqua" Grid.Row="1">
<ContentPresenter Grid.Row="1"></ContentPresenter>
</Border>
</Grid>
</ControlTemplate>
</Setter.Value>
</Setter>
</Style>
</Window.Resources>
<Grid>
<Expander Header="Expander" HorizontalAlignment="Left" Margin="205,95,0,0" VerticalAlignment="Top" Width="200">
<Grid Background="#FFE5E5E5">
<Menu>
<MenuItem Header="hi"></MenuItem>
</Menu>
</Grid>
</Expander>
</Grid>
</Window>
At the moment there is no action linked to ToggleButton. You need to utilize Expander.IsExpanded property by binding it
To ToggleButton.IsChecked
To Border.Visibility via BooleanToVisibilityConverter (custom or built in)
Set content row height to Auto
This way changing ToggleButton.IsChecked will change Expander.IsExpanded which in turn will affect visibility of content Border. It will also work when you change IsExpanded property from outside.
This is working XAML
<Window.Resources>
<BooleanToVisibilityConverter x:Key="BooleanToVisibilityConverter"/>
<Style TargetType="{x:Type Expander}">
<Setter Property="Template">
<Setter.Value>
<ControlTemplate TargetType="{x:Type Expander}">
<Grid>
<Grid.RowDefinitions>
<RowDefinition Height="30"/>
<RowDefinition Height="Auto" Name="contentRow"/>
</Grid.RowDefinitions>
<!--Expander Header-->
<Border Background="AliceBlue" Grid.Row="0">
<Grid>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="*"/>
<ColumnDefinition Width="20"/>
</Grid.ColumnDefinitions>
<ContentPresenter Grid.Column="0" ContentSource="Header" RecognizesAccessKey="True" VerticalAlignment="Center" HorizontalAlignment="Left" Margin="5" />
<ToggleButton Grid.Column="1" Content="x" IsChecked="{Binding RelativeSource={RelativeSource TemplatedParent}, Path=IsExpanded}"/>
</Grid>
</Border>
<!--Expander Content-->
<Border Background="Aqua" Grid.Row="1" Visibility="{Binding RelativeSource={RelativeSource TemplatedParent}, Path=IsExpanded, Converter={StaticResource BooleanToVisibilityConverter}}">
<ContentPresenter/>
</Border>
</Grid>
</ControlTemplate>
</Setter.Value>
</Setter>
</Style>
</Window.Resources>
EDIT
If you want whole header to be able to expand/collapse your Expander you need to bring ContentPresenter for Header into Content of ToggleButton. In your case basically bring header Grid into ToggleButton.Content
<!--Expander Header-->
<ToggleButton IsChecked="{Binding RelativeSource={RelativeSource TemplatedParent}, Path=IsExpanded}" HorizontalContentAlignment="Stretch" Grid.Row="0">
<Grid>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="*"/>
<ColumnDefinition Width="20"/>
</Grid.ColumnDefinitions>
<ContentPresenter Grid.Column="0" ContentSource="Header" RecognizesAccessKey="True" VerticalAlignment="Center" HorizontalAlignment="Left" Margin="5" />
<TextBlock Grid.Column="1" Text="x" HorizontalAlignment="Center" VerticalAlignment="Center" />
</Grid>
</ToggleButton>

How to make a custom tool tip template in WPF XAML

I have a number of buttons on a form (devcomponents ButtonDropDown controls to be precise).
I want to show a tool tip for each that contains a header at the top, an image on the left and a description on the right.
The header needs to be tied to the ButtonDropDown.Header, the image to the ButtonDropDown.Image. I also then need to define the description somewhere.
I've only been using WPF for a few weeks so I've not managed to find any answers via searching, though I have studied a few.
Below is my attempt at creating a template that really doesn't work at all:
<Style TargetType="dcr:ButtonDropDown">
<Setter Property="OverridesDefaultStyle" Value="True"></Setter>
<Setter Property="Template">
<Setter.Value>
<ControlTemplate TargetType="dcr:ButtonDropDown">
<ContentControl>
<ContentControl.ToolTip>
<Grid>
<Grid.ColumnDefinitions>
<ColumnDefinition />
<ColumnDefinition />
</Grid.ColumnDefinitions>
<Grid.RowDefinitions>
<RowDefinition />
<RowDefinition />
</Grid.RowDefinitions>
<Label Grid.Row="0" Grid.Column="0" Grid.ColumnSpan="2"
Content="{TemplateBinding Header}" FontWeight="Bold" />
<Viewbox Grid.Row="1" Grid.Column="0" Width="64" Height="32" Margin="3">
<ContentControl Content="{TemplateBinding Image}" />
</Viewbox>
<Label Grid.Row="1" Grid.Column="1"
Content="{TemplateBinding ToolTip.Content}" />
</Grid>
</ContentControl.ToolTip>
</ContentControl>
</ControlTemplate>
</Setter.Value>
</Setter>
</Style>
I then define a button as follows:
<dcr:ButtonDropDown Header="-X" Command="{Binding MoveCommand}" CommandParameter="xMinus"
ImagePosition="Top" IsEnabled="{Binding UserConfiguration.Move.Visible}"
ToolTip="move x axis down">
<dcr:ButtonDropDown.Image>
<Viewbox Width="32" Height="32" Margin="3">
<ContentControl Content="{StaticResource minusXImage}" />
</Viewbox>
</dcr:ButtonDropDown.Image>
</dcr:ButtonDropDown>
Please could someone give me an idea how to go about this?
I've gone some way to answering this question.
The following style is defined for ToolTip:
<Style TargetType="ToolTip">
<Setter Property="OverridesDefaultStyle" Value="True" />
<Setter Property="Template">
<Setter.Value>
<ControlTemplate TargetType="ToolTip">
<Border Background="GhostWhite" BorderBrush="Gainsboro" BorderThickness="1">
<Grid Background="White">
<Grid.ColumnDefinitions>
<ColumnDefinition />
<ColumnDefinition />
</Grid.ColumnDefinitions>
<Grid.RowDefinitions>
<RowDefinition />
<RowDefinition />
</Grid.RowDefinitions>
<Label Grid.Row="0" Grid.Column="0" Grid.ColumnSpan="2"
Content="{Binding RelativeSource={RelativeSource Mode=FindAncestor, AncestorType={x:Type ToolTip}},
Path=PlacementTarget.(dcr:ButtonDropDown.Header)}"
FontWeight="Bold" />
<Viewbox Grid.Row="1" Grid.Column="0" Width="64" Height="32" Margin="3">
<ContentControl Content="{Binding RelativeSource={RelativeSource Mode=FindAncestor, AncestorType={x:Type ToolTip}},
Path=PlacementTarget.(dcr:ButtonDropDown.Image)}" />
</Viewbox>
<Label Grid.Row="1" Grid.Column="1"
Content="{Binding RelativeSource={RelativeSource Mode=FindAncestor, AncestorType={x:Type ToolTip}},
Path=PlacementTarget.(dcr:ButtonDropDown.ToolTip)}" />
</Grid>
</Border>
</ControlTemplate>
</Setter.Value>
</Setter>
</Style>
I then define a button as above and the ToolTip text and header appear in the ToolTipas required.
The key is the binding:
Content="{Binding RelativeSource={RelativeSource Mode=FindAncestor, AncestorType={x:Type ToolTip}},
Path=PlacementTarget.(dcr:ButtonDropDown.Header)}"
Which finds the Tooltip as an ancestor of Label and casts its PlacementTarget into a ButtonDropDown
What doesn't work is the image. This appears in the ToolTip but is removed from the button.
This will also break any other controls' tooltips if they are not ButtonDropDown controls.
I'm beginning to think I'll need to create some custom controls that contain the information I require for the ToolTip for each control.

Template initialisation causes stack overflow

I have a DataModel like so :
public class Node
{
public List<Node> Children { get; private set; }
public string Name { get; private set; }
public Node(string _name, params Node[] _children)
{
Name = _name;
Children = new List<Node>(_children);
}
}
I now want to define a View for this model (I'm not using a TreeView for reasons out of scope of this question), that allows people to use it in one of two ways.
Example 1 : Default layout, the content should auto Expand
<NodeView DataContext="{Binding Root}"/>
The above should expand the node tree in the same way a treeview would, i.e recursively going down the Node and its children creating new views for each one.
Example 2 : Allow people to manually set the content
<NodeView DataContext="{Binding Root}">
<StackPanel>
<TextBlock Text="{Binding Children[0].Name"/>
<TextBlock Text="{Binding Children[1].Name"/>
<TextBlock Text="{Binding Children[2].Name"/>
</StackPanel>
</NodeView>
The above now won't expand, but only show the first three child nodes.
I thought I could do this with the following Custom Control, but I get a stackoverflow exception, what am I doing wrong?
<Style TargetType="{x:Type l:NodeView}">
<Setter Property="Content">
<Setter.Value>
<GroupBox>
<ItemsControl ItemsSource="{Binding Children}">
<ItemsControl.ItemTemplate>
<DataTemplate>
<l:NodeView />
</DataTemplate>
</ItemsControl.ItemTemplate>
</ItemsControl>
</GroupBox>
</Setter.Value>
</Setter>
<Setter Property="Template">
<Setter.Value>
<ControlTemplate TargetType="{x:Type l:NodeView}">
<Grid>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="18" />
<ColumnDefinition Width="*" />
</Grid.ColumnDefinitions>
<Grid.RowDefinitions>
<RowDefinition Height="Auto" />
<RowDefinition Height="Auto" />
</Grid.RowDefinitions>
<TextBlock Text="{Binding Name}"
Grid.Column="1" />
<ContentPresenter Grid.Row="1"
Grid.Column="1" />
</Grid>
</ControlTemplate>
</Setter.Value>
</Setter>
</Style>
Here is a link to the project if anyone finds that easier to use
https://www.dropbox.com/s/j32mm7gave17v7j/NodeView.zip
The problem comes basically from your Style. You set the Content. However you should specify the Template and the ItemTemplate.
The first one will descripe how NodeView is visuallised: a textblock with a list of children.
The second will describe how your children nodes are visualised: NodeView control.
<Style TargetType="{x:Type l:NodeView}">
<Setter Property="Template">
<Setter.Value>
<ControlTemplate TargetType="{x:Type l:NodeView}">
<Grid>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="18" />
<ColumnDefinition Width="*" />
</Grid.ColumnDefinitions>
<Grid.RowDefinitions>
<RowDefinition Height="Auto" />
<RowDefinition Height="Auto" />
</Grid.RowDefinitions>
<TextBlock Text="{Binding Name}"
Grid.Column="1" />
<ItemsControl Grid.Row="1" Grid.Column="1"
ItemsSource="{Binding Children}">
<ItemsControl.ItemTemplate>
<DataTemplate>
<l:NodeView Content="{Binding}"/>
</DataTemplate>
</ItemsControl.ItemTemplate>
</ItemsControl>
</Grid>
</ControlTemplate>
</Setter.Value>
</Setter>
</Style>
You will notice that it looks a bit strange with no ContentPresenter. But that's a result of a non appropriate base class of the NodeView. Content control is usually used for a single content but here you have a content with children items.
By the way it is not the only way you can write it. It is possible to keep the default template for the Template and to put the entire thing in to the ContentTemplate of the NodeView.
<Style TargetType="{x:Type l:NodeView}">
<Setter Property="ContentTemplate">
<Setter.Value>
<DataTemplate TargetType="{x:Type l:NodeView}">
<Grid>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="18" />
<ColumnDefinition Width="*" />
</Grid.ColumnDefinitions>
<Grid.RowDefinitions>
<RowDefinition Height="Auto" />
<RowDefinition Height="Auto" />
</Grid.RowDefinitions>
<TextBlock Text="{Binding Name}"
Grid.Column="1" />
<ItemsControl Grid.Row="1" Grid.Column="1"
ItemsSource="{Binding Children}">
<ItemsControl.ItemTemplate>
<DataTemplate>
<l:NodeView Content="{Binding}"/>
</DataTemplate>
</ItemsControl.ItemTemplate>
</ItemsControl>
</Grid>
</DataTemplate>
</Setter.Value>
</Setter>
</Style>
In the main window you should use Content property and not the DataContext.
PS: I wrote the code without testing so not sure it works out of the box.
The problem seems to be, that you reference the control you're templating, in the style of the control itself.
So when WPF tries to create the style, it tries to initialize it, whereby it has to create the datatemplate, so it has to initialize the style... and there you have your loop.
The only way around this, that I found, was to set your default content by code, like this:
public NodeView()
{
var dt = FindResource("DefaultNodeContent") as DataTemplate;
var lb = new ItemsControl();
lb.ItemTemplate = dt;
var binding = new Binding("Children");
lb.SetBinding(ItemsControl.ItemsSourceProperty, binding);
var gb = new GroupBox();
gb.Content = lb;
this.Content = gb;
}
For this, you'd have to add a resource dictionary to you app
<ResourceDictionary xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:l="clr-namespace:WpfApplication5">
<DataTemplate x:Key="DefaultNodeContent">
<l:NodeView DataContext="{Binding}" />
</DataTemplate>
</ResourceDictionary>
And then your style would change to:
<Style TargetType="{x:Type l:NodeView}">
<Setter Property="Template">
<Setter.Value>
<ControlTemplate TargetType="{x:Type l:NodeView}">
<Grid>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="18" />
<ColumnDefinition Width="*" />
</Grid.ColumnDefinitions>
<Grid.RowDefinitions>
<RowDefinition Height="Auto" />
<RowDefinition Height="Auto" />
</Grid.RowDefinitions>
<TextBlock Text="{Binding Name}"
Grid.Column="1" />
<ContentPresenter Grid.Row="1"
Grid.Column="1" />
</Grid>
</ControlTemplate>
</Setter.Value>
</Setter>
</Style>
Hope this helps.

How can I replicate 'float: right' in XAML?

I want to create a ListBoxItem with a layout that includes two areas, one 'float: left' and one 'float: right', with the item overall filling the entire width allocated to the ListBox and the ListBox filling its container (ie. expanding to fill the available space).
How can I achieve this in XAML?
thanks
For the "item overall filling the entire width allocated to the ListBox" you need a style like this:
<Style TargetType="ListBoxItem">
<Setter Property="HorizontalAlignment" Value="Stretch" />
<Setter Property="HorizontalContentAlignment" Value="Stretch" />
</Style>
and optionally disable horizontal scrolling for the listbox:
<ListBox ScrollViewer.HorizontalScrollBarVisibility="Disabled" .. >
for the DataTemplate's root panel you can either use a dockpanel:
<DockPanel>
<SomeControlLeft DockPanel.Dock="Left" Margin="0 0 5 0" />
<SomeControlRight DockPanel.Dock="Right" Margin="5 0 0 0" />
<SomeControlFill />
</DockPanel>
or a grid:
<Grid>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="Auto" />
<ColumnDefinition Width="5" />
<ColumnDefinition Width="*" />
<ColumnDefinition Width="5" />
<ColumnDefinition Width="Auto" />
</Grid.ColumnDefinitions>
<SomeControlLeft Grid.Column="0" />
<SomeControlRight Grid.Column="4" />
<SomeControlFill Grid.Column="2" />
</Grid>
This is the way I would do it:
<Style x:Key="ListBoxItemStyle" TargetType="ListBoxItem">
<Setter Property="Template">
<Setter.Value>
<ControlTemplate TargetType="ListBoxItem">
<Grid Background="Red">
<Grid.ColumnDefinitions>
<ColumnDefinition Width="*"/>
<ColumnDefinition Width="*"/>
</Grid.ColumnDefinitions>
<TextBlock Text="{TemplateBinding Content}"/>
<TextBlock Text="{TemplateBinding Tag}" Grid.Column="1"/>
</Grid>
</ControlTemplate>
</Setter.Value>
</Setter>
</Style>
</UserControl.Resources>
<Grid x:Name="LayoutRoot" Background="White">
<ListBox ItemContainerStyle="{StaticResource ListBoxItemStyle}">
<ListBoxItem Content="Lorem" Tag="Ipsum"/>
<ListBoxItem Content="Hello" Tag="World"/>
<ListBoxItem Content="Be" Tag="Happy"/>
</ListBox>
</Grid>
Use a Grid to position or dock elements to various parts of the form/panel.

Categories

Resources