简体   繁体   中英

Can't complete pattern mediator in MVVM

i'm having a problem with mediator pattern in mvvm

I'l describe almost all classes for better understanding of my problem.

  1. I'v got MainWindow and ViewModel for it, it is very simple and auctually doing nothing but holding one of my UserControls, there is a UserControl property in ViewModel that is binded to ContentControl.Content in MainWindow.

  2. UserControls are identical there is only a single button in each of them, and allso there are two ViewModels with commands to handle clikcs.

  3. Class Mediator is a singletone and i tried to use it for iteraction between my ViewModel

So what i'm trying to do is to switch between UserControls, not creating them and their ViewModel inside a MainWindowViewModel. Switching must take place after i'm clicking a buttons. For example if i click on the button on FirstUserControl then ContentControl of the MainWindow should switch to SecondUserControl.

The probleam appears in UserControlsViewModels where i should pass UserControls object as a parameters in Mediator NotifyCollegue() function, but i have no acces to them (of course, that is one of the principles of MVVM), and that is the problem of user types, because with standart types that should not be a problem (for example to pass int or string...).

i found this solutin here http://www.codeproject.com/Articles/35277/MVVM-Mediator-Pattern

And why i can't swith UserControls in MainWindowViewModel, because i want the MainWindow to be clear of everything except current UserControl binded to ContentControl.

What may be possible solutions to this problem, should i make another singletone class and collect all the userControls references there and use them inside UserControlsViewModels, or maybe something else?

I hope that I have clearly described my problem, and that there is some kind of solution.

I will be glad to answer any question and very grateful for the help!!!

oh, and that is not the real app, i just want to get the idea(concept) of mesaging system between ViewModels, not mixing ViewModel and not creation Views and their ViewModels inside of other ViewModels...

Thanks again!

MainView

<Window x:Class="TESTPROJECT.MainWindow"
    xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
    xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
    xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
    xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
    xmlns:local="clr-namespace:TESTPROJECT"
    mc:Ignorable="d"
    xmlns:i="http://schemas.microsoft.com/expression/2010/interactivity"
    Title="MainWindow" Height="500" Width="750">



<Grid>
    <ContentControl Grid.Row="1" Content="{Binding PagesControl}"/>
</Grid>

MainView ViewModel

namespace TESTPROJECT
{
    class MainWindowViewModel : ViewModelBase
    {
        private UserControl _pagesControl;
        public UserControl PagesControl
        {
            //Property that switches UserControls
            set
            {
                _pagesControl = value;
                OnPropertyChanged();
            }
            get
            {
                return _pagesControl;
            }
        }

        public MainWindowViewModel()
        {
            //Method that will be listening all the changes from UserControls ViewModels
            Mediator.Instance.Register(
                (object obj) =>
                {
                    PagesControl = obj as UserControl;
                }, ViewModelMessages.UserWroteSomething);
        }
    }
}

FirstUserControl

<UserControl x:Class="TESTPROJECT.FirstUserControl"
         xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
         xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
         xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006" 
         xmlns:d="http://schemas.microsoft.com/expression/blend/2008" 
         xmlns:local="clr-namespace:TESTPROJECT"
         xmlns:i="http://schemas.microsoft.com/expression/2010/interactivity"
         mc:Ignorable="d" 
         d:DesignHeight="300" d:DesignWidth="300">
<Grid>
    <Button Command="{Binding GetCommand}">
        hello, i'm first user control!
    </Button>
</Grid>

FirstUserControl ViewModel

namespace TESTPROJECT
{
class FirstUserControlViewModel : ViewModelBase
{
    //command that is binded to button
    private DelegateCommand getCommand;
    public ICommand GetCommand
    {
        get
        {
            if (getCommand == null)
                getCommand = new DelegateCommand(param => this.func(param), null);
            return getCommand;
        }
    }
    //method that will handle button click, and in it i'm sending a message
    //to MainWindowViewModel throug Mediator class 
    //and that is allso a problem place because in theory i should
    //pass the opposite UserControl object , but from here i have no 
    //acces to it
    private void func(object obj)
    {
        Mediator.Instance.NotifyColleagues(
            ViewModelMessages.UserWroteSomething,
            "PROBLEM PLACE");
    }
}

}

SecondUserControl

<UserControl x:Class="TESTPROJECT.SecondUserControl"
         xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
         xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
         xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006" 
         xmlns:d="http://schemas.microsoft.com/expression/blend/2008" 
         xmlns:local="clr-namespace:TESTPROJECT"
         mc:Ignorable="d" 
         d:DesignHeight="300" d:DesignWidth="300">
<Grid>
    <Button Command="{Binding GetCommand}">
        Hello, i'm second user control!
    </Button>
</Grid>

SecondUserControl ViewModel

namespace TESTPROJECT
{
class SecondUserControlViewModel : ViewModelBase
{
    //command that is binded to button
    private DelegateCommand getCommand;
    public ICommand GetCommand
    {
        get
        {
            if (getCommand == null)
                getCommand = new DelegateCommand(param => this.func(param), null);
            return getCommand;
        }
    }
    //method that will handle button click, and in it i'm sending a message
    //to MainWindowViewModel throug Mediator class 
    //and that is allso a problem place because in theory i should
    //pass the opposite UserControl object , but from here i have no 
    //acces to it
    private void func(object obj)
    {
        Mediator.Instance.NotifyColleagues(
            ViewModelMessages.UserWroteSomething,
            "PROBLEM PLACE");
    }
}

}

Class Mediator and enum ViewModelMessages

namespace TESTPROJECT
{
//this enum holding some kind of event names fro example UserWroteSomething
// is a name of switching one UserControl to another
public enum ViewModelMessages { UserWroteSomething = 1 };

class Mediator
{
    //Singletone part
    private static Mediator instance;
    public static Mediator Instance
    {
        get
        {
            if (instance == null)
                instance = new Mediator();
            return instance;
        }
    }
    private Mediator() { }
    //Singletone part

    //collection listeners that holds event names and handler functions
    List<KeyValuePair<ViewModelMessages, Action<Object>>> internalList = 
        new List<KeyValuePair<ViewModelMessages, Action<Object>>>();


    //new listener registration
    public void Register(Action<object> callBack, ViewModelMessages message)
    {
        internalList.Add(
            new KeyValuePair<ViewModelMessages, Action<Object>>(message, callBack));
    }

    // notifying all the listener about some changes
    // and those whose names fits will react
    public void NotifyColleagues(ViewModelMessages message, object args)
    {
        foreach(KeyValuePair<ViewModelMessages, Action<Object>> KwP in internalList)
            if(KwP.Key == message)
                KwP.Value(args);
    }
}

}

App starting point

    public partial class App : Application
{
    private void Application_Startup(object sender, StartupEventArgs e)
    {
        FirstUserControl first = new FirstUserControl() { DataContext = new FirstUserControlViewModel() };
        SecondUserControl second = new SecondUserControl() { DataContext = new SecondUserControlViewModel() };

        new MainWindow()
        {
            DataContext = new MainWindowViewModel() { PagesControl = first }
        }.ShowDialog();
    }
}

If I understand you correctly, you want to navigate to another view (or view model respectively) when a certain action on the currently active view model happens (eg you press a button).

If you want to use your mediator for this, you could structure it like this:

public class Mediator
{
    // These fields should be set via Constructor Injection
    private readonly MainWindowViewModel mainWindowViewModel;
    private readonly Dictionary<ViewModelId, IViewFactory> viewFactories;        

    public void NotifyColleagues(ViewModelId targetViewModelId, ViewModelArguments arguments)
    {
        var targetFactory = this.viewModelFactories[targetViewModelId];
        var view = targetFactory.Create(viewModelArguments);
        this.mainWindowViewModel.PagesControl = view;
    }

    // other members omitted to keep the example small
}

You would then create a factory for every view - view model combination. With the ViewModelArguments , you can pass information into the newly created view models that originate from other view models. ViewModelId can be a simple enum like your ViewModelMessage, instead you can also use the Type of the view model (which I would advise you to pursue).

Furthermore, I would advise you to not use a private constructor on the Mediator class because otherwise you cannot pass in the mainWindowViewModel and the dictionary for the view factories. You should be able to configure this in your Application-Startup method.

Also, please note that there are many other ways to structure MVVM applications, like eg using Data Templates to instantiate the view for a view model - but I think that is a bit too stretched for your little example.

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM