我想在TreeView中以OwnerDrawText模式自定义DrawNode。我发现即使使用这个处理程序它也很慢:
void RegistryTreeDrawNode(object sender, DrawTreeNodeEventArgs e)
{
e.DrawDefault = true;
}
我做错了吗?
感谢。
答案 0 :(得分:11)
我认为您可能需要为您尝试执行的操作显示更多代码。不应该有任何明显不同的绘画,而不是所有者绘画;你基本上覆盖了默认的绘制,然后在你发布的内容中撤消它。这是丑陋的,没有任何收获......但不应该是一个性能。
因此,摆脱缺乏代码并追求自定义绘制树的核心愿望,让我告诉你,现在没有很多好的信息。
过去几天我一直在做我自己的自定义树视图工作,最终可能会编写一本关于我所学到的所有内容的教程。在此期间,请随时查看我的代码,看看它是否会帮助您。
我只是一个自定义绘制的资源管理器树视图。填充树视图的代码与TreeView绘图代码分开。如果你想运行我的代码,你可能需要添加自己的+/-图像。
实用程序\ IconReader.cs
using System;
using System.Runtime.InteropServices;
namespace TreeViewTestProject.Utilities
{
public class IconReader
{
public enum IconSize
{
Large = 0,
Small = 1
};
public enum FolderType
{
Open = 0,
Closed = 1
};
/// <summary>
/// Returns an icon for a given file - indicated by the name parameter.
/// </summary>
/// <param name="name">Pathname for file.</param>
/// <param name="size">Large or small</param>
/// <param name="linkOverlay">Whether to include the link icon</param>
/// <returns>System.Drawing.Icon</returns>
public static System.Drawing.Icon GetFileIcon(string name, IconSize size, bool linkOverlay)
{
Shell32.SHFILEINFO shfi = new Shell32.SHFILEINFO();
uint flags = Shell32.SHGFI_ICON | Shell32.SHGFI_USEFILEATTRIBUTES;
if(true == linkOverlay) flags |= Shell32.SHGFI_LINKOVERLAY;
/* Check the size specified for return. */
if(IconSize.Small == size)
{
flags |= Shell32.SHGFI_SMALLICON;
}
else
{
flags |= Shell32.SHGFI_LARGEICON;
}
Shell32.SHGetFileInfo(name,
Shell32.FILE_ATTRIBUTE_NORMAL,
ref shfi,
(uint)System.Runtime.InteropServices.Marshal.SizeOf(shfi),
flags);
// Copy (clone) the returned icon to a new object, thus allowing us to clean-up properly
System.Drawing.Icon icon = (System.Drawing.Icon)System.Drawing.Icon.FromHandle(shfi.hIcon).Clone();
User32.DestroyIcon(shfi.hIcon); // Cleanup
return icon;
}
/// <summary>
/// Used to access system folder icons.
/// </summary>
/// <param name="size">Specify large or small icons.</param>
/// <param name="folderType">Specify open or closed FolderType.</param>
/// <returns>System.Drawing.Icon</returns>
public static System.Drawing.Icon GetFolderIcon(string Foldername, IconSize size, FolderType folderType)
{
// Need to add size check, although errors generated at present!
uint flags = Shell32.SHGFI_ICON | Shell32.SHGFI_USEFILEATTRIBUTES;
if(FolderType.Open == folderType)
{
flags |= Shell32.SHGFI_OPENICON;
}
if(IconSize.Small == size)
{
flags |= Shell32.SHGFI_SMALLICON;
}
else
{
flags |= Shell32.SHGFI_LARGEICON;
}
// Get the folder icon
Shell32.SHFILEINFO shfi = new Shell32.SHFILEINFO();
Shell32.SHGetFileInfo(Foldername,
Shell32.FILE_ATTRIBUTE_DIRECTORY,
ref shfi,
(uint)System.Runtime.InteropServices.Marshal.SizeOf(shfi),
flags);
System.Drawing.Icon.FromHandle(shfi.hIcon); // Load the icon from an HICON handle
// Now clone the icon, so that it can be successfully stored in an ImageList
System.Drawing.Icon icon = (System.Drawing.Icon)System.Drawing.Icon.FromHandle(shfi.hIcon).Clone();
User32.DestroyIcon(shfi.hIcon); // Cleanup
return icon;
}
}
public class Shell32
{
public const int MAX_PATH = 256;
[StructLayout(LayoutKind.Sequential)]
public struct SHITEMID
{
public ushort cb;
[MarshalAs(UnmanagedType.LPArray)]
public byte[] abID;
}
[StructLayout(LayoutKind.Sequential)]
public struct ITEMIDLIST
{
public SHITEMID mkid;
}
[StructLayout(LayoutKind.Sequential)]
public struct BROWSEINFO
{
public IntPtr hwndOwner;
public IntPtr pidlRoot;
public IntPtr pszDisplayName;
[MarshalAs(UnmanagedType.LPTStr)]
public string lpszTitle;
public uint ulFlags;
public IntPtr lpfn;
public int lParam;
public IntPtr iImage;
}
// Browsing for directory.
public const uint BIF_RETURNONLYFSDIRS = 0x0001;
public const uint BIF_DONTGOBELOWDOMAIN = 0x0002;
public const uint BIF_STATUSTEXT = 0x0004;
public const uint BIF_RETURNFSANCESTORS = 0x0008;
public const uint BIF_EDITBOX = 0x0010;
public const uint BIF_VALIDATE = 0x0020;
public const uint BIF_NEWDIALOGSTYLE = 0x0040;
public const uint BIF_USENEWUI = (BIF_NEWDIALOGSTYLE | BIF_EDITBOX);
public const uint BIF_BROWSEINCLUDEURLS = 0x0080;
public const uint BIF_BROWSEFORCOMPUTER = 0x1000;
public const uint BIF_BROWSEFORPRINTER = 0x2000;
public const uint BIF_BROWSEINCLUDEFILES = 0x4000;
public const uint BIF_SHAREABLE = 0x8000;
[StructLayout(LayoutKind.Sequential)]
public struct SHFILEINFO
{
public const int NAMESIZE = 80;
public IntPtr hIcon;
public int iIcon;
public uint dwAttributes;
[MarshalAs(UnmanagedType.ByValTStr, SizeConst = MAX_PATH)]
public string szDisplayName;
[MarshalAs(UnmanagedType.ByValTStr, SizeConst = NAMESIZE)]
public string szTypeName;
};
public const uint SHGFI_ICON = 0x000000100; // get icon
public const uint SHGFI_DISPLAYNAME = 0x000000200; // get display name
public const uint SHGFI_TYPENAME = 0x000000400; // get type name
public const uint SHGFI_ATTRIBUTES = 0x000000800; // get attributes
public const uint SHGFI_ICONLOCATION = 0x000001000; // get icon location
public const uint SHGFI_EXETYPE = 0x000002000; // return exe type
public const uint SHGFI_SYSICONINDEX = 0x000004000; // get system icon index
public const uint SHGFI_LINKOVERLAY = 0x000008000; // put a link overlay on icon
public const uint SHGFI_SELECTED = 0x000010000; // show icon in selected state
public const uint SHGFI_ATTR_SPECIFIED = 0x000020000; // get only specified attributes
public const uint SHGFI_LARGEICON = 0x000000000; // get large icon
public const uint SHGFI_SMALLICON = 0x000000001; // get small icon
public const uint SHGFI_OPENICON = 0x000000002; // get open icon
public const uint SHGFI_SHELLICONSIZE = 0x000000004; // get shell size icon
public const uint SHGFI_PIDL = 0x000000008; // pszPath is a pidl
public const uint SHGFI_USEFILEATTRIBUTES = 0x000000010; // use passed dwFileAttribute
public const uint SHGFI_ADDOVERLAYS = 0x000000020; // apply the appropriate overlays
public const uint SHGFI_OVERLAYINDEX = 0x000000040; // Get the index of the overlay
public const uint FILE_ATTRIBUTE_DIRECTORY = 0x00000010;
public const uint FILE_ATTRIBUTE_NORMAL = 0x00000080;
[DllImport("Shell32.dll")]
public static extern IntPtr SHGetFileInfo(
string pszPath,
uint dwFileAttributes,
ref SHFILEINFO psfi,
uint cbFileInfo,
uint uFlags
);
}
public class User32
{
/// <summary>
/// Provides access to function required to delete handle. This method is used internally
/// and is not required to be called separately.
/// </summary>
/// <param name="hIcon">Pointer to icon handle.</param>
/// <returns>N/A</returns>
[DllImport("User32.dll")]
public static extern int DestroyIcon(IntPtr hIcon);
}
}
ExplorerTreeView.cs:
using System;
using System.IO;
using System.Windows.Forms;
using TreeViewTestProject.Utilities;
using System.Collections;
namespace TreeViewTestProject
{
public partial class ExplorerTreeView : TreeViewEx
{
#region ExplorerNodeSorter Class
private class ExplorerNodeSorter : IComparer
{
public int Compare(object x, object y)
{
TreeNode nx = x as TreeNode;
TreeNode ny = y as TreeNode;
bool nxDir = (nx.ImageKey == kDirectoryImageKey);
bool nyDir = (ny.ImageKey == kDirectoryImageKey);
if(nxDir && !nyDir)
{
return -1;
}
else if(nyDir && !nxDir)
{
return 1;
}
else
{
return string.Compare(nx.Text, ny.Text);
}
}
}
#endregion
private const string kDirectoryImageKey = "directory";
private const string kReplacementText = "C43C65D1-D40F-46F0-BC5E-57265322DDFC";
public ExplorerTreeView()
{
InitializeComponent();
this.BeforeExpand += new TreeViewCancelEventHandler(ExplorerTreeView_BeforeExpand);
this.ImageList = m_FileIcons;
this.TreeViewNodeSorter = new ExplorerNodeSorter();
this.LabelEdit = true;
// Create the root of the tree and populate it
PopulateTreeView(@"C:\");
}
private void PopulateTreeView(string DirectoryName)
{
this.BeginUpdate();
string rootDir = DirectoryName;
TreeNode rootNode = CreateTreeNode(rootDir);
rootNode.Text = rootDir;
this.Nodes.Add(rootNode);
PopulateDirectory(rootNode);
this.EndUpdate();
}
private bool PathIsDirectory(string FullPath)
{
FileAttributes attr = File.GetAttributes(FullPath);
return ((attr & FileAttributes.Directory) == FileAttributes.Directory);
}
private TreeNode CreateTreeNode(string FullPath)
{
string key = FullPath.ToLower();
string name = "";
object tag = null;
if(PathIsDirectory(key))
{
DirectoryInfo info = new DirectoryInfo(FullPath);
key = kDirectoryImageKey;
name = info.Name;
tag = info;
}
else
{
FileInfo info = new FileInfo(FullPath);
name = info.Name;
tag = info;
}
if(!m_FileIcons.Images.ContainsKey(key))
{
if(key == "directory")
{
m_FileIcons.Images.Add(key, IconReader.GetFolderIcon(Environment.CurrentDirectory, IconReader.IconSize.Small, IconReader.FolderType.Open).ToBitmap());
}
else
{
m_FileIcons.Images.Add(key, IconReader.GetFileIcon(FullPath, IconReader.IconSize.Small, false));
}
}
TreeNode node = new TreeNode(name);
node.Tag = tag;
node.ImageKey = key;
node.SelectedImageKey = key;
return node;
}
private void PopulateDirectory(TreeNode ParentNode)
{
DirectoryInfo parentInfo = ParentNode.Tag as DirectoryInfo;
foreach(DirectoryInfo subDir in parentInfo.GetDirectories())
{
TreeNode child = CreateTreeNode(subDir.FullName);
PopulateForExpansion(child);
ParentNode.Nodes.Add(child);
}
foreach(FileInfo file in parentInfo.GetFiles())
{
ParentNode.Nodes.Add(CreateTreeNode(file.FullName));
}
}
private void PopulateForExpansion(TreeNode ParentNode)
{
// We need the +/- to show up if this directory isn't empty... but only want to populate the node on demand
DirectoryInfo parentInfo = ParentNode.Tag as DirectoryInfo;
try
{
if((parentInfo.GetDirectories().Length > 0) || (parentInfo.GetFiles().Length > 0))
{
ParentNode.Nodes.Add(kReplacementText);
}
}
catch { }
}
private void ReplacePlaceholderDirectoryNode(TreeNode ParentNode)
{
if((ParentNode.Nodes.Count == 1) && (ParentNode.Nodes[0].Text == kReplacementText))
{
ParentNode.Nodes.Clear();
PopulateDirectory(ParentNode);
}
}
private void ExplorerTreeView_BeforeExpand(object sender, TreeViewCancelEventArgs e)
{
this.BeginUpdate();
ReplacePlaceholderDirectoryNode(e.Node);
this.EndUpdate();
}
}
}
TreeViewEx.cs:
using System;
using System.Drawing;
using System.Drawing.Drawing2D;
using System.Runtime.InteropServices;
using System.Windows.Forms;
namespace TreeViewTestProject
{
public partial class TreeViewEx : TreeView
{
// Notes: TextRenderer uses GDI to render the text, whereas Graphics uses GDI+. "TreeView" has existed for a long long time
// and thus uses GDI under the covers. For User Drawing TreeNode's, we need to make sure we use the TextRenderer version
// of text rendering functions.
#region Properties
private DashStyle m_SelectionDashStyle = DashStyle.Dot;
public DashStyle SelectionDashStyle
{
get { return m_SelectionDashStyle; }
set { m_SelectionDashStyle = value; }
}
private DashStyle m_LineStyle = DashStyle.Solid;
public DashStyle LineStyle
{
get { return m_LineStyle; }
set { m_LineStyle = value; }
}
private bool m_ShowLines = true;
public new bool ShowLines // marked as 'new' to replace base functionality fixing ShowLines/FullRowSelect issues in base.
{
get { return m_ShowLines; }
set { m_ShowLines = value; }
}
protected override CreateParams CreateParams
{
get
{
// Removes all the flickering of repainting node's
// This is the only thing I found that works properly for doublebuffering a treeview.
CreateParams cp = base.CreateParams;
cp.ExStyle |= 0x02000000; // WS_CLIPCHILDREN
return cp;
}
}
#endregion
[DllImport("user32.dll", ExactSpelling = false, CharSet = CharSet.Auto)]
private static extern int GetWindowLong(IntPtr hWnd, int nIndex);
[DllImport("user32.dll", CharSet = CharSet.Auto, SetLastError = false)]
private static extern IntPtr SendMessage(HandleRef hWnd, uint Msg, IntPtr wParam, HandleRef lParam);
private const int GWL_STYLE = -16;
private const int WS_VSCROLL = 0x00200000;
private const uint TV_FIRST = 0x1100;
private const uint TVM_EDITLABELA = (TV_FIRST + 14);
private const uint TVM_EDITLABELW = (TV_FIRST + 65);
private bool m_SelectionChanged = false;
private bool m_DoubleClicked = false;
private bool m_HierarchyChanged = false;
public TreeViewEx()
{
InitializeComponent();
// ShowLines must be "false" for FullRowSelect to work - so we're overriding the variable to correct for that.
base.ShowLines = false;
this.FullRowSelect = true;
this.ItemHeight = 21;
this.DrawMode = TreeViewDrawMode.OwnerDrawAll;
this.DrawNode += OnDrawNode;
}
private void OnDrawNode(object sender, DrawTreeNodeEventArgs e)
{
e.DrawDefault = false;
if(e.Node.Bounds.IsEmpty) return;
// Clear out the previous contents for the node. If we don't do this, when you mousehover the font will get slightly more bold
Rectangle bounds = new Rectangle(0, e.Node.Bounds.Y, this.Width - 1, e.Node.Bounds.Height - 1);
e.Graphics.FillRectangle(SystemBrushes.Window, bounds);
// Draw everything
DrawNodeFocusedHighlight(e);
DrawNodeLines(e);
DrawPlusMinus(e);
DrawNodeIcon(e);
DrawNodeText(e);
}
private void DrawNodeFocusedHighlight(DrawTreeNodeEventArgs e)
{
if(SelectedNode != e.Node) return;
int scrollWidth = 0;
if(VScrollVisible())
{
scrollWidth = SystemInformation.VerticalScrollBarWidth;
}
Rectangle bounds = new Rectangle(0, e.Node.Bounds.Y, this.Width - scrollWidth, e.Node.Bounds.Height - 1);
if(!e.Node.IsEditing)
{
e.Graphics.FillRectangle(SystemBrushes.Highlight, bounds);
}
using(Pen focusPen = new Pen(Color.Black))
{
focusPen.DashStyle = SelectionDashStyle;
bounds = new Rectangle(0, e.Node.Bounds.Y, this.Width - scrollWidth - 5, e.Node.Bounds.Height - 2);
e.Graphics.DrawRectangle(focusPen, bounds);
}
}
private void DrawNodeText(DrawTreeNodeEventArgs e)
{
if(e.Node.Bounds.IsEmpty) return;
if(e.Node.IsEditing) return;
Rectangle bounds = e.Node.Bounds;
using(Font font = e.Node.NodeFont)
{
bounds.Width = TextRenderer.MeasureText(e.Node.Text, font).Width;
bounds.Y -= 1;
bounds.X += 1;
if(IsRootNode(e.Node))
{
bounds = new Rectangle(this.Margin.Size.Width + Properties.Resources.minus.Width + 9, 0, bounds.Width, bounds.Height);
}
Color fontColor = SystemColors.InactiveCaptionText;
if(this.Focused)
{
fontColor = e.Node.IsSelected?SystemColors.HighlightText:this.ForeColor;
}
TextRenderer.DrawText(e.Graphics, e.Node.Text, font, bounds, fontColor);
}
}
private bool IsRootNode(TreeNode Node)
{
return (Node.Level == 0 && Node.PrevNode == null);
}
private void DrawNodeLines(DrawTreeNodeEventArgs e)
{
DrawNodeLineVertical(e);
DrawNodeLineHorizontal(e);
}
private void DrawNodeLineVertical(DrawTreeNodeEventArgs e)
{
if(IsRootNode(e.Node)) return;
if(!ShowLines) return;
Pen linePen = new Pen(Color.Black);
linePen.DashStyle = LineStyle;
for(int x = 0; x < e.Node.Level; x++)
{
int xLoc = this.Indent + (x * this.Indent) + (Properties.Resources.minus.Width / 2);
int height = e.Bounds.Height;
if(ShouldDrawVerticalLineForLevel(e.Node, x))
{
e.Graphics.DrawLine(linePen, xLoc, e.Bounds.Top, xLoc, e.Bounds.Top + height);
}
}
// Draw the half line for the last node
if(e.Node.Parent.LastNode == e.Node)
{
int halfLoc = (e.Node.Level * this.Indent) + (Properties.Resources.minus.Width / 2);
e.Graphics.DrawLine(linePen, halfLoc, e.Bounds.Top, halfLoc, (e.Bounds.Top + e.Bounds.Height / 2) - 1);
}
}
private bool ShouldDrawVerticalLineForLevel(TreeNode Current, int Level)
{
TreeNode node = Current;
TreeNode c = Current;
while(node.Level != Level)
{
c = node;
node = node.Parent;
}
return !(node.LastNode == c);
}
private void DrawNodeLineHorizontal(DrawTreeNodeEventArgs e)
{
if(IsRootNode(e.Node)) return;
if(!ShowLines) return;
Pen linePen = new Pen(Color.Black);
int xLoc = (e.Node.Level * this.Indent) + (Properties.Resources.minus.Width / 2);
int midY = (e.Bounds.Top + e.Bounds.Bottom) / 2 - 1;
e.Graphics.DrawLine(linePen, xLoc, midY, xLoc + 7, midY);
}
private void DrawNodeIcon(DrawTreeNodeEventArgs e)
{
if(this.ImageList == null) return;
int indent = (e.Node.Level * this.Indent) + this.Margin.Size.Width;
int iconLeft = indent + this.Indent;
int imgIndex = this.ImageList.Images.IndexOfKey(e.Node.ImageKey);
if(!IsRootNode(e.Node))
{
if(imgIndex >= 0)
{
Image img = this.ImageList.Images[imgIndex];
int y = (e.Bounds.Y + e.Bounds.Height / 2) - (img.Height / 2) - 1;
e.Graphics.DrawImage(img, new Rectangle(iconLeft, y, img.Width, img.Height), new Rectangle(0, 0, img.Width, img.Height), GraphicsUnit.Pixel);
}
}
}
private void DrawPlusMinus(DrawTreeNodeEventArgs e)
{
if(e.Node.Nodes.Count == 0) return;
int indent = (e.Node.Level * this.Indent) + this.Margin.Size.Width;
int iconLeft = indent + this.Indent;
Image img = Properties.Resources.plus;
if(e.Node.IsExpanded) img = Properties.Resources.minus;
e.Graphics.DrawImage(img, iconLeft - img.Width - 2, (e.Bounds.Y + e.Bounds.Height / 2) - (img.Height / 2) - 1);
}
private bool VScrollVisible()
{
int style = GetWindowLong(this.Handle, GWL_STYLE);
return ((style & WS_VSCROLL) != 0);
}
private void BeginEditNode()
{
if(this.SelectedNode == null) return;
if(!this.LabelEdit) throw new Exception("This TreeView is not configured with LabelEdit=true");
IntPtr result = SendMessage(new HandleRef(this, this.Handle), TVM_EDITLABELA, IntPtr.Zero, new HandleRef(this.SelectedNode, this.SelectedNode.Handle));
if(result == IntPtr.Zero)
{
throw new Exception("Failed to send EDITLABEL message to TreeView control.");
}
}
private void TreeViewEx_BeforeLabelEdit(object sender, NodeLabelEditEventArgs e)
{
if(m_DoubleClicked)
{
m_DoubleClicked = false;
return;
}
if(m_SelectionChanged)
{
e.CancelEdit = true;
m_SelectionChanged = false;
}
}
private void TreeViewEx_MouseDoubleClick(object sender, MouseEventArgs e)
{
if(m_HierarchyChanged)
{
m_HierarchyChanged = false;
return;
}
if((e.Button & MouseButtons.Left) > 0)
{
if(this.LabelEdit && (this.SelectedNode != null))
{
m_DoubleClicked = true;
BeginInvoke(new MethodInvoker(delegate() { this.SelectedNode.BeginEdit(); }));
}
}
}
private void TreeViewEx_AfterCollapse(object sender, TreeViewEventArgs e)
{
m_HierarchyChanged = true;
}
private void TreeViewEx_AfterExpand(object sender, TreeViewEventArgs e)
{
m_HierarchyChanged = true;
}
}
}