一个用于UserControl和Window的ViewModel或单独的ViewModel

时间:2014-01-01 11:13:52

标签: c# .net wpf mvvm

我有MainWindowAddEdit 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?

希望这听起来很清楚,如果不是,请询问详情。

2 个答案:

答案 0 :(得分:5)

<强> Part 1. Display the properties of the control in MVVM

正如我在评论中所说:

  

在MVVM中,ViewModel不应该知道所找到的控件。在这种情况下,请使用附加行为或在View

中保留相同的旁边逻辑

ViewModelView没有直接关联,所以只需参考控件的名称就不对了。最好在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,每个都有自己的ViewModelModel

项目结构如下所示:

Project structure

Output

Using example

单击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的好处:

  1. 可重用性 - 将来如果您想对与UserControl相关的数据进行一些更改(可能是某些逻辑更改),您只需要去您的ViewModel并更新它,它将反映在所有窗口中。您不必担心转到每个Window的视图模型并更新代码。

  2. 可测试性 - 如果您想测试与您的控件相关的逻辑(数据部分我在这里说话而不是查看部分),您可以单独编写它。无需担心测试Window视图模型代码。

  3. 松散耦合 - 不止一个人可以孤立地工作。假设一个开发人员必须更新一些与主窗口相关的代码,而其他人必须更新一些与UserControl相关的代码。有了一个ViewModel,就会有一些重叠,它们不能孤立地工作,因为在他/她可以在ViewModel中插入他/她的代码之前,依赖于其他人来完成它的工作。

  4. 同时检查here以查看不同ViewModel之间的通信,因为您可能需要在窗口视图模型和用户控制视图模型之间进行通信,以便在左侧窗口中传递所选数据。