在可重复使用的用户控件中绑定转换器参数

Bind a Converter Parameter in re-usable User Control

我正在尝试创建一个可重复使用的用户控件(用于数据输入),其中有两个文本框,它们由 IValueConvertor 分别 link 编辑。

下面的XAML是原来的,正常的代码。这就是我试图在用户控件中重现的内容。

<WrapPanel>
    <TextBlock Text="Length of Fence"/>
    <TextBox Name="Metric" Width="50" Text="{Binding Path=LengthFence, Mode=TwoWay}"/>
    <TextBlock Text="Meters"/>
    <TextBox Text="{Binding ElementName=Metric, Path=Text, Converter={StaticResource MetersToInches}, StringFormat=N8}"/>
    <TextBlock Text="Inches"/>
</WrapPanel>

IValueConvertor(在 MainWindow.xaml 中)的代码隐藏是

    public class MetersToInches : IValueConverter
    {
        public object Convert(object value, Type targetType, object parameter, CultureInfo culture)
        {
            if (value.ToString() == "")
                return 0.0;
            try
            {
                double meters = System.Convert.ToDouble(value);
                var result = meters * 39.3701;
                return result;
            }
            catch
            {
                // Catch errors when users type invalid expressions.
                return 0.0;
            }

        }

        public object ConvertBack(object value, Type targettype, object parameter, CultureInfo culture)
        {
            if (value.ToString() == "")
                return 0.0;
            try
            {
                double inches = System.Convert.ToDouble(value);
                var result = inches * 0.0254;
                return result;
            }
            catch
            {
                // Catch errors when users type invalid expressions.
                return 0.0;
            }
        }

    }

这是 XAML 的样子:

现在我已经制作了一个可重用的 UserControl,其中三个依赖属性 Label 用于标签字符串,Value 用于在 ViewModel 中绑定 属性,以及 Units - 一个字符串 属性 来显示输入单位。

<UserControl ...
             x:Name="parent">
    <StackPanel DataContext="{Binding ElementName=parent}">
        <TextBlock Text="{Binding Path=Label}"/>
        <TextBox Text="{Binding Path=Value}"/>
        <TextBlock Text="{Binding Path=Units}"/>
    </StackPanel>

但是,这个可重复使用的控件只能处理输入的第一个 TextBox。我不知道如何在第二个 TextBox 中绑定 IValueConvertor。我需要这样做,因为我想绑定其他转换器,例如米到英尺、千克到磅等。

我读到 ConvertorParameter 不能绑定,因为它不是依赖项 属性 我不确定我是否可以使用多重绑定,主要是因为我不知道如何使用它正确 Binding ConverterParameter

如果您能告诉我如何执行此操作或指导我在 Whosebug 或其他地方找到适当的 link 解决此问题,我将不胜感激。或者如果有更好的方法来做到这一点。

非常感谢。

不确定您希望如何在一个控件中绑定多个转换器。如果我没记错的话,您想构建一个控件,当用户输入特定值时,您需要以不同的单位显示它。如果是这种情况,您可以创建一个转换器,其转换器参数为 "m"、"cm"、"inch" 等,并基于此您可以 return 结果。那么在这种情况下,您将拥有 4,5 个控件,每个控件都具有相同的转换器绑定但转换器值不同。如果不清楚并且您需要进一步的指导,请告知。

多值绑定

为了回答您的第 6 点,请参阅下面 xaml 中的示例多绑定转换器及其实现。我已经构建了一个简单的 RolesFilter,它将来自 xaml 的不同输入作为对象[],并且因为我已经知道预期的数据,所以我在转换器中转换它们。

public class RolesFilter : IMultiValueConverter
{
    public object Convert(object[] values, Type targetType, object parameter, CultureInfo culture)
    {
        try
        {
            FlipperObservableCollection<Role> _roles = (FlipperObservableCollection<Role>)values[0]; //Input
            Department _dept_param = values[1] as Department;
            bool _filter = (bool)values[2];
            string _id = "NA";
            if (values.Count() == 4 && values[3] is string) _id = (string)values[3] ?? "NA";

            //If we need a filter, then without department, it should return empty results
            if (!_filter) return _roles; //If no filter is required, then don't worry, go ahead with input values.
            if (_dept_param == null) return new FlipperObservableCollection<Role>(); //If department is null, then 
            List<Role> _filtered_list = _roles.ToList().Where(p => p.department.id == _dept_param.id && p.id != _id)?.ToList() ?? new List<Role>();
            return new FlipperObservableCollection<Role>(_filtered_list);
        }
        catch (Exception)
        {
            throw;
        }
    }

    public object[] ConvertBack(object value, Type[] targetTypes, object parameter, CultureInfo culture)
    {
        throw new NotImplementedException();
    }
}

我在 xaml 中使用多值转换器,如下所示。在这里,我正在根据另一个组合框和一个复选框过滤一个组合框的项目源。这只是一个示例,在您的情况下,您可以创建一个具有不同单位值的组合框。根据用户选择,您可以使用转换器和 return 值到文本框。

<ComboBox Height="30" SelectedItem="{Binding reports_to, NotifyOnTargetUpdated=True, NotifyOnSourceUpdated=True, UpdateSourceTrigger=PropertyChanged}">
                        <ComboBox.ItemsSource>
                            <MultiBinding Converter="{StaticResource roles_filter}">
                                <Binding Source="{StaticResource SingletonData__}" Path="roles" NotifyOnSourceUpdated="True" UpdateSourceTrigger="PropertyChanged"/>
                                <Binding Path="department" NotifyOnSourceUpdated="True" UpdateSourceTrigger="PropertyChanged"/>
                                <Binding ElementName="cbx_filter" Path="IsChecked"/>
                                <Binding Path="id" NotifyOnSourceUpdated="True" UpdateSourceTrigger="PropertyChanged"/>
                            </MultiBinding>
                        </ComboBox.ItemsSource>
                        <ComboBox.ItemTemplate>
                            <DataTemplate>
                                <WrapPanel>
                                    <TextBlock Text="{Binding department.name}"/>
                                    <TextBlock Text=" - "/>
                                    <TextBlock Text="{Binding name}"/>
                                </WrapPanel>
                            </DataTemplate>
                        </ComboBox.ItemTemplate>
                    </ComboBox>

首先,不要将 TextBox 彼此绑定(如问题开头的原始代码),而是将每个 TextBox 绑定到相同的支持 属性,在您的 UserControl 中是 Value

至于如何实现多重绑定,你可能不需要MultiBinding

我们必须选择一个 "standard" 度量单位开始 - 这将是实际存储在 属性 和任何数据库或文件中的单位。我假设这个标准单位是米 (m)。 IValueConverter 可用于在米和其他距离单位之间进行转换,使用 ConverterParameter 指定要转换的其他单位 to/from.

这是一个很好的入门示例。

public enum DistanceUnit { Meter, Foot, Inch, }

public class DistanceUnitConverter : IValueConverter
{
    private static Dictionary<DistanceUnit, double> conversions = new Dictionary<DistanceUnit, double>
    {
        { DistanceUnit.Meter, 1 },
        { DistanceUnit.Foot, 3.28084 },
        { DistanceUnit.Inch, 39.37008 }
    };

    //Converts a meter into another unit
    public object Convert(object value, Type targetType, object parameter, CultureInfo culture)
    {
        return conversions[(DistanceUnit)parameter] * (double)value;
    }

    //Converts some unit into a meter 
    public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture)
    {
        if (value == null) { return 0; }

        double v;

        var s = value as string;

        if (s == null)
        {
            v = (double)value;
        }
        else
        {
            if (s == string.Empty) { return 0; }
            v = double.Parse(s);
        }

        if (v == 0) { return 0; }

        return v / conversions[((DistanceUnit)parameter)];
    }
}

上面有几个问题。例如,我在使用它之前从不检查 parameter 是否真的是 DistanceUnit。但它有效。

这是我如何使用它的示例:

<StackPanel>
    <StackPanel.Resources>
        <local:DistanceUnitConverter x:Key="DistCon"/>
    </StackPanel.Resources>

    <StackPanel Orientation="Horizontal">
        <TextBox Text="{Binding Distance, Converter={StaticResource DistCon}, ConverterParameter={x:Static local:DistanceUnit.Meter}}" MinWidth="20"/>
        <TextBlock>m</TextBlock>
    </StackPanel>

    <StackPanel Orientation="Horizontal">
        <TextBox Text="{Binding Distance, Converter={StaticResource DistCon}, ConverterParameter={x:Static local:DistanceUnit.Foot}}" MinWidth="20"/>
        <TextBlock>ft</TextBlock>
    </StackPanel>
</StackPanel>

DistanceUnit enum 和内部 conversions 词典可以扩展为更多的度量单位。或者,您可以使用已经包含所有这些内容的第 3 方库,例如 UnitsNet.