如何对具有堆叠面板项的树视图进行排序
本文关键字:视图 排序 | 更新日期: 2023-09-27 18:14:51
我试图创建一些东西来快速定位和监视文件。所以我创建了一个TreeView,它将StackPanels作为项。StackPanel包含一个图像和一个标签。
private TreeViewItem createFile(string Name, string soureFile)
{
TreeViewItem tvi = new TreeViewItem();
StackPanel sp = new StackPanel();
Image i = new Image();
Label l_Text = new Label();
Label l_FileName = new Label();
l_FileName.Content = soureFile;
l_FileName.Width = 0;
l_Text.Content = Name;
System.Drawing.Bitmap dImg = (System.Drawing.Bitmap)Properties.Resources.ResourceManager.GetObject("Picture");
MemoryStream ms = new MemoryStream();
dImg.Save(ms, System.Drawing.Imaging.ImageFormat.Jpeg);
BitmapImage bImg = new BitmapImage();
bImg.BeginInit();
bImg.StreamSource = new MemoryStream(ms.ToArray());
bImg.EndInit();
i.Source = bImg;
i.Height = 20;
i.Width = 20;
sp.Name = "SP_File";
sp.Orientation = Orientation.Horizontal;
sp.Children.Add(i);
sp.Children.Add(l_Text);
sp.Children.Add(l_FileName);
tvi.Header = sp;
return tvi;
}
可以创建逻辑文件夹(仅用于创建结构(,并将文件和其他文件夹添加到文件夹中(仅引用hdd上的实际文件(。在我尝试对TreeView进行排序之前,这一直很好。我读过关于对树视图进行排序的文章
SortDescriptions.Add(new SortDescription("Header", ListSortDirection.Ascending));
显然,这对我不起作用,因为我不能用"Header.StackPanel.Label.Text"交换"Header"当我进一步阅读时,我似乎使用了错误的方法,不使用MVVM(在C#中对TreeViewItems列表进行数字排序(。
既然我几乎没有使用MVVM的经验,有人能向我解释一下如何最好地使用MVVM吗?我使用"watchedFile"列表来保存文件和文件夹。
我基本上有以下文件类
class watchedFile
{
public string name { get; private set; }
public string path { get; private set; }
public List<string> tags { get; private set; }
public watchedFile(string Name, string Path, List<string> Tags)
{
name = Name;
path = Path;
tags = Tags;
}
}
如果路径为null或为空,则它是一个文件夹。TreeViewItem有一个显示小"文件夹"或"图片"的小图像,一个显示"watchedFile.name"的标签,以及一个包含watchedFile.path的不可见标签(仅显示为工具提示(。我想我应该用DataBinding来做这件事,这样我就不需要添加一个不可见的标签了。
问题:
- 如何使用MVVM解决任务
- 当我只有wacthedFile.path要区分时,如何/在哪里将图像绑定到TreeViewItem
- 如何对关注的项目进行排序
- 我如何跟踪TreeView级别(这样我就可以从保存的文件中重建结构(
- 有没有一种方法可以在不使用MVVM/Data绑定的情况下使用StackPanel项对TreeView进行排序
非常感谢您的帮助。
以下是如何实现这种MVVM方式。
首先,编写视图模型类。在这里,我们得到了一个主视图模型,它有一组WatchedFile
实例,然后我们得到了WatchedFile
类本身。我还决定将Tag
作为一个类,而不仅仅是使用字符串。这使我们能够在XAML中编写数据模板,这些模板将显式和独占地用于显示Tag
实例,而不是一般的字符串。UI中充满了字符串。
如果没有代码段,那么编写通知属性会很麻烦。我有一些片段(偷吧!它们没有被钉住!(。
一旦你有了这个,排序就没什么大不了的了。如果要对根级别的项进行排序,则这些项是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
的任何子级的作用域中。HierarchicalDataTemplate
没有x:Key
属性,这使得它们隐式。这意味着当TreeView
的项被实例化时,每个根项的DataContext
都将有一个WatchedFile
类实例。由于WatchedFile
有一个隐式数据模板,它将用于填充其内容。TreeView
是递归的,因此它使用HierarchicalDataTemplate
而不是常规的DataTemplate
。HierarchicalDataTemplate
添加ItemSource
属性,该属性"告诉"项在其DataContext
对象上查找其子项的位置。CCD_ 20是用于根级树项目的CCD_。这些是Tag
,它有自己隐含的HierarchicalDataTemplate
。它没有孩子,所以不使用HierarchicalDataTemplate.ItemsSource
。
由于我们所有的集合都是ObservableCollection<T>
,因此您可以随时添加和删除任何集合中的项目,UI将自动更新。您可以对WatchedFile
的Name
和Path
属性执行同样的操作,因为它实现了INotifyPropertyChanged
,并且当它们的值更改时,其属性将引发PropertyChanged
。XAML UI在不被告知的情况下订阅所有通知事件,并做正确的事情——假设你已经告诉了它需要知道什么。
您的codeehind可以用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));
}
否则非MVVM解决方案将是。。。。
我看到你的标题是一个有两个孩子的StackPanel,你whish在标签的内容上排序,这是第二个孩子的
您可以将标签子项作为位置为[1]
的数组进行访问,因为数组是基于0的。
TreeView1.Items.SortDescriptions.Clear();
TreeView1.Items.SortDescriptions.Add(new SortDescription("Header.Children[1].Content", ListSortDirection.Ascending));
TreeView1.Items.Refresh();