One ViewModel for UserControl and Window or separate ViewModels

.net, c#, mvvm, wpf

Solution

`Part 1. Display the properties of the control in MVVM`

As I said in comments:

In MVVM ViewModel should not know about the controls, which are located. In such cases, use the attached behavior or leave the same side logic in View

`ViewModel` is not directly associated with a `View`, so just refer to the name of the control would not be right. It would be better to set a property in the `Model`, and bind it into `View` via `ViewModel`, but the property `Name` does not support Binding (quote from the MSDN):

Data binding a Name is technically possible, but is an extremely uncommon scenario because a data-bound Name cannot serve the main intended purpose of the property: to provide an identifier connection point for code-behind.

so I suggest to use the `Tag` property or `Uid`. In my example (give an below), I use `Uid` property for these purposes.

`Part 2. Communication via ViewModels (pattern Mediator)`

There are several embodiments of the Mediator pattern, but I like the most the implementation by `XAML Guy`, it is simple and clear - The Mediator Pattern.

`Implementation code`

public static class Mediator
{
    static IDictionary<string, List<Action<object>>> pl_dict = new Dictionary<string, List<Action<object>>>();

    static public void Register(string token, Action<object> callback)
    {
        if (!pl_dict.ContainsKey(token))
        {
            var list = new List<Action<object>>();
            list.Add(callback);
            pl_dict.Add(token, list);
        }
        else
        {
            bool found = false;
            foreach (var item in pl_dict[token])
                if (item.Method.ToString() == callback.Method.ToString())
                    found = true;
            if (!found)
                pl_dict[token].Add(callback);
        }
    }

    static public void Unregister(string token, Action<object> callback)
    {
        if (pl_dict.ContainsKey(token))
        {
            pl_dict[token].Remove(callback);
        }
    }

    static public void NotifyColleagues(string token, object args)
    {
        if (pl_dict.ContainsKey(token))
        {
            foreach (var callback in pl_dict[token])
                callback(args);
        }
    }
}

To demonstrate his work, I created a small example, which consists of two `Views`, each has its own `ViewModel` and `Model`.

The project structure is shown below:

`Output`

When you click on Button, ListOfData `ViewModel` communicates via mediator with DataDetails `ViewModel`, thus:

Mediator.NotifyColleagues("ShowDetails", true);
Mediator.NotifyColleagues("SetSelectedFruit", ListOfDataModel.FruitGreen);

All procedures that interact with the properties must register their `ViewModel` like this:

private void ShowDetails_Mediator(object args)
{
    bool showDetails = (bool)args;

    if (showDetails == true)
    {
        DataDetailsModel.IsVisible = true;
    }
    else
    {
        DataDetailsModel.IsVisible = false;
    }
}

private void SetSelectedFruit_Mediator(object args)
{
    string selectedFruit = (string)args;

    DataDetailsModel.SelectedFruit = selectedFruit;
}

public DataDetailsViewModel() 
{
    DataDetailsModel = new DataDetailsModel();

    Mediator.Register("ShowDetails", ShowDetails_Mediator);
    Mediator.Register("SetSelectedFruit", SetSelectedFruit_Mediator);
}

In the example I used a `DataTemplate` instead `UserControl`. Below are the main part of the project:

`MainWindow.xaml`

<Window x:Class="CommunicateWithVM.MainWindow"
    xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
    xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
    xmlns:ViewModels="clr-namespace:CommunicateWithVM.ViewModels"
    Title="MainWindow" 
    WindowStartupLocation="CenterScreen"
    Height="350"
    Width="525">

    <Grid>
        <ContentControl Name="ListOfData"
                        ContentTemplate="{StaticResource ListOfDataView}">

            <ViewModels:ListOfDataViewModel />            
        </ContentControl>

        <ContentControl Name="DataDetails"
                        ContentTemplate="{StaticResource DataDetailsView}">

            <ViewModels:DataDetailsViewModel />
        </ContentControl>
    </Grid>
</Window> 

`Models`

`DataDetailsModel`

public class DataDetailsModel : NotificationObject
{
    #region SelectedFruit

    private string _selectedFruit = "";

    public string SelectedFruit
    {
        get
        {
            return _selectedFruit;
        }

        set
        {
            _selectedFruit = value;
            NotifyPropertyChanged("SelectedFruit");
        }
    }

    #endregion

    #region IsVisible

    private bool _isVisible = false;

    public bool IsVisible
    {
        get
        {
            return _isVisible;
        }

        set
        {
            _isVisible = value;
            NotifyPropertyChanged("IsVisible");
        }
    }

    #endregion
}

`ListOfDataModel`

public class ListOfDataModel : NotificationObject
{
    #region FruitGreen

    private string _fruitGreen = "Apple";

    public string FruitGreen
    {
        get
        {
            return _fruitGreen;
        }

        set
        {
            _fruitGreen = value;
            NotifyPropertyChanged("FruitGreen");
        }
    }

    #endregion

    #region FruitYellow

    private string _fruitYellow = "Limon";

    public string FruitYellow
    {
        get
        {
            return _fruitYellow;
        }

        set
        {
            _fruitYellow = value;
            NotifyPropertyChanged("FruitYellow");
        }
    }

    #endregion
}

`ViewModels`

`DataDetailsViewModel`

public class DataDetailsViewModel
{
    #region DataDetailsModel

    private DataDetailsModel _dataDetailsModel = null;

    public DataDetailsModel DataDetailsModel
    {
        get
        {
            return _dataDetailsModel;
        }

        set
        {
            _dataDetailsModel = value;
        }
    }

    #endregion

    #region ShowDetails_Mediator

    private void ShowDetails_Mediator(object args)
    {
        bool showDetails = (bool)args;

        if (showDetails == true)
        {
            DataDetailsModel.IsVisible = true;
        }
        else
        {
            DataDetailsModel.IsVisible = false;
        }
    }

    #endregion

    #region SetSelectedFruit_Mediator

    private void SetSelectedFruit_Mediator(object args)
    {
        string selectedFruit = (string)args;

        DataDetailsModel.SelectedFruit = selectedFruit;
    }

    #endregion

    #region DataDetailsViewModel Constructor

    public DataDetailsViewModel() 
    {
        DataDetailsModel = new DataDetailsModel();

        Mediator.Register("ShowDetails", ShowDetails_Mediator);
        Mediator.Register("SetSelectedFruit", SetSelectedFruit_Mediator);
    }

    #endregion
}

`ListOfDataViewModel`

public class ListOfDataViewModel
{
    #region ListOfDataModel

    private ListOfDataModel _listOfDataModel = null;

    public ListOfDataModel ListOfDataModel
    {
        get
        {
            return _listOfDataModel;
        }

        set
        {
            _listOfDataModel = value;
        }
    }

    #endregion

    #region GreenButtonCommand

    private ICommand _greenButtonCommand = null;

    public ICommand GreenButtonCommand
    {
        get
        {
            if (_greenButtonCommand == null)
            {
                _greenButtonCommand = new RelayCommand(param => this.GreenButton(), null);
            }

            return _greenButtonCommand;
        }
    }

    private void GreenButton()
    {
        Mediator.NotifyColleagues("ShowDetails", true);
        Mediator.NotifyColleagues("SetSelectedFruit", ListOfDataModel.FruitGreen);
    }

    #endregion

    #region YellowButtonCommand

    private ICommand _yellowButtonCommand = null;

    public ICommand YellowButtonCommand
    {
        get
        {
            if (_yellowButtonCommand == null)
            {
                _yellowButtonCommand = new RelayCommand(param => this.YellowButton(), null);
            }

            return _yellowButtonCommand;
        }
    }

    private void YellowButton()
    {
        Mediator.NotifyColleagues("ShowDetails", true);
        Mediator.NotifyColleagues("SetSelectedFruit", ListOfDataModel.FruitYellow);
    }

    #endregion

    #region ListOfDataViewModel Constructor

    public ListOfDataViewModel() 
    {
        ListOfDataModel = new ListOfDataModel();
    }

    #endregion
}

`Views`

`DataDetailsView`

<ResourceDictionary xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
                xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
                xmlns:ViewModels="clr-namespace:CommunicateWithVM.ViewModels">

    <BooleanToVisibilityConverter x:Key="BooleanToVisibilityConverter" />

    <DataTemplate x:Key="DataDetailsView" DataType="{x:Type ViewModels:DataDetailsViewModel}">
        <StackPanel Width="200" 
                    Background="AliceBlue"
                    HorizontalAlignment="Right"
                    Visibility="{Binding Path=DataDetailsModel.IsVisible, 
                                         Converter={StaticResource BooleanToVisibilityConverter}}">

            <TextBlock Text="Fruit: " />
            <TextBlock Text="{Binding Path=DataDetailsModel.SelectedFruit}" />
        </StackPanel>
    </DataTemplate>    
</ResourceDictionary>

`ListOfDataView`

<ResourceDictionary xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
                    xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
                    xmlns:ViewModels="clr-namespace:CommunicateWithVM.ViewModels">

    <DataTemplate x:Key="ListOfDataView" DataType="{x:Type ViewModels:ListOfDataViewModel}">
        <StackPanel Width="200" 
                    Background="Azure"
                    HorizontalAlignment="Left">

            <Button Uid="{Binding Path=ListOfDataModel.FruitGreen}"
                    Content="GreenButton"
                    Command="{Binding Path=GreenButtonCommand}" />

            <Button Uid="{Binding Path=ListOfDataModel.FruitYellow}"
                    Content="YellowButton" 
                    Command="{Binding Path=YellowButtonCommand}" />
        </StackPanel>
    </DataTemplate>    
</ResourceDictionary>

This project is available at this link.

Problem

I have `MainWindow` and `AddEdit` `UserControl`. Inside `MainWindow` I render this AddEdit like `<Views:AddEditData />`, previous this namespace is added to Window element: ``` xmlns:Views="clr-namespace:MyProject.WPF.Views" +++++++++++++++ ++++++++++++++++ ListOfData + + DataDetails + + + + DataOne + + Name: txtBox1+ DataTwo + + + DataThree + + + + + Save data + +++++++++++++++ ++++++++++++++++ ``` When user selects data on left side (DataTwo for example) I want to display it's properties (for simplicity only Name property) inside AddEdit user control (DataDetails panel). Since this `UserControl` is stored separately from the MainWindow should I use same MainWindowViewModel and same datacontext or should I create separated ViewModel for AddEdit `UserControl`? Hopefully this sounds clear, if not please ask for details.

Original source

Related problems