Вот как это сделать MVVM.
Во-первых, напишите классы вида. Здесь у нас есть основная модель просмотра, в которой есть коллекция WatchedFile
экземпляров, а затем у нас есть класс WatchedFile
. Я также решил сделать класс Tag
вместо того, чтобы просто использовать строки. Это позволяет нам писать шаблоны данных в XAML, которые явно и исключительно будут использоваться для отображения экземпляров Tag
, а не строк в целом. Пользовательский интерфейс полон строк.
Свойства уведомления являются утомительными для записи, если у вас нет фрагмента. I have snippets (Украдите их! Они не прибиты!).
Как только вы получили это, сортировка не имеет большого значения. Если вы хотите отсортировать элементы корневого уровня, это WatchedFile
.
SortDescriptions.Add(new SortDescription("Name", ListSortDirection.Ascending));
Но мы сделаем это в XAML ниже.
Сериализация также проста: просто сделайте классы классов просмотра сериализуемыми. Важно то, что для сортировки и сериализации вам не нужно заботиться о том, что находится в шаблонах элементов treeview. StackPanels, GroupBoxes, что угодно - это вообще не имеет значения, потому что ваш код сортировки и сериализации просто касается ваших классов данных, а не элементов интерфейса. Вы можете существенно изменить визуальные детали в шаблонах данных, не беспокоясь об этом, затрагивая любую другую часть кода. Вот что приятно в MVVM.
ViewModels:
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
. HierarchicalDataTemplate
s не имеют свойства x:Key
, что делает их неявным. Это означает, что когда объекты TreeView
создаются, каждый из корневых элементов будет иметь экземпляр класса WatchedFile
для своего DataContext
. Поскольку WatchedFile
имеет неявный шаблон данных, который будет использоваться для заполнения его содержимого. TreeView
является рекурсивным, поэтому он использует HierarchicalDataTemplate
вместо обычного DataTemplate
. HierarchicalDataTemplate
добавляет свойство ItemSource
, в котором говорится о товаре, где следует искать его детей на своем объекте DataContext
. WatchedFile.Tags
- это ItemsSource
для элементов дерева корневого уровня. Это Tag
, который имеет свой собственный неявный HierarchicalDataTemplate
. У него нет детей, поэтому он не использует HierarchicalDataTemplate.ItemsSource
.
Поскольку все наши коллекции являются ObservableCollection<T>
, вы можете добавлять и удалять элементы из любой коллекции в любое время, и пользовательский интерфейс будет автоматически обновляться. Вы можете сделать то же самое с Name
и Path
свойствами WatchedFile
, поскольку он реализует INotifyPropertyChanged
и его свойства повышают PropertyChanged
при изменении их значений. Пользовательский интерфейс XAML подписывается на все уведомления, не сообщая ему, и делает правильные вещи - предполагая, что вы сказали ему, что ему нужно знать, чтобы сделать это.
Ваш отделенного кода может захватить SortedWatchedFiles
с FindResource
и изменить его SortDescriptions
, но это имеет смысл для меня, так как это агностик о том, как вы наполнении TreeView: за
<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));
}
спасибо за ваш очень подробный ответ - мне это очень нравится. пока он работает хорошо, и я хочу, чтобы я получил немного больше в MVVM. у меня все еще были проблемы. Я хотел бы, чтобы файл/папка имел несколько записей.поэтому я добавил наблюдаемую коллекцию к WatchedFile – Thoms
, пожалуйста, см. следующий «ответ» для дальнейших вопросов. – Thoms
жаль, что я не смог правильно прочитать;) заменил 'ItemsSource = {{Binding Tags}" 'на' ItemsSource = "{Binding Subs}" ' – Thoms