it-swarm.com.ru

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

У меня есть кнопка, и я привязал эту кнопку к команде в 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, но я не смог найти никакого решения для этого.

35
DT sawant

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

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

ВМ должны заботиться только о создании ВМ. Если вам действительно нужно новое окно для размещения новой виртуальной машины, предоставьте интерфейс, который вы сделали, но тот, который НЕ использует вид. Зачем вам нужен вид? Большинство (в первую очередь 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>
39
GazTheDestroyer

Одна возможность состоит в том, чтобы иметь это:

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 работает только тогда, когда окно будет иметь конструктор без параметров (но я думаю, что в этом случае это не должно быть проблемой!) В более общей ситуации см. Создать экземпляр универсального типа? для возможностей.

5
David E

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

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

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

</ContentPresenter>
</Window>
3
blindmeis

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

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

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

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

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

3
Erno de Weerd

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

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();
1
Ghareeb Falazi

Может быть, вы могли бы передать тип окна.

Попробуйте использовать Activator.CreateInstance().

См. Следующий вопрос: Создание объекта с типом, определенным во время выполнения .

Решение по чакриту:

// determine type here
var type = typeof(MyClass);

// create an object of the type
var obj = (MyClass)Activator.CreateInstance(type);
0
Hellin