-1

TLDR:我正在寻找正确的方法来为设计表面上的特定控件的并列图形的渲染计时,以便在选择该控件时始终在装饰字形之前绘制图形。

这个问题与 Winforms 的控件设计者有关:当用户将控件放置在设计图面上时,我想在控件的客户区上方显示一个图形。通过覆盖其 OnPaint 事件处理程序,然后使用可用于绘制桃色矩形的 e.Graphics 对象,我在某种程度上成功地为 TableLayoutPanel (TLP) 控件做到了这一点。下面是显示结果的图像:跨越控件宽度且高 35 像素的绘制图形 - 请记住,这是放置在设计图面上的控件的设计器实例(使用 BasicLoader 创建):

在此处输入图像描述

但是,在设计器中,如果我调整控件的大小,图形总是在调整大小的字形下方(上面有北/南和西/东箭头的字形):

在此处输入图像描述

在某些情况下,我尝试创建和维护各种布尔标志来抑制 OnPaint 消息。例如,我设置了一个标志来指示控件刚刚调整大小(要了解我是如何做到的,请参阅我最近的问题:BeginResize/EndResize Event for Control on WinForms Design Surface)以抑制图形的绘制,但是这不起作用,因为在我清除标志后不可避免地会引发 OnPaint 事件。我不想用我尝试使用/设置它们的所有标志和位置的详细信息来回答这个问题,但我只想说我煞费苦心地花了几个小时进行实验——但无济于事。我得出结论,一定有更好的方法。

当我绘制图形时,如何确保字形保持在顶部?

谢谢!

4

1 回答 1

1

我可以想到一些解决方案,包括以下内容:

  • 使用 PaddingTableLayoutPanel
  • 使用装饰器和字形
  • 创建自定义面板,具有标题和可编辑内容

我认为第一个解决方案很适合您,但是其他解决方案也有一些要点。

我也可以考虑一个基于NativeWindow类似已经实现的解决方案ErrorProvider,但是它使帖子太长了,而现有的选项已经足够好了。所以,如果你想追求这个想法,我把它留给你。

解决方案 1 - 使用 TableLayoutPanel 的填充

此解决方案适用于设计时和运行时

TableLayoutPanel有一个Padding属性,它的布局引擎尊重填充。您可以使用填充区域来渲染您想要的任何内容:

在此处输入图像描述

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));
    }
}

解决方案 2 - 使用 Adorner 和 Glyph

此解决方案仅适用于设计时

对于设计时渲染,我将使用Adorner和来处理它Glyph

如果我正在创建我的自定义设计器,所有代码都属于控件设计器,但是由于您不想为其创建新的控件设计器TableLayoutPanel,那么就像我在操作列表中注入新的自定义操作一样,这里我'会得到BehaviorService,我会在设计时向控件的装饰器注入一个装饰器,这将是结果:

在此处输入图像描述

该行为与其他字形非常相似,它会在控件调整大小时自动调整大小,您无需在设计时执行任何特定操作来处理调整大小。

请注意:这是一个设计时解决方案,并且绘画只是在设计师处完成。如果您需要运行时解决方案,则需要完全不同的解决方案。

这是代码:

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;
    }
}

笔记:

要了解有关锚点、字形和行为的更多信息,请查看以下链接:

解决方案 3 - 创建自定义面板,具有标题和可编辑内容

您可以创建具有标题和可编辑内容的自定义面板。然后在设计时禁止用户在标题部分放置内容:

为此,您需要创建一个新设计器,通过调用EnableDesignMode方法在设计时启用内部面板。然后对于内部面板,您需要创建一个设计器,该设计器禁用移动、调整大小并从设计器中删除一些属性。

我在这里发布了一个详细的答案:带有标题和内容的用户控件 - 允许在内容面板中放置控件并防止在设计时在标题中放置控件

于 2020-02-01T15:31:00.180 回答