從第一篇文章開始到現在,已經過了三個半月
MapleLooker 從最初的 .NET Framework 4.8 + WinForm 變成 .NET 8.0 + WPF
透過這個專案,學到一些 WPF 的皮毛,現在 MapleLooker 已經沒什麼東西可以翻新了
因此這篇將會是 MapleLooker 翻新的最後一篇文章 🙂
這次主要是實現針對 TreeView 節點的查詢功能
在原始 WinForm 版本中,這個功能並沒有實現,這次特別將它做出來,為這個專案劃下一個句點
TreeView 篩選的主要邏輯
這次的 TreeView 篩選是一個相當簡單的功能
不知道為什麼以前沒去實做這個功能
這次主要會透過 Visibility 這個屬性去過濾 TreeViewItem
由於 TreeViewItem 元件的組成相當複雜,因此篩選主要針對 Model 而不是 TreeViewItem 本身
一開始先為 TreeNode 建立 IsVisible 選項,並在 MainForm 的程式內新增套用篩選的程式:
public class WzTreeNode : INotifyPropertyChanged
{
/* ... */
private bool _isVisible = true;
public bool IsVisible {
get => _isVisible;
set {
_isVisible = value;
OnPropertyChanged();
}
}
/* ... */
}
public partial class MainForm : WpfUi.FluentWindow, INotifyPropertyChanged
{
/* ... */
private void ApplyFilter(string filterText) {
foreach (WzTreeNode node in RootNodes) {
this.ApplyFilterForTreeNode(node, filterText);
}
}
private bool ApplyFilterForTreeNode(WzTreeNode node, string filterText, bool parentVisible = false) {
bool visible = parentVisible || string.IsNullOrEmpty(filterText) || node.Name.Contains(filterText);
bool finalVisible = visible;
foreach (var child in node.Children) {
bool childVisible = this.ApplyFilterForTreeNode(child, filterText, visible);
finalVisible = finalVisible || childVisible;
}
node.IsVisible = finalVisible;
return finalVisible;
}
/* ... */
}
邏輯其實很單純,當我要尋找的節點與文字不匹配的時候,就把它隱藏起來
比較特別的是,我要找的節點如果在深處,那他的父節點也要顯示
而要尋找的節點底下還有子節點,則他底下所有的子節點全部都要顯示
當輸入為空字串時,則全部節點的 IsVisible 都會是 true
綁定 UI 並呼叫 ApplyFilter
方法有了,再來就是要綁定 UI 並觸發 ApplyFilter 方法
先建立 FilterText 屬性,然後再跟之前已經建立的 TextBox 做 TwoWay 綁定
public partial class MainForm : WpfUi.FluentWindow, INotifyPropertyChanged
{
/* ... */
private string _filterText = "";
public string FilterText {
get => _filterText;
set {
this._filterText = value;
OnPropertyChanged();
this.ApplyFilter(this._filterText);
}
}
/* ... */
}
<ui:AutoSuggestBox
Grid.Row="1"
Margin="10,10,10,0"
PlaceholderText="搜尋..."
Text="{Binding FilterText, ElementName=_parent, Mode=TwoWay}"/>
當 AutoSuggestBox 輸入之後,會改變 FilterText
改變當下便會呼叫 ApplyFilter 對 TreeView 進行隱藏
將 IsVisible 寫回 TreeViewItem
現在輸入文字後會控制 TreeNode 的顯示狀態
最後只要再建立 Convertor 將 IsVisible 轉成 Visibility,並設定到 TreeViewItem 即可
這邊我直接沿用之前寫好的 BoolVisibilityConvertor
之前的邏輯是 true 轉換成 Visible , false 轉換成 Hidden,但是 Hidden 只是把 UI 隱藏,但依然會佔用空間
所以這次的目標是將 false 轉成 Collapsed ,它可以讓 UI 不佔位,使被篩選的 TreeViewItem 直接消失
我為 BoolVisibilityConvertor 新增一個 Collapsed 屬性來決定這個 Convertor 在 false 的情況該如何轉換 Visibility 值
[ValueConversion(typeof(bool), typeof(Visibility))]
public class BoolVisibilityConvertor : IValueConverter
{
public bool Collapsed { get; init; } = false;
public object Convert(object value, Type targetType, object parameter, CultureInfo culture) {
bool b = (bool)value;
return b ? Visibility.Visible : (Collapsed ? Visibility.Collapsed : Visibility.Hidden);
}
public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture) {
throw new NotImplementedException();
}
}
這樣的寫法可以避免重複建立類似功能的 Convertor,並且能夠依照需求選擇該如何建立 Convertor
我只要在 Resource 建立時指定 Collapsed 即可建立將 false 轉換成 Collapsed 的 Convertor
<ui:FluentWindow.Resources>
<convert:BoolVisibilityConvertor x:Key="boolVisibility" Collapsed="True"/>
</ui:FluentWindow.Resources>
最後再將 IsVisible 套用到 TreeViewItem 即可
<TreeView.ItemContainerStyle>
<Style TargetType="TreeViewItem" BasedOn="{StaticResource DefaultTreeViewItemStyle}">
<Setter Property="Visibility" Value="{Binding IsVisible, Converter={StaticResource boolVisibility}, Mode=TwoWay}"/>
</Style>
</TreeView.ItemContainerStyle>
篩選成果
最後的成品如下:

其他優化
除了 TreeView 篩選以外,這次還有對 UI 稍微做一點點小優化
Image 節點文字會根據解析狀態變化
由於 Image 節點我是採用 LazyLoading 的方式來載入子節點
為了能夠更清楚的知道哪些解析過哪些還沒解析
所以我就把沒有解析的節點設定為灰色,讓我能夠一眼辨識

主題切換
專案本身因為個人偏好,預設是深色主題
但考慮到並非每個人都會用深色主題,所以做了一個開關來切換淺色主題跟深色主題
算是一個非常簡單的功能

結語
本來翻新 MapleLooker 這個專案主要是想寫一些程式重構的東西
但寫到後面才發現,這個專案的程式好像都跟 UI 有關 🙃
最後整個系列就變得像 WPF 學習紀錄了
會想要在這一篇結束 MapleLooker 這個系列,主要是因為大部分的功能以及介面都已經翻新完成
雖然不是每個部份都寫得很完美,但該有的大多都有了,也運作得很好
如果再繼續往下去寫,那就不是翻新了,而是繼續堆疊新功能,而且我也想不到該翻新什麼了
所以做完篩選框之後,就打算結束這個系列
WPF 我覺得是一個蠻有挑戰性的東西,它跟 WinForm 相比完全是不同層級的東西
前面需要學習的概念還蠻多的,但是學會以後,就能夠快速建立好看的 Desktop App
現在有 ChatGPT、Claude 等大語言模型的幫助,多少能降低一些學習門檻
儘管大語言模型的輸出並非每次都很可靠,但也提供不錯的靈感去尋找資源或實作想要的效果
不知道怎麼搞的,文章的產出時間漸漸變成以兩週為一個週期
後面也是盡可能在第一個週末投入時間想方向跟寫程式,並在第二個週末撰寫文章
雖然翻新系列到這篇就結束了,但我還是會繼續想一些題材來寫,紀錄一些個人思維與看法,看能不能提昇文字方面的表達能力