WPF数据绑定与简单算术操作?

18

我想要将一个接收到的整数与常量相加。实际上,我有几个地方想要绑定到同一个源值,但是要添加不同的常量。所以理想的解决方案应该像这样...

<TextBox Canvas.Top="{Binding ElementName=mySource, Path=myInt, Constant=5}"/>
<TextBox Canvas.Top="{Binding ElementName=mySource, Path=myInt, Constant=8}"/>
<TextBox Canvas.Top="{Binding ElementName=mySource, Path=myInt, Constant=24}"/>

(注意:这只是一个示例,用于展示思路,我的实际绑定场景并不是 TextBox 的 canvas 属性。但这更清楚地表明了思路)

目前我能想到的唯一解决方案是公开许多不同的源属性,每个属性都向相同的内部值添加不同的常数。所以我可以这样做...

<TextBox Canvas.Top="{Binding ElementName=mySource, Path=myIntPlus5}"/>
<TextBox Canvas.Top="{Binding ElementName=mySource, Path=myIntPlus8}"/>
<TextBox Canvas.Top="{Binding ElementName=mySource, Path=myIntPlus24}"/>

但这相当令人沮丧,因为在将来,我可能需要为新的常量添加新属性。而且如果我需要更改添加的值,我需要去修改源对象,这很糟糕。

难道没有比这更通用的方法吗?有任何WPF专家有想法吗?

5个回答

24

我使用一个我创建的 MathConverter 来进行所有简单的算术操作。转换器的代码在这里,可以像这样使用:

<TextBox Canvas.Top="{Binding SomeValue, 
             Converter={StaticResource MathConverter},
             ConverterParameter=@VALUE+5}" />

你甚至可以将它与更高级的算术运算一起使用,比如

Width="{Binding ElementName=RootWindow, Path=ActualWidth,
                Converter={StaticResource MathConverter},
                ConverterParameter=((@VALUE-200)*.3)}"

不错的转换器,但如果值为负数,则会出现错误。一个快速的解决方法是使用其他符号表示减号,而不是“-”,并相应地修改MathConverter代码中的操作数列表。当然,真正的解决方案需要在解析时添加更多逻辑。 - Maxim Zabolotskikh
@MaximZabolotskikh 负数只是 0 - @VALUE,所以用户可以轻松地输入它。或者更好的解决方案是,在我们期望一个数字标记时检查 - 符号,如果找到了就正确解析它。我想我可能已经在我保存在我的 WPF 库中的转换器副本中做到了这一点... - Rachel
0 - @VALUE 不起作用。更准确地说,我有一个基于0的列表,并希望显示文本“y的x元素”,因此我将索引(x)绑定为“@VALUE + 1”。但是空列表将返回-1作为索引,因此它变成了-1 + 1,mathconverter按操作数拆分,然后对于减号找不到第二个数字,类似这样的情况。因此需要进行更多的解析。但基本上我的评论的目的只是向任何可能在未来使用此代码的人指出可能存在的问题。无论如何,这是一个有用的转换器。 - Maxim Zabolotskikh
通用警告:我刚刚花了很长时间才弄清楚那些逗号是必需的。 - lucidbrot

7

我相信你可以通过值转换器来实现这个功能。这里有一篇博客文章介绍了如何在XAML中向值转换器传递参数。而这篇博客则详细介绍了如何实现值转换器。


1
值转换器可以接受参数这一事实似乎是解决此问题的好方法。感谢您的建议。 - Phil Wright

6

使用值转换器是解决问题的好方法,因为它允许您在将源值绑定到UI时修改它。

我在几个地方都使用了以下内容。

public class AddValueConverter : IValueConverter
{
    public object Convert(object value, Type targetType, object parameter, CultureInfo culture)
    {
        object result = value;
        int parameterValue;

        if (value != null && targetType == typeof(Int32) && 
            int.TryParse((string)parameter, 
            NumberStyles.Integer, culture, out parameterValue))
        {
            result = (int)value + (int)parameterValue;
        }

        return result;
    }

    public object ConvertBack(object value, Type targetType, object parameter, System.Globalization.CultureInfo culture)
    {
        throw new NotImplementedException();
    }
}

例子

 <Setter Property="Grid.ColumnSpan"
         Value="{Binding 
                   Path=ColumnDefinitions.Count,
                   RelativeSource={RelativeSource AncestorType=Grid},
                   Converter={StaticResource addValueConverter},
                   ConverterParameter=1}"
  />

0
我会使用多重绑定转换器,例如以下示例:
C# 代码:
namespace Example.Converters
{
        public class ArithmeticConverter : IMultiValueConverter
        {            
            public object Convert(object[] values, Type targetType, object parameter, CultureInfo culture)
            {
                double result = 0;

                for (int i = 0; i < values.Length; i++)
                {
                    if (!double.TryParse(values[i]?.ToString(), out var parsedNumber)) continue;

                    if (TryGetOperations(parameter, i, out var operation))
                    {
                        result = operation(result, parsedNumber);
                    }
                }

                return result;
            }

            public object[] ConvertBack(object value, Type[] targetTypes, object parameter, CultureInfo culture)
            {
                return new[] { Binding.DoNothing, false };
            }


            private static bool TryGetOperations(object parameter, int operationIndex, out Func<double, double, double> operation)
            {
                operation = null;
                var operations = parameter?.ToString().Split(',');

                if (operations == null || operations.Length == 0) return false;

                if (operations.Length <= operationIndex)
                {
                    operationIndex = operations.Length - 1;
                }

                return Operations.TryGetValue(operations[operationIndex]?.ToString(), out operation);
            }

            public const string Add = "+";
            public const string Subtract = "-";
            public const string Multiply = "*";
            public const string Divide = "/";

            private static IDictionary<string, Func<double, double, double>> Operations = new Dictionary<string, Func<double, double, double>>
            {
                { Add, (x, y) => x + y },
                { Subtract, (x, y) => x - y },
                { Multiply, (x, y) => x * y },
                { Divide, (x, y) => x / y }
            };
        }
}

XAML 代码:

<UserControl 
     xmlns:converters="clr-namespace:Example.Converters">
    <UserControl.Resources>
                    <converters:ArithmeticConverter x:Key="ArithmeticConverter" />
    </UserControl.Resources>

...

<MultiBinding Converter="{StaticResource ArithmeticConverter}" ConverterParameter="+,-">
            <Binding Path="NumberToAdd" />
            <Binding Path="NumberToSubtract" />
</MultiBinding>

-1

我从未使用过WPF,但我有一个可能的解决方案。

你的绑定路径是否可以映射到Map?如果可以,那么它应该能够接受一个参数(键)。你需要创建一个实现Map接口的类,但实际上只返回你初始化“Map”时添加到键的基本值。

public Integer get( Integer key ) { return baseInt + key; }  // or some such

没有一些从标签中传递数字的能力,我不知道你如何使其返回与原始值不同的增量。


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接