我想创建一个TextBox,可以进行测量并在必要时将其转换为不同的单位(最终结果为double
类型)。转化将由值IsMetric
控制。如果IsMetric == true
则"36.5 in"
会变成927.1
(双倍代表毫米)。相反,如果IsMetric == false
则"927.1 mm"
会变成36.5
。
我想在常规IValueConverter
上使用TextBox
,但ConverterParameter
不是DependencyProperty
因此我无法绑定IsMetric
对它来说。
我尝试IMultiValueConverter
,但ConvertBack
函数只接收TextBox
的当前值,而不是所有绑定值。这意味着在转换用户输入时我不知道IsMetric
。
我错过了ConvertBack
功能吗?如果没有,那么我是否需要创建一个派生自TextBox
?
答案 0 :(得分:3)
您可以使用两个转换器从Metric转换为另一个转换为Metric:
public class ToMetricConverter:IValueConverter
{
public object Convert(object value, Type targetType, object parameter, CultureInfo culture)
{
return "(metric) value";
}
public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture)
{
throw new NotImplementedException();
}
}
public class FromMetricConverter : IValueConverter
{
public object Convert(object value, Type targetType, object parameter, CultureInfo culture)
{
return "(Inch) value";
}
public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture)
{
throw new NotImplementedException();
}
}
在UI中使用DataTrigger
根据该bool值选择合适的转换器:
<Window.Resources>
<wpfApplication13:ToMetricConverter x:Key="ToMetricConverter"/>
<wpfApplication13:FromMetricConverter x:Key="FromMetricConverter"/>
</Window.Resources>
<Grid>
<StackPanel>
<CheckBox IsChecked="{Binding IsMetric,Mode=TwoWay,UpdateSourceTrigger=PropertyChanged}"></CheckBox>
<TextBox >
<TextBox.Style>
<Style TargetType="TextBox">
<Style.Triggers>
<DataTrigger Binding="{Binding IsMetric,Mode=TwoWay,UpdateSourceTrigger=PropertyChanged}" Value="True">
<Setter Property="Text" Value="{Binding Val,Converter={StaticResource ToMetricConverter}}"></Setter>
</DataTrigger>
<DataTrigger Binding="{Binding IsMetric,Mode=TwoWay,UpdateSourceTrigger=PropertyChanged}" Value="False">
<Setter Property="Text" Value="{Binding Val,Converter={StaticResource FromMetricConverter}}"></Setter>
</DataTrigger>
</Style.Triggers>
</Style>
</TextBox.Style>
</TextBox>
</StackPanel>
</Grid>
答案 1 :(得分:0)
如果这是您唯一想做的事情,请尝试使用其他方式来使用converter参数。 但是,我会选择这个选项 - 如果你的文本框中有更多的逻辑,或者往往有更多的依赖 - 创建继承自文本框的自定义控件,并添加自己的依赖属性。然后你可以使用你的IsMetric并在propertychanged等上转换它。
答案 2 :(得分:0)
我现在最终得到了这些内容。仍然可以享受一个不需要DataTrigger的解决方案。
它与@SamTheDev发布的答案略有不同,但沿着相同的路线。
XAML
<UserControl x:Class="MyNamespace.Controls.MeasurementTextBox"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:c="clr-namespace:MyNamespace.Converters"
xmlns:b="clr-namespace:MyNamespace.Behaviors"
xmlns:sys="clr-namespace:System;assembly=mscorlib"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
mc:Ignorable="d"
x:Name="root">
<UserControl.Resources>
<c:MeasurementUnitConverter x:Key="muc"/>
<c:MeasurementConverter2 x:Key="mc"/>
<sys:Boolean x:Key="BooleanFalse">False</sys:Boolean>
<sys:Boolean x:Key="BooleanTrue">True</sys:Boolean>
</UserControl.Resources>
<Grid HorizontalAlignment="Stretch" VerticalAlignment="Stretch">
<Grid.ColumnDefinitions>
<ColumnDefinition Width="*"/>
<ColumnDefinition Width="30"/>
</Grid.ColumnDefinitions>
<TextBox Margin="0" VerticalContentAlignment="Center" HorizontalAlignment="Stretch" HorizontalContentAlignment="Right" VerticalAlignment="Stretch"
b:AutoSelectBehavior.AutoSelect="True">
<TextBox.Style>
<Style TargetType="{x:Type TextBox}">
<Style.Triggers>
<DataTrigger Binding="{Binding UseMetric, ElementName=root}" Value="True">
<Setter Property="Text" Value="{Binding Measurement, Mode=TwoWay, ElementName=root, Converter={StaticResource mc}, ConverterParameter={StaticResource BooleanTrue}}"></Setter>
</DataTrigger>
<DataTrigger Binding="{Binding UseMetric, ElementName=root}" Value="False">
<Setter Property="Text" Value="{Binding Measurement, Mode=TwoWay, ElementName=root, Converter={StaticResource mc}, ConverterParameter={StaticResource BooleanFalse}}"></Setter>
</DataTrigger>
</Style.Triggers>
</Style>
</TextBox.Style>
</TextBox>
<!-- in or mm label -->
<Label VerticalAlignment="Center" Padding="0" Margin="5" HorizontalAlignment="Left" Grid.Column="1"
Content="{Binding UseMetric, ElementName=root, Converter={StaticResource muc}}"/>
</Grid>
</UserControl>
xaml.cs
using System;
using System.Windows;
using System.Windows.Controls;
namespace MyNamespace.Controls
{
/// <summary>
/// Interaction logic for MeasurementTextBox.xaml
/// </summary>
public partial class MeasurementTextBox : UserControl
{
public MeasurementTextBox()
{
// This call is required by the designer.
InitializeComponent();
}
public bool UseMetric {
get { return Convert.ToBoolean(GetValue(UseMetricProperty)); }
set { SetValue(UseMetricProperty, value); }
}
public static readonly DependencyProperty UseMetricProperty = DependencyProperty.Register("UseMetric", typeof(bool), typeof(MeasurementTextBox), new PropertyMetadata(MeasurementTextBox.UseMetricChanged));
private static void UseMetricChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
{
}
public double Measurement {
get { return (double)GetValue(MeasurementProperty); }
set { SetValue(MeasurementProperty, value); }
}
public static readonly DependencyProperty MeasurementProperty = DependencyProperty.Register("Measurement", typeof(double), typeof(MeasurementTextBox), new PropertyMetadata(MeasurementTextBox.MeasurementPropertyChanged));
private static void MeasurementPropertyChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
{
}
}
}
转换器
using System;
using System.Windows;
using System.Windows.Data;
namespace MyNamespace.Converters
{
class MeasurementConverter : IValueConverter
{
const double MILLIMETERS_IN_ONE_INCH = 25.4;
const string INCHES_ABBREVIATION = "in";
const string MILLIMETERS_ABBREVIATION = "mm";
const double ONE_THIRTY_SECOND = 0.03125;
const double ONE_SIXTEENTH = 0.0625;
const double ONE_EIGHTH = 0.125;
const double ONE_FOURTH = 0.25;
const double ONE_HALF = 0.5;
const double ONE = 1;
public double RoundToNearest(double value, int unitPrecision)
{
double fraction = 0;
int reciprocal = 0;
switch (unitPrecision)
{
case 0:
fraction = ONE;
reciprocal = (int)ONE;
break;
case 1:
fraction = ONE;
reciprocal = (int)ONE;
break;
case 2:
fraction = ONE_HALF;
reciprocal = (int)(1 / ONE_HALF);
break;
case 3:
fraction = ONE_FOURTH;
reciprocal = (int)(1 / ONE_FOURTH);
break;
case 4:
fraction = ONE_EIGHTH;
reciprocal = (int)(1 / ONE_EIGHTH);
break;
case 5:
fraction = ONE_SIXTEENTH;
reciprocal = (int)(1 / ONE_SIXTEENTH);
break;
case 6:
fraction = ONE_THIRTY_SECOND;
reciprocal = (int)(1 / ONE_THIRTY_SECOND);
break;
}
return Math.Round(value * reciprocal, MidpointRounding.AwayFromZero) * fraction;
}
public object Convert(object value, Type targetType, object parameter, System.Globalization.CultureInfo culture)
{
return value;
}
public object ConvertBack(object value, Type targetType, object parameter, System.Globalization.CultureInfo culture)
{
string strValue = (string)value;
bool isMetric = (bool)parameter;
double enteredValue = 0;
bool enteredValueIsImperial = false;
if (strValue.EndsWith(INCHES_ABBREVIATION))
{
enteredValueIsImperial = true;
strValue = strValue.Substring(0, strValue.Length - INCHES_ABBREVIATION.Length);
}
else if (strValue.EndsWith(MILLIMETERS_ABBREVIATION))
{
enteredValueIsImperial = false;
strValue = strValue.Substring(0, strValue.Length - MILLIMETERS_ABBREVIATION.Length);
}
else if (isMetric)
{
enteredValueIsImperial = false;
}
else
{
enteredValueIsImperial = true;
}
try
{
enteredValue = double.Parse(strValue);
}
catch (FormatException)
{
return DependencyProperty.UnsetValue;
}
if (isMetric)
{
if (enteredValueIsImperial)
{
//inches to mm
return RoundToNearest(enteredValue * MILLIMETERS_IN_ONE_INCH, 0);
//0 is mm
}
else
{
//mm to mm
return RoundToNearest(enteredValue, 0);
//0 is mm
}
}
else
{
if (enteredValueIsImperial)
{
//inches to inches
return RoundToNearest(enteredValue, 5);
}
else
{
//mm to inches
return RoundToNearest(enteredValue / MILLIMETERS_IN_ONE_INCH, 5);
}
}
}
}
}
用法:
<mynamespace:MeasurementTextBox Measurement="{Binding SomeLength, Mode=TwoWay}"
UseMetric="{Binding IsMetric}"/>