用户控制dependencyproperychanged事件触发,但绑定的viewmodel没有更新...发生了什么?

时间:2014-01-24 16:10:11

标签: c# wpf datepicker dependency-properties

我最近偶然发现了使用WPF DatePicker控件的this issue。基本上,弹出窗口会导致异常:

  1. 通过样式设置DatePickers控件模板
  2. 将IsEnabled绑定到ViewModel上的属性
  3. 运行程序并启用DatePicker
  4. 下拉日历弹出窗口
  5. 禁用DatePicker,然后重新启用它
  6. 再次下拉日历弹出窗口
  7. 我知道这是一个非常具体的场景,但它一直在我的应用程序中发生。完整控件的整个用户控件在禁用时启动,用户选择记录(填写表单),编辑,保存,重复。第二次运行它时,从DatePicker中选择日历会使其崩溃。

    我已经将DatePicker的控件模板设置为一种样式,以便在禁用时显示禁用的文本框,以更好地适应其余禁用外观的外观(我对组合框执行了同样的操作)。

    因此,知道这是我的问题的原因,我开始创建自己的用户控件来处理DatePicker的禁用外观。它只是包装一个Datepicker,提供一些依赖属性来绑定到我关心的DatePicker属性,并在禁用时用文本框交换用户控件的模板。这样我就可以得到我想要的外观,而无需查看DatePicker的错误。

    我的问题是,当我在用户控件上选择一个新日期时,DependencyPropertyChanged事件使用新值触发,这些值永远不会进入我绑定到的外部视图模型。我每个方向都看着它,这对我没有任何意义:

    后面的DisableableDatePicker代码

    public partial class DisableableDatePicker : UserControl
    {
        public static readonly DependencyProperty SelectedDateProperty = DependencyProperty.Register("SelectedDate", typeof(DateTime?), typeof(DisableableDatePicker), new PropertyMetadata(DateChanged));
    
        private static void DateChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
        {
            Console.WriteLine(((DateTime)e.NewValue).ToString("yyyy-MM-dd"));
        }
    
        public DateTime? SelectedDate
        {
            get { return (DateTime?)GetValue(SelectedDateProperty); }
            set { SetValue(SelectedDateProperty, value); }
        }
    
        public static readonly DependencyProperty DisplayDateEndProperty = DependencyProperty.Register("DisplayDateEnd", typeof(DateTime?), typeof(DisableableDatePicker));
        public DateTime? DisplayDateEnd
        {
            get { return (DateTime?)GetValue(DisplayDateEndProperty); }
            set { SetValue(DisplayDateEndProperty, value); }
        }
    
        public static readonly DependencyProperty DisplayDateStartProperty = DependencyProperty.Register("DisplayDateStart", typeof(DateTime?), typeof(DisableableDatePicker));
        public DateTime? DisplayDateStart
        {
            get { return (DateTime?)GetValue(DisplayDateStartProperty); }
            set { SetValue(DisplayDateStartProperty, value); }
        }
    
        public static readonly DependencyProperty BlackoutDatesProperty = DependencyProperty.Register("BlackoutDates", typeof(List<DateTime>), typeof(DisableableDatePicker));
        public List<DateTime> BlackoutDates
        {
            get { return (List<DateTime>)GetValue(BlackoutDatesProperty); }
            set { SetValue(BlackoutDatesProperty, value); }
        }
    
        public DisableableDatePicker()
        {
            InitializeComponent();
    
            ClearValue(SelectedDateProperty);
            ClearValue(DisplayDateEndProperty);
            ClearValue(DisplayDateStartProperty);
            ClearValue(BlackoutDatesProperty);
            ClearValue(IsEnabledProperty);
        }
    }
    

    我在DependencyPropertyChanged事件'DateChanged'中添加了在选择一个时将新值打印到控制台。这些新值在控制台中显示没问题。

    DisableableDatePicker XAML

    <UserControl x:Class="ALERT_Human_Resources.View.DisableableDatePicker"
                 ...             
                 Margin="0,0,0,3" x:Name="root" >
        <UserControl.Resources>
            <local:InverseBooleanConverter x:Key="DisableableDatePicker_InvertBoolean" />
            <system:String x:Key="DateFormat">yyyy-MM-dd</system:String>
        </UserControl.Resources>
        <UserControl.Style>
            <Style TargetType="{x:Type UserControl}">
                <Style.Triggers>
                    <Trigger Property="IsEnabled" Value="False">
                        <Setter Property="Template">
                            <Setter.Value>
                                <ControlTemplate>
                                    <TextBox IsEnabled="False" Text="{Binding Path=SelectedDate, ElementName=root" />
                                </ControlTemplate>
                            </Setter.Value>
                        </Setter>
                    </Trigger>
                </Style.Triggers>
            </Style>
        </UserControl.Style>
        <DatePicker
                        local:DependencyPropertyHost.BlackoutDates="{Binding Path=BlackoutDates, ElementName=root}"
                        DisplayDateEnd="{Binding Path=DisplayDateEnd, ElementName=root}" 
                        DisplayDateStart="{Binding Path=DisplayDateStart, ElementName=root}"
                        SelectedDate="{Binding Path=SelectedDate, ElementName=root, StringFormat={StaticResource DateFormat}}"
                        IsEnabled="{Binding Path=IsEnabled, ElementName=root}" />
    </UserControl>
    

    DisableableDatePicker用法

    <view:DisableableDatePicker Margin="0,0,0,3" 
        SelectedDate="{Binding HighlightDate, UpdateSourceTrigger=PropertyChanged}" 
        DisplayDateStart="{Binding StartDate}" 
        DisplayDateEnd="{Binding EndDate}" 
        IsEnabled="{Binding IsEditing}" 
        BlackoutDates="{Binding BlackoutDates}" />
    

    DisableableDatePicker绑定ViewModel属性

    public DateTime HighlightDate
    {
        get
        {
            return SelectedDailyHighlight == null ? DateTime.MinValue : SelectedDailyHighlight.HighlightDate;
        }
        set
        {
            if (SelectedDailyHighlight != null && value != null && SelectedDailyHighlight.HighlightDate == value)
                return;
            SelectedDailyHighlight.HighlightDate = value;
            OnPropertyChanged("HighlightDate");
        }
    }
    

    因此,当我在ViewModels getter和setter中放置断点时,getter被正确命中,但是从不调用setter。我发现这很奇怪,因为我的依赖属性报告它看到了新的值。

    这个难题的最后一部分是我如何约束Blackout日期。我希望能够绑定到一个简单的日期列表。为了方便起见,我在附属物中添加了以下内容:

     public static readonly DependencyProperty BlackoutDatesProperty =
        DependencyProperty.RegisterAttached("BlackoutDates", typeof(List<DateTime>),
        typeof(DependencyPropertyHost), 
        new FrameworkPropertyMetadata(null, OnBlackoutDatesChanged));
    
    public static List<DateTime> GetBlackoutDates(DependencyObject d)
    {
        return (List<DateTime>)d.GetValue(BlackoutDatesProperty);
    }
    
    public static void SetBlackoutDates(DependencyObject d, List<DateTime> value)
    {
        d.SetValue(BlackoutDatesProperty, value);
    }
    
    private static void OnBlackoutDatesChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
    {
        DatePicker datePicker = d as DatePicker;
        if (e.NewValue != null && datePicker != null)
        {
            List<DateTime> blackoutDates = (List<DateTime>)e.NewValue;
            var toRemove = datePicker.BlackoutDates.Select
                                                    (x => x.Start).Except
                                                    (blackoutDates.Select(y => y)).ToList();
            foreach (DateTime date in toRemove)
            {
                datePicker.BlackoutDates.Remove(datePicker.BlackoutDates.Single(x => x.Start == date));
            }
            foreach (DateTime date in blackoutDates)
            {
                if (!datePicker.BlackoutDates.Contains(date) && 
                    date >= datePicker.DisplayDateStart && 
                    date <= datePicker.DisplayDateEnd && 
                    datePicker.SelectedDate != date)
                {
                    datePicker.BlackoutDates.Add(new CalendarDateRange(date));
                }
            }
        }
    }
    

1 个答案:

答案 0 :(得分:1)

默认情况下,依赖项属性绑定OneWay。这意味着它将从View Model中读取,但不会写入它。您有2个选项可以解决此问题。

选项1:在绑定中将模式设置为TwoWay。

<view:DisableableDatePicker Margin="0,0,0,3" 
    SelectedDate="{Binding HighlightDate}" 
    DisplayDateStart="{Binding StartDate}" 
    DisplayDateEnd="{Binding EndDate}" 
    IsEnabled="{Binding IsEditing}" 
    BlackoutDates="{Binding BlackoutDates, Mode=TwoWay}" />

选项2:在后面的代码中将默认绑定模式设置为TwoWay。

 public static readonly DependencyProperty BlackoutDatesProperty =
    DependencyProperty.RegisterAttached("BlackoutDates", typeof(List<DateTime>),
    typeof(DependencyPropertyHost), 
    new FrameworkPropertyMetadata(null, FrameworkPropertyMetadataOptions.BindsTwoWayByDefault,  OnBlackoutDatesChanged));