MVVM - View模型中的UI相关代码 - 真正的关注点分离

时间:2014-04-16 14:35:03

标签: c# wpf xaml mvvm

我开始在现有的WPF c#应用程序中实现MVVM设计模式。我是全新的,以前从未使用过设计模式或依赖注入。我正在研究已经可用的框架并采用了MVVM灯。我将逻辑从视图移动到viewmodel。我在PopulateTestMenu中有很多与视图模型中的UI相关的代码。它还调用了事件处理程序。我该如何处理这个?

XAML 中我有:

<Window DataContext="{Binding Main, Source={StaticResource Locator}}">
    <Menu>
       <MenuItem Header="Load All History..." Command="{Binding LoadAllHistory}"> 

MainViewModel 类中,我有:

public ICommand LoadAllHistory { get; private set; }

public MainViewModel()
{            
    LoadAllHistory = new RelayCommand(() => LoadHistoryExecute(), () => true);
}

我从视图移至 viewmodel 的代码:

private void LoadHistoryExecute()
{
    try
    {
        OpenFileDialog ofd = new OpenFileDialog();
        ofd.Filter = "Test History File (*.xml)|*.xml";
        ofd.Title = "Open Test History";
        ofd.Multiselect = true;

        if (ofd.ShowDialog() == true)
        {
            ThreadPool.QueueUserWorkItem(LoadTestHistoryCallback, ofd.FileNames);
        }
    }
    catch
    {
       //some code
    }
}

private void LoadTestHistoryCallback(object state)
{
    try
    {
        string[] fileNames = (string[])state;

        foreach (string fileName in fileNames)
        {
            bool success = MyApp.Instance.ParseTestHistory(fileName);
            string status = success
                          ? String.Format("'{0}' loaded successfully.",      
               System.IO.Path.GetFileName(fileName))
                          : String.Format("Failed to load history from '{0}'.",    
               System.IO.Path.GetFileName(fileName));
            Dispatcher.CurrentDispatcher.DynamicInvoke(delegate()
            {
                Status = status;
            });
            PopulateTestMenu(new SortedList<int, int>());
        }
    }
    catch
    {
        //some code
    }
}

private void PopulateTestMenu(SortedList<int, int> indexes)
{
    try
    {
        _testMenuMutex.WaitOne();

        //Populate the Tests menu with the list of tests.
        Dispatcher.CurrentDispatcher.DynamicInvoke(delegate()
        {
            menuTests.Items.Clear();
            var checkEventHandler = new RoutedEventHandler(testMenuItem_Checked);
            bool added = false;

            if (MyApp.Instance.TestHistory != null && 
               MyApp.Instance.TestHistory.Count > 0)
            {
                List<ushort> subIds = new 
                   List<ushort>MyApp.Instance.TestHistory.Keys);

                foreach (ushort subId in subIds)
                {
                   MenuItem menuItem = null;
                    menuItem = new MenuItem();
                    menuItem.Header = subId.ToString().PadLeft(5, '0');**

                    MenuItem none = new MenuItem();
                    none.Header = "None";
                    none.IsCheckable = true;
                    none.IsChecked = true;
                    none.Checked += checkEventHandler;
                    none.Unchecked += checkEventHandler;

                    menuItem.Items.Add(none);

                    if (MyApp.Instance.TestHistory != null && 
                       MyApp.Instance.TestHistory.ContainsKey(subId))
                    {
                        var tests = MyApp.Instance.TestHistory[subId];

                        if (tests != null)
                        {
                            foreach (Test t in tests)
                            {
                                MenuItem item = new MenuItem();
                                item.IsCheckable = true;

                                string description = t.Description.Replace("\n", 
                                "\n".PadRight(34, ' '));
                                string header = abc;
                                item.Header = header;
                                item.DataContext = t;
                                item.Checked += checkEventHandler;
                                item.Unchecked += checkEventHandler;
                                menuItem.Items.Add(item);
                            }
                            if (tests.Count > 0)
                            {
                                menuTests.Items.Add(menuItem);
                                added = true;
                            }
                        }
                    }

                    // Carry over the previous selection.
                    if (indexes.ContainsKey(subId) && indexes[subId] > -1)
                    {                            ((MenuItem)menuItem.Items[indexes[subId]]).IsChecked = 
                         true;
                    }
                }
            }

2 个答案:

答案 0 :(得分:2)

我仍然想弄清楚你在问什么=)......

但是你混淆了一些东西......记住MVVM的一个核心概念是使viewmodel可测试并从viewmodel中删除所有与视图相关的代码。所以根本不依赖于WPF。因此,MenuItem看起来像WPF MenuItem,不应该在您的ViewModel中。

相反,你可以考虑制作一个MenuItemViewModel,它绑定到视图中的MenuItem。而且我可以看到ObservableCollection<MenuItemViewModel> TestMenu而不是您的排序列表。

在您的方法LoadTestHistoryCallback中,您可以MenuItemViewModel实现(可以通过DI完成)并将其添加到TestMenu集合中。 MenuItemViewModel可能具有status属性,可以从外部或内部分配。 (它还可以有一些额外的逻辑,嘿它是一个视图模型)。

然后,您可以在View中将其绑定到一个列表,其中的模板通过DataBinding表示MenuItem

<Menu DockPanel.Dock="Top" ItemsSource="{Binding Path=MenuItems}" />

因此,请记住ViewModel也可以包含ViewModelscollections viewmodel。 使用WPF的rich databinding api。 使用ObservebaleCollectionsProperties等可绑定属性,这些属性通过PropertyChanged通知进行了扩展。

HTH

PS:然后,您可以在ICommand中点击MenuItemViewModel并执行操作,或者更好地使用EventAggregatorMessenger来通知其他ViewModels。 ..(但那是另一个问题的故事=)......)

答案 1 :(得分:0)

您已经通过将代码移动到ViewModel来正确应用MVVM理论,但请记住,View应该只提供显​​示的“结构”。

显示的内容由ViewModel中的模型提供。

考虑到这一点,将ViewModel方法中的菜单部分分开并将它们放在View中,但保留Test对象创建部分(将ViewModel对象绑定到View结构就是它的内容)。

在PopulateTestMenu方法中,需要在View中指定菜单和菜单结构,而填充它们的数据需要在ViewModel中创建和格式化。

在视图中,您将相应的对象部分绑定到菜单结构,当模型绑定到视图时,ViewModel将自动用模型对象填充它。

查看代码,看来Test对象是ViewModel,需要在View中创建Menu和MenuItem结构,然后指定Test对象的特定属性与特定结构部分的绑定视图中的菜单。