Как открыть другое представление в WPF MVVM с помощью обработчиков кликов и команд? (Является ли мое решение разумным?)

Я пишу приложение WPF, в котором есть два windows.

У меня есть MainWindowViewModel который содержит еще две модели просмотра: AllTagsViewModel и PlotViewModel .

 public AllTagsViewModel AllTagsViewModel { get; private set; } public PlotViewModel PlotViewModel { get; private set; } 

На данный момент я использую это решение как обработчик кликов в главном окне:

 private void LaunchPlotWindow_OnClick(object sender, RoutedEventArgs e) { if (PlotWindow.GlobalInstanceCount == 0) { PlotWindow plotWindow = new PlotWindow(); PlotViewModel context = GetViewModel().PlotViewModel; plotWindow.DataContext = context; plotWindow.Show(); } } 

Я также привязываю команду к кнопке. Команда находится в MainWindowViewModel и создает экземпляр нового PlotViewModel с использованием конструктора PlotViewModel(AllTagsViewModel atvm) .

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

Что лучше для решения этой проблемы? Могу ли я использовать событие, чтобы поддерживать AllTagsViewModel в PlotViewModel в актуальном состоянии всегда с тем, что находится в MainWindowViewModel ? Мое решение на данный момент похоже на хак и очень плохую практику.

Спасибо за совет.

Предисловие. Обычно вы не хотите, чтобы ваш PlotViewModel и передал его в окно, так как это усложняет некоторые вещи.

К основным подходам относятся View-First и ViewModel First. В представлении «Сначала» вы создаете представление (страница, окно и т. Д.) И вводите в него ViewModel (обычно через конструктор). Хотя это немного затрудняет и передает объект параметра.

В этом и заключается NavigationService. Вы разрешаете View через контейнер IoC, а затем передаете параметр ViewModel, т. UserViewModel Если это UserViewModel вы передадите ему userId и ViewModel загрузит пользователя.

Решение: навигационная служба. Вы можете либо использовать существующую (Prism, либо другие MVVM Framework, которые поставляются со своими навигационными услугами).

Если вам нужен собственный простой, вы можете создать интерфейс INavigationService и ввести его в свои ViewModels.

 public interface INavigationService { // T is whatever your base ViewModel class is called void NavigateTo() where T ViewModel; void NavigateToNewWindow(); void NavigateToNewWindow(object parameter); void NavigateTo(object parameter); } 

и реализую его как (я предполагаю, что вы используете контейнер IoC, поскольку IoC является ключом к MVVM для блокировки объектов. Пример с Unity IoC Container)

 public class NavigationService : INavigationService { private IUnityContainer container; public NavigationService(IUnityContainer container) { this.container = container; } public void NavigateToWindow(object parameter) where T : IView { // configure your IoC container to resolve a View for a given ViewModel // ie container.Register(); in your // composition root IView view = container.Resolve(); Window window = view as Window; if(window!=null) window.Show(); INavigationAware nav = view as INavigationAware; if(nav!= null) nav.NavigatedTo(parameter); } } // IPlotView is an empty interface, only used to be able to resolve // the PlotWindow w/o needing to reference to it's concrete implementation as // calling navigationService.NavigateToWindow(userId); would violate // MVVM pattern, where navigationService.NavigateToWindow(userId); doesn't. There are also other ways involving strings or naming // convention, but this is out of scope for this answer. IView would // just implement "object DataContext { get; set; }" property, which is already // implemented Control objects public class PlotWindow : Window, IView, IPlotView { } 

и, наконец, вы реализуете свой class PlotViewModel и используете переданный параметр для загрузки объекта

 public class PlotViewModel : ViewModel, INotifyPropertyChanged, INavigationAware { private int plotId; public void NavigatedTo(object parameter) where T : IView { if(!parameter is int) return; // Wrong parameter type passed this.plotId = (int)parameter; Task.Start( () => { // load the data PlotData = LoadPlot(plotId); }); } private Plot plotData; public Plot PlotData { get { return plotData; } set { if(plotData != value) { plotData = value; OnPropertyChanged("PlotData"); } } } } 

Конечно, можно изменить NavigationService чтобы также установить DataContext внутри него. Или используйте строки для разрешения View / Window (например, Prism для приложений Windows Store).

И в конечном коде вы открываете окно, вызывая navigationService.NavigateToWindow(platId); в вашем коде (т. е. в ICommand который привязан к кнопке Command Property в вашем XAML.

У вашего подхода есть возможность создания PlotWindow без существующего PlotViewModel если вы используете CanExecute вашего CreatePlotViewModelCommand .

Чтобы избежать этой проблемы, я бы PlotViewModel MainWindowView к свойству PlotViewModel определенному внутри MainWindowViewModel . Таким образом вы узнаете, как только он изменится, и вы можете настроить шаблон, создающий соответствующее представление. ViewModels можно было бы легко создать с помощью команды, и представление будет создано только в том случае, если ViewModel существует.