在可重复使用的用户控件中绑定转换器参数
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.
我正在尝试创建一个可重复使用的用户控件(用于数据输入),其中有两个文本框,它们由 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.