简体   繁体   中英

Keeping Adorner Glyphs On Top for Selected Control in Winforms Designer

TLDR: I'm seeking the correct method for timing the rendering of a juxtaposed graphic for a particular control on a design surface so that the graphic always is painted ahead of the adornment glyphs when that control is selected.

This question concerns control designers for Winforms: When the user places a control on the design surface, I want to display a graphic above the client area of the control. I have succeeded to some extent doing that for a TableLayoutPanel (TLP) control by overriding its OnPaint event handler then using the e.Graphics object available to paint a peach-colored rectangle. Below is an image showing the results: a painted graphic that spans the width of the control and is 35 pixels high--remember, this is a designer instance of a control placed on a design surface (created with a BasicLoader):

在此处输入图片说明

However, within the designer, if I resize the control, the graphic always ends up below the resize glyph (the glyph that has the North/South and West/East arrows on it):

在此处输入图片说明

I've tried creating and maintaining various Boolean flags to suppress the OnPaint message under certain circumstances. For instance, I set a flag to indicate that the control was just resized (to see how I did that, see my recent question: BeginResize/EndResize Event for Control on WinForms Design Surface ) in order to suppress the painting of the graphic, but that didn't work because an OnPaint event is inevitably raised after I've cleared a flag. I don't want saddle this question with details of all the flags and places I tried to use/set them but suffice it to say that I painstakingly spent hours experimenting--to no avail. I've concluded that there must be a better way.

How can I ensure that the glyphs remain on top when I paint my graphics?

Thank you!

I can think of a few solutions, including the followings:

  • Using Padding of the TableLayoutPanel
  • Using Adorner and Glyph
  • Creating a custom panel, having header and editable content

I think the first solution will suit you well, however the other solutions also some points.

I can also think of a solution based on NativeWindow like what has been implemented in ErrorProvider , but It makes the post toooooo lengthy while the existing options are good enough. So I leave it to you if you like to pursue the idea.

Solution 1 - Using Padding of the TableLayoutPanel

This solution is for both design-time and run-time

TableLayoutPanel has a Padding property and its layout engine respects to the padding well. You can use the padding area to render whatever you want:

在此处输入图片说明

public class MyTLP : TableLayoutPanel
{
    public MyTLP()
    {
        Padding = new Padding(0, 30, 0, 0);
    }
    protected override void OnPaint(PaintEventArgs e)
    {
        base.OnPaint(e);
        e.Graphics.FillRectangle(Brushes.Orange,
            new Rectangle(0, 0, ClientRectangle.Width, Padding.Top));
    }
}

Solution 2 - Using Adorner and Glyph

This solution is just for design-time

For design-time rendering, I'll handle it using Adorner and Glyph .

If I was creating my custom designer, all the code belong to the control designer, but since you don't want to create a new control designer for TableLayoutPanel , then the same way that I injected a new custom action in the action lists , here I'll get BehaviorService and I'll inject an adorner to the adorners of the control at design time and this will be the result:

在此处输入图片说明

The behavior is quite similar to the other glyphs, it will be resized automatically when the control resizes and you don't need to do anything specific to handle the resize at design time.

Please note: It's a design-time solution and the painting is just being done at designer. In case you need a run-time solution, you need a totally different solution.

Here is the code:

using System;
using System.ComponentModel.Design;
using System.Drawing;
using System.Windows.Forms;
using System.Windows.Forms.Design;
using System.Windows.Forms.Design.Behavior;
public class MyTLP : TableLayoutPanel
{
    private IDesignerHost designerHost;
    private BehaviorService behaviorService;
    protected override void OnHandleCreated(EventArgs e)
    {
        base.OnHandleCreated(e);
        if (DesignMode && Site != null)
        {
            designerHost = Site.GetService(typeof(IDesignerHost)) as IDesignerHost;
            behaviorService = designerHost?.GetService(typeof(BehaviorService))
                as BehaviorService;
            if (behaviorService != null)
            {
                var adorner = new Adorner();
                behaviorService.Adorners.Insert(0, adorner);
                adorner.Glyphs.Add(new MyTLPGlypg(behaviorService, this));
            }
        }
    }
}
class MyTLPGlypg : Glyph
{
    Control control;
    BehaviorService behaviorSvc;
    public MyTLPGlypg(BehaviorService behaviorSvc, Control control) :
        base(new MyBehavior())
    {
        this.behaviorSvc = behaviorSvc;
        this.control = control;
    }
    public override Rectangle Bounds
    {
        get
        {
            var edge = behaviorSvc.ControlToAdornerWindow(control);
            var h = 30;
            return new Rectangle(edge.X, edge.Y - h, control.Size.Width, h);
        }
    }
    public override Cursor GetHitTest(Point p)
    {
        //Uncomment if you want to attach a specific behavior
        //if (Bounds.Contains(p)) return Cursors.Hand;
        return null;
    }
    public override void Paint(PaintEventArgs pe)
    {
        pe.Graphics.FillRectangle(Brushes.Orange, Bounds);
    }
}
class MyBehavior : Behavior
{
    public override bool OnMouseUp(Glyph g, MouseButtons button)
    {
        //Do something and return true, meand eventhandled
        return true;
    }
}

Note:

To learn more about anchors, glyphs and behavior, take a look at the following links:

Solution 3 - Creating a custom panel, having header and editable content

You can create a custom panel having header and editable content. Then at design time disallow user from dropping content on the header part:

To do so, you need to create a new designer which enables the inner panel on design-time by calling EnableDesignMode method. Then for the inner panel, you need to create a designer which disables moving, resizing and removes some properties from designer.

I've posted a detailed answer here: UserControl with header and content - Allow dropping controls in content panel and Prevent dropping controls in header at design time

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM