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

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

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

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

  1. Получите рабочий стол DC и нарисуйте на экране.
    Это будет использоваться в других приложениях, если они перекрывают форму.

  2. Переопределение панели 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 мс, и установите таймер на X мс?


На этом снимке экрана показана панель с выключенными WS_CLIPSIBLINGS и WS_CLIPCHILDREN.
Синяя линия нарисована на панели OnPaint и просто закрашена текстовыми полями и меткой. Красная линия нарисована сверху только потому, что она не нарисована из OnPaint панели (на самом деле она нарисована в результате нажатия кнопки)
Рисование поверх элементов управления внутри панели (C# WinForms)


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

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

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

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

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

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

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

MusiGenesis 12.11.2008 05:54

Хорошо, я собираюсь угадать: у вас есть один элемент управления, который пользователи перетаскивают по форме, и здесь проводится линия между элементом управления перетаскиванием и каким-то другим элементом управления?

MusiGenesis 12.11.2008 05:56

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

dtroy 12.11.2008 06:14

Эта статья может помочь в этом вопросе, но не уверен, что это решение: http://www.codeproject.com/KB/GDI-plus/WindowGraphics.aspx Я подумал, что добавлю его на случай, если он поможет другим.

dtroy 08.07.2009 10:55
Здесь - другое решение ..
TaW 08.08.2019 18:04
Стоит ли изучать PHP в 2026-2027 годах?
Стоит ли изучать PHP в 2026-2027 годах?
Привет всем, сегодня я хочу высказать свои соображения по поводу вопроса, который я уже много раз получал в своем сообществе: "Стоит ли изучать PHP в...
Поведение ключевого слова "this" в стрелочной функции в сравнении с нормальной функцией
Поведение ключевого слова "this" в стрелочной функции в сравнении с нормальной функцией
В JavaScript одним из самых запутанных понятий является поведение ключевого слова "this" в стрелочной и обычной функциях.
Приемы CSS-макетирования - floats и Flexbox
Приемы CSS-макетирования - floats и Flexbox
Здравствуйте, друзья-студенты! Готовы совершенствовать свои навыки веб-дизайна? Сегодня в нашем путешествии мы рассмотрим приемы CSS-верстки - в...
Тестирование функциональных ngrx-эффектов в Angular 16 с помощью Jest
В системе управления состояниями ngrx, совместимой с Angular 16, появились функциональные эффекты. Это здорово и делает код определенно легче для...
Концепция локализации и ее применение в приложениях React ⚡️
Концепция локализации и ее применение в приложениях React ⚡️
Локализация - это процесс адаптации приложения к различным языкам и культурным требованиям. Это позволяет пользователям получить опыт, соответствующий...
Пользовательский скаляр GraphQL
Пользовательский скаляр GraphQL
Листовые узлы системы типов GraphQL называются скалярами. Достигнув скалярного типа, невозможно спуститься дальше по иерархии типов. Скалярный тип...
43
5
81 497
10

Ответы 10

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

alt text

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

Кажется, что метку нужно добавить к панели первым, чтобы рисовать последней. Игра с BringToFront (), похоже, не сильно помогла

dtroy 18.11.2008 03:06

Вместо метки можно использовать другую панель (шириной 1 или 2 пикселя). Если вы переместите это на передний план, он будет перед всем остальным на панели, несмотря ни на что.

MusiGenesis 21.11.2008 03:03

Кое-что я заметил, играя с ним: если линия находится над элементом управления, для которого отключен параметр «WS_CLIPSIBLINGS», линия не будет нарисована поверх него. В остальном вроде нормально. Но что мы будем делать, если нам нужна диагональная линия?

dtroy 25.11.2008 03:40

Кстати, почему мой другой ответ не сработал? (с диагональной красной линией над кнопками)

MusiGenesis 25.11.2008 04:50

Дело не в том, что это не сработало, просто я не хотел подключаться к обработчику событий элемента управления. Я не знаю, сколько элементов управления будет на поверхности, которую я рисую. Что, если мы нарисуем что-то более сложное, чем линия, например движущиеся фигуры и т. д. Вы понимаете, о чем я?

dtroy 25.11.2008 07:25

@dtroy: пожалуйста, посмотрите мой новый ответ. Я был на полпути к комментарию типа «это принципиально невозможно», когда решил попробовать еще раз.

MusiGenesis 25.11.2008 09:52

Ну ... вы просто косвенно ответили на вопрос, который мучил меня весь день: я искал какой-нибудь способ нарисовать простой метка поверх всех других элементов управления внутри панели - без того, чтобы метка мешала ввод мыши на элементы управления под ним. Не имея вашего вклада, я подумал, что мне нужно нарисовать текст сам, и вот как я здесь оказался. Нет, оказывается, мне просто нужно было отключить ярлык. Спасибо!

neminem 29.07.2011 01:36

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

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

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

Чтобы просто нарисовать что-то на экране, используйте элемент управления надписью и отключите параметр AutoSize. Затем присоединитесь к событию Paint и установите свойства Size и Region.

Вот пример кода:

private void label1_Paint(object sender, PaintEventArgs e)
{
    System.Drawing.Drawing2D.GraphicsPath myGraphicsPath = new  System.Drawing.Drawing2D.GraphicsPath();
    myGraphicsPath.AddEllipse(new Rectangle(0, 0, 125, 125));
    myGraphicsPath.AddEllipse(new Rectangle(75, 75, 20, 20));
    myGraphicsPath.AddEllipse(new Rectangle(120, 0, 125, 125));
    myGraphicsPath.AddEllipse(new Rectangle(145, 75, 20, 20));
    //Change the button's background color so that it is easy
    //to see.
    label1.BackColor = Color.ForestGreen;
    label1.Size = new System.Drawing.Size(256, 256);
    label1.Region = new Region(myGraphicsPath);
}

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

Готово. Не хорошо. Как я уже упоминал в вопросе, элементы управления наверху рисуются после того, как панель «OnPaint ()» вернулась.

dtroy 13.11.2008 02:24

Вы переопределяете OnPaint панели, но я имею в виду OnPaint самого элемента управления (тот, который отображает «волну»). Если это все еще не работает, то какой элемент управления вы использовали для отображения «волны»?

faulty 13.11.2008 11:03

Элемент управления на картинке унаследован от UserControl, но то же самое касается Label, TextBox и т. д. Проблема в том, что на рисунке 8 элементов управления, 4 моих + 4 Splitter. Вы предлагаете, чтобы я нарисовал на каждом из них фрагмент линии?

dtroy 18.11.2008 02:42

что, если на панели есть другие элементы управления (разных типов)? Отменить все их OnPaint ()? Хотя я знаю, что это возможно, я надеялся на лучшее решение. Кстати, спасибо за вашу помощь

dtroy 18.11.2008 02:43

Извините за задержку с ответом. Да, я имел в виду фрагмент рисования для каждого из них и правильно добавил к каждому из них, чтобы вы могли легко установить, когда вы хотите, чтобы линия была из вашего пользовательского элемента управления. Как: ControlA.LinePosition = новая точка (100, 0); ControlB.LinePosition = новая точка (100, 0);

faulty 21.11.2008 10:53

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

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

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

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

dtroy 18.11.2008 02:35

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

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

alt text

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

panel1.Paint += PaintPanelOrButton;
button1.Paint += PaintPanelOrButton;
button2.Paint += PaintPanelOrButton;

а затем добавьте этот метод в код формы:

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

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

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

Что-то подобное нужно нарисовать в событии Paint каждого элемента управления, чтобы линия сохранялась. В .NET легко рисовать непосредственно на элементах управления, но все, что вы рисуете, стирается, когда кто-то нажимает кнопку или наводит на нее указатель мыши (если только это не постоянно перерисовывается в событиях Paint, как здесь).

Обратите внимание, что для того, чтобы это работало, любой нарисованный элемент управления должен иметь событие Paint. Я уверен, что вам придется изменить этот образец, чтобы добиться того, что вам нужно. Если вы придумали для этого хорошую обобщенную функцию, опубликуйте ее.

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

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

затем вызовите 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);
        }
    }
}

Обновлено: добавлена ​​диагональная поддержка

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

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

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

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

Ха, это могло бы быть хорошим решением, но ... посмотрите (см. Изображение ниже), что происходит, когда вы наводите указатель мыши на кнопку под линией или элемент управления под линией получает фокус. i73.photobucket.com/albums/i201/sdjc1/temp/screen4.png

dtroy 19.11.2008 02:19

@dtroy - я добавил код, который будет недействителен, когда другой элемент управления в коллекции parent.controls закрашивает ... он не будет работать также для текстового поля или полей со списком, но вы можете увидеть, что я сделал, чтобы скрыть их. не уверен, подойдет ли это для ваших нужд, но вы могли бы заставить его работать для того, что вы описываете.

Hath 19.11.2008 14:31

Небольшой комментарий к коду (еще не успел его запустить): 8465 = 0x02111, то есть OCM_COMMAND. это то, что вы пытаетесь получить? Кроме того, я заметил, что в «OnSimpleLineParentChanged» вы можете захотеть отделить событие от parentHooked, когда (Parent! = Null) && (parentHooked! = Null) 10x

dtroy 20.11.2008 02:30

@dtroy - добавлен отсоединение для parentHooked. Просто заметил, что у этого есть несколько проблем, если вы выполняете диагональ ... скажем, если вы хотите выбрать элемент управления, который был покрыт линией, вы не можете, потому что элемент управления линией покрывает его ... я полагаю, вы могли бы каким-то образом ретранслировать сообщения мыши но я не знаю .. немного грязно.

Hath 20.11.2008 13:10

Возможно, это самый большой код, когда-либо написанный для рисования одной линии. :)

MusiGenesis 21.11.2008 03:01

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

alt text

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

using System.Drawing.Drawing2D;

Шаг 2: Добавьте следующее в событие Resize Fline:

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

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

Этот элемент управления может располагаться перед любым другим элементом управления или за ним. Если вы установите для Enabled значение false, он все равно будет виден, но не будет мешать событиям мыши на элементах управления внизу.

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

Обновлять: это тоже хороший плотный однострочник. Просто поместите это в событие Resize UserControl:

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

MusiGenesis, спасибо за этот ответ. Это очень помогло. Но .. Я снова играл с этим сегодня и пытался создать пунктирную линию шириной в 1 пиксель, похожую на красную линию в вашем ответе выше. Поскольку у меня не могло быть открытого GraphicsPath, я пытался сделать элемент управления прозрачным

dtroy 02.04.2009 08:55

cont ... а затем нарисуйте на нем линию. Но я не мог найти способ, который работал бы со всеми элементами управления под ним и т. д. У вас есть какие-нибудь идеи ?? БЛАГОДАРНОСТЬ!

dtroy 02.04.2009 08:55

"Хорошие, плотные однострочные" - всегда плохо.

Glenn Maynard 12.05.2014 04:44

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

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

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

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

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

Базовый подход:

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

Дайте мне знать, что вы думаете ...

Другие вопросы по теме