【问题标题】:WPF Databinding Calculated Value sometimes minutes sometimes hoursWPF 数据绑定计算值有时几分钟有时几小时
【发布时间】:2009-07-01 00:07:11
【问题描述】:

我有一个带有文本框的 WPF 窗口,它使用标准的 WPF 数据绑定到一个对象。这一切都很好,问题是这样的:

用户正在输入时间估计,我想让用户选择以小时或分钟为单位输入数据(通过下拉列表)。我想以分钟为单位保存数据,如果用户选择小时,将他们的输入乘以 60 并存储数据。

如何使用 WPF 数据绑定实现这一目标?这甚至可能吗?

编辑

例如,如果下拉列表设置为小时,90 分钟将显示为 1.5,但如果选择 miutes,则显示为 90。

【问题讨论】:

  • 您的组合框是否同时包含以分钟和小时为单位的估计值,例如单个组合框中的 (10m, 30m, 1h, 2h),还是只有几分钟或几小时:(5m, 10m, 20m) 或 (1h, 2h, 3h) 取决于用户偏好?
  • 我正在使用组合框来选择要显示的单位以及用户输入的单位。

标签: .net wpf data-binding presentation


【解决方案1】:

您可以在窗口上使用特殊属性:

<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");
    }
}

【讨论】:

  • 元注释:在我们的 WPF 应用程序中,我们将单位选择实现为 ViewModel 的一部分,它处理 ViewModel 的所有成员与所选单位之间的转换(当然这是为了处理 Metric v .帝国)。这允许我们让每个 ViewModel 使用自己的单元。当我们希望多个 ViewModel 共享相同的单元时,我们只需将它们绑定到同一个 ComboBox。
【解决方案2】:

在完全误解了这个问题之后,这是我获得第一个赏金的努力;)

<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;
    }
}

【讨论】:

  • 这看起来很有趣...使用您的方法,您可以在 XAML 中指定要如何使用除数的特定实例的属性...我喜欢最大限度地减少重新发明现有代码的可能性。
【解决方案3】:
【解决方案4】:

您可以使用 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 中创建一个额外的属性来计算正确的值

【讨论】:

  • 问题是 ConvertBack 将某人输入的时间作为小时或分钟并将其转换为分钟。
【解决方案5】:

也许我想多了,但由于组合框既有显示值又有实际值(用户幕后),为什么不让您的估算列表有两列......两个小时和分钟。然后,根据选中的模式,只需将“显示”绑定更改为相应的模式。这样,它将始终存储您想要的幕后分钟数。没有其他转换为/从。

【讨论】:

    猜你喜欢
    • 2019-08-03
    • 1970-01-01
    • 1970-01-01
    • 2017-10-20
    • 2021-06-26
    • 2014-07-14
    • 2021-11-20
    • 2022-12-16
    • 2011-02-18
    相关资源
    最近更新 更多