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

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

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

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

public event EventHandler<MyDeleteArgs> RequiresDeleteDialog;

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


person Ray Booysen    schedule 18.01.2009    source источник
comment
Почему бы не привязаться к вспомогательному объекту в представлении?   -  person Paul Williams    schedule 27.04.2014
comment
Не уверен, что вы имеете в виду.   -  person Ray Booysen    schedule 09.05.2014
comment
Если я понимаю вопрос, вы не хотите, чтобы виртуальная машина открывала диалоговые окна, и вам не нужен код программной части в представлении. Кроме того, похоже, что вы предпочитаете команды событиям. Я согласен со всем этим, поэтому я использую вспомогательный класс в представлении, который предоставляет команду для обработки диалогового окна. Я ответил на этот вопрос в другой теме здесь: stackoverflow.com/a/23303267/420400. Однако последнее предложение звучит так, будто вам вообще не нужен какой-либо код, где-нибудь в представлении. Я понимаю эту озабоченность, но рассматриваемый код носит условный характер и вряд ли изменится.   -  person Paul Williams    schedule 09.06.2014
comment
Модель представления всегда должна отвечать за логику создания диалогового окна, и это в первую очередь причина его существования. Тем не менее, он не (и не должен) делать огромную работу по созданию самого представления. Я написал статью на эту тему на странице codeproject.com/Articles/ 820324 /, где я показываю, что всем жизненным циклом диалоговых окон можно управлять с помощью обычной привязки данных WPF без нарушения шаблона MVVM.   -  person Mark Feldman    schedule 02.02.2015


Ответы (23)


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

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

<BooleanToVisibilityConverter x:Key="booltoVis" />

as in:

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

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

Диалог Wpf

person Jeffrey Knight    schedule 08.05.2009
comment
Да, мне тоже нравится эта идея, но я хотел бы увидеть какой-нибудь пример этого элемента управления с точки зрения того, как его отображать, извлекать из него результат диалога и т. Д. Особенно в сценарии MVVM в Silverlight. - person Roboblob; 08.01.2010
comment
Как предотвратить взаимодействие пользователя с элементами управления под этим диалоговым окном? - person Andrew Garrison; 01.02.2010
comment
Отличная идея. Я создал Popup и контролировал видимость, привязав его к IsOpen. - person Zamboni; 23.05.2010
comment
Как предотвратить взаимодействие пользователя с элементами управления ...? : private void Window_PreviewKeyDown .... e.Handled = true; - person Jeffrey Knight; 23.05.2010
comment
Я не знаю, размещал ли Джеффри свое решение где-нибудь еще, но я попробовал это. Я создал свой собственный UserControl и реализовал его точно так же, как XAML Джеффри. Чтобы получить результат диалога, я использую RelayCommand и использую CommandParameters, чтобы передать результат Да / Нет. - person snurre; 03.08.2010
comment
Проблема с этим подходом заключается в том, что вы не можете открыть второй модальный диалог из первого, по крайней мере, без серьезных изменений в системе наложения ... - person Thomas Levesque; 28.08.2011
comment
И еще одна проблема с этим подходом заключается в использовании кода, который снижает необходимость в обслуживании. Во всех своих проектах я стараюсь как можно больше избегать использования кода позади (кроме случаев, когда у меня нет выбора). - person Jviaches; 28.12.2012
comment
@JeffreyKnight, когда я запрещаю пользователям взаимодействовать с такими элементами управления - private void Window_PreviewMouseDown .... e.Handled = true;, он также отключает контроль ошибок, поэтому пользователь также не может закрыть сообщение об ошибке. Как мне решить эту проблему? Кроме того, как вы сделали остальную часть окна серой / тусклой, когда выскакивает ошибка. Спасибо. Мне просто нравится это решение. - person Joe Slater; 08.07.2013
comment
Еще одна проблема с этим подходом заключается в том, что диалоговое окно нельзя переместить. В наших приложениях у нас должен быть подвижный диалог, чтобы пользователь мог видеть, что за ним стоит. - person JAB; 15.04.2015
comment
Мне такой подход кажется ужасным. Что мне не хватает? Чем это лучше настоящего диалогового окна? - person Jonathan Wood; 19.08.2016
comment
Как предотвратить взаимодействие пользователя с элементами управления под этим диалоговым окном? вместо решения для захвата событий можно задать диалоговому окну фон со 100% шириной / высотой. Однако, если вы не хотите, чтобы он был видимым, установка bg на transparent вместо null не позволит никаким событиям проходить. Тем не менее, все равно нужно обращать внимание, например, на ключевые события, если в фокусе находится кнопка или что-то в затемненном виде. - person Balázs; 09.03.2017
comment
Другая проблема - когда у вас два монитора и вы хотите перетащить диалог на второй. - person Vahid; 06.05.2017
comment
@Vahid: Или где-нибудь за пределами родительского окна, если на то пошло. - person O. R. Mapper; 20.06.2017
comment
холст + абсолютное позиционирование - что? Почему? Простое упоминание абсолютного позиционирования в подавляющем большинстве случаев является предупреждением о том, что что-то очень и очень неправильно. Как может быть полезна абсолютная позиция, если главное окно может иметь любой размер? В целом, мне кажется, что это решение изо всех сил пытается воспроизвести все аспекты ограниченных веб-интерфейсов, которые используют обходные пути, чтобы выглядеть так, как будто они являются настоящими графическими интерфейсами с настоящими диалоговыми окнами. - person O. R. Mapper; 20.06.2017

РЕДАКТИРОВАТЬ: Спустя более 10 лет я могу сказать, что использование Посредника или любого другого шаблона обмена сообщениями - действительно плохая идея на многих уровнях. Не делайте этого, просто реализуйте ответ Джеффри или IDialogService, внедренный в вашу модель представления.


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

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

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

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

person Roubachof    schedule 17.04.2009
comment
Марлон Греч только что опубликовал новую реализацию посредника: marlongrech.wordpress.com/2009/04/16/ - person Roubachof; 17.04.2009
comment
Просто замечание: шаблон Mediator не был введен в WPF Disciples, это классический шаблон GoF ... (dofactory.com/Patterns/PatternMediator.aspx). В противном случае хороший ответ;) - person Thomas Levesque; 26.06.2009
comment
Пожалуйста, боже, не используйте посредника или проклятого посыльного. Такой код с десятками летающих сообщений становится очень трудно отлаживать, если вы не можете каким-то образом вспомнить все многие моменты во всей кодовой базе, которые подписываются на каждое событие и обрабатывают его. Это становится кошмаром для новых разработчиков. Фактически, я считаю всю библиотеку MvvMLight массивным анти-шаблоном из-за ее повсеместного и ненужного использования асинхронного обмена сообщениями. Решение простое: вызовите отдельную диалоговую службу (например, IDialogService) вашего дизайна. В интерфейсе есть методы и события для обратных вызовов. - person Chris Bordeman; 21.03.2015

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

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

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

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

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

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

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

person user92541    schedule 18.04.2009
comment
Это действительно интересный подход к проблеме отображения диалоговых окон в MVVM. - person dthrasher; 11.05.2010
comment
"Showing a dialog requires a code-behind" ммм, вы можете назвать это во ViewModel - person Brock Hensley; 23.07.2013
comment
Я бы добавил пункт 3 - вы можете привязываться к другим объектам в представлении. Если оставить код диалогового окна пустым, это означает, что нигде в представлении нет кода C #, а привязка данных не подразумевает привязку к виртуальной машине. - person Paul Williams; 27.04.2014

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

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

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

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

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

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

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

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

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

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

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

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

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

person Mike Rowley    schedule 04.03.2010

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

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

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

interface IDialogService { Task ShowDialogAsync(DialogViewModel dlgVm); }

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

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

ПОМОЩЬ ПО ПРОСМОТРУ

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

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

б) Попросите представление подписаться на событие в его событии OnDataContextChanged. Обязательно скройте и откажитесь от подписки, если старое значение! = Null и в событии Window Unloaded.

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

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

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

person Chris Bordeman    schedule 21.03.2015
comment
Диалоговый сервис, конечно, намного проще, чем я обычно занимаюсь. Это также упрощает закрытие диалогового окна представления из родительской модели представления, что необходимо, когда модель родительского представления закрывается или отменяется. - person Chris Bordeman; 29.07.2015

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

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

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

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

person Cameron MacFarland    schedule 18.01.2009
comment
Виртуальная машина никогда не будет обрабатывать диалог, в моем примере было бы просто иметь событие, которое потребовало бы, чтобы диалог запускался и передавал информацию в некоторой форме EventArgs. Если за это отвечает представление, как оно передает обратно информацию на виртуальную машину? - person Ray Booysen; 18.01.2009
comment
Скажем, виртуальной машине нужно что-то удалить. ВМ вызывает метод View Delete, который возвращает логическое значение. Затем представление может либо удалить элемент напрямую и вернуть истину, либо показать диалоговое окно подтверждения и вернуть истину / ложь в зависимости от ответа пользователя. - person Cameron MacFarland; 18.01.2009
comment
ВМ ничего не знает о диалоге, а только попросила представление удалить что-то, что оно либо подтвердило, либо отклонило. - person Cameron MacFarland; 18.01.2009
comment
Я всегда думал, что суть MVVM - это Модель: бизнес-логика, ViewModel: логика графического интерфейса и View: без логики. Этому как-то противоречит ваш последний абзац. Пожалуйста, объясни! - person David Schmitt; 18.01.2009
comment
Просит вид что-то удалить? Наверняка наоборот. - person Ray Booysen; 18.01.2009
comment
Я новичок в применении шаблона MVVM, однако считаю ошибкой думать о ViewModel как о бизнес-уровне. Вместо этого я понимаю, что ViewModel содержит логику представления и отслеживает состояние, необходимое для пользовательского интерфейса. На мой взгляд, Модель - это место, где существует бизнес-логика и модель предметной области. - person jpierson; 28.09.2009
comment
Сначала необходимо определить, является ли запрос подтверждения перед удалением бизнес-логикой или логикой просмотра. Если это бизнес-логика, метод DeleteFile в модели не должен этого делать, а должен возвращать объект вопроса подтверждения. Это будет включать ссылку на делегата, который выполняет фактическое удаление. Если это не бизнес-логика, виртуальная машина должна создать виртуальную машину вопроса в DeleteFileCommand с двумя членами ICommand. Один за да и один за нет. Вероятно, есть аргументы в пользу обоих представлений, и в RL большинство пользователей, вероятно, встретят оба. - person Guge; 27.11.2009

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

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

person jbe    schedule 21.03.2010

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

person Eric Grover    schedule 29.01.2011

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

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

person Elad Katz    schedule 06.02.2011
comment
Вы должны включить сюда полный код, так как это то, что требуется SO для хороших ответов. Тем не менее связанный подход довольно аккуратный, так что спасибо за это! :) - person Yoda; 03.11.2016
comment
@yoda полный код довольно длинный, поэтому я предпочитаю ссылаться на него. Я отредактировал свой ответ, чтобы отразить изменения и указать на исправную ссылку - person Elad Katz; 10.11.2016
comment
Спасибо за улучшение. Тем не менее, здесь, на SO, лучше предоставить полностраничную прокрутку кода 3, чем ссылку, которая когда-нибудь может быть отключена. Хорошие статьи на сложные темы всегда довольно длинные - и я не вижу никакой пользы в открытии новой вкладки, переключении на нее и прокручивании ее вместо прокрутки на той же странице / вкладке, на которой я был до этого. ;) - person Yoda; 10.11.2016
comment
@EladKatz Я видел, что вы поделились некоторыми из своих реализаций WPF по предоставленной вами ссылке. У вас есть какое-нибудь решение для открытия нового окна из ViewModel? В основном у меня есть две формы, и каждая имеет одну ViewModel. Один пользователь нажимает кнопку, другая всплывает форма, и viewmodel1 отправляет свой объект в viewmodel2. В форме 2 пользователь может изменить объект, и когда он закроет окно, обновленный объект будет отправлен обратно в первую ViewModel. У вас есть какое-нибудь решение для этого? - person Ehsan; 12.02.2020

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

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

person Nikhil Kothari    schedule 24.04.2009

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

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

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

person mukapu    schedule 11.03.2010

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

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

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

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

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

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

    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 и т. Д.), Другие специфичны для этого проекта и используют пользовательские Windows. Таким же образом вы можете добавить дополнительные окна. Ключ состоит в том, чтобы сохранить элементы, специфичные для пользовательского интерфейса, на уровне представления и просто предоставлять возвращаемые данные с помощью POCO на уровне виртуальной машины.
  9. Выполните регистрацию IoC вашего интерфейса на слое просмотра с помощью этого класса. Вы можете сделать это в конструкторе основного представления (после вызова InitializeComponent()):

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

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

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

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

    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 и автозаполнение.

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

person dotNET    schedule 28.06.2019

Накатил собственный оконный загрузчик, описанный в ответе на этот вопрос:

Управление несколькими представлениями WPF в приложении

person Mark Bostleman    schedule 28.04.2010

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

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

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

person surfen    schedule 12.12.2011

Я знаю, что это старый вопрос, но когда я провел этот поиск, я нашел много связанных вопросов, но я не нашел действительно четкого ответа. Поэтому я делаю свою собственную реализацию диалогового окна / сообщения / попина и делюсь ею!
Я думаю, что это «доказательство 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

person Xav987    schedule 19.10.2016

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

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

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() по выбору пользователя.

person David Schmitt    schedule 18.01.2009

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

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

person Community    schedule 28.03.2009

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

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

person Mark Feldman    schedule 05.04.2018

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

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

Мои 2 цента!

person jogi    schedule 07.05.2018

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

Один из способов сделать это -

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

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.
    }
}

И в вашем модальном окне просмотра / 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: как закрыть окно

person Simone    schedule 27.01.2012
comment
Я не был противником, но подозреваю, что это связано с тем, что модель представления имеет прямую ссылку на представление. - person Brian Gideon; 06.05.2013
comment
@BrianGideon, спасибо за ваш комментарий. Я согласен, что это не изолированное решение. На самом деле, я не использую что-то похожее на whar, предложенное blindmeis. Спасибо еще раз. - person Simone; 22.06.2015
comment
Плохой тон - заглядывать в поле зрения, когда этого так легко не делать. - person Chris Bordeman; 29.07.2015