使用视图模型中的属性绑定到用户控件的属性(包含逻辑)

发布于 2024-11-05 14:55:47 字数 2296 浏览 1 评论 0原文

我正在使用 WPF 和 MVVM 构建一个应用程序。我遇到过一种情况,我有一个包含用户控件(代表计时器)的视图。该用户控件在其代码隐藏中有一个属性,该属性在获取和设置数据之前执行一些计算。

TimerControl.xaml.cs:

public DateTime? DateTimeValue
    {
        get
        {
            string hours = this.txtHours.Text;
            string minutes = this.txtMinutes.Text;
            string amPm = this.txtAmPm.Text;
            if (!string.IsNullOrWhiteSpace(hours) && !string.IsNullOrWhiteSpace(minutes) && !string.IsNullOrWhiteSpace(amPm))
            {
                string value = string.Format("{0}:{1} {2}", this.txtHours.Text, this.txtMinutes.Text, this.txtAmPm.Text);
                DateTime time = DateTime.Parse(value);                    
                return time;
            }
            else
            {
                return null;
            }
        }
        set
        {                
            DateTime? time = value;
            if (time.HasValue)
            {
                string timeString = time.Value.ToShortTimeString();
                //9:54 AM 
                string[] values = timeString.Split(':', ' ');
                if (values.Length == 3)
                {
                    this.txtHours.Text = values[0];
                    this.txtMinutes.Text = values[1];
                    this.txtAmPm.Text = values[2];
                }
            }                
        }
    }

现在我想将此属性绑定到视图的视图模型中存在的属性。以下是 VM 中的属性:

public DateTime? StartTime
{  
        get  
        {                
            return _StartTime;
        }

        set
        {
            _StartTime = value;
            RaisePropertyChanged("StartTime");
        }
} 

这就是我在 View 的 xaml 中执行绑定的方式。

MyView.xaml:

<my:TimeControl  Background="White" Grid.Column="1" Grid.Row="2" Margin="3" x:Name="StartTimeControl" DateTimeValue="{Binding StartTime}"  Width="150" Height="26" HorizontalAlignment="Left">

但它给了我一个错误: 无法在“TimeControl”类型的“DateTimeValue”属性上设置“Binding”。 “绑定”只能在 DependencyObject 的 DependencyProperty 上设置。

我花了几个小时努力寻找一种方法来使这种绑定发挥作用。我什至尝试在 TimeControl 的代码后面为 DateTimeValue 属性创建一个依赖属性,这解决了上述异常,但绑定仍然不起作用。每当我访问虚拟机代码后面的 StartTime 属性时,它都显示为空。尽管它应该通过获取 DateTimeValue 属性来显示有效值。

请建议我一种方法来完成这项工作。谢谢。

I'm building an application using WPF and MVVM. I've come across a situation where I have a view containing a usercontrol (representing a Timer). This usercontrol has a property in it's code behind which performs some calculations before getting and setting data.

TimerControl.xaml.cs:

public DateTime? DateTimeValue
    {
        get
        {
            string hours = this.txtHours.Text;
            string minutes = this.txtMinutes.Text;
            string amPm = this.txtAmPm.Text;
            if (!string.IsNullOrWhiteSpace(hours) && !string.IsNullOrWhiteSpace(minutes) && !string.IsNullOrWhiteSpace(amPm))
            {
                string value = string.Format("{0}:{1} {2}", this.txtHours.Text, this.txtMinutes.Text, this.txtAmPm.Text);
                DateTime time = DateTime.Parse(value);                    
                return time;
            }
            else
            {
                return null;
            }
        }
        set
        {                
            DateTime? time = value;
            if (time.HasValue)
            {
                string timeString = time.Value.ToShortTimeString();
                //9:54 AM 
                string[] values = timeString.Split(':', ' ');
                if (values.Length == 3)
                {
                    this.txtHours.Text = values[0];
                    this.txtMinutes.Text = values[1];
                    this.txtAmPm.Text = values[2];
                }
            }                
        }
    }

Now I wanted to bind this property to a property present in view model of the view. Following is property in the VM:

public DateTime? StartTime
{  
        get  
        {                
            return _StartTime;
        }

        set
        {
            _StartTime = value;
            RaisePropertyChanged("StartTime");
        }
} 

This is how I am performing binding in the xaml of View.

MyView.xaml:

<my:TimeControl  Background="White" Grid.Column="1" Grid.Row="2" Margin="3" x:Name="StartTimeControl" DateTimeValue="{Binding StartTime}"  Width="150" Height="26" HorizontalAlignment="Left">

But it is giving me an error that:
A 'Binding' cannot be set on the 'DateTimeValue' property of type 'TimeControl'. A 'Binding' can only be set on a DependencyProperty of a DependencyObject.

I've been struggling for hours trying to figure out a way to make this binding work. I have even tried to make a dependency property in the TimeControl's code behind for the DateTimeValue property, which has resolved the above exception, but the binding still doesn't work. Whenever I access StartTime property in the VM's code behind, it is showing null. Although it should show a valid value by getting the DateTimeValue property.

Kindly suggest me a way to make this work. Thanks.

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

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

发布评论

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

评论(2

吐个泡泡 2024-11-12 14:55:47

您在这个问题中显示的 DateTimeValue 属性的实现肯定是错误的并导致异常,因为 DateTimeValue 应该是依赖属性。

但您提到您尝试使用依赖属性但没有成功。我想原因是 DataContexts 的冲突,并且您的 XAML 看起来像这样:

<UserControl x:Class="Test.SomeView"
             xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
             xmlns:self="clr-namespace:Test"
             Name="Root">
    <WrapPanel>
        <self:TimerControl Time="{Binding StartTime}"/>
    </WrapPanel>
</UserControl>

此代码不起作用。为什么? TimerControl 的 DataContext 是继承的(或者您可能根本替换它),同时,当您处理 StartTime 时,您会想到 ViewModel 作为 DataContext。因此,您应该清楚地指向正确的 DataContext:

 <self:Timer Time="{Binding DataContext.StartTime, ElementName=Root}"/> 

===UPDATE===

我的 Timer 控件的整个代码(如您所见,我的 Timer 有文本框,当您输入一些文本时,文本框会引发适当的事件,我们处理并设置 Time 属性):

public partial class Timer : UserControl
{
    public Timer()
    {
        InitializeComponent();
    }

    public DateTime? Time
    {
        get
        {
            return (DateTime?)this.GetValue(Timer.TimeProperty);
        }
        set
        {
            this.SetValue(Timer.TimeProperty, value);
        }
    }

    public static readonly DependencyProperty TimeProperty =
        DependencyProperty.Register("Time",
            typeof(DateTime?),
            typeof(Timer),
            new FrameworkPropertyMetadata(null, FrameworkPropertyMetadataOptions.BindsTwoWayByDefault, (d, e) => { }));

    private void TextBox_TextChanged(object sender, TextChangedEventArgs e)
    {
        if (DateTime.Now.Ticks % 2 == 0)
        {
            this.Time = DateTime.Now;
        }
        else
        {
            this.Time = null;
        }
    }

}

并且 XAML:

<UserControl x:Class="Test.Timer">
    <Grid>
        <TextBox TextChanged="TextBox_TextChanged"/>
    </Grid>
</UserControl>

XAML 中时间控件的用法:

<UserControl x:Class="Test.StartupView"
             xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
             xmlns:self="clr-namespace:Test"
             Name="Root">
    <WrapPanel>
        <self:Timer Time="{Binding DataContext.StartTime, ElementName=Root}"/>
    </WrapPanel>
</UserControl> 

StartupView 的代码隐藏:

    public StartupView()
    {
        InitializeComponent();
        this.DataContext = new ViewModel();
    }

ViewModel 中的属性保持不变。在调试过程中,每当我更改计时器中的文本时,都会触发 StartTime 属性的设置器。

Your implementation of DateTimeValue property shown in this question is certainly wrong and leads to exception, because DateTimeValue should be dependency property.

But you mentioned that you have tried to use dependency property with no success. I suppose the reason is in collision of DataContexts and your XAML looks like this:

<UserControl x:Class="Test.SomeView"
             xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
             xmlns:self="clr-namespace:Test"
             Name="Root">
    <WrapPanel>
        <self:TimerControl Time="{Binding StartTime}"/>
    </WrapPanel>
</UserControl>

This code doesn't work. Why? DataContext of TimerControl is inherited (or maybe you replace it at all), meanwhile when you address to StartTime you have in mind ViewModel as DataContext. So you should clearly point to correct DataContext:

 <self:Timer Time="{Binding DataContext.StartTime, ElementName=Root}"/> 

===UPDATE===

The whole code of my Timer control (as you can see my Timer has textbox, when you input some text, textbox raises appropriate event, which we handle and set Time property):

public partial class Timer : UserControl
{
    public Timer()
    {
        InitializeComponent();
    }

    public DateTime? Time
    {
        get
        {
            return (DateTime?)this.GetValue(Timer.TimeProperty);
        }
        set
        {
            this.SetValue(Timer.TimeProperty, value);
        }
    }

    public static readonly DependencyProperty TimeProperty =
        DependencyProperty.Register("Time",
            typeof(DateTime?),
            typeof(Timer),
            new FrameworkPropertyMetadata(null, FrameworkPropertyMetadataOptions.BindsTwoWayByDefault, (d, e) => { }));

    private void TextBox_TextChanged(object sender, TextChangedEventArgs e)
    {
        if (DateTime.Now.Ticks % 2 == 0)
        {
            this.Time = DateTime.Now;
        }
        else
        {
            this.Time = null;
        }
    }

}

And XAML:

<UserControl x:Class="Test.Timer">
    <Grid>
        <TextBox TextChanged="TextBox_TextChanged"/>
    </Grid>
</UserControl>

Usage of Time control in XAML:

<UserControl x:Class="Test.StartupView"
             xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
             xmlns:self="clr-namespace:Test"
             Name="Root">
    <WrapPanel>
        <self:Timer Time="{Binding DataContext.StartTime, ElementName=Root}"/>
    </WrapPanel>
</UserControl> 

Code behind of StartupView:

    public StartupView()
    {
        InitializeComponent();
        this.DataContext = new ViewModel();
    }

Property in ViewModel remains the same. During debugging setter of StartTime property fires every time when I change text in Timer.

冷弦 2024-11-12 14:55:47

你到底想做什么?

您无法绑定到标准属性。如果你想绑定,你应该使用依赖属性。

public DateTime? DateTimeValue
{
    get { return (DateTime?)GetValue(DateTimeValueProperty); }
    set { SetValue(DateTimeValueProperty, value); }
}

// Using a DependencyProperty as the backing store for DateTimeValue.  This enables animation, styling, binding, etc...
public static readonly DependencyProperty DateTimeValueProperty =
    DependencyProperty.Register("DateTimeValue", typeof(DateTime?), typeof(TimeControl), new UIPropertyMetadata(null));

在 UserControl 内部:

<TextBox Text="{Binding DateTimeValue,RelativeSource={RelativeSource AncestorLevel=1, Mode=FindAncestor,AncestorType=UserControl}, Converter=...}" />

直接绑定到 DateTimeValue 是不可能的,因为没有可用于 string->DateTime 的转换器,因此您必须编写 IValueConverter 并在绑定中指定它。

当然,您应该能够从外部直接绑定该值。

What excatly do you want to do?

You can't bind to a standard property. If you want to bind you should use a dependency property.

public DateTime? DateTimeValue
{
    get { return (DateTime?)GetValue(DateTimeValueProperty); }
    set { SetValue(DateTimeValueProperty, value); }
}

// Using a DependencyProperty as the backing store for DateTimeValue.  This enables animation, styling, binding, etc...
public static readonly DependencyProperty DateTimeValueProperty =
    DependencyProperty.Register("DateTimeValue", typeof(DateTime?), typeof(TimeControl), new UIPropertyMetadata(null));

Inside the UserControl:

<TextBox Text="{Binding DateTimeValue,RelativeSource={RelativeSource AncestorLevel=1, Mode=FindAncestor,AncestorType=UserControl}, Converter=...}" />

To bind directly to a DateTimeValue is not possible because there is no converter available for string->DateTime so you have to write an IValueConverter and specify this in your binding.

From outside of course you should be able to bind the value directly.

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