简体   繁体   English

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

[英]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:问题:

  1. How can I solve the task using MVVM?如何使用 MVVM 解决任务?
  2. How/where can I bind the Image to the TreeViewItem when I have just the wacthedFile.path to distinguish?当我只有 wactedFile.path 来区分时,如何/在哪里可以将图像绑定到 TreeViewItem?
  3. How do I sort the watched items?如何对观看的项目进行排序?
  4. How do I keep track of the TreeView level (so i can rebuild the structure from a saved file)?如何跟踪 TreeView 级别(以便我可以从保存的文件重建结构)?
  5. Is there a way to sort a TreeView with StackPanel Items without using MVVM/Data-Binding?有没有办法在不使用 MVVM/数据绑定的情况下使用 StackPanel 项对 TreeView 进行排序?

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的项被实例化时,每个根项都将有一个用于其DataContextWatchedFile类实例。 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.您可以对WatchedFileNamePath属性执行相同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.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM