[英]How to sort a treeview that has stackpanel items
i tried to create somthing to quickly locate and watch files.我试图创建一些东西来快速定位和查看文件。 So i created a TreeView that has StackPanels as Items.所以我创建了一个以 StackPanels 作为项目的 TreeView。 A StackPanel contains an Image an a Label. 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;
}
One can create logical folders (just for creating a structure) and add files and other folders to the folders (just references to to the actual file on the hdd).可以创建逻辑文件夹(仅用于创建结构)并将文件和其他文件夹添加到文件夹中(仅引用硬盘上的实际文件)。 This worked fine until i tried to sort the TreeView.这工作正常,直到我尝试对 TreeView 进行排序。 I read stuff on on Sorting TreeViews with我阅读了关于 Sorting TreeViews 的内容
SortDescriptions.Add(new SortDescription("Header", ListSortDirection.Ascending));
Obviously this doesn't work for me since i cant exchange "Header" with "Header.StackPanel.Label.Text" As I read a little bit further it seems I used the wrong approach to the whole thing by not using MVVM ( Numerically sort a List of TreeViewItems in C# ).显然这对我不起作用,因为我不能用“Header.StackPanel.Label.Text”交换“Header”当我进一步阅读时,似乎我通过不使用MVVM( 数字排序)对整个事情使用了错误的方法C# 中的 TreeViewItems 列表)。
Since I have litte to no experience with MVVM can someone explain to me how it is best do this with MVVM?由于我几乎没有使用 MVVM 的经验,有人可以向我解释如何最好使用 MVVM 做到这一点吗? I use a List of "watchedFile" to keep the files and folders.我使用“watchedFile”列表来保存文件和文件夹。
I basically have the the following class for a file我基本上有以下文件类
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;
}
}
If path is null or empty its a folder.如果路径为空或清空其文件夹。 The TreeViewItem has a little Image which shows a little "folder" or "picture", a label which shows the "watchedFile.name" and an invisible label which contains the watchedfile.path (which is only shown as a tooltip). TreeViewItem 有一个小图像,显示一个小“文件夹”或“图片”,一个显示“watchedFile.name”的标签和一个包含watchedfile.path(仅显示为工具提示)的不可见标签。 I guess I should do this with DataBinding so I dont need to add an invisible Label.我想我应该用 DataBinding 做到这一点,所以我不需要添加一个不可见的标签。
Questions:问题:
Any help is highly appreciated.任何帮助都受到高度赞赏。
Here's how to do this MVVM fashion.以下是如何执行这种 MVVM 方式。
First, write viewmodel classes.首先,编写视图模型类。 Here, we've got a main viewmodel that has a collection of WatchedFile
instances, and then we've got the WatchedFile
class itself.在这里,我们有一个包含WatchedFile
实例集合的主视图WatchedFile
,然后我们有WatchedFile
类本身。 I've also decided to make Tag
a class, instead of just using strings.我还决定让Tag
成为一个类,而不仅仅是使用字符串。 This lets us write data templates in the XAML that explicitly and exclusively will be used to display Tag
instances, rather than strings in general.这让我们可以在 XAML 中编写数据模板,这些模板将显式且专门用于显示Tag
实例,而不是一般的字符串。 The UI is full of strings.用户界面充满了字符串。
The notification properties are tedious to write if you don't have a snippet.如果您没有代码段,那么编写通知属性会很乏味。 I have snippets (Steal them! They're not nailed down!). 我有片段(偷他们!他们没有被钉死!)。
Once you've got this, sorting is no big deal.一旦你有了这个,排序就没什么大不了的。 If you want to sort the root level items, those are WatchedFile
.如果你想对根级别的项目进行排序,那些是WatchedFile
。
SortDescriptions.Add(new SortDescription("Name", ListSortDirection.Ascending));
But we'll do that in XAML below.但我们将在下面的 XAML 中做到这一点。
Serialization is simple, too: Just make your viewmodel classes serializable.序列化也很简单:只需让您的视图模型类可序列化即可。 The important thing here is that your sorting and serialization don't have to care what's in the treeview item templates.这里重要的是您的排序和序列化不必关心树视图项模板中的内容。 StackPanels, GroupBoxes, whatever -- it doesn't matter at all, because your sorting and serialization code just deals with your data classes, not the UI stuff. StackPanels、GroupBoxes 等等——根本不重要,因为您的排序和序列化代码只处理您的数据类,而不是 UI 内容。 You can change the visual details in the data templates radically without having to worry about it affecting any other part of the code.您可以从根本上更改数据模板中的视觉细节,而不必担心它会影响代码的任何其他部分。 That's what's nice about MVVM.这就是 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
}
}
Code behind.后面的代码。 Note I only added one line here to what the wizard gave me.请注意,我只在向导给我的内容中添加了一行。
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();
}
}
}
And lastly the XAML:最后是 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>
The XAML is less than obvious. XAML 不太明显。 TreeView.Resources
is in scope for any child of the TreeView
. TreeView.Resources
在范围内的任何孩子TreeView
。 The HierarchicalDataTemplate
s don't have an x:Key
property, which makes them implicit . HierarchicalDataTemplate
没有x:Key
属性,这使得它们是隐式的。 That means that when the TreeView
's items are instantiated, each of the root items will have a WatchedFile
class instance for its DataContext
.这意味着当TreeView
的项被实例化时,每个根项都将有一个用于其DataContext
的WatchedFile
类实例。 Since WatchedFile
has an implicit data template, that will be used to fill in its content.由于WatchedFile
有一个隐式数据模板,它将用于填充其内容。 TreeView
is recursive, so it uses HierarchicalDataTemplate
instead of regular DataTemplate
. TreeView
是递归的,因此它使用HierarchicalDataTemplate
而不是常规DataTemplate
。 HierarchicalDataTemplate
adds the ItemSource
property, which tells the item where to look for its children on its DataContext
object. HierarchicalDataTemplate
添加了ItemSource
属性,该属性告诉项在其DataContext
对象上的何处查找其子项。 WatchedFile.Tags
is the ItemsSource
for the root-level tree items. WatchedFile.Tags
是根级树项的ItemsSource
。 Those are Tag
, which has its own implicit HierarchicalDataTemplate
.这些是Tag
,它有自己的隐式HierarchicalDataTemplate
。 It doesn't have children so it doesn't use HierarchicalDataTemplate.ItemsSource
.它没有孩子,所以它不使用HierarchicalDataTemplate.ItemsSource
。
Since all our collections are ObservableCollection<T>
, you can add and remove items from any collection at any time and the UI will update automagically.由于我们所有的集合都是ObservableCollection<T>
,您可以随时从任何集合中添加和删除项目,UI 将自动更新。 You can do the same with the Name
and Path
properties of WatchedFile
, because it implements INotifyPropertyChanged
and its properties raise PropertyChanged
when their values change.您可以对WatchedFile
的Name
和Path
属性执行相同WatchedFile
,因为它实现了INotifyPropertyChanged
并且其属性在其值更改时会引发PropertyChanged
。 The XAML UI subscribes to all the notification events without being told, and does the right thing -- assuming you've told it what it needs to know to do that. XAML UI 在不被告知的情况下订阅所有通知事件,并做正确的事情——假设您已经告诉它它需要知道什么才能做到这一点。
Your codebehind can grab SortedWatchedFiles
with FindResource
and change its SortDescriptions
, but this makes more sense to me, since it's agnostic about how you're populating the treeview:您的代码隐藏可以使用FindResource
获取SortedWatchedFiles
并更改其SortDescriptions
,但这对我来说更有意义,因为它与您如何填充树视图无关:
<Button Content="Re-Sort" Click="Button_Click" HorizontalAlignment="Left" />
<!-- ... snip ... -->
<TreeView
x:Name="WatchedFilesTreeView"
...etc. as before...
Code behind:后面的代码:
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));
}
or the non MVVM solution would have been....或者非 MVVM 解决方案将是......
I see your header is a StackPanel with 2 children and you whish to sort on the Content of the label, which is the 2nd child我看到你的标题是一个有 2 个孩子的 StackPanel,你希望对标签的内容进行排序,这是第二个孩子
You would access the label child as an array of position [1]
since arrays are 0 based.您可以将标签子项作为位置[1]
的数组访问,因为数组是基于 0 的。
TreeView1.Items.SortDescriptions.Clear();
TreeView1.Items.SortDescriptions.Add(new SortDescription("Header.Children[1].Content", ListSortDirection.Ascending));
TreeView1.Items.Refresh();
声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.