DependencyProperty VS управление состоянием без избыточности - PullRequest
2 голосов
/ 21 апреля 2011

Предположим, что у нас есть простой пользовательский интерфейс, который имеет только одну переменную состояния.Это состояние выражается в виде значения перечисления, например.Фаза1, Фаза2 и т. Д. В зависимости от состояния (фазы) пользовательского интерфейса, различных элементов пользовательского интерфейса, окон, которые должны быть видимыми или скрытыми.

Вот код:

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, то я добавлю избыточность в управление состоянием.Помимо дополнительного бремени, чтобы держать все в синхронизации, это может даже стать непоследовательным (если я не могу синхронизировать хорошо).

Каков будет правильный способ избежать избыточности в управлении состоянием пользовательского интерфейса, но при этом использовать возможности привязки данных, обеспечиваемые DependencyProperty-s?

Ответы [ 4 ]

3 голосов
/ 21 апреля 2011

Вы можете использовать INotifyPropertyChanged . Просто отправьте уведомление об изменении для данного WindowXVisible при изменении CurrentState (для этого DP имеет обратный вызов ).

Привязки обычно могут прослушивать изменения с помощью уведомлений DependencyProperty или INotifyPropertyChanged (которые должны отправляться вручную, в отличие от DP).

Вы можете использовать этот инструмент , чтобы автоматически генерировать вызовы уведомлений (без небольшого увеличения сложности вашего кода). Он справляется даже с такими нетривиальными случаями на удивление хорошо.

EDIT:

Зарегистрируйте это в PropertyMetadata объекта CurrentStateProperty.

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

OnPropertyChanged просто вызывает событие PropertyChanged с этим в качестве отправителя и строки в качестве имени свойства.

Это приведет к обновлению привязок Window1Visible и Window2Visible и получению новых значений.

Кстати, вы должны попытаться найти лучшие имена, чем Window1 и WIndow2.

2 голосов
/ 22 апреля 2011

Есть несколько хороших ответов, поэтому я не буду предлагать конкретное решение напрямую, но рассмотрю общую проблему, с которой вы сталкиваетесь.Вы боитесь, что добавление некоторого механизма свойств зависимостей для репликации кажущихся простыми концепций сопоставления состояний, которые у вас уже есть, потенциально избыточно или, что еще хуже, может работать некорректно.

Правда состоит в том, что вы просто будете реализовывать то жеконцепции, использующие инфраструктуру свойств зависимостей вместо инфраструктуры CLR.Да, это будет немного более многословно, но это основополагающая вещь, на которой основан весь WPF.Например, ListBox имеет SelectedItem и SelectedIndex, которые необходимо синхронизировать, как в вашем примере.Как только вы все сделаете правильно, независимо от того, что вы делаете, они никогда не выйдут из синхронизации.

Единственный раз, когда видимость может измениться, это когда состояние меняется, и вы можете установить видимость из обратного вызова изменения состояния., , если вы уверены, что обратный вызов будет называться .Фактически даже без свойств зависимостей вы иногда видите, как программисты используют именно эту стратегию, чтобы избежать вычислений в геттере для свойства.Так что думайте об этом не как о избыточности, а просто как о другом способе достижения той же цели.

1 голос
/ 21 апреля 2011

Привязка не работает для объектов 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не только скрывает объект, но и не влияет на измерение / расположение других элементов UIE.Скрытое влияет на Измерение и Расположение других элементов, но фактически не рисует элемент (думайте о нем больше как «Невидимый»).

1 голос
/ 21 апреля 2011

Вы можете привязать к CurrentState и написать IValueConverter, который преобразуется в Visibility.

Этот вопрос может быть полезен.

РЕДАКТИРОВАТЬ - Предполагая, что вам нужно свойство зависимости для привязки данных. В противном случае INotifyPropertyChanged будет проще.

...