TPTABP採用ABP6.0微服務架構。TPTABP擁有更強大的許可權系統,許可權可控制到按鈕、api級別。ABP本身使用多租戶、模塊化、領域驅動設計、微服務等架構設計, 支持多個ORM切換,支持後臺任務(HangFire,Quartz)集成、 事件匯流排、AutoMapper、審計日誌、數據過濾等基礎設 ...
在UI界面中,樹形視圖是比較常用的表示層級結構的方式,WPF中提供了TreeView控制項。對於TreeView控制項的基本使用已經有很多文章。大都是介紹如何在XAML中使用硬編碼的固定信息填充Treeview控制項,或者是後臺代碼遞歸遍曆數據源,動態創建TreeView。這裡我想介紹一下如何只通過XAML標記,不用一行後臺代碼遍曆數據實現TreeView。
技術要點與實現
本文的技術關鍵點是層級式數據模板HierarchicalDataTemplate
。HierarchicalDataTemplate
是一個特殊的DataTemplate
,它能夠包裝第二層模板。通過ItemsSource屬性查找下一層級的數據集合,並將它提供給第二層模板。這樣描述可能有點晦澀。接下來舉例進行描述。
首先假設一個應用場景。用樹形結構展現一個地區所有的學校->年級->班級->學生。首先定義幾個Model
public class School : ObservableObject
{
private bool _isOpen;
/// <summary>
/// 獲取或設置是否展開
/// </summary>
[System.Xml.Serialization.XmlIgnore]
public bool IsOpen { get { return _isOpen; } set { Set(ref _isOpen, value); } }
private bool _isSelected;
/// <summary>
/// 獲取或設置是否被選中
/// </summary>
[System.Xml.Serialization.XmlIgnore]
public bool IsSelected { get { return _isSelected; } set { Set(ref _isSelected, value); } }
public string SchoolID { get; set; }
public string SchoolName { get; set; }
public ObservableCollection<Grade> listGrade { get; set; }=new ObservableCollection<Grade>() { };
}
public class Grade : ObservableObject
{
private bool _isOpen;
[System.Xml.Serialization.XmlIgnore]
public bool IsOpen { get { return _isOpen; } set { Set(ref _isOpen, value); } }
private bool _isSelected;
[System.Xml.Serialization.XmlIgnore]
public bool IsSelected { get { return _isSelected; } set { Set(ref _isSelected, value); } }
public string GradeID { get; set; }
public string GradeName { get; set; }
public ObservableCollection<ClassInfo> ListClass { get; set; }=new ObservableCollection<ClassInfo>() { };
}
public class ClassInfo : ObservableObject
{
private bool _isOpen;
[System.Xml.Serialization.XmlIgnore]
public bool IsOpen { get { return _isOpen; } set { Set(ref _isOpen, value); } }
private bool _isSelected;
[System.Xml.Serialization.XmlIgnore]
public bool IsSelected { get { return _isSelected; } set { Set(ref _isSelected, value); } }
public string ClassID { get; set; }
public string ClassName { get; set; }
public ObservableCollection<Student> Students { get; set; }= new ObservableCollection<Student>() { };
}
public class Student : ObservableObject
{
private bool _isSelected;
[System.Xml.Serialization.XmlIgnore]
public bool IsSelected { get { return _isSelected; } set { Set(ref _isSelected, value); } }
public string Id { get; set; }
public string Name { get; set; }
}
接下來根據定義好的Model定義層級式數據模板HierarchicalDataTemplate
。
<HierarchicalDataTemplate DataType="{x:Type local:School}" ItemsSource="{Binding Path=listGrade}">
<TextBlock Text="{Binding Path=SchoolName}" />
</HierarchicalDataTemplate>
<HierarchicalDataTemplate DataType="{x:Type local:Grade}" ItemsSource="{Binding Path=ListClass}">
<TextBlock Text="{Binding Path=GradeName}" />
</HierarchicalDataTemplate>
<HierarchicalDataTemplate DataType="{x:Type local:ClassInfo}" ItemsSource="{Binding Path=Students}">
<TextBlock Text="{Binding Path=ClassName}" />
</HierarchicalDataTemplate>
<HierarchicalDataTemplate DataType="{x:Type local:Student}">
<CheckBox Command="{Binding SelectChangeCommand, ElementName=self}" CommandParameter="{Binding}" IsChecked="{Binding IsSelected}">
<TextBlock Text="{Binding Path=Name}" />
</CheckBox>
</HierarchicalDataTemplate>
其中最外層數據類型是School
,它的下一層數據集合是ObservableCollection<Grade> listGrade
,因此HierarchicalDataTemplate
中的ItemsSource
賦值為listGrade
,這裡我們再屬性控制項中只顯示學校的名稱,因此數據模板只是包含綁定了學校名稱SchoolName
的TextBlock
,如果需要顯示其他信息(比如學校年級數量或者學校圖標),只需增加相應XAML元素即可。緊接著按照這個方式定義好數據類型Grade
,ClassInfo
,Student
的層級式數據模板即可。
定義好了數據模型和相應的層級式數據模板HierarchicalDataTemplate
後,就可以直接把數據元綁定到TreeView
上了。假設要綁定的數據源實例是ObservableCollection<School> schools
。只需如下調用即可。
<TreeView MaxHeight="480"
ItemsSource="{Binding schools}"
VirtualizingPanel.IsVirtualizing="True"
VirtualizingPanel.VirtualizationMode="Recycling" />
這樣使用TreeView
是不是特別方便簡潔。不用為了展示樹形結構,特地定義一個遞歸類型的數據結構,UI展示全部交給XAML就行。JSON數據反序列化後直接綁定即可(XML或者DateSet也是類似的方法)。避免了遞歸遍曆數據源的操作,也不用考慮遞歸帶來的性能問題。
性能
前邊提到不用考慮遞歸帶來的性能問題。那本文介紹的方法對於大量數據的情況下性能到底怎樣呢?接下來做一個測試,模擬100W的數據量,具體為240個學校,每個學校3個年級,每個年級20個班,每個班70個學生,總共數據量是240x3x20x70=1008000個。以下是測試結果:
從圖中可以看到模擬100w數據耗時1.5s,記憶體增加了160M左右,數據渲染到界面不到1s,記憶體增加20M左右。結果還是令人滿意的。這是因為TreeView
支持開啟虛擬化(預設是關閉的,設置 VirtualizingPanel.IsVirtualizing="True"
開啟虛擬化),渲染界面是不會一次把所有UI元素全部創建好,而是根據屏幕上可見區域計算需要渲染的元素個數,創建少量的UI元素,從而減少記憶體和CPU資源的使用。例如本例中又100w條數據,可見區能顯示20條,TreeView只創建了41個UI元素。為什麼不是創建20個呢?這是由於為了確保良好的滾動性能,實際會多創建一些UI元素。
TreeView
預設關閉虛擬化,是因為早期的WPF發佈版本中的VirtualizingStackPanel不支持層次化數據,雖然現在已支持,但是TreeView
預設關閉虛擬化確保相容性。