DependencyProperty VS управление состоянием без избыточности

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

Вот код:

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

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


person user256890    schedule 21.04.2011    source источник
comment
Просто интересно, почему вы не расширили ItemsControl с DependencyProperty, который изменяет видимость ваших детей при ее изменении? Я также разместил альтернативный метод в качестве ответа.   -  person myermian    schedule 21.04.2011


Ответы (4)


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

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

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

РЕДАКТИРОВАТЬ:

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

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

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

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

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

person Matěj Zábský    schedule 21.04.2011
comment
Вы должны показать код, как это реализовать. Непонятно, как на основании того, что вы написали. - person Gabe; 21.04.2011

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

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

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

person Rick Sladkey    schedule 21.04.2011

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

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

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

person default.kramer    schedule 21.04.2011
comment
Хотя ваше предложение определенно работает, использование преобразователей значений (особенно если это делается часто) затрудняет интерпретацию кода. Реализация конкретных задач рассредоточена, что нехорошо. - person user256890; 23.04.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 не только скрывает объект, но и не влияет на измерение/расположение других элементов UI. Скрытый влияет на измерение и расположение других элементов, но на самом деле не рисует элемент (подумайте об этом больше как о «невидимом»).

person myermian    schedule 21.04.2011