Обработка диалоговых окон в WPF с MVVM

231
задан BoltClock 17 November 2011 в 07:26
поделиться

13 ответов

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

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

<BooleanToVisibilityConverter x:Key="booltoVis" />

как в:

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

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

Wpf dialog

132
ответ дан 23 November 2019 в 03:37
поделиться

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

Мое текущее решение похоже на это:

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
ответ дан Community 23 November 2019 в 03:37
поделиться

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

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

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

3
ответ дан Cameron MacFarland 23 November 2019 в 03:37
поделиться

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

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

0
ответ дан Dan Neely 23 November 2019 в 03:37
поделиться

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

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

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

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

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

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

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

29
ответ дан 23 November 2019 в 03:37
поделиться

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

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

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

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

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

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

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

51
ответ дан 23 November 2019 в 03:37
поделиться

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

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

2
ответ дан 23 November 2019 в 03:37
поделиться

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

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

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

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

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

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

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

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

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

  1. Для написания модульного теста вы можете предоставить пользовательскую реализацию IDialogService в вашем тестовом проекте и зарегистрировать этот класс в IoC в конструкторе вашего тестового класса. [+1139]
  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 и автозаполнение.

Надеюсь, что помогает всем.

1
ответ дан 23 November 2019 в 03:37
поделиться

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

#include <stdio.h>

int main()
{
    int x[3][4] = { 1,  2,  3,  4,
                    5,  6,  7,  8,
                    9, 10, 11, 12};
    int m = 3;
    int n = 4;
    for (int slice = 0; slice < m + n - 1; ++slice) {
        printf("Slice %d: ", slice);
        int z1 = slice < n ? 0 : slice - n + 1;
        int z2 = slice < m ? 0 : slice - m + 1;
        for (int j = slice - z2; j >= z1; --j) {
                printf("%d ", x[j][slice - j]);
        }
        printf("\n");
    }
    return 0;
}

. Выход:

Slice 0: 1
Slice 1: 5 2
Slice 2: 9 6 3
Slice 3: 10 7 4
Slice 4: 11 8
Slice 5: 12

, чтобы кратко объяснить, как это работает, каждый ломтик - это диагональ, начиная с первого столбца, пройдя по диагонали up - вправо и заканчивается на первом ряду (изначально влево, но теперь она поменялась в результате комментариев от плаката).

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

-121--3090646-

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

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

16
ответ дан 23 November 2019 в 03:37
поделиться

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

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

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

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

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

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

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

6
ответ дан 23 November 2019 в 03:37
поделиться

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

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

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

2
ответ дан 23 November 2019 в 03:37
поделиться

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

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

3
ответ дан 23 November 2019 в 03:37
поделиться

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

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

1
ответ дан 23 November 2019 в 03:37
поделиться
Другие вопросы по тегам:

Похожие вопросы: