我有一个带有文本框的 WPF 窗口,使用标准 WPF 数据绑定到对象。这一切都很好,问题是这样的:
用户正在输入时间估计,我想让用户选择以小时或分钟为单位输入数据(通过下拉列表)。我想在几分钟内保存数据,如果用户选择小时,将他们的输入乘以 60 并存储数据。
如何使用 WPF 数据绑定来实现这一点?这甚至可能吗?
编辑
例如,如果下拉列表设置为小时,90 分钟将显示为 1.5,但如果选择 miutes,则显示为 90。
我有一个带有文本框的 WPF 窗口,使用标准 WPF 数据绑定到对象。这一切都很好,问题是这样的:
用户正在输入时间估计,我想让用户选择以小时或分钟为单位输入数据(通过下拉列表)。我想在几分钟内保存数据,如果用户选择小时,将他们的输入乘以 60 并存储数据。
如何使用 WPF 数据绑定来实现这一点?这甚至可能吗?
编辑
例如,如果下拉列表设置为小时,90 分钟将显示为 1.5,但如果选择 miutes,则显示为 90。
您可以在窗口上使用特殊属性:
<ComboBox x:Name="Units">
<sys:String>Hours</sys:String>
<sys:String>Minutes</sys:String>
</ComboBox>
<TextBox x:Name="Value" Text="{Binding Path=Estimate, ElementName=ThisWindow}" />
然后实现特殊属性:
public double Estimate
{
get
{
switch(this.Units.SelectedItem as String)
{
case "Hours":
return this.estimate / 60.0;
default:
return this.estimate;
}
}
set
{
switch(this.Units.SelectedItem as String)
{
case "Hours":
this.estimate = value * 60.0;
break;
default:
this.estimate = value;
break;
}
this.OnPropertyChanged("Estimate");
}
}
在首先完全误解了这个问题之后,这是我获得第一个赏金的努力;)
<Window.Resources>
<local:DivisionConverter x:Key="HoursConverter" Divisor="60"/>
<DataTemplate DataType="{x:Type local:MyObject}">
<StackPanel Orientation="Horizontal">
<TextBox x:Name="InputBox" Text="{Binding Path=Estimate}" Width="80"/>
<ComboBox x:Name="InputUnit" SelectedItem="Minutes">
<System:String>Minutes</System:String>
<System:String>Hours</System:String>
</ComboBox>
</StackPanel>
<DataTemplate.Triggers>
<DataTrigger Binding="{Binding ElementName=InputUnit, Path=SelectedItem}" Value="Hours">
<Setter TargetName="InputBox" Property="Text" Value="{Binding Path=Estimate, Converter={StaticResource HoursConverter}}"/>
</DataTrigger>
</DataTemplate.Triggers>
</DataTemplate>
</Window.Resources>
<Grid>
<ContentControl VerticalAlignment="Top">
<local:MyObject Estimate="120"/>
</ContentControl>
</Grid>
一般来说,我不喜欢专门的转换器:一段时间后,您会忘记哪个转换器到底做了什么,并且每次您认为需要一个转换器时,您最终都会通过转换器代码,或者更糟糕的是,您构建了第二个完全相同的转换器。所以现在我只在其他所有方法都失败时才使用它们。
相反,我定义了一个通用的DivisionConverter,它将 Convert 方法中的值相除,并将 ConvertBack 方法中的值相乘,正如您所期望的那样,无需查看此处的代码;)
另外,我使用 DataTemplate 和 Triggers,而其他人使用 MultiBinding 或者(更糟糕的是)在 getter 和 setter 中添加了这个 UI 逻辑。这将所有这些相对简单的 UI 逻辑保留在一个可监督的地方。
现在只是为了完成,这是代码隐藏:
public class MyObject
{
public double Estimate { get; set; }
}
public class DivisionConverter : IValueConverter
{
public double Divisor { get; set; }
public object Convert(object value, Type targetType, object parameter, CultureInfo culture)
{
double doubleValue = (double)value;
return doubleValue / Divisor;
}
public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture)
{
double inputValue;
if (!double.TryParse((string)value, NumberStyles.Any, culture, out inputValue))
return 0;
return inputValue * Divisor;
}
}
您可以使用 MultiValueConverter :
XAML
<ComboBox ItemsSource="{StaticResource values}">
<ComboBox.ItemTemplate>
<DataTemplate>
<TextBlock>
<TextBlock.Text>
<MultiBinding Converter="{StaticResource minutToHours}">
<Binding/>
<Binding Path="IsChecked" ElementName="chkHours"/>
</MultiBinding>
</TextBlock.Text>
</TextBlock>
</DataTemplate>
</ComboBox.ItemTemplate>
</ComboBox>
<CheckBox Name="chkHours" Content="Show hours"/>
转换器的 C# 代码
public class MinuteToHoursConverter : IMultiValueConverter
{
#region IMultiValueConverter Members
public object Convert(object[] values, Type targetType, object parameter, System.Globalization.CultureInfo culture)
{
int minutes = 0;
if (values[0] is TimeSpan)
{
minutes = (int)((TimeSpan)values[0]).TotalMinutes;
}
else
{
minutes = System.Convert.ToInt32(values[0]);
}
bool showHours = System.Convert.ToBoolean(values[1]);
if (showHours)
return (minutes / 60.0).ToString();
else
return minutes.ToString();
}
public object[] ConvertBack(object value, Type[] targetTypes, object parameter, System.Globalization.CultureInfo culture)
{
throw new NotSupportedException();
}
#endregion
}
但是这个解决方案可能不是最优的......恕我直言,如果您使用 MVVM 模式,最好的方法是在 ViewModel 中创建一个额外的属性来计算正确的值
也许我想多了,但是由于组合框既有显示值又有实际值(用户幕后),为什么不让您的估算列表有两列……分别是小时和分钟. 然后,根据选中的模式,只需将“显示”绑定更改为相应的模式。这样,它将始终存储您想要的幕后分钟数。没有其他转换为/从。