如何对具有stackpanel项目的树视图进行排序

时间:2016-10-24 14:32:05

标签: c# wpf mvvm data-binding treeview

我试图创建一些快速定位和观看文件的东西。所以我创建了一个将StackPanels作为Items的TreeView。 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。我阅读了使用

对TreeViews进行排序的内容
    SortDescriptions.Add(new SortDescription("Header", ListSortDirection.Ascending));

显然这对我来说不起作用,因为我无法交换" Header"用" Header.StackPanel.Label.Text" 随着我进一步阅读,似乎我通过不使用MVVM(Numerically sort a List of TreeViewItems in C#)使用了错误的方法。

由于我没有使用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;
    }        
}

如果path为null或清空其文件夹。 TreeViewItem有一个小图片,显示一个&#34;文件夹&#34;或&#34;图片&#34;,一个标签,显示&#34; watchedFile.name&#34;和一个不可见的标签,其中包含watchedfile.path(仅显示为工具提示)。我想我应该使用DataBinding这样做,所以我不需要添加一个不可见的标签。

问题:

  1. 如何使用MVVM解决任务?
  2. 当我只有wacthedFile.path来区分时,我如何/在哪里可以将Image绑定到TreeViewItem?
  3. 如何对观看的项目进行排序?
  4. 如何跟踪TreeView级别(因此我可以从保存的文件重建结构)?
  5. 有没有办法在不使用MVVM /数据绑定的情况下使用StackPanel项对TreeView进行排序?
  6. 非常感谢任何帮助。

2 个答案:

答案 0 :(得分:1)

这里是如何做这个MVVM时尚的。

首先,编写viewmodel类。在这里,我们有一个主视图模型,其中包含WatchedFile个实例的集合,然后我们自己获得了WatchedFile类。我还决定让Tag成为一个类,而不仅仅是使用字符串。这使我们可以在XAML中编写数据模板,这些模板显式地和唯一地用于显示Tag实例,而不是一般的字符串。用户界面充满了字符串。

如果您没有片段,通知属性会很繁琐。 I have snippets(偷了他们!他们没有被钉死!)。

一旦你有了这个,排序就没什么大不了的了。如果要对根级别项目进行排序,则这些项目为WatchedFile

SortDescriptions.Add(new SortDescription("Name", ListSortDirection.Ascending));

但我们将在下面的XAML中做到这一点。

序列化也很简单:只需使您的viewmodel类可序列化即可。这里重要的是,您的排序和序列化不必关心树视图项模板中的内容。 StackPanels,GroupBoxes,无论如何 - 它根本不重要,因为你的排序和序列化代码只处理你的数据类,而不是UI的东西。您可以从根本上更改数据模板中的可视详细信息,而不必担心它会影响代码的任何其他部分。这对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没有x:Key属性,这使得他们隐式。这意味着当TreeView项被实例化时,每个根项将为其WatchedFile具有DataContext类实例。由于WatchedFile具有隐式数据模板,因此将用于填充其内容。 TreeView是递归的,因此它使用HierarchicalDataTemplate而非常规DataTemplateHierarchicalDataTemplate添加了ItemSource属性,该属性告诉项目在DataContext对象上查找子项的位置。 WatchedFile.Tags是根级树项的ItemsSource。这些是Tag,它有自己的隐式HierarchicalDataTemplate。它没有孩子,因此它不会使用HierarchicalDataTemplate.ItemsSource

由于我们的所有馆藏都是ObservableCollection<T>,您可以随时在任何馆藏中添加和删除项目,并且UI会自动更新。您可以对Name的{​​{1}}和Path属性执行相同的操作,因为它实现了WatchedFile,并且当其值发生更改时,其属性会引发INotifyPropertyChanged。 XAML UI在没有被告知的情况下订阅所有通知事件,并做正确的事情 - 假设您已经告诉它需要知道做什么。

您的代码隐藏可以使用PropertyChanged获取SortedWatchedFiles并更改其FindResource,但这对我来说更有意义,因为它不知道您是如何填充的树视图:

SortDescriptions

代码背后:

    <Button Content="Re-Sort" Click="Button_Click" HorizontalAlignment="Left" />

    <!-- ... snip ... -->

    <TreeView
        x:Name="WatchedFilesTreeView"
        ...etc. as before...

答案 1 :(得分:-1)

或者非MVVM解决方案本来是......

我看到您的标题是一个有两个孩子的StackPanel,您希望对标签的内容(第二个孩子)进行排序

由于数组是基于0的,因此您可以将标签子项作为位置[1]的数组来访问。

TreeView1.Items.SortDescriptions.Clear();
TreeView1.Items.SortDescriptions.Add(new SortDescription("Header.Children[1].Content", ListSortDirection.Ascending));
TreeView1.Items.Refresh();