65.9K
CodeProject 正在变化。 阅读更多。
Home

WPF/Silverlight 使用 Flags 类进行绑定

starIconstarIconstarIconstarIconstarIcon

5.00/5 (6投票s)

2011年7月8日

CPOL

10分钟阅读

viewsIcon

19385

downloadIcon

207

需要一个特殊的类来支持将一组标志绑定到视图。

引言

标志枚举在编程中是一个非常有用的工具。然而,在绑定中使用标志并不简单。无论如何,必须指定特定的标志。这可以通过使用绑定“参数”来实现。有了这些信息,为视图进行转换就相当简单了,但问题是反向转换。您通常会绑定到复选框,而这个值是 true 或 false(三态没有意义)。在反向转换时,没有关于其他标志状态的信息。如果没有这些信息,就不可能更新 ViewModel 中的标志值。这是需要一个特殊类来支持将一组标志绑定到 View 的主要原因之一。一旦有了在 WPFSilverlight 中都支持标志的实现,实现就可以更加清晰。

描述

当我开发 WPF(或 Silverlight)应用程序时,我喜欢最小化 ViewModel 中支持 View 所需的属性,因此经常使用 IValueConverter 来确定值是否为 null,并将其转换为 boolVisibility 属性以启用或隐藏控件。我还喜欢使用带有 IValueConverter 的状态枚举来改变 UI 的外观。我还会有一个状态枚举,然后在使用绑定到状态枚举实例时,在参数中使用状态名称。

我遇到过一种情况,我有一个 CheckBox 集合可以启用或禁用。当选择多个(启用)CheckBox 控件时,会有带有箭头的线,这些线会连接相邻的 CheckBox 控件以及被未选中复选框分隔的 CheckBox 控件。每个复选框的左侧都关联一个图形。该图形由两个四分之一圆组成,一个从左向上弯曲,另一个从同一起点向左向下弯曲。有一个指向左的箭头,其尖端位于两条曲线的起点。然后有一条线连接两个四分之一圆的独立端点。通过隐藏或显示这些图形元素,可以将所有复选框连接起来,线以箭头结束。我希望有一种简洁的方式来实现这个 UI。

我使用了两个整数,其中每个复选框和相关图形都由一个位表示。一个整数用于控制每个复选框是否启用;另一个整数表示复选框的选中状态。第二个整数用于控制图形。为了进行绑定,使用了绑定的 Parameter 属性,每个 CheckBox 和相关图形在所有绑定中都使用一个特定的整数。图形将绑定到指示所有复选框选中状态的整数值,因此可以确定相关的 CheckBox 是否被选中,以及它是否与最大或最小值复选框关联,或者它是否超出最大值和最小值(箭头要么向上,要么向下,不同时向上向下)。请查看示例中图形的代码以获取如何完成此操作的详细信息。

最初,我创建了大量的内联代码,但知道这不是完美的解决方案。一个更好的解决方案是创建一个封装所有这些功能的类。

作为使用绑定到复选框集合的标志枚举的示例,我使用了两个复选框集合,第一个用于启用或禁用第二个集合中的复选框。设置一个特定的复选框将启用第一个复选框集合镜像中的集合中的复选框。第二个复选框将设置另一个标志枚举上的值,该枚举将控制图形。图形与第二个复选框集合绑定到相同的标志枚举,以使图形正确连接第二个复选框集合。(注意:我已经包含了一个与第一个复选框集合关联的图形,以展示使用 BindingFlags 类的 Value 属性进行绑定,以展示我最初如何解决问题,并且代码位于 Graphics 类中,以支持绑定到 Value 属性而不是 BindingFlags 类的实例。)

根据实现,我使用无符号整数来存储标志。这将支持 32 个标志,这应该满足大多数场景;短整数不能满足更多情况,并且在处理或内存方面不会提供太多好处。

对于该功能,我需要计算关于整个标志的信息:最小设置标志值和最大设置标志值。有一些高效的方法来计算整数中位的属性,但算法并不明显。这是将功能封装在类中的原因。我将这些函数实现为属性,以便它们可以直接用于绑定。这还有一个好处,即当引发 BindingFlags 类实例的 PropertyChanged 事件时,这些函数的绑定将更新。我需要并且希望高效的一些处理开销较高的标志函数是计算设置标志的数量、最大标志值和最小标志值。

/// <summary>
/// Counts number of flags set
/// </summary>
/// <returns>number of flags set</returns>
public int Count
{
    get
    {
        int count = 0;
        uint n = (uint)flags;
        while (n != 0)
        {
            count++;
            n &= (n - 1);
        }
        return count;
    }
}

/// <summary>
/// value of flag for highest value flag set
///      -1 if nothing set
/// </summary>
/// <returns>value of flag for highest value flag set</returns>
public int Max
{
    get
    {
        if (flags == 0) return -1;
        uint v = (uint)flags;
        int result = 0;
 
        for (int i = 4; i >= 0; i--) // unroll for speed...
        {
            if ((v & MaxFunctionFlagsMasks[i]) > 0)
            {
                v >>= MaxMinFunctionBitShiftArray[i];
                result |= MaxMinFunctionBitShiftArray[i];
            }
        }
        return result;
    }
}
 
/// <summary>
/// value of flag for lowest value flag set
///      -1 if nothing set
/// </summary>
/// <returns>value of flag for lowest value flag set</returns>
public int Min
{
    get
    {
        if (flags == 0) return -1;
        uint v = (uint)flags;
        int result = 0;
 
        for (int i = 4; i >= 0; i--) // unroll for speed...
        {
            if ((v & MinFunctionFlagsMasks[i]) == 0)
            {
                v >>= MaxMinFunctionBitShiftArray[i];
                result |= MaxMinFunctionBitShiftArray[i];
            }
        }
        return result;
    }
}

private static uint[] MaxFunctionFlagsMasks = { 0x2, 0xC, 0xF0, 0xFF00, 0xFFFF0000 };
private static int[] MaxMinFunctionBitShiftArray = { 1, 2, 4, 8, 16 };
private static uint[] MinFunctionFlagsMasks = { 0x1, 0x3, 0xF, 0xFF, 0x0000FFFF };

我将 IValueConverter 功能整合到同一个类中,只是为了将所有代码放在一起,并消除需要暴露更多变量的需要。BindingFlags 类中有几个变量专门用于 IValueConverter,并且有一个支持 IValueConverter 的私有构造函数,因此只能由该类使用。将同一个类用于两个功能显然会产生内存成本,也许使用该类的人可能希望将 IValueConverter 代码拆分出来。IValueConverter 接受一个参数,该参数是标志编号,其中代表最低有效位的标志为“0”,代表最高有效位的标志为“31”。当转换为标志的 bool 值时,只需将参数用作标志索引,并返回一个 bool 值来指示标志是否已设置。当反向转换时,使用一个特殊构造函数创建 BindingFlags 实例,该构造函数设置一个标志以指示这是一个特殊的 BindingFlags 实例。然后,在 ViewModel 的 BindingFlags 属性中使用 DependencyPropertyUpdateDependencyPropertyUpdate 方法使用标志值来指示哪个标志(可以是多个标志,但此功能未使用)已更改。IValueConverter 代码的另一个特点是,当在 XAML 中声明 IValueConverter 时,可以指定返回的 true 和 false 值。默认值分别为 bool true 和 false。注意:IValueConverter 代码中只检查 TrueValue,如果是,则 TrueValueFalseValue 都设置为默认值。在示例中,我使用此功能,通过标记为 FlagVisibilityConverter 的实例传递 Visibility.CollapsedVisibility.Visible

以下是 IValueConverter 代码

#region IValueConverter
/// <summary>
/// Returns true is particular flag is set, otherwise false
/// </summary>
public object Convert(object value, Type targetType, object parameter, 
              System.Globalization.CultureInfo culture)
{
    if (TrueValue == null)
    {
        // If TrueValue and FalseValue have not been set,
        // create defaults of boolean True and False
        TrueValue = true;
        FalseValue = false;
    }
    BindingFlags f = value as BindingFlags;
    int p;
    if (f != null & int.TryParse(parameter.ToString(), out p))
    {
        return (f[p] ? TrueValue : FalseValue);
    }
    return FalseValue;
}
 
/// <summary>
/// Returns Flag instance to be used to set or reset value in Flag class
///      Use Flag.ProcessFlag(flag) in property to handle change
/// </summary>
public object ConvertBack(object value, Type targetType, object parameter, 
              System.Globalization.CultureInfo culture)
{
    int p;
    if (value is bool && int.TryParse(parameter.ToString(), out p))
    {
        // Note that return is actually special instance of flag used to pass
        // information about which value is affected, and if to set or reset
        return new BindingFlags(p, (bool)value);
    }
    // else return an empty flag element
    return new BindingFlags();
}
 
/// <summary>
/// Value return from Convert method if value is set
/// </summary>
public object TrueValue { get; set; }
 
/// <summary>
/// Value return from Convert method if value is not set
/// </summary>
public object FalseValue { get; set; }
#endregion

当创建用于绑定的 BindingFlags 类属性时,使用以下代码

public BindingFlags Flags
{
    get { return _Flags; }
    set
    {
        if (value.DependencyPropertyUpdate(ref _Flags))
        {
            if (PropertyChanged != null)
            {
                PropertyChanged(this, new PropertyChangedEventArgs("Flags"));
            }
        }
    }
}
private BindingFlags _GraphicFlags;

DependencyPropertyUpdate 方法接受 BindingFlags 的单个引用参数。这必须是引用参数,以便可以更改 BindingFlags 的实例。这是强制绑定更新所必需的。如果只更新 BindingFlags 中的字段或属性,则不会触发更改事件,因为代码无法识别变量已发生更改(Microsoft,如果更改 GetHashCode() 返回值会强制重新计算绑定,那就太好了)。此外,我确保此方法不会更改两个 BindingFlags 实例中的任何一个。布尔返回值用于检查是否应触发 PropertyChanged 事件。

以下是用于更新标志值的代码,返回类型为 bool,如果任何标志发生更改,则为 true

public bool DependencyPropertyUpdate(ref BindingFlags f)
{
    if (isSpecialInstance)
    {
        uint localFlag;
        if (isSpecialInstanceSet)
            localFlag = flags | f.flags;
        else
            localFlag = f.flags & (flags ^ (uint)0xFFFFFFFF);

        if (localFlag == f.flags)
            return false;
            
        f = new BindingFlags(localFlag);
        return true;
    }
    else
    {
        // in this case just force update, then check if there is a change
        BindingFlags orgFlag = f;
        f = this;
        return flags != orgFlag.flags;
    }
}

请注意,在此方法中检查字段 isSpecialInstance;如果设置了特殊的 bool 值以指示标志字段中的值用于设置或重置该标志字段中设置的位,则新的 BindingFlags 值与当前标志进行 OR 运算(这将强制设置指定的标志);否则,BindingFlags 值与所有一进行异或运算,然后与标志值标志进行 AND 运算(这将强制重置指定的标志)。如果 isSpecialInstanceSet 布尔值为 true,则设置位;否则,重置位。

在示例中,我尝试展示除了启用和设置复选框以及控制图形之外,绑定还可以使用的多种方式。我用它来在关联的复选框被选中时使 TextBlock 可见。希望这种模式对其他人有用。

我还为 BindingFlags 类创建了一个特殊的 Masked 方法,用于将掩码(也是 BindingFlags 实例)应用于标志字段,并将结果作为 BindingFlags 类返回。当第一组中相应的复选框未设置时,我需要一些函数来重置第二组中的复选框。我所需要做的就是使用它来设置控制图形的 BindingFlags 实例,当控制第一组复选框的 BindingFlags 实例发生变化时。该方法返回 BindingFlags 的实例,因为我必须创建 BindingFlags 的新实例以确保绑定识别到已发生更改。

public BindingFlags ControlFlags
{
    get { return _ControlFlags; }
    set
    {
        if (value.DependencyPropertyUpdate(ref _ControlFlags))
        {
            if (PropertyChanged != null)
            {
                 PropertyChanged(this, new PropertyChangedEventArgs("ControlFlags"));
            }
 
            // This line will ensure that no GraphicFlags are set if the
            // corresponding TestFlag is not set. Use existing GraphicsFlags 
            // code to check for changes.
            GraphicFlags = GraphicFlags.Masked(ControlFlags);
        }
    }
}

关注点

我遇到很多麻烦才让 BindingFlags 的属性绑定正常工作。在修复了几个错误后,我通过绑定到 Value 属性说服自己代码是有效的。代码在视图中进行了此更改后完美运行,但我认为这不太直观,并且需要在创建图形的类中编写一些奇怪的代码。我回去做了一些更改,包括最初将几个标志设置为 true,然后发现绑定到类的实例是有效的,只是触发更改逻辑没有。我试图只添加 INotifyPropertyChanged 接口,并在类中引发 PropertyChanged 事件,但这没有用。还尝试重写 EqualsGetHashCode;这也没有用。继承自 DependencyPropertyDependencyObject 也不起作用。经过多次沮丧之后,我终于弄清楚,在绑定中需要做的是在值发生变化时创建 BindingFlags 类的新实例。

我还有另一个问题,我必须仔细考虑才能解决。我想使用启用第二组标志的标志来确保第二组中的复选框在被禁用时未选中。我能够通过使用 MultiValueConverter 来实现这一点,但问题是无法转换回去,因为我再次没有第一组标志。因此,我能够强制在复选框被禁用时复选框不被设置,但无法使控制 BindingFlags 实例更新以与复选框的状态对应,因此图形会处于错误状态。当然,BindingFlags 实例也会是错误的。这可以通过使用与复选框相同的多重绑定来修复,但绑定的 BindingFlags 实例将是错误的。这就是我创建 Masked 方法的原因。

摘要

这个解决方案远非完美,主要是因为从 View 到 View-Model 的绑定方法需要与正常绑定不同的代码,但它并不比使用值类型或字符串进行绑定更困难。此外,当 BindingFlags 类的值发生更改时,需要创建它的新实例。该实现需要一个不寻常的值转换器,以不同于正常方式使用 BindingFlags 类的实例。尽管如此,我不认为这些问题很重要,并且该类是更清晰地在 XAML 绑定中实现解决方案的有效工具。

帮助:我使用的图形没有按照我想要的方式调整大小。如果有人有解决方案,我将不胜感激。

© . All rights reserved.