Обробка діалогів у WPF з MVVM


235

У шаблоні MVVM для WPF обробка діалогів - одна з найскладніших операцій. Оскільки ваша модель перегляду нічого не знає про перегляд, діалогове спілкування може бути цікавим. Я можу викрити ICommandте, що коли подання викликає його, може з'явитися діалогове вікно.

Хтось знає про хороший спосіб обробляти результати діалогів? Я говорю про діалоги Windows, такі як MessageBox.

Одним із способів, яким ми це зробили, було те, що подію на модуль перегляду буде підписано, коли потрібне діалогове вікно.

public event EventHandler<MyDeleteArgs> RequiresDeleteDialog;

Це нормально, але це означає, що для перегляду потрібен код, який би я хотів триматися подалі.


Чому б не прив’язати до помічника-об’єкта у Перегляді?
Пол Вільямс

1
Не впевнений, що ти маєш на увазі.
Рей Буйсен

1
Якщо я розумію питання, ви не хочете, щоб з'явилося діалогове вікно VM, і ви не хочете, щоб код у списку переглядався. Крім того, це здається, що ви віддаєте перевагу командам подіям. Я погоджуюся з усім цим, тому використовую клас помічників у Перегляді, який відкриває команду для обробки діалогового вікна. Я відповів на це запитання в іншій темі тут: stackoverflow.com/a/23303267/420400 . Тим НЕ менше, останнє речення звучить так , як ви не хочете який - або код на всіх, в будь-якому місці в поданні. Я розумію це занепокоєння, але код, про який йде мова, лише умовний, і він, ймовірно, не зміниться.
Пол Вільямс

4
Модель перегляду завжди повинна відповідати за логіку створення діалогового вікна, ось і вся причина його існування в першу чергу. Однак, це не означає (і не повинно) робити важкого підйому, створюючи сам погляд. Я написав статтю на цю тему за адресою codeproject.com/Articles/820324/…, де я показую, що всім життєвим циклом діалогових вікон можна керувати за допомогою звичайного прив'язки даних WPF та без порушення схеми MVVM.
Марк Фельдман

Відповіді:


131

Я пропоную відмовитись від модальних діалогів 1990 року та замість цього здійснити керування як накладення (полотно + абсолютне позиціонування) із видимістю, прив’язаною до булевих даних назад у VM. Ближче до керування типу «Аякс».

Це дуже корисно:

<BooleanToVisibilityConverter x:Key="booltoVis" />

а саме:

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

Ось як я реалізований як елемент управління користувачем. Клацання на "x" закриває елемент керування в рядку коду в коді usercontrol позаду. (Оскільки у мене є перегляди в .exe та ViewModels у dll, я не почуваюся погано щодо коду, який маніпулює інтерфейсом користувача.)

Діалогове вікно Wpf


20
Так, мені також подобається ця ідея, але я хотів би побачити приклад цього контролю з точки зору того, як його показати, і отримати результат діалогу з нього і т. Д. Особливо в сценарії MVVM в Silverlight.
Роболоб

16
Як ви заважаєте користувачеві взаємодіяти з елементами управління під цим діалоговим накладом?
Ендрю Гарнісон

16
Проблема такого підходу полягає в тому, що ви не можете відкрити другий модальний діалог з першого, принаймні, без деяких серйозних модифікацій системи накладання ...
Томас Левеск

6
Ще одна проблема такого підходу полягає в тому, що діалог не можна переміщувати. У наших програмах ми повинні мати діалогове діалогове вікно, щоб користувач міг бачити, що за ним стоїть.
JAB

12
Цей підхід мені здається жахливим. Що я пропускаю? Як це краще, ніж справжнє діалогове вікно?
Джонатан Вуд

51

Для цього слід використовувати посередника. Посередник - це загальна модель дизайну, яка також відома як Месенджер у деяких своїх реалізаціях. Це парадигма типу Реєстрація / Повідомлення та дозволяє вашій ViewModel та Views спілкуватися через низькозв'язаний механізм обміну повідомленнями.

Слід переглянути групу WPF Disciples в google і просто знайти Посередника. Ви будете дуже задоволені відповідями ...

Однак ви можете почати з цього:

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


2
Marlon grech щойно опублікував нову реалізацію посередника: marlongrech.wordpress.com/2009/04/16/…
Roubachof

21
Лише зауваження: модель Медіатора не була представлена ​​учнями WPF, це класичний шаблон GoF ... ( dofactory.com/Patterns/PatternMediator.aspx ). Гарна відповідь інакше;)
Томас Левеск

10
Нехай бог, не використовуй посередника чи проклятого посланця. Такий код з десятками повідомлень, що пролітають навколо, стає дуже важким налагодженням, якщо ви не зможете якось запам’ятати всі безлічі пунктів у всій своїй кодовій базі, які підписуються та обробляють кожну подію. Це стає кошмаром для нових дияволів. Насправді я вважаю всю бібліотеку MvvMLight масовою антидіаграмою для її поширеного та непотрібного використання асинхронних повідомлень. Рішення просте: зателефонуйте в окрему діалогову службу (тобто IDialogService) вашого дизайну. В інтерфейсі є методи та події для зворотних викликів.
Кріс Бордеман

34

Хороший діалог MVVM повинен:

  1. Буде оголошено лише XAML.
  2. Отримайте всю його поведінку від прив'язки даних.

На жаль, WPF не надає цих функцій. Показ діалогового вікна вимагає виклику за кодом ShowDialog(). Клас Window, який підтримує діалоги, не може бути оголошений у XAML, тому він не може бути легко пов'язаний з даними DataContext.

Щоб вирішити це, я написав заглушку XAML, що сидить у логічному дереві та ретрансляції, пов'язану з даними, Windowта обробляє діалогове вікно та приховує. Ви можете знайти його тут: http://www.codeproject.com/KB/WPF/XAMLDialog.aspx

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

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

Можливо, ви хочете додати стиль, який задається Showing. Я пояснюю це у своїй статті. Я сподіваюся, що це вам допоможе.


2
Це дійсно цікавий підхід до проблеми показу діалогових вікон у MVVM.
dthrasher

2
"Showing a dialog requires a code-behind"ммм, ви можете зателефонувати цьому у ViewModel
Brock Hensley

Я додав би пункт 3 - ви вільні прив’язуватись до інших об'єктів у межах перегляду. Якщо залишити код діалогу порожнім, то це означає, що ніде в огляді немає коду C #, а прив'язка даних не означає прив'язку до VM.
Пол Вільямс

25

Я використовую такий підхід для діалогів з MVVM.

Все, що я маю зараз зробити, - це зателефонувати наступне з моєї моделі перегляду.

var result = this.uiDialogService.ShowDialog("Dialogwindow title goes here", dialogwindowVM);

з якої бібліотеки походить uiDialogService?
aggietech

1
немає бібліотеки. це лише невеликий інтерфейс і реалізація: stackoverflow.com/questions/3801681 / ... . щоб бути справедливим atm, він має ще кілька перевантажень для моїх потреб :) (висота, ширина, параметри властивості тощо)
blindmeis

16

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

Дивіться повне рішення Silverlight 4 тут Модальні діалоги з MVVM та Silverlight 4


Так само, як і підхід @Elad Katz, у вашій відповіді бракує пов'язаного вмісту. Будь ласка, вдосконаліть свою відповідь, вставивши її, оскільки це так, що вважається гарною відповіддю тут на ТАК. Тим не менше, дякую за ваш внесок! :)
Йода

6

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

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

Добре, щоб ViewModel безпосередньо викликав діалогове вікно. Однак, коли ви тестуєте ViewModel, це означає, що діалогове вікно вискакує під час вашого тесту, або виходить з ладу всі разом (ніколи цього не намагався).

Отож, під час тестування має статися тестова версія діалогового вікна. Це означає, що для будь-якого діалогового вікна вам потрібно створити інтерфейс і знущатися з діалогової відповіді або створити тестовий макет, який матиме поведінку за замовчуванням.

Ви вже повинні використовувати якийсь Локатор послуг або IoC, який ви можете налаштувати, щоб надати вам правильну версію залежно від контексту.

Використовуючи такий підхід, ваш ViewModel все ще перевіряється, і залежно від того, як ви знущаєтесь із діалогів, ви можете контролювати поведінку.

Сподіваюсь, це допомагає.


6

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

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

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

interface IDialogService { Task ShowDialogAsync(DialogViewModel dlgVm); }

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

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

ПОГЛЯД ДОСЛІДЖЕННЯ

Це означає, що ви переглядаєте подію в моделі перегляду. Це все може бути перетворено на Blend Behavior, щоб уникнути коду та використання ресурсів, якщо ви настільки схильні (FMI, підклас класу "Поведінка", щоб побачити своєрідну властивість Blendable на стероїдах). Поки що ми зробимо це вручну на кожному перегляді:

a) Створіть OpenXXXXXDialogEvent зі спеціальним корисним навантаженням (похідний клас DialogViewModel).

б) Попросити представлення передплатити подію в його події OnDataContextChanged. Не забудьте приховати та скасувати підписку, якщо старе значення! = Null та у події Вивантажене вікно.

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

Цей підхід є більш гнучким, але вимагає більше роботи. Я не дуже його використовую. Єдиною приємною перевагою є можливість, наприклад, розмістити погляд у вкладці. Я використав алгоритм, щоб розмістити його в межах поточного контролю користувачів або, якщо він недостатньо великий, обертати візуальне дерево, поки не знайдеться достатньо великий контейнер.

Це дозволяє діалоговим вікнам бути близьким до місця, яке вони фактично використовуються, лише затемнюють частину програми, пов’язану з поточною діяльністю, і дозволяють користувачеві переміщатися в межах додатка, не маючи вручну відштовхувати діалогові вікна, навіть мати кілька квазі- модальні діалоги відкриваються на різних вкладках або підпоглядах.


Діалогове обслуговування набагато простіше, звичайно, і те, що я зазвичай роблю. Це також дозволяє легко закрити діалогове вікно подання з батьківської моделі подання, що необхідно, коли модель батьківського подання закривається чи скасовується.
Кріс Бордеман

4

Скористайтеся командою, що може бути заморожена

<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();
    }
}

Цей код потребує певної роботи, але це найкраща ідея на сьогоднішній день, особливо для системних діалогів, таких як діалоги файлів або принтерів. Діалоги належать до View, якщо щось робить. У діалогових вікнах файлу результат (вибране ім'я файлу) може бути переданий до внутрішньої команди як його параметр.
Антон Тихий

3

Я думаю, що керування діалоговим вікном повинно відповідати погляду, і погляд повинен мати код для його підтримки.

Якщо ви зміните взаємодію ViewModel - View для обробки діалогів, то ViewModel залежить від цієї реалізації. Найпростіший спосіб вирішити цю проблему - це зробити Погляд відповідальним за виконання завдання. Якщо це означає, що це діалогове вікно, то добре, але це може бути і повідомлення про статус у рядку стану тощо.

Моя думка полягає в тому, що вся справа в шаблоні MVVM полягає в відокремленні бізнес-логіки від графічного інтерфейсу, тому вам не слід змішувати логіку графічного інтерфейсу (для відображення діалогового вікна) у бізнес-шарі (ViewModel).


2
Віртуальний комп'ютер ніколи не оброблятиме діалог, у моєму прикладі буде спрощено подію, яка вимагатиме діалогового вікна та передачі інформації в якійсь формі EventArgs. Якщо вигляд відповідає, як він передає інформацію назад до ВМ?
Рей Буйсен

Скажімо, VM потрібно щось видалити. VM викликає метод View Delete, який повертає булева. Потім View може або видалити елемент безпосередньо, і повернути true, або показати діалог підтвердження і повернути true / false, залежно від відповіді користувачів.
Камерон Макфарланд

Віртуальний комп'ютер нічого не знає про діалогове вікно, але лише попросив перегляд видалити щось, яке перегляд підтвердив або спростував.
Камерон Макфарланд

Я завжди думав, що точкою MVVM є Модель: бізнес-логіка, ViewModel: логіка GUI та View: немає логіки. Що певним чином суперечить вашому останньому абзацу. Будь ласка, поясніть!
Девід Шмітт

2
Спочатку це слід визначити, якщо запит на підтвердження попереднього видалення - це бізнес-логіка чи логіка перегляду. Якщо це ділова логіка, метод DeleteFile в моделі не повинен цього робити, а повертати об’єкт питання підтвердження. Це стосуватиметься посилання на делегата, який робить фактичне видалення. Якщо це не ділова логіка, VM повинен побудувати VM запитання в DeleteFileCommand з двома членами ICommand. Один за так і один за ні. Напевно є аргументи для обох поглядів, і в RL більшість використання, мабуть, зустрінеться і з обома.
Guge

3

Цікавою альтернативою є використання контролерів, які відповідають за показ думок (діалогових вікон).

Як це працює, показано WPF Application Framework (WAF) .


3

Чому б просто не підняти подію у вітрині та підписатись на подію у поданні? Це дозволило б зберегти логіку програми та перегляд відокремленим, а також дозволить використовувати дочірнє вікно для діалогів.


3

Я реалізував поведінку, яка слухає повідомлення з ViewModel. Він заснований на рішенні Laurent Bugnion, але оскільки він не використовує код позаду і є багаторазовим використання, я думаю, що це більш елегантно.

Як змусити WPF вести себе так, ніби MVVM підтримується поза коробкою


1
Ви повинні включити тут повний код, тому що саме так потрібно для хороших відповідей. Тим не менш, пов'язаний підхід досить акуратний, тому дякую за це! :)
Йода

2
@yoda повний код досить довгий, і саме тому я краще посилаюся на нього. Я відредагував свою відповідь, щоб відобразити зміни та вказати на посилання, яке не порушено
Elad Katz

Дякуємо за покращення. Тим не менш, краще надати код прокрутки на повній сторінці коду тривалий час на SO, ніж посилання, яке колись може бути офлайн. Хороші статті для складних тем завжди досить довгі - і я не бачу ніякої користі в тому, щоб відкрити нову вкладку, перейти на неї та прокрутити там під час прокрутки на тій самій сторінці / вкладці, на якій я був до цього. ;)
Йода

@EladKatz Я бачив, що ви поділилися деякою частиною своєї реалізації WPF у наданому вами посиланні. Чи є у вас рішення для відкриття нового вікна від ViewModel? В основному я маю дві форми, і кожна має один ViewModel. Один користувач натискає кнопку, інша спливає форма, а viewmodel1 відправляє свій об’єкт у viewmodel2. У формі 2 користувач може змінити об'єкт, і коли він закриє вікно, оновлений об’єкт буде відправлений назад у перший ViewModel. Чи є у вас рішення для цього?
Ехсан

2

Я думаю, що представлення може мати код для обробки події з моделі перегляду.

Залежно від події / сценарію, він також може мати тригер події, який підписується для перегляду подій моделі та одну чи більше дій, щоб викликати у відповідь.




1

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

Мені подобається підхід до сервісу - Він менш гнучкий, тому користувачі менше шансів щось зламати :) Це також відповідає частині WinForms моєї програми (MessageBox.Show) Але якщо ви плануєте показати багато різних діалогів, InteractionRequest - це кращий шлях.

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


1

Я знаю, що це давнє запитання, але коли я здійснив цей пошук, я знаходжу багато споріднених питань, але я не знайшов дійсно чіткої відповіді. Тож я роблю власну реалізацію діалогового вікна / скриньки повідомлень / popin, і я їм ділюсь!
Я думаю, що це "доказ 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


1

Стандартний підхід

Провівши роки, що займаються цією проблемою у WPF, я нарешті з'ясував стандартний спосіб впровадження діалогів у WPF. Ось переваги такого підходу:

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

Отже, що є ключовим. Це DI + IoC .

Ось як це працює. Я використовую MVVM Light, але цей підхід може бути поширений і на інші рамки:

  1. Додайте проект WPF Application до свого рішення. Назвіть це додаток .
  2. Додати бібліотеку класів ViewModal. Назвіть це ВМ .
  3. Посилання на додаток VM-проект. Проект VM нічого не знає про додаток.
  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. Додайте реалізацію цього інтерфейсу в проект App.

    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. Тим самим способом можна додати більше спеціальних вікон. Ключовим моментом є збереження специфічних для інтерфейсу елементів у шарі View та просто викриття повернених даних за допомогою POCOs у рівні 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 нічого не знає про те, як питання «Так / Ні» буде представлено користувачеві шаром інтерфейсу користувача і все ще може успішно працювати з поверненим результатом у діалоговому вікні.

Інші безкоштовні пільги

  1. Для написання тестового модуля ви можете надати користувальницьку реалізацію IDialogServiceу вашому тестовому проекті та зареєструвати цей клас в IoC в конструкторі вашого тестового класу.
  2. Вам потрібно імпортувати деякі простори імен, наприклад, Microsoft.Win32для доступу до діалогових вікон "Відкрити" та "Зберегти". Я їх покинув, оскільки є також доступна версія WinForms для цих діалогів, плюс хтось може захотіти створити свою власну версію. Також зауважте, що деякі з ідентифікаторів, які використовуються, - DialogPresenterце імена моїх власних вікон (наприклад SettingsWindow). Вам потрібно буде або видалити їх із інтерфейсу та реалізації, або надати власні вікна.
  3. Якщо ваш VM виконує багатопотоковість, зателефонуйте MVVM Light на DispatcherHelper.Initialize()початку життєвого циклу вашої програми.
  4. За винятком того, DialogPresenterщо вводиться в шар Перегляд, інші 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 і автоматичного завершення.

Сподіваюся, що допоможе всім.


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

Я боровся з тією ж проблемою. Я придумав спосіб взаємодіяти між Поглядом та ViewModel. Ви можете ініціювати надсилання повідомлення з ViewModel до Перегляду, щоб сказати йому, що він відображає скриньку повідомлень, і воно буде повідомляти про результат. Тоді ViewModel може відповісти на результат, повернутий з Перегляду.

Я демонструю це у своєму блозі :


0

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

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


0

Вибачте, але я повинен передзвонити. Я переглянув кілька запропонованих рішень, перш ніж знайти простір імен Prism.Wpf.Interactivity в проекті Prism. Ви можете використовувати запити на взаємодію та дію спливаючого вікна для того, щоб згорнути спеціальне вікно або для більш простих потреб вбудовані спливаючі вікна сповіщень та підтвердження. Вони створюють справжні вікна та ними керують. ви можете передавати контекстний об'єкт із будь-якими залежностями, необхідними у діалоговому вікні. Ми використовуємо це рішення у своїй роботі, оскільки я знайшов його. У нас є численні старші дияволи, і ніхто не придумав нічого кращого. Нашим попереднім рішенням було надання діалогового сервісу в накладку та використання класу презентаторів для того, щоб це відбулося, але вам довелося мати фабрики для всіх моделей перегляду діалогів тощо.

Це не банально, але також не дуже складно. І він вбудований в Призму, і тому найкраща (або краща) практика ІМХО.

Мої 2 копійки!


-1

EDIT: так, я погоджуюсь, що це невірний підхід 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: Як закрити вікно


2
Я не був головою, але я підозрюю, що це тому, що модель перегляду має пряме посилання на вигляд.
Брайан Гедеон

@BrianGideon, дякую за ваш коментар Я погоджуюся, що це не розв'язане рішення. Насправді я не використовую щось подібне до причалу, запропонованого сліпою. Знову дякую.
Симона

Це погана форма потрапляти в поле зору, коли так легко не робити.
Кріс Бордеман
Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.