Обработка диалогов в WPF с помощью MVVM

235

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

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

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

public event EventHandler<MyDeleteArgs> RequiresDeleteDialog;

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

Рэй Бойсен
источник
Почему бы не выполнить привязку к вспомогательному объекту в представлении?
Пол Уильямс
1
Не уверен, что вы имеете в виду.
Рэй Бойзен
1
Если я понимаю вопрос, вы не хотите, чтобы виртуальные машины выскакивали в диалогах, и вы не хотите, чтобы код был в представлении. Кроме того, звучит так, как будто вы предпочитаете команды событиям. Я согласен со всем этим, поэтому я использую вспомогательный класс в View, который предоставляет команду для обработки диалога. Я ответил на этот вопрос в другой ветке здесь: stackoverflow.com/a/23303267/420400 . Тем не менее, последнее предложение звучит так , как вы не хотите какой - либо код на всех, в любом месте в представлении. Я понимаю эту проблему, но рассматриваемый код является лишь условным и вряд ли изменится.
Пол Уильямс
4
Модель представления всегда должна отвечать за логику создания диалогового окна, и в этом вся причина его существования. Это сказало, что это не (и не должно) делать тяжелую работу по созданию самого представления. Я написал статью на эту тему на codeproject.com/Articles/820324/…, где я показываю, что весь жизненный цикл диалоговых окон может управляться с помощью обычной привязки данных WPF и без нарушения шаблона MVVM.
Марк Фельдман

Ответы:

131

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

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

<BooleanToVisibilityConverter x:Key="booltoVis" />

как в:

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

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

Wpf диалог

Джеффри Найт
источник
20
Да, мне также нравится эта идея, но я хотел бы увидеть некоторые примеры этого элемента управления с точки зрения того, как показать его, извлечь из него результат диалога и т. Д. Особенно в сценарии MVVM в Silverlight.
Робоблоб
16
Как вы препятствуете взаимодействию пользователя с элементами управления под этим наложением диалога?
Эндрю Гаррисон
17
Проблема с этим подходом состоит в том, что вы не можете открыть второй модальный диалог из первого, по крайней мере, без каких-либо серьезных модификаций в системе наложения ...
Томас Левеск
6
Другая проблема с этим подходом состоит в том, что «диалог» не может быть перемещен. В наших приложениях у нас должен быть подвижный диалог, чтобы пользователь мог видеть, что стоит за ним.
JAB
13
Этот подход кажется мне ужасным. Чего мне не хватает? Как это лучше, чем настоящее диалоговое окно?
Джонатан Вуд
51

Вы должны использовать посредника для этого. Посредник - это распространенный шаблон проектирования, также известный как 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

Roubachof
источник
2
Марлон Греч только что опубликовал новую реализацию посредника: marlongrech.wordpress.com/2009/04/16/…
Roubachof
21
Просто замечание: шаблон Mediator не был представлен WPF Disciples, это классический шаблон GoF ... ( dofactory.com/Patterns/PatternMediator.aspx ). Хороший ответ иначе;)
Томас Левеск
10
Пожалуйста, Боже, не используйте посредника или проклятого посланника. Такого рода код с десятками летающих сообщений становится очень трудным для отладки, если вы не можете как-то запомнить все многочисленные точки в вашей кодовой базе, которые подписываются и обрабатывают каждое событие. Это становится кошмаром для новых разработчиков. На самом деле, я считаю, что вся библиотека MvvMLight является мощным анти-паттерном для его повсеместного и ненужного использования асинхронного обмена сообщениями. Решение простое: вызовите отдельную диалоговую службу (например, IDialogService) вашего проекта. Интерфейс имеет методы и события для обратных вызовов.
Крис Бордеман
34

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

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

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

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

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

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

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

user92541
источник
2
Это действительно интересный подход к проблеме отображения диалоговых окон в MVVM.
dthrasher
2
"Showing a dialog requires a code-behind"ммм, вы можете назвать это во ViewModel
Брок Хенсли
Я бы добавил пункт 3 - вы можете привязывать другие объекты в представлении. Оставление кода диалога пустым означает, что нигде в представлении нет кода C #, а привязка данных не подразумевает привязку к ВМ.
Пол Уильямс
25

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

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

var result = this.uiDialogService.ShowDialog("Dialogwindow title goes here", dialogwindowVM);
blindmeis
источник
из какой библиотеки поступает uiDialogService?
aggietech
1
нет библиотеки это просто небольшой интерфейс и реализация: stackoverflow.com/questions/3801681/… . чтобы быть справедливым, у него есть еще несколько перегрузок для моих нужд :) (высота, ширина,
настройки
16

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

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

Roboblob
источник
Как и в подходе @Elad Katz, в вашем ответе отсутствует связанный контент - пожалуйста, улучшите свой ответ, вставив его, так как это то, что считается хорошим ответом здесь, на SO. Тем не менее, спасибо за ваш вклад! :)
Йода
6

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

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

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

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

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

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

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

Майк Роули
источник
6

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

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

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

interface IDialogService { Task ShowDialogAsync(DialogViewModel dlgVm); }

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

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

ВИД ПОМОЩЬ

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

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

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

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

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

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

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

Используйте команду 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();
    }
}
Maxm007
источник
Этот код требует некоторой работы, но на данный момент это лучшая идея, особенно для системных диалогов, таких как диалоги файлов или принтеров. Диалоги принадлежат View, если что-то делает. Для файловых диалогов результат (выбранное имя файла) может быть передан внутренней команде в качестве параметра.
Антон Тихий
3

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

Если вы измените взаимодействие ViewModel - View для обработки диалогов, тогда ViewModel зависит от этой реализации. Самый простой способ справиться с этой проблемой - сделать View ответственным за выполнение задачи. Если это означает показ диалога, тогда все в порядке, но также может быть сообщение о состоянии в строке состояния и т. Д.

Моя точка зрения заключается в том, что весь смысл шаблона MVVM состоит в том, чтобы отделить бизнес-логику от GUI, поэтому не следует смешивать логику GUI (для отображения диалога) на бизнес-уровне (ViewModel).

Кэмерон МакФарланд
источник
2
Виртуальная машина никогда не будет обрабатывать диалог, в моем примере это будет просто событие, которое потребует запуска диалога и передачи информации в некоторой форме EventArgs. Если представление отвечает, как оно передает информацию обратно в виртуальную машину?
Рэй Бойсен
Скажем, виртуальная машина должна что-то удалить. ВМ вызывает метод на View Delete, который возвращает логическое значение. Представление может затем либо удалить элемент напрямую и вернуть значение true, либо показать диалоговое окно подтверждения и вернуть значение true / false в зависимости от ответа пользователя.
Кэмерон Макфарланд
Виртуальная машина ничего не знает о диалоге, а только просит представление удалить что-либо, что представление либо подтвердило, либо отклонило.
Кэмерон Макфарланд
Я всегда думал, что целью MVVM является Model: бизнес-логика, ViewModel: логика GUI и View: нет логики. Что как-то противоречит вашему последнему абзацу. Пожалуйста, объясни!
Дэвид Шмитт
2
Сначала необходимо определить, является ли запрос на подтверждение перед удалением бизнес-логикой или логикой просмотра. Если это бизнес-логика, метод DeleteFile в модели не должен этого делать, а должен возвращать объект вопроса подтверждения. Это будет включать ссылку на делегата, который выполняет фактическое удаление. Если это не бизнес-логика, виртуальная машина должна построить виртуальную машину вопроса в DeleteFileCommand с двумя членами ICommand. Один за да и один за нет. Вероятно, существуют аргументы для обоих представлений, и в RL большинство пользователей, вероятно, встретятся с обоими.
Guge
3

Интересной альтернативой является использование контроллеров, которые отвечают за отображение представлений (диалогов).

Как это работает, показано в WPF Application Framework (WAF) .

JBE
источник
3

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

Эрик Гровер
источник
3

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

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

Элад Кац
источник
1
Вы должны включить полный код здесь, так как это то, что SO требует для хороших ответов. Тем не менее, связанный подход довольно опрятен, так что спасибо за это! :)
Йода
2
@yoda полный код довольно длинный, и поэтому я бы предпочел ссылку на него. Я отредактировал свой ответ, чтобы отразить изменения и указать на ссылку, которая не сломана
Elad Katz
Спасибо за улучшение. Тем не менее, здесь, на SO, лучше предоставить полностраничную прокрутку кода 3, а не ссылку, которая когда-нибудь может быть отключена. Хорошие статьи по сложным темам всегда довольно длинные - и я не вижу никакой выгоды в открытии новой вкладки, переключении на нее и прокрутке там по прокрутке на той же странице / вкладке, на которой я был до этого. ;)
Йода
@EladKatz Я видел, что вы поделились частью своей реализации WPF по предоставленной вами ссылке. Есть ли у вас решение для открытия нового окна из ViewModel? В основном у меня есть две формы, и у каждой есть одна ViewModel. Один пользователь нажимает кнопку, появляется другая форма, и viewmodel1 отправляет свой объект в viewmodel2. В форме 2 пользователь может изменить объект, и когда он закроет окно, обновленный объект будет отправлен обратно в первую ViewModel. Есть ли у вас решение для этого?
Эсан
2

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

В зависимости от события / сценария, он также может иметь триггер события, который подписывается на просмотр событий модели, и одно или несколько действий для вызова в ответ.

Нихил Котари
источник
2

У меня была такая же ситуация и я обернул MessageBox в невидимый элемент управления дизайнера. Подробности в моем блоге

http://geekswithblogs.net/mukapu/archive/2010/03/12/user-prompts-messagebox-with-mvvm.aspx

То же самое можно распространить на любые модальные диалоги, управление просмотром файлов и т. Д.

mukapu
источник
1

Карл Шиффлетт создал образец приложения для отображения диалоговых окон, используя сервисный подход и подход Prism InteractionRequest.

Мне нравится сервисный подход - он менее гибок, поэтому у пользователей меньше шансов что-то сломать :) Это также согласуется с частью моего приложения в WinForms (MessageBox.Show). Но если вы планируете показывать много разных диалогов, тогда InteractionRequest является лучший способ пойти.

http://karlshifflett.wordpress.com/2010/11/07/in-the-box-ndash-mvvm-training/

сурфен
источник
1

Я знаю, что это старый вопрос, но когда я выполнил этот поиск, я нашел много связанных вопросов, но я не нашел действительно четкого ответа. Поэтому я делаю свою собственную реализацию диалогового окна / сообщения / попина и делюсь этим!
Я думаю, что это «доказательство MVVM», и я пытаюсь сделать его простым и корректным, но я новичок в WPF, поэтому не стесняйтесь комментировать или даже делать запрос на извлечение.

https://github.com/Plasma-Paris/Plasma.WpfUtils

Вы можете использовать это так:

public RelayCommand YesNoMessageBoxCommand { get; private set; }
async void YesNoMessageBox()
{
    var result = await _Service.ShowMessage("This is the content of the message box", "This is the title", System.Windows.MessageBoxButton.YesNo);
    if (result == System.Windows.MessageBoxResult.Yes)
        // [...]
}

Или как это, если вы хотите более сложный попин:

var result = await _Service.ShowCustomMessageBox(new MyMessageBoxViewModel { /* What you want */ });

И это показывает такие вещи:

2

Xav987
источник
1

Стандартный подход

Потратив годы на решение этой проблемы в WPF, я наконец нашел стандартный способ реализации диалогов в WPF. Вот преимущества этого подхода:

  1. ЧИСТАЯ
  2. Не нарушает шаблон проектирования MVVM
  3. ViewModal никогда не ссылается ни на одну из библиотек пользовательского интерфейса (WindowBase, PresentationFramework и т. Д.)
  4. Идеально подходит для автоматического тестирования
  5. Диалоги можно легко заменить.

Так в чем же ключ. Это DI + IoC .

Вот как это работает. Я использую MVVM Light, но этот подход может быть распространен и на другие фреймворки:

  1. Добавьте проект приложения WPF в свое решение. Называть это приложение .
  2. Добавьте библиотеку классов ViewModal. Назовите это VM .
  3. Приложение ссылается на проект VM. Проект VM ничего не знает о App.
  4. Добавьте ссылку NuGet на MVVM Light для обоих проектов . Я использую MVVM Light Standard эти дни, но с полной версией Framework у вас тоже все в порядке.
  5. Добавьте интерфейс IDialogService в проект VM:

    public interface IDialogService
    {
      void ShowMessage(string msg, bool isError);
      bool AskBooleanQuestion(string msg);
      string AskStringQuestion(string msg, string default_value);
    
      string ShowOpen(string filter, string initDir = "", string title = "");
      string ShowSave(string filter, string initDir = "", string title = "", string fileName = "");
      string ShowFolder(string initDir = "");
    
      bool ShowSettings();
    }
  6. Предоставьте открытое статическое свойство IDialogServiceтипа в вашем ViewModelLocator, но оставьте часть регистрации для слоя View для выполнения. Это ключ .

    public static IDialogService DialogService => SimpleIoc.Default.GetInstance<IDialogService>();
  7. Добавьте реализацию этого интерфейса в проект приложения.

    public class DialogPresenter : IDialogService
    {
        private static OpenFileDialog dlgOpen = new OpenFileDialog();
        private static SaveFileDialog dlgSave = new SaveFileDialog();
        private static FolderBrowserDialog dlgFolder = new FolderBrowserDialog();
    
        /// <summary>
        /// Displays a simple Information or Error message to the user.
        /// </summary>
        /// <param name="msg">String text that is to be displayed in the MessageBox</param>
        /// <param name="isError">If true, Error icon is displayed. If false, Information icon is displayed.</param>
        public void ShowMessage(string msg, bool isError)
        {
                if(isError)
                        System.Windows.MessageBox.Show(msg, "Your Project Title", MessageBoxButton.OK, MessageBoxImage.Error);
                else
                        System.Windows.MessageBox.Show(msg, "Your Project Title", MessageBoxButton.OK, MessageBoxImage.Information);
        }
    
        /// <summary>
        /// Displays a Yes/No MessageBox.Returns true if user clicks Yes, otherwise false.
        /// </summary>
        /// <param name="msg"></param>
        /// <returns></returns>
        public bool AskBooleanQuestion(string msg)
        {
                var Result = System.Windows.MessageBox.Show(msg, "Your Project Title", MessageBoxButton.YesNo, MessageBoxImage.Question) == MessageBoxResult.Yes;
                return Result;
        }
    
        /// <summary>
        /// Displays Save dialog. User can specify file filter, initial directory and dialog title. Returns full path of the selected file if
        /// user clicks Save button. Returns null if user clicks Cancel button.
        /// </summary>
        /// <param name="filter"></param>
        /// <param name="initDir"></param>
        /// <param name="title"></param>
        /// <param name="fileName"></param>
        /// <returns></returns>
        public string ShowSave(string filter, string initDir = "", string title = "", string fileName = "")
        {
                if (!string.IsNullOrEmpty(title))
                        dlgSave.Title = title;
                else
                        dlgSave.Title = "Save";
    
                if (!string.IsNullOrEmpty(fileName))
                        dlgSave.FileName = fileName;
                else
                        dlgSave.FileName = "";
    
                dlgSave.Filter = filter;
                if (!string.IsNullOrEmpty(initDir))
                        dlgSave.InitialDirectory = initDir;
    
                if (dlgSave.ShowDialog() == DialogResult.OK)
                        return dlgSave.FileName;
                else
                        return null;
        }
    
    
        public string ShowFolder(string initDir = "")
        {
                if (!string.IsNullOrEmpty(initDir))
                        dlgFolder.SelectedPath = initDir;
    
                if (dlgFolder.ShowDialog() == DialogResult.OK)
                        return dlgFolder.SelectedPath;
                else
                        return null;
        }
    
    
        /// <summary>
        /// Displays Open dialog. User can specify file filter, initial directory and dialog title. Returns full path of the selected file if
        /// user clicks Open button. Returns null if user clicks Cancel button.
        /// </summary>
        /// <param name="filter"></param>
        /// <param name="initDir"></param>
        /// <param name="title"></param>
        /// <returns></returns>
        public string ShowOpen(string filter, string initDir = "", string title = "")
        {
                if (!string.IsNullOrEmpty(title))
                        dlgOpen.Title = title;
                else
                        dlgOpen.Title = "Open";
    
                dlgOpen.Multiselect = false;
                dlgOpen.Filter = filter;
                if (!string.IsNullOrEmpty(initDir))
                        dlgOpen.InitialDirectory = initDir;
    
                if (dlgOpen.ShowDialog() == DialogResult.OK)
                        return dlgOpen.FileName;
                else
                        return null;
        }
    
        /// <summary>
        /// Shows Settings dialog.
        /// </summary>
        /// <returns>true if User clicks OK button, otherwise false.</returns>
        public bool ShowSettings()
        {
                var w = new SettingsWindow();
                MakeChild(w); //Show this dialog as child of Microsoft Word window.
                var Result = w.ShowDialog().Value;
                return Result;
        }
    
        /// <summary>
        /// Prompts user for a single value input. First parameter specifies the message to be displayed in the dialog 
        /// and the second string specifies the default value to be displayed in the input box.
        /// </summary>
        /// <param name="m"></param>
        public string AskStringQuestion(string msg, string default_value)
        {
                string Result = null;
    
                InputBox w = new InputBox();
                MakeChild(w);
                if (w.ShowDialog(msg, default_value).Value)
                        Result = w.Value;
    
                return Result;
        }
    
        /// <summary>
        /// Sets Word window as parent of the specified window.
        /// </summary>
        /// <param name="w"></param>
        private static void MakeChild(System.Windows.Window w)
        {
                IntPtr HWND = Process.GetCurrentProcess().MainWindowHandle;
                var helper = new WindowInteropHelper(w) { Owner = HWND };
        }
    }
  8. Хотя некоторые из этих функций являются общими ( ShowMessageи AskBooleanQuestionт. Д.), Другие специфичны для этого проекта и используют пользовательские функции Window. Вы можете добавить больше пользовательских окон таким же образом. Ключ заключается в том, чтобы сохранить специфичные для пользовательского интерфейса элементы в слое View и просто представить возвращенные данные с помощью POCO на уровне VM .
  9. Выполните IoC-регистрацию вашего интерфейса в слое View, используя этот класс. Вы можете сделать это в конструкторе вашего основного вида (после InitializeComponent()вызова):

    SimpleIoc.Default.Register<IDialogService, DialogPresenter>();
  10. Вот и ты. Теперь у вас есть доступ ко всем функциям диалога на уровнях VM и View. Ваш уровень VM может вызывать эти функции следующим образом:

    var NoTrump = ViewModelLocator.DialogService.AskBooleanQuestion("Really stop the trade war???", "");
  11. Так чисто вы видите. Уровень VM ничего не знает о том, как вопрос «Да / Нет» будет представлен пользователю уровнем UI, и все еще может успешно работать с возвращенным результатом из диалога.

Другие бесплатные льготы

  1. Для написания модульного теста вы можете предоставить пользовательскую реализацию IDialogServiceв вашем тестовом проекте и зарегистрировать этот класс в IoC в конструкторе вашего тестового класса.
  2. Вам нужно будет импортировать некоторые пространства имен, такие как Microsoft.Win32доступ к диалогам открытия и сохранения. Я не учел их, потому что есть также версия этих диалоговых окон для WinForms, плюс кто-то может захотеть создать свою собственную версию. Также обратите внимание, что некоторые из используемых идентификаторов DialogPresenterявляются именами моих собственных окон (например SettingsWindow). Вам нужно будет либо удалить их как из интерфейса, так и из реализации или предоставить свои собственные окна.
  3. Если ваша виртуальная машина выполняет многопоточность, вызовите MVVM Light на DispatcherHelper.Initialize()раннем этапе жизненного цикла вашего приложения.
  4. Кроме того, DialogPresenterкоторый внедряется в слой View, должны быть зарегистрированы другие ViewModals, ViewModelLocatorа затем должно быть открыто публичное статическое свойство этого типа для использования слоем View. Что-то вроде этого:

    public static SettingsVM Settings => SimpleIoc.Default.GetInstance<SettingsVM>();
  5. По большей части ваши диалоги не должны иметь никакого кода для таких вещей, как привязка или установка DataContext и т. Д. Вы даже не должны передавать вещи в качестве параметров конструктора. XAML может сделать все это для вас, вот так:

    <Window x:Class="YourViewNamespace.SettingsWindow"
      xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
      xmlns:local="clr-namespace:YourViewProject"
      xmlns:vm="clr-namespace:YourVMProject;assembly=YourVMProject"
      DataContext="{x:Static vm:ViewModelLocator.Settings}"
      d:DataContext="{d:DesignInstance Type=vm:SettingsVM}" />
  6. Установка DataContextэтого способа дает вам все виды преимуществ времени разработки, таких как Intellisense и автозаполнение.

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

Dotnet
источник
0

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

Мое текущее решение выглядит так:

public class SelectionTaskModel<TChoosable> : ViewModel
    where TChoosable : ViewModel
{
    public SelectionTaskModel(ICollection<TChoosable> choices);
    public ReadOnlyCollection<TChoosable> Choices { get; }
    public void Choose(TChoosable choosen);
    public void Abort();
}

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

Дэвид Шмитт
источник
0

Я боролся с той же проблемой. Я придумал способ связи между View и ViewModel. Вы можете инициировать отправку сообщения из ViewModel в View, чтобы показать ему окно сообщения, и оно сообщит результат. Затем ViewModel может ответить на результат, возвращенный из View.

Я демонстрирую это в своем блоге :

Дэн возится с камином
источник
0

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

https://www.codeproject.com/Articles/820324/Implementing-Dialog-Boxes-in-MVVM

Марк Фельдман
источник
0

Извините, но я должен вмешаться. Я прошел через несколько предложенных решений, прежде чем нашел пространство имен Prism.Wpf.Interactivity в проекте Prism. Вы можете использовать запросы взаимодействия и действие всплывающего окна, чтобы либо свернуть пользовательское окно, либо для более простых нужд есть встроенные всплывающие окна уведомлений и подтверждений. Они создают настоящие окна и управляются как таковые. Вы можете передать объект контекста с любыми зависимостями, которые вам нужны в диалоге. Мы используем это решение на моей работе, так как я нашел его. У нас здесь много старших разработчиков, и никто не придумал ничего лучшего. Нашим предыдущим решением было использование диалогового сервиса в виде оверлея и использование класса презентатора, чтобы это произошло, но вы должны были иметь фабрики для всех моделей представления диалога и т. Д.

Это не тривиально, но и не очень сложно. И он встроен в Призму и поэтому является лучшей (или лучшей) практикой ИМХО.

Мои 2 цента!

Jogi
источник
-1

РЕДАКТИРОВАТЬ: да, я согласен, что это не правильный подход MVVM, и сейчас я использую что-то похожее на то, что предлагает вслепую.

Один из способов, которым вы могли бы это

В вашей модели основного вида (где вы открываете модальный):

void OpenModal()
{
    ModalWindowViewModel mwvm = new ModalWindowViewModel();
    Window mw = new Window();
    mw.content = mwvm;
    mw.ShowDialog()
    if(mw.DialogResult == true)
    { 
        // Your Code, you can access property in mwvm if you need.
    }
}

И в вашем модальном окне View / ViewModel:

XAML:

<Button Name="okButton" Command="{Binding OkCommand}" CommandParameter="{Binding RelativeSource={RelativeSource FindAncestor, AncestorType={x:Type Window}}}">OK</Button>
<Button Margin="2" VerticalAlignment="Center" Name="cancelButton" IsCancel="True">Cancel</Button>

ViewModel:

public ICommand OkCommand
{
    get
    {
        if (_okCommand == null)
        {
            _okCommand = new ActionCommand<Window>(DoOk, CanDoOk);
        }
        return _okCommand ;
    }
}

void DoOk(Window win)
{
    <!--Your Code-->
    win.DialogResult = true;
    win.Close();
}

bool CanDoOk(Window win) { return true; }

или аналогично тому, что выложено здесь WPF MVVM: как закрыть окно

Симона
источник
2
Я не был отрицательным голосом, но подозреваю, что это потому, что модель представления имеет прямую ссылку на представление.
Брайан Гидеон
@BrianGideon, спасибо за ваш комментарий. Я согласен, что это не несвязанное решение. На самом деле, я не использую что-то похожее на whar, предложенное вслепую. Еще раз спасибо.
Симона
Плохо добираться до вида, когда так просто не сделать.
Крис Бордеман