本人尊重別人勞動成果,感覺寫的很好,拿過來分享一下,本文不是原文,而是個人的理解,學習和分享
聲明:原文: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選擇的問題
待解決:樣式,還有在讀取節點信息時,友好提示,例如,"信息讀取中..."