将颜色传递给 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>