0

In form1 designer I added a treeview control and added to it one root node and one child node. And created paint event.

In form1 code :

using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Data;
using System.Drawing;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using System.Windows.Forms;

namespace Test
{
    public partial class Form1 : Form
    {
        public Form1()
        {
            InitializeComponent();

            AdvancedTreeView atv = new AdvancedTreeView();
        }

        private void Form1_Paint(object sender, PaintEventArgs e)
        {
            AdvancedTreeView.FillPolygonPoint(e);
        }
    }
}

The class AdvancedTreeView :

using System;
using System.Drawing;
using System.Windows.Forms;

public class AdvancedTreeView : TreeView
{
    private static Image myimage;

    public AdvancedTreeView()
    {
        DrawMode = TreeViewDrawMode.OwnerDrawAll;
        ShowLines = false;
        AlternateBackColor = BackColor;
    }

    public Color AlternateBackColor { get; set; }

    protected override void OnDrawNode(DrawTreeNodeEventArgs e)
    {
        e.DrawDefault = true;
        base.OnDrawNode(e);

        // background
        Color backColor = (GetTopNodeIndex(e.Node) & 1) == 0 ? BackColor : AlternateBackColor;
        using (Brush b = new SolidBrush(backColor))
        {
            e.Graphics.FillRectangle(b, new Rectangle(0, e.Bounds.Top, ClientSize.Width, e.Bounds.Height));
        }

        // icon
        if (e.Node.Nodes.Count > 0)
        {

            Image icon = GetIcon(e.Node.IsExpanded); // TODO: true=down;false:right
            e.Graphics.DrawImage(icon, e.Bounds.Left - icon.Width - 3, e.Bounds.Top);
        }

        // text (due to OwnerDrawText mode, indenting of e.Bounds will be correct)
        TextRenderer.DrawText(e.Graphics, e.Node.Text, Font, e.Bounds, ForeColor);

        // indicate selection (if not by backColor):
        if ((e.State & TreeNodeStates.Selected) != 0)
            ControlPaint.DrawFocusRectangle(e.Graphics, e.Bounds);
    }

    private Image GetIcon(bool isExpanded)
    {
        return myimage;
    }

    private int GetTopNodeIndex(TreeNode node)
    {
        while (node.Parent != null)
            node = node.Parent;

        return Nodes.IndexOf(node);
    }

    public static void FillPolygonPoint(PaintEventArgs e)
    {

        // Create solid brush.
        SolidBrush blueBrush = new SolidBrush(Color.Blue);

        // Create points that define polygon.
        Point point1 = new Point(0, 0);
        Point point2 = new Point(20, 10);
        Point point3 = new Point(0, 20);

        Point[] curvePoints = { point1 , point2, point3 };

        // Draw polygon to screen.
        e.Graphics.FillPolygon(blueBrush, curvePoints);

        myimage = new Bitmap(10,10,e.Graphics);
    }
}

It's getting to the line in the AdvancedTreeView :

DrawMode = TreeViewDrawMode.OwnerDrawAll;

But it's never getting into the OnDrawNode. I tried also TreeViewDrawMode.OwnerDrawText but it's never getting to the OnDrawNode.

Daniel Lip
  • 3,867
  • 7
  • 58
  • 120
  • You could use breakpoint to see which line that didnt get – Syafiqur__ Jul 19 '19 at 08:14
  • 1
    Because in `Form1_Paint` you call a **static method** which has no clue about `OnDrawNode().` The `avt` instance you create in the ctor is just created and made available for the GC when ctor ends then `OnDrawNode()` won't ever be called on a disposed object and it won't ever see `myimage` static field. In general you should avoid `static` methods but, anyway, in this case you also need to put the `AdvancedTreeControl` somewhere in the UI: create it using the designer or add it manually with something similar to `this.Controls.Add(avt)` – Adriano Repetti Jul 19 '19 at 08:15
  • Have you actually set a breakpoint inside `OnDrawNode`, maybe on `e.DrawDefault = true;`, so you also have the chance to see what it does? `FillPolygonPoint()` must be called from `OnDrawMode`, you cannot pass it the `PaintEventArgs` of a Form to then do `myimage = new Bitmap(10,10,e.Graphics);`, which creates a blank image (`e.Graphics` is used just for the resolution). Use in the TreeView's `DrawTreeNodeEventArgs`. Calling `base.OnDrawNode(e);` at that point means that you will paint over what a possible inheritor has painted. So maybe don't call it. Many other issues. – Jimi Jul 19 '19 at 08:39
  • But *In form1 designer I added a treeview control and added to it one root node and one child node*. Then what is this: `AdvancedTreeView atv = new AdvancedTreeView();`? This is another, unrelated, control. And this: `AdvancedTreeView.FillPolygonPoint(e);`, what should it do then? – Jimi Jul 19 '19 at 08:41
  • 1
    I knew I had already seen this code [somewhere](https://stackoverflow.com/a/42955909/5114784). :)) In your form constructor you create a local instance of the `AdvancedTreeView`, which has a quite short lifetime... 1 line. Instead, drop an `AdvancedTreeView` onto your form in the designer (if you build the project it must appear in the Toolbox). If it works, I hope I deserve an upvote for my original answer. :)))) – György Kőszeg Jul 19 '19 at 09:16
  • @GyörgyKőszeg So I did what you suggested but still it's not getting there using a break point but it does throw an exception in the OnDrawNode on the line : e.Graphics.DrawImage(icon, e.Bounds.Left - icon.Width - 3, e.Bounds.Top); It says null on it once I'm trying to add to the AdvancedTreeView nodes. Before even running the program. I dragged the AdvancedTreeView in form1 designer but it's showing this exception when adding nodes. – Daniel Lip Jul 19 '19 at 11:37
  • @GyörgyKőszeg What I want to do is to make this treeview style like in your old answer here : https://stackoverflow.com/questions/42954579/customize-treeview/42955909#42955909 but without icon !! I'm trying to create the right/down arrows using the FillPolygonPoint method. – Daniel Lip Jul 19 '19 at 11:38
  • 1
    Your `myImage` is set only in the static method. Set it in the constructor or just remove that part if you want to draw it by yourself. – György Kőszeg Jul 19 '19 at 11:40
  • @GyörgyKőszeg Look at my question here I asked some hours ago : https://stackoverflow.com/questions/57103020/how-can-i-get-a-icon-and-how-to-use-it-with-the-geticon-method I want to create this treeview style but without icon but using the Graphics.FillPolygon – Daniel Lip Jul 19 '19 at 11:40
  • @GyörgyKőszeg I'm calling it in form1 paint event : private void Form1_Paint(object sender, PaintEventArgs e) { AdvancedTreeView.FillPolygonPoint(e); } And then using a small method in the class advancedtreeview that return the image. How can I call the FillPolygonPoint in a constructor ? – Daniel Lip Jul 19 '19 at 11:44
  • When I'm running the program it's getting to the OnDrawMode but it's not drawing it like the icons in your answer. It's just drawing a triangle on the top left corner of the form. – Daniel Lip Jul 19 '19 at 11:47
  • 1
    I meant you should initialize your image in the constructor so it will not be null. Something like `myImage = new Bitmap(...)`. Or `myImage = Resources.ArrowDown;` if you added a resource to your project. – György Kőszeg Jul 19 '19 at 11:48
  • @GyörgyKőszeg I'm init it inside the FillPolygonPoint method : myimage = new Bitmap(10,10,e.Graphics); nd then when I return the image in the GetIcon method. When running the game now it's not null. But yet I can't add child nodes in the designer it's not showing any exceptions but it's not adding child nodes only root nodes. – Daniel Lip Jul 19 '19 at 11:52
  • 1
    Don't panic, I will write an answer once I will have time. – György Kőszeg Jul 19 '19 at 11:53
  • I added an answer. I will link here your duplicate as well. – György Kőszeg Jul 19 '19 at 12:46

1 Answers1

1

So here is a more complete version of the "original" AdvancedTreeView sample from the old answer.

New members are openedIcon and closedIcon fields, ArrowColor property and GetIcon/InitIcon methods.

public class AdvancedTreeView : TreeView
{
    private Bitmap openedIcon, closedIcon;

    public AdvancedTreeView()
    {
        DrawMode = TreeViewDrawMode.OwnerDrawText;
        ShowLines = false;
        AlternateBackColor = BackColor;
        ArrowColor = SystemColors.WindowText;
    }

    public Color AlternateBackColor { get; set; }
    public Color ArrowColor { get; set; }

    protected override void OnDrawNode(DrawTreeNodeEventArgs e)
    {
        // background
        Color backColor = (GetTopNodeIndex(e.Node) & 1) == 0 ? BackColor : AlternateBackColor;
        using (Brush b = new SolidBrush(backColor))
        {
            e.Graphics.FillRectangle(b, new Rectangle(0, e.Bounds.Top, ClientSize.Width, e.Bounds.Height));
        }

        // icon
        if (e.Node.Nodes.Count > 0)
        {
            Image icon = GetIcon(e.Node.IsExpanded);
            e.Graphics.DrawImage(icon, e.Bounds.Left - icon.Width - 3, e.Bounds.Top);
        }

        // text (due to OwnerDrawText mode, indenting of e.Bounds will be correct)
        TextRenderer.DrawText(e.Graphics, e.Node.Text, Font, e.Bounds, ForeColor);

        // indicate selection (if not by backColor):
        if ((e.State & TreeNodeStates.Selected) != 0)
            ControlPaint.DrawFocusRectangle(e.Graphics, e.Bounds);
    }

    private int GetTopNodeIndex(TreeNode node)
    {
        while (node.Parent != null)
            node = node.Parent;

        return Nodes.IndexOf(node);
    }

    private Image GetIcon(bool nodeIsExpanded)
    {
        if (openedIcon == null)
            InitIcons();
        return nodeIsExpanded ? openedIcon : closedIcon;
    }

    private void InitIcons()
    {
        openedIcon = new Bitmap(16, 16);
        closedIcon = new Bitmap(16, 16);
        using (Brush b = new SolidBrush(ArrowColor))
        {
            using (Graphics g = Graphics.FromImage(openedIcon))
                g.FillPolygon(b, new[] { new Point(0, 0), new Point(15, 0), new Point(8, 15), });
            using (Graphics g = Graphics.FromImage(closedIcon))
                g.FillPolygon(b, new[] { new Point(0, 0), new Point(15, 8), new Point(0, 15), });
        }
    }
}

Designer after setting the colors and adding nodes:

enter image description here

At runtime:

enter image description here

It still can be improved by invalidating colors when they are changed and so on...

György Kőszeg
  • 17,093
  • 6
  • 37
  • 65
  • Working great. Thank you. – Daniel Lip Jul 19 '19 at 13:42
  • For some reason it's changing the colors the back colors only for the second root node. I added to the collection in the designer two root nodes. Both have childs. The first one when expand it it's not changing to the light blue only in the second root. – Daniel Lip Jul 19 '19 at 13:51
  • I tried now with root nodes it seems like only the second root node and his child have the light blue background. The others have only the DeepSkyBlue – Daniel Lip Jul 19 '19 at 13:54
  • 1
    _"The first one when expand it it's not changing to the light blue only in the second root."_ - because it's not how it works. The color alternates per root node: `GetTopNodeIndex(e.Node) & 1` is 0 for even root nodes and is 1 for odd ones. If you need to alternate by open/closed state adjust the back color in `OnDrawNode` by `e.Node.IsExpanded`. – György Kőszeg Jul 19 '19 at 14:21
  • 1
    Please ask new questions as new posts instead of comments. – György Kőszeg Jul 19 '19 at 17:58
  • I did thanks. https://stackoverflow.com/questions/57117625/how-can-i-make-that-i-will-be-able-to-drag-with-the-mouse-the-nodes-and-replace – Daniel Lip Jul 19 '19 at 18:04