私はこの質問が数回以上尋ねられたことを知っていますが、今のところそれに対する良い解決策を見つけることができませんでした。
他のコントロールを備えたパネルがあります。
パネル上のすべてのコントロールの上に線を引きたい
私は3つのタイプのソリューションに出会いました(それらはどれも私が望んでいた方法で働きました):
デスクトップを取得DC and画面に描画します。
これは、他のアプリケーションがフォームに重なる場合に描画します。
パネルの「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がオフになっているパネルを示しています。
青い線はパネルのOnPaintでペイントされ、テキストボックスとラベルによって単純にペイントされます。
赤い線は、パネルのOnPaintからペイントされていないためにのみ上部にペイントされます(実際には、ボタンがクリックされた結果としてペイントされます)。
3番目:透明なレイヤーを作成し、そのレイヤーの上に描画します。
次を使用して透明なコントロールを作成しました:
protected override CreateParams CreateParams {
get {
CreateParams cp = base.CreateParams;
cp.ExStyle |= 0x00000020; //WS_EX_TRANSPARENT
return cp;
}
}
問題は、透明なコントロールをパネルとそのすべてのコントロールの上に配置することです。
「BringToFront()」を使用して前面に表示しようとしましたが、役に立たなかったようです。
LineコントロールのOnPaint()ハンドラーに入れました。
他の場所に置いてみますか??
-これは、パネルの上部に別のコントロールを持つことに関する問題も作成します。 (マウスクリックなどをキャッチします。)
助けていただければ幸いです!
**編集:黒い線は、私がやろうとしていたことのサンプルです。 (Windowsペイントを使用してペイント)
これは思ったよりもずっと簡単だということがわかりました。私の他の答えを受け入れないでくれてありがとう。 Fline(floatingを作成する2段階のプロセスを次に示します。 line-申し訳ありませんが、遅れています):
ステップ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または明白なもの(デザイナーで)に変更します。これに関する奇妙な癖の1つは、デザイナでドラッグすると、リリースするまでしっかりしたブロックとして表示されることです-大したことではありません。
このコントロールは、他のコントロールの前または後ろに表示できます。 Enabledをfalseに設定すると、表示されますが、その下のコントロールのマウスイベントに干渉しません。
もちろん、これを目的に合わせて強化することもできますが、これは基本的な原則を示しています。あなたが好きな形のコントロールを作成するために同じテクニックを使うことができます(これの私の最初のテストは三角形を作りました)。
Update:これにより、ニースの密なワンライナーも作成されます。これを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}));
線を単純な水平線または垂直線にする場合は、メインパネルに別のパネル(マウスイベントを取得しないように無効化)を配置し、その高さ(または幅)を3または4ピクセル(またはあなたが望むものは何でも)、そしてそれを前面に持ってきてください。実行中に行の場所を変更する必要がある場合は、パネルを移動して、表示と非表示を切り替えることができます。外観は次のとおりです。
好きな場所をクリックすることもでき、ラインはまったく干渉しません。線は、あらゆる種類のコントロールの上に描画されます(ただし、ComboBoxまたはDatePickerのドロップダウン部分は線の上に表示されますが、いずれにしても良いです)。青い線は同じものですが、後ろに送られます。
はい、これを行うことができます。問題は、パネルとその上のコントロールがすべて(APIの意味で)個別のウィンドウであり、したがってすべて個別の描画面であるということです。この効果を得るために描画する描画面は1つもありません(トップレベルの画面面を除き、その上に描画することは無礼と見なされます)。
(cough-hack-cough)トリックは、コントロールの下のパネルに線を引き、各コントロール自体にも線を引くことです。この結果(ボタンをクリックしてマウスを動かしても持続します):
Winformsプロジェクトを作成します(デフォルトではForm1に付属しています)。図に示すように、パネル(「panel1」という名前)と2つのボタン(「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イベントからではなく、少なくともあなたが私なら、テキストボックスの最上部。それが問題にならないことを願っています。
Windowsフォームパネルは、コントロールのコンテナです。パネル内の他のコントロールの上に何かを描画したい場合、必要なのは別のコントロール(zオーダーの一番上)です。
幸いなことに、四角形以外の境界を持つWindowsフォームコントロールを作成できます。この手法を見てください: http://msdn.Microsoft.com/en-us/library/aa289517(VS.71).aspx
画面に何かを描くには、ラベルコントロールを使用して、オートサイズをオフにします。次に、Paintイベントにアタッチし、サイズと領域のプロパティを設定します。
コードサンプルを次に示します。
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);
}
新しい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);
}
}
テストされていないので、改善できると確信しています
私が考えることができる唯一の簡単な解決策は、上にペイントする各コントロールのペイントイベントハンドラーを作成することです。次に、これらのハンドラー間の線の描画を調整します。これは最も便利な解決策ではありませんが、コントロールの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
}
[〜#〜] edit [〜#〜]私が抱えていた再帰的なペイントの問題を取り除く方法を見つけました。だから、今、私にとって、これはあなたが達成したいものに非常に、非常に、非常に近いように見えます。
ここに私が思いつくものがあります。元の質問で概説したアプローチ#3を使用します。 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);
}
}
}
}
}
どう考えているか教えてください ...
元のコードは次のとおりです。
protected override CreateParams CreateParams
{
get
{
CreateParams cp;
cp = base.CreateParams;
cp.Style &= 0x7DFFFFFF; //WS_CLIPCHILDREN
return cp;
}
}
これは動作します!!
最良の方法は、線を引くコントロールを継承することだと思います。 OnPaintメソッドをオーバーライドし、内部からbase.Paint()を呼び出した後、同じグラフィックインスタンスを使用して線を描画します。同時に、メインフォームから直接ラインを制御できるように、ラインを描画するポイントを指定するパラメーターを設定することもできます。
これは解決策#1(デスクトップを取得するDCで画面に描画する)についてはどうですか。