写TreeListView控件的那个人的确很牛X,看他的代码的确学到了不少东西。。。看后想自己也写个控件玩玩,看到有人要三态的TreeView,于是花了三天时间学习了一下TreeView控件,写了一些代码,初步达到效果。现将自己的方法介绍如下: 首先说说我用的资料: 反编译器Reflector:看看MS的TreeView的架构和写法 MSDN的Tree-View Control Reference:了解每个message和结构的定义和用途 安装VS下的CommCtrl.h文件:了解message和一些枚举的实际值 做法如下: step1.定义APIsEnums.cs文件,参照CommCtrl.h给出 #region TreeViewMessages / TVM /// <summary> /// TreeView Messages / TVM /// </summary> public enum TreeViewMessages : int { FIRST = 0x1100, DELETEITEM = FIRST+1, EXPAND = FIRST+2, GETITEMRECT = FIRST+4, GETCOUNT = FIRST+5, GETINDENT = FIRST+6, SETINDENT = FIRST+7, ..... } 在这里我用到的TV_Message其实只有HITTEST = FIRST+17,但为了学习,都列下来了。。。 step2.定义APIsStructs.cs文件,给出了 #region HITTESTINFO/TV [StructLayout(LayoutKind.Sequential, CharSet=CharSet.Auto, Pack=1)] public struct HITTESTINFO { public POINTAPI pt; public UInt32 flags; public IntPtr hItem; } #endregion 等结构,由于比较多,这里就不一一列举了。。但我主要用到的就这个,其他的比较常用的像什么NMHDR 的就不写出来了。。 step3 .定义APIsUser32.cs文件,给出了 //hittest [DllImport("user32.dll", CharSet=CharSet.Auto)] public static extern IntPtr SendMessage(IntPtr hWnd, APIsEnums.TreeViewMessages msg, int wParam, ref APIsStructs.HITTESTINFO lParam); //getRec [DllImport("user32.dll", CharSet=CharSet.Auto)] public static extern bool SendMessage(IntPtr hWnd, APIsEnums.TreeViewMessages msg, bool wParam, ref APIsStructs.RECT rc); 等。。。。 step4.开始写ExTreeNode:TreeNode了,在这里,我 修改了如下属性: #region Checked private CheckStates checkedState = CheckStates.UnChecked; [DefaultValue(typeof(CheckStates), "UnChecked")] public new CheckStates Checked { get { return checkedState; } set { if(this.checkedState == value) return; else { CheckStates temp = this.checkedState; this.checkedState = value; switch(value) { #region UnChecked case CheckStates.UnChecked : if((this.CheckedDirection & CheckDirection.All) != CheckDirection.All) { base.Checked = false; } if((this.CheckedDirection & CheckDirection.Downwards) == CheckDirection.Downwards) { for(int i=0;i<this.Nodes.Count;i++) { ((ExTreeNode)(this.Nodes[i])).CheckedDirection = CheckDirection.Downwards; ((ExTreeNode)(this.Nodes[i])).Checked = CheckStates.UnChecked; } } if((this.CheckedDirection & CheckDirection.Upwards) == CheckDirection.Upwards) { if(this.Parent != null) { this.Parent.CheckedDirection = CheckDirection.Upwards; for(int i=0;i<this.Parent.Nodes.Count;i++) { if(((ExTreeNode)this.Parent.Nodes[i]).Checked != CheckStates.UnChecked) { this.Parent.Checked = CheckStates.HalfChecked; return; } } this.Parent.Checked = CheckStates.UnChecked; } } break; #endregion #region HalfChecked case CheckStates.HalfChecked : base.Checked = true; if((this.CheckedDirection & CheckDirection.Upwards) == CheckDirection.Upwards) { if(this.Parent != null) { this.Parent.CheckedDirection = CheckDirection.Upwards; this.Parent.Checked = CheckStates.HalfChecked; } } break; #endregion case CheckStates.Checked: if((this.CheckedDirection & CheckDirection.All) != CheckDirection.All) { base.Checked = true; } if((this.CheckedDirection & CheckDirection.Downwards) == CheckDirection.Downwards) { for(int i=0;i<this.Nodes.Count;i++) { ((ExTreeNode)(this.Nodes[i])).CheckedDirection = CheckDirection.Downwards; ((ExTreeNode)(this.Nodes[i])).Checked = CheckStates.Checked; } } if((this.CheckedDirection & CheckDirection.Upwards) == CheckDirection.Upwards) { if(this.Parent != null) { this.Parent.CheckedDirection = CheckDirection.Upwards; for(int i=0;i<this.Parent.Nodes.Count;i++) { if(((ExTreeNode)this.Parent.Nodes[i]).Checked != CheckStates.Checked) { this.Parent.Checked = CheckStates.HalfChecked; return; } } this.Parent.Checked = CheckStates.Checked; } } break; } } } } #endregion #region Parent /// <summary> /// Get the parent of this item /// </summary> new public ExTreeNode Parent { get { return (ExTreeNode)base.Parent; } } #endregion #region TreeView public new ExTreeView TreeView { get { if(base.TreeView != null)return (ExTreeView)base.TreeView; if(Parent != null) return(Parent.TreeView); return(null); } } #endregion 加入了一个属性 #region CheckedDirection private CheckDirection checkDirection = CheckDirection.None; public CheckDirection CheckedDirection { get{return checkDirection;} set{this.checkDirection = value;} } #endregion 其中checkDirection和TreeListView的一样。 step5.开始写ExTreeView : TreeView了,添加了私有变量 private ExTreeNode _clickedNode = null; 重写了CheckBoxes属性。。 #region CheckBoxes private CheckBoxesTypes checkboxes = CheckBoxesTypes.None; [Category("Modified properties")] [DefaultValue(typeof(CheckBoxesTypes), "None")] [Browsable(true)] new public CheckBoxesTypes CheckBoxes { get { return checkboxes; } set { if(checkboxes == value) return; checkboxes = value; //checkDirection = value == CheckBoxesTypes.Recursive ? CheckDirection.All : CheckDirection.None; base.CheckBoxes = value == CheckBoxesTypes.None ? false : true; if(Created) Invalidate(); } } #endregion step6开始重写消息处理部分了 由于我准备只用click来重绘,而暂时不从CUSTOMDRAW里截取,所以代码如下 #region LBUTTONDOWN case APIsEnums.WindowMessages.LBUTTONDOWN: APIsStructs.HITTESTINFO hitTestInfo = new APIsStructs.HITTESTINFO(); hitTestInfo.pt.x = (short) ((int) m.LParam); hitTestInfo.pt.y = ((int) m.LParam) >> 0x10; IntPtr hitem = APIsUser32.SendMessage(this.Handle,APIsEnums.TreeViewMessages.HITTEST,0,ref hitTestInfo); if((hitTestInfo.flags & (UInt32)APIsEnums.TVHTFLAGS.ONITEMSTATEICON) != 0 ) { ExTreeNode checkedNode = (ExTreeNode)this.GetNodeAt(hitTestInfo.pt.x,hitTestInfo.pt.y); if(checkedNode == null || checkedNode.IsVisible == false) { this._clickedNode = null; m.Result = (IntPtr)1; return; } switch(checkedNode.Checked) { case CheckStates.UnChecked: checkedNode.CheckedDirection = CheckDirection.All; checkedNode.Checked = CheckStates.Checked; break; case CheckStates.HalfChecked: checkedNode.CheckedDirection = CheckDirection.All; checkedNode.Checked = CheckStates.UnChecked; this._clickedNode = checkedNode; m.Result = (IntPtr)1; return; case CheckStates.Checked: checkedNode.CheckedDirection = CheckDirection.All; checkedNode.Checked = CheckStates.UnChecked; break; } this._clickedNode = checkedNode; } break; #endregion 然后重写OnClick及其对应的方法如下 protected override void OnClick(EventArgs e) { base.OnClick (e); if(this._clickedNode != null) { ExTreeNode temp = this._clickedNode; switch(this._clickedNode.Checked) { case CheckStates.UnChecked: while(temp.Parent!=null) { if(temp.Parent.Checked == CheckStates.HalfChecked) { this.DrawHalfChecked(temp.Parent); } if(temp.Parent.Checked == CheckStates.Checked) { this.DrawChecked(temp.Parent); } temp = temp.Parent; } break; case CheckStates.HalfChecked: break; case CheckStates.Checked: while(temp.Parent!=null) { if(temp.Parent.Checked == CheckStates.HalfChecked) { this.DrawHalfChecked(temp.Parent); } if(temp.Parent.Checked == CheckStates.Checked) { this.DrawChecked(temp.Parent); } temp = temp.Parent; } break; } } } private void DrawHalfChecked(ExTreeNode node) { if(node.IsVisible) { Graphics g = Graphics.FromHwnd(this.Handle); Rectangle recv = new Rectangle(node.Bounds.Location.X-11,node.Bounds.Location.Y+5,7,7); Brush brush = new Drawing.Drawing2D.LinearGradientBrush(recv, Color.Gray, Color.LightBlue, 45, false); g.FillRectangle(brush,recv); } } private void DrawChecked(ExTreeNode node) { if(node.IsVisible) { Graphics g = Graphics.FromHwnd(this.Handle); Rectangle recv = new Rectangle(node.Bounds.Location.X-11,node.Bounds.Location.Y+5,7,7); Brush brush = new Drawing.Drawing2D.LinearGradientBrush(recv, Color.Brown, Color.Chocolate, 45, false); g.FillRectangle(brush,recv); } } 由于我没有那个打勾的ICON,这里先用一个咖啡色的东西先表示那个勾,放在DrawChecked里。 到这里基本实现了外观的三态,其实更标准的是截获CUSTOMDRAW对每次重绘的item进行指定,但最近项目也忙,还没有那么多时间,等过段时间再来完成,实现真正的三态。。像那个ExTreeNode对应的editor都没有写,还有DrawHalfChecked里没有计算用image时的大小,如果用了还要再-16。。。还有。。。很多很多没做,这只是一种尝试。。过几天再来完善。。。 呵呵,谢谢各位看管,欢迎各位批评指正。。。 
|