Toggle two different elements in DataTemplate using Style Triggers - wpf

I have an object in ViewModel whose properties are displayed by a datatemplate. The screen also has a button toggling the IsEditing flag in ViewModel, which should make the object properties editable, like the following:
Name should change from TextBlock to TextBox;
Color should change from colored rectangle to ComboBox with color options;
Category should change from TextBlock to ComboBox;
I know how to implement this with two completely independent DataTemplates, using a Style and a DataTrigger to toggle between them:
<ContentControl Content="{Binding FancyObject}">
<ContentControl.Style>
<Style TargetType="ContentControl">
<Setter Property="ContentTemplate" Value="{StaticResource DisplayTemplate}"/>
<Style.Triggers>
<DataTrigger Binding="{Binding DataContext.IsEditing, ElementName=UserControl}" Value="True">
<Setter Property="ContentTemplate" Value="{StaticResource EditTemplate}"/>
</DataTrigger>
</Style.Triggers>
</Style>
</ContentControl.Style>
</ContentControl>
And currently the DisplayTemplate is like this:
<DataTemplate x:Key="DisplayTemplate" DataType="my:FancyObject">
<Border>
<DockPanel DataContext="{Binding Metadata}">
<Border>
<TextBlock Text="{Binding Name}"/>
</Border>
<DataGrid
AutoGenerateColumns="False"
ItemsSource="{Binding FancyObjectCollection}">
<DataGrid.Columns>
<!-- Text and Template columns -->
</DataGrid.Columns>
</DataGrid>
</DockPanel>
</Border>
</DataTemplate>
The problem is: using two independent but similar templates would mean a duplication of layout, since only some fields would change, but the overall structure is the same.
Another option I imagine is to use a single template defined inside the Style, and use the Trigger to change the fields individually, but I don't know how to do it, or even if it is possible at all.

You can use one template.
In the template add both TextBlock and TextBox, same for all your other controls on the original template.
Bind the controls visibility to bool to visibility converter. (Or use triggers) Only one set of your control will be seen each time (based on IsEditing flag)

The ControlTemplate is only used upon generating the UI Elements. If you change the template AFTER generating the items, the generated items will not change.
You can also not use a trigger to change a TextBox to a TextBlock and vice versa.
Your only option is indeed to mirror the layout twice and hide/display it via the data bound property.

Related

Address a property of parent ListView from a listview item

Desired: set border properties of a listview depending on conditions in displayed items. For example:
Set the border of an entire ListView red if any one of its (string) items begins with "S". So the problem is how to address a parent listview from its items (within xaml preferably)
Something like
<ListView Name="lv" Grid.Row="2" ItemsSource="{Binding TheItemList}" Height="100" BorderBrush="Black">
<ListView.ItemTemplate>
<DataTemplate>
<TextBlock Text="{Binding }">
<TextBlock.Style>
<Style>
<Style.Triggers>
<DataTrigger Binding="{Binding Converter={StaticResource StringToBoolean}}
Value="True">
<Setter ???? Value="Red"/>
</DataTrigger>
</Style.Triggers>
</Style>
</TextBlock.Style>
</TextBlock>
</DataTemplate>
</ListView.ItemTemplate>
</ListView>
(where the converter is the obvious thing) does not work: I can' use
TargetName="lv" in the ???? part of the setter, that's outside the scope. Neither can I use <Style TargetType="ListView"> in the style declaration. I could of course go up and back through the view model, but how does one do this in .xaml?
Must be having a bad day, this seems like an obvious thing. My googling hasn't yielded much unfortunately.
A setter in a Style can only set a property of the element to which the Style is applied, i.e. a Style for a TextBlock cannot set the property of the parent ListView.
You could "move" the Style to the ListView and implement the converter to return true if ListBox.Items.OfType<string>().Any(x => x?.StartsWith("s") == true) or something similar.
But a Style for a child element cannot set a property of the parent element.

How to "inject" style in a DataTemplate

I am trying to separate the DataTemplates from the Styles in my code.
I use DataTemplate to define, e.g. that the data should be displayed as two buttons and I use styles to define, e.g. that the background of those buttons should be green.
What I am trying to achieve is having a DataTemplate defined in one file and using that in multiple UserControls where the style comes from the UserControls.
Let's say I have the following style in the Resources of a UserControl:
<Style x:Key="ButtonStyle" TargetType="{x:Type Button}">
<Setter Property="Foreground" Value="Green"/>
<Style.Triggers>
<Trigger Property="IsMouseOver" Value="True">
<Setter Property="Foreground" Value="Red"/>
</Trigger>
</Style.Triggers>
</Style>
Another UserControl might have something similar with different colors.
Then, I have a ContentControl in that UserControl that will have some view model and some DataTemplate:
<ContentControl Content="{Binding SelectedViewModel}"
ContentTemplate="{Binding SelectedDataTemplate}"/>
The DataTemplate can be something as simple as this:
<DataTemplate x:Key="TwoButtonsTemplate">
<StackPanel>
<Button Content="One"/>
<Button Content="Two"/>
</StackPanel>
</DataTemplate>
I would like the two buttons to have the ButtonStyle from the UserControl.Resources without directly referencing it. (So the DataTemplate can come from a different file or being able to use the DataTemplate in a similar context with another UserControl's style).
I tried to change the TargetType of ButtonStyle to ContentControl, assign the style to the ContentControl and set Foreground="{TemplatedParent Foreground}" on the Buttons, but in this way both Foreground will change when any of them (i.e. the ContentControl itself) is hovered.
Is there any way of "inheriting" a style in the DataTemplate or "injecting" the style from the UserControl?
P.S. I understand if I move the style into a separate file and reference that in the DataTemplate file I can simply use it as StaticResource, but that will couple the DataTemplate to that specific style and I won't be able to re-use it with other styles.
try DynamicResource:
<DataTemplate x:Key="TwoButtonsTemplate">
<StackPanel>
<Button Content="One" Style="{DynamicResource ButtonStyle}"/>
<Button Content="Two" Style="{DynamicResource ButtonStyle}"/>
</StackPanel>
</DataTemplate>
when TwoButtonsTemplate template is instantiated in UserControl, which declares ButtonStyle resource, that resource will be found and applied to buttons.

Binding in Style Setter in DataTemplate WPF

Okay, I have a relatively involved problem. I'm trying to create a Window in WPF. The main element on this window is a DataGrid. Each one of the rows in the DataGrid has a DetailsPane which I set using DataGrid.RowDetailsTemplate. Depending on certain row-specific values, I need the DetailsPane to display different elements. To accomplish this I placed a ContentControl at the root of the DataTemplate and used a Style with DataTriggers to set its Content property. Now, inside one of these Setters is a ComboBox. This ComboBox needs to have its ItemsSource bound to a list, which is stored in a dependency property on the Window level (because its the same list regardless of the row). Below is a simplified version of what I'm looking at:
<Window>
...
<DataGrid>
...
<DataGrid.RowDetailsTemplate>
<DataTemplate>
<ContentControl>
<Style TargetType="ContentControl">
<Style.Triggers>
<DataTrigger Binding="{Binding RowSpecificBooleanProperty}" Value="False">
<Setter Property="Content">
<Setter.Value>
...
<ComboBox ItemsSource={HowDoIBindThisToTheWindowProperty}/>
</Setter.Value>
</Setter>
</DataTrigger>
</Style.Triggers>
</Style>
</ContentControl>
</DataTemplate>
</DataGrid.RowDetailsTemplate>
</DataGrid>
</Window>
So what I'm trying to figure out is how to bind the ItemsSource of that ComboBox to a dependency property of the top-level window. Andy idea how to accomplish that?
EDIT:
I should have mentioned this before but I've already tried using {RelativeSource AncestorType=Window} and ElementName in the binding. In both cases the list in the ComboBox is blank at runtime.
ItemsSource="{Binding WhateverList, RelativeSource={RelativeSource AncestorType=Window}}"

DRY - Subclassing WPF windows

I have several WPF windows which will be very similar except for the columns on a DataGrid (the DataContext will be ObservableCollections of different objects), the text in some Labels and a Button click handler.
For each window, the <DataGrid.Columns> part of the DataGrid is different. It uses AutoGenerateColumns="False" and shows different columns for the different objects.
I wonder if it's possible to subclass a base WPF window so I can just write the <DataGrid.Columns> part on the XAML for each subclass instead of writing it in code.
Or what other techniques exist for abiding by the DRY principle on WPF while still using XAML?
How do I populate DataGrid Columns from the datasource...
Yes, you have come across a limitation here. The Columns property is not bindable; in fact it isn't even settable, you can only add and remove from the collection. There is a workaround at this question: How do I bind a WPF DataGrid to a variable number of columns?
So theoretically, you could add the columns to <Application.Resources>, then databind an attached property as in the question above, and write a value converter that builds a column collection based on the datasource value, pulling from Application.Current.Resources. But this seems more convoluted than it needs to be.
I think you could just use a style trigger that replaces some Content with different DataGrids:
<ContentControl>
<ContentControl.Style>
<Style TargetType="ContentControl">
<Setter Property="Content">
<Setter.Value>
<DataGrid Style="{StaticResource CommonStyle}">
<DataGrid.Columns>
... default columns go here ...
</DataGrid.Columns>
</DataGrid>
<Setter.Value>
</Setter>
<Style.Triggers>
<DataTrigger Binding="{Binding SomeCondition}" Value="True">
<Setter Property="Content">
<DataGrid Style="{StaticResource CommonStyle}">
<DataGrid.Columns>
... alternate columns ...
</DataGrid.Columns>
</DataGrid>
</Setter>
</DataTrigger>
... additional triggers as needed ...
</Style.Triggers>
</Style>
</ContentControl.Style>
</ContentControl>
This could be part of a larger common view template -- no need to create separate view classes.
I would do it with one window and different DataTemplates. However, if you want to use inheritance then you could override the DataTemplate in the Window.Resources using the key that is referenced by the base Window. The DataTemplate would have the Xaml for the whole datagrid.

Conditionally changing the Foreground of a WPF ComboBox item depending upon a value in the databound ItemsSource item

I have a WPF ComboBox bound to a Collection List<Users>. I have applied a DataTemplate to show the FirstName using a TextBlock and this works as expected:
<ComboBox Margin="5" ItemsSource="{Binding Path=TheUsers}" Name="cboUsers">
<ComboBox.ItemTemplate>
<DataTemplate>
<TextBlock Margin="10" Text="{Binding Path=FirstName}">
</TextBlock>
</DataTemplate>
</ComboBox.ItemTemplate>
</ComboBox>`
I have an item in my User class called IsActive which is a Boolean value. If true then I want to set the Foreground of the TextBlock to Navy.
I have spent so much time on what should be so easy and looked all over the web but most articles talk about changing the overall colour or binding to another element in the xaml.
I tried implementing a DataTrigger and after an hour removed the code because it was not working. It would not recognise my field name. Does anyone have a very simple guide to how to do this or what would be the best approach?
As you apparently are not dealing with fields after all, this style should do what you want:
<TextBlock.Style>
<Style TargetType="{x:Type TextBlock}">
<Style.Triggers>
<DataTrigger Binding="{Binding IsActive}" Value="True">
<Setter Property="Foreground" Value="Navy"/>
</DataTrigger>
</Style.Triggers>
</Style>
</TextBlock.Style>
It would not recognise my field name.
You cannot bind to fields, end of story.

Resources