Шаблон для привязки команд к дочерней ViewModel из родительского меню

Я создаю приложение WPF MVVM с помощью Caliburn Micro. У меня есть набор кнопок в меню (лента), которые находятся в представлении для моей модели представления оболочки, которая является ScreenConductor. Основываясь на текущей активной модели просмотра экрана, я хотел бы, чтобы кнопки ленты были отключены/включены, если они доступны для использования с активным экраном, и вызывать действия или команды на активном экране.

Это похоже на обычный сценарий. Существует ли шаблон для создания такого поведения?


person grimus    schedule 06.03.2013    source источник
comment
привяжите свои кнопки к команде реле внутри вашей модели представления, которая устанавливает canexecute на основе текущего сценария.   -  person d.moncada    schedule 07.03.2013
comment
Это создало бы эффект, но я надеялся на что-то более динамичное и менее грубое. Возможно, модель представления экрана может определять, какие команды она поддерживает, поэтому дополнительные экраны могут быть добавлены без явного связывания для каждой кнопки и экрана в модели представления ScreenConductor.   -  person grimus    schedule 07.03.2013


Ответы (5)


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

person Ibrahim Najjar    schedule 10.03.2013

РЕДАКТИРОВАТЬ: просто снова смотрю на ваш вопрос, и я думаю, что это намного проще, чем кажется.

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

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

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

<Button cal:Message.Attach="Command1" cal:Action.TargetWithoutContext="{Binding ActiveItem}" />

Затем просто реализуйте метод в своей дочерней виртуальной машине, как обычно.

public void Command1() { }

и опционально защита CanXX

public bool CanCommand1 
{ 
    get 
    { 
        if(someCondition) return false; 

        return true; 
    } 
}

Предполагая, что вы не станете намного сложнее, чем это, это должно сработать для вас.

Я собираюсь быстро просмотреть исходный код CM и посмотреть, смогу ли я придумать что-то, что сработает для этого.

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

Хорошо, вы можете настроить функцию ActionMessage.ApplyAvailabilityEffect, чтобы получить желаемый эффект - в вашем методе bootstrapper.Configure() (или где-то при запуске) используйте:

        ActionMessage.ApplyAvailabilityEffect = context =>
        {
            var source = context.Source;

            if (ConventionManager.HasBinding(source, UIElement.IsEnabledProperty))
            {
                return source.IsEnabled;
            }

            if (context.CanExecute != null)
            {
                source.IsEnabled = context.CanExecute();
            }
            // Added these 3 lines to get the effect you want
            else if (context.Target == null)
            {
                source.IsEnabled = false;
            }
            // EDIT: Bugfix - need this to ensure the button is activated if it has a target but no guard
            else 
            {
                source.IsEnabled = true;
            }

            return source.IsEnabled;
        };

Кажется, это работает для меня - нет цели для методов, которые нельзя было бы связать с командой, поэтому в этом случае я просто установил IsEnabled в false. Это активирует кнопки только тогда, когда метод с соответствующей сигнатурой найден на активной дочерней виртуальной машине — очевидно, хорошо протестируйте его, прежде чем использовать :)

person Charleh    schedule 12.03.2013
comment
Хм, думаю, это не совсем правильно, мне нужно поставить защиту CanXX, чтобы она работала - еще раз посмотрю... - person Charleh; 12.03.2013
comment
Хорошо, я настроил, просто нужно убедиться, что кнопка не остается одна, когда цель найдена, но нет защиты CanXX (вместо этого предполагается, что вам нужны кнопки без защиты, но с обработчиком быть включенным) - person Charleh; 12.03.2013

Создайте методы и сопутствующие логические свойства для каждой из ваших команд в вашей модели представления оболочки. (См. код ниже для примера.) Соглашения Caliburn.Micro автоматически подключат их к кнопкам. Затем просто создайте события изменения свойств для логических свойств при изменении представлений, чтобы они были переоценены.

Например, предположим, что у вас есть кнопка «Сохранить». Имя этой кнопки в вашем xaml будет Сохранить, и в вашей модели представления у вас будет метод Сохранить вместе с логическим свойством CanSave. Увидеть ниже:

public void Save()
{
    var viewModelWithSave = ActiveItem as ISave;
    if (viewModelWithSave != null) viewModelWithSave.Save();
}

public bool CanSave { get { return ActivateItem is ISave; } }

Затем в вашем проводнике всякий раз, когда вы меняете активный экран, вы будете вызывать NotifyOfPropertyChange(() => CanSave);. Это приведет к тому, что ваша кнопка будет отключена или включена в зависимости от того, способен ли активный экран обрабатывать эту команду. В этом примере, если активный экран не реализует ISave, кнопка Сохранить будет отключена.

person Brandon Baker    schedule 07.03.2013

Я бы использовал агрегацию событий Caliburn.Micro в этом сценарии следующим образом:

  • Создайте класс с именем ScreenCapabilities с набором логических атрибутов (например, CanSave, CanLoad и т. д.)
  • Создайте сообщение с именем ScreenActivatedMessage со свойством типа ScreenCapabilities
  • Создайте модель представления для своей ленты, которая подписывается (обрабатывает) ScreenActivatedMessage

В методе Handle модели представления ленты задайте локальные свойства CanXXX на основе предоставленного ScreenCapabilities.

Это будет выглядеть примерно так (код набран вручную, не тестировался):

public class ScreenCapabilities
{
  public bool CanSave { get; set; }
  // ...
}

public class ScreenActivatedMessage
{
  public ScreenCapabilities ScreenCapabilities { get; set; }
  // ...
}

public class RibbonViewModel : PropertyChangedBase, IHandle<ScreenActivatedMessage>
{
  private bool _canSave;
  public bool CanSave
  {
    get { return _canSave; }
    set { _canSave = value; NotifyPropertyChanged(() => CanSave); }
  }

  // ...

  public void Handle(ScreenActivatedMessage message)
  {
    CanSave = message.ScreenCapabilities.CanSave;
    // ...
  }
}

Затем, в подходящем месте, когда экран изменится, опубликуйте сообщение. См. см. Caliburn.Micro wiki. больше информации.

person Michael Teper    schedule 07.03.2013

Определите свойство (скажем, ActiveScreen) для активного экрана в модели представления оболочки. И давайте предположим, что у вас есть свойства для каждой кнопки, такие как DeleteButton, AddButton. Экран — это модель просмотра для экранов.

    private Screen activeScreen;

    public Screen ActiveScreen
    {
        get
        {
            return activeScreen;
        }
        set
        {
            activeScreen= value;

            if (activeScreen.Name.equals("Screen1"))
            {
                 this.AddButton.IsEnabled = true; 
                 this.DeleteButton.IsEnabled = false; 
            }
            if else (activeScreen.Name.equals("Screen2"))
            {
                 this.AddButton.IsEnabled = true; 
                 this.DeleteButton.IsEnabled = true; 
            }

            NotifyPropertyChanged("ActiveScreen");
        }
    }
person Haritha    schedule 07.03.2013