Возможны ли «изменчивые» привязки данных в Windows Forms? - PullRequest
1 голос
/ 01 августа 2010

Давайте предположим, что я реализую пользовательский интерфейс Winforms, где все команды придерживаются следующего шаблона:

interface ICommand
{
    bool CanExecute { get; }
    void Execute();
}

Кнопки или элементы меню, которые запускают такую ​​команду, должны иметь следующую настройку:

  • свойство Enabled связано с командой CanExecute
  • событие Click связано с командой Execute (через промежуточный обработчик событий из-за отличающихся сигнатур методов)

Проблема в CanExecute заключается в том, что реализация INotifyPropertyChanged не работает, поскольку это свойство не может быть изменено напрямую, но зависит от других факторов в программе.которые не должны быть связаны с командой.И не нужно запускать событие команды PropertyChanged в совершенно не связанных частях программы.

Как вы сообщите менеджеру связывания данных, когда CanExecute изменилось?

Вот (чисто вымышленный) пример моей проблемы:

bool CanExecute
{
    get
    {
        return User.LoggedInForAtLeastNMinutes(5);
        // how would you trigger data-binding updates for CanExecute? 
    }
}

В идеале, я бы хотел, чтобы пользовательский интерфейс постоянно проверял CanExecute (как если бы это было изменчивое поле), но AFAIK это не таккак работает привязка данных Winforms.У кого-нибудь есть решение этой проблемы?


Примечание: Мне известно о WPF, кстати.История моего вопроса в том, что я собираюсь постепенно улучшать существующее приложение Winforms в общем направлении WPF.Но на самом деле использование WPF и, следовательно, избавление от проблемы, о которой я спрашивал, сейчас неосуществимо.

Ответы [ 5 ]

2 голосов
/ 01 августа 2010

Я бы реализовал INotifyPropertyChanged независимо (или добавил бы событие CanExecuteChanged, которое имеет тот же эффект). Я бы очень старался, чтобы объекты знали, когда в нужное время вызывать событие измененного свойства, а не опрашивали.

Например, в вашем вымышленном примере вы можете иметь событие UserLoggedIn. В ответ на это вы можете установить 5-минутный таймер; по истечении этого таймера вы вызываете событие измененного свойства.

Если вы пойдете на избирательный подход, тогда вы столкнетесь с двумя опасностями:

  • Слишком частый опрос, когда ваше приложение использует проверку ЦП на наличие событий, которые еще не могут произойти (например, опрос каждые 10 секунд, чтобы увидеть, истекли ли 5 ​​минут)
  • Недостаточный опрос, когда элементы управления, связанные со свойствами CanExecute, отстают от остальной части пользовательского интерфейса (например, задержка между выбором текста и обновлением свойства CopyTextCommand.CanExecute)

Гибридный подход - это , принятый Microsoft Foundation Classes в C ++, который должен был выполнять эту проверку каждый раз, когда цикл обработки сообщений приложения был бездействующим. Это разумный подход, если вы знаете, что только взаимодействие с пользовательским интерфейсом может повлиять на ваше свойство CanExecute.

1 голос
/ 01 августа 2010

Используйте Timer для постоянного опроса свойства CanExecute.Вызвать событие PropertyChanged при изменении свойства.

0 голосов
/ 02 декабря 2010

Я опрашиваю событие Application.Idle, поскольку логика выполнения может быть простой, проблем не должно быть.

вот выдержка из моей текущей реализации CommandManager;

 public CommandManager()
    {
        Commands = new List<ICommand>();

        Binders = new List<ICommandBinder>
                      {
                          new ControlBinder(),
                          new MenuItemCommandBinder()
                      };

        Application.Idle += UpdateCommandState;
    }
 private void UpdateCommandState(object sender, EventArgs e)
    {
        Commands.Do(c => c.Enabled);
    }

(Do () - это просто метод расширения, который выполняет foreach, например linq Select (), но вместо Actionc выполняется действие)

Я писал об этом некоторое время назад, не стесняйтесь проверять: http://codewithpassion.blogspot.com/2010/11/icommand-and-commandmanager-for-windows.html

надеюсь, это поможет

0 голосов
/ 09 августа 2010

Я не знаю, почему я не подумал об этом более раннем ... Следующий пример демонстрирует изменение другого ответа, который я написал, но он ближе к BCL:

class Person : INotifyPropertyChanged
{
    public DateTime DateOfBirth
    {
        get { ... }
        set { ...; OnPropertyChanged("DateOfBirth"); }
    } 

    public bool IsDateOfBirthValid
    {
        get { ... }
    }

    public Person()
    {
        this.PropertyChanged += DateOfBirth_PropertyChanged;
    }

    void DateOfBirth_PropertyChanged(object sender, PropertyChangedEventArgs e)
    {
        if (this == sender && "DateOfBirth".Equals(e.PropertyName))
        {
            OnPropertyChanged("IsDateOfBirthValid");
        }
    }
}
0 голосов
/ 08 августа 2010

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


1) Сначала определите новый атрибут, который будет использоваться для указаниязависимость между свойствами:

class DependentOnAttribute : Attribute
{
    public DependentOnAttribute(string propertyName) { ... }
    public string PropertyName { get { ... } }
}

2) Во-вторых, примените атрибут к зависимому свойству, например, следующим образом:

class Person : INotifyPropertyChanged
{
    public DateTime DateOfBirth { get { ... }; set { ... } }

    [DependentOn("DateOfBirth")]
    public bool IsDateOfBirthValid { get { ... } }

    public event PropertyChangedEventHandler PropertyChanged;
}

3) В-третьих, определите метод расширения на PropertyChangedEventHandler, который будет использоваться для запуска события, рекурсивного разрешения зависимостей и запуска события для всех этих тоже:

public static void Notify(this PropertyChangedEventHandler propertyChangedEvent,
                          object obj, string propertyName)
{
    // make sure someone is subscribed to the event:
    if (propertyChangedEvent == null) return;

    // raise the PropertyChanged event for the specified property:
    propertyChangedEvent(obj, new PropertyChangedEventArgs(propertyName));

    // find all other properties that are dependent on the specified property:
    var namesOfDependentProperties = from property in obj.GetType().GetProperties()
                                     where ...  // (abbreviated for legibility)
                                     select property.Name;

    // trigger the event for all dependent properties (recursion!):
    foreach (var nameOfDependentProperty in namesOfDependentProperties)
    {
        propertyChangedEvent.Notify(obj, nameOfDependentProperty);
    }
}

Теперь PropertyChanged событие должно быть инициировано с помощью этого метода расширения, например, для DateOfBirth:

public DateTime DateOfBirth
{
     get { ... }
     set
     {
         ...;
         PropertyChanged.Notify(this, "DateOfBirth");
     }
 }

Это также вызовет PropertyChanged для всех зависимых свойств.

Все это требуетнемного общей инфраструктуры (а именно, новый класс атрибутов и метод расширения), применение атрибута DependentOn к зависимым атрибутам и вызов события PropertyChanged с помощью метода расширения Notify.

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

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...