【问题标题】:DependencyProperty VS redundancy-free state managementDependencyProperty VS 无冗余状态管理
【发布时间】:2011-08-10 12:18:13
【问题描述】:

假设我们有一个只有一个状态变量的简单 UI。此状态表示为枚举值,例如。 Phase1、Phase2 等。根据 UI 所处的状态(阶段)、不同的 UI 元素、应该可见或隐藏的窗口。

代码如下:

public enum Phases { Phase1, Phase2, Phase3 }

public class UIStateModel : DependencyObject
{
    public static DependencyProperty CurrentStateProperty =
        DependencyProperty.Register("CurrentStateProperty",
                                    typeof(Phases),
                                    typeof(UIStateModel));
    public Phases CurrentState
    {
        get { return (Phases)GetValue(CurrentStateProperty); }
        set { SetValue(CurrentStateProperty, value); }
    }
    public Visibility Window1Visible // Databound to Window1.Visibility
    {
        get
        {
            if (this.CurrentState == Phases.Phase1) return Visibility.Visible;
            else return Visibility.Hidden;
        }
    }
    public Visibility Window2Visible // Databound to Window2.Visibility
    {
        get
        {
            if (this.CurrentState == Phases.Phase2) return Visibility.Visible;
            else return Visibility.Hidden;
        }
    } 
    ...
}

问题是与上面代码的数据绑定不起作用,因为WindowXVisible属性不是DependencyProperty-s。如果我将所有属性都转为 DependencyProperty,那么我将在状态管理中引入冗余。除了保持一切同步的额外负担外,它甚至会变得不一致(如果我不能很好地同步)。

什么是避免在 UI 状态管理中引入冗余但仍利用 DependencyProperty-s 促进的数据绑定功能的正确方法?

【问题讨论】:

  • 只是想知道为什么您没有扩展 ItemsControl 的 DependencyProperty,当它改变时会改变您孩子的可见性?我还发布了另一种方法作为答案。

标签: .net wpf dependency-properties redundancy


【解决方案1】:

您可以使用INotifyPropertyChanged。当 CurrentState 更改时,只需为给定的 WindowXVisible 发送更改通知(DP has a callback 为此)。

绑定通常可以通过 DependencyProperty 或 INotifyPropertyChanged 通知来监听更改(但必须手动发送,与 DP 不同)。

您可以使用this tool 自动生成通知调用(不会稍微增加代码的复杂性)。它甚至可以很好地处理这种不平凡的情况。

编辑:

将此注册到 CurrentStateProperty 的 PropertyMetadata 中。

    private static void OnCurrentStateChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
    {
        this.OnPropertyChanged("CurrentState");
        this.OnPropertyChanged("Window1Visible");
        this.OnPropertyChanged("Window2Visible");
    }

OnPropertyChanged 只是调用 PropertyChanged 事件,并将 this 作为发送者,将字符串作为属性名称。

这将导致 Window1Visible 和 Window2Visible 绑定更新并获取新值。

顺便说一句,你应该尝试找出比 Window1 和 WIndow2 更好的名称。

【讨论】:

  • 你应该展示一些如何实现它的代码。根据您所写的内容尚不清楚。
【解决方案2】:

有几个很好的答案,所以我不会直接提出具体的解决方案,但我会解决您面临的普遍问题。您担心添加一些依赖属性机制来复制您已有的看似简单的状态匹配概念可能是多余的,甚至更糟的是它可能无法正常工作。

事实上,您将使用依赖属性基础结构而不是 CLR 基础结构简单地重新实现相同的概念。是的,它会更冗长一些,但这是整个 WPF 所基于的坚如磐石的东西。例如,ListBox 具有需要保持同步的 SelectedItemSelectedIndex,就像在您的示例中一样。一旦你做对了,无论你做什么,它们都不会不同步。

可见性改变的唯一时间是状态改变的时候,所以你可以从状态改变回调中设置可见性,只要你相信回调会被调用。事实上,即使没有依赖属性,您有时也会看到程序员正是使用这种策略来避免在 getter 中对属性进行计算。所以不要认为它是多余的,而只是实现相同目标的不同方式。

【讨论】:

    【解决方案3】:

    您可以绑定到CurrentState 并编写一个转换为可见性的IValueConverter

    This question 可能有用。

    编辑 - 假设您需要一个依赖属性来进行数据绑定。否则,INotifyPropertyChanged 会更容易。

    【讨论】:

    • 虽然您的建议确实有效,但使用值转换器(特别是如果经常这样做)会使代码更难解释。特定关注点的实现分散,这是不好的。
    【解决方案4】:

    绑定不适用于 CLR 对象。它仅适用于依赖属性。因此,我会将 WindowXStyle 变成一个只读的依赖属性:

    using System;
    using System.ComponentModel;
    using System.Windows;
    
    namespace MyNamespace
    {
        public enum Phases { Phase1, Phase2, Phase3 }
    
        public class UIStateModel : DependencyObject
        {
            static UIStateModel()
            {
                CurrentStateProperty = DependencyProperty.Register("CurrentState", typeof(Phases), typeof(UIStateModel),
                    new FrameworkPropertyMetadata
                    {
                        PropertyChangedCallback = new PropertyChangedCallback(OnCurrentStateChanged)
                    });
    
                Window1VisibilityPropertyKey = DependencyProperty.RegisterReadOnly("Window1Visiblity", typeof(Visibility), typeof(UIStateModel),
                    new PropertyMetadata());
                Window1VisibilityProperty = Window1VisibilityPropertyKey.DependencyProperty;
    
                Window2VisibilityPropertyKey = DependencyProperty.RegisterReadOnly("Window2Visiblity", typeof(Visibility), typeof(UIStateModel),
                    new PropertyMetadata());
                Window2VisibilityProperty = Window2VisibilityPropertyKey.DependencyProperty;
            }
    
            public Phases CurrentState
            {
                get { return (Phases)GetValue(CurrentStateProperty); }
                set { SetValue(CurrentStateProperty, value); }
            }
    
            public static DependencyProperty CurrentStateProperty;
    
            [DesignerSerializationVisibility(DesignerSerializationVisibility.Hidden)]
            public Visibility Window1Visibility
            {
                get { return (Visibility)GetValue(Window1VisibilityProperty); }
                protected set { SetValue(Window1VisibilityPropertyKey, value); }
            }
    
            public static readonly DependencyProperty Window1VisibilityProperty;
            private static readonly DependencyPropertyKey Window1VisibilityPropertyKey;
    
            [DesignerSerializationVisibility(DesignerSerializationVisibility.Hidden)]
            public Visibility Window2Visibility
            {
                get { return (Visibility)GetValue(Window2VisibilityProperty); }
                protected set { SetValue(Window2VisibilityPropertyKey, value); }
            }
    
            public static readonly DependencyProperty Window2VisibilityProperty;
            private static readonly DependencyPropertyKey Window2VisibilityPropertyKey;
    
    
            public Visibility Window1Visible // Databound to Window1.Visibility
            {
                get
                {
                    if (this.CurrentState == Phases.Phase1) return Visibility.Visible;
                    else return Visibility.Hidden;
                }
            }
            public Visibility Window2Visible // Databound to Window2.Visibility
            {
                get
                {
                    if (this.CurrentState == Phases.Phase2) return Visibility.Visible;
                    else return Visibility.Hidden;
                }
            }
    
            private static void OnCurrentPageChanged(DependencyObject obj, DependencyPropertyChangedEventArgs e)
            {
                UIStateModel element = (UIStateModel)obj;
    
                Phases oldPhase = (Phases)e.OldValue;
                Phases newPhase = (Phases)e.NewValue;
    
                //Probably want to use Collapsed as apposed to Hidden for UI Measure/Arrange purposes
                switch (oldPhase)
                {
                    case Phases.Phase1:
                        element.Window1Visibility = Visibility.Hidden;
                        break;
                    case Phases.Phase2:
                        element.Window2Visibility = Visibility.Hidden;
                        break;
                    case Phases.Phase3:
                        //element.Window3Visiblity = Visibility.Hidden;
                        break;
                    default:
                        //??
                        break;
                }
    
                switch (newPhase)
                {
                    case Phases.Phase1:
                        element.Window1Visibility = Visibility.Visible;
                        break;
                    case Phases.Phase2:
                        element.Window2Visibility = Visibility.Visible;
                        break;
                    case Phases.Phase3:
                        //element.Window3Visiblity = Visibility.Visible;
                        break;
                    default:
                        //??
                        break;
                }
            }
    
            //...
        }
    }
    

    请注意,您可能还希望将 Visiblity.Collapsed 与 Visiblity.Hidden 相对使用 ... Collapsed 不仅隐藏对象,而且不影响其他 UIElement 的测量/排列。隐藏会影响其他元素的测量和排列,但它实际上并不绘制元素(更多地按照“不可见”的方式考虑它)。

    【讨论】:

      猜你喜欢
      • 2010-10-27
      • 2011-01-17
      • 2018-08-18
      • 2014-10-19
      • 1970-01-01
      • 2017-02-27
      • 1970-01-01
      • 1970-01-01
      • 2020-03-10
      相关资源
      最近更新 更多