因为目标类型的问题,在WPF中连锁使用IValueConvertes出现了一些问题。

11 浏览
0 Comments

因为目标类型的问题,在WPF中连锁使用IValueConvertes出现了一些问题。

我试图像Town在Is there a way to chain multiple value converters in XAML?中的回答中那样链接转换器。

我想通过添加targetType检查使个别转换器更严格:-

if (targetType != typeof(bool))
        throw new InvalidOperationException("目标必须是布尔类型");

但是由于每个阶段的最终目标类型与目标类型不同,所以链条失败了。

我可以删除类型检查以使其更宽松,就像大多数示例中的SO一样,但我更喜欢一个能够尊重每个转换器的类型检查的链式调用。例如,为了更好地进行单元测试等。

另外,IValueConverter接口没有公开目标类型,我发现很难自己添加该检查。

public class InverseBooleanConverter : IValueConverter
    {
    public object Convert(object value, Type targetType, object parameter, CultureInfo culture)
        {
        if (targetType != typeof(bool))
            throw new InvalidOperationException("目标必须是布尔类型");
        if (!(value is bool))
            throw new ArgumentException("参数'value'必须是布尔类型");
        return !(bool)value;
        }
         ....
     }
[ValueConversion(typeof(bool), typeof(Visibility))]
public class VisibilityFromBoolConverter : IValueConverter
    {
    public object Convert(object value, Type targetType, object parameter, CultureInfo culture)
        {
        if (targetType != typeof(Visibility))
            throw new InvalidOperationException("目标必须是Visibility类型");
        if (!(value is bool))
            throw new ArgumentException("参数'value'必须是布尔类型");
        var isVisible = (bool)value;
        return isVisible ? Visibility.Visible : Visibility.Collapsed;
        }
        ....
     }

而组合如下:-

            
                  
                  
            

但是我从InverseBooleanConverter获得异常"The target must be a boolean",因为它期望目标是bool而不是Visibility(链的最终目标)。

0
0 Comments

问题的原因是原始的ValueConverterGroup代码将最终的targetType传递给每个阶段,这就是为什么你的检查失败的原因。你需要做的就是修改这种行为,将下一个转换器的targetType传递进去。

解决方法是修改ValueConverterGroup类的Convert方法,将目标类型(targetType)的传递方式改为传递给下一个转换器的目标类型。代码如下:

[ValueConversion(typeof(bool), typeof(Visibility))]
public class ValueConverterGroup : List, IValueConverter
{
    #region IValueConverter Members
    public object Convert(object value, Type targetType, object parameter, System.Globalization.CultureInfo culture)
    {
        for (int i = 0; i < this.Count(); i++)
        {
            var targ = (i == this.Count() - 1) 
                ? targetType 
                : (this[i + 1].GetType().GetCustomAttributes(typeof(ValueConversionAttribute), false).First() as ValueConversionAttribute).SourceType;
            value = this[i].Convert(value, targ, parameter, culture);
        }
        if (value.GetType() != (this.GetType().GetCustomAttributes(typeof(ValueConversionAttribute), false).First() as ValueConversionAttribute).TargetType)
            throw new InvalidOperationException("Last target must be of type " + targetType.Name);
        return value;
    }
    public object ConvertBack(object value, Type targetType, object parameter, System.Globalization.CultureInfo culture)
    {
        throw new NotImplementedException();
    }
    #endregion      
}

然而,反射是一个比较重的操作,所以我想避免使用它。但是这个解决方法正好实现了我想要的链式转换功能。对我来说,这似乎是在质量、时间和成本中选择两项的完美例子 :(。无论如何,谢谢,希望有一些可以在xaml中原生实现的方法。

好吧,列表本身可能不会经常更改,所以你可以从ObservableCollection派生它,然后在OnCollectionChanged重写中将所有内容标记为脏。然后在Convert函数中,只有在脏标志为true时才执行反射和检查,并缓存目标类型以供后续调用使用。你的方程式中的第四个变量是复杂性 🙂 - Mark Feldman 10 分钟前

0
0 Comments

WPF中使用IValueConverter链式调用时的问题是由于目标类型引起的。

根据文档,targetType参数是绑定属性的类型,而不是传入的值的类型。因此,在InverseBooleanConverter中,目标类型将是System.Visibility类型。

为了进行这种类型检查,你应该检查传入的对象的类型:

if (value != null && value.GetType() != typeof(bool))
    throw new InvalidOperationException("The target must be a boolean");

但是,我强烈建议您不要从转换器中抛出异常,这可能会严重影响UI渲染的速度,并且当您有一个充满很多控件的屏幕时,很难跟踪异常(例如,您在一个包含几千行的网格中,其中一个模板化的数据网格单元格抛出了异常,您如何找到它?)。如果您坚持要抛出异常,请至少在其周围使用#if DEBUG定义,以便它不会出现在您的发布代码中。相反,如果转换器无法成功转换值,您应该返回DependencyProperty.UnsetValue。这确保您不会遇到难以跟踪的运行时异常,并且还确保绑定子系统可以使用FallbackValue等功能。

感谢您提出从转换器中抛出异常可能会导致应用程序变慢,但在开发过程中这非常有帮助,因为绑定错误不太详细,所以最好包装在#if debug中!但是,对于类型检查目标类型检查是另一种检查,不同于传入值的检查。在您的代码中,您检查传入的值并抛出关于目标的消息。

也许您误解了我的回答...当每个转换器独立运行时,您如何可能对targetType进行类型检查,您只是链接它们的结果。Mark已经向您展示了一种方法,但请相信我,这违反了许多规则,这不是一件好事-仅仅因为它“可以”这样做,并不意味着它“应该”这样做。一旦您在列表类型的控件中使用该方法,您的应用程序将立即受到性能损失。但这取决于您-如果您想使用该方法,请去尝试,但这不是设计转换器的方式。

我想要的是每个转换器都是可复用的,以便在单独调用或组合使用时进行足够的检查。我会看看是否在生产环境中保留此检查,因为目前我还没有将其添加到列表控件中。谢谢建议!

0