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






3.25/5 (7投票s)
本文讨论了一个使用 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/