以下是如何执行这种 MVVM 方式。
首先,编写视图模型类。在这里,我们有一个包含WatchedFile 实例集合的主视图模型,然后我们有WatchedFile 类本身。我还决定将Tag 设为一个类,而不仅仅是使用字符串。这让我们可以在 XAML 中编写数据模板,这些模板明确且专门用于显示 Tag 实例,而不是一般的字符串。 UI 充满了字符串。
如果您没有 sn-p,则通知属性编写起来很繁琐。 I have snippets(偷他们!他们没有被钉死!)。
一旦你有了这个,排序就没什么大不了的了。如果要对根级别项目进行排序,则为 WatchedFile。
SortDescriptions.Add(new SortDescription("Name", ListSortDirection.Ascending));
但我们将在下面的 XAML 中执行此操作。
序列化也很简单:只需让您的视图模型类可序列化即可。这里重要的是您的排序和序列化不必关心树视图项目模板中的内容。 StackPanels、GroupBoxes 等等——根本不重要,因为您的排序和序列化代码只处理您的数据类,而不是 UI 的东西。您可以从根本上更改数据模板中的视觉细节,而不必担心它会影响代码的任何其他部分。这就是 MVVM 的优点。
视图模型:
using System;
using System.Collections.Generic;
using System.Collections.ObjectModel;
using System.ComponentModel;
using System.Linq;
using System.Runtime.CompilerServices;
using System.Text;
using System.Threading.Tasks;
namespace WatchedFile.ViewModels
{
public class ViewModelBase : INotifyPropertyChanged
{
public event PropertyChangedEventHandler PropertyChanged;
protected void OnPropertyChanged([CallerMemberName] string propertyName = null)
{
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
}
}
public class WatchedFile : ViewModelBase
{
#region Name Property
private String _name = default(String);
public String Name
{
get { return _name; }
set
{
if (value != _name)
{
_name = value;
OnPropertyChanged();
}
}
}
#endregion Name Property
#region Path Property
private String _path = default(String);
public String Path
{
get { return _path; }
set
{
if (value != _path)
{
_path = value;
OnPropertyChanged();
}
}
}
#endregion Path Property
#region Tags Property
private ObservableCollection<Tag> _tags = new ObservableCollection<Tag>();
public ObservableCollection<Tag> Tags
{
get { return _tags; }
protected set
{
if (value != _tags)
{
_tags = value;
OnPropertyChanged();
}
}
}
#endregion Tags Property
}
public class Tag
{
public Tag(String value)
{
Value = value;
}
public String Value { get; private set; }
}
public class MainViewModel : ViewModelBase
{
public MainViewModel()
{
Populate();
}
public void Populate()
{
// Arbitrary test info, just for display.
WatchedFiles = new ObservableCollection<WatchedFile>
{
new WatchedFile() { Name = "foobar.txt", Path = "c:\\testfiles\\foobar.txt", Tags = { new Tag("Testfile"), new Tag("Text") } },
new WatchedFile() { Name = "bazfoo.txt", Path = "c:\\testfiles\\bazfoo.txt", Tags = { new Tag("Testfile"), new Tag("Text") } },
new WatchedFile() { Name = "whatever.xml", Path = "c:\\testfiles\\whatever.xml", Tags = { new Tag("Testfile"), new Tag("XML") } },
};
}
#region WatchedFiles Property
private ObservableCollection<WatchedFile> _watchedFiles = new ObservableCollection<WatchedFile>();
public ObservableCollection<WatchedFile> WatchedFiles
{
get { return _watchedFiles; }
protected set
{
if (value != _watchedFiles)
{
_watchedFiles = value;
OnPropertyChanged();
}
}
}
#endregion WatchedFiles Property
}
}
后面的代码。请注意,我只在向导给我的内容中添加了一行。
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
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;
namespace WatchedFile
{
/// <summary>
/// Interaction logic for MainWindow.xaml
/// </summary>
public partial class MainWindow : Window
{
public MainWindow()
{
InitializeComponent();
DataContext = new ViewModels.MainViewModel();
}
}
}
最后是 XAML:
<Window
x:Class="WatchedFile.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:scm="clr-namespace:System.ComponentModel;assembly=WindowsBase"
xmlns:local="clr-namespace:WatchedFile"
xmlns:vm="clr-namespace:WatchedFile.ViewModels"
mc:Ignorable="d"
Title="MainWindow" Height="350" Width="525">
<Window.Resources>
<CollectionViewSource
x:Key="SortedWatchedFiles"
Source="{Binding WatchedFiles}">
<CollectionViewSource.SortDescriptions>
<scm:SortDescription PropertyName="Name" Direction="Ascending" />
</CollectionViewSource.SortDescriptions>
</CollectionViewSource>
</Window.Resources>
<Grid>
<TreeView
ItemsSource="{Binding Source={StaticResource SortedWatchedFiles}}"
>
<TreeView.Resources>
<HierarchicalDataTemplate
DataType="{x:Type vm:WatchedFile}"
ItemsSource="{Binding Tags}"
>
<TextBlock
Text="{Binding Name}"
ToolTip="{Binding Path}"
/>
</HierarchicalDataTemplate>
<HierarchicalDataTemplate
DataType="{x:Type vm:Tag}"
>
<TextBlock
Text="{Binding Value}"
/>
</HierarchicalDataTemplate>
</TreeView.Resources>
</TreeView>
</Grid>
</Window>
XAML 不太明显。 TreeView.Resources 在TreeView 的任何子项的范围内。 HierarchicalDataTemplates 没有 x:Key 属性,这使得它们隐式。这意味着当TreeView 的项目被实例化时,每个根项目都将有一个WatchedFile 类实例用于其DataContext。由于WatchedFile 有一个隐式数据模板,它将用于填充其内容。 TreeView 是递归的,所以它使用HierarchicalDataTemplate 而不是常规的DataTemplate。 HierarchicalDataTemplate 添加了 ItemSource 属性,该属性告诉项目在其 DataContext 对象上查找其子项的位置。 WatchedFile.Tags 是根级树项的 ItemsSource。这些是Tag,它有自己的隐含HierarchicalDataTemplate。它没有孩子,所以它不使用HierarchicalDataTemplate.ItemsSource。
由于我们所有的集合都是ObservableCollection<T>,您可以随时从任何集合中添加和删除项目,并且用户界面将自动更新。您可以对WatchedFile 的Name 和Path 属性执行相同的操作,因为它实现了INotifyPropertyChanged,并且当它们的值更改时,它的属性会提高PropertyChanged。 XAML UI 在不被告知的情况下订阅所有通知事件,并且做正确的事情——假设您已经告诉它它需要知道什么才能做到这一点。
您的代码隐藏可以使用FindResource 获取SortedWatchedFiles 并更改其SortDescriptions,但这对我来说更有意义,因为它与您如何填充树视图无关:
<Button Content="Re-Sort" Click="Button_Click" HorizontalAlignment="Left" />
<!-- ... snip ... -->
<TreeView
x:Name="WatchedFilesTreeView"
...etc. as before...
后面的代码:
private void Button_Click(object sender, RoutedEventArgs e)
{
var cv = CollectionViewSource.GetDefaultView(WatchedFilesTreeView.ItemsSource);
cv.SortDescriptions.Clear();
cv.SortDescriptions.Add(
new System.ComponentModel.SortDescription("Name",
System.ComponentModel.ListSortDirection.Descending));
}