Numerically sort a List of TreeViewItems in C#

后端 未结 1 816
自闭症患者
自闭症患者 2021-01-16 09:42

This question is a follow up to this question. My overall goal at the moment is to add to my program\'s TreeViewItem (my TreeViewItem has child nod

相关标签:
1条回答
  • 2021-01-16 10:13

    Ok. Delete all your code and start all over.

    1: It is essential that you read up on MVVM before writing a single line of code in WPF.

    You can read about it here and here and here

    <Window x:Class="MiscSamples.SortedTreeView"
            xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
            xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
            xmlns:cmp="clr-namespace:System.ComponentModel;assembly=WindowsBase"
            Title="SortedTreeView" Height="300" Width="300">
        <DockPanel>
            <TextBox Text="{Binding NewValueString}" DockPanel.Dock="Top"/>
            <Button Click="AddNewItem" DockPanel.Dock="Top" Content="Add"/>
            <TreeView ItemsSource="{Binding ItemsView}" SelectedItemChanged="OnSelectedItemChanged">
                <TreeView.ItemTemplate>
                    <HierarchicalDataTemplate ItemsSource="{Binding ItemsView}">
                        <TextBlock Text="{Binding Value}"/>
                    </HierarchicalDataTemplate>
                </TreeView.ItemTemplate>
            </TreeView>
        </DockPanel>
    </Window>
    

    Code Behind:

    public partial class SortedTreeView : Window
    {
        public SortedTreeViewWindowViewModel ViewModel { get { return DataContext as SortedTreeViewWindowViewModel; } set { DataContext = value; } }
    
        public SortedTreeView()
        {
            InitializeComponent();
            ViewModel = new SortedTreeViewWindowViewModel()
                {
                    Items = {new TreeViewModel(1)}
                };
        }
    
        private void AddNewItem(object sender, RoutedEventArgs e)
        {
            ViewModel.AddNewItem();
        }
    
        //Added due to limitation of TreeViewItem described in http://stackoverflow.com/questions/1000040/selecteditem-in-a-wpf-treeview
        private void OnSelectedItemChanged(object sender, RoutedPropertyChangedEventArgs<object> e)
        {
            ViewModel.SelectedItem = e.NewValue as TreeViewModel;
        }
    }
    

    2: First thing you can notice above is that the Code Behind does NOTHING. It just delegates functionality to something called the ViewModel (not ModelView, which is a misspelling)

    Why is that?

    Because it's a much better approach. Period. Having the application logic / business logic and data separated and decoupled from the UI is the best thing to ever happen to any developer.

    So, What's the ViewModel about?

    3: The ViewModel exposes Properties that contain the Data to be shown in the View, and Methods that contain the logic to operate with the Data.

    So it's as simple as:

    public class SortedTreeViewWindowViewModel: PropertyChangedBase
    {
        private string _newValueString;
        public int? NewValue { get; set; }
    
        public string NewValueString
        {
            get { return _newValueString; }
            set
            {
                _newValueString = value;
                int integervalue;
    
                //If the text is a valid numeric value, use that to create a new node.
                if (int.TryParse(value, out integervalue))
                    NewValue = integervalue;
                else
                    NewValue = null;
    
                OnPropertyChanged("NewValueString");
            }
        }
    
        public TreeViewModel SelectedItem { get; set; }
    
        public ObservableCollection<TreeViewModel> Items { get; set; }
    
        public ICollectionView ItemsView { get; set; }
    
        public SortedTreeViewWindowViewModel()
        {
            Items = new ObservableCollection<TreeViewModel>();
            ItemsView = new ListCollectionView(Items) {SortDescriptions = { new SortDescription("Value",ListSortDirection.Ascending)}};
        }
    
        public void AddNewItem()
        {
            ObservableCollection<TreeViewModel> targetcollection;
    
            //Insert the New Node as a Root node if nothing is selected.
            targetcollection = SelectedItem == null ? Items : SelectedItem.Items;
    
            if (NewValue != null && !targetcollection.Any(x => x.Value == NewValue))
            {
                targetcollection.Add(new TreeViewModel(NewValue.Value));
                NewValueString = string.Empty;    
            }
    
        }
    }
    

    See? All your 11 requirements are fulfilled by 5 lines of code in the AddNewItem() method. No Header.ToString() stuff, no casting anything, no horrible code behind approaches.

    Just simple, simple properties and INotifyPropertyChanged.

    And what about the sorting?

    4: The sorting is performed by the CollectionView, and it occurs at the ViewModel level, not at the View Level.

    Why?

    Because Data is kept separate from it's visual representation in the UI.

    5: Finally, here is the actual Data Item:

    public class TreeViewModel: PropertyChangedBase
    {
        public int Value { get; set; }
    
        public ObservableCollection<TreeViewModel> Items { get; set; }
    
        public CollectionView ItemsView { get; set; }
    
        public TreeViewModel(int value)
        {
            Items = new ObservableCollection<TreeViewModel>();
            ItemsView = new ListCollectionView(Items)
                {
                    SortDescriptions =
                        {
                            new SortDescription("Value",ListSortDirection.Ascending)
                        }
                };
            Value = value;
        }
    }
    

    This is the class that will hold the data, in this case, an int Value, because you only care about numbers, so that's the right data type, and then an ObservableCollection that will hold the child nodes, and again the CollectionView that takes care of the sorting.

    6: Whenever you use DataBinding in WPF (which is essential to all this MVVM thing) you need to implement INotifyPropertyChanged, so this is the PropertyChangedBase class All ViewModels inherit from:

    public class PropertyChangedBase:INotifyPropertyChanged
    {
        public event PropertyChangedEventHandler PropertyChanged;
    
        protected virtual void OnPropertyChanged(string propertyName)
        {
            Application.Current.Dispatcher.BeginInvoke((Action) (() =>
                                                                     {
                                                                         PropertyChangedEventHandler handler = PropertyChanged;
                                                                         if (handler != null) handler(this, new PropertyChangedEventArgs(propertyName));
                                                                     }));
        }
    }
    

    And wherever there's a change in a property you need to Notify that by doing:

    OnPropertyChanged("PropertyName");
    

    as in

    OnPropertyChanged("NewValueString");
    

    And this is the result:

    enter image description here

    • Just copy and paste all my code in a File -> New Project -> WPF Application and see the results for yourself.

    • Let me know if you need me to clarify anything.

    0 讨论(0)
提交回复
热议问题