将颜色传递给 IValueConverter
Pass Color to IValueConverter
我正在使用 MVVM 设计模式制作 WPF 应用程序。该应用程序的一部分是信号强度条。我们只用一个矩形用户控件创建了它,并创建了一个 4 列网格,因此我们需要做的就是更改控件的背景色或前景色。
我关于如何做到这一点的想法是简单地为 4 个部分中的每一个存储布尔值并使用值转换器。但是,此控件有 3 个实例,每个实例都有不同的颜色。如何将所需的颜色传递给转换器?我知道转换器有一个参数参数,但我没能找到任何使用它的例子,所以我什至不确定参数参数是否是我要找的。
通常您可能需要 ColorToBrushConverter
,但不需要 BooleanToColor。
我会简单地为每个栏创建带有触发器的不同样式,例如
<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>
您选择的方法可能无法很好地解决您的问题(很难参数化段的颜色),但您的具体问题很好,所以我会回答。
如您所见,很难将字符串以外的任何内容传递给 ConverterParameter
。但你不必。如果您从 MarkupExtension
派生转换器,您可以在使用它时分配命名和类型化属性,并且不必将其创建为资源(实际上,将其创建为资源会破坏事情,因为那样会是一个共享实例,并且属性在创建时被初始化)。由于 XAML 解析器知道在 class 上声明的属性的类型,它将对 Brush
应用默认的 TypeConverter
,并且您将获得与如果您将 "PapayaWhip"
分配给 "Border.Background"
或其他任何内容。
这适用于任何类型,当然,不仅仅是 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;
}
}
}
用法:
<TextBox
xmlns:hec="clr-namespace:HollowEarth.Converters"
Foreground="{Binding MyFlagProp, Converter={hec:BoolBrushConverter TrueBrush=YellowGreen, FalseBrush=DodgerBlue}}"
/>
您也可以给 BoolBrushConverter
一个带参数的构造函数。
public BoolBrushConverter(Brush tb, Brush fb)
{
TrueBrush = tb;
FalseBrush = fb;
}
并且在 XAML...
<TextBox
xmlns:hec="clr-namespace:HollowEarth.Converters"
Foreground="{Binding MyFlagProp, Converter={hec:BoolBrushConverter YellowGreen, DodgerBlue}}"
/>
我认为这不适合这种情况。但有时语义如此清晰, 属性 名称是不必要的。 {hec:GreaterThan 4.5}
,例如
更新
这是 SignalBars 控件的完整实现。这对你的四段有五段,但你可以很容易地删除一个;这仅在模板中,Value
属性 是一个 double
,可以按您喜欢的任何方式细分(同样,在模板中)。
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>
示例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>
我正在使用 MVVM 设计模式制作 WPF 应用程序。该应用程序的一部分是信号强度条。我们只用一个矩形用户控件创建了它,并创建了一个 4 列网格,因此我们需要做的就是更改控件的背景色或前景色。
我关于如何做到这一点的想法是简单地为 4 个部分中的每一个存储布尔值并使用值转换器。但是,此控件有 3 个实例,每个实例都有不同的颜色。如何将所需的颜色传递给转换器?我知道转换器有一个参数参数,但我没能找到任何使用它的例子,所以我什至不确定参数参数是否是我要找的。
通常您可能需要 ColorToBrushConverter
,但不需要 BooleanToColor。
我会简单地为每个栏创建带有触发器的不同样式,例如
<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>
您选择的方法可能无法很好地解决您的问题(很难参数化段的颜色),但您的具体问题很好,所以我会回答。
如您所见,很难将字符串以外的任何内容传递给 ConverterParameter
。但你不必。如果您从 MarkupExtension
派生转换器,您可以在使用它时分配命名和类型化属性,并且不必将其创建为资源(实际上,将其创建为资源会破坏事情,因为那样会是一个共享实例,并且属性在创建时被初始化)。由于 XAML 解析器知道在 class 上声明的属性的类型,它将对 Brush
应用默认的 TypeConverter
,并且您将获得与如果您将 "PapayaWhip"
分配给 "Border.Background"
或其他任何内容。
这适用于任何类型,当然,不仅仅是 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;
}
}
}
用法:
<TextBox
xmlns:hec="clr-namespace:HollowEarth.Converters"
Foreground="{Binding MyFlagProp, Converter={hec:BoolBrushConverter TrueBrush=YellowGreen, FalseBrush=DodgerBlue}}"
/>
您也可以给 BoolBrushConverter
一个带参数的构造函数。
public BoolBrushConverter(Brush tb, Brush fb)
{
TrueBrush = tb;
FalseBrush = fb;
}
并且在 XAML...
<TextBox
xmlns:hec="clr-namespace:HollowEarth.Converters"
Foreground="{Binding MyFlagProp, Converter={hec:BoolBrushConverter YellowGreen, DodgerBlue}}"
/>
我认为这不适合这种情况。但有时语义如此清晰, 属性 名称是不必要的。 {hec:GreaterThan 4.5}
,例如
更新
这是 SignalBars 控件的完整实现。这对你的四段有五段,但你可以很容易地删除一个;这仅在模板中,Value
属性 是一个 double
,可以按您喜欢的任何方式细分(同样,在模板中)。
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>
示例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>