关于三态的TreeView的一些想法

类别:.NET开发 点击:0 评论:0 推荐:

写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。。。还有。。。很多很多没做,这只是一种尝试。。过几天再来完善。。。

呵呵,谢谢各位看管,欢迎各位批评指正。。。

本文地址:http://com.8s8s.com/it/it41507.htm