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.