Рисование поверх элементов управления внутри панели (C # WinForms)

37

Я знаю, что этот вопрос задавали более чем несколько раз, но пока я не смог найти для него хорошее решение.

У меня есть панель с другим контролем над ней.
Я хочу нарисовать линию на ней и поверх всех элементов управления на панели

Я столкнулся с тремя типами решений (без них работал так, как я хотел):

  • Получить рабочий стол 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.
Синяя линия окрашена в панель OnPaint и просто нарисована текстовыми полями и ярлыками.
Красная линия окрашена сверху только потому, что она не окрашивается с панели OnPaint (она фактически нарисована в результате нажатия кнопки)

Изображение 14288


3rd: Создание прозрачного слоя и рисование поверх этого слоя.
Я создал прозрачный элемент управления, используя:

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

Проблема по-прежнему заключается в том, что прозрачный элемент управления находится поверх панели и всех ее элементов управления.
Я попытался вывести его на передний план, используя: "BringToFront()", но, похоже, это не помогло.
Я поместил его в обработчик OnPaint() управления линией.
Должен ли я попытаться положить его в другое место?
- Это также создает проблему с другим контролем над панелью. (улавливание щелчков мыши и т.д.)

Любая помощь будет принята с благодарностью!

** EDIT: Черная линия - образец того, что я пытался сделать. (используется краска для окон)

Изображение 14289

  • 0
    Я должен спросить: почему? (извините, я ненавижу, когда люди спрашивают об этом - я не говорю, что это плохая идея, но мне любопытно)
  • 0
    Хорошо, я собираюсь догадаться: у вас есть один элемент управления, который пользователи перетаскивают по форме, и здесь проходит линия между элементом управления перетаскиванием и другим элементом управления?
Показать ещё 2 комментария
Теги:
winforms

10 ответов

16

Оказывается, это намного проще, чем я думал. Спасибо, что не принял ни одного из моих других ответов. Вот двухэтапный процесс создания Fline ( f loating line - извините, это поздно):

alt text http://www.freeimagehosting.net/uploads/1fd1206890.jpg

Шаг 1: добавьте UserControl в свой проект и назовите его "Fline". Добавьте следующие инструкции:

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 на красный или что-то очевидное (в дизайнере). Одна странная причуда об этом заключается в том, что когда вы перетаскиваете ее в дизайнере, она отображается как сплошной блок, пока вы его не отпустите - не огромная сделка.

Этот элемент управления может отображаться перед любым другим элементом управления или за ним. Если вы установите 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}));
  • 0
    MusiGenesis, спасибо за этот ответ. Это очень помогло. Но .. Я играл сегодня снова и пытался создать пунктирную линию шириной 1 пиксель, аналогичную красной линии в вашем ответе выше. Поскольку у меня не было открытого GraphicsPath, я пытался сделать элемент управления прозрачным
  • 0
    продолжение ... и затем нарисуйте линию на нем. Но я не смог найти способ, который бы работал со всеми элементами управления и т. Д. У вас есть какие-нибудь идеи? СПАСИБО!
Показать ещё 1 комментарий
10

Если вы хотите, чтобы линия была простой горизонтальной или вертикальной линией, добавьте другую панель (отключен, чтобы она не забирала какие-либо события мыши) на главной панели, установите ее высоту (или ширину) на 3 или 4 пиксели (или все, что вы хотите), и доведите его до фронта. Если вам нужно изменить, где находится строка во время выполнения, вы можете просто перемещать панель и делать ее видимой и невидимой. Вот как это выглядит:

alt text http://www.freeimagehosting.net/uploads/832018002a.jpg

Вы даже можете щелкнуть в любом месте, где хотите, и линии не мешают вообще. Линия нарисована над любым типом управления вообще (хотя выпадающая часть ComboBox или DatePicker все еще отображается над строкой, что хорошо в любом случае). Синяя линия - это одно и то же, но отправлено обратно.

  • 0
    Кажется, что ярлык должен быть добавлен к панели первым, чтобы быть нарисованным последним. Игра с BringToFront (), кажется, не очень помогает
  • 0
    Вместо метки вы можете использовать другую панель (шириной 1 или 2 пикселя). Если вы перенесете это на передний план, он будет перед всем остальным на панели, несмотря ни на что.
Показать ещё 5 комментариев
6

Да, это можно сделать. Проблема в том, что панель и элементы управления на ней - это все отдельные окна (в смысле API) и, следовательно, все отдельные поверхности рисования. Для получения этого эффекта нет ни одной поверхности рисования (отличной от поверхности экрана верхнего уровня, и она считалась невежливой нарисовать все это).

Трюк (cough- hack -cough) состоит в том, чтобы нарисовать линию на панели под элементами управления, а также нарисовать ее на каждом из элементов управления, в результате чего это (что будет сохраняться даже когда вы нажимаете кнопки и передвигаете мышь):

alt text http://www.freeimagehosting.net/uploads/2dce0ba7ab.gif

Создайте проект winforms (который должен поставляться с Form1 по умолчанию). Добавьте панель (с именем "panel1" ) и две кнопки ( "кнопка1" и "кнопка2" ) на панели, как показано. Добавьте этот код в конструктор формы:

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 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. Я уверен, вам придется изменить этот образец, чтобы достичь того, что вам нужно. Если вы придумали хорошую обобщенную функцию для этого, отправьте ее.

Обновление: этот метод не будет работать для полос прокрутки, текстовых полей, comboboxes, listviews или вообще что-либо с темой текстового типа как часть его (а не потому, что он только смещает для кнопок в приведенном выше примере - вы просто можете " t рисовать поверх текстового поля вообще, по крайней мере, не из его события Paint, по крайней мере, если вы меня). Надеюсь, это не проблема.

5

Панель форм окон представляет собой контейнер для элементов управления. Если вы хотите нарисовать что-то поверх других элементов управления внутри панели, то вам нужен другой элемент управления (в верхней части z-порядка).

К счастью, вы можете создавать элементы форм Windows, которые имеют непрямоугольные границы. Посмотрите на эту технику: http://msdn.microsoft.com/en-us/library/aa289517(VS.71).aspx

Чтобы просто нарисовать что-нибудь на экране, используйте элемент управления меткой и выключите AutoSize. Затем присоедините к событию 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 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);
}
3

Сделайте новый LineControl: Управление следующим образом:

затем вызовите BringToFront() после InitializeComponent

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

Изменить: добавлена ​​поддержка диагонали

Я добавил некоторую поддержку элементов управления, которые перерисовываются, когда они получают фокус.

текстовые поля и comboboxs не будут работать, так как вам нужно будет сделать свой собственный и зацепить там красить команды следующим образом:

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

Его не тестировали, и я уверен, что вы можете улучшить его

  • 0
    Hath, это могло бы быть хорошим решением, но ... посмотрите (см. Изображение ниже), что происходит, когда вы наводите курсор мыши под кнопкой под линией, или элемент управления под линией получает фокус. i73.photobucket.com/albums/i201/sdjc1/temp/screen4.png
  • 0
    @dtroy - я добавил код, который станет недействительным, когда другой элемент управления в коллекции parent.controls закрасит ... он не будет работать так же хорошо для текстовых полей или комбинированных списков, но вы можете увидеть, что я сделал, чтобы покрыть их. Не уверен, что это подойдет для ваших нужд, но вы могли бы заставить его работать для того, что вы пишете.
Показать ещё 3 комментария
3

Единственное простое решение, о котором я могу думать, - создать обработчики событий 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
}
2

РЕДАКТИРОВАТЬ Найден способ избавиться от рекурсивной картины, которую я имел. Итак, теперь, для меня это выглядит очень, очень, очень близко к тому, чего вы хотите достичь.

Вот что я мог придумать. Он использует подход №3, описанный в исходном вопросе. Код несколько длинный, потому что задействованы три класса:

  • Частный класс, называемый DecorationCanvas. Это происходит от панели и использует WS_EX_TRANSPARENT, чтобы обеспечить прозрачный холст, чтобы нарисовать наши вещи на
  • Сам класс панели, я назвал его DecoratedPanel, он выводится из панели
  • Класс дизайнера под названием DecoratedPanelDesigner для панели, чтобы убедиться, что ZOrder можно сохранить во время разработки.

Основной подход:

  • В конструкторе DecoratedPanel создайте экземпляр DecorationCanvas и добавьте его в коллекцию DecoratedPanel Controls.
  • Переопределите OnControlAdded и OnControlRemoved, чтобы автоматически перехватывать/удалять события рисования для дочерних элементов управления и убедиться, что DecorationCanvas остается поверх ZOrder.
  • Всякий раз, когда скрытый элемент управления рисует, аннулируйте соответствующий прямоугольник DecorationCanvas.
  • Переопределить OnResize и OnSizeChanged, чтобы убедиться, что DecorationCanvas имеет тот же размер, что и DecoratedPanel. (Я попытался выполнить это, используя свойство Anchor, но это как-то не получилось).
  • Предоставьте внутренний метод reset DecorationCanvas ZOrder из класса DecoratedPanelDesigner.

Хорошо работает в моей системе (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 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

Как насчет этого решения # 1 (Получить рабочий стол DC и рисовать на экране):

  • Получите рабочий стол DC и графический объект для этого DC [Graphics.fromHDC(...)]
  • Задайте свойство Clip для полученного графического объекта как видимый в данный момент регион вашей формы. (Я еще не исследовал, как найти видимую область формы)
  • Сделайте графический рендеринг.
1

Исходный код должен быть:

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

Это работает!!

  • 0
    Джон, спасибо за твой ответ. Вы правы насчет ошибки в вопросе, и я исправил ее. Это позволит вам рисовать поверх других элементов управления, но поскольку элементы управления на панели рисуются после OnPaint панели, они будут просто нарисованы поверх того, что вы нарисовали.
1

Я думаю, что лучший способ - наследовать контроль, на который вы хотите нарисовать линию. Переопределите метод OnPaint, вызовите base.Paint() изнутри, после этого нарисуйте линию, используя один и тот же графический экземпляр. В то же время вы также можете иметь параметр, который определен в тот момент, когда линия должна быть нарисована, чтобы вы могли контролировать линию непосредственно из вашей основной формы.

  • 0
    Сделано это. Не хорошо. Как я упоминал в этом вопросе, элементы управления поверх него закрашиваются после того, как «OnPaint ()» панели вернулась.
  • 0
    Вы переопределяете OnPaint панели, но я имею в виду OnPaint самого элемента управления (тот, который отображает «волну»). Если это все еще не сработало, то какой элемент управления вы использовали для отображения «волны»?
Показать ещё 3 комментария

Ещё вопросы

Сообщество Overcoder
Наверх
Меню