I have following code. Here listClear is an object, which is filled by ViewModel. I am using properties of this object to fill a Grid. In below code what property should I use to make Button disable in DataTrigger. I want Button to be disabled when Grid is empty, otherwise it should be enabled.
<Button Grid.Column="3" Margin="2" Command="{Binding Path=ClearCommand}" Content="Clear">
<Button.Style>
<Style TargetType="Button">
<Setter Property="IsEnabled" Value="True" />
<Style.Triggers>
<DataTrigger Binding="{Binding Path=listClear}" Value="">
<Setter Property="IsEnabled" Value="False"/>
</DataTrigger>
</Style.Triggers>
</Style>
</Button.Style>
</Button>
You can use the List Count property for this to indicate empty
Example:
public partial class MainWindow : Window
{
private ObservableCollection<string> myVar = new ObservableCollection<string>();
public MainWindow()
{
InitializeComponent();
MyList.Add("test");
}
public ObservableCollection<string> MyList
{
get { return myVar; }
set { myVar = value; }
}
private void Button_Click(object sender, RoutedEventArgs e)
{
MyList.Clear();
}
}
Xaml:
<Window x:Class="WpfApplication11.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:local="clr-namespace:WpfApplication11"
Title="MainWindow" Height="136.3" Width="208" x:Name="UI">
<Grid DataContext="{Binding ElementName=UI}">
<Button Content="Clear" Click="Button_Click">
<Button.Style>
<Style TargetType="Button">
<Setter Property="IsEnabled" Value="True" />
<Style.Triggers>
<DataTrigger Binding="{Binding Path=MyList.Count}" Value="0">
<Setter Property="IsEnabled" Value="False"/>
</DataTrigger>
</Style.Triggers>
</Style>
</Button.Style>
</Button>
</Grid>
</Window>
If your only using IEnumerable<T> its a bit more difficult because IEnumerable has no public properties to bint to, you would have to make a converter.
Something like this
public class IsEmptyConverter : IValueConverter
{
public object Convert(object value, Type targetType, object parameter, CultureInfo culture)
{
if (value is IEnumerable)
{
var enumerable = (IEnumerable)value;
foreach (var item in enumerable)
{
return false;
}
}
return true;
}
public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture)
{
throw new NotImplementedException();
}
}
Xaml:
<Window x:Class="WpfApplication11.MainWindow"
xmlns:local="clr-namespace:Namespace for converter"
....
....
<Window.Resources>
<local:IsEmptyConverter x:Key="IsEmptyConverter" />
</Window.Resources>
....
....
<DataTrigger Binding="{Binding Path=MyList, Converter={StaticResource IsEmptyConverter}}" Value="true">
Related
I am trying to display a list with different object types (in the example dogs and cats). The types should be visually different (in the example realized by red and blue font in the DataTemplate in MainWindow.xaml).
Additionally I want that cats can neither be selected, nor that they get a colored background at the IsMouseOver trigger. However, I am not succeeding in the latter. I have left a few of my attempts as comments in MainWindow.xaml.
MainWindow.xaml
<Window.Resources>
<local:AnimalToFocusConverter x:Key="AnimalToFocusConverter" />
<local:AnimalToBackgroundColorConverter x:Key="AnimalToBackgroundColorConverter" />
<DataTemplate DataType="{x:Type local:Dog}">
<TextBlock Text="{Binding Path=Name}" Foreground="Red" />
</DataTemplate>
<DataTemplate DataType="{x:Type local:Cat}">
<TextBlock Text="{Binding Path=Name}" Foreground="Blue" />
</DataTemplate>
</Window.Resources>
<Grid>
<ListView ItemsSource="{Binding AnimalCollection}">
<ListView.ItemContainerStyle>
<Style TargetType="ListViewItem">
<Setter Property="Background" Value="Transparent" />
<!--<Setter Property="Template">
<Setter.Value>
<ControlTemplate TargetType="{x:Type ListViewItem}">
<Grid Background="Transparent">
<ContentPresenter />
</Grid>
</ControlTemplate>
</Setter.Value>
</Setter>
<Style.Triggers>
<Trigger Property="IsMouseOver" Value="True">
<Setter Property="Background" Value="{Binding Converter={StaticResource AnimalToBackgroundColorConverter}}" />
</Trigger>
</Style.Triggers>-->
</Style>
</ListView.ItemContainerStyle>
</ListView>
</Grid>
Animals.cs
public class Animals
{
public ObservableCollection<IAnimal> AnimalCollection { get; set; } = new ObservableCollection<IAnimal>();
public Animals()
{
AnimalCollection.Add(new Dog());
AnimalCollection.Add(new Dog());
AnimalCollection.Add(new Cat());
AnimalCollection.Add(new Dog());
AnimalCollection.Add(new Cat());
}
}
IAnimal.cs
public interface IAnimal
{
}
Dog.cs / Cat.cs
public class Dog : ObservableObject, IAnimal
{
private static int counter = 0;
public string Name { get; set; }
public Dog()
{
Name = $"Dog{++counter}";
}
}
Converter.cs
[ValueConversion(typeof(IAnimal), typeof(bool))]
public class AnimalToFocusConverter : IValueConverter
{
public object Convert(object value, Type targetType, object parameter, CultureInfo culture)
{
return value switch
{
Dog => true,
Cat => false,
_ => false,
};
}
// ...
[ValueConversion(typeof(IAnimal), typeof(SolidColorBrush))]
public class AnimalToBackgroundColorConverter : IValueConverter
{
public object Convert(object value, Type targetType, object parameter, CultureInfo culture)
{
return value switch
{
Dog => new SolidColorBrush(Colors.LightBlue),
Cat => new SolidColorBrush(Colors.Transparent),
_ => new SolidColorBrush(Colors.LightBlue),
};
}
You could set the IsHitTestVisible property of the ListViewItem containers for the Cat objects to false:
<ListView ItemsSource="{Binding AnimalCollection}">
<ListView.ItemContainerStyle>
<Style TargetType="ListViewItem">
<Setter Property="Background" Value="Transparent" />
<Setter Property="IsHitTestVisible"
Value="{Binding Converter={StaticResource AnimalToFocusConverter}}" />
</Style>
</ListView.ItemContainerStyle>
</ListView>
I have the following piece of code.
<Button.Style>
<Style BasedOn="{StaticResource {x:Static ToolBar.ButtonStyleKey}}"
TargetType="{x:Type Button}">
<Setter Property="Visibility" Value="Collapsed" />
<Style.Triggers>
<DataTrigger Binding="{Binding Status, Mode=OneWay}"
Value="{x:Static comm:DeviceStatus.Standby}">
<Setter Property="Visibility" Value="Visible" />
</DataTrigger>
<DataTrigger Binding="{Binding Status, Mode=OneWay}"
Value="{x:Static comm:DeviceStatus.Busy}">
<Setter Property="Visibility" Value="Visible" />
</DataTrigger>
<DataTrigger Binding="{Binding Status, Mode=OneWay}"
Value="{x:Static comm:DeviceStatus.Offline}">
<Setter Property="Visibility" Value="Visible" />
</DataTrigger>
<DataTrigger Binding="{Binding Status, Mode=OneWay}"
Value="{x:Static comm:DeviceStatus.StartingStream}">
<Setter Property="Visibility" Value="Visible" />
</DataTrigger>
<DataTrigger Binding="{Binding Status, Mode=OneWay}"
Value="{x:Static comm:DeviceStatus.Connecting}">
<Setter Property="Visibility" Value="Visible" />
</DataTrigger>
<DataTrigger Binding="{Binding Status, Mode=OneWay}"
Value="{x:Static comm:DeviceStatus.Disconnecting}">
<Setter Property="Visibility" Value="Visible" />
</DataTrigger>
<DataTrigger Binding="{Binding Status, Mode=OneWay}"
Value="{x:Static comm:DeviceStatus.DownloadingFiles}">
<Setter Property="Visibility" Value="Visible" />
</DataTrigger>
</Style.Triggers>
</Style>
</Button.Style>
The button is hidden by default and is visible when a property in the viewmodel has one out of a few values. The property is of an enum type called DeviceStatus.
Basically it conducts an OR operation on the provided triggers.
So, the visibility of the button is determined by: Status == StandBy || Status == Busy || ...
How can I implement this without having to have 8 triggers?
I would like to have something like the following:
<DataTrigger Binding="{Binding Status, Mode=OneWay}">
<DataTrigger.AnyValue>
<AnyValueItem Value="{x:Static comm:DeviceStatus.Standby}" />
<AnyValueItem Value="{x:Static comm:DeviceStatus.Busy}" />
<AnyValueItem Value="{x:Static comm:DeviceStatus.Offline}" />
...
</DataTrigger.AnyValue>
<Setter Property="Visibility" Value="Visible" />
</DataTrigger>
Where the Visibility of the Button is set to Visible if the binding gets ANY of the supplied values.
Thanks!
After reading the answers I got some ideas and found a satisfying extendable solution.
First I created the following converter.
public sealed class EnumOrConverter : IValueConverter
{
public object Convert(object value, Type targetType, object parameter, CultureInfo culture)
{
DeviceStatus status = (DeviceStatus)value;
DeviceStatus[] statuses = parameter as DeviceStatus[];
if (statuses.Any(s => s == status))
{
return Visibility.Visible;
}
else
{
return Visibility.Collapsed;
}
}
public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture)
{
throw new NotImplementedException();
}
}
Second I simply added the following code to the XAML.
<Button Command="{Binding SomeCommand}">
<Button.Style>
<Style BasedOn="{StaticResource {x:Static ToolBar.ButtonStyleKey}}"
TargetType="{x:Type Button}">
<Setter Property="Visibility">
<Setter.Value>
<Binding Path="Status" Converter="{StaticResource EnumOrConverter}">
<Binding.ConverterParameter>
<x:Array Type="{x:Type comm:DeviceStatus}">
<x:Static Member="comm:DeviceStatus.Standby" />
<x:Static Member="comm:DeviceStatus.Busy" />
<x:Static Member="comm:DeviceStatus.Offline" />
<x:Static Member="comm:DeviceStatus.StartingStream" />
<x:Static Member="comm:DeviceStatus.Connecting" />
<x:Static Member="comm:DeviceStatus.Disconnecting" />
<x:Static Member="comm:DeviceStatus.DownloadingFiles" />
</x:Array>
</Binding.ConverterParameter>
</Binding>
</Setter.Value>
</Setter>
</Style>
</Button.Style>
</Button>
This allows me to reuse the converter logic for many other buttons and no adding of variables or properties to the ViewModel. What also happened is that the converter encapsulates the logic of "OR:ing" the parameters and "defaulting to Visibility.Collapsed". And adding a new parameter simply requires one line of code in XAML which is where it belongs.
The DataTrigger has no support for some kind of list of possible values. It only has a single Value property.
The easiest way to work around this would be to add a property to the view model that returns a value that indicates whether to display the Button:
public bool IsVisible => Status == Standby || Status == Busy || ...;
XAML:
<DataTrigger Binding="{Binding IsVisible, Mode=OneWay}" Value="True">
<Setter Property="Visibility" Value="Visible" />
</DataTrigger>
Another option may be to use a converter as suggested by #l33t. You would then simply move the logic out of the view model, e.g.:
public class Converter : IValueConverter
{
public object Convert(object value, Type targetType, object parameter, CultureInfo culture)
{
ViewModel vm = value as ViewModel;
return (vm != null && (vm.Status == Standby || vm.Status == Busy || ...)) ? Vsibility.Visible : Visibility.Collapsed;
}
public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture)
{
throw new NotImplementedException();
}
}
XAML:
<Style ...>
<Style.Resources>
<local:Converter x:Key="conv" />
</Style>
<Setter Property="Visibility" Value="{Binding Path=., Converter={StaticResource converter}}" />
You could just make a property:
private DeviceStatus _Status;
public DeviceStatus Status
{
get { return _Status; }
set
{
this.Set(ref _Status, value);
RaisePropertyChanged(nameof(this.StatusVisibility));
}
}
public Visibility StatusVisibility
{
get
{
switch (_Status)
{
case DeviceStatus.Busy: //add other statuses here
return Visibility.Visible;
}
return Visibility.Collapsed;
}
}
and then in your button:
<Button Content="MyButton" Visibility="{Binding StatusVisibility}"></Button>
For others. An answer that's similar to Lugvig W's answer but keeps with DataTriggers and is more generalised
ValueConverter
public sealed class AnyMatchConverter : IValueConverter
{
public object Convert(object value, Type targetType, object parameter, CultureInfo culture)
{
if (parameter is Array objects && value is not null)
{
return Array.BinarySearch(objects, value) != -1;
}
return DependencyProperty.UnsetValue;
}
...
XAML
<DataTrigger Value="True">
<DataTrigger.Binding>
<Binding Path="Status" Converter="{StaticResource AnyMatchConverter}">
<Binding.ConverterParameter>
<x:Array Type="{x:Type comm:DeviceStatus}">
<x:Static Member="comm:DeviceStatus.Standby" />
<x:Static Member="comm:DeviceStatus.Busy" />
...
</x:Array>
</Binding.ConverterParameter>
</Binding>
</DataTrigger.Binding>
...
I have the startup window in a WPF application, and I have this code in my view:
<Window x:Class="MyView"
Name="ucPrincipal"
Title="{Binding Titulo}"
Visibility="{Binding EsUpdaterVisible, Mode=TwoWay}">
<Window.Resources>
<ResourceDictionary>
<ResourceDictionary.MergedDictionaries>
<ResourceDictionary Source="../Recursos/Diccionarios/Converters.xaml" />
</ResourceDictionary.MergedDictionaries>
</ResourceDictionary>
</Window.Resources>
<Button Content="Aceptar" HorizontalAlignment="Left" Margin="10,145,0,0" VerticalAlignment="Top" Width="75">
<i:Interaction.Triggers>
<i:EventTrigger EventName="PreviewMouseLeftButtonDown">
<cmd:EventToCommand Command="{Binding AceptarCommand}" PassEventArgsToCommand="True" />
</i:EventTrigger>
</i:Interaction.Triggers>
</Button>
My ViewModel:
private RelayCommand _aceptarCommand;
public RelayCommand AceptarCommand
{
get { return _aceptarCommand ?? (_aceptarCommand = new RelayCommand(aceptarCommand)); }
}
private void aceptarCommand()
{
try
{
EsUpdaterVisible = false;
Titulo = "Después de aceptar.";
}
catch { throw; }
}
private bool _esUpdaterVisible = true;
public bool EsUpdaterVisible
{
get { return _esUpdaterVisible; }
set
{
if (_esUpdaterVisible != value)
{
_esUpdaterVisible = value;
base.RaisePropertyChangedEvent("EsUpdaterVisible");
}
}
}
private string _titulo = "Inicio";
public string Titulo
{
get { return _titulo; }
set
{
if (_titulo != value)
{
_titulo = value;
base.RaisePropertyChangedEvent("Titulo");
}
}
}
When I click the aceptar button, the title of the window is changed, but the windows is still visible.
I would like to hide the window in some cases from the view model. How I could do that?
Thanks.
If you wouldn't like to use converter, just xaml part:
<Window x:Class="MyView"
Name="ucPrincipal"
Title="{Binding Titulo}">
<Window.Style>
<Style TargetType="Window">
<Style.Triggers>
<DataTrigger Binding="{Binding EsUpdaterVisible,UpdateSourceTrigger=PropertyChanged}" Value="True">
<Setter Property="Visibility" Value="Visible"/>
</DataTrigger>
<DataTrigger Binding="{Binding EsUpdaterVisible,UpdateSourceTrigger=PropertyChanged}" Value="False">
<Setter Property="Visibility" Value="Collapsed"/> <!-- use hide instead of collapsed if you would like to open again this instance of window after close. -->
</DataTrigger>
</Style.Triggers>
</Style>
</Window.Style>
Visibility is not a boolean type.
You can use a converter to accomplish that.
Converter:
[ValueConversion(typeof(bool), typeof(Visibility))]
public class VisibilityConverter : IValueConverter
{
public object Convert(object value, Type targetType, object parameter, CultureInfo culture)
{
return (bool)value ? Visibility.Visible : Visibility.Collapsed;
}
public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture)
{
throw new NotImplementedException();
}
}
Than your XAML will look something like this:
<Window x:Class="MyView"
Name="ucPrincipal"
Title="{Binding Titulo}"
Visibility="{Binding EsUpdaterVisible, Converter={StaticResource visibilityConverter}}">
I am making a WPF application using the MVVM design pattern. Part of the app is a signal strength bar. We created it with just a rectangular user control and created a 4 column grid, so all we need to do is change either the background or foreground color of the control.
My idea on how to do this is a simply store Boolean values for each of the 4 sections and use a value converter. However, there are 3 instances of this control, each with a different color. How can I pass the needed color into the converter? I know converters have a parameter argument, but I haven't been able to find any examples using it, so I'm not even sure if the parameter argument is what I'm looking for.
Your case may not be best addressed by the method you've chosen (it makes it hard to parameterize the colors of the segments), but your specific question is a good one, so I'll answer it.
As you've found, it's tough to pass anything but a string to ConverterParameter. but you don't have to. If you derive a converter from MarkupExtension, you can assign named and typed properties when you use it, and also not have to create it as a resource (indeed, creating it as a resource would break the thing, since that would be a shared instance and the properties are initialized when it's created). Since the XAML parser knows the types of the properties declared on the class, it will apply the default TypeConverter for Brush, and you'll get the exact same behavior as if you were assigning "PapayaWhip" to "Border.Background" or anything else.
This works with any type, of course, not just Brush.
namespace HollowEarth.Converters
{
public class BoolBrushConverter : MarkupExtension, IValueConverter
{
public Brush TrueBrush { get; set; }
public Brush FalseBrush { get; set; }
public object Convert(object value, Type targetType, object parameter, CultureInfo culture)
{
return System.Convert.ToBoolean(value) ? TrueBrush : FalseBrush;
}
public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture)
{
throw new NotImplementedException();
}
public override object ProvideValue(IServiceProvider serviceProvider)
{
return this;
}
}
}
Usage:
<TextBox
xmlns:hec="clr-namespace:HollowEarth.Converters"
Foreground="{Binding MyFlagProp, Converter={hec:BoolBrushConverter TrueBrush=YellowGreen, FalseBrush=DodgerBlue}}"
/>
You could give BoolBrushConverter a constructor that takes parameters, too.
public BoolBrushConverter(Brush tb, Brush fb)
{
TrueBrush = tb;
FalseBrush = fb;
}
And in XAML...
<TextBox
xmlns:hec="clr-namespace:HollowEarth.Converters"
Foreground="{Binding MyFlagProp, Converter={hec:BoolBrushConverter YellowGreen, DodgerBlue}}"
/>
I don't think that's a good fit for this case. But sometimes the semantics are so clear, the property name is unnecessary. {hec:GreaterThan 4.5}, for example.
UPDATE
Here's a complete implementation of a SignalBars control. This has five segments to your four, but you can easily remove one; that's only in the template, and the Value property is a double that could be subdivided any way you like (again, in the template).
SignalBars.cs
using System;
using System.ComponentModel;
using System.Windows.Media;
using System.Globalization;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Data;
using System.Windows.Markup;
namespace HollowEarth
{
public class SignalBars : ContentControl
{
static SignalBars()
{
DefaultStyleKeyProperty.OverrideMetadata(typeof(SignalBars), new FrameworkPropertyMetadata(typeof(SignalBars)));
}
#region Value Property
public double Value
{
get { return (double)GetValue(ValueProperty); }
set { SetValue(ValueProperty, value); }
}
public static readonly DependencyProperty ValueProperty =
DependencyProperty.Register("Value", typeof(double), typeof(SignalBars),
new PropertyMetadata(0d));
#endregion Value Property
#region InactiveBarFillBrush Property
[Bindable(true)]
[Category("Appearance")]
[DefaultValue("White")]
public Brush InactiveBarFillBrush
{
get { return (Brush)GetValue(InactiveBarFillBrushProperty); }
set { SetValue(InactiveBarFillBrushProperty, value); }
}
public static readonly DependencyProperty InactiveBarFillBrushProperty =
DependencyProperty.Register("InactiveBarFillBrush", typeof(Brush), typeof(SignalBars),
new FrameworkPropertyMetadata(Brushes.White));
#endregion InactiveBarFillBrush Property
}
public class ComparisonConverter : MarkupExtension, IMultiValueConverter
{
public virtual object Convert(object[] values, Type targetType, object parameter, CultureInfo culture)
{
if (values.Length != 2)
{
throw new ArgumentException("Exactly two values are expected");
}
var d1 = GetDoubleValue(values[0]);
var d2 = GetDoubleValue(values[1]);
return Compare(d1, d2);
}
/// <summary>
/// Overload in subclasses to create LesserThan, EqualTo, whatever.
/// </summary>
/// <param name="a"></param>
/// <param name="b"></param>
/// <returns></returns>
protected virtual bool Compare(double a, double b)
{
throw new NotImplementedException();
}
protected static double GetDoubleValue(Object o)
{
if (o == null || o == DependencyProperty.UnsetValue)
{
return 0;
}
else
{
try
{
return System.Convert.ToDouble(o);
}
catch (Exception)
{
return 0;
}
}
}
public object[] ConvertBack(object value, Type[] targetTypes, object parameter, CultureInfo culture)
{
throw new NotImplementedException();
}
public override object ProvideValue(IServiceProvider serviceProvider)
{
return this;
}
}
public class GreaterThan : ComparisonConverter
{
protected override bool Compare(double a, double b)
{
return a > b;
}
}
}
Themes\Generic.xaml
<ResourceDictionary
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
>
<Style
xmlns:he="clr-namespace:HollowEarth"
TargetType="{x:Type he:SignalBars}"
>
<!-- Foreground is the bar borders and the fill for "active" bars -->
<Setter Property="Foreground" Value="Black" />
<Setter Property="InactiveBarFillBrush" Value="White" />
<Setter Property="Template">
<Setter.Value>
<ControlTemplate TargetType="Control">
<ControlTemplate.Resources>
<Style TargetType="Rectangle">
<Setter Property="Width" Value="4" />
<Setter Property="VerticalAlignment" Value="Bottom" />
<Setter Property="Stroke" Value="{Binding Foreground, RelativeSource={RelativeSource TemplatedParent}}" />
<Setter Property="StrokeThickness" Value="1" />
<Setter Property="Fill" Value="{Binding InactiveBarFillBrush, RelativeSource={RelativeSource TemplatedParent}}" />
<Setter Property="Margin" Value="0,0,1,0" />
<Style.Triggers>
<DataTrigger Value="True">
<DataTrigger.Binding>
<MultiBinding Converter="{he:GreaterThan}">
<MultiBinding.Bindings>
<Binding
Path="Value"
RelativeSource="{RelativeSource TemplatedParent}"
/>
<Binding
Path="Tag"
RelativeSource="{RelativeSource Self}"
/>
</MultiBinding.Bindings>
</MultiBinding>
</DataTrigger.Binding>
<Setter Property="Fill" Value="{Binding Foreground, RelativeSource={RelativeSource TemplatedParent}}" />
</DataTrigger>
</Style.Triggers>
</Style>
</ControlTemplate.Resources>
<ContentControl
ContentTemplate="{Binding ContentTemplate, RelativeSource={RelativeSource TemplatedParent}}">
<StackPanel
Orientation="Horizontal"
SnapsToDevicePixels="True"
UseLayoutRounding="True"
>
<!-- Set Tags to the minimum threshold value for turning the segment "on" -->
<!-- Remove one of these to make it four segments. To make them all equal height, remove Height here
and set a fixed height in the Rectangle Style above. -->
<Rectangle Height="4" Tag="0" />
<Rectangle Height="6" Tag="2" />
<Rectangle Height="8" Tag="4" />
<Rectangle Height="10" Tag="6" />
<Rectangle Height="12" Tag="8" />
</StackPanel>
</ContentControl>
</ControlTemplate>
</Setter.Value>
</Setter>
</Style>
</ResourceDictionary>
Example XAML:
<StackPanel
xmlns:he="clr-namespace:HollowEarth"
Orientation="Vertical"
HorizontalAlignment="Left"
>
<Slider
Minimum="0"
Maximum="10"
x:Name="SignalSlider"
Width="200"
SmallChange="1"
LargeChange="4"
TickFrequency="1"
IsSnapToTickEnabled="True"
/>
<he:SignalBars
HorizontalAlignment="Left"
Value="{Binding Value, ElementName=SignalSlider}"
InactiveBarFillBrush="White"
Foreground="DarkRed"
/>
</StackPanel>
Usually you may need a ColorToBrushConverter, but not a BooleanToColor.
I would simply create different styles with triggers for each bar, like
<Style.Triggers>
<DataTrigger Binding="{Binding IsOffline}" Value="True">
<Setter Property="Background" Value="Salmon" />
</DataTrigger>
<DataTrigger Binding="{Binding IsPrinting}" Value="True">
<!--<Setter Property="Background" Value="Honeydew" />-->
<Setter Property="Background" Value="LightGreen" />
</DataTrigger>
</Style.Triggers>
So here's the idea.
If the textbox is empty, 'DataTrigger' should set the outline (BorderBrush) to red.
If the textbox is not empty / has text; then the dataTrigger should set the BorderBrush to Blue.
xmlns:conv="clr-namespace:WpfApplication1"
Title="MainWindow" Height="350" Width="525">
<Window.Resources>
<!-- conv is referenced in the "clr-namespace:WpfApplication1" namespace. It's bassically a referal to a converter I'm using -->
<conv:IsNullConverter x:Key="isNullConverter"/>
<Style TargetType="{x:Type TextBox}">
<Style.Triggers>
<!-- if the textbox is empty, then the setter should set the border colour to red-->
<DataTrigger Binding="{Binding Words, UpdateSourceTrigger=PropertyChanged, Converter={StaticResource isNullConverter}}" Value="True">
<Setter Property="BorderBrush" Value="Red"/>
</DataTrigger>
<!-- If it has text inside it, setter should set the border colour to blue -->
<DataTrigger Binding="{Binding Words, UpdateSourceTrigger=PropertyChanged, Converter={StaticResource isNullConverter}}" Value="False">
<Setter Property="BorderBrush" Value="Blue"/>
</DataTrigger>
</Style.Triggers>
</Style>
</Window.Resources>
<Grid>
<TextBox x:Name="first" FontSize="14" TabIndex="1" Background="Black" BorderThickness="5" Foreground="White" Margin="29,10,132,272" />
</Grid>
Because it's not possible for datatriggers to see if a value is NOT null indipendently, I had to add some code to help it do that.
using System.Windows.Data;
using System.Globalization;
using System.ComponentModel;
namespace WpfApplication1
{
public class IsNullConverter : IValueConverter, INotifyPropertyChanged
{
// The string that the 'conv:' checks against
private string FOO;
// The DataTriggrer is bound to 'Words'
public string Words
{
get
{
return FOO;
}
set
{
if (FOO != value)
{
FOO = value;
RaisePropertyChanged("Words");
}
}
}
private void RaisePropertyChanged(string prop)
{
if (PropertyChanged == null)
{
PropertyChanged(this, new PropertyChangedEventArgs(prop));
}
}
public event PropertyChangedEventHandler PropertyChanged;
public string Error
{
get { return null; }
}
// This is the 'Convert' Parameter conv checks against. Here is the problem is
public object Convert(object value, Type targetType, object parameter, CultureInfo culture)
{
//This checks against the FOO string at the top of this class. Not the FOO in 'Words' get & set string
if (FOO == null)
{
value = true;
}
// So even if 'Words' raises the property changed, The origional FOO string remains unaffected.
// So the Datatrigger is never fired
if (FOO != null)
{
value = false;
}
return value;
}
public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture)
{
throw new InvalidOperationException("IsNullConverter can only be used OneWay.");
}
}
}
The thing is if I append the string FOO like;
private string FOO = "Something";
The Datatrigger fires at runtime and changes the outline colour to blue.
But I need the colour to be based on the textbox content rather than what I directly declare the string as.
I tried binding the data Trigger to the 'Words' string but the outline colour remains red, empty or not.
And suggestions? I really don't mind if I have to completely throw this code upside down if there's a better way of doing it.
Here you go:
<TextBox x:Name="first" FontSize="14" TabIndex="1" Background="Black" BorderThickness="5" Foreground="White" Margin="29,10,132,272">
<TextBox.Style>
<Style TargetType="TextBox">
<Setter Property="BorderBrush" Value="Blue"/>
<Style.Triggers>
<DataTrigger Binding="{Binding Text, RelativeSource={RelativeSource Self}}" Value="">
<Setter Property="BorderBrush" Value="Red"/>
</DataTrigger>
<DataTrigger Binding="{Binding Text, RelativeSource={RelativeSource Self}}" Value="{x:Null}">
<Setter Property="BorderBrush" Value="Red"/>
</DataTrigger>
</Style.Triggers>
</Style>
</TextBox.Style>
</TextBox>
Bind to the TextBox.Text property by using RelativeSource:
<DataTrigger Binding="{Binding Text, RelativeSource={RelativeSource Self}, Converter={StaticResource isNullConverter}}" Value="True">
<!--apropriate setter-->
</DataTrigger>
This trigger sets the BorderBrush if Text is empty. To set the border brush when Text is not empty, just use normal Setter, without DataTrigger.
Also, note that within your ValueConverter you should use String.IsNullOrEmpty instead of plain NULL comparison.
You can simplify your converter to look something like
public class IsNullConverter : IValueConverter
{
public object Convert(object value, Type targetType, object parameter, CultureInfo culture)
{
return String.IsNullOrEmpty((string) value);
}
public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture)
{
throw new InvalidOperationException("IsNullConverter can only be used OneWay.");
}
}
and then in style bind TextBox.Text via converter
<Window.Resources>
<conv:IsNullConverter x:Key="isNullConverter"/>
<Style TargetType="{x:Type TextBox}">
<Setter Property="BorderBrush" Value="Blue"/>
<Style.Triggers>
<DataTrigger Binding="{Binding RelativeSource={RelativeSource Self}, Path=Text, Converter={StaticResource isNullConverter}}" Value="True">
<Setter Property="BorderBrush" Value="Red"/>
</DataTrigger>
</Style.Triggers>
</Style>
</Window.Resources>
this will work but only if TextBox is not focused as then default template will take over and change BorderBrush. You can make it work but then you'll need to change default template as well where simpliest template would be another Setter in your Style
<Setter Property="Template">
<Setter.Value>
<ControlTemplate TargetType="{x:Type TextBox}">
<Border
BorderBrush="{TemplateBinding BorderBrush}"
Background="{TemplateBinding Background}"
BorderThickness="{TemplateBinding BorderThickness}">
<ScrollViewer x:Name="PART_ContentHost"/>
</Border>
</ControlTemplate>
</Setter.Value>
</Setter>