本文介绍了在 C# Winforms 中,有没有办法在所有控件周围放置虚线边框,并在运行时选择特定控件时显示夹点?的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

我在一个使用类似于 Visual Studio 的 IDE 的团队中工作,为我们的本地客户开发自定义 Winform 代码.在我们的代码中,我们重写了用户控件以使我们的任务更轻松,但我们的大多数控件都派生自基本的 C# Winform 控件.

我目前需要帮助在我们的所有控件周围实现虚线边框,使用 Visual Studio 提供的夹点类型.

未选中的控件

选定控件

此功能的需求量很大,因为它可以帮助对齐而无需对视觉指南进行补偿.

我们目前在所有控件周围实现了一个深色边框,使用

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

在生成的控件周围放置一个黑色边框,在上面的代码片段中是一个组合框.

一位成员指出我们要使用 Microsoft 文档中所示的边距和填充:

在示例中,我只是创建了一个透明面板并绘制了选择边框.这只是一个示例,执行大小调整和定位超出了示例的范围.它只是向您展示如何在控件周围绘制选择边框.您还可以使用该想法创建一个 SelctionBorder 控件并将大小和定位逻辑封装在控件中,而不是绘制边框,而是将 SelectionBorder 控件的实例添加到透明面板并在其大小和定位事件中,更改相应的控件坐标.

请注意,这只是一个示例,在真实的设计师环境中,您应该考虑很多重要的事情.

透明面板

使用 System.Windows.Forms;公共类透明面板:面板{常量 int WS_EX_TRANSPARENT = 0x20;受保护的覆盖 CreateParams CreateParams{得到{CreateParams cp = base.CreateParams;cp.ExStyle = cp.ExStyle |WS_EX_TRANSPARENT;返回cp;}}受保护的覆盖无效 OnPaintBackground(PaintEventArgs e){}}

寄宿表格

使用系统;使用 System.Collections.Generic;使用 System.Drawing;使用 System.Linq;使用 System.Windows.Forms;公共部分类 HostForm :表格{私有面板容器面板;私有透明面板透明面板;私有 PropertyGrid 属性网格;公共主机表单(){this.transparentPanel = 新的透明面板();this.containerPanel = 新面板();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 = "容器面板";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);}私有 void HostForm_Load(对象发送者,EventArgs e){this.ActiveControl = 透明面板;/******************************************//*加载要编辑的表单*//******************************************/var f = 新表格();f.Location = new Point(8, 8);f.TopLevel = 假;this.containerPanel.Controls.Add(f);选定对象 = f;f.显示();}控制选定对象;控制选定对象{获取{返回选定对象;}放{选定对象 = 值;propertyGrid.SelectedObject = 值;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();}void transparentPanel_Paint(对象发送者,PaintEventArgs e){如果(选定对象!= null)DrawBorder(e.Graphics, this.transparentPanel.RectangleToClient(SelectedObject.Parent.RectangleToScreen(SelectedObject.Bounds)));}私有 IEnumerable<Control>GetAllControls(控制控件){var controls = control.Controls.Cast<Control>();return controls.SelectMany(ctrl => GetAllControls(ctrl)).Concat(controls);}void DrawBorder(图形 g,矩形 r){变量 d = 4;r.Inflate(d, d);ControlPaint.DrawBorder(g, r, Color.Black, ButtonBorderStyle.Dotted);var rectangles = 新列表<矩形>();var r1 = new Rectangle(r.Left - d, r.Top - d, 2 * d, 2 * d);矩形.Add(r1);r1.Offset(r.Width/2, 0);矩形.Add(r1);r1.Offset(r.Width/2, 0);矩形.Add(r1);r1.Offset(0, r.Height/2);矩形.Add(r1);r1.Offset(0, r.Height/2);矩形.Add(r1);r1.Offset(-r.Width/2, 0);矩形.Add(r1);r1.Offset(-r.Width/2, 0);矩形.Add(r1);r1.Offset(0, -r.Height/2);矩形.Add(r1);g.FillRectangles(Brushes.White, rectangles.ToArray());g.DrawRectangles(Pens.Black, rectangles.ToArray());}protected override bool ProcessTabKey(bool forward){返回假;}受保护的覆盖无效 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:

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 中,有没有办法在所有控件周围放置虚线边框,并在运行时选择特定控件时显示夹点?的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持!

08-04 05:31