【问题标题】:TreeView custom DrawNode .NET 3.5 Windows FormsTreeView 自定义 DrawNode .NET 3.5 Windows 窗体
【发布时间】:2012-02-03 23:33:48
【问题描述】:

我想在 TreeView 中以 OwnerDrawText 模式自定义 DrawNode。我发现即使使用这个处理程序也很慢:

    void RegistryTreeDrawNode(object sender, DrawTreeNodeEventArgs e)
    {
        e.DrawDefault = true;
    }

我做错了吗?

谢谢。

【问题讨论】:

    标签: c# windows forms treeview


    【解决方案1】:

    我认为您可能需要为您正在尝试执行的操作显示更多代码。不应该有任何明显不同的绘图,而不是完全没有所有者绘图;您基本上是在覆盖默认绘制,然后在您发布的内容中撤消它。这是丑陋的没有收获......但不应该是一个完美的打击。

    因此,从缺乏代码的角度出发,追求自定义绘制树的核心愿望,让我告诉你,目前没有很多好的信息。

    在过去的几天里,我一直在做自己的自定义树视图工作,并且可能最终会针对我所学的所有内容编写教程。同时,请随时查看我的代码,看看它是否对您有所帮助。

    我的只是一个自定义绘制的资源管理器树视图。填充树视图的代码与 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;
            }
        }
    }
    

    【讨论】:

    • 天哪!感谢这一点,这两行帮助很大: if (e.Node.Bounds.IsEmpty) return;
    猜你喜欢
    • 1970-01-01
    • 2013-05-08
    • 1970-01-01
    • 2010-10-15
    • 2017-02-16
    • 2010-10-28
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多