【问题标题】:XAML/C# How to create a two-way binding with one source and multiple targets?XAML/C# 如何创建一个源和多个目标的双向绑定?
【发布时间】:2019-02-20 19:39:30
【问题描述】:

我想用三个文本框定义一个时间跨度;一个代表小时、分钟和秒。数据验证超出了我的问题范围。

我在xaml中定义了三个文本框:

<UserControl
    x:Class="Test_Timer.Timer"
    xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
    xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
    xmlns:local="using:Test_Timer"
    xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
    xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
    mc:Ignorable="d"
    x:Name="timer">
    <StackPanel Orientation="Horizontal>
        <TextBox x:Name="hoursBox" />
        <TextBlock Text=":" />
        <TextBox x:Name="minutesBox" />
        <TextBlock Text=":" />
        <TextBox x:Name="secondsBox" />
    </StackPanel>
</UserControl>

我的 ViewModel 包含一个 TimeSpan 属性,并在时间更改时发出通知(通过 INotifyPropertyChanged)。 ViewModel 是通用的,可用于其他视图。为小时、分钟和秒添加三个单独的属性以便我可以单独绑定它们是不可接受的。

class TimerVM : ViewModelBase
{
    private TimeSpan m_duration = new TimeSpan();
    public TimeSpan Duration
    {
        get { return m_duration; }
        set
        {
            m_duration = value;
            NotifyPropertyChanged(nameof(Duration));
        }
    }
}

如何在三个文本框和 Duration 属性之间设置双向绑定?

【问题讨论】:

  • wpf 和 uwp 是 2 个独立的平台,请为您的应用所在的平台添加适当的标签。
  • @touseefbsb xaml 和绑定组件在 uwp 和 wpf 之间是通用的。由于我的问题主要与绑定有关,我相信它适用于两个平台,并且很乐意接受使用任何一个的答案。但是,您的观点已被注意到,我将更改标题以删除 UWP。
  • 您是否尝试过将每个文本框绑定到嵌套属性,例如“Duration.Hours”到小时文本框等等?
  • TimeSpan 上的 Hours、Minutes 和 Seconds 属性是只读的。我希望我需要做一些处理来预先组合它们。可能只是必须在代码隐藏中完成的情况之一。

标签: c# wpf xaml data-binding uwp


【解决方案1】:

在您的 Grid 中将转换器定义为 StaticResource,然后将文本框与 ConverterConverter Parameter 一起绑定到持续时间。 p>

<UserControl
    x:Class="Test_Timer.Timer"
    xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
    xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
    xmlns:local="using:Test_Timer"
    xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
    xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
    mc:Ignorable="d"
    x:Name="timer">
    <Grid>
        <Grid.Resources>
            <local:DateFormatter x:Key="DurationConverter" />
        </Grid.Resources>
        <StackPanel Orientation="Horizontal>            
            <TextBox x:Name="hoursBox" Text="{Binding vm.Duration, Mode=TwoWay,
                            Converter={StaticResource DurationConverter}, 
                            ConverterParameter=hoursBox}"/>
            <TextBlock Text=":" />
            <TextBox x:Name="minutesBox" Text="{Binding vm.Duration, Mode=TwoWay,
                            Converter={StaticResource DurationConverter}, 
                            ConverterParameter=minutesBox}"/>
            <TextBlock Text=":" />
            <TextBox x:Name="secondsBox" Text="{Binding vm.Duration, Mode=TwoWay,
                            Converter={StaticResource DurationConverter}, 
                            ConverterParameter=secondsBox}"/>
        </StackPanel>
    </Grid
</UserControl>

然后您需要在命名空间 Test_Timer 内的后端中定义此转换器,如下所示:

public class DurationFormatter : IValueConverter
{
    public object Convert(object value, Type targetType, 
        object parameter, string language)
    {
        string formatString = parameter as string;
        if (formatString == "hoursBox")
        {
            return ((TimeSpan)value).Hours.ToString();
        }
        else if (formatString == "minutesBox")
        {
            return ((TimeSpan)value).Minutes.ToString();
        }
        else
        {
            return ((TimeSpan)value).Seconds.ToString();
        }
    }

    public object ConvertBack(object value, Type targetType, 
        object parameter, string language)
    {
        string formatString = parameter as string;
        if (formatString == "hoursBox")
        {
            return TimeSpan.FromHours(ConvertToInt32(((string)value)));//Here you get the hours value sent from textbox to backend.
        }
        else if (formatString == "minutesBox")
        {
            return TimeSpan.FromMinutes(ConvertToInt32(((string)value)));//Here you get the minutes value sent from textbox to backend.
        }
        else
        {
            return TimeSpan.FromSeconds(ConvertToInt32(((string)value)));//Here you get the seconds value sent from textbox to backend.
        }
    }
}

convert 方法从您的 viewModel 属性中获取数据并根据需要进行转换,然后将数据返回给您的 UI(文本框)。

convertback 方法从您的文本框中获取数据并对其进行转换,然后将其发送到您的 viewmodel 属性。

我已经向您展示了如何实现这一目标。您只需要弄清楚您想如何处理转换返回方法,我在这里编写的代码从文本框中获取一个字符串并相应地转换为 TimeSpan 对象(取决于它是否来自哪个文本框 ConverterParameter 帮助我们),然后使用 return 语句将其分配给 viewmodel Duration 属性。现在由您决定如何在将它们发送到 Duration 之前将它们组合起来。

建议

根据您的场景,您需要以某种方式组合小时、分钟和秒,然后将其分配给视图模型的持续时间。所以我建议一种可能的方法来做到这一点。

创建一个可以容纳 3 个静态属性的公共静态类,它们会在 ConvertConvertBack 方法中不断更新,因此这将帮助您进行组合。

public static class DurationValues
{
    public static string Hours="";
    public static string Minutes="";
    public static string Seconds="";
}

并具有如下的转换类。

public class DurationFormatter : IValueConverter
{
    public object Convert(object value, Type targetType, 
        object parameter, string language)
    {
        string formatString = parameter as string;
        if (formatString == "hoursBox")
        {
            string rValue = ((TimeSpan)value).Hours.ToString();
            DurationValues.Hours=rValue;
            return rValue;
        }
        else if (formatString == "minutesBox")
        {
            string rValue = ((TimeSpan)value).Minutes.ToString();
            DurationValues.Minutes=rValue;
            return rValue;
        }
        else
        {
            string rValue = ((TimeSpan)value).Seconds.ToString();
            DurationValues.Seconds=rValue;
            return rValue;
        }
    }

    public object ConvertBack(object value, Type targetType, 
        object parameter, string language)
    {
        string formatString = parameter as string;
        if (formatString == "hoursBox")
        {
            DurationValues.Hours = (string)value;
            var ts = new TimeSpan (DurationValues.Hours,DurationValues.Minutes,DurationValues.Seconds);
            return ts;
        }
        else if (formatString == "minutesBox")
        {
            DurationValues.Minutes = (string)value;
            var ts = new TimeSpan (DurationValues.Hours,DurationValues.Minutes,DurationValues.Seconds);
            return ts;
        }
        else
        {
            DurationValues.Seconds = (string)value;
            var ts = new TimeSpan (DurationValues.Hours,DurationValues.Minutes,DurationValues.Seconds);
            return ts;
        }
    }
}

另一个 IValueConverter 示例:https://www.wpf-tutorial.com/data-binding/value-conversion-with-ivalueconverter/

【讨论】:

  • 这真的有效吗?您可以结合这 3 个属性并创建一个设置了小时、分钟和秒的 TimeSpan,还是每次都创建一个新的 TimeSpan
  • 它每次在 ConvertBack 方法中都会创建一个新的 TimeSpan,你如何结合这 3 个实际上取决于你的场景,你必须弄清楚。
  • 这是一个好的开始。它绝对解决了从 Duration 属性更新文本框的问题。但正如您所提到的,它并没有解决如何将文本框属性组合成一个持续时间。我不知道你为什么认为这件作品取决于场景。你能详细说明吗?我预见的问题是我们在转换器中一次只能访问一个文本框值,因此不可能将这三个结合起来。
  • @SimCard 我已经更新了我的答案并添加了一个建议,您可以通过 1 种方法来实现您所需要的
  • 我之所以说这取决于场景是因为您在问题中声明您不想在 viewmodel 中使用 3 个单独的属性,因为这实际上改变了我们实现这一点的方式。
【解决方案2】:

这是我最终得到的解决方案(99% 的灵感来自 touseefbsb 的答案)。

这是 XAML:

<UserControl
    x:Class="Test_Timer.Timer"
    xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
    xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
    xmlns:local="using:Test_Timer"
    xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
    xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
    mc:Ignorable="d"
    x:Name="timer">
    <UserControl.Resources>
        <local:TimeSpanConverter x:Key="TimeSpanConverter" />
    <UserControl.Resources>
    <Grid>
        <StackPanel Orientation="Horizontal>            
            <TextBox x:Name="hoursBox" Text="{Binding vm.Duration, Mode=TwoWay,
                        Converter={StaticResource TimeSpanConverter}, 
                        ConverterParameter=hours}"/>
            <TextBlock Text=":" />
            <TextBox x:Name="minutesBox" Text="{Binding vm.Duration, Mode=TwoWay,
                        Converter={StaticResource TimeSpanConverter}, 
                        ConverterParameter=minutes}"/>
            <TextBlock Text=":" />
            <TextBox x:Name="secondsBox" Text="{Binding vm.Duration, Mode=TwoWay,
                        Converter={StaticResource TimeSpanConverter}, 
                        ConverterParameter=seconds}"/>
        </StackPanel>
    </Grid>
</UserControl>

还有转换器:

class TimeSpanConverter : IValueConverter
{
    public int Hours { get; set; }

    public int Minutes { get; set; }

    public int Seconds { get; set; }

    public object Convert(object value, Type targetType, object parameter, string language)
    {
        string strParam = (string)parameter;
        TimeSpan ts = (TimeSpan)value;

        switch(strParam.ToLower())
        {
            case "hours":
                return ts.Hours.ToString();
            case "minutes":
                return ts.Minutes.ToString();
            case "seconds":
                return ts.Seconds.ToString();
        }
        return "0";
    }

    public object ConvertBack(object value, Type targetType, object parameter, string language)
    {
        string strParam = (string)parameter;
        int intVal = int.Parse((string)value);

        switch (strParam.ToLower())
        {
            case "hours":
                Hours = intVal;
                break;
            case "minutes":
                Minutes = intVal;
                break;
            case "seconds":
                Seconds = intVal;
                break;
        }

        return new TimeSpan(Hours, Minutes, Seconds);
    }
}

请注意,我需要为每个计时器使用不同的转换器实例,因为我依赖于 Hour、Minute 和 Second 属性。

【讨论】:

    猜你喜欢
    • 2015-09-25
    • 1970-01-01
    • 2018-07-27
    • 2012-09-21
    • 2022-06-20
    • 1970-01-01
    • 2016-07-18
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多