【问题标题】:One ViewModel for UserControl and Window or separate ViewModels一个用于 UserControl 和 Window 的 ViewModel 或单独的 ViewModel
【发布时间】:2014-01-01 11:13:52
【问题描述】:

我有MainWindowAddEdit UserControl。在MainWindow 中,我像<Views:AddEditData /> 一样渲染这个AddEdit,之前这个命名空间被添加到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?

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

【问题讨论】:

  • 代替你,我会创建不同的ViewModels,因为它不能依赖于MainWindow。两个ViewModels 之间的通信将通过Mediator 模式完成,它易于使用且非常强大。你需要问自己另一个问题。在哪里访问控制,得到他的名字?在 MVVM 中(据我了解,您使用它),ViewModel 不应该知道位于的控件。在这种情况下,请使用附加的行为或在View 中保留相同的侧逻辑。如果您对模式Mediator 感兴趣,我可以在答案中描述它。
  • 请做,我很感兴趣。
  • 好的,我会的,时间也一样。

标签: c# .net wpf mvvm


【解决方案1】:

Part 1. Display the properties of the control in MVVM

正如我在 cmets 中所说的:

在 MVVM 中,ViewModel 不应该知道控件所在的位置。在这种情况下,使用附加的行为或在视图中保留相同的侧逻辑

ViewModel 不直接与View 关联,所以只引用控件的名称是不对的。最好在Model中设置一个属性,通过ViewModel绑定到View,但是Name属性不支持Binding(引用自MSDN):

数据绑定名称在技术上是可行的,但这种情况极为罕见,因为数据绑定名称不能满足该属性的主要预期目的:为代码隐藏提供标识符连接点。

所以我建议使用Tag 属性或Uid。在我的示例中(在下面给出),我将Uid 属性用于这些目的。

Part 2. Communication via ViewModels (pattern Mediator)

Mediator模式的实现有好几种,但我最喜欢XAML Guy的实现,简单明了-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);
        }
    }
}

为了展示他的工作,我创建了一个小例子,它由两个Views组成,每个都有自己的ViewModelModel

项目结构如下图:

Output

当您单击 Button 时,ListOfData ViewModel 通过中介与 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>

此项目可在此link 获得。

【讨论】:

    【解决方案2】:

    因为 UserControl 是单独维护的,而不是 Window 内容的一部分。我建议使用单独的 ViewModel

    拥有独立 ViewModel 的好处:

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

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

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

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

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 2016-11-17
      • 2016-07-29
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2013-08-08
      相关资源
      最近更新 更多