C # variable height for nodes in TreeView

Despite my Google efforts, I couldn't find a solution to use a standard .NET tree and have a variable height for each node in that tree view.

I need a way to have 2 node types with different heights.

Ideally, I would also like one node type to grow larger as well as the mouse hovers over it.

Any smart guy? :)

+2


source to share


4 answers


This is not possible with System.Windows.Forms.TreeView or any third party I am aware of. The capabilities of the standard tree view correspond to those shown in the folder view of Windows Explorer.

Perhaps you would like to give us additional information about your task or purpose. This will allow us to point out other alternatives.



Of course, you can always implement your own tree view, but with your requirements, this can become quite a cumbersome task, I think.

+1


source


I have not found an answer to your question. @Frank is right that this is not possible in WinForms.

However, Microsoft's design guidelines suggest some alternatives to TreeViews IF and your hierarchy is only two levels.



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

+2


source


I realize this is quite old ... but yesterday I found something interesting. This thread contains Chris Forbes' answer, which says that it is indeed possible to have elements of variable height if the tree structure has TVS_NONEVENHEIGHT . I played around with this idea and the github snippet he linked to and found that it does work but does not provide 100% flexibility (see list of limitations below). I'm also not sure if it was convenient to change the height of the element on hover.

Why it does what it does is outside of me though, since the window style just seems to allow the user to set the element's odd height instead of an even one.

Limitations and caveats:

  • It takes a lot of work as the node needs to be completely rendered by the owner. This code example is not fully functional in this regard.
  • You can only set the height of the item to multiply the ItemHeight control (in fact, you actually specified it to the factor you want, 1, 2, 3, ...). I experimented with setting the ItemHeight control to 1 and then setting the node height to the pixel height I wanted. It seems to work, but if you add elements at design time it just creates weird and garbled results in the designer. I haven't tested this thoroughly though.
  • You cannot set the height if the node has not been added to the TreeNodeCollection since the TreeNode descriptor has not been created.
  • You cannot change the height of an element at design time.
  • I don't use pinvoke a lot, so some of these definitions might need some work. For example, the original definition of TVITEMEX has some conditional entries that I didn't know how to replicate.

Here is a 247 snippet that demonstrates this, just replace the Program.cs of your Windows Forms application with this code.

It still takes a lot of work as the OwnerDraw code doesn't do anything yet regarding drawing icons, tree lines, checkboxes, etc., but I thought it was a pretty awesome find worth posting here.

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 height. Will throw an error if the Node has not yet been added to a TreeView,
    /// as it 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 height. Will throw an error if the Node has not yet been added to a TreeView,
    /// as it 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 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


source


If you are using an icon, the easiest way is to set the size of the icon ... this at least worked in a compact framework, I set the image height to the height I want the object to be. you can just have an icon that matches your background if you don't want the icon.

0


source







All Articles