34
votes

I'm making an interface to edit scenarios for a game. Basically it consists of events, which have nested conditions and actions. So, I planned on using two treeviews - one for selecting the event, and other for selecting the condition/action inside the event to edit.

Now, you see, if I select an event (in left treeview) and then try to select something in the right treeview, the left treeview will stop showing the blue selection rectangle. This is obviously bad because now the user doesn't know which event is he editing!

The only way I found to retain some sort of information about what is the current selection is by using SelectedImageIndex, but that's only one little image that will be different.

Is there any other way to highlight the treenode while there is no focus on the treeview? I know I can just use Graphics.DrawRectangle or something, but I heard that drawing should be done in Paint event and treeview has no paint event... So I guess if I draw it on the event of losing focus, and then drag the form out of the screen or something, it will be "erased"?

Anyway, please tell me if you got an idea (other than using a separate icon for selected and not selected treenode).

6
the image you have posted is gone. do you have a copy somewhere? i'll remove the link in the meantime..Neuron

6 Answers

71
votes

What you are looking for is the HideSelection property on the TreeView.

From MSDN:

Gets or sets a value indicating whether the selected tree node remains highlighted even when the tree view has lost the focus.

Link: http://msdn.microsoft.com/en-us/library/system.windows.forms.treeview.hideselection.aspx

Code:

TreeView.HideSelection = false;
17
votes

It is still shown but only in light grey which depending on your screen and current setup can be near in visible!

Override the OnDrawNode event. So you create and new class (call it "SpecialTreeView") an inherit from the Microsoft TreeView like class SpecialTreeView : TreeView. Then you add the following event override:

protected override void OnDrawNode(DrawTreeNodeEventArgs e)
{
    TreeNodeStates treeState = e.State;
    Font treeFont = e.Node.NodeFont ?? e.Node.TreeView.Font;

    // Colors.
    Color foreColor = e.Node.ForeColor;
    string strDeselectedColor = @"#6B6E77", strSelectedColor = @"#94C7FC";
    Color selectedColor = System.Drawing.ColorTranslator.FromHtml(strSelectedColor);
    Color deselectedColor = System.Drawing.ColorTranslator.FromHtml(strDeselectedColor);

    // New brush.
    SolidBrush selectedTreeBrush = new SolidBrush(selectedColor);
    SolidBrush deselectedTreeBrush = new SolidBrush(deselectedColor);

    // Set default font color.
    if (foreColor == Color.Empty)
        foreColor = e.Node.TreeView.ForeColor;

    // Draw bounding box and fill.
    if (e.Node == e.Node.TreeView.SelectedNode)
    {
        // Use appropriate brush depending on if the tree has focus.
        if (this.Focused)
        {
            foreColor = SystemColors.HighlightText;
            e.Graphics.FillRectangle(selectedTreeBrush, e.Bounds);
            ControlPaint.DrawFocusRectangle(e.Graphics, e.Bounds, foreColor, SystemColors.Highlight);
            TextRenderer.DrawText(e.Graphics, e.Node.Text, treeFont, e.Bounds,
                                         foreColor, TextFormatFlags.GlyphOverhangPadding);
        }
        else
        {
            foreColor = SystemColors.HighlightText;
            e.Graphics.FillRectangle(deselectedTreeBrush, e.Bounds);
            ControlPaint.DrawFocusRectangle(e.Graphics, e.Bounds, foreColor, SystemColors.Highlight);
            TextRenderer.DrawText(e.Graphics, e.Node.Text, treeFont, e.Bounds,
                                         foreColor, TextFormatFlags.GlyphOverhangPadding);
        }
    }
    else
    {
        if ((e.State & TreeNodeStates.Hot) == TreeNodeStates.Hot)
        {
            e.Graphics.FillRectangle(SystemBrushes.Window, e.Bounds);
            TextRenderer.DrawText(e.Graphics, e.Node.Text, hotFont, e.Bounds,
                                         System.Drawing.Color.Black, TextFormatFlags.GlyphOverhangPadding);
        }
        else
        {
            e.Graphics.FillRectangle(SystemBrushes.Window, e.Bounds);
            TextRenderer.DrawText(e.Graphics, e.Node.Text, treeFont, e.Bounds,
                                         foreColor, TextFormatFlags.GlyphOverhangPadding);
        }
    }
}

Compile the code and you should see "SpecialTreeView" in your tool box in the designer. Replace your TreeView with this new one using the same name and the only thing that will be different is the selection colours. When selected it will be selectedColor, when not selected the deselectedColor.

I hope this helps.

12
votes

Fast solution:

Set the properties:

  • HideSelection = false;
  • DrawMode = TreeViewDrawMode.OwnerDrawText;

Then in the DrawNode event handler simply do:

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

On Windwos 7 this restores the old rendering, including the dashed box around the selection (which actually looks a bit outdated). The text will be white with focus, and black without focus. The background stays blue and visible.

This answer is not new, the others also contain these steps, but this is the minimal needed (at least in Windows 7, didn't test other OS's).

8
votes

Not absolutely perfect solution, but quite near:

treeView.HideSelection = false;
treeView.DrawMode = TreeViewDrawMode.OwnerDrawText;
treeView.DrawNode += (o, e) =>
{
    if (!e.Node.TreeView.Focused && e.Node == e.Node.TreeView.SelectedNode)
    {
        Font treeFont = e.Node.NodeFont ?? e.Node.TreeView.Font;
        e.Graphics.FillRectangle(Brushes.Gray, e.Bounds);
        ControlPaint.DrawFocusRectangle(e.Graphics, e.Bounds, SystemColors.HighlightText, SystemColors.Highlight);
        TextRenderer.DrawText(e.Graphics, e.Node.Text, treeFont, e.Bounds, SystemColors.HighlightText, TextFormatFlags.GlyphOverhangPadding);
    }
    else
        e.DrawDefault = true;
};
treeView.MouseDown += (o, e) =>
{
    TreeNode node = treeView.GetNodeAt(e.X, e.Y);
    if (node != null && node.Bounds.Contains(e.X, e.Y))
        treeView.SelectedNode = node;
};
3
votes

Found an easier way:

  1. Set TreeView.HideSelection = True
  2. Add the following to TreeView.AfterSelect-Callback:
private void treeViewBenutzerverwaltung_AfterSelect(object sender, TreeViewEventArgs e)
{
    // Select new node
    e.Node.BackColor = SystemColors.Highlight;
    e.Node.ForeColor = SystemColors.HighlightText;
    if (_lastSelectedNode != null)
    {
        // Deselect old node
        _lastSelectedNode.BackColor = SystemColors.Window;
        _lastSelectedNode.ForeColor = SystemColors.WindowText;
    }
    _lastSelectedNode = e.Node;
}
1
votes

Similar to the previous one but with the appearance more similar to the Win10 standard:

treeView.HideSelection = false;
treeView.DrawMode = TreeViewDrawMode.OwnerDrawText;

treeView.DrawNode += (o, e) =>
{
    if (e.Node == e.Node.TreeView.SelectedNode)
    {
        Font font = e.Node.NodeFont ?? e.Node.TreeView.Font;
        Rectangle r = e.Bounds;
        r.Offset(0, 1);
        Brush brush = e.Node.TreeView.Focused ? SystemBrushes.Highlight : Brushes.Gray;
        e.Graphics.FillRectangle(brush, e.Bounds);
        TextRenderer.DrawText(e.Graphics, e.Node.Text, font, r, SystemColors.HighlightText, TextFormatFlags.GlyphOverhangPadding);
    }
    else
        e.DrawDefault = true;
};

treeView.MouseDown += (o, e) =>
{
    TreeNode node = treeView.GetNodeAt(e.Location);
    if (node != null && node.Bounds.Contains(e.Location)) treeView.SelectedNode = node;
};