【问题标题】:C# variable height for nodes in a TreeViewTreeView 中节点的 C# 可变高度
【发布时间】:2009-10-01 21:23:44
【问题描述】:

尽管我在谷歌上做了很多努力,但我还是找不到使用默认 .NET 树视图并为该树视图中的每个节点设置可变高度的解决方案。

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

理想情况下,我还希望一种节点类型也可以随着鼠标悬停而变大。

周围有聪明人吗? :)

【问题讨论】:

  • 这是 Windows 窗体 TreeView 还是 Asp.Net TreeView?我猜是 WinForms,因为我无法想象 Asp.Net 很难使用 css 调整大小,但我想确定一下。
  • 您猜对了,感谢您的澄清。 C# 普通窗口窗体代码。我开始怀疑我是否最终不应该自己控制,所有默认控件似乎都有某种限制……叹息

标签: c# winforms treeview


【解决方案1】:

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

为什么它会做它所做的事情超出了我的理解,因为窗口样式似乎只是让用户能够设置一个奇数的项目高度而不是一个偶数。

限制和警告:

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

这里有一个 247 行的代码 sn-p 来演示这一点,只需用此代码替换 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】:

    我没有找到您问题的答案。 @Frank 是正确的,这在 WinForms 中是不可能的。

    但是,Microsoft 设计指南为 TreeViews 提供了一些替代方案如果您的层次结构只有两层深。

    来自https://docs.microsoft.com/en-us/windows/win32/uxguide/ctrl-tree-views

    如果您需要显示超过两个级别的层次结构(不包括根节点),则必须使用树视图。

    【讨论】:

    【解决方案3】:

    这对于 System.Windows.Forms.TreeView 或我所知道的任何第 3 方替换都是不可能的。标准树视图的功能与 Windows 资源管理器文件夹视图中显示的功能相同。

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

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

    【讨论】:

    • 太棒了...我已经在进行自定义绘图了。我想做一个具有 2 级层次结构的简单列表。 1 表示组(小标题),1 表示其中的项目(大两倍)。我尝试使用列表视图,因为您没有可变大小而放弃,然后我尝试使用列表框并设法使某些工作正常工作,但是折叠/展开组的代码不是很好,因为我自己管理添加/删除,所以我我尝试使用可以做我想要但没有可变项目长度的树视图。多么痛苦:)
    【解决方案4】:

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

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2010-12-19
      • 1970-01-01
      • 2011-07-30
      • 2011-04-22
      • 2015-05-14
      相关资源
      最近更新 更多