我知道这个问题被问了好几次,但到目前为止我还没有找到一个好的解决方案。
我有一个有其他控制权的小组。
我想在它上面和在面板中的所有控件上绘制一条线
我遇到了3种类型的解决方案(不是按照我想要的方式工作):
获取桌面DC并在屏幕上绘图。
如果它们与表格重叠,这将吸引其他应用程序。
覆盖面板的“CreateParams”:
=
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 ms后完成绘制,并将计时器设置为X ms?
此屏幕截图显示WS_CLIPSIBLINGS和WS_CLIPCHILDREN关闭的面板。
蓝线在Panel的OnPaint上绘制,只是被文本框和标签涂上
红色线条仅涂在上面,因为它没有从面板的OnPaint上绘制(它实际上是由于点击了按钮而绘制的)
第3步:创建透明图层并在该图层的顶部绘图 我用:
创建了一个透明控件protected override CreateParams CreateParams {
get {
CreateParams cp = base.CreateParams;
cp.ExStyle |= 0x00000020; //WS_EX_TRANSPARENT
return cp;
}
}
问题仍然存在,将透明控件置于面板及其所有控件之上
我尝试使用“BringToFront()”将它带到前面,但它似乎没有帮助。
我把它放在Line控件的OnPaint()处理程序中
我应该尝试把它放在其他地方吗?
- 这也会在面板顶部设置另一个控件时产生问题。 (抓住鼠标点击等..)
非常感谢任何帮助!
**编辑: 黑线是我试图做的样本。 (使用窗户油漆来涂漆)
答案 0 :(得分:17)
事实证明这比我想象的容易得多。谢谢你不接受我的任何其他答案。以下是创建 Fline 的两步流程( f 缓动行 - 抱歉,已经晚了):
第1步:将UserControl添加到项目中并将其命名为“Fline”。将以下内容添加到using语句中:
using System.Drawing.Drawing2D;
第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);
编译,然后将Fline拖到表单或面板上。重要提示:默认的BackColor与表单相同,因此将Fline的BackColor更改为Red 或显而易见的(在设计器中)。关于这一点的一个奇怪的怪癖是,当你在设计器中拖动它时,它会显示为一个坚实的块,直到你释放它 - 这不是一件大事。
此控件可以出现在任何其他控件的前面或后面。如果将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}));
答案 1 :(得分:10)
如果您希望线条只是一条简单的水平线或垂直线,请在主面板上放置另一个面板(禁用它不会拾取任何鼠标事件),将其高度(或宽度)设置为3或4像素(或任何你想要的),并把它带到前面。如果您需要在运行期间更改线的位置,则只需移动面板并使其可见且不可见。以下是它的外观:
您甚至可以点击任何您喜欢的地方,这些线条根本不会干扰。这条线完全被绘制在任何类型的控件上(尽管ComboBox或DatePicker的下拉部分仍显示在该行的上方,无论如何都是好的)。蓝线是同样的东西,但发送回来。
答案 2 :(得分:6)
是的,这可以做到。问题是面板和它上面的控件都是独立的窗口(在API意义上),因此所有单独的绘图表面。没有一个绘图表面可以用来获得这种效果(除了顶层屏幕表面,并且它被认为是不礼貌的)。
(咳嗽 - 黑客 - 咳嗽)技巧是在控件下方的面板上绘制线条,并在每个控件本身上绘制它,导致这个(这将持续存在)当你单击按钮并移动鼠标时:)
创建一个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事件。我相信你必须修改这个样本才能达到你的需要。如果您想出一个很好的广义功能,请发布。
更新:此方法不适用于滚动条,文本框,组合框,列表视图,或基本上任何文本框类型的东西作为其中的一部分(并不是因为它只偏移上面示例中的按钮 - 你可以' t根据文本框绘制,至少不是从它的Paint事件中绘制,至少不是如果你是我的话。希望这不会成为问题。
答案 3 :(得分:5)
Windows窗体面板是控件的容器。如果你想在面板中的其他控件之上绘制一些东西,那么你需要的是另一个控件(在z顺序的顶部)。
幸运的是,您可以创建具有非矩形边框的窗体控件。看看这种技巧:http://msdn.microsoft.com/en-us/library/aa289517(VS.71).aspx
要在屏幕上绘制内容,请使用标签控件,然后关闭AutoSize。然后附加到Paint事件并设置Size和Region Properties。
以下是代码示例:
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 :(得分:3)
我能想到的唯一简单的解决方案是为要在其上绘制的每个控件创建Paint事件处理程序。然后协调这些处理程序之间的线条图。这不是最方便的解决方案,但是这将使您能够在控件的 top 上绘制。
假设按钮是面板的子控件:
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
}
答案 5 :(得分:3)
制作一个新的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);
}
}
}
编辑:添加对角线支持
我添加了一些支持,以便在获得焦点时重新绘制。
textboxes和comboboxs不会像你需要自己做的那样工作并挂钩那样的paintish命令:
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);
}
}
它没有经过测试,我相信你可以改进它
答案 6 :(得分:2)
编辑找到一种方法摆脱我的递归绘画问题。所以,现在,对我而言,这看起来非常非常接近你想要实现的目标。
这是我能想到的。它使用原始问题中概述的方法#3。代码有点冗长,因为涉及三个类:
基本方法是:
在我的系统上运行正常(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);
}
}
}
}
}
让我知道你的想法...
答案 7 :(得分:1)
我认为最好的方法是继承你想要绘制线条的控件。重写OnPaint方法,从内部调用base.Paint(),之后使用相同的图形实例绘制线条。同时,您还可以使用一个特定参数来绘制线条,以便您可以直接从主窗体控制线条。
答案 8 :(得分:1)
原始代码应为:
protected override CreateParams CreateParams
{
get
{
CreateParams cp;
cp = base.CreateParams;
cp.Style &= 0x7DFFFFFF; //WS_CLIPCHILDREN
return cp;
}
}
这有效!!
答案 9 :(得分:1)
如何处理解决方案#1(获取桌面DC和Draw在屏幕上):