使用ComboBox和TreeView控制項實現下拉樹控制項ComboTreeView ...
最近做Winform項目需要用到類似ComboBox的TreeView控制項。
雖然各種第三方控制項很多,但是存在各種版本不相容問題。所以自己寫了個簡單的ComboTreeView控制項。
下圖是實現效果:
目前實現的比較簡單,能滿足我項目中的需求。
此處是項目中的代碼簡化後的版本,供大家參考。
1 using System; 2 using System.Collections.Generic; 3 using System.Windows.Forms; 4 5 namespace CustomControl.Tree 6 { 7 public abstract class ComboTreeView<T> : ComboBox where T : class 8 { 9 protected const int WM_LBUTTONDOWN = 0x0201, WM_LBUTTONDBLCLK = 0x0203; 10 11 protected TreeView treeView; 12 protected ToolStripControlHost treeViewHost; 13 protected ToolStripDropDown dropDown; 14 protected bool dropDownOpen = false; 15 protected TreeNode selectedNode; 16 protected T selectedNodeData; 17 protected T toBeSelected; 18 19 public ComboTreeView(TreeView internalTreeView) 20 { 21 if (null == internalTreeView) 22 { 23 throw new ArgumentNullException("internalTreeView"); 24 } 25 this.InitializeControls(internalTreeView); 26 } 27 28 public event TreeNodeChangedEventHandler TreeNodeChanged; 29 30 protected virtual void InitializeControls(TreeView internalTreeView) 31 { 32 this.treeView = internalTreeView; 33 this.treeView.BorderStyle = BorderStyle.FixedSingle; 34 this.treeView.Margin = new Padding(0); 35 this.treeView.Padding = new Padding(0); 36 this.treeView.AfterExpand += new TreeViewEventHandler(this.WhenAfterExpand); 37 38 this.treeViewHost = new ToolStripControlHost(this.treeView); 39 this.treeViewHost.Margin = new Padding(0); 40 this.treeViewHost.Padding = new Padding(0); 41 this.treeViewHost.AutoSize = false; 42 43 this.dropDown = new ToolStripDropDown(); 44 this.dropDown.Margin = new Padding(0); 45 this.dropDown.Padding = new Padding(0); 46 this.dropDown.AutoSize = false; 47 this.dropDown.DropShadowEnabled = true; 48 this.dropDown.Items.Add(this.treeViewHost); 49 this.dropDown.Closed += new ToolStripDropDownClosedEventHandler(this.OnDropDownClosed); 50 51 this.DropDownWidth = this.Width; 52 base.DropDownStyle = ComboBoxStyle.DropDownList; 53 base.SizeChanged += new EventHandler(this.WhenComboboxSizeChanged); 54 } 55 56 public new ComboBoxStyle DropDownStyle 57 { 58 get { return base.DropDownStyle; } 59 set { base.DropDownStyle = ComboBoxStyle.DropDownList; } 60 } 61 62 public virtual TreeNode SelectedNode 63 { 64 get { return this.selectedNode; } 65 private set { this.treeView.SelectedNode = value; } 66 } 67 68 public virtual T SelectedNodeData 69 { 70 get { return this.selectedNodeData; } 71 set 72 { 73 this.selectedNodeData = value; 74 this.toBeSelected = value; 75 this.UpdateComboBox(value); 76 } 77 } 78 79 protected new int SelectedIndex 80 { 81 get { return base.SelectedIndex; } 82 set { base.SelectedIndex = value; } 83 } 84 85 protected new object SelectedItem 86 { 87 get { return base.SelectedItem; } 88 set { base.SelectedItem = value; } 89 } 90 91 public virtual string DisplayMember { get; set; } = "Name"; 92 93 /// <summary>Gets the internal TreeView control.</summary> 94 public virtual TreeView TreeView => this.treeView; 95 96 /// <summary>Gets the collection of tree nodes that are assigned to the tree view control.</summary> 97 /// <returns>A <see cref="T:System.Windows.Forms.TreeNodeCollection" /> that represents the tree nodes assigned to the tree view control.</returns> 98 public virtual TreeNodeCollection Nodes => this.treeView.Nodes; 99 100 public new int DropDownHeight { get; set; } = 100; 101 102 public new int DropDownWidth { get; set; } = 100; 103 104 protected virtual void ShowDropDown() 105 { 106 this.dropDown.Width = this.Width; 107 this.dropDown.Height = this.DropDownHeight; 108 this.treeViewHost.Width = this.Width; 109 this.treeViewHost.Height = this.DropDownHeight; 110 this.treeView.Font = this.Font; 111 this.dropDown.Focus(); 112 this.dropDownOpen = true; 113 this.dropDown.Show(this, 0, base.Height); 114 } 115 116 protected virtual void HideDropDown() 117 { 118 this.dropDown.Hide(); 119 this.dropDownOpen = false; 120 } 121 122 protected virtual void ToggleDropDown() 123 { 124 if (!this.dropDownOpen) 125 { 126 this.ShowDropDown(); 127 } 128 else 129 { 130 this.HideDropDown(); 131 } 132 } 133 134 protected override void WndProc(ref Message m) 135 { 136 if ((WM_LBUTTONDOWN == m.Msg) || (WM_LBUTTONDBLCLK == m.Msg)) 137 { 138 if (!this.Focused) 139 { 140 this.Focus(); 141 } 142 this.ToggleDropDown(); 143 } 144 else 145 { 146 base.WndProc(ref m); 147 } 148 } 149 150 protected override void Dispose(bool disposing) 151 { 152 if (disposing) 153 { 154 if (this.dropDown != null) 155 { 156 this.dropDown.Dispose(); 157 this.dropDown = null; 158 } 159 } 160 base.Dispose(disposing); 161 } 162 163 protected virtual void WhenTreeNodeChanged(TreeNode newValue) 164 { 165 if ((null != this.selectedNode) || (null != newValue)) 166 { 167 bool changed; 168 if ((null != this.selectedNode) && (null != newValue)) 169 { 170 changed = (this.selectedNode.GetHashCode() != newValue.GetHashCode()); 171 } 172 else 173 { 174 changed = true; 175 } 176 177 if (changed && (null != this.TreeNodeChanged)) 178 { 179 try 180 { 181 this.TreeNodeChanged.Invoke(this, new TreeNodeChangedEventArgs(this.selectedNode, newValue)); 182 } 183 catch (Exception) 184 { 185 // do nothing 186 } 187 } 188 189 this.selectedNode = newValue; 190 } 191 } 192 193 protected virtual void OnDropDownClosed(object sender, ToolStripDropDownClosedEventArgs e) 194 { 195 if (null == this.toBeSelected) 196 { 197 var selectedNode = this.treeView.SelectedNode; 198 var selectedData = this.GetTreeNodeData(selectedNode); 199 this.UpdateComboBox(selectedData); 200 this.WhenTreeNodeChanged(selectedNode); 201 } 202 } 203 204 protected virtual void UpdateComboBox(T data) 205 { 206 base.DisplayMember = this.DisplayMember; // update DisplayMember 207 if (null != data) 208 { 209 this.DataSource = new List<T>() { data }; 210 this.SelectedIndex = 0; 211 } 212 else 213 { 214 this.DataSource = null; 215 } 216 } 217 218 protected virtual void WhenAfterExpand(object sender, TreeViewEventArgs e) 219 { 220 if (null != this.toBeSelected) 221 { 222 if (this.SelectChildNode(e.Node.Nodes, this.toBeSelected)) 223 { 224 this.toBeSelected = null; 225 } 226 } 227 } 228 229 protected virtual void WhenComboboxSizeChanged(object sender, EventArgs e) 230 { 231 this.DropDownWidth = base.Width; 232 } 233 234 public virtual bool SelectChildNode(TreeNodeCollection nodes, T data) 235 { 236 var node = this.FindChildNode(nodes, data); 237 if (null != node) 238 { 239 this.DoSelectTreeNode(node); 240 return true; 241 } 242 else 243 { 244 return false; 245 } 246 } 247 248 protected abstract bool Identical(T x, T y); 249 250 protected virtual void DoSelectTreeNode(TreeNode node) 251 { 252 this.SelectedNode = node; 253 this.ExpandTreeNode(node.Parent); 254 } 255 256 public virtual TreeNode FindChildNode(TreeNodeCollection nodes, T data) 257 { 258 foreach (TreeNode node in nodes) 259 { 260 var nodeData = this.GetTreeNodeData(node); 261 if (this.Identical(nodeData, data)) 262 { 263 return node; 264 } 265 } 266 267 return null; 268 } 269 270 public virtual void ExpandTreeNode(TreeNode node) 271 { 272 if (null != node) 273 { 274 node.Expand(); 275 this.ExpandTreeNode(node.Parent); 276 } 277 } 278 279 public abstract T GetTreeNodeData(TreeNode node); 280 } 281 }