Making Templates generic - c#

I have a WPF DataGrid that has some cells (not columns) that need to switch from readonly to editable based on some value in the viewmodel. The only way I could find to do this (after much searching) is something like this:
<UserControl.Resources>
<DataTemplate x:Key="CellTemplate">
<TextBlock
Text="{Binding MyValue}"
Style="{StaticResource CellTextBlockNumber}" />
</DataTemplate>
<DataTemplate x:Key="ReadonlyCellTemplate">
<TextBlock
Text="{Binding MyValue}"
Style="{StaticResource CellTextBlockNumberReadonly}" />
</DataTemplate>
<DataTemplate x:Key="EditableCellTemplate">
<TextBox
Text="{Binding MyValue}"
Style="{StaticResource CellTextBoxNumber}"
FocusManager.FocusedElement="{Binding RelativeSource={RelativeSource Self}}" />
</DataTemplate>
</UserControl.Resources>
The various styles just do things like set background colors etc.
Then in the DataGrid.Columns sections:
<DataGridTemplateColumn>
<DataGridTemplateColumn.CellTemplate>
<DataTemplate>
<ContentPresenter x:Name="Presenter" Content="{Binding}" ContentTemplate="{StaticResource CellTemplate}" />
<DataTemplate.Triggers>
<DataTrigger Binding="{Binding EnableCells}" Value="False">
<Setter TargetName="Presenter" Property="ContentTemplate" Value="{StaticResource ReadonlyCellTemplate}" />
</DataTrigger>
</DataTemplate.Triggers>
</DataTemplate>
</DataGridTemplateColumn.CellTemplate>
<DataGridTemplateColumn.CellEditingTemplate>
<DataTemplate>
<ContentPresenter x:Name="Presenter" Content="{Binding}" ContentTemplate="{StaticResource EditableCellTemplate}" />
<DataTemplate.Triggers>
<DataTrigger Binding="{Binding EnableCells}" Value="False">
<Setter TargetName="Presenter" Property="ContentTemplate" Value="{StaticResource ReadonlyCellTemplate}" />
</DataTrigger>
</DataTemplate.Triggers>
</DataTemplate>
</DataGridTemplateColumn.CellEditingTemplate>
</DataGridTemplateColumn>
So basically the ContentPresenter's ContentTemplate is being swapped out based on a DataTrigger.
This works fine for a single column, but I have about 10 columns that need this ability (at the cell level), and I really don't want to create 3 templates per column if I don't have to. Is there a way to modify this so the templates bind to a "generic" value (instead of MyValue) that I can specify at the DataGridColumn level?

Use the templates that you already have, only change is that in the text block the text should be Text="{Binding }".
Then in content control (in the cell/edit template), have the content bound like this:
Content="{Binding MyValue}"
So now you'll have only three templates and at each column you can specify a different binding for the content of the content control.

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.

Binding and Styling a DataGridColumnHeader

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>

How can I know if item is selected inside the ItemTemplate?

I have a ListBox that use my custom ItemTemplate. I want to set Visibility property in my TextBlock (inside my template) depending on selected item. I think of doing it using triggers. But how can I know inside my template if current item is selected or not?
<DataTemplate x:Key="myTemplate">
<StackPanel Orientation="Horizontal">
<Image Tag="{Binding priority}" Loaded="SetIconPriority"/>
<Image Tag="{Binding alarm}" Loaded="SetIconAlarm"/>
<!-- I want this TextBlock to be visible only when item is selected -->
<TextBlock Text="{Binding description}"/>
</StackPanel>
</DataTemplate>
edit:
It works, thanks! Code:
<TextBlock Grid.Column="2" Grid.Row="1" Text="{Binding opis}">
<TextBlock.Style>
<Style TargetType="TextBlock">
<Style.Triggers>
<DataTrigger Binding="{Binding IsSelected, RelativeSource={RelativeSource AncestorType=ListBoxItem}}" Value="False">
<Setter Property="Visibility" Value="Collapsed"/>
</DataTrigger>
</Style.Triggers>
</Style>
</TextBlock.Style>
</TextBlock>
Using a RelativeSource binding with AncestorType being ListBoxItem.
<DataTrigger Binding="{Binding IsSelected,
RelativeSource={RelativeSource AncestorType=ListBoxItem}}"
Value="True">
(May want to reverse the logic and Collapse on False instead, avoids the default value Setter)

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.

WPF: Change background on some combobox items

I'm trying to change the background of certain items in a combobox that meet a condition
<ComboBox ItemsSource="{Binding Path=Model.Names, Mode=OneWay}" SelectedValue="{Binding Path=SelectedCompanyName}" DisplayMemberPath="Alias" />
The thing is that "Alias" is saved in two different places (in company and in order) and if they dont match we want to highlight this.
I want to do something like this:
<Style>...
<DataTrigger Binding="{Binding Path=isMismatch}" Value="True>
<Setter Property="Background" Value="Red" />...
Any help is appreciated.
You need to create custom data template like this:
<ComboBox Width="300" Height="30" ItemsSource="{Binding Path=Model.Names, Mode=OneWay}">
<ComboBox.ItemTemplate>
<DataTemplate>
<Grid x:Name="templateGrid">
<TextBox Text="{Binding Name}" />
</Grid>
<DataTemplate.Triggers>
<DataTrigger Binding="{Binding isMismatch}" Value="True">
<Setter TargetName="templateGrid"
Property="Background" Value="Red" />
</DataTrigger>
</DataTemplate.Triggers>
</DataTemplate>
</ComboBox.ItemTemplate>
</ComboBox>
If you want to highlight the selection based on the values of two properties, I think you could use a MultiValueConverter, together with a MultiBinding.

Categories