WPF One DataTrigger with multiple Values - wpf

I have a Slider and a TextBlock on my WPF window. The TextBlock needs to change the background, foreground and font size by the value of the slider, by rang.
I built a converter that receives the value of the slider and returns a 0, 1 or 3 for each group.
public class ValueByRange : IValueConverter
{
public object Convert(object value, System.Type targetType, object parameter, System.Globalization.CultureInfo culture)
{
double.TryParse(value?.ToString(), out double dValue);
if (dValue > 80)
return 2;
else if (dValue > 50)
return 1;
return 0;
}
public object ConvertBack(object value, System.Type targetType, object parameter, System.Globalization.CultureInfo culture)
{
return false;
}
}
My TextBlock looks like this:
<TextBlock Width="30" Text="{Binding ElementName= theSlider, Path=Value}">
<TextBlock.Style>
<Style TargetType="TextBlock">
<Setter Property="Background" Value="Transparent"/>
<Setter Property="Foreground" Value="Black"/>
<Setter Property="FontSize" Value="12"/>
<Setter Property="TextAlignment" Value="Center"/>
<Style.Triggers>
<DataTrigger Binding="{Binding ElementName=theSlider, Path=Value, Converter={StaticResource ValueByRange}}" Value="1">
<Setter Property="Background" Value="Yellow"/>
<Setter Property="Foreground" Value="Black"/>
<Setter Property="FontSize" Value="14"/>
</DataTrigger>
<DataTrigger Binding="{Binding ElementName=theSlider, Path=Value, Converter={StaticResource ValueByRange}}" Value="2">
<Setter Property="Background" Value="Red"/>
<Setter Property="Foreground" Value="White"/>
<Setter Property="FontSize" Value="16"/>
</DataTrigger>
</Style.Triggers>
</Style>
</TextBlock.Style>
</TextBlock>
It's working fine, but it doesn't feel like the best approach. The converter fires 2 time, each for every DataTrigger. I need more than this 3 ranges and that means the converter will fire more times.
It's also not helpful to build a converter for each property, for the same reason.
Is there a way to fire the converter only ones and then check the result (without using code beyond)?
I know this is not the correct syntax, but I mean something like this:
<DataTrigger Binding="{Binding ElementName=theSlider, Path=Value, Converter={StaticResource ValueByRange}}">
<DataTrigger.Value Value ="1">
<Setter Property="Background" Value="Yellow" />
<Setter Property="Foreground" Value="Black" />
<Setter Property="FontSize" Value="14" />
</DataTrigger.Value>
<DataTrigger.Value Value ="2">
<Setter Property="Background" Value="Red" />
<Setter Property="Foreground" Value="White" />
<Setter Property="FontSize" Value="16" />
</DataTrigger.Value>
</DataTrigger>

Well, as Clemens said, there's nothing wrong with having a converter called multiple times.
But if you really want to do it, here's an approach that works:
<Grid>
<Grid.Resources>
<local:ValueByRange x:Key="ValueByRange" />
<Style x:Key="TextBlockInLabelStyle" TargetType="TextBlock">
<Setter Property="Background" Value="Transparent"/>
<Setter Property="Foreground" Value="Black"/>
<Setter Property="FontSize" Value="12"/>
<Setter Property="TextAlignment" Value="Center"/>
<Style.Triggers>
<DataTrigger Binding="{Binding}" Value="1">
<Setter Property="Background" Value="Yellow"/>
<Setter Property="Foreground" Value="Black"/>
<Setter Property="FontSize" Value="14"/>
</DataTrigger>
<DataTrigger Binding="{Binding}" Value="2">
<Setter Property="Background" Value="Red"/>
<Setter Property="Foreground" Value="White"/>
<Setter Property="FontSize" Value="16"/>
</DataTrigger>
</Style.Triggers>
</Style>
</Grid.Resources>
<Grid.RowDefinitions>
<RowDefinition />
<RowDefinition />
<RowDefinition />
<RowDefinition />
</Grid.RowDefinitions>
<Slider Grid.Row="0"
x:Name="Slider1"
Interval="1"
Minimum="0"
Maximum="100" />
<Slider Grid.Row="1"
x:Name="Slider2"
Interval="1"
Minimum="0"
Maximum="100" />
<Label Grid.Row="2" Content="{Binding ElementName=Slider1, Path=Value, Converter={StaticResource ValueByRange}}">
<Label.Resources>
<Style TargetType="{x:Type TextBlock}" BasedOn="{StaticResource TextBlockInLabelStyle}" />
</Label.Resources>
</Label>
<Label Grid.Row="3" Content="{Binding ElementName=Slider2, Path=Value, Converter={StaticResource ValueByRange}}">
<Label.Resources>
<Style TargetType="{x:Type TextBlock}" BasedOn="{StaticResource TextBlockInLabelStyle}" />
</Label.Resources>
</Label>
</Grid>

I want to make an annotation to the answer above:
This makes use of a very confusing and badly documented feature, which is discussed here and confused the hell out of me when reading the answer.
To be clear: I did NOT understand why the TARGETTYPE here was TextBlock, when we clearly use a Label and the answer is discussed in the link above. It has to do with the fact that the valueconverter returns a DOUBLE, which gets treated with the TEXTBLOCK style while a LABEL style in its place would be ignored. I tested it the hard way...

Related

How can I highlight the cell which contains the error?

I have a DataGrid with four columns for which I have defined a style as well as a triggered style for the case the user enters an invalid value.
<Style TargetType="{x:Type DataGridRow}">
<Setter Property="FontSize" Value="16"/>
<Setter Property="FontFamily" Value="ArialMT"/>
<Setter Property="Height" Value="24"/>
<Setter Property="VerticalAlignment" Value="Center"/>
<Setter Property="ValidationErrorTemplate">
<Setter.Value>
<ControlTemplate>
<Grid>
<Ellipse Width="12" Height="12" Fill="Red" Stroke="Black" StrokeThickness="0.5"/>
<TextBlock FontWeight="Bold" Padding="4,0,0,0" Margin="0" VerticalAlignment="Top" Foreground="White" Text="!" />
</Grid>
</ControlTemplate>
</Setter.Value>
</Setter>
<Style.Triggers>
<Trigger Property="Validation.HasError" Value="true">
<Setter Property="BorderThickness" Value="1"/>
<Setter Property="BorderBrush" Value="Red"/>
<Setter Property="IsEnabled" Value="True" />
<Setter Property="ToolTip" Value="{Binding RelativeSource={x:Static RelativeSource.Self}, Path=(Validation.Errors)[0].ErrorContent}"/>
</Trigger>
</Style.Triggers>
</Style>
This works well and the complete DataGridRow is marked as faulty because I used this in the XAML:
<DataGrid.RowValidationRules>
<local:CycleValidationRule ValidationStep="UpdatedValue" />
</DataGrid.RowValidationRules>
Now I want to highlight the DataGridCell with the invalid value explictely additionally (setting the background colour). Hence, I defined another style:
<Style x:Key="cycleErrStyle" TargetType="{x:Type TextBlock}">
<Setter Property="Background" Value="Magenta"/>
<Style.Triggers>
<Trigger Property="Validation.HasError" Value="true" >
<Setter Property="Background" Value="Red" />
<Setter Property="Foreground" Value="White" />
</Trigger>
</Style.Triggers>
</Style>
but this doesn't work.
When I set <Trigger Property="Validation.HasError" Value="false" > to false, the style affects. It seems as if the Validation.HasError property has been reset after validation for the grid's row.
In the XAML I defined this:
<DataGridTextColumn x:Name="TagCycle" Header="Cycle" Binding="{Binding Mode=TwoWay, Path=RawTag.Cycle, UpdateSourceTrigger=PropertyChanged, ValidatesOnDataErrors=True}"
ElementStyle="{StaticResource ResourceKey=cycleErrStyle}" />
How can I highlight the invalid cell additionally to marking the row as faulty?
You could use a DataTrigger that binds to the Validation.HasError attached property of the parent DataGridRow:
<Style x:Key="cycleErrStyle" TargetType="{x:Type TextBlock}">
<Setter Property="Background" Value="Magenta"/>
<Style.Triggers>
<DataTrigger Binding="{Binding Path=(Validation.HasError), RelativeSource={RelativeSource AncestorType=DataGridRow}}" Value="true" >
<Setter Property="Background" Value="Red" />
<Setter Property="Foreground" Value="White" />
</DataTrigger>
</Style.Triggers>
</Style>

Binding TexBox Name to TextBlock Name, Placeholder

Short background on the problem: I am trying to make a readable, xaml only TextBox Placeholder, which is packed to a ResourceDictionary.
At this point - I have made a well working prototype, which looks and used on the page like this:
<Grid>
<TextBox Style="{StaticResource SearchBox}"
Width="325"
x:Name="UsarioDisponiblesSearch"/>
<TextBlock IsHitTestVisible="False"
Text="Search..."
VerticalAlignment="Center"
HorizontalAlignment="Left"
Margin="5,0,0,0"
Foreground="{StaticResource WhiteFadedBrush}">
<TextBlock.Style>
<Style TargetType="{x:Type TextBlock}">
<Setter Property="Visibility" Value="Collapsed"/>
<Style.Triggers>
<DataTrigger Binding="{Binding Text, ElementName=UsarioDisponiblesSearch}" Value="">
<Setter Property="Visibility" Value="Visible"/>
</DataTrigger>
</Style.Triggers>
</Style>
</TextBlock.Style>
</TextBlock>
<Image Source="/img/search.png" Height="15" HorizontalAlignment="Right" Margin="0,0,5,0" />
</Grid>
Grid, that holds the SearchBox consist of 3 elements:
TextBox - which will receive search string;
TextBlock - which actually holds the PlaceHolder. It disapears, as soon as ElementName of TextBox is not an empty string;
Image - custom icon on the right from the SearchBox.
What I want to achieve is a look like this on the page:
<Grid>
<TextBox Style="{StaticResource SearchBox}"
Width="325"
x:Name="UarioDisponiblesSearch"/>
<TextBlock Style="{StaticResource PlaceHolder}"
x:Name="{Binding Text, ElementName=UarioDisponiblesSearch}" />
</Grid>
And like around this styles, described in the ResourceDictionary:
<Style x:Key="SearchBox" TargetType="{x:Type TextBox}">
<Setter Property="Background" Value="Transparent" />
<Setter Property="BorderThickness" Value="0" />
<Setter Property="VerticalAlignment" Value="Center" />
<Setter Property="HorizontalAlignment" Value="Left" />
<Setter Property="Margin" Value="5,0,0,0" />
<Setter Property="Foreground" Value="{StaticResource WhiteBrush}" />
</Style>
<Style x:Key="Placeholder" TargetType="{x:Type TextBlock}">
<Setter Property="IsHitTestVisible" Value="False" />
<Setter Property="Text" Value="Search..." />
<Setter Property="VerticalAlignment" Value="Center" />
<Setter Property="HorizontalAlignment" Value="Center" />
<Setter Property="Margin" Value="5,0,0,0" />
<Setter Property="Foreground" Value="{StaticResource WhiteFadedBrush}" />
<Setter Property="Visibility" Value="Collapsed"/>
<Style.Triggers>
<DataTrigger Binding="{Binding Text, RelativeSource={RelativeSource Self}}" Value="">
<Setter Property="Visibility" Value="Visible"/>
</DataTrigger>
</Style.Triggers>
</Style>
The main obstacle on my way there is the Binding, because I am not really good still in understanding how does it work, and the fact, that at this point three elements will have to share the same Name property (Which I suppose is a pretty huge obstacle as well).
I am not sticking to exactly this construction but I want it to be reusable, useful for the community and neat looking.
This XAML will give a templated TextBox that will display a given message if the Text is empty on the TextBox and it does not have focus. So when you click it the message disappears, and if you enter text (or have bound text) the message will also disappear.
You can do any further fancy bindings and such if you want to make it more re-usable. I.E. you could templatebind the DisplayText.Text to something to allow for dynamic messages.
When I was doing a quick search as I was styling, I stumbled on This, which is almost the same thing. So it would be worth giving fair reference. I prefer to use keys and compartmentalize my templates and styles. In the event I want a slightly different style elsewhere, then i can just do a BasedOn Style (which i do often). It is really 6 of one and half a dozen of another (although I didn't test the linked code).
<ControlTemplate x:Key="SearchMessageTextBoxControlTemplate" TargetType="{x:Type TextBox}">
<Grid>
<Grid x:Name="SearchTextGrid">
<ScrollViewer x:Name="PART_ContentHost" Background="{TemplateBinding Background}" />
<TextBlock x:Name="DisplayText" Text="Type Your Search..."
HorizontalAlignment="Center"
VerticalAlignment="Center"
Opacity="0.5"
Visibility="Hidden"
FontSize="{TemplateBinding FontSize}"/>
</Grid>
</Grid>
<ControlTemplate.Triggers>
<Trigger Property="IsFocused" Value="True">
<Setter TargetName="DisplayText" Property="Visibility" Value="Hidden"/>
</Trigger>
<MultiTrigger>
<MultiTrigger.Conditions>
<Condition Property="IsFocused" Value="False"/>
<Condition Property="Text" Value=""/>
</MultiTrigger.Conditions>
<Setter TargetName="DisplayText" Property="Visibility" Value="Visible"/>
</MultiTrigger>
</ControlTemplate.Triggers>
</ControlTemplate>
<Style x:Key="WaterMarkMessageTextBoxStyle" TargetType="{x:Type TextBox}">
<Setter Property="FontSize" Value="20"/>
<Setter Property="Background" Value="DarkSlateGray"/>
<Setter Property="CaretBrush" Value="White"/>
<Setter Property="Foreground" Value="#F2FFE5"/>
<Setter Property="FontWeight" Value="Normal"/>
<Setter Property="BorderThickness" Value="2"/>
<Setter Property="HorizontalContentAlignment" Value="Center"/>
<Setter Property="VerticalContentAlignment" Value="Center"/>
<Setter Property="TextWrapping" Value="Wrap"/>
<Setter Property="Template" Value="{StaticResource SearchMessageTextBoxControlTemplate}"/>
</Style>
You would use it as follows:
<TextBox Style="{StaticResource WaterMarkMessageTextBoxStyle}"/>

WPF ListView & Converter

I have a ListView with a GridViewColumn that has an image in it, it uses a converter to convert a bool to a ImageSource. It was working perfectly for months and now it suddenly just shows a red dot instead of my image.
Converter:
public object Convert(object value, Type targetType, object parameter, System.Globalization.CultureInfo culture)
{
if ((bool)value)
return new BitmapImage(new Uri(#"/Dionysus.Styling;component/Images/Actions-dialog-ok-apply-icon.png", UriKind.RelativeOrAbsolute));
else return
null;
}
Xaml:
<GridViewColumn Header="">
<GridViewColumn.CellTemplate>
<DataTemplate>
<Image Source="{Binding Path=IsDelayedOrPreferred, Converter={StaticResource DelayConverter}, Mode=TwoWay}" />
</DataTemplate>
</GridViewColumn.CellTemplate>
</GridViewColumn>
Result:
Has anyone seen this behaviour before or know what might have changed. The only recent changes that I made was upgrading to VS 2013 but all other ListViews with Converters
are still working as expected.
Style:
<Style TargetType="{x:Type ListView}">
<Setter Property="BorderBrush" Value="White"/>
<Setter Property="Foreground" Value="Black"/>
<Setter Property="AlternationCount" Value="2"/>
<Setter Property="Background" Value="WhiteSmoke"/>
<EventSetter Event="Loaded" Handler="ListView_Loaded"/>
</Style>
<Style TargetType="{x:Type GridViewColumnHeader}">
<Setter Property="HorizontalContentAlignment" Value="Left"/>
<Setter Property="Margin" Value="1,0"/>
</Style>
<Style TargetType="{x:Type ListViewItem}" x:Key="ListViewStyle">
<Style.Triggers>
<Trigger Property="ItemsControl.AlternationIndex" Value="0">
<Setter Property="Background" Value="WhiteSmoke"></Setter>
</Trigger>
<Trigger Property="ItemsControl.AlternationIndex" Value="1">
<Setter Property="Background" Value="White"></Setter>
</Trigger>
<DataTrigger Binding="{Binding IsFirst}" Value="True">
<Setter Property="Background" Value="LightGreen"></Setter>
</DataTrigger>
<DataTrigger Binding="{Binding IsLastUnpaid}" Value="True">
<Setter Property="Background" Value="LightSalmon"></Setter>
</DataTrigger>
<Trigger Property="IsSelected" Value="True">
<Setter Property="Background" Value="#FF41B1E1"></Setter>
</Trigger>
</Style.Triggers>
<Setter Property="Height" Value="20" />
<Setter Property="HorizontalAlignment" Value="Left"/>
</Style>
UPDATE:
I recently changed from a ListView to a DataGrid and then I see a little exclamation mark as if there is data validation errors on the DataGrid.
Any ideas?

Unable to change background for a Wpf toolkit autocompletebox?

How should i change the background of the wpftoolkit autocompletebox.? I tried by subscribing to enabledchanged event and changed the background of the control which is not changing at all.Can any body help.
<WpfToolkit:AutoCompleteBox x:Name="txtBxSearch" Background="White" IsTextCompletionEnabled="True" MinimumPrefixLength="0" FilterMode="Contains" Height="24" Width="150" KeyDown="txtBxSearch_KeyDown" >
<WpfToolkit:AutoCompleteBox.TextBoxStyle>
<Style TargetType="TextBox">
<Setter Property="FocusVisualStyle" Value="{x:Null}"/>
<Setter Property="FocusManager.FocusedElement" Value="{Binding RelativeSource={RelativeSource Self}}"/>
</Style>
</WpfToolkit:AutoCompleteBox.TextBoxStyle>
</WpfToolkit:AutoCompleteBox>
You can do this in XAML:
<toolkit:AutoCompleteBox
ToolTip="Enter the path of an assembly."
x:Name="tbAssembly" Height="27" Width="200"
Populating="tbAssembly_Populating"
>
<toolkit:AutoCompleteBox.Style>
<Style TargetType="toolkit:AutoCompleteBox">
<Style.Triggers>
<DataTrigger Binding="{Binding ElementName=tbAssembly, Path=IsEnabled}" Value="True">
<Setter Property="Background" Value="Yellow" />
<Setter Property="Foreground" Value="Red" />
</DataTrigger>
<DataTrigger Binding="{Binding ElementName=tbAssembly, Path=IsEnabled}" Value="False">
<Setter Property="Background" Value="Black" />
<Setter Property="Foreground" Value="Yellow" />
</DataTrigger>
</Style.Triggers>
</Style>
</toolkit:AutoCompleteBox.Style>
</toolkit:AutoCompleteBox>

Enabling or disabling validation upon context

Introduction
I have two TextBox in my view, each binded to some properties in my view-model (Property1, Property2).
TextBox are alternatively enabled upon some boolean, and properties, are validated using IDataErrorInfo in the view-model + some styling in the view.
Problem
I would like to disable validation style when items are disabled.
NB1: Currently, solution I found is to change validation scheme directly in the view-model, but this requires to notify for property changes in order to force the view to re-read IDataErrorInfo (while properties haven't really changed, only selector ...)
NB2: My problem is really close to this one, but the description and the solutions are far too complex for me to really get the point.
Pseudo-Code
<UserControl
<UserControl.Resources>
<Style TargetType="{x:Type Control}" x:Key="ControlValidationStyle">
...
</Style>
</UserControl.Resources>
...
<TextBox
Text="{Binding Property1,
ValidatesOnDataErrors=True,
UpdateSourceTrigger=PropertyChanged}"
IsEnabled="{Binding IsMode1}"
Style="{StaticResource ControlValidationStyle}"
/>
<TextBox
Text="{Binding Property2,
ValidatesOnDataErrors=True,
UpdateSourceTrigger=PropertyChanged}"
IsEnabled="{Binding IsMode1,
Converter={StaticResource BoolInverse}}"
Style="{StaticResource ControlValidationStyle}"
/>
</UserControl>
ControlValidationStyle
<Style TargetType="{x:Type Control}" x:Key="ControlValidationStyle">
<Style.Resources>
<Style TargetType="ToolTip">
<Setter Property="Background" Value="Tomato" />
<Setter Property="BorderBrush" Value="Red" />
<Setter Property="BorderThickness" Value="2" />
<Setter Property="Foreground" Value="white" />
</Style>
</Style.Resources>
<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="Bisque"/>
<Setter Property="BorderBrush" Value="Red" />
<Setter Property="BorderThickness" Value="2" />
<Setter Property="Foreground" Value="Red" />
</Trigger>
</Style.Triggers>
</Style>
Why wont you use MultiTrigger instead of Trigger:
<MultiTrigger>
<MultiTrigger.Conditions>
<Condition Property="Validation.HasError" Value="true" />
<Condition Property="IsEnabled" Value="true" />
</MultiTrigger.Conditions>
<Setter .../>
</MultiTrigger>

Resources