简体   繁体   English

PropertyChangedEventHandler都为空,但是绑定只能工作一次,为什么?

[英]PropertyChangedEventHandler is both times null, but the binding works only once, why?

I made a little Project to learn a bit about MVVM. 我做了一个小项目,以了解有关MVVM的知识。 It is a calculator that calculates when you are allowed to go home from work. 它是一个计算器,用于计算何时允许您下班回家。

I made a UserControl with two Textboxes and a single label as a simple "TimePicker". 我用两个Textboxes和一个标签作为简单的“ TimePicker”制作了一个UserControl This Usercontrol has a ViewModel (the Mainwindow even has one) which manages the time of one Timepicker. 此用户控件有一个ViewModel(主窗口甚至有一个),它管理一个Timepicker的时间。 It has three Properties: an int called TimeValue which is just the value of hours and minutes and two ints called Hours and Minutes . 它具有三个属性:一个名为TimeValue的int,它仅是hours和minutes的值;两个int称为Hours and Minutes My two Textboxes are bound to them and display them. 我的两个文本框已绑定并显示它们。 Setting one value via Textbox also resets Time, setting time (via Property) resets Hours and Minutes, both textboxes are updated after setting this value. 通过文本框设置一个值还会重置时间,通过属性设置时间(通过属性)会重置小时和分钟,两个文本框在设置此值后都会更新。

This works quit fine. 这项工作很好。 Now I wanted to add a second Property called ReadOnly . 现在,我想添加第二个属性ReadOnly ReadOnly is needed for the TimePicker which displayes the time to go. 显示时间的TimePicker需要ReadOnly。 It makes no sense to manually set this time so I want to have a possibility to set both Textboxes IsReadOnly Property. 这次手动设置没有任何意义,因此我希望可以同时设置两个Textboxes IsReadOnly属性。

ReadOnly is now a second Property of the UserControl. ReadOnly现在是UserControl的第二个属性。 Because I am lazy I wanted to directly bind the Property and both Textboxes via the UserControl and bind only the IsReadOnly -Property to the UserControl. 因为我很懒,所以我想直接通过UserControl绑定属性和两个文本框,并且仅将IsReadOnly -Property绑定到UserControl。

This was the Code of my Idea (Usercontrol): 这是我的想法代码(用户控件):

public partial class TimeBox : UserControl, INotifyPropertyChanged
{
    private SingleTimeViewModel viewModel;

    //... other Properties

    public static DependencyProperty ReadOnlyProperty = DependencyProperty.Register("ReadOnly", typeof(Boolean), typeof(TimeBox), new PropertyMetadata(false));

    // Schnittstellen-Ereignis  
    public event PropertyChangedEventHandler PropertyChanged;
    protected internal void OnPropertyChanged(string propertyname)
    {
        if (PropertyChanged != null)
            PropertyChanged(this, new PropertyChangedEventArgs(propertyname));
    }

    public TimeBox()
    {
        InitializeComponent();
        viewModel = new SingleTimeViewModel(SingleTime.CreateSingleTime());
        this.DataContext = viewModel;
    }

    //... Code of other Properties

    private bool _ReadOnly;
    public bool ReadOnly
    {
        get
        {
            return _ReadOnly;
        }
        set
        {
            if (_ReadOnly == value)
                return;
            _ReadOnly = value;
            OnPropertyChanged("ReadOnly");
        }
    }

    //... Other Methods
}

This was bound to both Textboxes via XAML (Bindings for Text lead to ViewModel, IsReadOnly should bind to TimeBox): 这通过XAML绑定到两个文本框( Text绑定导致ViewModel,IsReadOnly应该绑定到TimeBox):

<UserControl x:Name="TimeBoxControl" x:Class="TimeCalculator.TimeBox"
         xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
         ... >
    <WrapPanel Grid.Column="7" HorizontalAlignment="Stretch" Margin="0,0,0,0" Grid.Row="1" VerticalAlignment="Center" >
        <TextBox x:Name="txtBxHours" ... Text="{Binding Hours}" ... IsReadOnly="{Binding ReadOnly, ElementName=TimeBoxControl}" />
        <Label x:Name="lblSeparator" ... />
        <TextBox x:Name="txtBxMinutes" ... Text="{Binding Minutes}" ...  IsReadOnly="{Binding ReadOnly, ElementName=TimeBoxControl}" />
   </WrapPanel>
</UserControl>

I made the values readonly at the Constructor of the Mainwindow of my Project after InitializeComponent . InitializeComponent之后,我在项目的主窗口的构造函数中将值设为只读。 Therefore I used the following lines: 因此,我使用了以下几行:

this.TmBxMayGo.ReadOnly = true;
this.TmBxMustGo.ReadOnly = true;
this.TmBxTimeUntilMayGo.ReadOnly = true;
this.TmBxTimeUntilMustGo.ReadOnly = true;
this.TmBxCurrentOvertime.ReadOnly = true;

This did not work, after some debugging I found out it did not because PropertyChangedEventHandler PropertyChanged was always null . 这不起作用,经过一些调试后,我发现它不起作用,因为PropertyChangedEventHandler PropertyChanged始终为null I searched a lot to find a solution for this problem, but I made none of the common mistakes (eg forgot : INotifyPropertyChanged , wrong names in Strings or else). 我进行了很多搜索以找到解决此问题的方法,但我没有犯任何常见错误(例如,忘记了: INotifyPropertyChanged ,字符串中错误的名称或其他名称)。

I finally gave up and made it via ViewModel. 我终于放弃了,并通过ViewModel做到了。 But then I realised that PropertyChangedEventHandler PropertyChanged was also null when I set it via ViewModel, but the textboxes were ReadOnly after calling. 但是后来我意识到,当我通过ViewModel进行设置时, PropertyChangedEventHandler PropertyChanged也为null ,但是调用后文本框为ReadOnly。

Now the two questions I have: 现在我有两个问题:

  1. Does it make sense to make an own ViewModel for an single Usercontrol? 为单个Usercontrol制作自己的ViewModel是否有意义?
  2. Why is that so? 为什么会这样? How can it be that PropertyChanged is null twice but only works once? PropertyChanged两次为null却只能工作一次又怎么可能呢?
  1. Yes, it makes sense to have a single ViewModel for the single standlone logically seperated UI. 是的,为单个独立的逻辑上分离的UI拥有一个ViewModel是有意义的。 It divides the responsibility from the mainviewmodel. 它将责任与mainview模型分开。 So put all your properties in your ViewModel. 因此,将所有属性放入ViewModel中。

  2. WPF only attaches the handler to the PropertyChanged event of the INotifyPropertyChanged object when that object is set as the DataContext of the view that is why your PropertyChanged is null before you set the DataContext of your usercontrol. 当该对象设置为视图的DataContext时,WPF仅将处理程序附加到INotifyPropertyChanged对象的PropertyChanged事件,这就是为什么在设置用户控件的DataContext之前PropertyChanged为null的原因。 And your textboxes are still disabled because while initializing the binding, getter of these properties are called and UI is updated with the default values you gave. 而且您的文本框仍然处于禁用状态,因为在初始化绑定时,将调用这些属性的getter并使用您提供的默认值来更新UI。

a cleaner approach for doing the mentioned would be something like this 进行上述提到的更清洁的方法将是这样的

    public bool ReadOnly
    {
        get { return (bool)GetValue(ReadOnlyProperty); }
        set { SetValue(ReadOnlyProperty, value); }
    }

    // Using a DependencyProperty as the backing store for ReadOnly.  This enables animation, styling, binding, etc...
    public static readonly DependencyProperty ReadOnlyProperty =
        DependencyProperty.Register("ReadOnly", typeof(bool), typeof(TimeBox), new PropertyMetadata(false, null, CoerceReadOnly));

    private static object CoerceReadOnly(DependencyObject d, object baseValue)
    {
        if ((d as TimeBox)._Enabled == baseValue)
            return DependencyProperty.UnsetValue;
        return baseValue;
    }

here I am using Coerce Value callback to check the condition, so by returning DependencyProperty.UnsetValue will cancel the property change otherwise it will proceed 在这里我使用Coerce Value回调检查条件,因此通过返回DependencyProperty.UnsetValue将取消属性更改,否则它将继续

more info on Coerce here http://msdn.microsoft.com/en-us/library/ms745795(v=vs.110).aspx#Advanced 有关Coerce的更多信息,请参见http://msdn.microsoft.com/zh-cn/library/ms745795(v=vs.110).aspx#Advanced

for other question, I would say you may probably not require to create a usercontrol unless you want to deploy a control library. 对于其他问题,我想除非您要部署控件库,否则可能不需要创建用户控件。 Do try to leverage data templates for the same. 一定要尽量利用数据模板。

and secondly since you are binding to a usercontrol which work on dependency framework the regular notifications may not work as expected. 其次,由于您绑定到在依赖项框架上工作的用户控件,因此常规通知可能无法按预期方式工作。

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM