问题描述
我在一个团队中工作,开发类似于 Visual Studio 的 IDE,为我们的本地客户开发自定义 Winform 代码.在我们的代码中,我们覆盖了用户控件以简化我们的任务,但我们的大多数控件都源自基本的 C# Winform 控件.
我目前需要帮助来在所有控件周围实现虚线边框,使用 Visual Studio 提供的抓点类型.
未选择的控件
选定控件
此功能的需求量很大,因为它可以帮助对齐而不补偿视觉指南.
我们目前已经在所有控件周围实现了一个黑色边框,使用
this.BackColor = Color.Black;this.Height = ComboBox.Height + 4;
在生成的控件周围放置一个黑色边框,在上面的代码片段中是一个组合框.
一位成员向我们指出使用边距和填充,如 Microsoft 文档中所示:
在示例中,我刚刚创建了一个透明面板并绘制了选择边框.这只是一个示例,执行大小调整和定位超出了示例的范围.这只是为了向您展示如何在控件周围绘制选择边框.也可以用这个思路创建一个SelctionBorder
控件,并在控件中封装大小和定位逻辑,而不是绘制边框,将一个SelectionBorder
控件的实例添加到透明面板中并在其大小和定位事件中,更改相应的控件坐标.
请注意,这只是一个例子,在真正的设计师环境中,您应该考虑很多重要的事情.
透明面板
使用 System.Windows.Forms;公共类透明面板:面板{const int WS_EX_TRANSPARENT = 0x20;受保护的覆盖 CreateParams CreateParams{得到{CreateParams cp = base.CreateParams;cp.ExStyle = cp.ExStyle |WS_EX_TRANSPARENT;返回cp;}}protected override void OnPaintBackground(PaintEventArgs e){}}
主办表格
使用系统;使用 System.Collections.Generic;使用 System.Drawing;使用 System.Linq;使用 System.Windows.Forms;公共部分类 HostForm :表单{私人面板容器面板;私有透明面板透明面板;私有 PropertyGrid propertyGrid;公共主机窗体(){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 = "透明面板";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 = "主机";this.Load += this.HostForm_Load;this.transparentPanel.MouseClick += this.transparentPanel_MouseClick;this.transparentPanel.Paint += this.transparentPanel_Paint;this.ResumeLayout(false);}private void HostForm_Load(对象发送者,EventArgs e){this.ActiveControl = 透明面板;/********************************************//*加载要编辑的表单*//********************************************/var f = new Form();f.Location = new Point(8, 8);f.TopLevel = false;this.containerPanel.Controls.Add(f);SelectedObject = f;f.Show();}控制选定对象;控制选定对象{得到 { 返回选定的对象;}放{selectedObject = 值;propertyGrid.SelectedObject = value;this.Refresh();}}void transparentPanel_MouseClick(对象发送者,MouseEventArgs e){如果(this.Controls.Count == 0)返回;SelectedObject = GetAllControls(this.containerPanel).Where(x => x.Visible).Where(x => x.Parent.RectangleToScreen(x.Bounds).Contains(this.transparentPanel.PointToScreen(e.Location))).FirstOrDefault();this.Refresh();}无效transparentPanel_Paint(对象发送者,PaintEventArgs e){if (SelectedObject != null)DrawBorder(e.Graphics, this.transparentPanel.RectangleToClient(SelectedObject.Parent.RectangleToScreen(SelectedObject.Bounds)));}私有 IEnumerable<Control>GetAllControls(控制控件){var 控件 = control.Controls.Cast();返回controls.SelectMany(ctrl => GetAllControls(ctrl)).Concat(controls);}无效的DrawBorder(图形g,矩形r){无功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);矩形.添加(r1);r1.Offset(r.Width/2, 0);矩形.添加(r1);r1.Offset(r.Width/2, 0);矩形.添加(r1);r1.Offset(0, r.Height/2);矩形.添加(r1);r1.Offset(0, r.Height/2);矩形.添加(r1);r1.Offset(-r.Width/2, 0);矩形.添加(r1);r1.Offset(-r.Width/2, 0);矩形.添加(r1);r1.Offset(0, -r.Height/2);矩形.添加(r1);g.FillRectangles(Brushes.White, rectangles.ToArray());g.DrawRectangles(Pens.Black, rectangles.ToArray());}protected override bool ProcessTabKey(bool forward){返回假;}protected override void OnResize(EventArgs e){base.OnResize(e);this.Refresh();}}
I work in a team working on a IDE similar to Visual Studio to develop custom Winform code for our local clients. In our code we have User Controls overridden to make our tasks easier but most of our controls are derived from basic C# Winform Controls.
I currently need help in implementing dotted border around all our controls, with the type of grip points as provided by Visual Studio.
Unselected Controls
Selected Controls
This feature is highly demanded as it can help in aligning without compensation on visual guidelines.
We have currently implemented a dark border around all controls, using
this.BackColor = Color.Black;
this.Height = ComboBox.Height + 4;
Which puts a black border around the generated Controls, which in the above code snippet is a ComboBox.
One member pointed us towards using Margins and Padding as shown in the Microsoft documentation: https://msdn.microsoft.com/library/3z3f9e8b(v=vs.110)
But this is mostly theory and does not seem to help much. the closest thing that has come to solve this problem so far has been an online CodeProject link:
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);
}
}
I am surprized to not find a close match to my search so far, perhaps i am using the wrong terminology, as I recently got into programming in this domain.
I believe that future online searches are going to be benifitted, if this problem gets solved. Looking forward for pointers form those with experience in this problem. Really appreciate any help in this direction.
Developing a custom form designer is not a trivial task and needs a lot of knowledge and a lot of time and I believe the best solution which you can use, is hosting windows forms designer.
It's not just about drawing selection borders:
- Each control has it's own designer with specific features, for example some controls like
MenuStrip
has it's own designer which enables you to add/remove items on designer. - Controls may have some specific sizing and positioning rules. For example some of them are auto-sized like
TextBox
or docked controls can not be reposition by mouse and so on. - Components are not visible on your form which you may need to edit them.
- Some properties are design-time properties.
- Some properties are added using extender providers and you need to perform additional tasks to provide a way to change them in your custom designer.
- And a lot of other considerations.
Solution 1 - Hosting Windows Forms Designer
To learn more about design time architecture, take a look at Design-Time Architecture. To host windows forms designer in your application, you need to implement some interfaces like IDesignerHost
, IContainer
, IComponentChangeService
, IExtenderProvider
, ITypeDescriptorFilterService
, IExtenderListService
, IExtenderProviderService
.
For some good examples you can take a look at:
- Hosting Windows Forms Designers by Tim Dawson
- Tailor Your Application by Building a Custom Forms Designer with .NET by Sayed Y. Hashimi
You may find this post useful:
The post contains a working example on how to host windows forms designer at run-time and generate code:
Solution 2 - Drawing selection border over a transparent panel
While I strongly recommend using the first solution, but just for learning purpose if you want to draw selection border around controls, you can add the forms which you want to edit as a control to the host form, then put a transparent panel above the form. Handle Click
event of transparent Panel and find the control under mouse position and draw a selection border around it on transparent panel like this:
In the example, I just created a transparent panel and drew selection border. It's just an example and performing sizing and positioning is out of scope of the example. It's just to show you how you can draw selection border around controls. You also can use the idea to create a SelctionBorder
control and encapsulate sizing and positioning logic in the control and instead of drawing the borders, add an instance of SelectionBorder
control to transparent panel and in its sizing and positioning events, change corresponding control coordinates.
Please pay attention it's just an example and in a real designer environment you should consider a lot of important things.
Transparent Panel
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)
{
}
}
Host Form
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();
}
}
这篇关于在 C# Winforms 中,有没有办法在所有控件周围放置虚线边框并在运行时选择特定控件时显示抓点?的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持!