在C# WinForms中,如何在面板内部的控件上方绘制图形?

46
我知道这个问题已经被问了很多次,但到目前为止我还没有找到一个好的解决方案。
我有一个带有其他控件的面板。 我想在它上面画一条线,并在面板中所有控件的顶部显示。
我发现了3种解决方案(没有一种能按照我想要的方式工作):
1. 获取桌面 DC 并在屏幕上绘制。 如果它们重叠窗体,则会在其他应用程序上绘制。
2. 重写面板的 "CreateParams":
3. 在面板上使用 Graphics 绘图。

=

protected override CreateParams CreateParams {  
  get {  
    CreateParams cp;  
    cp = base.CreateParams;  
    cp.Style &= ~0x04000000; //WS_CLIPSIBLINGS
    cp.Style &= ~0x02000000; //WS_CLIPCHILDREN
    return cp;  
  }  
}           

//注意,我也尝试禁用WS_CLIPSIBLINGS并在OnPaint()中绘制线条。但是...由于面板的OnPaint在其中的控件的OnPaint之前被调用,因此内部控件的绘制仅会在线条上方绘制。
我看到有人建议使用消息过滤器来侦听WM_PAINT消息,并使用计时器,但我认为这个解决方案既不是“良好的实践”,也不是有效的解决方案。
你会怎么做?决定控件内部完成绘制后X毫秒,然后将计时器设置为X毫秒吗?


这个屏幕截图显示了关闭WS_CLIPSIBLINGS和WS_CLIPCHILDREN的面板。
蓝色线条是在Panel的OnPaint中绘制的,只是被文本框和标签绘制。
红色线条之所以在顶部绘制,是因为它不是从面板的OnPaint中绘制的(实际上是由于点击按钮而绘制的)。
alt text


第三步:创建一个透明层并在该层上绘制。 我使用以下方法创建了一个透明控件:
protected override CreateParams CreateParams {  
  get {  
    CreateParams cp = base.CreateParams;  
    cp.ExStyle |= 0x00000020; //WS_EX_TRANSPARENT  
    return cp;  
  }  
}

问题仍然存在,即将透明控件放在面板及其所有控件的顶部。 我尝试使用 "BringToFront()" 将其置于最前面,但似乎没有帮助。 我将其放在了 Line 控件的 OnPaint() 处理程序中。 我应该尝试把它放在别的地方吗? - 这也会导致在面板上方有另一个控件时出现问题。(捕捉鼠标点击等。)
任何帮助都将不胜感激!
**编辑: 黑色线条是我尝试做的示例。(使用 Windows 绘图工具进行绘制)

alt text


我必须问一下:为什么?(抱歉,我讨厌别人问这个问题 - 我不是说这是一个坏主意,但我很好奇) - MusiGenesis
好的,我猜测一下:您有一个控件,用户可以在窗体上拖动,这里的线是在拖动控件和其他控件之间绘制的? - MusiGenesis
我已经添加了一张照片来展示我想要做的事情。我现在正在尝试您的标签解决方案。这是一个权宜之计,但现在可能足够了。 为了讨论起见,如果这条线不是水平或垂直的,有什么想法吗? - dtroy
这篇文章可能对这个主题有所帮助,但不确定它是否是解决方案:http://www.codeproject.com/KB/GDI-plus/WindowGraphics.aspx 我觉得我应该加上它以帮助其他人。 - dtroy
这里是另一种解决方案... - TaW
10个回答

22

原来这比我想象中的要容易得多。感谢您没有接受我的其他答案。以下是创建Fline(浮动线 - 对不起,太晚了)的两个步骤:

alt text

第一步:向项目添加一个UserControl并将其命名为“Fline”。添加以下using语句:

using System.Drawing.Drawing2D;

步骤2: 添加以下内容到Fline的调整大小事件中:

int wfactor = 4; // half the line width, kinda
// create 6 points for path
Point[] pts = {
    new Point(0, 0), 
    new Point(wfactor, 0), 
    new Point(Width, Height - wfactor),
    new Point(Width, Height) ,
    new Point(Width - wfactor, Height),
    new Point(0, wfactor) };
// magic numbers! 
byte[] types = {
    0, // start point
    1, // line
    1, // line
    1, // line
    1, // line
    1 }; // line 
GraphicsPath path = new GraphicsPath(pts, types);
this.Region = new Region(path);

编译后,将Fline拖到您的窗体或面板上。重要提示:默认的BackColor与表单相同,因此请在设计器中更改Fline的BackColor为红色或其他明显颜色。这个奇怪的小问题是,在设计器中拖动它时,它会显示为实心块,直到您释放它 - 这不是什么大问题。

此控件可以出现在任何其他控件的前面或后面。如果将Enabled设置为false,它仍然可见但不会干扰控件下面的鼠标事件。

当然,您需要根据自己的目的来增强它,但这显示了基本原理。您可以使用相同的技术创建任何形状的控件(我最初的测试制作了一个三角形)。

更新:这也可以做成漂亮而简洁的一行代码。只需将以下代码放入您的UserControl的Resize事件中:

this.Region=new Region(new System.Drawing.Drawing2D.GraphicsPath(new Point[]{new Point(0,0),new Point(4,0),new Point(Width,Height-4),new Point(Width,Height),new Point(Width-4,Height),new Point(0,4)},new byte[]{0,1,1,1,1,1}));

MusiGenesis,谢谢你的回答。它帮了我很多。但是...今天我又试着玩了一下,并且尝试创建一个类似于你上面回答中的红线的1像素宽虚线。由于我不能使用开放的GraphicsPath,所以我试图使控件透明。 - dtroy
然后在其上绘制一条线。但我找不到适用于所有控件的方法等...你有什么想法吗?谢谢! - dtroy
7
过于紧凑的一句话往往不是好事。 - Glenn Maynard

10
如果你只想要一条简单的水平或垂直线,可以在主面板上添加另一个面板(禁用,以便不会响应任何鼠标事件),将其高度(或宽度)设置为3或4像素(或者您想要的任何值),并将其放置在最前面。如果需要在运行时更改线的位置,只需移动该面板并使其可见和不可见即可。下面是示例图: 替代文本 您甚至可以单击任何地方,这些线都不会干扰。该线可以画在任何控件上面(尽管ComboBox或DatePicker的下拉部分仍然显示在上方,这非常好)。蓝色线是相同的线但被放到了背后。

似乎必须先将标签添加到面板中才能最后绘制。尝试使用BringToFront()并没有什么帮助。 - dtroy
你可以使用另一个面板(1或2像素宽)代替标签。如果将其置于最前面,它将始终位于面板上的所有其他内容之前。 - MusiGenesis
在使用它时,我注意到了一些问题: 如果该行位于一个禁用了“WS_CLIPSIBLINGS”的控件上方,则该行不会被绘制在其上方。否则,看起来还好。但是如果我们想要一条对角线怎么办? - dtroy
不是它没有起作用,只是我不想挂钩控件的事件处理程序。我不知道我正在绘制的表面上会有多少控件。如果我们画一些比线条更复杂的东西,比如移动形状等等...你知道我的意思吗? - dtroy
嗯... 你刚好间接回答了一个困扰我整个下午的问题:我一直在寻找一种方法,在面板内部的所有其他控件上方绘制一个简单的标签,而不会使标签干扰到其下的控件的鼠标输入。如果没有你的建议,我想我需要自己绘制文本,这也是我来到这里的原因。但事实证明,我只需要禁用标签就可以了。谢谢! - neminem
显示剩余2条评论

7
是的,这是可以做到的。问题在于面板和其上的控件都是单独的窗口(在API意义上),因此都是单独的绘图表面。没有一个绘图表面可以绘制以获得这种效果(除了顶层屏幕表面,但在其上随意绘制被认为是不礼貌的)。
(咳咳-hack-咳咳)技巧是在控件下方的面板上绘制线条,并在每个控件本身上绘制它,从而产生如下结果(即使在单击按钮和移动鼠标时也会持续存在):

alt text

创建一个 WinForms 项目(默认情况下应该带有 Form1)。在面板上添加一个面板(命名为“panel1”)和两个按钮(“button1”和“button2”),如图所示。在窗体的构造函数中添加以下代码:
panel1.Paint += PaintPanelOrButton;
button1.Paint += PaintPanelOrButton;
button2.Paint += PaintPanelOrButton;

然后将此方法添加到表单的代码中:

private void PaintPanelOrButton(object sender, PaintEventArgs e)
{
    // center the line endpoints on each button
    Point pt1 = new Point(button1.Left + (button1.Width / 2),
            button1.Top + (button1.Height / 2));
    Point pt2 = new Point(button2.Left + (button2.Width / 2),
            button2.Top + (button2.Height / 2));

    if (sender is Button)
    {
        // offset line so it's drawn over the button where
        // the line on the panel is drawn
        Button btn = (Button)sender;
        pt1.X -= btn.Left;
        pt1.Y -= btn.Top;
        pt2.X -= btn.Left;
        pt2.Y -= btn.Top;
    }

    e.Graphics.DrawLine(new Pen(Color.Red, 4.0F), pt1, pt2);
}

在每个控件的Paint事件中绘制类似这样的内容,以使线条持久存在。在.NET中直接绘制控件很容易,但是当有人单击按钮或将鼠标移动到上面时,绘制的任何内容都会被清除(除非像这里一样在Paint事件中不断重绘)。请注意,为使此方法有效,任何覆盖的控件都必须具有Paint事件。我相信您需要修改此示例以实现所需功能。如果您想要一个通用的好函数,请发布它。更新:此方法无法在滚动条、文本框、组合框、列表视图或基本上任何带有文本框类型的部分的控件中工作(并不是因为它仅在上面的示例中偏移了按钮 - 您根本无法从其Paint事件中在文本框上绘制,至少我是这样)。希望这不会成为问题。

5
一个Windows窗体面板是控件的容器。如果您想在面板上方绘制一些东西,覆盖其他控件,那么您需要另一个控件(置于Z序列的顶部)。
幸运的是,您可以创建具有非矩形边框的Windows窗体控件。请查看这个技术:http://msdn.microsoft.com/en-us/library/aa289517(VS.71).aspx 要在屏幕上绘制内容,使用标签控件,并关闭AutoSize。然后,附加到Paint事件并设置Size和Region属性。
下面是一个代码示例:
private void label1_Paint(object sender, PaintEventArgs e)
{
    System.Drawing.Drawing2D.GraphicsPath myGraphicsPath = new  System.Drawing.Drawing2D.GraphicsPath();
    myGraphicsPath.AddEllipse(new Rectangle(0, 0, 125, 125));
    myGraphicsPath.AddEllipse(new Rectangle(75, 75, 20, 20));
    myGraphicsPath.AddEllipse(new Rectangle(120, 0, 125, 125));
    myGraphicsPath.AddEllipse(new Rectangle(145, 75, 20, 20));
    //Change the button's background color so that it is easy
    //to see.
    label1.BackColor = Color.ForestGreen;
    label1.Size = new System.Drawing.Size(256, 256);
    label1.Region = new Region(myGraphicsPath);
}

4
我能想到的唯一简单的解决方案是为想要在其上绘制的每个控件创建Paint事件处理程序,然后在这些处理程序之间协调线条绘制。虽然这不是最方便的解决方案,但这将使您能够在控件的顶部绘制。假设按钮是面板的子控件:
panel.Paint += new PaintEventHandler(panel_Paint);
button.Paint += new PaintEventHandler(button_Paint);

protected void panel_Paint(object sender, PaintEventArgs e)
{
    //draw the full line which will then be partially obscured by child controls
}

protected void button_Paint(object sender, PaintEventArgs e)
{
    //draw the obscured line portions on the button
}

4
创建一个新的LineControl:像这样的控件:
然后在InitializeComponent之后调用BringToFront()。
public partial class MainForm : Form
    {
        public MainForm()
        {
            InitializeComponent();
            this.simpleLine1.BringToFront();
        }
    }



using System;
using System.Windows.Forms;
using System.Drawing;
using System.Collections.Generic;

public class SimpleLine : Control
{
    private Control parentHooked;   
    private List<Control> controlsHooked;

    public enum LineType
    {
        Horizontal,
        Vertical,
        ForwardsDiagonal,
        BackwardsDiagonal
    }

    public event EventHandler AppearanceChanged;
    private LineType appearance;
    public virtual LineType Appearance
    {
        get
        {
            return appearance;
        }
        set
        {
            if (appearance != value)
            {
                this.SuspendLayout();
                switch (appearance)
                {
                    case LineType.Horizontal:
                        if (value == LineType.Vertical)
                        {
                            this.Height = this.Width;
                        }

                        break;
                    case LineType.Vertical:
                        if (value == LineType.Horizontal)
                        {
                            this.Width = this.Height;
                        }
                        break;
                }
                this.ResumeLayout(false);

                appearance = value;
                this.PerformLayout();
                this.Invalidate();
            }
        }
    }
    protected virtual void OnAppearanceChanged(EventArgs e)
    {
        if (AppearanceChanged != null) AppearanceChanged(this, e);
    }

    public event EventHandler LineColorChanged;
    private Color lineColor;
    public virtual Color LineColor
    {
        get
        {
            return lineColor;
        }
        set
        {
            if (lineColor != value)
            {
                lineColor = value;
                this.Invalidate();
            }
        }
    }
    protected virtual void OnLineColorChanged(EventArgs e)
    {
        if (LineColorChanged != null) LineColorChanged(this, e);
    }

    public event EventHandler LineWidthChanged;
    private float lineWidth;
    public virtual float LineWidth
    {
        get
        {
            return lineWidth;
        }
        set
        {
            if (lineWidth != value)
            {
                if (0 >= value)
                {
                    lineWidth = 1;
                }
                lineWidth = value;
                this.PerformLayout();
            }
        }
    }
    protected virtual void OnLineWidthChanged(EventArgs e)
    {
        if (LineWidthChanged != null) LineWidthChanged(this, e);
    }

    public SimpleLine()
    {
        base.SetStyle(ControlStyles.AllPaintingInWmPaint | ControlStyles.Selectable, false);
        base.SetStyle(ControlStyles.SupportsTransparentBackColor, true);
        base.BackColor = Color.Transparent;

        InitializeComponent();

        appearance = LineType.Vertical;
        LineColor = Color.Black;
        LineWidth = 1;
        controlsHooked = new List<Control>();

        this.ParentChanged += new EventHandler(OnSimpleLineParentChanged);
    }

    private void RemoveControl(Control control)
    {
        if (controlsHooked.Contains(control))
        {
            control.Paint -= new PaintEventHandler(OnControlPaint);
            if (control is TextboxX)
            {
                TextboxX text = (TextboxX)control;
                text.DoingAPaint -= new EventHandler(text_DoingAPaint);
            }
            controlsHooked.Remove(control);
        }
    }

    void text_DoingAPaint(object sender, EventArgs e)
    {
        this.Invalidate();
    }

    private void AddControl(Control control)
    {
        if (!controlsHooked.Contains(control))
        {
            control.Paint += new PaintEventHandler(OnControlPaint);
            if (control is TextboxX)
            {
                TextboxX text = (TextboxX)control;
                text.DoingAPaint += new EventHandler(text_DoingAPaint);
            }
            controlsHooked.Add(control);
        }
    }

    private void OnSimpleLineParentChanged(object sender, EventArgs e)
    {
        UnhookParent();

        if (Parent != null)
        {

            foreach (Control c in Parent.Controls)
            {
                AddControl(c);
            }
            Parent.ControlAdded += new ControlEventHandler(OnParentControlAdded);
            Parent.ControlRemoved += new ControlEventHandler(OnParentControlRemoved);
            parentHooked = this.Parent;
        }
    }

    private void UnhookParent()
    {
            if (parentHooked != null)
            {
                foreach (Control c in parentHooked.Controls)
                {
                    RemoveControl(c);
                }
                parentHooked.ControlAdded -= new ControlEventHandler(OnParentControlAdded);
                parentHooked.ControlRemoved -= new ControlEventHandler(OnParentControlRemoved);
                parentHooked = null;
            }
    }

    private void OnParentControlRemoved(object sender, ControlEventArgs e)
    {
        RemoveControl(e.Control);
    }   

    private void OnControlPaint(object sender, PaintEventArgs e)
    {
        int indexa =Parent.Controls.IndexOf(this) , indexb = Parent.Controls.IndexOf((Control)sender);
        //if above invalidate on paint
        if(indexa < indexb)
        {
            Invalidate();
        }
    }

    private void OnParentControlAdded(object sender, ControlEventArgs e)
    {
        AddControl(e.Control);
    }

    private System.ComponentModel.IContainer components = null;
    private void InitializeComponent()
    {
        components = new System.ComponentModel.Container();
    }
    protected override void Dispose(bool disposing)
    {
        if (disposing && (components != null))
        {
            components.Dispose();
        }
        base.Dispose(disposing);
    }

    protected override CreateParams CreateParams
    {
        get
        {
            CreateParams cp = base.CreateParams;
            cp.ExStyle |= 0x20;  // Turn on WS_EX_TRANSPARENT
            return cp;
        }
    }

    protected override void OnLayout(LayoutEventArgs levent)
    {
        switch (this.Appearance)
        {
            case LineType.Horizontal:
                this.Height = (int)LineWidth;
                this.Invalidate();
                break;
            case LineType.Vertical:
                this.Width = (int)LineWidth;
                this.Invalidate();
                break;
        }

        base.OnLayout(levent);
    }

    protected override void OnPaintBackground(PaintEventArgs pevent)
    {
        //disable background paint
    }

    protected override void OnPaint(PaintEventArgs pe)
    {
        switch (Appearance)
        {
            case LineType.Horizontal:
                DrawHorizontalLine(pe);
                break;
            case LineType.Vertical:
                DrawVerticalLine(pe);
                break;
            case LineType.ForwardsDiagonal:
                DrawFDiagonalLine(pe);
                break;
            case LineType.BackwardsDiagonal:
                DrawBDiagonalLine(pe);
                break;
        }
    }

    private void DrawFDiagonalLine(PaintEventArgs pe)
    {
        using (Pen p = new Pen(this.LineColor, this.LineWidth))
        {
            pe.Graphics.DrawLine(p, this.ClientRectangle.X, this.ClientRectangle.Bottom,
                                    this.ClientRectangle.Right, this.ClientRectangle.Y);
        }
    }

    private void DrawBDiagonalLine(PaintEventArgs pe)
    {
        using (Pen p = new Pen(this.LineColor, this.LineWidth))
        {
            pe.Graphics.DrawLine(p, this.ClientRectangle.X, this.ClientRectangle.Y,
                                    this.ClientRectangle.Right, this.ClientRectangle.Bottom);
        }
    }

    private void DrawHorizontalLine(PaintEventArgs pe)
    {
        int  y = this.ClientRectangle.Height / 2;
        using (Pen p = new Pen(this.LineColor, this.LineWidth))
        {
            pe.Graphics.DrawLine(p, this.ClientRectangle.X, y,
                                    this.ClientRectangle.Width, y);
        }
    }

    private void DrawVerticalLine(PaintEventArgs pe)
    {
        int x = this.ClientRectangle.Width / 2;
        using (Pen p = new Pen(this.LineColor, this.LineWidth))
        {
            pe.Graphics.DrawLine(p,x, this.ClientRectangle.Y,
                                   x, this.ClientRectangle.Height);
        }
    }
}

编辑:添加对角线支持

我为控件添加了一些支持,当它们获得焦点时重新绘制。

文本框和组合框不能直接使用,您需要自己创建并连接它们的绘图命令,如下:

public class TextboxX : TextBox
{
    public event EventHandler DoingAPaint;
    protected override void WndProc(ref Message m)
    {
        switch ((int)m.Msg)
        {
            case (int)NativeMethods.WindowMessages.WM_PAINT:
            case (int)NativeMethods.WindowMessages.WM_ERASEBKGND:
            case (int)NativeMethods.WindowMessages.WM_NCPAINT:
            case 8465: //not sure what this is WM_COMMAND?
                if(DoingAPaint!=null)DoingAPaint(this,EventArgs.Empty);
                break;
        }           
        base.WndProc(ref m);
    }
}

这个还没有经过测试,我相信你可以改进它。


Hath,这本来是一个不错的解决方案,但是...看看(见下图)当你将鼠标悬停在线条下面的按钮或控件上时会发生什么,或者当控件获得焦点时会发生什么。http://i73.photobucket.com/albums/i201/sdjc1/temp/screen4.png - dtroy
@dtroy - 我已经添加了代码,当父控件集合中的另一个控件绘制时,它将失效... 对于文本框或组合框可能不太有效,但您可以看到我所做的来覆盖它们。不确定是否符合您的需求,但您可以使其适用于您所描述的内容。 - Hath
关于代码的快速评论(尚未运行):8465 = 0x02111,即OCM_COMMAND。这是您要获取的内容吗? 此外,在“OnSimpleLineParentChanged”中,当(Parent!= null)&&(parentHooked!= null)时,您可能希望从parentHooked分离事件。 谢谢。 - dtroy
@dtroy - 为parentHooked添加了detach。刚刚注意到,如果你想选择被线条覆盖的控件,对角线上可能会有一些问题..因为线条控件会遮挡它..我猜你可以以某种方式传递鼠标消息,但我不确定..有点不太优雅。 - Hath
这可能是为绘制一条线而编写的最多代码。 :) - MusiGenesis

3

以下是对解决方案#1的建议(获取桌面DC并在屏幕上绘制):

  1. 获取桌面DC及其图形对象 [Graphics.fromHDC(...)]
  2. 将所得到的图形对象的剪辑属性设置为当前窗体可见区域。(我还没有研究如何找到窗体的可见区域)
  3. 进行您的图形渲染。

2

编辑:我找到了解决递归绘制问题的方法。所以,对我来说,这看起来非常接近您想要实现的内容。

下面是我的解决方案。它使用原始问题中概述的第3种方法。代码有点长,因为涉及到三个类:

  1. 一个名为DecorationCanvas的私有类。它派生自Panel并使用WS_EX_TRANSPARENT提供透明画布以在其上绘制内容。
  2. 面板类本身,我称之为DecoratedPanel,它派生自Panel。
  3. 面板的设计器类DecoratedPanelDesigner,以确保可以在设计时保留ZOrder。

基本做法如下:

  • 在DecoratedPanel的构造函数中,创建DecorationCanvas的实例并将其添加到DecoratedPanel控件集合中。
  • 重写OnControlAdded和OnControlRemoved方法,用于自动钩住/取消钩住子控件的绘制事件,并确保DecorationCanvas始终处于ZOrder的顶部。
  • 每当包含的控件绘制时,使相应的DecorationCanvas矩形无效。
  • 重写OnResize和OnSizeChanged方法,以确保DecorationCanvas与DecoratedPanel具有相同的大小。(我尝试使用Anchor属性实现此目的,但某些地方失败了)。
  • 提供一个内部方法,在DecoratedPanelDesigner中重置DecorationCanvas的ZOrder。

在我的系统上(VS2010 / .net4 / Windows XP SP3)运行良好。以下是代码:

using System;
using System.ComponentModel;
using System.ComponentModel.Design;
using System.Drawing;
using System.Windows.Forms;
using System.Windows.Forms.Design;

namespace WindowsFormsApplication3
{
  [Designer("WindowsFormsApplication3.DecoratedPanelDesigner")]
  public class DecoratedPanel : Panel
  {
    #region decorationcanvas

    // this is an internal transparent panel.
    // This is our canvas we'll draw the lines on ...
    private class DecorationCanvas : Panel
    {
      public DecorationCanvas()
      {
        // don't paint the background
        SetStyle(ControlStyles.Opaque, true);
      }

      protected override CreateParams CreateParams
      {
        get
        {
          // use transparency
          CreateParams cp = base.CreateParams;
          cp.ExStyle |= 0x00000020; //WS_EX_TRANSPARENT
          return cp;
        }
      }
    }

    #endregion

    private DecorationCanvas _decorationCanvas;

    public DecoratedPanel()
    {
      // add our DecorationCanvas to our panel control
      _decorationCanvas = new DecorationCanvas();
      _decorationCanvas.Name = "myInternalOverlayPanel";
      _decorationCanvas.Size = ClientSize;
      _decorationCanvas.Location = new Point(0, 0);
      // this prevents the DecorationCanvas to catch clicks and the like
      _decorationCanvas.Enabled = false;
      _decorationCanvas.Paint += new PaintEventHandler(decoration_Paint);
      Controls.Add(_decorationCanvas);
    }

    protected override void Dispose(bool disposing)
    {
      if (disposing && _decorationCanvas != null)
      {
        // be a good citizen and clean up after yourself

        _decorationCanvas.Paint -= new PaintEventHandler(decoration_Paint);
        Controls.Remove(_decorationCanvas);
        _decorationCanvas = null;
      }

      base.Dispose(disposing);
    }

    void decoration_Paint(object sender, PaintEventArgs e)
    {
      // --- PAINT HERE ---
      e.Graphics.DrawLine(Pens.Red, 0, 0, ClientSize.Width, ClientSize.Height);
    }

    protected override void OnControlAdded(ControlEventArgs e)
    {
      base.OnControlAdded(e);

      if (IsInDesignMode)
        return;

      // Hook paint event and make sure we stay on top
      if (!_decorationCanvas.Equals(e.Control))
        e.Control.Paint += new PaintEventHandler(containedControl_Paint);

      ResetDecorationZOrder();
    }

    protected override void OnControlRemoved(ControlEventArgs e)
    {
      base.OnControlRemoved(e);

      if (IsInDesignMode)
        return;

      // Unhook paint event
      if (!_decorationCanvas.Equals(e.Control))
        e.Control.Paint -= new PaintEventHandler(containedControl_Paint);
    }

    /// <summary>
    /// If contained controls are updated, invalidate the corresponding DecorationCanvas area
    /// </summary>
    /// <param name="sender"></param>
    /// <param name="e"></param>
    void containedControl_Paint(object sender, PaintEventArgs e)
    {
      Control c = sender as Control;

      if (c == null)
        return;

      _decorationCanvas.Invalidate(new Rectangle(c.Left, c.Top, c.Width, c.Height));
    }

    protected override void OnResize(EventArgs eventargs)
    {
      base.OnResize(eventargs);
      // make sure we're covering the panel control
      _decorationCanvas.Size = ClientSize;
    }

    protected override void OnSizeChanged(EventArgs e)
    {
      base.OnSizeChanged(e);
      // make sure we're covering the panel control
      _decorationCanvas.Size = ClientSize;
    }

    /// <summary>
    /// This is marked internal because it gets called from the designer
    /// to make sure our DecorationCanvas stays on top of the ZOrder.
    /// </summary>
    internal void ResetDecorationZOrder()
    {
      if (Controls.GetChildIndex(_decorationCanvas) != 0)
        Controls.SetChildIndex(_decorationCanvas, 0);
    }

    private bool IsInDesignMode
    {
      get
      {
        return DesignMode || LicenseManager.UsageMode == LicenseUsageMode.Designtime;
      }
    }
  }

  /// <summary>
  /// Unfortunately, the default designer of the standard panel is not a public class
  /// So we'll have to build a new designer out of another one. Since Panel inherits from
  /// ScrollableControl, let's try a ScrollableControlDesigner ...
  /// </summary>
  public class DecoratedPanelDesigner : ScrollableControlDesigner
  {
    private IComponentChangeService _changeService;

    public override void Initialize(IComponent component)
    {
      base.Initialize(component);

      // Acquire a reference to IComponentChangeService.
      this._changeService = GetService(typeof(IComponentChangeService)) as IComponentChangeService;

      // Hook the IComponentChangeService event
      if (this._changeService != null)
        this._changeService.ComponentChanged += new ComponentChangedEventHandler(_changeService_ComponentChanged);
    }

    /// <summary>
    /// Try and handle ZOrder changes at design time
    /// </summary>
    /// <param name="sender"></param>
    /// <param name="e"></param>
    void _changeService_ComponentChanged(object sender, ComponentChangedEventArgs e)
    {
      Control changedControl = e.Component as Control;
      if (changedControl == null)
        return;

      DecoratedPanel panelPaint = Control as DecoratedPanel;

      if (panelPaint == null)
        return;

      // if the ZOrder of controls contained within our panel changes, the
      // changed control is our control
      if (Control.Equals(panelPaint))
        panelPaint.ResetDecorationZOrder();
    }

    protected override void Dispose(bool disposing)
    {
      if (disposing)
      {
        if (this._changeService != null)
        {
          // Unhook the event handler
          this._changeService.ComponentChanged -= new ComponentChangedEventHandler(_changeService_ComponentChanged);
          this._changeService = null;
        }
      }

      base.Dispose(disposing);
    }

    /// <summary>
    /// If the panel has BorderStyle.None, a dashed border needs to be drawn around it
    /// </summary>
    /// <param name="pe"></param>
    protected override void OnPaintAdornments(PaintEventArgs pe)
    {
      base.OnPaintAdornments(pe);

      Panel panel = Control as Panel;
      if (panel == null)
        return;

      if (panel.BorderStyle == BorderStyle.None)
      {
        using (Pen p = new Pen(SystemColors.ControlDark))
        {
          p.DashStyle = System.Drawing.Drawing2D.DashStyle.Dash;
          pe.Graphics.DrawRectangle(p, 0, 0, Control.Width - 1, Control.Height - 1);
        }
      }
    }
  }
}

请告诉我您的想法...


1

我认为最好的方法是继承你想要画线的控件。重写OnPaint方法,在其中调用base.Paint(),然后使用相同的图形实例绘制线条。同时,你还可以有一个参数来指定线条应该在哪个点上绘制,这样你就可以直接从主窗体控制线条。


已经完成了,但效果不好。 正如我在问题中提到的那样,在面板的"OnPaint()"返回后,其顶部的控件才开始绘制。 - dtroy
你正在覆盖面板的OnPaint方法,但我指的是控件本身的OnPaint方法(显示“波形”的那个)。如果仍然无法解决问题,那么你用来显示“波形”的控件是什么? - faulty
图片中的控件都是从UserControl继承而来的,标签、文本框等也是如此。 问题是... 图片中有8个控件,其中4个是我的控件+4个分隔符。您是建议我在每个控件上画一条线段吗? - dtroy
如果面板上还有其他控件(不同类型),该怎么办?覆盖它们所有的 OnPaint() 函数吗? 虽然我知道这是一种可能性,但我希望有一个更好的解决方案。 顺便说一下,感谢您的帮助。 - dtroy
抱歉回复晚了。是的,我指的是为它们中的每一个绘制片段,并且给它们添加一个属性,这样您就可以轻松地设置您希望从用户控件中绘制的线条。 例如: ControlA.LinePosition = new Point(100, 0); ControlB.LinePosition = new Point(100, 0); - faulty

1

原始代码应该是:

    protected override CreateParams CreateParams
    {
        get
        {
            CreateParams cp;
            cp = base.CreateParams;
            cp.Style &= 0x7DFFFFFF; //WS_CLIPCHILDREN
            return cp;
        }
    }

这个有效!!


John,谢谢你的回复。你关于问题的错误说得对,我已经修复了它。这样做会使你能够在其他控件的上面进行绘制,但是由于面板内的控件是在面板的OnPaint之后绘制的,它们将只是被绘制在你所绘制的图形的上方。 - dtroy

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接