我有MainWindow
和AddEdit
UserControl
。在MainWindow
内部,我将此AddEdit呈现为<Views:AddEditData />
,之前此命名空间已添加到Window元素中:
xmlns:Views="clr-namespace:MyProject.WPF.Views"
+++++++++++++++ ++++++++++++++++
ListOfData + + DataDetails +
+ + +
DataOne + + Name: txtBox1+
DataTwo + + +
DataThree + + +
+ + Save data +
+++++++++++++++ ++++++++++++++++
当用户选择左侧的数据时(例如DataTwo)我想在AddEdit用户控件(DataDetails面板)中显示它的属性(为简单起见,只有Name属性)。
由于这个UserControl
与MainWindow分开存储,我应该使用相同的MainWindowViewModel和相同的datacontext,还是应该为AddEdit UserControl
创建单独的ViewModel?
希望这听起来很清楚,如果不是,请询问详情。
答案 0 :(得分:5)
<强> Part 1. Display the properties of the control in MVVM
强>
正如我在评论中所说:
在MVVM中,ViewModel不应该知道所找到的控件。在这种情况下,请使用附加行为或在View
中保留相同的旁边逻辑
ViewModel
与View
没有直接关联,所以只需参考控件的名称就不对了。最好在Model
中设置一个属性,并通过View
将其绑定到ViewModel
,但属性Name
不支持Binding(引自{{ 3}}):
数据绑定名称在技术上是可行的,但这是一种非常罕见的情况,因为数据绑定名称无法满足属性的主要用途:为代码隐藏提供标识符连接点。
所以我建议使用Tag
属性或Uid
。在我的示例中(给出一个下面的内容),我将Uid
属性用于这些目的。
<强> Part 2. Communication via ViewModels (pattern Mediator)
强>
Mediator 模式有几个实施例,但我最喜欢 XAML Guy
的实现,它简单明了 - {{3} }。
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);
}
}
}
为了展示他的作品,我创建了一个小例子,其中包含两个Views
,每个都有自己的ViewModel
和Model
。
项目结构如下所示:
Output
单击Button时,ListOfData ViewModel
通过mediator与DataDetails ViewModel
进行通信,因此:
Mediator.NotifyColleagues("ShowDetails", true);
Mediator.NotifyColleagues("SetSelectedFruit", ListOfDataModel.FruitGreen);
与属性交互的所有过程都必须像这样注册ViewModel
:
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);
}
在示例中,我使用DataTemplate
代替UserControl
。以下是该项目的主要部分:
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>
此项目位于此MSDN。
答案 1 :(得分:3)
由于UserControl是单独维护的,而不是Window内容的一部分。我建议将 单独的ViewModel 。
拥有单独的ViewModel的好处:
可重用性 - 将来如果您想对与UserControl相关的数据进行一些更改(可能是某些逻辑更改),您只需要去您的ViewModel并更新它,它将反映在所有窗口中。您不必担心转到每个Window的视图模型并更新代码。
可测试性 - 如果您想测试与您的控件相关的逻辑(数据部分我在这里说话而不是查看部分),您可以单独编写它。无需担心测试Window视图模型代码。
松散耦合 - 不止一个人可以孤立地工作。假设一个开发人员必须更新一些与主窗口相关的代码,而其他人必须更新一些与UserControl相关的代码。有了一个ViewModel,就会有一些重叠,它们不能孤立地工作,因为在他/她可以在ViewModel中插入他/她的代码之前,依赖于其他人来完成它的工作。
同时检查here以查看不同ViewModel之间的通信,因为您可能需要在窗口视图模型和用户控制视图模型之间进行通信,以便在左侧窗口中传递所选数据。