How can I open another view in WPF MVVM using click handlers and commands? (Is my solution reasonable?)

前端 未结 2 1255
梦毁少年i
梦毁少年i 2020-12-20 05:33

I am writing a WPF application that has two windows.

I have a MainWindowViewModel that holds two more view models: AllTagsViewModel and

相关标签:
2条回答
  • 2020-12-20 06:31

    Foreword: Usually you wouldn't want to have your PlotViewModel and pass it to a window, as it makes a few things more complicated.

    There are to basic approaches View-First and ViewModel First. In View-First you create the View (Page, Window etc) and inject the ViewModel into it (usually via constructor). Though this makes it a bit difficult to and pass a parameter object to it.

    Which is where the NavigationService comes. You resolve the View via IoC container, then pass a parameter to the ViewModel, i.e. if it's a UserViewModel you'd pass the userId to it and the ViewModel will load the user.

    The solution: Navigation Service You can either use an existing one (Prism, or other MVVM Frameworks which come with their own navigation services).

    If you want a own simple one, you could create an INavigationService interface and inject it into your ViewModels.

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

    and implement it like (I am assuming you use a IoC container, since IoC is a key to MVVM to key the objects decoupled. Example with Unity IoC Container)

    public class NavigationService : INavigationService
    {
        private IUnityContainer container;
        public NavigationService(IUnityContainer container) 
        {
            this.container = container;
        }
        public void NavigateToWindow<T>(object parameter) where T : IView
        {
            // configure your IoC container to resolve a View for a given ViewModel
            // i.e. container.Register<IPlotView, PlotWindow>(); in your
            // composition root
            IView view = container.Resolve<T>();
    
            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<PlotWindow>(userId); would violate 
    // MVVM pattern, where navigationService.NavigateToWindow<IPlotWindow>(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
    {
    }
    

    and finally you implement your PlotViewModel class and use the passed parameter to load the object

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

    Of course could modify the NavigationService to also set the DataContext inside it. Or use strings to resolve the View/Window (such as Prism for Windows Store Apps does).

    And in the final code you open the window by calling navigationService.NavigateToWindow<IPlotView>(platId); in your code (i.e. in an ICommand which is bound to a buttons Command Property in your XAML.

    0 讨论(0)
  • 2020-12-20 06:34

    Your approach has the possibility of creating a PlotWindow without the existing PlotViewModel if you use the CanExecute of your CreatePlotViewModelCommand.

    To avoid that problem I would bind the MainWindowView to the PlotViewModel property defined inside the MainWindowViewModel. That way you will get informed once it changes and you can set up a template creating the corresponding view. The ViewModels could than easily be created using a command and the view will only be created if a ViewModel exists.

    0 讨论(0)
提交回复
热议问题