2

让我们假设我们有一个只有一个状态变量的简单 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,那么我将在状态管理中引入冗余。除了保持一切同步的额外负担外,它甚至会变得不一致(如果我不能很好地同步)。

What would be the correct way to avoid introducing redundancy in the UI state management, but still leverage the power of databinding facilitated by DependencyProperty-s?

4

4 回答 4

3

You can use INotifyPropertyChanged. Simply send a change notification for the given WindowXVisible when CurrentState changes (DP has a callback for this).

Bindings can generally listen to changes either via DependencyProperty or INotifyPropertyChanged notifications (which must be sent manually though, unlike with DP).

You can use this tool to generate the notification calls automatically (without increasing complexity of your code by a bit). It handles even such nontrivial cases surprisingly well.

EDIT:

Register this into PropertyMetadata of the CurrentStateProperty.

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

OnPropertyChanged simply invokes PropertyChanged event with this as sender and the string as property name.

This will cause Window1Visible and Window2Visible bindings to update and get the new values.

By the way, you should try to figure out better names than Window1 and WIndow2.

于 2011-04-21T18:11:01.130 回答
2

There are several good answers so I won't directly propose a specific solution but I will address the general concern you are facing. You are afraid that adding some dependency property machinery to replicate the seeming simple state matching concepts you already have is potentially redundant or even worse that it might not work correctly.

The truth is that you will be simply reimplementing the same concepts using dependency property infrastructure instead of CLR infrastructure. Yes, it will be a little more verbose, but this is rock-solid stuff that the whole of WPF is based on. For example a ListBox has a SelectedItem and SelectedIndex that it needs to keep in sync, just like in your example. Once you get it right, no matter what you do, they will never get out of sync.

The only time the visiblity can change is when the state changes and so you can set the visibility from the state change callback, as long as you trust that the callback will be called. In fact even without dependency properties, you sometimes see programmers using exactly this strategy to avoid computations in the getter for a property. So think of it not a redundancy but merely a different way to accomplish the same goal.

于 2011-04-21T22:22:13.203 回答
1

You can bind to CurrentState and write an IValueConverter that converts to Visibility.

This question might be useful.

EDIT - Assuming you need a dependency property for databinding purposes. Otherwise, INotifyPropertyChanged is easier.

于 2011-04-21T18:22:58.657 回答
1

Binding does not work for CLR objects. It only works for Dependency Properties. Hence, I would turn WindowXStyle into a read-only dependency property:

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;
            }
        }

        //...
    }
}

Take note that you'll also probably want to use Visiblity.Collapsed as apposed to Visiblity.Hidden ... Collapsed not only hides the object, but it does not affect the Measurement/Arrangement of other UIElements. Hidden affects the Measurement and Arrangement of other elements, but it doesn't actually draw the element (think of it more along the lines of "Invisible").

于 2011-04-21T19:02:43.483 回答