写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。。。还有。。。很多很多没做,这只是一种尝试。。过几天再来完善。。。
呵呵,谢谢各位看管,欢迎各位批评指正。。。