尽管谷歌努力,但我找不到使用默认.NET树视图的解决方案,并且在该树视图中的每个节点都有可变高度。
我需要一种方法来拥有两种不同高度的节点。
理想情况下,我还希望当鼠标悬停时,一个节点类型也会变大。
周围有谁聪明? :)
答案 0 :(得分:3)
我没有找到你问题的答案。 @Frank说这在WinForms中是不可能的。
但是,Microsoft设计指南为TreeView提供了一些替代方法 IF 您的层次结构只有两层深度。
答案 1 :(得分:2)
我意识到这已经很老了......但我昨天发现了一些有趣的东西。 This thread包含克里斯福布斯的答案,该答案表明如果树视图具有TVS_NONEVENHEIGHT样式,确实可以有可变高度的项目。我玩弄了这个想法和他所链接的github code snippet,并发现这确实有效但不能提供100%的灵活性(参见下面的限制列表)。我也不确定是否适合在鼠标悬停时更改项目的高度。
为什么它的功能超出了我的范围,因为窗口样式似乎使用户能够设置奇数项高度而不是偶数项。
限制和警告:
这是一个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)
如果您使用的是图标,最简单的方法是设置图标大小......这至少在紧凑的框架中工作,我将图像高度设置为我想要的对象的高度。如果你不想要一个图标,你可能只有一个与你的背景相匹配的图标。