Открытие нового окна в 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. Я прочитал много статей о Stackru, но не смог найти никакого решения для этого.
7 ответов
Вы говорите: "Создание экземпляра окна и показ окна из модели представления является нарушением MVVM". Это правильно.
Теперь вы пытаетесь создать интерфейс, который принимает тип представления, указанный виртуальной машиной. Это такое же нарушение. Возможно, вы абстрагировали логику создания интерфейса, но вы по-прежнему запрашиваете создание представлений внутри виртуальной машины.
ВМ должны заботиться только о создании ВМ. Если вам действительно нужно новое окно для размещения новой виртуальной машины, предоставьте интерфейс, который вы сделали, но тот, который НЕ использует вид. Зачем вам нужен вид? Большинство (в первую очередь VM) проектов MVVM используют неявные шаблоны данных, чтобы связать представление с определенной VM. ВМ ничего не знает о них.
Как это:
class WindowService:IWindowService
{
public void ShowWindow(object viewModel)
{
var win = new Window();
win.Content = viewModel;
win.Show();
}
}
Очевидно, что вам нужно убедиться, что у вас есть неявные шаблоны VM->View, настроенные в app.xaml, чтобы это работало. Это просто стандартная первая виртуальная машина 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>
Одна возможность состоит в том, чтобы иметь это:
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
работает только тогда, когда окно будет иметь конструктор без параметров (но я думаю, что в этом случае это не должно быть проблемой!) В более общей ситуации см. Создание экземпляра универсального типа? для возможностей.
Вы можете написать такую функцию:
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 преобразовать логическое имя в тип.
Используйте 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>
Я нахожу принятое решение очень полезным, но, пробуя его на практике, я обнаружил, что ему не хватает возможности закрепить 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();
Вот это небольшой вклад. Чтобы быть более общим, мы можем получить тип viewModel, который передается методу showWindow, затем найти соответствующее ему представление и, наконец, создать его экземпляр.
class WindowService : IWindowService
{
public void showWindow(object viewModel)
{
string viewToSearch;
Type foundViewType;
// Find the type of the viewModel
Type t = viewModel.GetType();
// Get the views
List<Type> myViews = Assembly.GetExecutingAssembly().GetTypes()
.Where(t => t.Namespace == "[yourNameSpace].Views")
.ToList();
// Find the corresponding view
viewToSearch = t.Name.Replace("ViewModel", "View");
foundViewType = myViews.Find(x => x.Name.Equals(viewToSearch));
if (foundViewType != null)
{
var window = Activator.CreateInstance(foundViewType);
((Window)window).DataContext = viewModel;
((Window)window).Show();
}
}
}
Затем вы можете вызвать его в своей модели представления.
WindowService windowService = new WindowService();
windowService.showWindow(this);
Может быть, вы могли бы передать тип окна.
Попробуйте использовать Activator.CreateInstance()
,
См. Следующий вопрос: Создание объекта с типом, определенным во время выполнения.
Решение по чакриту:
// determine type here
var type = typeof(MyClass);
// create an object of the type
var obj = (MyClass)Activator.CreateInstance(type);