WPF数据绑定计算值有时几分钟有时几个小时

发布于 2024-07-26 01:04:16 字数 280 浏览 5 评论 0原文

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

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

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

编辑

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

I have a WPF window with a textbox, using standard WPF Databinding to an object. This all works fine, the issue is this:

The user is entering a time estimate, I would like to give the user the option to enter the data in hours or minutes (through a drop down list). I'd like to keep the data in minutes, and if the user selects hours, multiply their input by 60 and store the data.

How can I achieve that with WPF databinding? Is this even possible?

edit

For example, 90 minutes would show up as 1.5 if the dropdown list is set to hours, but 90 if miutes is selected.

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(5

擦肩而过的背影 2024-08-02 01:04:17

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

<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 和触发器,而其他人使用 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;
    }
}

After first completely misunderstanding the question, here's my effort at getting my first bounty ;)

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

In general i dislike specialized converters: after a while you lose track of which converter does what exactly, and you end up going through converter code everytime you think you need one, or worse you build a second one which does exactly the same. So now i'm only using them when all else fails.

Instead i defined a general purpose DivisionConverter, which divides the value in the Convert method, and multiplies the value in the ConvertBack method, exactly as you'd expect, no need to look at the code here ;)

Additionaly i use a DataTemplate and Triggers where others used a MultiBinding or (imo worse) added this UI logic in the getter and setter. This keeps all this relatively simple UI logic in one overseeable place.

Now just to be complete this is the code-behind:

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;
    }
}
最美的太阳 2024-08-02 01:04:17

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

You can use a 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# code for Converter

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
}

But this solution is probably not optimal... IMHO the best way if you're using the MVVM pattern is to create an extra property in the ViewModel to compute the correct value

策马西风 2024-08-02 01:04:17

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

Maybe I'm overthinking this, but since a combobox has both a Display Value, and an Actual value (behind-the-scenes from the user), why not have your estimate list have two columns... both the hours and minutes respectively. Then, based on whichever mode is checked, just change the "Display" binding to the respective one. This way, it will always store the behind-the-scenes minutes number you wanted. No other conversion to/from.

幸福丶如此 2024-08-02 01:04:16

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

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

You could use a special property on your window:

<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}" />

And then implement the special property:

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");
    }
}
~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文