En C # Winforms, ¿hay alguna manera de poner un borde punteado alrededor de todos los controles y mostrar los puntos de agarre al seleccionar los controles específicos en el tiempo de ejecución?

Trabajo en un equipo que trabaja en un IDE similar a Visual Studio para desarrollar código de Winform personalizado para nuestros clientes locales. En nuestro código, tenemos controles de usuario anulados para facilitar nuestras tareas, pero la mayoría de nuestros controles se derivan de controles de forma de C básicos.

Actualmente necesito ayuda para implementar el borde punteado alrededor de todos nuestros controles, con el tipo de puntos de agarre proporcionados por Visual Studio.

Controles no seleccionados

enter image description here

Controles seleccionados

enter image description here

Esta función es muy demandada, ya que puede ayudar a alinear sin compensación las pautas visuales.

Actualmente hemos implementado un borde oscuro alrededor de todos los controles, usando

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

Que pone un borde negro alrededor de los Controles generados, que en el fragmento de código anterior es un ComboBox.

Un miembro nos indicó utilizar los márgenes y el relleno como se muestra en la documentación de Microsoft: https://msdn.microsoft.com/library/3z3f9e8b(v=vs.110)

Pero esto es principalmente teoría y no parece ayudar mucho. Lo más cercano que ha llegado para resolver este problema hasta ahora ha sido un enlace de CodeProject en línea:

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

Estoy sorprendido de no encontrar una coincidencia cercana a mi búsqueda hasta el momento, tal vez estoy usando la terminología incorrecta, ya que recientemente ingresé a la progtwigción en este dominio.

Creo que las futuras búsquedas en línea se beneficiarán si se resuelve este problema. Mirando hacia adelante para los punteros forma aquellos con experiencia en este problema. Realmente aprecio cualquier ayuda en esta dirección.

Trabajo en equipo trabajando en un IDE similar a Visual Studio ….

Desarrollar un diseñador de formulario personalizado no es una tarea trivial y necesita mucho conocimiento y mucho tiempo, y creo que la mejor solución que puede usar es el diseñador de formularios de Windows.

No se trata solo de dibujar fronteras de selección:

  • Cada control tiene su propio diseñador con características específicas, por ejemplo, algunos controles, como MenuStrip tienen su propio diseñador, lo que le permite agregar / eliminar elementos en el diseñador.
  • Los controles pueden tener algunas reglas específicas de dimensionamiento y posicionamiento. Por ejemplo, algunos de ellos son de tamaño automático, como TextBox o los controles acoplados no pueden ser reubicados por el mouse, y así sucesivamente.
  • Los componentes no son visibles en su formulario, por lo que puede necesitar editarlos.
  • Algunas propiedades son propiedades de tiempo de diseño.
  • Algunas propiedades se agregan usando proveedores de extensión y debe realizar tareas adicionales para proporcionar una forma de cambiarlas en su diseñador personalizado.
  • Y muchas otras consideraciones.

Solución 1 – Hosting Windows Forms Designer

Para obtener más información sobre la architecture del tiempo de diseño, eche un vistazo a Design-Time Architecture . Para alojar el diseñador de formularios de Windows en su aplicación, debe implementar algunas interfaces como IDesignerHost , IContainer , IComponentChangeService , IExtenderProvider , ITypeDescriptorFilterService , IExtenderListService , IExtenderProviderService .

Para algunos buenos ejemplos puedes echar un vistazo a:

  • Hosting Windows Forms Designers por Tim Dawson
  • Adapte su aplicación construyendo un diseñador de formularios personalizado con .NET por Sayed Y. Hashimi

Solución 2 – Dibujar el borde de selección en un panel transparente

Si bien recomiendo utilizar la primera solución, pero solo para fines de aprendizaje si desea dibujar un borde de selección alrededor de controles, puede agregar los formularios que desea editar como control al formulario de host, luego coloque un panel transparente sobre el formulario . Controlar el evento Click del Panel transparente y encontrar el control debajo de la posición del mouse y dibujar un borde de selección alrededor del mismo en un panel transparente como este:

enter image description here

En el ejemplo, acabo de crear un panel transparente y dibujar un borde de selección. Es solo un ejemplo y realizar el dimensionamiento y el posicionamiento está fuera del scope del ejemplo. Es solo para mostrarle cómo puede dibujar el borde de selección alrededor de los controles. También puede usar la idea para crear un control SelctionBorder y encapsular la lógica de dimensionamiento y posicionamiento en el control y en lugar de dibujar los bordes, agregar una instancia del control SelectionBorder al panel transparente y en sus eventos de dimensionamiento y posicionamiento, cambiar las coordenadas de control correspondientes.

Preste atención, es solo un ejemplo y en un entorno de diseñador real, debe considerar muchas cosas importantes.

Panel transparente

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

Formulario de host

 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 GetAllControls(Control control) { var controls = control.Controls.Cast(); 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(); 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(); } } 

En este caso, sería conveniente tener cuidado al modelar un diseñador de IU después de que el diseñador de Winforms sea una decisión fácil, de hecho implementarlo es un trabajo que puede mantenerlo ocupado durante muchos meses. Descubrir que no puedes pintar fuera de los límites de control es de hecho el primer obstáculo con el que te encontrarás, muchos más así.

El primer atajo que podría considerar es dibujar marcadores de posición para los controles para que no dependa de la clase Control. Funciona bien siempre que no tenga que mirar muy de cerca como el control real (es decir, renunciar a WYSIWYG) y no tiene que cambiar el tamaño de ellos.

Pero seguramente descartarás eso. Luego tiene que hacer lo mismo que el diseñador de Winforms, tiene que superponer una ventana transparente en la parte superior de la superficie de diseño. Puede dibujar todo lo que desee en esa superposición y proporciona un aislamiento automático del mouse y del teclado, por lo que el control en sí mismo es completamente ajeno a la interacción en tiempo de diseño. Encuentre ejemplos de dicha superposición en esta publicación y esta publicación .

Por último, cabe mencionar que también puede aprovechar el diseñador existente de Winforms en sus propios proyectos. Tienes que implementar IDesignerHost. Y un montón más, desafortunadamente el nivel de abstracción es bastante alto y los documentos de MSDN son bastante breves. Lo mejor que puede hacer es trabajar a partir de una muestra que muestra un diseñador con todas las funciones. Este artículo de KB tiene el enlace. El código es excelente y está bien documentado. Obtiene un diseñador casi completo con la ventana Caja de herramientas y Propiedades que de / serializa el diseño desde / a XML y puede generar código C # y VB.NET. Mire más allá de la interfaz de usuario deslumbrante, no habilita los estilos visuales y las opciones de color son las que haría 🙂 Lo bonito no era el objective de la muestra del código.

He creado I Windows Form Application Espero que esto te ayude

Código BackEnd 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ódigo de diseñador C #

 namespace WindowsFormsApplication2 { partial class Form1 { ///  /// Required designer variable. ///  private System.ComponentModel.IContainer components = null; ///  /// Clean up any resources being used. ///  /// true if managed resources should be disposed; otherwise, false. protected override void Dispose(bool disposing) { if (disposing && (components != null)) { components.Dispose(); } base.Dispose(disposing); } #region Windows Form Designer generated code ///  /// Required method for Designer support - do not modify /// the contents of this method with the code editor. ///  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

Hacer el color de fondo de GroupBox1 ‘Transparente’ porque estoy dibujando en Formulario no en GroupBox

También puede crear un borde en los controles seleccionados al agregar if (c es ComboBox)

o si (c.Name == “comboBox1”) en el bucle foreach

!! ¡Cambia el color según tu necesidad!

 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)); rX = rX - 2; rY = rY - 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; }