10

Без шаблона MVVM, вызов нового окна в приложениях WPF довольно прост:

// Обработчик кнопки открытия другого окна

void OpenOtherWindow(object sender, RoutedEventArgs e) {
    OtherWindow otherWindow = new OtherWindow();
    otherWindow.ViewModel = "ViewModel"; 
    otherWindow.Show();
    otherWindow.ShowViewModel();
}

// Другое окно
public partial class OtherWindow : Window {
    public string ViewModel { get; set; }

        public OtherWindow(){
            InitializeComponent();
        }

        public void ShowViewModel(){
            MessageBox.Show(ViewModel);
        }
}

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

MainWindowView.xaml (Элемент View)

<Window.CommandBindings>
    <CommandBinding Command="local:AutomationCommands.OpenOtherWindow" Executed="OpenOtherWindow_Executed"/>
</Window.CommandBindings>

MainWindowView.xaml.cs

private void OpenOtherWindow_Executed(object sender, ExecutedRoutedEventArgs e) {
    OtherWindow otherWindow = new OtherWindow();
    otherWindow.ViewModel = "ViewModel"; 
    otherWindow.Show();
    otherWindow.ShowViewModel();
}

AutomationCommands.cs

public static RoutedCommand OpenList = new RoutedCommand("OpenOtherWindow", typeof(AutomationCommands), new InputGestureCollection(){
        new KeyGesture(Key.N, ModifierKeys.Control)
});

Согласно концепции шаблона MVVM, в MainWindowView.xaml.cs особо логики быть не должно. Какие альтернативные, более правильные с точки зрения MVVM подходы Вы можете преложить?


Обновление (Конкурс)

Несколько часов смотрел разные примеры по данной теме на разных языках. Все они либо неполные, потому не получается довести их до рабочего приложения, либо слишком сложные, а потому крайне трудны для понимания и адаптировать их под своё приложение, понимая при этом что делаешь, невозможно без помощи более опытных wpf-разработчиков. Потому, в данном конкурсе задача будет такая:

  • Сделать минимальное, но рабочее приложение, на основе которого уже можно будет дальше что-то усложнять.
  • Ничего лишнего: две кнопки на главном экране; одна открывает обычное окно, другая - диалоговое (в чём разница в случае с WPF - честно говоря не понимаю, поэтому включите, пожалуйста, это объяснение в ответ). В новых окнах будет по одной надписи, что окно успешно открыто. Всё, больше нам в разметке ничего не нужно.

введите сюда описание изображения введите сюда описание изображения введите сюда описание изображения

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

введите сюда описание изображения

  • Не надо писать мини-фреймворки - люди, у которых нет опыта разработки в WPF, их не поймут, а потому не смогут воспользоваться ими с понимаем. Нам нужен только код для открытия окон (в рамках MVVM - с использование сервисов) а так же оповещение сервисов для закрытия окна.
  • Поскольку приложение простейшее, то никаких особых действий при закрытии окна не будет. Однако прошу Вас добавить пустой метод для обработки закрытия окна, чтобы при дальнейшем усложнении данного примера было понятно, куда этот метод вставлять (в этом методе может быть, например, сохранение введённых данных).

Заготовки кода:

MainWindowView.xaml - элемент View главного окна

<Window x:Class="MVVM_OpenNewWindowMinimalExample.MainWindow"
        xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
        xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
        xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
        xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
        mc:Ignorable="d"
        Title="MainWindow" Height="350" Width="350">
    <StackPanel>
        <Button Content="Открыть обычное дочернее окно" Command="{Binding OpenChildWindow}"/>
        <Button Content="Открыть диалоговое окно" Command="{Binding OpenDialogWindow}"/>
    </StackPanel>
</Window>

MainWindowView.xaml.cs - это backcode называется, да?

namespace MVVM_OpenNewWindowMinimalExample.Views {
    public partial class MainWindowView : Window {
        public MainWindowView() {
            InitializeComponent();
        }
    }
}

App.xaml.cs

namespace MVVM_OpenNewWindowMinimalExample {
    public partial class App : Application {
        protected override void OnStartup(StartupEventArgs e) {
            base.OnStartup(e);
            var modelView = new MainWindowView {
                DataContext = new MainWindowViewModel()
            };
            modelView.Show();
        }
    } 
}

Ссылка на Visual Studio-проект (Yandex Диск). Возможно, станет недоступной через некоторое время после окончания конкурса.

Боков Глеб
  • 1,116
  • 2
  • 25
  • 70

3 Answers3

12

Очень хороший вопрос. Открытие новых окон — это сложный момент в рамках паттерна MVVM.

Смотрите. С одной стороны, решение об открытии нового окна принадлежит уровню бизнес-логики, то есть, VM. С другой стороны, VM не имеет права ничего знать о View. Получается противоречие.

Обычно это противоречие решают, создавая дополнительный сервис открытия окон, который настраивается в начале работы программы, и лежит между VM и View. С таким подходом VM-код создаёт VM-объект некоторого типа, и просит сервис обеспечить показ. Сервис проверяет тип объекта, создаёт окно нужного типа, и показывает его, установив переданный объект в качестве DataContext.

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


Вам ещё придётся организовывать логику, оповещающую сервис о закрытии окна (и, возможно, запрашивающую подтверждение этого в VM).

VladD
  • 206,799
  • Благодарю Вас за ответ! Я видел примеры с сервисом для открытия окон, но не понял одного момента, потому во всех примерах было только одно дополнительное окно. Подскажите пожалуйста, сервис нужно создавать отдельный для каждого окна, или достаточно одного для нескольких окон? – Боков Глеб Oct 08 '17 at 00:12
  • @GurebuBokofu: Многочисленные сервисы не имеют смысла, по-моему. Достаточно одного сервиса. – VladD Oct 08 '17 at 08:03
  • Имеют. У меня каждый инстанс такого сервиса отвечает за стек окон начиная от немодального, чтобы выставлять верный Owner – vitidev Oct 09 '17 at 03:50
  • Возможно, в идеале, сервис должен связывать между собой VM и делегат, возвращающий FrameworkElement (который имеет свойство DataContext). В этом делегате может быть как открытие нового окна, так и, например, просто показ и возврат какой-то панели основного окна или вообще какая-то логика, которая проверяет, например, ориентацию экрана, его разрешение или размеры... – Андрей NOP Oct 09 '17 at 06:10
  • @vitidev: Код получится чересчур джаваобразным. Я бы всё упаковал в один сервис, за счёт небольшого усложнения кода сервиса при этом упростится код клиента. – VladD Oct 09 '17 at 08:46
  • @Андрей: А, ну это да, UI может захотеть и не открывать новое окно. (Или показать псевдопопап внутри главного окна, да.) – VladD Oct 09 '17 at 08:49
  • Вообще не знаю о чем вы. Если иметь статик сервис, то разница есть. А если передавать через конструктор, то разницы никакой. – vitidev Oct 09 '17 at 13:11
  • @vitidev: Таскать сервис с собой и передавать его вниз всем VM-модулям? В расчёте на то, что этот сервис нужно будет в каком-то из модулей подменить? Слишком дорого, слишком мало даёт. Разумеется, статический сервис. – VladD Oct 09 '17 at 13:18
  • Нет. не в расчете на подмену, а потому что может образоваться новый стек окон. Открываем 2 НЕмодальных окна и вот у нас 2 родителя для их открываемых дочерних окон. Собственно со статического сервиса, в котором подобное было не решить, я на это перешел. – vitidev Oct 09 '17 at 13:24
8

Написано приложение, демонстрирующее открытие и закрытие окон с использованием паттерна MVVM. Так же, команды отвечающие за открытие окон привязаны на горячие клавиши (Ctrl-Y - открыть первое окно, Ctrl-Z - открыть второе окно) Код приложения находится в git https://github.com/bakulev/MVVM_OpenNewWindowMinimalExample Работоспособность приложения проверена. В коде использованы советы описанные в вопросе Сервис создания модальных и немодальных окон в контексте паттерна MVVM

Anton Bakulev
  • 331
  • 1
  • 5
  • 1
    Отличная работа! Примите, пожалуйста, призовые очки репутации в качестве благодарности. Уверен, Ваш пример в репозитории поможет многим быстрее освоить разработку WPF. – Боков Глеб Oct 14 '17 at 07:43
  • А как программно закрыть модальное окно непосредственно из view model окна? CancellationToken? – Mike Waters Aug 08 '18 at 06:56
  • 1
    Получается await ShowDialog'a и он ждет window.Close – Mike Waters Aug 08 '18 at 07:05
1

Как вариант, можно воспользоваться классическим паттерном "Команда", как показано здесь. Тогда, в идеале, можно достичь того, что в представлении останется только инициализация модели представления.

Streletz
  • 11,734
  • Пожалуйста, покажите пример прямо в этом ответе. Ссылки не годятся в качестве ответов, потому что они ненадежны и не вечны. – Nick Volynkin Oct 13 '17 at 03:51
  • Кстати, похоже что вы на свой блог даете ссылку. Это вообще очень здорово, что вы делитесь знаниями в блоге. Но в таком случае принято явно упоминать, что блог ваш. (Иначе участники могут посчитать, что это спам.) – Nick Volynkin Oct 13 '17 at 03:52