im struggling with figuring out how to check if a string contains certain characters / words and make the datatriggers go off based on that.
In my example below I would like the datatrigger to go off when there is a color in the value, and what comes after doesnt matter. The first trigger, if value contains "RED" trigger should go off no matter if it says RED Apple ,RED car, RED little ball etc.
<DataTrigger Binding="{Binding Name}" Value="RED Apple" >
<Setter Property="Foreground" Value="Red" />
</DataTrigger>
<DataTrigger Binding="{Binding Name}" Value="YELLOW Lemon" >
<Setter Property="Foreground" Value="Yellow" />
</DataTrigger>
<DataTrigger Binding="{Binding Name}" Value="GREEN Pear" >
<Setter Property="Foreground" Value="Green" />
</DataTrigger>
How can I achieve this
Create a Converter
public class ColorConverter : IValueConverter {
public object Convert(object value, Type targetType, object parameter, CultureInfo culture) {
return ((string)value.Contains("Color");
}
public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture) {
throw new NotImplementedException();
}
}
Then use the following XAML.
<Window.Resources>
<myNamespace:ColorConverter x:Key="ColorConverter" Color="red" />
</Window.Resources>
<DataTemplate.Triggers>
<DataTrigger Binding="{Binding Path=Color,
Converter={StaticResource ColorConverter}}">
<DataTrigger.Value>true</DataTrigger.Value>
<Setter TargetName="Color" Property="Foreground" Value="Red"/>
</DataTrigger>
</DataTemplate.Triggers>
Related
I set up a style for a label which should be triggered if the value of the bound filed in the underlying DataTable is greater than zero:
<c:Groesser0BooleanValueConverter x:Key="G0" />
<Style x:Key="DashboardProzent" TargetType="{x:Type Label}">
<Style.Triggers>
<DataTrigger Binding="{Binding Converter={StaticResource G0}}" Value="{x:Null}">
<Setter Property="Foreground" Value="Red"/>
</DataTrigger>
</Style.Triggers>
</Style>
The label itself is set up this way (a DataTemplate in a resource Dictionary):
<Label Content="{Binding percentCol}" Style="{StaticResource DashboardPrzoent}" Grid.Row="0" Grid.Column="2"/>
The converter looks like this:
public class Groesser0BooleanValueConverter : IValueConverter
{
public object Convert(object value, Type targetType, object parameter, CultureInfo culture)
{
if (System.Convert.ToInt32(value) > 0)
{
return true;
}
else
{
return false;
}
}
public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture)
{
throw new NotImplementedException();
}
}
If a run the whole thing, i get the error in the Convert method of the Groesser0BooleanValueConverter class:
Unable to cast object of type 'System.Data.DataRowView' to type
'System.IConvertible'
If I check the parameter of the Convert method of the Converter, it shows that a System.Data.DataRowView has been passed, instead of the value of the percentCol field, which I would have expected. How can I get the Label to pass the value instead of the whole row?
Update:
If I set the Label to
<Label Content="{Binding Path=percentCol}" Style="{StaticResource DashboardPrzoent}" Grid.Row="0" Grid.Column="2"/>
the error still occurs. If I disable the trigger, the value is shown (even without the Path= segment.
I don't want to state the field name in the style segment, as I would like to use it for other values to.
First I think the main problem is due to your Binding in the DataTrigger
<DataTrigger Binding="{Binding Converter={StaticResource G0}}" Value="{x:Null}">
This will pass on the direct DataContext of your Label to your Converter, which is obviously not what you want.
You should have:
<DataTrigger Binding="{Binding Converter={StaticResource G0}, Path=percentCol}" Value="False">
I also corrected the Value part, since your 'Converter' only sends back True/False. If you want to change back to Value="{x:Null}", you will have to modify your converter.
I would also advise to be safer in your Converter, otherwise the explicit conversion System.Convert.ToInt32(value) may (will...) raise an error.
I wanted to set the Binding Path and the Binding Converter in two different locations, for example a DataTemplate and a Style file.
This can be done by using {Binding Content, RelativeSource={RelativeSource Self}, Converter={StaticResource converterName}} in the Style and setting the Binding Path as usual in the DataTemplate
Converter
<Style x:Key="DashboardPrzoent" TargetType="{x:Type Label}">
...
<Style.Triggers>
<DataTrigger Binding="{Binding Content, RelativeSource={RelativeSource Self}, Converter={StaticResource G0}}" Value="True">
<Setter Property="Foreground" Value="{StaticResource Gruen}"/>
</DataTrigger>
<DataTrigger Binding="{Binding Content, RelativeSource={RelativeSource Self}, Converter={StaticResource G0}}" Value="False">
<Setter Property="Foreground" Value="{StaticResource Rot}"/>
</DataTrigger>
</Style.Triggers>
</Style>
DataTemplate
<Label Content="{Binding Path=percentCol}" ... />
I have a ToggleButton being a standard WPF class and I want to bind IsChecked to a property status of my model and the Status can have more than 2 values: Status1, Status2, Status3, Status4. The type of Status is SomeThirdPartyClassStatus and I don't have access to its source code.
<ToggleButton IsChecked="{Binding Status???}" />
So how can I bind Status to IsChecked then? I prefer a xaml solution.
I want to bind IsChecked property so that it's True when the Status is equal to Status1, and it's false in other cases. I prefer not to write any code in a *.cs file, but only xaml code.
As an alternative you can use pure XAML solution with DataTrigger. Assuming that you have something like
public enum SomeThirdPartyClassStatus {
Status1,
Status2,
Status3,
Status4
}
you can do
<ToggleButton>
<ToggleButton.Style>
<Style TargetType="{x:Type ToggleButton}">
<Setter Property="IsChecked" Value="False"/>
<Style.Triggers>
<DataTrigger Binding="{Binding Status}" Value="{x:Static local:SomeThirdPartyClassStatus.Status1}">
<Setter Property="IsChecked" Value="True"/>
</DataTrigger>
</Style.Triggers>
</Style>
</ToggleButton.Style>
</ToggleButton>
Where local is namespace for SomeThirdPartyClassStatus like
xmlns:local="clr-namespace:WpfApplication1"
Caveat is that it will work one-way only
EDIT
For two-way binding you'll need custom IValueConverter
public class EnumConverter : IValueConverter
{
public object Convert(object value, Type targetType, object parameter, CultureInfo culture)
{
return (SomeThirdPartyClassStatus)value == SomeThirdPartyClassStatus.Status1;
}
public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture)
{
return (bool?)value == true ? SomeThirdPartyClassStatus.Status1 : SomeThirdPartyClassStatus.Status2;
}
}
and then the binding would look like
<ToggleButton IsChecked="{Binding Path=Status, Converter={StaticResource EnumConverter}}"/>
Create a class derived from IValueConverter which you apply to your binding.
https://msdn.microsoft.com/en-us/library/system.windows.data.ivalueconverter(v=vs.110).aspx
Update:
Simply use this style for toggle button
<Style TargetType="{x:Type ToggleButton}">
<Style.Triggers>
<DataTrigger Binding="{Binding Status}" Value="Status1" >
<Setter Property="IsChecked" Value="True" />
</DataTrigger>
</Style.Triggers>
</Style>
So if ever Status has value Status1 toggle button will be checked.
I have a combobox that has the period of the day as its items. I am trying to make the isSelected item based on the period on the day through the use of a converter but for some reason my code won't work.
Below is my xaml:
<Window.Resources>
<staticData:SelectedPeriodConverter x:Key="SelectedPeriodConverter"/>
</Window.Resources>
<ComboBox Grid.Column="4" Margin="0,7" Width="100" HorizontalAlignment="Right" Name="PeriodPicker" VerticalAlignment="Top" Height="25" SelectedItem="PM">
<ComboBoxItem>AM</ComboBoxItem>
<ComboBoxItem>PM</ComboBoxItem>
<ComboBox.ItemContainerStyle>
<Style>
<Style.Triggers>
<DataTrigger Binding="{Binding Path=Content, RelativeSource={RelativeSource Self}, Converter={StaticResource SelectedPeriodConverter}}" Value="True">
<Setter Property="ComboBoxItem.IsEnabled" Value="False"/>
</DataTrigger>
</Style.Triggers>
</Style>
</ComboBox.ItemContainerStyle>
</ComboBox>
and my c# code for the converter is as follows:
public class SelectedPeriodConverter : IValueConverter
{
public object Convert(object values, Type targetType, object parameter, CultureInfo culture)
{
string test = values.ToString();
if (test == DateTime.Now.ToString("tt"))
{
return true;
}
else
{
return false;
}
}
public object ConvertBack(object value, Type targetTypes, object parameter, CultureInfo culture)
{
return value;
}
}
The weird thing is is if I change isSelected to IsEnable it will trigger but otherwise it won't.
Another way I tried was to have the styling in the Windows resources.
This worked if I targetted comboboxitem but would not work if I added an x:class and used ItemContainerStyle to isolate the trigger to one combobox as I did not want it working on all the comboboxes I had in my form.
<Style TargetType="{x:Type ComboBoxItem}">
<Style.Triggers>
<DataTrigger Binding="{Binding Path=Content, RelativeSource={RelativeSource Self}, Converter={StaticResource SelectedPeriodConverter}}" Value="True">
<Setter Property="IsSelected" Value="True"/>
</DataTrigger>
</Style.Triggers>
</Style>
<ComboBox Grid.Column="4" Margin="0,7" Width="100" HorizontalAlignment="Right" Name="PeriodPicker" VerticalAlignment="Top" Height="25" SelectedItem="PM">
<ComboBoxItem>AM</ComboBoxItem>
<ComboBoxItem>PM</ComboBoxItem>
</ComboBox>
Does anyone know how I can get this to work?
thanks Callum
The fix was quiet simple.
Inserting the second code inside ComboBox.Resources tags instead of Windows.Resources tags worked for me like so:
<ComboBox.Resources>
<staticData:SelectedPeriodConverter x:Key="SelectedPeriodConverter"/>
<Style TargetType="{x:Type ComboBoxItem}">
<Style.Triggers>
<DataTrigger Binding="{Binding Path=Content, RelativeSource={RelativeSource Self}, Converter={StaticResource SelectedPeriodConverter}}" Value="True">
<Setter Property="IsSelected" Value="True"/>
</DataTrigger>
</Style.Triggers>
</Style>
</ComboBox.Resources>
thanks Callum
sometimes I see when the binding present an incorrect input shows a red border around the control. How can I modify it or where? And if it's possible, put me an example.
Thanks in advance.
You can do something like this for a TextBox
<Style x:Key="TextBoxWithValidation" TargetType="{x:Type TextBox}" BasedOn="{StaticResource {x:Type TextBox}}">
<Setter Property="Validation.ErrorTemplate" Value="{StaticResource TextBoxValidationTemplate}"/>
<Style.Triggers>
<Trigger Property="Validation.HasError" Value="true">
<Setter Property="Background" Value="{StaticResource BackgroundValidationBrush}"/>
<Setter
Property="ToolTip" Value="{Binding RelativeSource={RelativeSource Mode=Self},
Path=(Validation.Errors)[0].ErrorContent)}"
/>
</Trigger>
</Style.Triggers>
</Style>
Where BackgroundValidationBrush would be say Pink.
Note that the common solution of binding ToolTip to (Validation.Errors)[0].ErrorContent will result in lots of debug spew (technical term) when there are no errors, so you're better off with a converter like this:
[ValueConversion(typeof(ReadOnlyObservableCollection<ValidationError>), typeof(string))]
public class ValidationErrorsToStringConverter : IValueConverter
{
public object Convert(object value, Type targetType, object parameter,
CultureInfo culture)
{
var errors = value as ReadOnlyObservableCollection<ValidationError>;
// If there are no errors then return an empty string.
// This prevents debug exception messages that result from the usual Xaml of "Path=(Validation.Errors)[0].ErrorContent".
// Instead we use "Path=(Validation.Errors), Converter={StaticResource ValidationErrorsConverter}".
if (errors == null)
{
return string.Empty;
}
var errors2 = errors.Select(e => e.ErrorContent).OfType<string>().ToArray();
return errors.Any() ? string.Join("\n", errors2) : string.Empty;
}
public object ConvertBack(object value, Type targetType, object parameter,
CultureInfo culture)
{
throw new NotImplementedException();
}
}
and the we can use
<converters:ValidationErrorsToStringConverter x:Key="ValidationErrorsConverter"/>
<!-- Style to be used as the base style for all text boxes -->
<Style x:Key="TextBoxWithValidation" TargetType="{x:Type TextBox}" BasedOn="{StaticResource {x:Type TextBox}}">
<Setter Property="Validation.ErrorTemplate" Value="{StaticResource TextBoxValidationTemplate}"/>
<Style.Triggers>
<Trigger Property="Validation.HasError" Value="true">
<Setter Property="Background" Value="{StaticResource BackgroundValidationBrush}"/>
<Setter
Property="ToolTip" Value="{Binding RelativeSource={RelativeSource Mode=Self},
Path=(Validation.Errors),
Converter={StaticResource ValidationErrorsConverter}}"
/>
</Trigger>
</Style.Triggers>
</Style>
I think you searching for something like
Data Validation
Can use a sniplet:
<Binding.ValidationRules>
<DataErrorValidationRule/>
</Binding.ValidationRules>
Define your validation rules and in case of failure you will be given a red rectangle
arround the associated control.
Set the attached property Validaton.ErrorTemplate in your TextBox style. (See the docs for an example)
I would like to implement a "NullText" behavior for a TextBlock that is bound to a property in a ViewModel. When that property in the ViewModel is null or empty, I would like to display gray italic text something like "No Data". I'd like this to follow MVVM pattern but I am lost...
Update
So after playing around with the solution James Webster suggested, I got it to work like this...
<UserControl.Resources>
<BooleanToVisibilityConverter x:Key="BooleanToVisibilityConverter" />
<c:NullOrEmptyValueConverter x:Key="NullOrEmptyValueConverter" Text="(No Data)"/>
</UserControl.Resources>
...
<TextBlock Name="SerialNumberTextBlock" Text="{Binding Path=SerialNumber, Converter={StaticResource NullOrEmptyValueConverter}}">
<TextBlock.Resources>
<Style TargetType="TextBlock">
<Style.Triggers>
<DataTrigger Binding="{Binding ElementName=SerialNumberTextBlock, Path=Text}" Value="(No Data)">
<Setter Property="FontStyle" Value="Italic"/>
</DataTrigger>
</Style.Triggers>
</Style>
</TextBlock.Resources>
</TextBlock>
I'd recommend implementing an IValueConverter; if the source value is not null or empty, then pass it through to the TextBlock. If the source value is null or empty, then render your chosen text.
public class NullValueConverter : IValueConverter
{
public object Convert(object value, Type targetType, object parameter, CultureInfo culture)
{
string str = (string)value;
if (str.IsNullOrWhitespace())
{
return "No Data";
}
return str;
}
public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture)
{
... //An empty implementation I expect...
}
}
However I have just realised that you want to set the style as well... hmmm, probably a DataTrigger that sets the style if the value is 'No Data' required I expect;
<TextBlock Text="{Binding Path=SomeProperty, Converter={StaticResource keyToNullValueConverter}">
<TextBlock.Triggers>
<DataTrigger Binding="{Binding Path=Text}" Value="No Data">
<Setter Property="FontStyle" Value="Italic"/>
</DataTrigger>
</TextBlock.Triggers>
</TextBlock>
Something along those lines might work.
I think you don't need to create Converter Class, you can simply write your style code like this.
<Style TargetType="TextBlock">
<Style.Triggers>
<DataTrigger Binding="{Binding ElementName=SerialNumberTextBlock, Path=Text}" Value="{x:Null}">
<Setter Property="FontStyle" Value="Italic"/>
</DataTrigger>
<DataTrigger Binding="{Binding ElementName=SerialNumberTextBlock, Path=Text}" Value="{x:Static System:String.Empty}">
<Setter Property="FontStyle" Value="Italic"/>
</DataTrigger>
</Style.Triggers>
</Style>
Note :- You need to include the system namespace as
xmlns:System="clr-namespace:System;assembly=mscorlib"
You could try to bind to a property that looks thus
private string _textBlockText;
public string TextBlockText
{
get
{
if(string.IsNullOrEmpty(_textBlockText))
{
return "No Data";
}
return _textBlockText;
}
set
{
_textBlockText = value;
}
}
and then use the XAML that James has mentioned for styling. Saves the need for a converter.
Very late to the party, but here is my answer.
<TextBox >
<TextBox.Style>
<Style TargetType="{x:Type TextBox}">
<Setter Property="Text" Value="{Binding MyText, TargetNullValue=No data}"/>
<Style.Triggers>
<DataTrigger Binding="{Binding MyText}" Value="{x:Null}">
<Setter Property="FontStyle" Value="Italic"/>
</DataTrigger>
<DataTrigger Binding="{Binding MyText}" Value="{x:Static System:String.Empty}">
<Setter Property="FontStyle" Value="Italic"/>
</DataTrigger>
<Trigger Property="IsFocused" Value="True">
<Setter Property="Text" Value="{Binding MyText}"/>
</Trigger>
</Style.Triggers>
</Style>
</TextBox.Style>
</TextBox>
My answer assumes that you want the default text to disappear on focus, which is more in line how inputs with default text usually behave. Also this works only for null values. If you need a check for string empty or additional logic, you could instead use a converter like in other provided answers. The key idea is, that you remove null value text or converter in the binding when control gains focus and thus only display the default text when control has no focus. This also prevents the default value flowing back to your view model.
DataTriggers part for font style was kindly borrowed from pchajer's answer.