Binding and Styling a DataGridColumnHeader - c#

I have a DataGridColumn like this:
<DataGridTextColumn
Binding="{Binding
Path=Name,
UpdateSourceTrigger=PropertyChanged}"
HeaderStyle="{StaticResource HeaderWrapped}">
<DataGridTextColumn.HeaderTemplate>
<DataTemplate>
<TextBlock
Text="{Binding
Path=DataContext.Name,
RelativeSource={RelativeSource AncestorType={x:Type UserControl}}}" />
</DataTemplate>
</DataGridTextColumn.HeaderTemplate>
</DataGridTextColumn>
The HeaderWrapped style is in a ResourceDictionary that is imported in the control. It looks like this:
<Style x:Key="HeaderWrapped" TargetType="DataGridColumnHeader">
<Setter Property="ContentTemplate">
<Setter.Value>
<DataTemplate>
<TextBlock TextWrapping="Wrap" Text="{TemplateBinding Content}" />
</DataTemplate>
</Setter.Value>
</Setter>
</Style>
When I run the app, the header Text is bound properly, but the TextWrapping is not set. I'm guessing that the DataGridTextColumn.HeaderTemplate overwrites the template from my ResourceDictionary. Is there a way that I can keep the styling for the headers in a ResourceDictionary but still bind the Text property of the header?

What you're trying to do is basically setting a Style for the Header at first, and then tell it to forget about it and use a brand new template for the header. you can't set both DataGridTextColumn.HeaderStyle.ContentTemplate and DataGridTextColumn.HeaderTemplate
However I can think of one workarounds to this problem:
<DataGridTextColumn
Binding="{Binding
Path=Name,
UpdateSourceTrigger=PropertyChanged}"
Tag="{Binding
Path=DataContext.Name,
RelativeSource={RelativeSource AncestorType={x:Type UserControl}}}"
HeaderStyle="{StaticResource HeaderWrapped}">
</DataGridTextColumn>
<Style x:Key="HeaderWrapped" TargetType="DataGridColumnHeader">
<Setter Property="ContentTemplate">
<Setter.Value>
<DataTemplate>
<TextBlock TextWrapping="Wrap" Text="{TemplateBinding Tag}" />
</DataTemplate>
</Setter.Value>
</Setter>
</Style>

Related

ToolTip to show validation errors works for TextBlock bound to POCO but not Property within POCO. Why?

In my WPF MVVM project I use INotifyDataErrorinfo to handle validation within a DataGrid. I can successfully style error cells in my "Operator" column thus:
<DataGridTemplateColumn Header="Operator" Width="140">
<DataGridTemplateColumn.CellTemplate>
<DataTemplate>
<StackPanel>
<TextBlock Text="{Binding Operator}">
<TextBlock.Style>
<Style TargetType="TextBlock">
<Style.Triggers>
<DataTrigger Binding="{Binding OperatorId, Converter={StaticResource IsOperatorIdNullConverter}}" Value="False">
<Setter Property="FontWeight" Value="Bold"/>
</DataTrigger>
<Trigger Property="Validation.HasError" Value="true">
<Setter Property="ToolTip">
<Setter.Value>
<ToolTip DataContext="{Binding RelativeSource={RelativeSource Self}, Path=PlacementTarget}">
<ItemsControl ItemsSource="{Binding Path=(Validation.Errors)}" DisplayMemberPath="ErrorContent"/>
</ToolTip>
</Setter.Value>
</Setter>
<Setter Property="Background" Value="Salmon"/>
</Trigger>
</Style.Triggers>
</Style>
</TextBlock.Style>
</TextBlock>
</StackPanel>
</DataTemplate>
</DataGridTemplateColumn.CellTemplate>
<DataGridTemplateColumn.CellEditingTemplate>
<DataTemplate>
<Grid>
<controls:AutoCompleteBox Text="{Binding Operator, UpdateSourceTrigger=LostFocus, Mode=TwoWay}"
ItemsSource="{Binding Path=Data.OperatorNames, Source={StaticResource proxy}}"
IsTextCompletionEnabled="True"
FilterMode="Contains"
MinimumPrefixLength="3"/>
</Grid>
</DataTemplate>
</DataGridTemplateColumn.CellEditingTemplate>
In my "OperatorType" column however this same technique doesn't work. Errors are being detected and the system default error styling is shown but my custom styling isn't. The code is:
<DataGridTemplateColumn Header="Operator type" Width="140">
<DataGridTemplateColumn.CellTemplate>
<DataTemplate>
<TextBlock Text="{Binding OperatorType.OperatorTypeName}">
<TextBlock.Style>
<Style TargetType="TextBlock">
<Style.Triggers>
<Trigger Property="Validation.HasError" Value="true">
<Setter Property="ToolTip">
<Setter.Value>
<ToolTip DataContext="{Binding RelativeSource={RelativeSource Self}, Path=PlacementTarget}">
<ItemsControl ItemsSource="{Binding Path=(Validation.Errors)}" DisplayMemberPath="ErrorContent"/>
</ToolTip>
</Setter.Value>
</Setter>
<Setter Property="Background" Value="Salmon"/>
</Trigger>
</Style.Triggers>
</Style>
</TextBlock.Style>
</TextBlock>
</DataTemplate>
</DataGridTemplateColumn.CellTemplate>
<DataGridTemplateColumn.CellEditingTemplate>
<DataTemplate>
<Grid>
<controls:AutoCompleteBox ItemsSource="{Binding Path=Data.OperatorTypeNames, Source={StaticResource proxy}}"
ItemTemplate="{StaticResource AutoCompleteBoxItemOperatorTypeTemplate}"
SelectedItem="{Binding OperatorType, Mode=TwoWay, UpdateSourceTrigger=LostFocus}"
ValueMemberPath="OperatorTypeName"
IsTextCompletionEnabled="True"
FilterMode="Contains"
MinimumPrefixLength="3"/>
</Grid>
</DataTemplate>
</DataGridTemplateColumn.CellEditingTemplate>
</DataGridTemplateColumn>
The only differences as far as I can see are that:
The Text in "Operator" is using bound to a POCO (Operator) while for "OperatorType" it is bound to a POCO property (OperatorType.OperatorTypeName)
The AutoCompleteBox declarations are slightly different
I've tried numerous settings for the ToolTip DataContext but nothing seems to work.
Question
What do I need to change to get the "OperatorType" customised error styling to work?
Well, it was a bit of a journey, but the solution was to set a DataContext on the TextBlock:
<TextBlock DataContext="{Binding OperatorType}" Text="{Binding OperatorTypeName}">
This causes the Trigger to be pointed at the OperatorType POCO whereas the Text in the box is taken from OperatorType.OperatorTypeName.

how to show only selected item from treeview in wpf

I am working with a Wpf/XAML based application which has DataGrid with one of the DataGridColumn containing the TreeView control to let user select the item required.
<DataGrid.Columns>
<DataGridTextColumn Header="SerialNumber" Width="*" Binding="{Binding SerialNumber}" />
<DataGridTemplateColumn Header="Field" Width="*">
<DataGridTemplateColumn.CellEditingTemplate>
<DataTemplate>
<uControls:FieldTreeViewControl />
</DataTemplate>
</DataGridTemplateColumn.CellEditingTemplate>
<DataGridTemplateColumn.CellTemplate>
<DataTemplate>
<TextBlock Text="{Binding CurrentField.FieldName,Mode=TwoWay}"/>
</DataTemplate>
</DataGridTemplateColumn.CellTemplate>
</DataGridTemplateColumn>
Field column in above code is the one which displays treeviewcontrol in the DataGrid cell by referencing the FieldTreeViewControl and it works perfectly. The xaml code of FieldTreeViewControl is :
<UserControl>
.......
<Grid>
<TreeView x:Name="MyUITreeView" ItemsSource="{Binding Fields}">
<TreeView.Resources>
<Style TargetType="{x:Type TreeViewItem}">
<Setter Property="IsExpanded" Value="{Binding IsNodeExpanded, Mode=TwoWay}" />
<Setter Property="IsSelected" Value="{Binding IsNodeSelected, Mode=TwoWay}" />
</Style>
<DataTemplate x:Key="NormalTemplate">
<StackPanel Orientation="Horizontal">
<!--<TextBlock Text="-" Margin="3"/>-->
<TextBlock Text="{Binding FieldName}" Margin="3"/>
<StackPanel.ContextMenu>
<ContextMenu Name="myChildMenu" DataContext="{Binding PlacementTarget,RelativeSource={RelativeSource Self}}">
<MenuItem Header="Add Field" Command="{Binding DataContext.AddFieldCommand}" CommandParameter="{Binding}">
</MenuItem>
</ContextMenu>
</StackPanel.ContextMenu>
</StackPanel>
</DataTemplate>
<!--<DataTemplate x:Key="EditTemplate">
<TextBox Text="{Binding FieldName}"/>
</DataTemplate>-->
</TreeView.Resources>
<TreeView.ItemTemplate>
<HierarchicalDataTemplate ItemsSource="{Binding Children}">
<ContentPresenter Content="{Binding}">
<ContentPresenter.Style>
<Style TargetType="{x:Type ContentPresenter}">
<Setter Property="ContentTemplate" Value="{StaticResource NormalTemplate}"/>
<Style.Triggers>
<DataTrigger
Binding="{Binding IsNodeSelected,
RelativeSource={RelativeSource
FindAncestor,
AncestorType={x:Type TreeViewItem}}}"
Value="True">
</DataTrigger>
</Style.Triggers>
</Style>
</ContentPresenter.Style>
</ContentPresenter>
</HierarchicalDataTemplate>
</TreeView.ItemTemplate>
</TreeView>
</Grid>
Now when user completes selecting an item from treeview I want to collapse treeview to show only that selected item. Later when user wants to change his selection , then the treeview should be available again by clicking on that column/cell. Is that possible to do this in wpf/XAML ? I tried to follow the link but not able to implement it for my scenario.

WPF Datgrid binding text with tooltip to to different properties

I want to show the body of an email in a tooltip and a shortened version in the column
<DataGridTextColumn Binding="{Binding Body}"
Header="{x:Static t:Resource.GridBody}" Width="100">
<DataGridTextColumn.ElementStyle>
<Style TargetType="TextBlock">
<Setter Property="TextTrimming" Value="CharacterEllipsis"/>
<Setter Property="ToolTip"
Value="{Binding Text, RelativeSource= {RelativeSource Self}}" />
</Style>
</DataGridTextColumn.ElementStyle>
</DataGridTextColumn>
I get the ellipsis on each character, I have a property that has the first three lines and I would like to bind that to the column and the body to the ToolTip
Instead of using style I used a DataGridTemplateColumn instead of a DataGridTextColumn
<DataGridTemplateColumn Header="{x:Static t:Resource.GridBody}" Width="150">
<DataGridTemplateColumn.CellTemplate>
<DataTemplate>
<TextBlock Text="{Binding ShortBody}" TextTrimming="CharacterEllipsis" ToolTip="{Binding Body}" />
</DataTemplate>
</DataGridTemplateColumn.CellTemplate>
</DataGridTemplateColumn>

Adding a context menu only to the Expander header

I'm trying to compose a data grid (in an MVVM 4.5 project) that will show my data grouped by a specific property (by use of expanders). This all works but I'd like to add a context menu with the options "Expand All" and "Collapse All" which will collapse/expand all the groups. The event handlers for this menu item click events are being handled in the window's code-behind.
The problem is my context menu is applied to the expander and thus inherited by all its children which includes the <ItemsPresenter/> and this all rows/cells.
I want to only apply the context menu to the grouped header itself. This is achievable if it's applied to the innards (like the StackPanel in the example below) but in that case, the context menu isn't accessible for the entire Header line, only on the StackPanel contents/text.
I'm planning on using a different context menu for the items themselves (add/edit etc) and have a Collapse/Expand context menu only apply to the group header. Is this achievable?
<DataGrid Name="dgData" ItemsSource="{Binding MyItems}" AutoGenerateColumns="False" CanUserAddRows="False" CanUserDeleteRows="False">
<DataGrid.Resources>
<!--GroupHeader Text-->
<Style x:Key="gridGroupTextStyle" TargetType="TextBlock">
<Setter Property="FontSize" Value="12"/>
<Setter Property="FontWeight" Value="Bold"/>
</Style>
<!--GroupHeader ContextMenu-->
<ContextMenu x:Key="cm_columnHeaderMenu">
<MenuItem Name="mi_ExpandAll" Header="Expand groups"/>
<MenuItem Name="mi_CollapseAll" Header="Collapse groups"/>
</ContextMenu>
<!--Grouping style-->
<Style x:Key="filesGroupHeaderStyle" TargetType="{x:Type GroupItem}">
<Setter Property="Template">
<Setter.Value>
<ControlTemplate TargetType="{x:Type GroupItem}">
<Expander x:Name="exp" IsExpanded="true">
<Expander.Style>
<Style TargetType="{x:Type Expander}">
<Setter Property="ContextMenu" Value="{StaticResource cm_columnHeaderMenu}"/>
</Style>
</Expander.Style>
<Expander.Header>
<StackPanel Orientation="Horizontal">
<TextBlock Text="{Binding Name}" Style="{StaticResource gridGroupTextStyle}"/>
<TextBlock Text=" (" Style="{StaticResource gridGroupTextStyle}"/>
<TextBlock Text="{Binding ItemCount}" Style="{StaticResource gridGroupTextStyle}"/>
<TextBlock Text=")" Style="{StaticResource gridGroupTextStyle}"/>
</StackPanel>
</Expander.Header>
<ItemsPresenter/>
</Expander>
</ControlTemplate>
</Setter.Value>
</Setter>
</Style>
</DataGrid.Resources>
<DataGrid.GroupStyle>
<GroupStyle ContainerStyle="{StaticResource filesGroupHeaderStyle}">
<GroupStyle.Panel>
<ItemsPanelTemplate>
<DataGridRowsPresenter/>
</ItemsPanelTemplate>
</GroupStyle.Panel>
</GroupStyle>
</DataGrid.GroupStyle>
<DataGrid.Columns>
<DataGridTextColumn Header="Column 1" Binding="{Binding Prop1}" IsReadOnly="True"/>
<DataGridTextColumn Header="Column 2" Binding="{Binding Prop2}" IsReadOnly="True"/>
<DataGridTextColumn Header="Column 3" Binding="{Binding Prop3}" IsReadOnly="True"/>
</DataGrid.Columns>
</DataGrid>
If i understand you right, you want to click anywhere in your StackPanel and get the ContextMenu to work like this:
If yes, here we go in code:
<Expander Grid.Row="1">
<Expander.Style>
<Style TargetType="{x:Type Expander}">
<Setter Property="ContextMenu" Value="{x:Null}"/>
</Style>
</Expander.Style>
<Expander.Header>
<StackPanel Orientation="Horizontal" Width="{Binding Path=ActualWidth, RelativeSource={RelativeSource FindAncestor, AncestorType={x:Type Expander}}}" Background="Pink" HorizontalAlignment="Stretch">
<StackPanel.Style>
<Style TargetType="StackPanel" >
<Setter Property="ContextMenu" Value="{StaticResource cm_columnHeaderMenu}"></Setter>
</Style>
</StackPanel.Style>
<TextBlock Text="Hello" />
<TextBlock Text=" (" />
<TextBlock Text="world" />
<TextBlock Text=")" />
<TextBlock Text="{Binding Path=ActualWidth, RelativeSource={RelativeSource FindAncestor, AncestorType={x:Type Expander}}}" />
</StackPanel>
</Expander.Header>
<TextBlock Text="Dummy"></TextBlock>
</Expander>
What did i do?
First, bind your Stackpanel-Width to actual width of Expander
Second, make the Stackpanel stretch.
Hope this helps

How to change the data template in runtime?

In my application, I have a CheckBox in it, I would like when it's checked, show one DataTemplate and when it's unchecked, show another one.
Here is the snippet with the two templates
<DataGrid x:Name="dataGrid" LoadingRow="dataGrid_LoadingRow_1" ItemsSource="{Binding Item3}"
<DataGrid.RowHeaderTemplate>
<DataTemplate>
<TextBlock Text="{Binding StudentId}"/>
</DataTemplate>
<DataTemplate>
<TextBlock Text="{Binding FullName}"/>
</DataTemplate>
</DataGrid.RowHeaderTemplate>
</DataGrid>
I'm not sure how to implement it, but I supposed that I need the interface INotifyPropertyChanged in my User control to fire or just determine when has changed.
You can do it only via triggers. If the above thing is your requirement. You can do it simply via triggers. I tried and it worked for me.
<Window.Resources>
<ControlTemplate x:Key="MyRowHeaderTemplate">
<TextBlock x:Name="RowHeaderTxt"
Text="{Binding StudentId, RelativeSource={RelativeSource AncestorType={x:Type Window}}}"/>
<ControlTemplate.Triggers>
<DataTrigger Binding="{Binding Path=IsChecked, ElementName=MyCheckBox}"
Value="True">
<Setter TargetName="RowHeaderTxt" Property="Text"
Value="{Binding FullName, RelativeSource={RelativeSource AncestorType={x:Type Window}}}"/>
</DataTrigger>
</ControlTemplate.Triggers>
</ControlTemplate>
</Window.Resources>
<StackPanel>
<CheckBox x:Name="MyCheckBox"/>
<DataGrid ItemsSource="{Binding Item3}" AutoGenerateColumns="True">
<DataGrid.RowHeaderTemplate>
<DataTemplate>
<ContentControl Template="{StaticResource MyRowHeaderTemplate}"/>
</DataTemplate>
</DataGrid.RowHeaderTemplate>
</DataGrid>
</StackPanel>
Try this.

Categories