WPF Concatenated DataTrigger Binding - wpf

In my application i'd like to change the Background property of a TextBox when the <Property>IsChanged property of my Model is set to True
I have been able to successfully do this for one of my TextBox controls with the following style:
Notice the hard-coded binding for FirstNameIsChanged in the DataTrigger
<TextBox.Style>
<Style TargetType="TextBox" BasedOn="{StaticResource {x:Type TextBox}}">
<Style.Triggers>
<DataTrigger Binding="{Binding FirstNameIsChanged}" Value="True">
<Setter Property="Background" Value="SteelBlue"/>
</DataTrigger>
</Style.Triggers>
</Style>
</TextBox.Style>
If my application has a form with 10 or so TextBox controls (FirstName, MiddleName, LastName, Age, Gender etc.) i don't want to copy and paste the above code if i'm only changing the Binding on the DataTrigger for each property.
I've researched using MultiBinding in combination with RelativeSource and StringFormat. I'm trying to create a binding that gets the Tag property of the TextBox and adds "IsChanged" to the name but i have not managed to get it working (I don't see any binding warnings/errors in Visual Studio either)
This is the Style i'm trying to create:
<Style x:Key="TextBoxTestStyle" TargetType="TextBox" BasedOn="{StaticResource {x:Type TextBox}}">
<Style.Triggers>
<DataTrigger Value="True">
<DataTrigger.Binding>
<MultiBinding StringFormat="\{0\}IsChanged">
<Binding Path="Tag"
RelativeSource="{RelativeSource Mode=FindAncestor, AncestorType={x:Type TextBox}}" />
</MultiBinding>
</DataTrigger.Binding>
<Setter Property="Background" Value="Red"/>
</DataTrigger>
</Style.Triggers>
</Style>
Each of the Properties on my Model have their own IsChanged property that i'm using for Change Tracking, this is a quick preview of my Model and how i've made the IsChanged properties:
public class Person
{
public string FirstName { get; set; }
public bool FirstNameIsChanged { get; set; }
public string MiddleName { get; set; }
public bool MiddleNameIsChanged { get; set; }
public string LastName { get; set; }
public bool LastNameIsChanged { get; set; }
public int Age { get; set; }
public bool AgeIsChanged { get; set; }
public DateTime? DateOfBirth { get; set; }
public bool DateOfBirthIsChanged { get; set; }
}
As an example, if i have the following TextBox and i set the Tag property to "FirstName", the style should set the Background property to "Red" if the FirstNameIsChanged property is set to True :
<TextBox x:Name="FirstNameTextBox"
Tag="FirstName"
Margin="3"
Height="23"
HorizontalAlignment="Stretch"
VerticalAlignment="Center"
Style="{DynamicResource TextBoxTestStyle}"
Text="{Binding FirstName,
Mode=TwoWay,
NotifyOnValidationError=true,
ValidatesOnExceptions=true,
UpdateSourceTrigger=PropertyChanged}"/>
The style (If it's codable) would allow me to set the Tag property on 10 TextBox controls but only have 1 style for all my TextBox controls instead of having to copy and paste the style 10 times. I also want to avoid adding "Boiler plate" code if i decide to add additional properties to my Model in the future such as an IsValid property or an IsEnabled property.
Some ideas i've had:
Can i add "IsChanged" to the string value of the Tag property and use the resulting string in a binding? e.g. Tag("FirstName") + "IsChanged" = "FirstNameIsChanged"
From a Style can i walk up the visual tree and grab the Binding that is already on the Text property and add "IsChanged" to the path? e.g. BindingPath("FirstName") + "IsChanged" = "FirstNameIsChanged"

You shouldn't be comparing to a boolean. Also, to make it Generic, I would make the Tag TextBox-independent, e.g. IsChanged:
<Style x:Key="TextBoxTestStyle" TargetType="TextBox">
<Style.Triggers>
<Trigger Property="Tag" Value="IsChanged">
<Setter Property="Background" Value="Red"/>
</Trigger>
</Style.Triggers>
</Style>
EDIT
To preserve to Booleans you could use a less generic solution. Since Tag expects a string I added a Converter (you could also create another Dependeny Property expecting a boolean).
<....Resources>
<local:BoolToStringConverter x:Key="BoolToStringConverter"/>
<Style x:Key="TextBoxStyle" TargetType="TextBox">
<Style.Triggers>
<Trigger Property="Tag" Value="True">
<Setter Property="Background" Value="Red"/>
</Trigger>
</Style.Triggers>
</Style>
</....Resources>
...
<TextBox
x:Name="FirstNameTextBox"
Tag="{Binding FirstNameIsChanged,
Converter={StaticResource BoolToStringConverter}}"
Style="{StaticResource TextBoxStyle}"
...
/>
<TextBox
x:Name="MiddleNameTextBox"
Tag="{Binding MiddleNameIsChanged,
Converter={StaticResource BoolToStringConverter}}"
Style="{StaticResource TextBoxStyle}"
...
/>
...
Without reaching for code-behind (see my comment), this is probably the shortest solution. If you're not using the TextBox Tag Property for anything else, you could also make the Style implicit.

Related

How to use depenceny property in the style of tooltip to set the text?

In a button, I am using a dependency property to pass information from the view model to the style of the button, so I can set the color of the color according to some conditions.
The code for the button is this:
The style in my xaml file:
<Style x:Key="BotonesColorEstadosTemplate" TargetType="{x:Type Button}">
<Setter.Value>
<ControlTemplate TargetType="{x:Type Button}">
<ControlTemplate.Triggers>
<MultiTrigger>
<MultiTrigger.Conditions>
<Condition Property="dp:BotonesEstadosAttachedProperty.CodigoEstado" Value="0"/>
<Condition Property="dp:BotonesEstadosAttachedProperty.AccionHabilitada" Value="true"/>
</MultiTrigger.Conditions>
</MultiTrigger>
<ContentTemplate>
</ContentTemplate>
</Style>
The dependency properties:
public static class BotonesEstadosAttachedProperty
{
public static readonly DependencyProperty CodigoEstadoProperty =
DependencyProperty.RegisterAttached(
"CodigoEstado",
typeof(short),
typeof(BotonesEstadosAttachedProperty));
public static short GetCodigoEstado(DependencyObject obj)
{
return (short)obj.GetValue(CodigoEstadoProperty);
}
public static void SetCodigoEstado(DependencyObject obj, short value)
{
obj.SetValue(CodigoEstadoProperty, value);
}
public static readonly DependencyProperty AccionHabilitadaProperty =
DependencyProperty.RegisterAttached(
"AccionHabilitada",
typeof(bool),
typeof(BotonesEstadosAttachedProperty));
public static bool GetAccionhabilitada(DependencyObject obj)
{
return (bool)obj.GetValue(AccionHabilitadaProperty);
}
public static void SetAccionHabilitada(DependencyObject obj, bool value)
{
obj.SetValue(AccionHabilitadaProperty, value);
}
}
How to use in the button:
<Button Name="btnAlmacenesActualizar" Content="..." Height="23" Margin="3,0,0,0" Width="23"
ap:BotonesEstadosDependencyProperty.CodigoEstado="{Binding CodigoEstadoActualizarAlmacenes}"
ap:BotonesEstadosDependencyProperty.AccionHabilitada="{Binding EsAccionActualizarAlmacenesHabilitada}">
With this, I can use a property of my view model and pass to my style, that use the information in the trigger to set the color of the button.
Now, I would like to have a style for the tooltip, to have the default configuration for all the tooltips, and I would like to can pass the text of the tooltip at first, but later I would like to pass another variables. By the moment, to test, I would like to try with the text.
I have this style:
<Style TargetType="ToolTip" x:Key="ToolTipDefaultStyle">
<Setter Property="ContentTemplate">
<Setter.Value>
<DataTemplate>
<StackPanel>
<TextBlock>
<TextBlock.Style>
<Style TargetType="{x:Type TextBlock}">
<Setter Property="Text" Value="dp:ToolTipAttachedProperty.Texto"/>
</Style>
</TextBlock.Style>
</TextBlock>
</StackPanel>
</DataTemplate>
</Setter.Value>
</Setter>
</Style>
My StackPanel with the tooltip:
<StackPanel Name="spTiposIva" Orientation="Vertical" Margin="5,0,0,0"
ap:ToolTipDependencyProperty.Texto="{Binding TiposIvaTooltip}">
<StackPanel.ToolTip>
<ToolTip Style="{StaticResource ToolTipDefaultStyle}"/>
</StackPanel.ToolTip>
</StackPanel>
But in this case the text that is shown is "dp:ToolTipAttachedProperty.Texto". So I would like if it is possible to do the same than with the button, use a dependency propety to pass information from view model to the style.
Thanks.
You are currently not binding the attached property, you are assigning a string as Value. For binding to attached properties, you need to use the binding markup extension and parentheses, e.g.:
<Setter Property="Text" Value="{Binding (dp:ToolTipAttachedProperty.Texto)"/>
However, in your case you need to refer to the parent control of ToolTip, which your property is attached to. Normally, you would do this with a RelativeSource binding and AncestorType, but this does not work here, because ToolTip is not within the same visual tree as the parent control.
Instead, you can access the control via the PlacementTarget property on the parent ToolTip.
<Setter Property="Text" Value="{Binding PlacementTarget.(dp:ToolTipAttachedProperty.Texto), RelativeSource={RelativeSource AncestorType={x:Type ToolTip}}}"/>
Please also check your XAML for typos. The attached properties type does not match on the StackPanel and in the ToolTip style: ToolTipDependencyProperty or ToolTipAttachedProperty?

User control parameter in wpf mvvm

I want to create a user control with a bool parameter that defines a dynamic behavior, using MVVM pattern, so I can use the user control in another view that way :
<local:MyUserControl BoolParam={Binding aBoolBinding} />
About the coding of the user control, the xaml should use the value of BoolParam to do something like this :
...
<TextBox>
<TextBox.Style>
<Style TargetType="TextBox">
<Style.Trigger>
<DataTrigger Binding="{referenceToBoolParam}" Value="False" >
<Setter Property="Visibility" Value="Collapsed" />
</DataTrigger>
</Style.Triggers>
</Style>
</TextBox.Style>
</TextBox>
...
Am I supposed to define a property
public bool BoolParam { get; set; }
in the code-behind of the user control, and to code the logic associated to this parameter in the ViewModel of the user control ?
Actually I am a bit confused about it, what is the good practice ?
BoolParam should be a dependency property for you to be able to bind something to it. You define this in the code-behind of MyUserControl:
public bool BoolParam
{
get { return (bool)this.GetValue(BoolParamProperty); }
set { this.SetValue(BoolParamProperty, value); }
}
public static readonly DependencyProperty BoolParamProperty = DependencyProperty.Register(
"BoolParam", typeof(bool), typeof(MyUserControl), new PropertyMetadata(false));
You could then set the DataContext of the UserControl to an instance of a view model that contains a public bool property called aBoolBinding and bind to this one as usual.
View Model:
private bool _b;
public bool aBoolBinding
{
get
{
return _b;
}
set
{
_b = value;
NotifyPropertyChanged();
}
}
View:
<local:MyUserControl BoolParam="{Binding aBoolBinding}" />
This is how data binding works. A target property in the views is bound to a source property of a view model.
Edit:
This binds to the BoolParam property of the UserControl from a TextBox style defined in the UserControl:
<TextBox>
<TextBox.Style>
<Style TargetType="TextBox">
<Style.Triggers>
<DataTrigger Binding="{Binding BoolParam, RelativeSource={RelativeSource AncestorType=UserControl}}" Value="False" >
<Setter Property="Visibility" Value="Collapsed" />
</DataTrigger>
</Style.Triggers>
</Style>
</TextBox.Style>
</TextBox>

Selecting User Control for Data Template based on an Enum

I am working on a WPF app and currently I have an ItemsControl bound up to my View Model ObservableCollection and I have a DataTemplate that uses a UserControl to render the items on canvas. Can you use multiple User Controls and then switch which one is used based on an Enum? Another way to look it is to either create a Button or a TextBox for the item in the ObservableCollection based on an Enum.
You can select the data template for an item using a custom DataTemplateSelector. Assume we have the following:
public enum Kind
{
Button, TextBox,
}
public class Data
{
public Kind Kind { get; set; }
public string Value { get; set; }
}
Your data template selector might then look like this:
public class MyTemplateSelector : DataTemplateSelector
{
public DataTemplate ButtonTemplate { get; set; }
public DataTemplate TextBoxTemplate { get; set; }
public override DataTemplate SelectTemplate(object item, DependencyObject container)
{
Data data = (Data)item;
switch (data.Kind)
{
case Kind.Button:
return ButtonTemplate;
case Kind.TextBox:
return TextBoxTemplate;
}
return base.SelectTemplate(item, container);
}
}
In XAML, declare templates for all the cases you want to cover, in this case buttons and text boxes:
<Window.Resources>
<ResourceDictionary>
<DataTemplate x:Key="ButtonTemplate" DataType="local:Data">
<Button Content="{Binding Value}" />
</DataTemplate>
<DataTemplate x:Key="TextBoxTemplate" DataType="local:Data">
<TextBox Text="{Binding Value}" />
</DataTemplate>
</ResourceDictionary>
</Window.Resources>
Finally, have your ItemsControl create an instance of your custom template selector, initializing its two DataTemplateproperties from the above data templates:
<ItemsControl>
<ItemsControl.ItemTemplateSelector>
<local:MyTemplateSelector
ButtonTemplate="{StaticResource ButtonTemplate}"
TextBoxTemplate="{StaticResource TextBoxTemplate}"/>
</ItemsControl.ItemTemplateSelector>
<ItemsControl.Items>
<local:Data Kind="Button" Value="1. Button" />
<local:Data Kind="TextBox" Value="2. TextBox" />
<local:Data Kind="TextBox" Value="3. TextBox" />
<local:Data Kind="Button" Value="4. Button" />
</ItemsControl.Items>
</ItemsControl>
(In real life, set the ItemsSource instead of declaring the items inline, as I did.)
For completeness: To access your C# classes you need to set up the namespace, e.g.,
xmlns:local="clr-namespace:WPF"
Another possible quick solution is to use Data Triggers:
<ContentControl>
<ContentControl.Style>
<Style TargetType="ContentControl">
<Setter Property="Content"
Value="{StaticResource YourDefaultLayout}" />
<Style.Triggers>
<DataTrigger Binding="{Binding YourEnumVMProperty}"
Value="{x:Static local:YourEnum.EnumValue1}">
<Setter Property="Content"
Value="{StaticResource ContentForEnumValue1}" />
</DataTrigger>
<DataTrigger Binding="{Binding YourEnumVMProperty}"
Value="{x:Static local:YourEnum.EnumValue2}">
<Setter Property="Content"
Value="{StaticResource ContentForEnumValue2}" />
</DataTrigger>
</Style.Triggers>
</Style>
</ContentControl.Style>
</ContentControl>
You could also define the template of a whole control using a trigger setter.
I prefer this because there is no need to define all the DataTemplateSelector stuff etc.

Handling exclusive-or conditions when binding to two boolean properties in XAML

So I have a class I want to bind to:
public class Awesome {
public bool OptionA1 { get; set; }
public bool OptionA2 { get; set; }
public bool OptionB1 { get; set; }
public bool OptionB2 { get; set; }
}
When I expose this class in my GUI, I have two radio buttons (called radioButtonA and radioButtonB) which let the user pick between groups A and B, and I have two checkboxes to let the user change A1/A2 and B1/B2 respectively depending on which radiobutton ischecked.
The logic in the class doesn't technically differentiate between A and B like I do on my GUI. So if OptionA1 == True and OptionB1 == True, the class will execute behaviour for OptionA1 and B1 as expected.
However, I want to expose groups A and B as exclusive-or: meaning that if OptionA1 or OptionA2 are set to true, OptionB1 and OptionB2 must both be false. And vice versa.
The following binding will automatically repurpose/rebind the checkbox depending on which radiobutton/group is checked, but it does not enforce the exclusive-or relationship between the groups. So, the user can click RadioButtonA and check both boxes. If the user then clicks RadioButtonB, OptionA1 and OptionA2 are still set to true (as expected). How do I automatically set them to false when RadioButtonB ischecked?
<CheckBox Content="Do Option 1">
<CheckBox.Style>
<Style TargetType="CheckBox">
<Style.Triggers>
<DataTrigger Binding="{Binding ElementName=radioButtonA, Path=IsChecked}" Value="True">
<Setter Property="IsChecked">
<Setter.Value>
<Binding Path="OptionA1" Mode="TwoWay"/>
</Setter.Value>
</Setter>
</DataTrigger>
<DataTrigger Binding="{Binding ElementName=radioButtonB, Path=IsChecked}" Value="True">
<Setter Property="IsChecked">
<Setter.Value>
<Binding Path="OptionB1" Mode="TwoWay"/>
</Setter.Value>
</Setter>
</DataTrigger>
</Style.Triggers>
</Style>
</CheckBox.Style>
</CheckBox>
I don't think you're going to be able to do this in straight XAML. You are going to have to either write code in your code-behind/viewmodel or write a custom IValueConverter (perhaps a MultiConverter?) that handles it. Either way, its some code.

WPF Datagrid Cell with Validation Error Style

I am trying to change the default style of a DataGridCell (within a WPF Toolkit DataGrid) when there is a validation error. The default is a red border. How can I put my own template?
Thanks.
Try this:
<!-- Cell Style -->
<Style x:Key="CellErrorStyle" TargetType="{x:Type TextBlock}">
<Style.Triggers>
<Trigger Property="Validation.HasError" Value="true">
<Setter Property="ToolTip"
Value="{Binding RelativeSource={RelativeSource Self},
Path=(Validation.Errors)[0].ErrorContent}"/>
<Setter Property="Background" Value="Yellow"/>
</Trigger>
</Style.Triggers>
</Style>
And use it:
<DataGrid.Columns>
<DataGridTextColumn
ElementStyle="{StaticResource CellErrorStyle}">
</DataGridTextColumn>
</DataGrid.Columns>
There's a nice tutorial from Diederik Krols that does exactly what you're asking for the WPF Toolkit DataGrid.
One kind of solution is below, but first, let me share my findings.
It seems like the validation errors never reach inside the column's ElementStyle or CellStyle. The reason I suspect this is because it reaches and can be used in the column's EditingElementStyle and the datagrid's RowStyle.
For example, you can set the style based on Validation.HasError:
<DataGrid.RowStyle>
<Style TargetType="{x:Type DataGridRow}">
<Style.Triggers>
<Trigger Property="Validation.HasError" Value="True">
<Setter Property="Background" Value="Red" />
</Trigger>
</Style.Triggers>
</Style>
</DataGrid.RowStyle>
or you can set the Validation.ErrorTemplate as well:
<DataGrid.RowStyle>
<Style TargetType="{x:Type DataGridRow}">
<Setter Property="Validation.ErrorTemplate">
<Setter.Value>
<ControlTemplate>
<Border BorderBrush="Red" BorderThickness="3">
<AdornedElementPlaceholder />
</Border>
</ControlTemplate>
</Setter.Value>
</Setter>
</Style>
</DataGrid.RowStyle>
and both work just fine. Same on the EditingElementStyle. Neither of these really solves the problem: changing the row style obviously doesn't show which cell the error is in, and the editing style is not visible once the text box is defocused.
Unfortunately, for some reason, the same method doesn't work on the ElementStyle or the CellStyle. I'm inclined to believe this is a bug because in this tutorial it says after showing an example of setting a Validation.HasError triggered style on the EditingElementStyle:
You can implement more extensive customization by replacing the
CellStyle used by the column.
The solution
One workaround is not to use a trigger but rather bind the background (or whatever style property you want) of the cell to a new property of the data object. I'll show what I mean.
In this example, there are products, they have a category, which will be displayed in a text column in the datagrid. Here's the XAML:
<DataGrid AutoGenerateColumns="False" ItemsSource="{Binding Products}">
<DataGrid.Columns>
<!-- other columns -->
<DataGridTextColumn Header="Category">
<DataGridTextColumn.CellStyle>
<Style TargetType="{x:Type DataGridCell}">
<Setter Property="Background"
Value="{Binding Mode=OneWay, Path=CategoryErrorBackgroundColor}" />
</Style>
</DataGridTextColumn.CellStyle>
<DataGridTextColumn.Binding>
<Binding Path="Category" UpdateSourceTrigger="PropertyChanged">
<Binding.ValidationRules>
<ExceptionValidationRule />
</Binding.ValidationRules>
</Binding>
</DataGridTextColumn.Binding>
</DataGridTextColumn>
<!-- other columns -->
</DataGrid.Columns>
</DataGrid>
And here's the Product class:
public class Product : INotifyPropertyChanged
{
// ...other fields and properties
private string category;
private SolidColorBrush categoryErrorBackgroundColor;
public string Category
{
get
{
return category;
}
set
{
// validation checks
if (value.Lenght < 5)
{
CategoryErrorBackgroundColor = Brushes.Red;
// Notice that throwing is not even necessary for this solution to work
throw new ArgumentException("Category cannot be shorter than 5 characters.");
}
else
{
CategoryErrorBackgroundColor = Brushes.Transparent;
}
category = value;
}
}
// This is the property I'm binding to the cell's background
// It has to have the appropriate type
public SolidColorBrush CategoryErrorBackgroundColor
{
get
{
return categoryErrorBackgroundColor;
}
set
{
categoryErrorBackgroundColor = value;
OnPropertyChanged();
}
}
public event PropertyChangedEventHandler PropertyChanged;
protected virtual void OnPropertyChanged([CallerMemberName] string propertyName = null)
{
var handler = PropertyChanged;
if (handler != null) handler(this, new PropertyChangedEventArgs(propertyName));
}
}
Obviously, the huge downside of this solution is that it requires one (or more if you want more complex styles) style property for every property in the data object, and it needs a lot of manual settings of these properties. But it is kind of a solution nevertheless.

Resources