1. 程式人生 > >潛移默化學會WPF(Treeview異步加載節點) - AYUI框架 - 博客園

潛移默化學會WPF(Treeview異步加載節點) - AYUI框架 - 博客園

而是 事件 不變 如果 apr get pri fill first

原文:潛移默化學會WPF(Treeview異步加載節點) - AYUI框架 - 博客園

本人尊重別人勞動成果,感覺寫的很好,拿過來分享一下,本文不是原文,而是個人的理解,學習和分享

聲明:原文:http://blog.csdn.net/qing2005/article/details/6523002

一、基本工作

1.新建WPF應用程序 TreeViewLoadingAsync

2.新建文件夾DB,把準備好的 Access 示例數據庫Sample.mdb拷貝到DB文件夾下

此時會彈出 數據源配置向導 窗口,點下一步,勾選表和視圖,點擊完成,此時app.config數據庫連接字符串都已經生成好了

數據庫就一張表

技術分享圖片

3.用linq獲得基本數據

在DB文件夾下新建一個DepartmentHelper類獲得基本數據

新建兩個靜態對象

       static SampleDataSet ds = new SampleDataSet();
       static DepartmentTableAdapter da = new DepartmentTableAdapter();

第一個 Sample是你的數據庫名稱,然後加DataSet,這個對象可直接敲出,你可以理解為Sample這個數據庫的臨時數據集,就是另一個形式的數據庫,充當臨時數據庫的角色,這個數據庫我麽直接可以用linq去操作它,不用sql了,目前還是空的一個數據庫,下面一行代碼是 Sample中的一個表名稱+TableAdapter,部門表適配器,這樣的話就可以用linq語法直接操作Department這張表了,如果還有其他表以此類推。(講的不專業,只是方便理解)DepartmentTableAdapter

寫完不變色,按一下Shift+Alt+F10導入命名空間,導不進來,請檢查一下名稱是否拼寫錯誤

繼續寫代碼

技術分享圖片
  static DepartmentHelper() {
            da.Fill(ds.Department);
        }
public static IEnumerable GetSubDepartments(int pid) {
            var list = ds.Department.Where(c => c.PID == pid).ToList();
            return list;
        }
技術分享圖片
staticDepartmentHelper(),靜態構造函數,就是在調用DepartmentHelper裏面的方法時首先要向ds裏面的Department表中填充數據,這裏用static修飾的,所以DepartmentHelper的對象是保存在內存中的,所以不會重復被創建,知道該程序關閉時,這塊被占用的內存會被釋放
(使用 static 修飾符聲明屬於類型本身而不是屬於特定對象的靜態成員。static 修飾符可用於類、字段、方法、屬性、運算符、事件和構造函數,但不能用於索引器、析構函數或類以外的類型)
如果把這個思想理解了,Entity Framework就好學了
這兩個方法很簡單不講了

二、新建視圖模型
1.本例子簡單,不細分MVVM了,思想是的
新建DepartmentViewModel類,實現INotifyPropertyChanged接口,導入
System.ComponentModel;System.Collections.ObjectModel;這兩個命名空間,慣性,實現該接口
技術分享圖片

先寫3個變量,1個構造函數

技術分享圖片
        private DepartmentViewModel(object currentObject)
        {

        }
        //臨時子節點用,當Expanded時移除此節點,添加子節點
        static readonly DepartmentViewModel _temp = new DepartmentViewModel(null);
        //選中的子節點
        private static ObservableCollection<DepartmentViewModel> _checkedItems = new ObservableCollection<DepartmentViewModel>();
        public ObservableCollection<DepartmentViewModel> CheckedItems
        {
            get
            {
                return _checkedItems;
            }
        }

        //根節點
        static DepartmentViewModel _rootItem;
技術分享圖片

先寫4個屬性,保存父節點數據,子節點集合,treeview上要顯示的文字

技術分享圖片
  private DepartmentViewModel _parent;
        public DepartmentViewModel Parent
        {
            get { return _parent; }
            set { _parent = value; }
        }

        private List<DepartmentViewModel> _children;
        public List<DepartmentViewModel> Children
        {
            get { return _children; }
            private set { _children = value; }
        }

        private object _current;
        public object Current
        {
            get { return _current; }
            set { _current = value; }
        }
        public string DisplayText
        {
            get { return ((SampleDataSet.DepartmentRow)Current)["DName"].ToString(); }
        }
技術分享圖片

添加判斷

技術分享圖片
      /// <summary>
        /// 判斷是否有子節點(邏輯是:如果只有一個臨時子節點,說明沒有真正的子節點)
        /// </summary>
        /// <returns></returns>
        private bool HasChildren() {
            return !(Children.Count == 1 && Children[0] == _temp);
        }
技術分享圖片

添加checkbox處理代碼

技術分享圖片
  private bool? _isChecked;
        public bool? IsChecked {
            get { return _isChecked; }
            set {
                SetCheckState(value, true, true);
                
            }
        }
        private void SetCheckState(bool? value, bool updateChildren, bool updateParent) {
            if (_isChecked != value) {
                _isChecked = value;

                //通知選中項的集合
                if (_isChecked == true) {
                    _checkedItems.Add(this);
                    PropertyChanged(this, new PropertyChangedEventArgs("CheckedItems"));
                } else if (_isChecked == false) {
                    _checkedItems.Remove(this);
                    PropertyChanged(this, new PropertyChangedEventArgs("CheckedItems"));
                }

                PropertyChanged(this, new PropertyChangedEventArgs("IsChecked"));

                if (updateChildren) {
                    if (HasChildren()) {
                        Children.ForEach(c => c.SetCheckState(value, true, false));
                    }
                }
                if (updateParent && _parent != null) {
                    _parent.VerifyState();
                }
            }
        }
        private void VerifyState() {
            bool? state = null;
            for (int i = 0; i < this.Children.Count; ++i) {
                bool? currentState = this.Children[i].IsChecked;
                if (i == 0) {
                    state = currentState;
                } else if (state != currentState) {
                    state = null;
                    break;
                }
            }
            this.SetCheckState(state, false, true);
        }
技術分享圖片

構造函數,添加一下代碼,初始化一些值

        private DepartmentViewModel(object currentObject) {
            Current = currentObject;
            _isChecked = false;
            Children = new List<DepartmentViewModel>();
            Children.Add(_temp);  //好讓顯示有個圖標箭頭,一個treeview節點下至少一個子節點
        }

展開節點,展開如果沒有子節點,把默認的那個節點移除,

技術分享圖片
        private bool _isExpanded;
        public bool IsExpanded {
            get { return _isExpanded; }
            set {
                if (value != _isExpanded) {
                    _isExpanded = value;
                    PropertyChanged(this, new PropertyChangedEventArgs("IsExpanded"));
                }
                if (!HasChildren()) {
                    Children.Remove(_temp);
                    LoadChildren();
                }
            }
        }
技術分享圖片 技術分享圖片
 /// <summary>
        /// 加載子節點
        /// </summary>
        private void LoadChildren() {
            if (Current != null) {
                int pid = Convert.ToInt32(((SampleDataSet.DepartmentRow)Current)["DID"]);
                var list = DepartmentHelper.GetSubDepartments(pid);
                foreach (var item in list) {
                    DepartmentViewModel model = new DepartmentViewModel(item) { _isChecked = this.IsChecked };
                    if (model.IsChecked == true) {
                        _checkedItems.Add(model);
                        PropertyChanged(this, new PropertyChangedEventArgs("CheckedItems"));
                    }
                    Children.Add(model);
                }
                Init();
            }
        }
技術分享圖片 技術分享圖片
  public static List<DepartmentViewModel> Create() {
            // 獲得ID獲得部門對象
            var list = DepartmentHelper.GetSubDepartments(0);
            DepartmentViewModel root = new DepartmentViewModel(null);
            _rootItem = root;
            root.Children.Clear();
            foreach (var item in list) {
                root.Children.Add(new DepartmentViewModel(item));
            }
            return root.Children;
        }

/// <summary>
/// 初始化,用於設置父節點
/// </summary>
private void Init() {
   if (!HasChildren()) return;
     foreach (DepartmentViewModel child in Children) {
     child.Parent = this;
     child.Init();
     }
     PropertyChanged(this, new PropertyChangedEventArgs("Children"));
  }
技術分享圖片

就一個xaml窗體文件,就前臺,後臺沒有代碼

技術分享圖片
<Window x:Class="DepartmentTreeView.MainWindow"
        xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
        xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
        xmlns:local="clr-namespace:TreeViewLoadingAsync"
        Title="MainWindow" Height="329" Width="212" FontFamily="Arial">
    <Window.Resources>
        <ObjectDataProvider x:Key="depProvider" ObjectType="{x:Type local:DepartmentViewModel}" MethodName="Create" />
        <Style x:Key="TreeViewItemStyle" TargetType="{x:Type TreeViewItem}">
            <Setter Property="IsExpanded" Value="{Binding Path=IsExpanded,Mode=TwoWay}" />
        </Style>
        <HierarchicalDataTemplate x:Key="CheckBoxItemTemplate" ItemsSource="{Binding Children}">
            <StackPanel Orientation="Horizontal" Margin="0,2,0,0">
                <CheckBox Focusable="False" IsChecked="{Binding IsChecked,Mode=TwoWay}" VerticalAlignment="Center" />
                <ContentPresenter Content="{Binding DisplayText,Mode=OneWay}" Margin="2,0" />
            </StackPanel>
        </HierarchicalDataTemplate>
    </Window.Resources>
    <Grid Margin="5">
        <Grid.RowDefinitions>
            <RowDefinition />
            <RowDefinition Height="Auto" />
            <RowDefinition Height="Auto" />
        </Grid.RowDefinitions>
        <TreeView Name="tvDepartment" Grid.Row="0" ItemContainerStyle="{StaticResource TreeViewItemStyle}" ItemsSource="{Binding Source={StaticResource depProvider}}" ItemTemplate="{StaticResource CheckBoxItemTemplate}" />
       
    </Grid>
</Window>
技術分享圖片
對上分析:
<ObjectDataProvider x:Key="depProvider" ObjectType="{x:Type local:DepartmentViewModel}" MethodName="Create" />
對象類型的數據源提供器:它很常用的,這裏ObjectType指定了該對象的類型,是個DepartmentViewModel類型的,該類型下面有個方法叫做Create方法,所以後臺不用指定數據了,Create方法返回的是一個List<DepartmentViewModel>類型的
如果你要的數據,多個地方都要用的到,不容易綁定,試著把公用的數據放在資源裏,例如:ObjectDataProvider ,還有個XMLDataProvider有興趣可以看一下


整體思路:
1.創建數據庫的linq類,寫個讀取Department數據的訪問類,這裏叫DepartmentHelper,其實也就是數據訪問層,根據父節點ID獲取對象集合
2.創建ViewModel,主要通過這個方法List<DepartmentViewModel> Create() 提供treeview的數據;
①獲得父節點ID是0的,即根節點集合
②創建一個虛擬根節點DepartmentViewModel類型的,把得到的真正根節點的DepartmentViewModel類型化後,遍歷根節點結合,添加到虛擬根節點的Children集合中
3.前臺給treeview綁定數據,ObjectDataProvider
4.綁定容器樣式ItemContainerStyle,子項目數據源ItemsSource,子項目模板ItemTemplate
5.由於DepartmentViewModel實現了INotifyPropertyChanged接口,直接對他裏面的值修改,於是不要對前臺的控件執行修改,就可以改變了
6.treeview的ItemContainerStyle樣式修改了IsExpanded屬性,設置了Mode屬性雙向綁定
7.在DepartmentViewModel中有個IsExpanded屬性,在設置set屬性中觸發事件,觸發LoadChildren方法,讀取以此ID為父節點ID的那些部門對象,根據父節點左邊的CheckBox狀態,初始化其他節點的選中狀態,子對象集合全部放入Children集合內,想要立即更新UI的狀態,調用PropertyChanged委托就行了
8.UI界面綁定了Chekbox的IsChecked屬性和DepartmentViewModel中的IsChecked屬性,同理雙向的,IsChecked屬性,SetCheckState方法,VerifyState方法
9.Init方法,是遍歷Children,設置Children這個集合中的對象的Parent屬性,遞歸把Children中Children等以此類推全部設置一下
10.CheckedItems集合存著的是DepartmentViewModel對象,也就是間接的選中的TreeviewItem對象。也方便後臺提取選中項的信息,然後繼續操作


擴展:在treeview外添加一個按鈕,添加單擊事件
技術分享圖片
        private void Button_Click(object sender, RoutedEventArgs e)
        {
            ObjectDataProvider provider = FindResource("depProvider") as ObjectDataProvider;
            List<DepartmentViewModel> firstLevelItems = provider.Data as List<DepartmentViewModel>;

            ICollectionView view = CollectionViewSource.GetDefaultView(firstLevelItems);
            DepartmentViewModel rootItem = view.CurrentItem as DepartmentViewModel;

            StringBuilder builder = new StringBuilder();
            foreach (DepartmentViewModel checkItem in rootItem.CheckedItems)
            {
                builder.AppendLine(checkItem.DisplayText);
            }
            MessageBox.Show("Checked items:\n" + builder.ToString());
        }
技術分享圖片

本例子靚點:MVVM,Treeview ViewModel的設計,點擊讀取加載節點信息,checkbox的正確選擇,後臺能夠獲得選擇的treeview中選擇的項;
難點:ViewModel類
巧妙:利用ObjectDataProvider,IsExpanded巧妙雙向綁定時,利用屬性動態加載數據,後臺頁面無代碼;CheckBox版本treeview選擇的問題
待解決:樣式,還有在讀取節點信息時,友好提示,例如,"信息讀取中..."






潛移默化學會WPF(Treeview異步加載節點) - AYUI框架 - 博客園