TreeView中节点的C#变量高度

时间:2009-10-01 21:23:44

标签: c# winforms treeview

尽管谷歌努力,但我找不到使用默认.NET树视图的解决方案,并且在该树视图中的每个节点都有可变高度。

我需要一种方法来拥有两种不同高度的节点。

理想情况下,我还希望当鼠标悬停时,一个节点类型也会变大。

周围有谁聪明? :)

4 个答案:

答案 0 :(得分:3)

我没有找到你问题的答案。 @Frank说这在WinForms中是不可能的。

但是,Microsoft设计指南为TreeView提供了一些替代方法 IF 您的层次结构只有两层深度。

http://msdn.microsoft.com/en-us/library/aa511496.aspx

答案 1 :(得分:2)

我意识到这已经很老了......但我昨天发现了一些有趣的东西。 This thread包含克里斯福布斯的答案,该答案表明如果树视图具有TVS_NONEVENHEIGHT样式,确实可以有可变高度的项目。我玩弄了这个想法和他所链接的github code snippet,并发现这确实有效但不能提供100%的灵活性(参见下面的限制列表)。我也不确定是否适合在鼠标悬停时更改项目的高度。

为什么它的功能超出了我的范围,因为窗口样式似乎使用户能够设置奇数项高度而不是偶数项。

限制和警告:

  • 它需要大量工作,因为节点必须完全由所有者绘制。在这方面,此代码示例并不完全正常。
  • 您只能将项目高度设置为控件的ItemHeight属性的倍数(事实上,您实际上将其设置为您想要的因子,1,2,3,...)。我尝试将控件的ItemHeight属性设置为1,然后将节点高度设置为我想要的像素高度。它确实有效,但如果你在设计时添加项目,它只会在设计师中产生奇怪和破碎的效果。我没有彻底测试过这个。
  • 如果尚未将节点添加到TreeNodeCollection中,则无法设置高度,因为尚未创建TreeNode的句柄。
  • 您无法在设计时修改项目高度。
  • 我不经常使用pinvoke的东西,所以其中一些定义可能需要一些工作。例如,TVITEMEX的原始定义有一些条件条目,我不知道如何复制。

这是一个247行代码片段,演示了这一点,只需用此代码替换Windows窗体应用程序的Program.cs。

它仍然需要大量的工作,因为OwnerDraw代码还没有做任何关于绘图图标,树线,复选框等的事情,但我认为这是一个非常令人惊讶的发现,值得在这里发布。

using System;
using System.ComponentModel;
using System.Drawing;
using System.Runtime.InteropServices;
using System.Windows.Forms;

namespace TreeTest
{
  static class Program
  {
    /// <summary>
    /// The main entry point for the application.
    /// </summary>
    [STAThread]
    static void Main()
    {
      Application.EnableVisualStyles();
      Application.SetCompatibleTextRenderingDefault(false);
      Application.Run(new TreeForm());
    }
  }

  public static class NativeExtensions
  {
    public const int TVS_NONEVENHEIGHT = 0x4000;

    [DllImport("user32")]
    //private static extern IntPtr SendMessage(IntPtr hwnd, uint msg, IntPtr wp, IntPtr lp);
    private static extern IntPtr SendMessage(IntPtr hwnd, uint msg, IntPtr wp, ref TVITEMEX lp);

    private const int TVM_GETITEM = 0x1100 + 62;
    private const int TVM_SETITEM = 0x1100 + 63;

    [StructLayout(LayoutKind.Sequential)]
    private struct TVITEMEX
    {
      public uint mask;
      public IntPtr hItem;
      public uint state;
      public uint stateMask;
      public IntPtr pszText;
      public int cchTextMax;
      public int iImage;
      public int iSelectedImage;
      public int cChildren;
      public IntPtr lParam;
      public int iIntegral;
      public uint uStateEx;
      public IntPtr hwnd;
      public int iExpandedImage;
      public int iReserved;
    }

    [Flags]
    private enum Mask : uint
    {
      Text = 1,
      Image = 2,
      Param = 4,
      State = 8,
      Handle = 16,
      SelectedImage = 32,
      Children = 64,
      Integral = 128,
    }

    /// <summary>
    /// Get a node's height. Will throw an error if the Node has not yet been added to a TreeView,
    /// as it's handle will not exist.
    /// </summary>
    /// <param name="tn">TreeNode to work with</param>
    /// <returns>Height in multiples of ItemHeight</returns>
    public static int GetHeight(this TreeNode tn)
    {
      TVITEMEX tvix = new TVITEMEX
      {
        mask = (uint)(Mask.Handle | Mask.Integral),
        hItem = tn.Handle,
        iIntegral = 0
      };
      SendMessage(tn.TreeView.Handle, TVM_GETITEM, IntPtr.Zero, ref tvix);
      return tvix.iIntegral;
    }

    /// <summary>
    /// Set a node's height. Will throw an error if the Node has not yet been added to a TreeView,
    /// as it's handle will not exist.
    /// </summary>
    /// <param name="tn">TreeNode to work with</param>
    /// <param name="height">Height in multiples of ItemHeight</param>
    public static void SetHeight(this TreeNode tn, int height)
    {
      TVITEMEX tvix = new TVITEMEX
      {
        mask = (uint)(Mask.Handle | Mask.Integral),
        hItem = tn.Handle,
        iIntegral = height
      };
      SendMessage(tn.TreeView.Handle, TVM_SETITEM, IntPtr.Zero, ref tvix);
    }
  }

  public class TreeViewTest : TreeView
  {
    public TreeViewTest()
    {
      // Do DoubleBuffered painting
      SetStyle(ControlStyles.AllPaintingInWmPaint, true);
      SetStyle(ControlStyles.OptimizedDoubleBuffer, true);

      // Set value for owner drawing ...
      DrawMode = TreeViewDrawMode.OwnerDrawAll;
    }

    /// <summary>
    /// For TreeNodes to support variable heights, we need to apply the
    /// TVS_NONEVENHEIGHT style to the control.
    /// </summary>
    protected override CreateParams CreateParams
    {
      get
      {
        var cp = base.CreateParams;
        cp.Style |= NativeExtensions.TVS_NONEVENHEIGHT;
        return cp;
      }
    }

    /// <summary>
    /// Do not tempt anyone to change the DrawMode property, be it via code or via
    /// Property grid. It's still possible via code though ...
    /// </summary>
    [DesignerSerializationVisibility(DesignerSerializationVisibility.Hidden),
      EditorBrowsable(EditorBrowsableState.Never)]
    public new TreeViewDrawMode DrawMode
    {
      get { return base.DrawMode; }
      set { base.DrawMode = value; }
    }

    /// <summary>
    /// OwnerDraw code. Still needs a lot of work, no tree lines, symbols, checkboxes etc. are drawn
    /// yet, just the plain item text and background ...
    /// </summary>
    /// <param name="e"></param>
    protected override void OnDrawNode(DrawTreeNodeEventArgs e)
    {
      e.DrawDefault = false;

      // Draw window colour background
      e.Graphics.FillRectangle(SystemBrushes.Window, e.Bounds);

      // Draw selected item background
      if (e.Node.IsSelected)
        e.Graphics.FillRectangle(SystemBrushes.Highlight, e.Node.Bounds);

      // Draw item text
      TextRenderer.DrawText(e.Graphics, e.Node.Text, Font, e.Node.Bounds,
        e.Node.IsSelected ? SystemColors.HighlightText : SystemColors.WindowText,
        Color.Transparent, TextFormatFlags.Top | TextFormatFlags.NoClipping);

      // Draw focus rectangle
      if (Focused && e.Node.IsSelected)
        ControlPaint.DrawFocusRectangle(e.Graphics, e.Node.Bounds);

      base.OnDrawNode(e);
    }

    /// <summary>
    /// Without this piece of code, for some reason, drawing of items that get selected/unselected
    /// is deferred until MouseUp is received.
    /// </summary>
    /// <param name="e"></param>
    protected override void OnMouseDown(MouseEventArgs e)
    {
      base.OnMouseDown(e);
      TreeNode clickedNode = GetNodeAt(e.X, e.Y);
      if (clickedNode.Bounds.Contains(e.X, e.Y))
      {
        SelectedNode = clickedNode;
      }
    }
  }

  public class TreeForm : Form
  {
    public TreeForm() { InitializeComponent(); }

    private System.ComponentModel.IContainer components = null;

    protected override void Dispose(bool disposing)
    {
      if (disposing && (components != null))
      {
        components.Dispose();
      }
      base.Dispose(disposing);
    }

    private void InitializeComponent()
    {
      this.treeViewTest1 = new TreeTest.TreeViewTest();
      this.SuspendLayout();
      // 
      // treeViewTest1
      // 
      this.treeViewTest1.Dock = System.Windows.Forms.DockStyle.Fill;
      this.treeViewTest1.Location = new System.Drawing.Point(0, 0);
      this.treeViewTest1.Name = "treeViewTest1";
      this.treeViewTest1.Size = new System.Drawing.Size(284, 262);
      this.treeViewTest1.TabIndex = 0;
      // 
      // Form2
      // 
      this.AutoScaleDimensions = new System.Drawing.SizeF(6F, 13F);
      this.AutoScaleMode = System.Windows.Forms.AutoScaleMode.Font;
      this.ClientSize = new System.Drawing.Size(284, 262);
      this.Controls.Add(this.treeViewTest1);
      this.Name = "Form2";
      this.Text = "Form2";
      this.ResumeLayout(false);
    }

    private TreeViewTest treeViewTest1;

    protected override void OnLoad(EventArgs e)
    {
      base.OnLoad(e);

      AddNodes(treeViewTest1.Nodes, 0, new Random());
    }

    private void AddNodes(TreeNodeCollection nodes, int depth, Random r)
    {
      if (depth > 2) return;

      for (int i = 0; i < 3; i++)
      {
        int height = r.Next(1, 4);
        TreeNode tn = new TreeNode { Text = $"Node {i + 1} at depth {depth} with height {height}" };
        nodes.Add(tn);
        tn.SetHeight(height);
        AddNodes(tn.Nodes, depth + 1, r);
      }
    }
  }
}

答案 2 :(得分:1)

使用System.Windows.Forms.TreeView以及我所知道的任何第三方替换都无法做到这一点。标准树视图的功能与Windows资源管理器文件夹视图中显示的功能相符。

也许您想向我们提供有关您的任务或目标的更多信息。这将使我们能够指出其他一些替代方案。

当然,您可以随时实现自己的树视图,但是根据您的要求,我认为这可能会成为一项非常耗时的任务。

答案 3 :(得分:0)

如果您使用的是图标,最简单的方法是设置图标大小......这至少在紧凑的框架中工作,我将图像高度设置为我想要的对象的高度。如果你不想要一个图标,你可能只有一个与你的背景相匹配的图标。