在面板内的控件顶部绘图(C# WinForms)
声明:本页面是StackOverFlow热门问题的中英对照翻译,遵循CC BY-SA 4.0协议,如果您需要使用它,必须同样遵循CC BY-SA许可,注明原文地址和作者信息,同时你必须将它归于原作者(不是我):StackOverFlow
原文地址: http://stackoverflow.com/questions/282838/
Warning: these are provided under cc-by-sa 4.0 license. You are free to use/share it, But you must attribute it to the original authors (not me):
StackOverFlow
Drawing on top of controls inside a panel (C# WinForms)
提问by dtroy
I know this question had been asked more than a few times, but so far I haven't been able to find a good solution for it.
我知道这个问题已经被问过多次,但到目前为止我还没有找到一个好的解决方案。
I've got a panel with other control on it.
I want to draw a line on it and on top of all the controls in the panel
我有一个带有其他控件的面板。
我想在它上面和面板中的所有控件之上画一条线
I came across 3 types of solutions (non of them worked the way I wanted) :
我遇到了 3 种类型的解决方案(它们都不是我想要的方式):
Get the desktop DC and Draw on the screen.
This will draw on other applications if they overlap the form.Overriding the panel's "CreateParams":
获取桌面 DC 并在屏幕上绘制。
如果其他应用程序与表单重叠,这将利用它们。覆盖面板的“CreateParams”:
=
=
protected override CreateParams CreateParams {
get {
CreateParams cp;
cp = base.CreateParams;
cp.Style &= ~0x04000000; //WS_CLIPSIBLINGS
cp.Style &= ~0x02000000; //WS_CLIPCHILDREN
return cp;
}
}
//NOTE I've also tried disabling WS_CLIPSIBLINGS
//注意我也试过禁用 WS_CLIPSIBLINGS
and then drawing the line OnPaint().
But... Since the panel's OnPaint is called before the OnPaint of the controls in it,
the drawing of the controls inside simply paints on top of the line.
I've seen someone suggest using a message filter to listen to WM_PAINT mesages, and use a timer, but I don't think this solution is either "good practice" or effective.
What would you do ? Decide that the controls inside have finished drawing after X ms, and set the timer to X ms ?
然后绘制 OnPaint() 线。但是...由于面板的 OnPaint 在其中的控件的 OnPaint 之前被调用,所以内部控件的绘制只是在线条的顶部绘制。
我看到有人建议使用消息过滤器来收听 WM_PAINT 消息,并使用计时器,但我认为这个解决方案既不是“好的做法”,也不是有效的。
你会怎么办 ?确定里面的控件在 X ms 后绘制完成,并将定时器设置为 X ms ?
This screen shot shows the panel with WS_CLIPSIBLINGS and WS_CLIPCHILDREN turned off.
The Blue line is painted at the Panel's OnPaint, and simply being painted on by the textboxes and label.
The Red line is painted on top only because it's not being painted from the panel's OnPaint (It's actually painted as a result of a Button being clicked)
此屏幕截图显示了 WS_CLIPSIBLINGS 和 WS_CLIPCHILDREN 关闭的面板。
蓝线绘制在面板的 OnPaint 上,并简单地由文本框和标签绘制。
红线绘制在顶部只是因为它不是从面板的 OnPaint 绘制的(它实际上是由于单击按钮而绘制的)
3rd: Creating a transparent layer and drawing on top of that layer.
I've created a transparent control using:
第三:创建一个透明层并在该层之上绘图。
我使用以下方法创建了一个透明控件:
protected override CreateParams CreateParams {
get {
CreateParams cp = base.CreateParams;
cp.ExStyle |= 0x00000020; //WS_EX_TRANSPARENT
return cp;
}
}
The problem is still, putting the transparent control on top of the Panel and all its controls.
I've tried bringing it to the front using: "BringToFront()" , but it didn't seem to help.
I've put it in the Line control's OnPaint() handler.
Should I try putting it somewhere else ??
- This also creates issue with having another control on top of the panel. (catching the mouse clicks etc..)
问题仍然是,将透明控件放在 Panel 及其所有控件的顶部。
我试过使用: "BringToFront()" 把它放在前面,但它似乎没有帮助。
我已经把它放在 Line 控件的 OnPaint() 处理程序中。
我应该尝试把它放在其他地方吗??
- 这也会产生在面板顶部有另一个控件的问题。(捕捉鼠标点击等。)
Any help would be greatly appreciated!
任何帮助将不胜感激!
**EDIT: The black line is a sample of what I was trying to do. (used windows paint to paint it)
**编辑:黑线是我试图做的一个样本。(用窗户油漆来画它)
回答by MusiGenesis
If you want the line to be just a simple horizontal or vertical line, put another panel (disabled so it doesn't pick up any mouse events) on the main panel, set its height (or width) to 3 or 4 pixels (or whatever you want), and bring it to front. If you need to change where the line is during runtime, you can just move the panel around and make it visible and invisible. Here is how it looks:
如果您希望该线只是一条简单的水平或垂直线,请在主面板上放置另一个面板(已禁用,因此不会接收任何鼠标事件),将其高度(或宽度)设置为 3 或 4 个像素(或无论你想要什么),然后把它放在前面。如果您需要在运行时更改线条的位置,您只需移动面板并使其可见和不可见。这是它的外观:
You can even click anywhere you like, and the lines don't interfere at all. The line is drawn over any kind of control at all (although the dropdown part of a ComboBox or a DatePicker is still shown above the line, which is good anyway). The blue line is just the same thing but sent to back.
你甚至可以点击任何你喜欢的地方,线条完全不会干扰。这条线是在任何类型的控件上绘制的(尽管 ComboBox 或 DatePicker 的下拉部分仍然显示在线上方,这无论如何都很好)。蓝线是一样的东西,但被发送到后面。
回答by asponge
The only simple solution I can think of is to create Paint event handlers for each control you want to paint on top of. Then coordinate the line drawing between these handlers. This is not the most convenient solution, however this will give you the ability to paint on topof the controls.
我能想到的唯一简单的解决方案是为要在其上绘制的每个控件创建 Paint 事件处理程序。然后协调这些处理程序之间的线条绘制。这不是最方便的解决方案,但是这将使您能够在控件顶部绘画。
Assuming button is a child control of panel:
假设按钮是面板的子控件:
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
}
回答by Matt Brunell
A windows forms panel is a container for controls. If you want to draw something on top of other controls within a panel, then what you need is another control ( at the top of the z order ).
Windows 窗体面板是控件的容器。如果您想在面板内的其他控件之上绘制某些内容,那么您需要的是另一个控件(在 z 顺序的顶部)。
Luckily, you can create windows forms controls which have non-rectangular borders. Look at this technique: http://msdn.microsoft.com/en-us/library/aa289517(VS.71).aspx
幸运的是,您可以创建具有非矩形边框的窗体控件。看看这个技术:http: //msdn.microsoft.com/en-us/library/aa289517(VS.71).aspx
To just draw something on the screen, use a label control, and turn AutoSize off. Then attach to the Paint event and set the Size and Region Properties.
要仅在屏幕上绘制某些内容,请使用标签控件并关闭 AutoSize。然后附加到 Paint 事件并设置大小和区域属性。
Here's a code sample:
这是一个代码示例:
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);
}
回答by faulty
I think the best way is to inherit the control of which you want to draw a line on. Override the OnPaint method, call base.Paint() from within, after that draw the line using the same graphic instance. At the same time, you can also have a parameter which specific at which point the line should be draw, so that you can control the line directly from your main form.
我认为最好的方法是继承你想画线的控件。覆盖 OnPaint 方法,从内部调用 base.Paint(),然后使用相同的图形实例绘制线条。同时,您还可以有一个参数,指定应在哪一点绘制线条,以便您可以直接从主窗体控制线条。
回答by faulty
Original code should be :
原始代码应该是:
protected override CreateParams CreateParams
{
get
{
CreateParams cp;
cp = base.CreateParams;
cp.Style &= 0x7DFFFFFF; //WS_CLIPCHILDREN
return cp;
}
}
This works !!
这有效!!
回答by MusiGenesis
Yes, this can be done. The problem is that the panel and the controls on it are all separate windows (in the API sense), and thus all separate drawing surfaces. There is no one drawing surface to draw on to get this effect (other than the top-level screen surface, and it's considered impolite to draw all over that).
是的,这是可以做到的。问题在于面板及其上的控件都是单独的窗口(在 API 意义上),因此都是单独的绘图表面。没有一个绘图表面可以绘制以获得这种效果(除了顶级屏幕表面,在上面绘制被认为是不礼貌的)。
The (cough-hack-cough) trick is to draw the line on the panel underneath the controls, and also draw it on each of the controls themselves, resulting in this (which will persist even when you click the buttons and move the mouse around):
( cough- hack-cough) 技巧是在控件下方的面板上绘制线条,并在每个控件本身上绘制线条,从而导致这种情况(即使您单击按钮并四处移动鼠标,它也会持续存在) ):
Create a winforms project (which should come with Form1 by default). Add a panel (named "panel1") and two buttons ("button1" and "button2") on the panel as shown. Add this code in the form's constructor:
创建一个 winforms 项目(默认情况下应与 Form1 一起提供)。如图所示,在面板上添加一个面板(名为“panel1”)和两个按钮(“button1”和“button2”)。在表单的构造函数中添加以下代码:
panel1.Paint += PaintPanelOrButton;
button1.Paint += PaintPanelOrButton;
button2.Paint += PaintPanelOrButton;
and then add this method to the form's code:
然后将此方法添加到表单的代码中:
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);
}
Something like this needs to be drawn in each control's Paint event in order for the line to persist. It's easy to draw directly on controls in .NET, but whatever you draw is wiped away when someone clicks the button or moves the mouse over it (unless it's perpetually redrawn in the Paint events, as here).
需要在每个控件的 Paint 事件中绘制这样的东西,以便线条持续存在。在 .NET 中直接在控件上绘制很容易,但是当有人单击按钮或将鼠标移到其上时,您绘制的任何内容都会被擦除(除非它在 Paint 事件中永久重绘,如下所示)。
Note that for this to work, any control drawn over has to have a Paint event. I'm sure you will have to modify this sample to achieve what you need. If you come up with a good generalized function for this, please post it.
请注意,要使其正常工作,绘制的任何控件都必须具有 Paint 事件。我相信您将不得不修改此示例以实现您的需要。如果您为此提出了一个很好的通用函数,请发布它。
Update: this method will not work for scrollbars, textboxes, comboboxes, listviews, or basically anything with a textbox-type thing as part of it (and not because it only offsets for buttons in the example above - you just can't draw on top of a textbox at all, at least not from its Paint event, at least not if you're me). Hopefully that won't be a problem.
更新:此方法不适用于滚动条、文本框、组合框、列表视图或基本上任何包含文本框类型的东西(而不是因为它仅用于上面示例中的按钮的偏移量 - 您只是无法绘制一个文本框的顶部,至少不是来自它的 Paint 事件,至少不是如果你是我)。希望这不会成为问题。
回答by Hath
Make a new LineControl : Control like this:
创建一个新的 LineControl :像这样控制:
then call BringToFront() after the InitializeComponent
然后在 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);
}
}
}
Edit: Added diagonal support
编辑:添加对角线支持
I've added some support for controls that repaint when they get the focus.
我添加了一些对在获得焦点时重新绘制的控件的支持。
textboxes and comboboxs wont work as is you will need to make your own and hook there paintish commands like so:
文本框和组合框将无法正常工作,因为您需要自己制作并钩住像这样的油漆命令:
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);
}
}
Its not tested and i'm sure you can improve on it
它没有经过测试,我相信你可以改进它
回答by MusiGenesis
Turns out this is a whole lot easier than I thought. Thanks for not accepting any of my other answers. Here is the two-step process for creating a Fline(floating line- sorry, it's late):
事实证明,这比我想象的要容易得多。感谢您不接受我的任何其他答案。下面是创建一个两步过程Fline(˚Floating线-对不起,这是晚):
Step 1: Add a UserControl to your project and name it "Fline". Add the following to the using statements:
第 1 步:将 UserControl 添加到您的项目并将其命名为“Fline”。将以下内容添加到 using 语句中:
using System.Drawing.Drawing2D;
Step 2: Add the following to the Fline's Resize event:
第 2 步:将以下内容添加到 Fline 的 Resize 事件中:
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);
Compile, and then drag a Fline onto your form or panel. Important: the default BackColor is the same as the form, so change the Fline's BackColor to Redor something obvious (in the designer). One weird quirk about this is that when you drag it around in the designer it shows as a solid block until you release it - not a huge deal.
编译,然后将 Fline 拖到窗体或面板上。重要提示:默认的 BackColor 与表单相同,因此将 Fline 的 BackColor 更改为 Red或其他明显的(在设计器中)。关于这一点的一个奇怪的怪癖是,当你在设计器中拖动它时,它会显示为一个实心块,直到你释放它 - 没什么大不了的。
This control can appear in front of or behind any other control. If you set Enabled to false, it will still be visible but will not interfere with mouse events on the controls underneath.
此控件可以出现在任何其他控件的前面或后面。如果将 Enabled 设置为 false,它仍然可见,但不会干扰下方控件上的鼠标事件。
You'll want to enhance this for your purposes, of course, but this shows the basic principle. You can use the same technique for creating a control of whatever shape you like (my initial test of this made a triangle).
当然,您会希望出于您的目的来增强它,但这显示了基本原则。您可以使用相同的技术来创建您喜欢的任何形状的控件(我对此的初始测试制作了一个三角形)。
Update: this makes a nice dense one-liner, too. Just put this in your UserControl's Resize event:
更新:这也是一个很好的密集单线。只需将其放在 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}));
回答by Ski
How about this take on solution #1 (Get the desktop DC and Draw on the screen):
这个解决方案#1(获取桌面DC并在屏幕上绘制)如何:
- Get the desktop DC and the graphics object for that DC [Graphics.fromHDC(...)]
- Set the Clip property of the resulting Graphics object to be the currently visible region of your form. (I have not researched yet how to find the visible region of a form)
- Do your graphics rendering.
- 获取桌面 DC 和该 DC 的图形对象 [Graphics.fromHDC(...)]
- 将生成的 Graphics 对象的 Clip 属性设置为窗体的当前可见区域。(我还没有研究如何找到表单的可见区域)
- 进行图形渲染。
回答by takrl
EDITFound a way to get rid of the recursive painting issue I had. So, now, to me, this looks very, very, very close to what you want to achieve.
编辑找到了一种方法来摆脱我遇到的递归绘画问题。所以,现在,对我来说,这看起来非常、非常、非常接近你想要实现的目标。
Here's what I could come up with. It uses approach #3 outlined in the original question. The code is somewhat lengthy because three classes are involved:
这是我能想到的。它使用原始问题中概述的方法#3。代码有点长,因为涉及三个类:
- A private class called DecorationCanvas. This derives from Panel and uses WS_EX_TRANSPARENT to provide a transparent canvas to draw our stuff on
- The panel class itself, I called it DecoratedPanel, it derives from Panel
- A designer class called DecoratedPanelDesigner for the panel, to make sure that the ZOrder can be preserved during Design time.
- 一个名为 DecorationCanvas 的私有类。这源自 Panel 并使用 WS_EX_TRANSPARENT 提供一个透明的画布来绘制我们的东西
- 面板类本身,我称之为 DecoratedPanel,它派生自 Panel
- 一个名为 DecoratedPanelDesigner 的设计器类用于面板,以确保 ZOrder 可以在设计时保留。
The basic approach is:
基本方法是:
- In the constructor of the DecoratedPanel, create an instance of DecorationCanvas and add it to the DecoratedPanel Controls collection.
- Override OnControlAdded and OnControlRemoved, to automatically hook/unhook paint events for child controls, and to make sure the DecorationCanvas stays on top of the ZOrder.
- Whenever a contained control paints, invalidate the corresponding DecorationCanvas rectangle.
- Override OnResize and OnSizeChanged to make sure the DecorationCanvas has the same size as the DecoratedPanel. (I tried to accomplish this using the Anchor property, but it failed somehow).
- Provide an internal method to reset the DecorationCanvas ZOrder from within the DecoratedPanelDesigner.
- 在 DecoratedPanel 的构造函数中,创建 DecorationCanvas 的实例并将其添加到 DecoratedPanel Controls 集合中。
- 覆盖 OnControlAdded 和 OnControlRemoved,以自动挂钩/取消挂钩子控件的绘制事件,并确保 DecorationCanvas 保持在 ZOrder 的顶部。
- 每当包含的控件绘制时,使相应的 DecorationCanvas 矩形无效。
- 覆盖 OnResize 和 OnSizeChanged 以确保 DecorationCanvas 具有与 DecoratedPanel 相同的大小。(我尝试使用 Anchor 属性完成此操作,但不知何故失败了)。
- 提供内部方法以从 DecoratedPanelDesigner 中重置 DecorationCanvas ZOrder。
Works fine on my system (VS2010 / .net4 / Windows XP SP3). Here's the code:
在我的系统 (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);
}
}
}
}
}
Let me know what you think ...
让我知道你的想法 ...