WPF DataTrigger that accepts parameter? - c#

I have the following style defined in my XAML to enable buttons only when something in the DataContext has changed (IsDirty = true) :
<!-- Style for Buttons to enable based on IsDirty value -->
<Style x:Key="EnableWhenDirtyButtonStyle" TargetType="{x:Type Button}"
BasedOn="{StaticResource ButtonStyle}">
<Setter Property="IsEnabled" Value="False" />
<Style.Triggers>
<!-- Enable button when something has changed -->
<DataTrigger Binding="{Binding RelativeSource={RelativeSource FindAncestor,
AncestorType={x:Type UserControl}}, Path=DataContext.IsDirty}" Value="True">
<Setter Property="Button.IsEnabled" Value="true" />
</DataTrigger>
</Style.Triggers>
</Style>
This works as long as there is only one DataContext within the UserControl. I now have the situation where I have 3 different DataViews so I have 3 different IsDirty values (i.e., CustomerTableIsDirty, OrderTableIsDirty, OrderDetailTableIsDirty). In this case I can create three new *DisableWhenDirtyButtonStyle in the UserControl like:
<Style x:Key="CustomerTableEnableWhenDirtyButtonStyle"
TargetType="{x:Type Button}"
BasedOn="{StaticResource ButtonStyle}">
<Setter Property="Button.IsEnabled" Value="False" />
<Style.Triggers>
<DataTrigger Binding="{Binding Path=CustomerTableIsDirty}" Value="true">
<Setter Property="Button.IsEnabled" Value="True" />
</DataTrigger>
</Style.Triggers>
</Style>
Is there a way to create a DataTrigger such that the binding value can be passed into the style as a parameter?
Alternatively, is there a way to add conditions to a MultiDataTrigger when inheriting a style via 'BasedOn' which would already have a MultiDataTrigger defined. For instance:
<Style x:Key="CustomerTableEnableWhenDirtyButtonStyle"
TargetType="{x:Type Button}"
BasedOn="{StaticResource EnableWhenDirtyButtonStyle}">
<!-- Add the following to existing MultiDataTrigger in EnableWhenDirtyButtonStyle -->
<Condition Binding="{Binding Path=CustomerTableIsDirty}" Value="true" />
</Style>
I CANNOT use MultiBinding as this style is part of a base project which gets used by multiple other projects (as a DLL). The users of this DLL would not be able to update the style to include the necessary Binding Path.

Instead of using 3 different names, just use a single name IsDirty.

Related

using triggers as resources

I want to define triggers as resources to use them later in my controls.
Like this:
<Window.Resources>
<DataTrigger x:Key="Trigger1" Binding="{Binding ViewModelProperty1}" Value="Val1">
<Setter Property="IsEnabled" Value="False"/>
</DataTrigger>
<DataTrigger x:Key="Trigger2" Binding="{Binding ViewModelProperty2}" Value="Val2">
<Setter Property="IsEnabled" Value="False"/>
</DataTrigger>
...
</Window.Resources>
However, when I try to run the code, the compiler complains that IsEnabled is not a valid member. I think this is because it cannot know if the control in question will even have the property "IsEnabled". Like with styles, I think I need to somehow specifiy the TargetType (which would be, in my case, FrameworkElement). But how?
NOTE:
Please do not suggest to use styles instead of triggers as resources. Since a control can only have ONE style, but I need to give SEVERAL triggers to one control, styles are no option here:
In my actual code I have a Button that should have trigger 1, 2 and 4 and a TextBox that should have trigger 1 and 3 and a Label that should have trigger 2, 3 and 4... I think you get it.
You can do it like this (note how I prepend IsEnabled with FrameworkElement and also how I reference those resources from style triggers):
<Window.Resources>
<DataTrigger x:Key="Trigger1"
Binding="{Binding ViewModelProperty1}"
Value="Val1">
<Setter Property="FrameworkElement.IsEnabled"
Value="False" />
</DataTrigger>
<DataTrigger x:Key="Trigger2"
Binding="{Binding ViewModelProperty2}"
Value="Val2">
<Setter Property="FrameworkElement.IsEnabled"
Value="False" />
</DataTrigger>
</Window.Resources>
<Button>
<Button.Style>
<Style>
<Style.Triggers>
<StaticResource ResourceKey="Trigger1" />
<StaticResource ResourceKey="Trigger2" />
</Style.Triggers>
</Style>
</Button.Style>
</Button>

Creating a trigger in a CellTemplate for a ListView? (confusion with Templates in general)

I'm starting to get a little confused as I delve further into WPF and I feel like this example will help in better understanding things. My requirement is this: I have a ListView that is using a binding to a collection of plain .NET objects, I want to do two things:
1) highlight the cell of a row in the ListView if the value is a certain value - I figure I can use the GridViewColumn.CellTemplate for this and create a DataTemplate with a DataTrigger, however I am becoming confused here - is the DataType for the DataTemplate supposed to be the ListViewItem or is it supposed to be the type of the underlying object itself?
This is a general point of confusion for me in WPF ..not knowing when to type it to the underlying collection object (which I've seen in examples) vs the list-item type itself. Here is my first attempt:
<GridViewColumn Header="Position">
<GridViewColumn.CellTemplate>
<DataTemplate DataType="{x:Type ListViewItem}">
<TextBlock Text="{Binding Path=PositionCode}"></TextBlock>
<DataTemplate.Triggers>
<DataTrigger Binding="{Binding PositionCode}" Value="QB">
<Setter Property="Foreground" Value="Blue" />
</DataTrigger>
<DataTrigger Binding="{Binding PositionCode}" Value="RB">
<Setter Property="Foreground" Value="Green" />
</DataTrigger>
<DataTrigger Binding="{Binding PositionCode}" Value="WR">
<Setter Property="Foreground" Value="Red" />
</DataTrigger>
</DataTemplate.Triggers>
</DataTemplate>
</GridViewColumn.CellTemplate>
</GridViewColumn>
However, this not surprisingly leads to the error message
Cannot find the Template Property 'Background' on the type 'System.Windows.Controls.ContentPresenter'
2) similar to 1) I want to have a similar rule on another criteria I want to highlight the entire row, instead of just the cell based on a similar DataTrigger property but same time I want the cell highlighting to take precedence over the row highlighting.
How would I do this and what template do I need to override to do this? I'm guessing it's the ListView.ItemTemplate but what would the data type be?
Try this:
<GridViewColumn Header="Position">
<GridViewColumn.CellTemplate>
<DataTemplate DataType="{x:Type ListViewItem}">
<TextBlock Name="TextBlockName" Text="{Binding Path=PositionCode}"></TextBlock>
<DataTemplate.Triggers>
<DataTrigger Binding="{Binding PositionCode}" Value="QB">
<Setter TargetName="TextBlockName" Property="Foreground" Value="Blue" />
</DataTrigger>
<DataTrigger Binding="{Binding PositionCode}" Value="RB">
<Setter TargetName="TextBlockName" Property="Foreground" Value="Green" />
</DataTrigger>
<DataTrigger Binding="{Binding PositionCode}" Value="WR">
<Setter TargetName="TextBlockName" Property="Foreground" Value="Red" />
</DataTrigger>
</DataTemplate.Triggers>
</DataTemplate>
</GridViewColumn.CellTemplate>
</GridViewColumn>
I think dvvrd's answer addresses your first question. For the other part (building the row style), you can use the ItemContainerStyleSelector.
<ListView ItemContainerStyleSelector="{StaticResource Selector}" ...
I wrote a simple implementation like this:
public class RowStyleSelector : StyleSelector
{
public override System.Windows.Style SelectStyle(object item, System.Windows.DependencyObject container)
{
var i = (item as Item);
if (i.I == 0) return (Style)App.Current.Resources["Selected"];
else return (Style)App.Current.Resources["Normal"];
}
}
Then the different styles, along with the selector reference, go in App.xaml:
<Application.Resources>
<res:RowStyleSelector x:Key="Selector" />
<Style x:Key="Selected" TargetType="ListViewItem">
<Setter Property="Background" Value="DarkGray" />
</Style>
<Style x:Key="Normal" TargetType="ListViewItem">
<Setter Property="Background" Value="LightBlue" />
</Style>
</Application.Resources>
This approach effectively sets the background color depending on criteria in your model (the Item class in my example), with the column highlighting still in effect.

Datagrid multitrigger referencing values from code-behind

I'm still bloody green in WPF and have not yet fully grasped the concept behind it. I've got the following problem:
I want to set triggers in a datagrid depending on a precondition.
Example:
In my code-behind, I have a string variable, let's call it variableString. Now depending on the the value of variableString, I want to enable/disable the triggers inside a datagrid, that I have defined in XAML like:
if(variableString == "a")
then
XAML
<DataGrid AutoGenerateColumns="False" Margin="5,5,0,75" Name="dataGrid1" ItemsSource="Binding}">
<DataGrid.ItemContainerStyle>
<Style TargetType="DataGridRow">
<Style.Triggers>
<DataTrigger Binding="{Binding Path=SomeColumnName}" Value="someValue">
<Setter Property="Background" Value="White"/>
<DataTrigger Binding="{Binding Path=SomeColumName}" Value="someOtherValue">
<Setter Property="Background" Value="Red"/>
</Style.Triggers>
</Style>
</DataGrid.ItemContainerStyle>
Otherwise, if
if(variableString == "b")
then
Do Nothing`
I've already tried binding the string to the datacontext of the datagrid, but that was rather contra-productive, as it removes my binding to the database.
Can anyone help me here. An example, a push in the right direction etc...
I really like the options that WPF gives you, however it's that fundamental things, that were so easy to handle in WinForms, that drive me mad in WPF.
Thanks
I think you want a MultiDataTrigger, which allows you to base your trigger off of multiple values
<MultiDataTrigger>
<MultiDataTrigger.Conditions>
<Condition Binding="{Binding Path=SomeColumnName}" Value="someValue" />
<Condition Binding="{Binding RelativeSource={RelativeSource AncestorType={x:Type Window}}, Path=variableString}" Value="A" />
</MultiDataTrigger.Conditions>
<Setter Property="Background" Value="White" />
</MultiDataTrigger>
To find your string in the code behind, you'll probably have to use some kind of RelativeSource binding to find the class containing that property. My example assumes there is a public property called variableString on the Window class

Deriving (Expanding) a Control Template in WPF

I have this DataGrid I want to change the Disabled behaviour (xaml);
I want to change this small part in the template.
If it is not possible i dont mind to use:
<Setter Property="OverridesDefaultStyle" Value="True"/>
And To replace the entire xaml (template) of my control, But I need the complete template to copy paste and help where to change the Disabled look-like part.
Can anyone help me?
EDIT: It's been pointed out that all the default control templates are available at MSDN which makes the below relevant, but I'll leave it here for interest.
Given an instance of a control you get serialize the markup for the control template using the System.Windows.Markup.XamlWriter class.
To get a control template:
string markup = System.Windows.Markup.XamlWriter.Save(control.Template);
To get a complete dump (including triggers etc) of the control template use.
StringBuilder markupBuilder = new StringBuilder();
XmlWriter writer = XmlWriter.Create(markupBuilder);
System.Windows.Markup.XamlDesignerSerializationManager manager =
new System.Windows.Markup.XamlDesignerSerializationManager(writer);
manager.XamlWriterMode = System.Windows.Markup.XamlWriterMode.Value;
// data grid named dataGrid1
var template = dataGrid1.Template;
System.Windows.Markup.XamlWriter.Save(dataGrid1.Template, manager);
string markup = markupBuilder.ToString();
If you just looking to change the foreground color of the DataGrid when it's disabled, you should be able to use styles together with triggers rather than replacing the entire template.
<DataGrid>
<DataGrid.Resources>
<Style
TargetType="{x:Type DataGridColumnHeader}">
<Style.Triggers>
<DataTrigger
Binding="{Binding IsEnabled, RelativeSource={RelativeSource AncestorType={x:Type DataGrid}}}"
Value="False">
<Setter
Property="Foreground"
Value="Green" />
</DataTrigger>
</Style.Triggers>
</Style>
<Style
TargetType="{x:Type DataGridCell}">
<Style.Triggers>
<DataTrigger
Binding="{Binding IsEnabled, RelativeSource={RelativeSource AncestorType={x:Type DataGrid}}}"
Value="False">
<Setter
Property="Foreground"
Value="Green" />
</DataTrigger>
</Style.Triggers>
</Style>
</DataGrid.Resources>
<!-- Column Definitions -->
</DataGrid>
Adding the above 2 styles to the DataGrid's resources collection will, set the foreground of each column header and data row cell to green when the DataGrid is disabled.
To define the disabled behavior of any control, you should change the Disabled visual state accordingly in the control template.

WPF data validation is overriding theme on the interface

I built a WPF application and manage to get the validation working thanks to posts on stackoverflow.The only probblem i'm having is that it's overriding the theme i'm using.
example the theme makes the textboxes look like a round rectangle but after setting the binding it look like the default textboxes. here is my code :
<Button.Style>
<Style TargetType="{x:Type Button}">
<Setter Property="IsEnabled" Value="false" />
<Style.Triggers>
<!-- Require the controls to be valid in order to press OK -->
<MultiDataTrigger>
<MultiDataTrigger.Conditions>
<Condition Binding="{Binding ElementName=txtEmail, Path=(Validation.HasError)}" Value="false" />
</MultiDataTrigger.Conditions>
<Setter Property="IsEnabled" Value="true" />
</MultiDataTrigger>
</Style.Triggers>
</Style>
</Button.Style>
code behind is:
//Form loaded event code
txtEmail.GetBindingExpression(TextBox.TextProperty).UpdateSource();
I've tried to look into the theme file but i was quickly lost.i thought i could use that file like a web css file.Now i've disabled the data binding because of that.Is there any work around for this? thanks for reading this
Not sure if that's the root problem, but try adding BasedOn="{StaticResource {x:Type Button}}" to style element.
Button.Style>
<Style TargetType="{x:Type Button}" BasedOn="{StaticResource {x:Type Button}}">
...

Categories