Обработка диалогов в WPF с помощью MVVM - PullRequest
226 голосов
/ 18 января 2009

В шаблоне MVVM для WPF обработка диалогов является одной из более сложных операций. Так как ваша модель представления ничего не знает о представлении, диалоговое общение может быть интересным. Я могу выставить ICommand, чтобы, когда представление вызывает его, мог появиться диалог.

Кто-нибудь знает хороший способ обработки результатов из диалогов? Я говорю о диалоговых окнах, таких как MessageBox.

Одним из способов, которым мы это сделали, было событие в модели представления, на которое представление подписывалось, когда требовался диалог.

public event EventHandler<MyDeleteArgs> RequiresDeleteDialog;

Это нормально, но это означает, что для представления требуется код, от которого я бы хотел остаться в стороне.

Ответы [ 23 ]

130 голосов
/ 08 мая 2009

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

Это очень полезно:

<BooleanToVisibilityConverter x:Key="booltoVis" />

как в:

<my:ErrorControl Visibility="{Binding Path=ThereWasAnError, Mode=TwoWay, Converter={StaticResource booltoVis}, UpdateSourceTrigger=PropertyChanged}"/>

Вот как я реализовал один пользовательский элемент управления. Нажатие на «x» закрывает элемент управления в строке кода в коде пользовательского элемента управления. (Поскольку у меня есть Views в .exe и ViewModels в dll, я не чувствую себя плохо из-за кода, который манипулирует UI.)

Диалог Wpf

51 голосов
/ 17 апреля 2009

Вы должны использовать посредника для этого. Посредник - это распространенный шаблон проектирования, также известный как Messenger в некоторых его реализациях. Это парадигма типа Register / Notify, которая позволяет вашим ViewModel и Views взаимодействовать через механизм обмена сообщениями с низкой связью.

Вы должны проверить группу учеников Google WPF и просто найти посредника. Вы будете очень довольны ответами ...

Однако вы можете начать с этого:

http://joshsmithonwpf.wordpress.com/2009/04/06/a-mediator-prototype-for-wpf-apps/

Наслаждайтесь!

Редактировать: вы можете увидеть ответ на эту проблему с помощью MVVM Light Toolkit здесь:

http://mvvmlight.codeplex.com/Thread/View.aspx?ThreadId=209338

29 голосов
/ 18 апреля 2009

Хороший диалог MVVM должен:

  1. Быть объявленным только с XAML.
  2. Получите все его поведение из привязки данных.

К сожалению, WPF не предоставляет эти функции. Для отображения диалога требуется вызов кода для ShowDialog (). Класс Window, который поддерживает диалоги, не может быть объявлен в XAML, поэтому его нельзя легко связать с DataContext.

Чтобы решить эту проблему, я написал элемент-заглушку XAML, который находится в логическом дереве и ретранслирует привязку данных к окну и обрабатывает отображение и скрытие диалога. Вы можете найти его здесь: http://www.codeproject.com/KB/WPF/XAMLDialog.aspx

Он действительно прост в использовании и не требует каких-либо странных изменений в вашей ViewModel и не требует событий или сообщений. Основной вызов выглядит так:

<dialog:Dialog Content="{Binding Path=DialogViewModel}" Showing="True" />

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

23 голосов
/ 10 февраля 2011

Я использую этот подход для диалогов с MVVM.

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

var result = this.uiDialogService.ShowDialog("Dialogwindow title goes here", dialogwindowVM);
16 голосов
/ 22 января 2010

Мое текущее решение решает большинство упомянутых вами проблем, но оно полностью абстрагировано от платформенных вещей и может быть использовано повторно. Кроме того, я не использовал связывание кода только с DelegateCommands, которые реализуют ICommand. Диалог - это, по сути, View - отдельный элемент управления, имеющий собственный ViewModel, который отображается из ViewModel основного экрана, но запускается из пользовательского интерфейса через привязку DelagateCommand.

См. Полное решение Silverlight 4 здесь Модальные диалоги с MVVM и Silverlight 4

6 голосов
/ 04 марта 2010

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

Моя первоначальная мысль заключалась в том, что ViewModel нельзя разрешать вызывать диалоговое окно напрямую, так как он не имеет никакого дела, решая, как должен отображаться диалог. Из-за этого я начал думать о том, как я мог бы передавать сообщения так же, как и в MVP (т.е. View.ShowSaveFileDialog ()). Однако я думаю, что это неправильный подход.

Это нормально для ViewModel для непосредственного вызова диалога. Тем не менее, когда вы тестируете ViewModel, это означает, что диалоговое окно будет либо всплывать во время вашего теста, либо потерпеть неудачу все вместе (на самом деле никогда не пробовал это).

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

Вы уже должны использовать какой-либо Service Locator или IoC, который вы можете настроить для предоставления вам правильной версии в зависимости от контекста.

Используя этот подход, ваша ViewModel все еще тестируема, и в зависимости от того, как вы макетируете свои диалоги, вы можете контролировать поведение.

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

5 голосов
/ 21 марта 2015

Есть два хороших способа сделать это: 1) сервис диалога (простой, чистый) и 2) вспомогательный просмотр. View Assisted предоставляет некоторые полезные функции, но, как правило, не стоит.

ДИАЛОГ СЕРВИС

a) интерфейс службы диалога, например, через конструктор или некоторый контейнер зависимостей:

interface IDialogService { Task ShowDialogAsync(DialogViewModel dlgVm); }

b) Ваша реализация IDialogService должна открыть окно (или внедрить некоторый элемент управления в активное окно), создать представление, соответствующее имени данного типа dlgVm (использовать регистрацию или соглашение контейнера или ContentPresenter с типом DataTemplates, связанным с типом) , ShowDialogAsync должен создать TaskCompletionSource и вернуть его .Task proptery. Класс DialogViewModel сам нуждается в событии, которое вы можете вызвать в производном классе, когда хотите закрыть, и посмотреть в диалоговом окне, чтобы фактически закрыть / скрыть диалоговое окно и завершить TaskCompletionSource.

b) Для использования просто вызовите await this.DialogService.ShowDialog (myDlgVm) в вашем экземпляре некоторого производного от DialogViewModel класса. После возвращения await посмотрите на свойства, которые вы добавили в диалоговую виртуальную машину, чтобы определить, что произошло; вам даже не нужен обратный звонок.

ПОМОЩЬ ВИДА

Это ваш взгляд на прослушивание события в модели представления. Все это может быть заключено в Blend Behavior, чтобы избежать выделения кода и использования ресурсов, если вы так склонны (FMI, подкласс класса «Behavior», чтобы увидеть своего рода Blendable присоединенное свойство на стероидах). Сейчас мы сделаем это вручную для каждого представления:

a) Создайте OpenXXXXXDialogEvent с пользовательской полезной нагрузкой (производный класс DialogViewModel).

б) Пусть представление подписывается на событие в своем событии OnDataContextChanged. Не забудьте скрыть и отписаться, если старое значение! = Null и в событии Unloaded окна.

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

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

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

4 голосов
/ 17 ноября 2011

Используйте команду freezable

<Grid>
        <Grid.DataContext>
            <WpfApplication1:ViewModel />
        </Grid.DataContext>


        <Button Content="Text">
            <Button.Command>
                <WpfApplication1:MessageBoxCommand YesCommand="{Binding MyViewModelCommand}" />
            </Button.Command>
        </Button>

</Grid>
public class MessageBoxCommand : Freezable, ICommand
{
    public static readonly DependencyProperty YesCommandProperty = DependencyProperty.Register(
        "YesCommand",
        typeof (ICommand),
        typeof (MessageBoxCommand),
        new FrameworkPropertyMetadata(null)
        );


    public static readonly DependencyProperty OKCommandProperty = DependencyProperty.Register(
        "OKCommand",
        typeof (ICommand),
        typeof (MessageBoxCommand),
        new FrameworkPropertyMetadata(null)
        );


    public static readonly DependencyProperty CancelCommandProperty = DependencyProperty.Register(
        "CancelCommand",
        typeof (ICommand),
        typeof (MessageBoxCommand),
        new FrameworkPropertyMetadata(null)
        );


    public static readonly DependencyProperty NoCommandProperty = DependencyProperty.Register(
        "NoCommand",
        typeof (ICommand),
        typeof (MessageBoxCommand),
        new FrameworkPropertyMetadata(null)
        );


    public static readonly DependencyProperty MessageProperty = DependencyProperty.Register(
        "Message",
        typeof (string),
        typeof (MessageBoxCommand),
        new FrameworkPropertyMetadata("")
        );

    public static readonly DependencyProperty MessageBoxButtonsProperty = DependencyProperty.Register(
        "MessageBoxButtons",
        typeof(MessageBoxButton),
        typeof(MessageBoxCommand),
        new FrameworkPropertyMetadata(MessageBoxButton.OKCancel)
        );

    public ICommand YesCommand
    {
        get { return (ICommand) GetValue(YesCommandProperty); }
        set { SetValue(YesCommandProperty, value); }
    }

    public ICommand OKCommand
    {
        get { return (ICommand) GetValue(OKCommandProperty); }
        set { SetValue(OKCommandProperty, value); }
    }

    public ICommand CancelCommand
    {
        get { return (ICommand) GetValue(CancelCommandProperty); }
        set { SetValue(CancelCommandProperty, value); }
    }

    public ICommand NoCommand
    {
        get { return (ICommand) GetValue(NoCommandProperty); }
        set { SetValue(NoCommandProperty, value); }
    }

    public MessageBoxButton MessageBoxButtons
    {
        get { return (MessageBoxButton)GetValue(MessageBoxButtonsProperty); }
        set { SetValue(MessageBoxButtonsProperty, value); }
    }

    public string Message
    {
        get { return (string) GetValue(MessageProperty); }
        set { SetValue(MessageProperty, value); }
    }

    public void Execute(object parameter)
    {
        var messageBoxResult = MessageBox.Show(Message);
        switch (messageBoxResult)
        {
            case MessageBoxResult.OK:
                OKCommand.Execute(null);
                break;
            case MessageBoxResult.Yes:
                YesCommand.Execute(null);
                break;
            case MessageBoxResult.No:
                NoCommand.Execute(null);
                break;
            case MessageBoxResult.Cancel:
                if (CancelCommand != null) CancelCommand.Execute(null); //Cancel usually means do nothing ,so can be null
                break;

        }
    }

    public bool CanExecute(object parameter)
    {
        return true;
    }

    public event EventHandler CanExecuteChanged;


    protected override Freezable CreateInstanceCore()
    {
        throw new NotImplementedException();
    }
}
3 голосов
/ 06 февраля 2011

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

Как заставить WPF вести себя так, как будто MVVM поддерживается из коробки

3 голосов
/ 29 января 2011

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

...