65.9K
CodeProject 正在变化。 阅读更多。
Home

最简单的 MVVM 教程 -WPF MVVM 快速回顾

starIconstarIconstarIcon
emptyStarIcon
starIcon
emptyStarIcon

3.25/5 (7投票s)

2016年4月10日

CPOL

7分钟阅读

viewsIcon

44826

downloadIcon

701

本文讨论了一个使用 WPF MVVM 设计模式构建的简单应用程序,并解释了命令绑定和数据绑定的基本原理。

引言

本文解释了 WPF MVVM 设计模式的基本概念,例如 DataContext、数据绑定、CommandBinding 等。我将在此解释的示例将结构清晰,以说明模型 (Model)、视图 (View) 和视图模型 (ViewModel) 是什么。

本文不深入探讨 MVVM 概念,但肯定能帮助您入门/复习,并且您将能够独立创建简单的 WPF 应用程序。我假设您具备 C# 和 .NET 的基础知识。

背景

我将提取一些关键概念,并以非常直接的方式来解释 MVVM 及其实现方式。

什么是松耦合系统设计?

耦合是指一个类对另一个类的依赖。在紧耦合系统中,一个类的更改会迫使另一个类也进行更改,从而使系统难以维护并降低组件的可重用性。为了实现稳定且维护成本低的应用程序,需要实现松耦合。

什么是 MVVM?

MVVM 是 WPF、Silverlight 和 Windows 8 开发中的核心概念。

模型-视图分离的想法在业界已经存在了大约 25 年。

一个设计良好的应用程序易于开发、测试和维护,但要创建这样的应用程序,需要某种程度的分离和封装方法。一种常见的方法是将 UI 与业务逻辑分开,使它们松耦合。这使您可以在不更改业务代码逻辑的情况下灵活地更改 UI。

模型-视图-视图模型 (Model-View-ViewModel) 是一种应用程序设计模式,它允许开发人员完全将其业务逻辑与 UI 依赖项分开。这还使应用程序更加清晰易于测试。

模型

模型 (Model) 代表应用程序中使用的实体类。它代表我们将处理的实际数据,例如联系人(联系人姓名、地址、电子邮件、电话号码等)。模型包含信息以及业务和验证逻辑。它不负责任何与 UI 相关的更改,例如格式化文本或使按钮看起来更漂亮。业务逻辑封装在作用于模型的其他类中。这并非总是如此。模型可能包含验证。

视图

视图 (View) 代表 UI(窗口、页面和用户控件),而 ViewModel 则公开视图绑定到的命令和可绑定数据。

ViewModel

ViewModel 没有对 Views 的引用——它只持有对 Model 的引用。用户与 View 的交互会触发 ViewModel 中的命令,ViewModel 中的更新会通过 Binding 传播到 View。它有助于维护 View 的状态,并响应 View 上的操作来操作 Model。

使用代码

实现一个简单的 MVVM 应用程序。

让我们通过创建具有以下结构的解决方案开始

我倾向于使用这种结构,因为它更容易理解。

在 Views 文件夹中,我们将放置 Views,即与 UI 相关的 xaml 文件。

在 Model 文件夹中,我们将放置实体类。

在 ViewModel 中,我们将放置 Views 的 ViewModel 类。一个 View 对应一个 ViewModel,但一个 ViewModel 可以有多个 View 使用它。

Helper 文件夹将包含 Command 类和其他有用的辅助类。

本文的最终产品将是一个简单的应用程序,它在一个树状视图中显示所选文件夹中的所有目录和文件,您可以在其中导航并打开文件/文件夹。它看起来如下

点击打开直接打开文件或文件夹。

模型

Model 将包含以下类

GenericRecord 类

此类将作为基类,实现 INotifyPropertyChanged 接口,并且还可以包含可以在派生类中重写的虚拟成员。我们将此类继承到我们的模型类中。

Drive 类

  • Drive 类具有以下属性
    • DriveName
    • ObservableCollection of Folders
    • ObservableCollection of Files

Folder 类

  • Folder 类具有以下属性
    • FolderName
    • FolderPath
    • ObservableCollection of Folders
    • ObservableCollection of Files

File 类

  • File 类具有以下属性
    • FileName
    • FilePath

GenericRecord.cs

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.ComponentModel;

namespace EasyNavigate.Model
{
    class GenericRecord : INotifyPropertyChanged
    {
        internal void RaisePropertyChanged(string prop)
        {
            if (PropertyChanged != null) { PropertyChanged(this, new PropertyChangedEventArgs(prop)); }
        }
        public event PropertyChangedEventHandler PropertyChanged;
    }
} 

Drive.cs

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.ComponentModel;
using System.Collections.ObjectModel;

namespace EasyNavigate.Model
{
    class Drive : GenericRecord
    {
        string _DriveName;

        public string DriveName
        {
            get { return _DriveName; }
            set { _DriveName = value; RaisePropertyChanged("DriveName"); }
        }

        private ObservableCollection<Folder> _Folders;

        public ObservableCollection<Folder> Folders
        {
            get { return _Folders; }
            set { _Folders = value; RaisePropertyChanged("Folders"); }
        }

        private ObservableCollection<Model.File> _Files;

        public ObservableCollection<Model.File> Files
        {
            get { return _Files; }
            set { _Files = value; RaisePropertyChanged("Files"); }
        }
    }
} 

Folder.cs

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.ComponentModel;
using System.Collections.ObjectModel;

namespace EasyNavigate.Model
{
    class Folder : GenericRecord
    {
        string _FolderName;

        public string FolderName
        {
            get { return _FolderName; }
            set { _FolderName = value; RaisePropertyChanged("FolderName"); }
        }

        string _FolderPath;

        public string FolderPath
        {
            get { return _FolderPath; }
            set { _FolderPath = value; RaisePropertyChanged("FolderPath"); }
        }

        private ObservableCollection<Folder> _Folders;

        public ObservableCollection<Folder> Folders
        {
            get { return _Folders; }
            set { _Folders = value; RaisePropertyChanged("Folders"); }
        }

        private ObservableCollection<Model.File> _Files;

        public ObservableCollection<Model.File> Files
        {
            get { return _Files; }
            set { _Files = value; RaisePropertyChanged("Files"); }
        }
    }
} 

File.cs

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.ComponentModel;

namespace EasyNavigate.Model
{
    class File : GenericRecord
    {
        string _FileName;

        public string FileName
        {
            get { return _FileName; }
            set { _FileName = value; RaisePropertyChanged("FileName"); }
        }

        string _FilePath;

        public string FilePath
        {
            get { return _FilePath; }
            set { _FilePath = value; RaisePropertyChanged("FilePath"); }
        }
    }
} 

助手

RelayCommand 类实现 ICommand 接口用于命令处理。这个经典的 WPF 概念允许您绑定命令以执行操作,例如按钮单击。您将不会编写按钮单击事件的粘合代码,而是将单击时要执行的操作绑定到某个应执行的命令。

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Windows.Input;

namespace EasyNavigate.Helpers
{
    public class RelayCommand : ICommand
    {
        #region Fields

        readonly Action<object> _execute;
        readonly Predicate<object> _canExecute;

        #endregion // Fields

        #region Constructors

        public RelayCommand(Action<object> execute)
            : this(execute, null)
        {
        }

        public RelayCommand(Action<object> execute, Predicate<object> canExecute)
        {
            if (execute == null)
                throw new ArgumentNullException("execute");

            _execute = execute;
            _canExecute = canExecute;
        }
        #endregion // Constructors

        #region ICommand Members

        public bool CanExecute(object parameter)
        {
            return _canExecute == null ? true : _canExecute(parameter);
        }

        public event EventHandler CanExecuteChanged
        {
            add { CommandManager.RequerySuggested += value; }
            remove { CommandManager.RequerySuggested -= value; }
        }

        public void Execute(object parameter)
        {
            _execute(parameter);
        }

        #endregion // ICommand Members
    }
}

视图

窗口的 DataContext 通过 UI 本身设置为 ViewModelMainWindow 类,如下所示

<Window.DataContext>

<vm:ViewModelMainWindow/>

</Window.DataContext>

您也可以在 MainWindow 构造函数内的代码隐藏中进行设置,如下所示

public MainWindow()

{

            InitializeComponent();

            this.DataContext = new ViewModelMainWindow();

            grpBoxOpenFolderOrFile.Visibility = Visibility.Collapsed;

}

MainWindow.xaml

UI 显示 4 个部分

  • 左侧的可用逻辑驱动器下拉列表
  • 选择驱动器后,它将在右侧的下一部分显示所有可用的文件夹
  • 第三部分将以树状视图格式显示所选文件夹中的所有文件和文件夹
  • 底部的第四部分将显示一个文本框和一个“打开”按钮

代码隐藏,即 MainWindow.xaml.cs

代码隐藏将处理 ComboBox 的 SelectionChanged 事件

  • cmb_Drives_SelectionChanged
  • cmbFolder_SelectionChanged

“打开”按钮绑定到 OpenSelectedItemCommand

treeViewMenu 的 SelectedItemChanged 事件

  • trvMenu_SelectedItemChanged

这些事件处理程序将调用代码隐藏文件中编写的适当逻辑,使用 DataContext。

以下是 UI 的 xaml 文件。驱动器组合框绑定到 ViewModelMainWindow 类中定义的 CollecDrive 集合,文件夹组合框绑定到 CollecFolder 集合。

文件夹详细信息部分是一个树状视图,其项绑定到 Items 集合。这将根据选定的驱动器和选定的文件夹进行填充。

<Window x:Class="EasyNavigate.MainWindow"
        xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
        xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
        xmlns:vm="clr-namespace:EasyNavigate.ViewModel"
        Title="Easy Navigate v1.0" Height="550" Width="650">
   
    <Grid>
        <Grid.RowDefinitions>
            <RowDefinition Height="Auto"/>
            <RowDefinition Height="Auto"/>
            <RowDefinition Height="Auto" />
        </Grid.RowDefinitions>
        <Grid.ColumnDefinitions>
            <ColumnDefinition/>
            <ColumnDefinition/>
        </Grid.ColumnDefinitions>

        <GroupBox Margin="12,10,12,12" Header="Drive" HorizontalAlignment="Left" VerticalAlignment="Top" Height="50" Width="251" >
            <StackPanel Orientation="Horizontal" HorizontalAlignment="Center">
                <TextBlock FontWeight="Bold" Text="Available Logical Drive(s):"/>
                <ComboBox x:Name="cmb_Drives" ItemsSource="{Binding CollecDrive}" SelectedItem="{Binding SelectedDrive}" DisplayMemberPath="DriveName" Margin="5,0,0,5" VerticalAlignment="Top" Height="22" Width="45" SelectionChanged="cmb_Drives_SelectionChanged" />
            </StackPanel>
        </GroupBox>

        <GroupBox Margin="12,10,12,12" Header="Folder" HorizontalAlignment="Right" VerticalAlignment="Top" Height="50" Width="251" Grid.Row="0" Grid.Column="1" Visibility="{Binding IsFolderGrpBxVisible}">
            <StackPanel Orientation="Horizontal" HorizontalAlignment="Center">
                <TextBlock FontWeight="Bold" Text="Select a Folder:"/>
                <ComboBox x:Name="cmbFolder" ItemsSource="{Binding CollecFolder}" SelectedItem="{Binding SelectedFolder}" DisplayMemberPath="FolderName"  Margin="5,0,0,5" VerticalAlignment="Top" Height="22" Width="126" SelectionChanged="cmbFolder_SelectionChanged"/>
            </StackPanel>
        </GroupBox>

        <GroupBox x:Name="grpBoxFolderViewDetails" Margin="12,10,12,12" Header="Folder Details" HorizontalAlignment="Left"  Height="300" Width="600" VerticalAlignment="Top" Grid.Row="1" Grid.ColumnSpan="2" Visibility="{Binding IsFolderDetailsGrpBxVisible}" >
                <TreeView Name="trvMenu" SelectedItemChanged="trvMenu_SelectedItemChanged">
                    <TreeView.ItemTemplate>
                        <HierarchicalDataTemplate DataType="{x:Type vm:MenuItem}" ItemsSource="{Binding Items}">
                            <TextBlock Text="{Binding Title}"/>
                        </HierarchicalDataTemplate>
                    </TreeView.ItemTemplate>
                </TreeView>
        </GroupBox>

        <GroupBox x:Name="grpBoxOpenFolderOrFile" Margin="12,10,12,12" Header="Open File/Folder" HorizontalAlignment="Left"  VerticalAlignment="Top" Height="80" Width="380" Grid.Row="2" Grid.ColumnSpan="2" Visibility="{Binding IsFolderDetailsGrpBxVisible}" >
            <StackPanel Orientation="Horizontal">
            <TextBox x:Name="txtSelectedItemPath" Text="{Binding SelectedItemPath}" Margin="5,5,5,5" Height="30" Width="300"/>
                <Button x:Name="btnOpen" Content="Open" Command="{Binding OpenSelectedItemCommand}" Margin="5,5,5,5" Width="50" Height="25"/>
            </StackPanel>
        </GroupBox>
    </Grid>
</Window>

MainWindow.xaml.cs

此类中的 cmb_Drives_SelectionChanged 事件将处理从下拉列表中选择驱动器时发生的情况。一旦您从下拉列表中选择了一个驱动器,该事件将调用 ViewModel 中的 HandleCmbDriveSelectionChanged 方法,该方法将返回所选驱动器中的所有文件夹和文件,并填充 CollecFolder 集合,该集合使用 RaisePropertyChanged 通知 UI。

cmbFolder_SelectionChanged 事件将处理从下拉列表中选择文件夹时发生的情况。

一旦您从下拉列表中选择了一个文件夹,该事件将调用 ViewModel 中定义的 HandleCmbFolderSelectionChanged 方法。此方法将返回一个选定的文件夹,该文件夹包含子文件夹的集合(迭代方式)以及所选文件夹中的所有文件。

基于选定的文件夹,我们将使用 BuildMenuItem 方法为树状视图构建菜单项,并将该菜单项添加到树状视图中。

选择树状视图上的项将调用 trvMenu_SelectedItemChanged,通过它我们将在 ViewModel 中使用 getPathFromParentItem 方法获取系统中选定项的路径,并将其绑定到文本框的 Text 属性。

注意,UI 相关的操作在代码隐藏中处理,但从那里它会转到 ViewModel 来处理所有逻辑。

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Data;
using System.Windows.Documents;
using System.Windows.Input;
using System.Windows.Media;
using System.Windows.Media.Imaging;
using System.Windows.Navigation;
using System.Windows.Shapes;
using EasyNavigate.ViewModel;
using System.Collections.ObjectModel;
using EasyNavigate.Model;
using System.Diagnostics;

namespace EasyNavigate
{
    /// <summary>
    /// Interaction logic for MainWindow.xaml
    /// </summary>
    public partial class MainWindow : Window
    {
        public MainWindow()
        {
            InitializeComponent();
            this.DataContext = new ViewModelMainWindow();
            grpBoxOpenFolderOrFile.Visibility = Visibility.Collapsed;
        }

        private void cmb_Drives_SelectionChanged(object sender, SelectionChangedEventArgs e)
        {
            var vm=this.DataContext as ViewModelMainWindow;
            vm.HandleCmbDriveSelectionChanged();
            grpBoxOpenFolderOrFile.Visibility = Visibility.Collapsed;
        }

        private void cmbFolder_SelectionChanged(object sender, SelectionChangedEventArgs e)
        {
            trvMenu.Items.Clear();
            var vm = this.DataContext as ViewModelMainWindow;
            Folder folderContent=vm.HandleCmbFolderSelectionChanged();
            if (folderContent != null)
            {
                EasyNavigate.ViewModel.MenuItem root = vm.BuildMenuItem(folderContent,null);
                
                trvMenu.Items.Add(root);
                grpBoxFolderViewDetails.Visibility = Visibility.Visible;
            }
            else
            {
                grpBoxFolderViewDetails.Visibility = Visibility.Collapsed;
            }
            grpBoxOpenFolderOrFile.Visibility = Visibility.Collapsed;
        }

        private void trvMenu_SelectedItemChanged(object sender, RoutedPropertyChangedEventArgs<object> e)
        {
            var vm = this.DataContext as ViewModelMainWindow;
            vm.SelectedItem = (sender as TreeView).SelectedItem as EasyNavigate.ViewModel.MenuItem;

            if (vm.SelectedItem != null)
            {
                vm.SelectedItemPath = vm.SelectedDrive.DriveName + ":\\" + vm.getPathFromParentItem(vm.SelectedItem);//"
                grpBoxOpenFolderOrFile.Visibility = Visibility.Visible;
                
            }
        }

    }
}

ViewModel

ViewModelBase.cs

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.ComponentModel;

namespace EasyNavigate.ViewModel
{
    class ViewModelBase : INotifyPropertyChanged
    {
        //basic ViewModelBase
        internal void RaisePropertyChanged(string prop)
        {
            if (PropertyChanged != null) { PropertyChanged(this, new PropertyChangedEventArgs(prop)); }
        }
        public event PropertyChangedEventHandler PropertyChanged;
    }
}

ViewModelMain.cs

此类包含我们将公开并绑定到 View 的属性。

此类包含当用户在 UI 上执行某些操作时将执行的逻辑。这里的代码不言自明。请您看一遍。

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;

using System.Collections.ObjectModel;
using System.Windows;
using System.Windows.Controls;
using System.IO;
using EasyNavigate.Model;
using EasyNavigate.Helpers;
using System.Diagnostics;

namespace EasyNavigate.ViewModel
{
    class ViewModelMainWindow: ViewModelBase
    {
        public RelayCommand OpenSelectedItemCommand { get; set; }

        ObservableCollection<Drive> _CollecDrive;

        public ObservableCollection<Drive> CollecDrive
        {
            get { return _CollecDrive; }
            set { _CollecDrive = value; RaisePropertyChanged("CollecDrive"); }
        }

        ObservableCollection<Folder> _CollecFolder;

        public ObservableCollection<Folder> CollecFolder
        {
            get { return _CollecFolder; }
            set { _CollecFolder = value; RaisePropertyChanged("CollecFolder"); }
        }

        Visibility _IsFolderGrpBxVisible;

        public Visibility IsFolderGrpBxVisible
        {
            get { return _IsFolderGrpBxVisible; }
            set { _IsFolderGrpBxVisible = value; RaisePropertyChanged("IsFolderGrpBxVisible"); }
        }

        private Visibility _IsFolderDetailsGrpBxVisible;

        public Visibility IsFolderDetailsGrpBxVisible
        {
            get { return _IsFolderDetailsGrpBxVisible; }
            set { _IsFolderDetailsGrpBxVisible = value; RaisePropertyChanged("IsFolderDetailsGrpBxVisible"); }
        }

        private Drive _SelectedDrive;

        public Drive SelectedDrive
        {
            get { return _SelectedDrive; }
            set { _SelectedDrive = value; RaisePropertyChanged("SelectedDrive"); }
        }

        private Folder _SelectedFolder;

        public Folder SelectedFolder
        {
            get { return _SelectedFolder; }
            set { _SelectedFolder = value; RaisePropertyChanged("SelectedFolder"); }
        }

        public ObservableCollection<Drive> getAllDrivesInSystem()
        {
            string[] arrDrives = System.IO.Directory.GetLogicalDrives();

            ObservableCollection<Drive> drives = new ObservableCollection<Drive>();

            if (arrDrives != null && arrDrives.Length > 0)
            {
                foreach (string s in arrDrives)
                {
                    string temp = null;

                    if (s != null && s.Contains(":") && !s.Contains("C"))
                    {
                        int indexOfColon = s.IndexOf(":");
                        temp = s.Remove(indexOfColon);

                        Drive newDrive = new Drive();
                        newDrive.DriveName = temp;

                        ObservableCollection<Folder> foldersInDrive= getAllFoldersInDriveOrFolder(newDrive,null);
                        newDrive.Folders=foldersInDrive;

                        ObservableCollection<EasyNavigate.Model.File> filesInDrive= getAllFilesInDriveOrFolder(newDrive,null);
                        newDrive.Files = filesInDrive;

                        drives.Add(newDrive);
                    }
                }
            }
            return drives;
        }

        private ObservableCollection<Model.File> getAllFilesInDriveOrFolder(Drive drive,Folder folder)
        {
            ObservableCollection<Model.File> files = new ObservableCollection<Model.File>();

            if (folder == null)
            {
                System.IO.DriveInfo di = new System.IO.DriveInfo(drive.DriveName + ":\\");//"
                System.IO.DirectoryInfo dirInfo = di != null ? di.RootDirectory : null;

                DirectoryInfo[] dirInfoFolders = dirInfo != null ? dirInfo.GetDirectories() : null;

                FileInfo[] fileInfo = dirInfo != null ? dirInfo.GetFiles() : null;

                if (fileInfo != null && fileInfo.Length > 0)
                {
                    foreach (FileInfo item in fileInfo)
                    {
                        FileAttributes attributes = item.Attributes;

                        string attrNames = Convert.ToString(attributes);

                        if (attrNames != null && !attrNames.Contains("Hidden"))
                        {
                            Model.File file = new Model.File();
                            file.FileName = item.Name;
                            files.Add(file);
                        }
                    }
                }
            }
            else
            {
                string[] filesInFolder = Directory.GetFiles(folder.FolderPath);

                if (filesInFolder != null && filesInFolder.Length > 0)
                {
                    foreach (string item in filesInFolder)
                    {
                        int folderPathLength = folder.FolderPath.Length;

                        string fileInFolder = null;

                        fileInFolder = item.Remove(0, folderPathLength + 1);

                        Model.File file = new Model.File();

                        file.FileName = fileInFolder;
                        file.FilePath = item;

                        files.Add(file);
                    }
                }
            }

            return files;
        }

        public ObservableCollection<Folder> getAllFoldersInDriveOrFolder(Drive drive ,Folder folder)
        {
            ObservableCollection<Folder> folders = new ObservableCollection<Folder>();

            if (folder == null)
            {
                System.IO.DriveInfo di = new System.IO.DriveInfo(drive.DriveName + ":\\");//"
                System.IO.DirectoryInfo dirInfo = di != null ? di.RootDirectory : null;

                DirectoryInfo[] dirInfoFolders = dirInfo != null ? dirInfo.GetDirectories() : null;

                FileInfo[] fileInfo = dirInfo != null ? dirInfo.GetFiles() : null;
                if (dirInfoFolders != null && dirInfoFolders.Length > 0)
                {
                    foreach (DirectoryInfo item in dirInfoFolders)
                    {
                        FileAttributes attributes = item.Attributes;

                        string attrNames = Convert.ToString(attributes);

                        if (attrNames != null && !attrNames.Contains("Hidden"))
                        {
                            Folder newFolder = new Folder();
                            newFolder.FolderName = item.Name;
                            newFolder.FolderPath=drive.DriveName+":\\"+ newFolder.FolderName;//"

                            ObservableCollection<Folder> subFolders = getAllFoldersInDriveOrFolder(drive, newFolder);
                            newFolder.Folders = subFolders;

                            ObservableCollection<Model.File> files = getAllFilesInDriveOrFolder(drive, newFolder);
                            newFolder.Files = files;
                            folders.Add(newFolder);
                        }
                    }
                }
            }
            else
            {
                string [] subFolders = Directory.GetDirectories(folder.FolderPath);
                Folder subfolder= new Folder();
                if(subFolders!=null && subFolders.Length>0)
                {
                    foreach (string item in subFolders)
                    {
                        int folderPathLength = folder.FolderPath.Length;

                        string fldr=null;

                        fldr = item.Remove(0, folderPathLength+1);

                        subfolder= new Folder();

                        subfolder.FolderName = fldr;
                        subfolder.FolderPath = item;
                        subfolder.Folders=getAllFoldersInDriveOrFolder(drive,subfolder);
                        subfolder.Files = getAllFilesInDriveOrFolder(drive,subfolder);

                        folders.Add(subfolder);
                    }
                }
               
            }

            return (folders != null && folders.Count > 0) ? folders : null;
        }

        public ViewModelMainWindow()
        {
            IsFolderDetailsGrpBxVisible = Visibility.Collapsed;

            CollecDrive = getAllDrivesInSystem();

            OpenSelectedItemCommand = new RelayCommand(OpenSelectedItem);
        }

        public void HandleCmbDriveSelectionChanged()
        {
            CollecFolder = getAllFoldersInDriveOrFolder(SelectedDrive,null);
        }

        public Folder HandleCmbFolderSelectionChanged()
        {
            if (SelectedFolder != null)
            {
                IsFolderDetailsGrpBxVisible = Visibility.Visible;
            }

            else
            {
                IsFolderDetailsGrpBxVisible = Visibility.Collapsed;
            }

            return SelectedFolder;
        }

        private MenuItem _SelectedItem;

        public MenuItem SelectedItem
        {
            get { return _SelectedItem; }
            set { _SelectedItem = value; }
        }

        public ViewModel.MenuItem BuildMenuItem(Folder folderContent, MenuItem parentMenuItem)
        {
            EasyNavigate.ViewModel.MenuItem root = new EasyNavigate.ViewModel.MenuItem() { Title = folderContent.FolderName, parentItem = parentMenuItem };
            
            ObservableCollection<Folder> foldersInFolder = folderContent.Folders;
            ObservableCollection<EasyNavigate.Model.File> filesInFolder = folderContent.Files;

            if (foldersInFolder != null && foldersInFolder.Count > 0 && ((filesInFolder == null) || filesInFolder.Count == 0))
            {
                for (int i = 0; i < foldersInFolder.Count; i++)
                {
                    EasyNavigate.ViewModel.MenuItem item = BuildMenuItem(foldersInFolder[i], root);
                    root.Items.Add(item);
                }
            }

            if ((foldersInFolder == null || foldersInFolder.Count == 0) && filesInFolder != null && filesInFolder.Count > 0)
            {
                for (int i = 0; i < filesInFolder.Count; i++)
                {
                    MenuItem item = BuildMenuItem(filesInFolder[i], root);
                    root.Items.Add(item);
                }
            }

            if (foldersInFolder != null && foldersInFolder.Count > 0 && filesInFolder != null && filesInFolder.Count > 0)
            {
                bool itemsAdded = false;
                for (int i = 0; (i < foldersInFolder.Count + filesInFolder.Count && !itemsAdded); i++)
                {
                    if (i >= foldersInFolder.Count)
                    {
                        for (int j = 0; j < filesInFolder.Count; j++)
                        {
                            ViewModel.MenuItem item = BuildMenuItem(filesInFolder[j], root);
                            root.Items.Add(item);
                            itemsAdded = true;
                        }
                    }
                    else
                    {
                        EasyNavigate.ViewModel.MenuItem item = BuildMenuItem(foldersInFolder[i],root);
                        root.Items.Add(item);
                    }
                }
            }
            return root;
        }

        public ViewModel.MenuItem BuildMenuItem(EasyNavigate.Model.File file, MenuItem parentMenuItem)
        {
            EasyNavigate.ViewModel.MenuItem root = new EasyNavigate.ViewModel.MenuItem() { Title = file.FileName, parentItem = parentMenuItem };

            return root;

        }

        public string getPathFromParentItem(ViewModel.MenuItem menuItem)
        {
            string path = "";

            if (menuItem != null && menuItem.parentItem == null)
            {
                path = menuItem.Title;
                return path;
            }
            else
            {
                path = getPathFromParentItem(menuItem.parentItem) +"\\"+ menuItem.Title;//"
            }
            return path;
        }

        private string _SelectedItemPath;

        public string SelectedItemPath
        {
            get { return _SelectedItemPath; }
            set { _SelectedItemPath = value; RaisePropertyChanged("SelectedItemPath"); }
        }

        void OpenSelectedItem(object parameter)
        {
            if (SelectedItemPath != null)
            {
                Process.Start("explorer.exe", SelectedItemPath);
            }
        }
    }
}

MenuItem.cs

此类是为我们自己的目的而定义的类,用于构建菜单项控件。

每个菜单项都将通过 parentItem 属性指向其父菜单项。对于最顶层的菜单项,它将为 null。

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Collections.ObjectModel;

namespace EasyNavigate.ViewModel
{
    public class MenuItem
    {
        public MenuItem()
        {
            this.Items = new ObservableCollection<MenuItem>();
        }

        public string Title { get; set; }

        public ObservableCollection<MenuItem> Items { get; set; }

        public MenuItem parentItem { get; set; }
    }
}

关注点

我从学习的角度构建了这个示例应用程序。在实现此应用程序时,我了解了 xaml 语法、TreeView、MenuItem、Command binding、data binding 等。

如果您对本文有任何疑问,请随时提出。

如果您发现任何错误/bug 或任何可以改进的地方,请发表评论。非常欢迎提出建议。

保持快乐,不断学习。

历史

在 Model 中添加了一个基类。

附带完整的示例项目。

已添加参考。

参考文献

https://blogs.msdn.microsoft.com/jerrynixon/2012/10/12/xaml-binding-basics-101/

http://social.technet.microsoft.com/wiki/contents/articles/13536.easy-mvvm-examples-in-extreme-detail.aspx

https://blogs.msdn.microsoft.com/dancre/2006/10/11/datamodel-view-viewmodel-pattern-series/

© . All rights reserved.