Подтвердить что ты не робот

Открытие нового окна в MVVM WPF

У меня есть Button, и я связал эту кнопку, чтобы команда в ViewModel сообщила OpenWindowCommand. Когда я нажимаю кнопку, я хочу открыть новое окно. Но создание экземпляра окна и отображение окна из модели просмотра является нарушением MVVM. Я создал интерфейс, например

interface IWindowService
{
 void showWindow(object dataContext);
}

и WindowService реализует этот интерфейс, например

class WindowService:IWindowService
{
 public void showWindow(object dataContext)
 {
  ChildWindow window=new ChildWindow();
  window.DataContext=dataContext;
  window.Show();
  }
}

В этом классе я указал ChildWindow. Таким образом, этот класс тесно связан с показом ChildWindow. Когда я хочу показать другое окно, я должен реализовать другой класс с тем же интерфейсом и логикой. Как я могу сделать этот класс общим, чтобы я мог передавать только экземпляр любого окна, и класс сможет открыть любое окно? Я не использую какие-либо встроенные MVVM-фреймворки. Я прочитал много статей о StackOverflow, но я не смог найти решение для этого.

4b9b3361

Ответ 1

Вы говорите: "Создание экземпляра окна и отображение окна из модели просмотра является нарушением MVVM". Это правильно.

Теперь вы пытаетесь создать интерфейс, который принимает вид вида, указанный VM. Это как раз такое нарушение. Вы, возможно, отвлекли логику создания интерфейса, но вы по-прежнему запрашиваете создание представлений из VM.

VM должна заботиться только о создании виртуальных машин. Если вам действительно нужно новое окно для размещения новой виртуальной машины, укажите интерфейс, как вы это сделали, но тот, который НЕ принимает вид. Зачем вам нужен вид? В большинстве проектов MVVM (VM) используются неявные шаблоны данных для связывания представления с конкретной виртуальной машиной. VM ничего не знает о них.

Вот так:

class WindowService:IWindowService
{
    public void ShowWindow(object viewModel)
    {
        var win = new Window();
        win.Content = viewModel;
        win.Show();
    }
}

Очевидно, вам нужно убедиться, что у вас есть VM- > View неявные шаблоны, установленные в app.xaml, чтобы это работало. Это всего лишь стандартный VM MVVM.

например:

<Application x:Class="My.App"
             xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
             xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
             xmlns:vm="clr-namespace:My.App.ViewModels"
             xmlns:vw="clr-namespace:My.App.Views"
             StartupUri="MainWindow.xaml">
    <Application.Resources>

        <DataTemplate DataType="{x:Type vm:MyVM}">
            <vw:MyView/>
        </DataTemplate>

    </Application.Resources>
</Application>

Ответ 2

Одна из возможностей заключается в следующем:

class WindowService:IWindowService
{
 public void showWindow<T>(object DataContext) where T: Window, new() 
 {
  ChildWindow window=new T();
  window.Datacontext=DataContext;
  window.show();
 }
}

Тогда вы можете просто пойти примерно так:

windowService.showWindow<Window3>(windowThreeDataContext);

Для получения дополнительной информации о новом ограничении см. http://msdn.microsoft.com/en-gb/library/sd2w2ew5.aspx

Примечание: new() constraint работает только там, где у окна будет конструктор без параметров (но я думаю, это не должно быть проблемой в этом случае!). В более общей ситуации см. Создать экземпляр родового типа? для возможностей.

Ответ 3

используйте contentpresenter в вашем окне, где вы привязываете свой DataConext. А затем определите Datatemplate для вашего DataContext, чтобы wpf мог визуализировать ваш DataContext. что-то похожее на мою службу DialogWindow

так что все, что вам нужно, это ваш единственный ChildWindow с ContentPresenter:

<Window x:Class="ChildWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation" 
WindowStartupLocation="CenterOwner" SizeToContent="WidthAndHeight">
<ContentPresenter Content="{Binding .}">

</ContentPresenter>
</Window>

Ответ 4

Вы можете написать такую ​​функцию:

class ViewManager
{
    void ShowView<T>(ViewModelBase viewModel)
        where T : ViewBase, new()
    {
        T view = new T();
        view.DataContext = viewModel;
        view.Show(); // or something similar
    }
}

abstract class ViewModelBase
{
    public void ShowView(string viewName, object viewModel)
    {
        MessageBus.Post(
            new Message 
            {
                Action = "ShowView",
                ViewName = viewName,
                ViewModel = viewModel 
            });
    }
}

Убедитесь, что свойство ViewCase имеет свойство DataContext. (Вы можете наследовать UserControl)

В общем, я бы сделал какую-то шину сообщений и получал ViewManager для сообщений, запрашивающих представление. ViewModels отправит сообщение с запросом на просмотр и отображаемыми данными. Затем ViewManager будет использовать приведенный выше код.

Чтобы запретить вызывающему ViewModel знать о типах Вид, вы можете передать строковое/логическое имя представления в ViewManager и заставить ViewManager перевести логическое имя в тип.

Ответ 6

Я считаю, что принятое решение очень полезно, но при попытке его практически я обнаружил, что ему не хватает возможности сделать UserControl (представление, полученное из сопоставления VM → View) в окне хостинга, чтобы занять всей области, предоставленной им. Поэтому я расширил решение, включив эту возможность:

public Window CreateWindowHostingViewModel(object viewModel, bool sizeToContent)
{
   ContentControl contentUI = new ContentControl();
   contentUI.Content = viewModel;
   DockPanel dockPanel = new DockPanel();
   dockPanel.Children.Add(contentUI);
   Window hostWindow = new Window();
   hostWindow.Content = dockPanel;

   if (sizeToContent)
       hostWindow.SizeToContent = SizeToContent.WidthAndHeight;

   return hostWindow;
}

Трюк здесь - использование DockPanel для размещения представления, преобразованного из виртуальной машины.

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

var win = CreateWindowHostingViewModel(true, viewModel)
win.Title = "Window Title";
win.Show();

или следующим образом, если у вас есть фиксированный размер для окна:

var win = CreateWindowHostingViewModel(false, viewModel)
win.Title = "Window Title";
win.Width = 500;
win.Height = 300;
win.Show();