Ошибка RoutedUICommand PreviewExecuted?

Я создаю приложение с помощью шаблона разработки MVVM, и я хочу использовать RoutedUICommands, определенный в классе ApplicationCommands. Так как свойством CommandBindings Представления (читает UserControl) не является DependencyProperty, мы не можем связать CommandBindings, определенный в ViewModel к Представлению непосредственно. Я решил это путем определения абстрактного класса Представления, который связывает это программно, на основе интерфейса ViewModel, который гарантирует, что каждый ViewModel имеет ObservableCollection CommandBindings. Это все хорошо работает, однако, в некоторых сценариях я хочу выполнить логику, которая определяется в различных классах (Представление и ViewModel) та же команда. Например, при сохранении документа.

В ViewModel код сохраняет документ на диск:

private void InitializeCommands()
{
    CommandBindings = new CommandBindingCollection();
    ExecutedRoutedEventHandler executeSave = (sender, e) =>
    {
        document.Save(path);
        IsModified = false;
    };
    CanExecuteRoutedEventHandler canSave = (sender, e) => 
    {
        e.CanExecute = IsModified;
    };
    CommandBinding save = new CommandBinding(ApplicationCommands.Save, executeSave, canSave);
    CommandBindings.Add(save);
}

На первый взгляд предыдущий код - все, что я хотел сделать, но TextBox в Представлении, с которым связывается документ, только обновляет свой Источник, когда он теряет свой фокус. Однако я могу сохранить документ, не теряя фокус путем нажатия Ctrl+S. Это означает, что документ сохраняется перед изменениями, где Обновлено в источнике, эффективно игнорируя изменения. Но начиная с изменения UpdateSourceTrigger к PropertyChanged не жизнеспособный вариант по причинам производительности, что-то еще должно вызвать обновление перед сохранением. Таким образом, я думал, позволяет, используют событие PreviewExecuted для принуждения обновления в событии PreviewExecuted, как так:

//Find the Save command and extend behavior if it is present
foreach (CommandBinding cb in CommandBindings)
{
    if (cb.Command.Equals(ApplicationCommands.Save))
    {
        cb.PreviewExecuted += (sender, e) =>
        {
            if (IsModified)
            {
                BindingExpression be = rtb.GetBindingExpression(TextBox.TextProperty);
                be.UpdateSource();
            }
            e.Handled = false;
        };
    }
}

Однако присвоение обработчика к событию PreviewExecuted, кажется, отменяет событие в целом, даже когда я явно установил свойство Handled на ложь. Таким образом, executeSave eventhandler, который я определил в предыдущем примере кода, больше не выполняется. Обратите внимание на это, когда я изменю cb. PreviewExecuted к cb. Выполняемый обе части кода выполняются, но не в правильном порядке.

Я думаю, что это - Ошибка в .NET, потому что необходимо смочь добавить обработчик к PreviewExecuted и Выполняемый и сделать, чтобы они были выполнены в порядке, если Вы не отмечаете событие, как обработано.

Кто-либо может подтвердить это поведение? Или я неправильно? Существует ли обходное решение для этой Ошибки?

5
задан Dave Clemmer 24 September 2011 в 18:11
поделиться

2 ответа

EDIT 2: Из просмотра исходного кода кажется, что внутренне это работает так:

  1. Элемент UIElement вызывает CommandManager.TranslateInput() в ответ на ввод пользователя (мышь или клавиатура).
  2. Затем CommandManager проходит через CommandBindings на разных уровнях в поисках команды, связанной с вводом.
  3. Когда команда найдена, вызывается ее метод CanExecute(), и если он возвращает true, вызывается Executed().
  4. В случае RoutedCommand каждый из методов делает по сути одно и то же - вызывает пару присоединенных событий CommandManager.PreviewCanExecuteEvent и CommandManager. CanExecuteEvent (или PreviewExecutedEvent и ExecutedEvent) на UIElement, который инициировал процесс. На этом первая фаза завершена.
  5. Теперь в UIElement зарегистрированы обработчики класса для этих четырех событий, и эти обработчики просто вызывают CommandManager.OnCanExecute() и CommandManager.CanExecute() (как для событий предварительного просмотра, так и для фактических событий).
  6. Только здесь в методах CommandManager.OnCanExecute() и CommandManager.OnExecute() вызываются обработчики, зарегистрированные в CommandBinding. Если таковых не найдено, CommandManager передает событие родителю UIElement, и начинается новый цикл, пока команда не будет обработана или не будет достигнут корень визуального дерева.

Если вы посмотрите на исходный код класса CommandBinding, там есть метод OnExecuted(), который отвечает за вызов обработчиков, зарегистрированных вами для событий PreviewExecuted и Executed через CommandBinding. Там есть такой бит:

PreviewExecuted(sender, e); 
e.Handled = true;

это устанавливает событие как обработанное сразу после возврата обработчика PreviewExecuted и поэтому Executed не вызывается.

EDIT 1: При рассмотрении событий CanExecute и PreviewCanExecute есть ключевое различие:

 PreviewCanExecute(sender, e); 
 if (e.CanExecute)
 { 
 e.Handled = true; 
 }

установка Handled в true здесь условна, и поэтому программист решает, продолжать или нет выполнение CanExecute. Просто не устанавливайте значение CanExecute для CanExecuteRoutedEventArgs в true в обработчике PreviewCanExecute, и обработчик CanExecute будет вызван.

Что касается свойства ContinueRouting события Preview - при установке значения false оно предотвращает дальнейшую маршрутизацию события Preview, но никак не влияет на следующее основное событие.

Обратите внимание, что это работает только в том случае, если обработчики зарегистрированы через CommandBinding.

Если вы все же хотите, чтобы выполнялись и PreviewExecuted, и Executed, у вас есть два варианта:

  1. Вы можете вызвать Execute() метод маршрутизируемой команды из обработчика PreviewExecuted. Просто подумав об этом, вы можете столкнуться с проблемами синхронизации, поскольку вы вызываете обработчик Executed до того, как PreviewExecuted завершен. На мой взгляд, это не очень хороший способ.
  2. Вы можете зарегистрировать обработчик PreviewExecuted отдельно через статический метод CommandManager.AddPreviewExecutedHandler(). Он будет вызываться непосредственно из класса UIElement и не будет задействовать CommandBinding. EDIT 2: Посмотрите на пункт 4 в начале поста - это события, для которых мы добавляем обработчики.

Судя по всему, так сделано специально. Почему? Можно только догадываться...

3
ответ дан 15 December 2019 в 00:59
поделиться

я строю следующую работу, чтобы получить недостающее поведение ContinueRouting:

foreach (CommandBinding cb in CommandBindings)
{
    if (cb.Command.Equals(ApplicationCommands.Save))
    {
        ExecutedRoutedEventHandler f = null;
        f = (sender, e) =>
        {
            if (IsModified)
            {
                BindingExpression be = rtb.GetBindingExpression(TextBox.TextProperty);
                be.UpdateSource();

                // There is a "Feature/Bug" in .Net which cancels the route when adding PreviewExecuted
                // So we remove the handler and call execute again
                cb.PreviewExecuted -= f;
                cb.Command.Execute(null);
            }
        };
        cb.PreviewExecuted += f;
    }
}
1
ответ дан 15 December 2019 в 00:59
поделиться
Другие вопросы по тегам:

Похожие вопросы: