WPF 控件多選問題
用過的 WPF 的同學,肯定用過控件的選擇功能,例如 ListBox 或者 DataGrid 等。其中有一種控件 ———— TreeView 的多選並沒有我們想象中的那么開箱即用。
之前就遇到一個需求,TreeView 要支持多個選中項,且能從數據端(ViewModel)改變樹節點的選中狀態,然而原生 TreeView 控件是不支持設置多個選中項的。
為什么 TreeView 不支持多選呢?
從 TreeView 這個類可以看出,它是繼承於 ItemsControl 的,本質上它就是一個嵌套 ItemsControl 的控件。單個 ItemsControl 的選中非常容易處理,多層嵌套就不一樣了,原生控件也沒有提供直接可用的接口來實現多選項。
失敗的嘗試
剛開始做多選 TreeView 的時候,首先采用了增加 IsSelected 屬性來綁定選中狀態,數據結構如下:
public class TreeNode
{
public bool Selected { get; set; }
// other data of tree node...
}
本以為將 TreeViewItem 的 IsSelected 屬性綁定到 Selected 屬性,再使用 DataTrigger 讓選中項樣式高亮使即可輕松搞定,沒想到寫完后測試卻發現不同層級的 TreeViewItem 無法同時處於選中狀態。因為雙向綁定的存在,控件的 IsSelected 屬性實時同步到了 ViewModel 的狀態數據中,導致多選失敗。
后來在 Selected 的 set 方法中嘗試加入一些條件判斷,試圖區分用戶的點擊和控件自動的數據修改,均沒有理想的解決所有場景下的操作要求。
改變思路
上面的方法中,我花了大量的精力去解決控件 IsSelected 屬性與 ViewModel 數據的同步,雙向綁定使得數據流的管理極為混亂和復雜,那為什么不把兩者的關系解耦呢?
- 取消 IsSelected 與 Selected 的綁定,只保留 Selected 數據到控件樣式的單向綁定。
- 從 View 的操作出發,將用戶的點擊事件發送到 ViewModel,ViewModel 可以查到 Ctrl/Shift 按鍵的狀態,此時更新 Selected 將變得非常簡單且易維護。通過統一的入口去修改 Selected 屬性,而控件的選中狀態只由 Selected 數據改變,不隨着用戶在 View 層的操作而改變。這樣的實現方式將極大地怎加選中操作的靈活性。
- 同時,全選、反選、批量選擇等操作也能在 ViewModel 中完成,只要思考業務需求而不必考慮控件交互帶來的復雜度。
總結
WPF 的雙向綁定帶給 UI 開發帶來極大的便利,卻也造成了復雜交互的維護性問題,適當使用單向綁定能有不錯的效果。
當時在解決 TreeView 多選問題的時候,我還沒有接觸 Web 前端。后來學習 React 的時候,發現我在處理 TreeView 數據流動方向的方式竟然與 React 渲染時的單向數據流有一定的相似之處。