В C# Winforms есть ли способ поставить пунктирную границу вокруг всех элементов управления и показать точки захвата при выборе конкретных элементов управления во время выполнения?

Я работаю в команде, работающей над IDE, подобной Visual Studio, для разработки пользовательского кода Winform для наших локальных клиентов. В нашем коде пользовательские элементы управления переопределены, чтобы упростить задачи, но большинство элементов управления получены из базовых элементов управления Winform C#.

в настоящее время мне нужна помощь в реализации пунктирной границы вокруг всех наших элементов управления с типом точек захвата, предоставленных Visual Studio.

невыбранные Управление

enter image description here

Выбранных Элементов

enter image description here

эта особенность сильно потребована по мере того как она может помочь в выравнивать без компенсации на визуальных директивах.

в настоящее время мы внедрили темную границу вокруг всех элементов управления, используя

this.BackColor = Color.Black;
this.Height = ComboBox.Height + 4;

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

один из членов указал нам на использование полей и отступов, как показано в документации Microsoft:https://msdn.microsoft.com/library/3z3f9e8b (v=vs. 110)

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

public class MyGroupBox : GroupBox
{
    protected override void OnPaint(PaintEventArgs e)
    {
    base.OnPaint(e);
    ControlPaint.DrawBorder(e.Graphics, ClientRectangle,
        Color.Black, BORDER_SIZE, ButtonBorderStyle.Inset,
        Color.Black, BORDER_SIZE, ButtonBorderStyle.Inset,
        Color.Black, BORDER_SIZE, ButtonBorderStyle.Inset,
        Color.Black, BORDER_SIZE, ButtonBorderStyle.Inset);
    } 
}

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

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

4 ответов


я работаю в команде, работающей над IDE, подобной Visual Studio ....

разработка пользовательского конструктора форм не является тривиальной задачей и требует много знаний и много времени, и я считаю, что лучшим решением, которое вы можете использовать, является хостинг Windows forms designer.

речь идет не только о рисовании границ отбора:

  • каждый элемент управления имеет свой собственный конструктор с определенными функциями, например, некоторые элементы управления, такие как MenuStrip имеет свой собственный конструктор, который позволяет добавлять/удалять элементы в конструкторе.
  • элементы управления могут иметь определенные правила калибровки и позиционирования. Например, некоторые из них имеют автоматический размер, например TextBox или закрепленные элементы управления не могут быть перемещены мышью и так далее.
  • компоненты не отображаются в форме, которую может потребоваться изменить.
  • некоторые свойства являются свойствами времени разработки.
  • некоторые свойства добавляются с помощью поставщиков расширителей и вы необходимо выполнить дополнительные задачи, чтобы предоставить способ их изменения в пользовательском конструкторе.
  • и много других соображений.

Решение 1-Хостинг Windows Forms Designer

чтобы узнать больше об архитектуре времени проектирования, взгляните на Дизайн-Архитектура. Для размещения конструктора windows forms в приложении необходимо реализовать некоторые интерфейсы, такие как IDesignerHost, IContainer, IComponentChangeService, IExtenderProvider, ITypeDescriptorFilterService, IExtenderListService, IExtenderProviderService.

для некоторых хороших примеров вы можете посмотреть на:

решение 2-рисование границы выбора в прозрачной панели

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

enter image description here

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

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

Прозрачная Панель

using System.Windows.Forms;
public class TransparentPanel : Panel
{
    const int WS_EX_TRANSPARENT = 0x20;
    protected override CreateParams CreateParams
    {
        get
        {
            CreateParams cp = base.CreateParams;
            cp.ExStyle = cp.ExStyle | WS_EX_TRANSPARENT;
            return cp;
        }
    }
    protected override void OnPaintBackground(PaintEventArgs e)
    {
    }
}

Форму

using System;
using System.Collections.Generic;
using System.Drawing;
using System.Linq;
using System.Windows.Forms;
public partial class HostForm : Form
{
    private Panel containerPanel;
    private TransparentPanel transparentPanel;
    private PropertyGrid propertyGrid;
    public HostForm()
    {
        this.transparentPanel = new TransparentPanel();
        this.containerPanel = new Panel();
        this.propertyGrid = new PropertyGrid();
        this.SuspendLayout();
        this.propertyGrid.Width = 200;
        this.propertyGrid.Dock = DockStyle.Right;
        this.transparentPanel.Dock = System.Windows.Forms.DockStyle.Fill;
        this.transparentPanel.Name = "transparentPanel";
        this.containerPanel.Dock = System.Windows.Forms.DockStyle.Fill;
        this.containerPanel.Name = "containerPanel";
        this.ClientSize = new System.Drawing.Size(450, 210);
        this.Controls.Add(this.transparentPanel);
        this.Controls.Add(this.propertyGrid);
        this.Controls.Add(this.containerPanel);
        this.Name = "HostForm";
        this.Text = "Host";
        this.Load += this.HostForm_Load;
        this.transparentPanel.MouseClick += this.transparentPanel_MouseClick;
        this.transparentPanel.Paint += this.transparentPanel_Paint;
        this.ResumeLayout(false);
    }
    private void HostForm_Load(object sender, EventArgs e)
    {
        this.ActiveControl = transparentPanel;
        /**************************************/
        /*Load the form which you want to edit*/
        /**************************************/   
        var f = new Form(); 
        f.Location = new Point(8, 8);
        f.TopLevel = false;
        this.containerPanel.Controls.Add(f);
        SelectedObject = f;
        f.Show();
    }
    Control selectedObject;
    Control SelectedObject
    {
        get { return selectedObject; }
        set
        {
            selectedObject = value;
            propertyGrid.SelectedObject = value;
            this.Refresh();
        }
    }
    void transparentPanel_MouseClick(object sender, MouseEventArgs e)
    {
        if (this.Controls.Count == 0)
            return;
        SelectedObject = GetAllControls(this.containerPanel)
            .Where(x => x.Visible)
            .Where(x => x.Parent.RectangleToScreen(x.Bounds)
                .Contains(this.transparentPanel.PointToScreen(e.Location)))
            .FirstOrDefault();
        this.Refresh();
    }
    void transparentPanel_Paint(object sender, PaintEventArgs e)
    {
        if (SelectedObject != null)
            DrawBorder(e.Graphics, this.transparentPanel.RectangleToClient(
                SelectedObject.Parent.RectangleToScreen(SelectedObject.Bounds)));
    }
    private IEnumerable<Control> GetAllControls(Control control)
    {
        var controls = control.Controls.Cast<Control>();
        return controls.SelectMany(ctrl => GetAllControls(ctrl)).Concat(controls);
    }
    void DrawBorder(Graphics g, Rectangle r)
    {
        var d = 4;
        r.Inflate(d, d);
        ControlPaint.DrawBorder(g, r, Color.Black, ButtonBorderStyle.Dotted);
        var rectangles = new List<Rectangle>();
        var r1 = new Rectangle(r.Left - d, r.Top - d, 2 * d, 2 * d); rectangles.Add(r1);
        r1.Offset(r.Width / 2, 0); rectangles.Add(r1);
        r1.Offset(r.Width / 2, 0); rectangles.Add(r1);
        r1.Offset(0, r.Height / 2); rectangles.Add(r1);
        r1.Offset(0, r.Height / 2); rectangles.Add(r1);
        r1.Offset(-r.Width / 2, 0); rectangles.Add(r1);
        r1.Offset(-r.Width / 2, 0); rectangles.Add(r1);
        r1.Offset(0, -r.Height / 2); rectangles.Add(r1);
        g.FillRectangles(Brushes.White, rectangles.ToArray());
        g.DrawRectangles(Pens.Black, rectangles.ToArray());
    }
    protected override bool ProcessTabKey(bool forward)
    {
        return false;
    }
    protected override void OnResize(EventArgs e)
    {
        base.OnResize(e);
        this.Refresh();
    }
}

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

первый ярлык, который вы можете рассмотреть, - это нарисовать держатели для элементов управления, чтобы вы не зависели от класса Управления. Работает отлично, пока это не нужно слишком внимательно смотреть на реальный контроль (т. е. отказаться от WYSIWYG), и вам не нужно их изменять.

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

и последнее, но не менее важное: стоит отметить, что вы также можете использовать существующий конструктор Winforms в своих собственных проектах. Вы должны реализовать IDesignerHost. И еще куча, к сожалению, уровень абстракции довольно высок, а документы MSDN довольно кратки. Лучше всего работать с образцом, который показывает полнофункционального дизайнера. эта статья KB имеет ссылка на сайт. Код отлично и хорошо документирован, вы получаете почти полный конструктор с панелью инструментов и окном свойств, который де / сериализует дизайн из / в XML и может генерировать C# и VB.NET код. Посмотрите мимо кричащего пользовательского интерфейса, он не включает визуальные стили и выбор цвета-это то, что я бы сделал :) сделать это довольно не было целью образца кода.


Я создал приложение Windows Form надеюсь, что это поможет вам

Бэкэнд C# Код

using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Data;
using System.Drawing;
using System.Drawing.Drawing2D;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using System.Windows.Forms;

namespace WindowsFormsApplication2
{

    public partial class Form1 : Form
    {
        public Form1()
        {
            InitializeComponent();
            this.Paint += new PaintEventHandler(this_Paint);
        }
        private void this_Paint(object sender, PaintEventArgs e)
        {
            Pen pen = new Pen(Color.Green, 2.0F);
            pen.DashStyle = DashStyle.Dash;
            foreach (Control c in groupBox1.Controls)
            {
                e.Graphics.DrawRectangle(pen, (groupBox1.Location.X + c.Location.X)-1, (groupBox1.Location.Y + c.Location.Y)-1, c.Width + 2, c.Height + 2);
            }
            pen.Dispose();
        }
        private void Form1_Load(object sender, EventArgs e)
        {

        }
    }
}

Дизайнер C# Код

namespace WindowsFormsApplication2
{
    partial class Form1
    {
        /// <summary>
        /// Required designer variable.
        /// </summary>
        private System.ComponentModel.IContainer components = null;

        /// <summary>
        /// Clean up any resources being used.
        /// </summary>
        /// <param name="disposing">true if managed resources should be disposed; otherwise, false.</param>
        protected override void Dispose(bool disposing)
        {
            if (disposing && (components != null))
            {
                components.Dispose();
            }
            base.Dispose(disposing);
        }

        #region Windows Form Designer generated code

        /// <summary>
        /// Required method for Designer support - do not modify
        /// the contents of this method with the code editor.
        /// </summary>
        private void InitializeComponent()
        {
            this.groupBox1 = new System.Windows.Forms.GroupBox();
            this.comboBox1 = new System.Windows.Forms.ComboBox();
            this.comboBox2 = new System.Windows.Forms.ComboBox();
            this.comboBox3 = new System.Windows.Forms.ComboBox();
            this.comboBox4 = new System.Windows.Forms.ComboBox();
            this.groupBox1.SuspendLayout();
            this.SuspendLayout();
            // 
            // groupBox1
            // 
            this.groupBox1.BackColor = System.Drawing.Color.Transparent;
            this.groupBox1.Controls.Add(this.comboBox4);
            this.groupBox1.Controls.Add(this.comboBox3);
            this.groupBox1.Controls.Add(this.comboBox2);
            this.groupBox1.Controls.Add(this.comboBox1);
            this.groupBox1.Location = new System.Drawing.Point(33, 36);
            this.groupBox1.Name = "groupBox1";
            this.groupBox1.Size = new System.Drawing.Size(193, 184);
            this.groupBox1.TabIndex = 0;
            this.groupBox1.TabStop = false;
            this.groupBox1.Text = "groupBox1";
            // 
            // comboBox1
            // 
            this.comboBox1.FormattingEnabled = true;
            this.comboBox1.Location = new System.Drawing.Point(36, 40);
            this.comboBox1.Name = "comboBox1";
            this.comboBox1.Size = new System.Drawing.Size(121, 21);
            this.comboBox1.TabIndex = 0;
            // 
            // comboBox2
            // 
            this.comboBox2.FormattingEnabled = true;
            this.comboBox2.Location = new System.Drawing.Point(36, 67);
            this.comboBox2.Name = "comboBox2";
            this.comboBox2.Size = new System.Drawing.Size(121, 21);
            this.comboBox2.TabIndex = 1;
            // 
            // comboBox3
            // 
            this.comboBox3.FormattingEnabled = true;
            this.comboBox3.Location = new System.Drawing.Point(36, 94);
            this.comboBox3.Name = "comboBox3";
            this.comboBox3.Size = new System.Drawing.Size(121, 21);
            this.comboBox3.TabIndex = 1;
            // 
            // comboBox4
            // 
            this.comboBox4.FormattingEnabled = true;
            this.comboBox4.Location = new System.Drawing.Point(36, 121);
            this.comboBox4.Name = "comboBox4";
            this.comboBox4.Size = new System.Drawing.Size(121, 21);
            this.comboBox4.TabIndex = 1;
            // 
            // Form1
            // 
            this.AutoScaleDimensions = new System.Drawing.SizeF(6F, 13F);
            this.AutoScaleMode = System.Windows.Forms.AutoScaleMode.Font;
            this.ClientSize = new System.Drawing.Size(284, 261);
            this.Controls.Add(this.groupBox1);
            this.Name = "Form1";
            this.Text = "Form1";
            this.Load += new System.EventHandler(this.Form1_Load);
            this.groupBox1.ResumeLayout(false);
            this.ResumeLayout(false);

        }

        #endregion

        private System.Windows.Forms.GroupBox groupBox1;
        private System.Windows.Forms.ComboBox comboBox1;
        private System.Windows.Forms.ComboBox comboBox4;
        private System.Windows.Forms.ComboBox comboBox3;
        private System.Windows.Forms.ComboBox comboBox2;
    }
}

enter image description here

сделать GroupBox1 backgroundcolour "прозрачным", потому что я рисую на форме не на GroupBox

вы также можете создать границу для выбранных элементов управления, добавив если (c-ComboBox)

или if (c.Name = = "comboBox1") в цикле foreach

!! Измените цвет согласно вашей потребности !!


public partial class Form1 : Form
{
    public Form1()
    {
        InitializeComponent();
        hatchedPen = (Pen)SystemPens.ControlDarkDark.Clone();
        hatchedPen.DashStyle = System.Drawing.Drawing2D.DashStyle.Dot;
    }

    protected override void OnPaint(PaintEventArgs e)
    {
        base.OnPaint(e);
        // Clear any existing grab handles
        using (Graphics g = Graphics.FromHwnd(this.Handle))
        {

            foreach (Control ctrl in Controls)
            {
                var rect = GetGrabBounds(ctrl);
                g.FillRectangle(SystemBrushes.ButtonFace, rect);
            }
        }

        // Need to draw grab handles?
        if (ActiveControl != null && e.ClipRectangle.IntersectsWith(GetGrabBounds(ActiveControl)))
        {
            DrawGrabHandles(ActiveControl);
        }
    }

    private void DrawGrabHandles(Control ctrl)
    {
        using (Graphics g = Graphics.FromHwnd(this.Handle))
        {
            Rectangle bounds = GetGrabRect(ctrl);
            g.DrawRectangle(hatchedPen, bounds);
            foreach (Point pt in new Point[]
                {
                    new Point(bounds.Left, bounds.Top),
                    new Point(bounds.Left + bounds.Width / 2, bounds.Top),
                    new Point(bounds.Right, bounds.Top),
                    new Point(bounds.Left, bounds.Top + bounds.Height / 2),
                    new Point(bounds.Right, bounds.Top + bounds.Height / 2),
                    new Point(bounds.Left, bounds.Bottom),
                    new Point(bounds.Left + bounds.Width / 2, bounds.Bottom),
                    new Point(bounds.Right, bounds.Bottom),

                })
            {
                Rectangle r = new Rectangle(pt, new Size(5, 5));
                r.X = r.X - 2;
                r.Y = r.Y - 2;
                g.FillRectangle(SystemBrushes.ButtonFace, r);
                g.DrawRectangle(SystemPens.ControlDarkDark, r);
            }
        }
    }

    private static Rectangle GetGrabRect(Control ctrl)
    {
        var result = ctrl.Bounds;
        result = Rectangle.Inflate(result, 4, 4);
        result.X--;
        result.Y--;
        return result;
    }

    private static Rectangle GetGrabBounds(Control ctrl)
    {
        var result = GetGrabRect(ctrl);
        result.Inflate(4, 4);
        return result;
    }

    private Pen hatchedPen;
}