如何对具有堆叠面板项的树视图进行排序

本文关键字:视图 排序 | 更新日期: 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来做这件事,这样我就不需要添加一个不可见的标签了。

问题:

  1. 如何使用MVVM解决任务
  2. 当我只有wacthedFile.path要区分时,如何/在哪里将图像绑定到TreeViewItem
  3. 如何对关注的项目进行排序
  4. 我如何跟踪TreeView级别(这样我就可以从保存的文件中重建结构(
  5. 有没有一种方法可以在不使用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.ResourcesTreeView的任何子级的作用域中。HierarchicalDataTemplate没有x:Key属性,这使得它们隐式。这意味着当TreeView的项被实例化时,每个根项的DataContext都将有一个WatchedFile类实例。由于WatchedFile有一个隐式数据模板,它将用于填充其内容。TreeView是递归的,因此它使用HierarchicalDataTemplate而不是常规的DataTemplateHierarchicalDataTemplate添加ItemSource属性,该属性"告诉"项在其DataContext对象上查找其子项的位置。CCD_ 20是用于根级树项目的CCD_。这些是Tag,它有自己隐含的HierarchicalDataTemplate。它没有孩子,所以不使用HierarchicalDataTemplate.ItemsSource

由于我们所有的集合都是ObservableCollection<T>,因此您可以随时添加和删除任何集合中的项目,UI将自动更新。您可以对WatchedFileNamePath属性执行同样的操作,因为它实现了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();