5

How can I change the expand/collapse images from the plus ( + ) and minus ( - ) images that appear when ShowPlusMinus and/or ShowRootLines are true.

To help visualize, I would like to make the following TreeView treeview plus/minus +/-

Look like this (like Windows explorer)

treeview arrows

Kraang Prime
  • 9,981
  • 10
  • 58
  • 124
user1858718
  • 137
  • 2
  • 3
  • 12
  • The link that Sunny provided gave all the details required. If you can understand it you should read about the techniques used to gain a clearer understanding rather than trying to find a simpler solution. You are trying to do something that it wasn't designed to do, it most likely isn't going to be incredibly simple, although I'd argue using PInvoke isn't very hard... – Ian Nov 29 '12 at 12:58

3 Answers3

12

Expanding on Ivan Ičin's solution :

[DllImport("uxtheme.dll", ExactSpelling = true, CharSet = CharSet.Unicode)]
private static extern int SetWindowTheme(IntPtr hwnd, string pszSubAppName, string pszSubIdList);

public static void SetTreeViewTheme(IntPtr treeHandle) {
     SetWindowTheme(treeHandle, "explorer", null);
}

To use, add a TreeView to your form, and in Form_Load :

SetTreeViewTheme( treeView1.Handle );

Alternatively, you can extend the TreeView object

public class MyTreeView : TreeView
{

    [DllImport("uxtheme.dll", ExactSpelling = true, CharSet = CharSet.Unicode)]
    private static extern int SetWindowTheme(IntPtr hwnd, string pszSubAppName, string pszSubIdList);

    public MyTreeView() {
        SetWindowTheme(this.Handle, "explorer", null);
    }
}

before and after treeview Depicts what it looks like before and after calling SetWindowTheme

Community
  • 1
  • 1
Kraang Prime
  • 9,981
  • 10
  • 58
  • 124
  • 2
    There is a potential issue with calling SetWindowTheme in the control constructor - you may not have a handle yet. The more reliable method is to override the OnHandleCreated event and do it there. There is also a potential for old operating systems to puke (pre-vista) on this, so checking the OS Version is appropriate - This is shown at the following like (not my work, but what I followed to get it working): https://www.cyotek.com/blog/enabling-shell-styles-for-the-listview-and-treeview-controls-in-csharp – snow_FFFFFF Nov 08 '17 at 23:05
  • @snow_FFFFFF - a check for older operating systems wasn't done as support for Windows OS's prior to Vista are no longer supported in general at the time. – Kraang Prime Nov 09 '17 at 03:50
  • That's fine, but you can't expect every user to follow Microsoft's edicts. I work with several corporations that still run XP. In any case, I ran into the issue with using the control constructor (on a listview, not a treeview), so it might be worth updating your answer to use the OnHandleCreated event instead. – snow_FFFFFF Nov 09 '17 at 16:58
4

There are 3 methods I can think of:

  1. Sunny already mentioned using SetWindowTheme(TreeView.Handle, "explorer", null)

  2. Using WPF if that is the option and adding TreeViewItem object

  3. Overriding OnPaint methods, which is too complicated, considering you can do just 1, so 1 or 2 it is up to you to chose.

Ivan Ičin
  • 9,672
  • 5
  • 36
  • 57
4

When you want to customize your treeview control, Microsoft provides a property named "TreeViewDrawMode" on the treeview control, it's value is a enum which has 3 values:Normal, OwnerDrawText, OwnerDrawAll, in your situation, you must use OwnerDrawAll. after you set that property as OwnerDrawAll, when the treeview's nodes are showing, a event named "DrawNode" will be triggered, so you can process your drawing there. when you draw it by yourself, usually you need to draw 3 things: expand/collapse icon, node icon, node text. my sample is below: //define the icon file path string minusPath = Application.StartupPath + Path.DirectorySeparatorChar + "minus.png"; string plusPath = Application.StartupPath + Path.DirectorySeparatorChar + "plus.png"; string nodePath = Application.StartupPath + Path.DirectorySeparatorChar + "directory.png";

    public FrmTreeView()
    {
        InitializeComponent();
        //setting to customer draw
        this.treeView1.DrawMode = TreeViewDrawMode.OwnerDrawAll;
        this.treeView1.DrawNode += new DrawTreeNodeEventHandler(treeView1_DrawNode);
    }

    void treeView1_DrawNode(object sender, DrawTreeNodeEventArgs e)
    {
        Rectangle nodeRect = e.Node.Bounds;
        /*--------- 1. draw expand/collapse icon ---------*/
        Point ptExpand = new Point(nodeRect.Location.X - 20, nodeRect.Location.Y + 2);
        Image expandImg = null;
        if (e.Node.IsExpanded || e.Node.Nodes.Count < 1)
            expandImg = Image.FromFile(minusPath);
        else
            expandImg = Image.FromFile(plusPath);
        Graphics g = Graphics.FromImage(expandImg);
        IntPtr imgPtr = g.GetHdc();
        g.ReleaseHdc();
        e.Graphics.DrawImage(expandImg, ptExpand);

        /*--------- 2. draw node icon ---------*/
        Point ptNodeIcon = new Point(nodeRect.Location.X - 4, nodeRect.Location.Y + 2);
        Image nodeImg = Image.FromFile(nodePath);
        g = Graphics.FromImage(nodeImg);
        imgPtr = g.GetHdc();
        g.ReleaseHdc();
        e.Graphics.DrawImage(nodeImg, ptNodeIcon);
        /*--------- 3. draw node text ---------*/
        Font nodeFont = e.Node.NodeFont;
        if (nodeFont == null)
            nodeFont = ((TreeView)sender).Font;
        Brush textBrush = SystemBrushes.WindowText;
        //to highlight the text when selected
        if ((e.State & TreeNodeStates.Focused) != 0)
            textBrush = SystemBrushes.HotTrack;
        //Inflate to not be cut
        Rectangle textRect = nodeRect;
        //need to extend node rect
        textRect.Width += 40;
        e.Graphics.DrawString(e.Node.Text, nodeFont, textBrush, Rectangle.Inflate(textRect, -12, 0));
     }

the result of my test is like this: picture of result

Scott Yang
  • 567
  • 2
  • 6
  • 3
    Note that this post incorrectly uses `Graphics.FromImage()` call. This call is expensive (very expensive in case of large trees). You should instead use the already created `Graphics` object `e.Graphics`, which allows you to draw images using `DrawImage()` method. You should cache your expand/collapse icons once (using `Image.FromFile()`) at the start of your program. – dotNET Feb 28 '17 at 08:03