You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

如何阻止设计时拖动ucSwitchButtonList内按钮并解决空引用异常

问题描述

我们创建了名为ucSwitchButtonList的用户控件作为容器,用于维护按钮状态与排列按钮位置,该控件仅包含一个FlowLayoutPanel。编译后可在窗体上添加ucSwitchButtonList,并通过CollectionEditor成功添加按钮;设计时可修改按钮属性,但拖动按钮时Visual Studio会抛出NullReferenceException异常。我们希望实现类似TabControl中TabPage的效果:按钮可选中但设计时无法拖动,请问如何阻止拖动或处理设计时拖动事件?


相关代码

ucSwitchButtonList.Designer.cs

partial class ucSwitchButtonList
{
    private System.ComponentModel.IContainer components = null;

    protected override void Dispose(bool disposing)
    {
        if (disposing && (components != null))
        {
            components.Dispose();
        }
        base.Dispose(disposing);
    }

    private void InitializeComponent()
    {
        this.flpBtnContainer = new System.Windows.Forms.FlowLayoutPanel();
        this.SuspendLayout();
        // 
        // flpBtnContainer
        // 
        this.flpBtnContainer.AutoScroll = true;
        this.flpBtnContainer.AutoSizeMode = System.Windows.Forms.AutoSizeMode.GrowAndShrink;
        this.flpBtnContainer.Location = new System.Drawing.Point(1, 1);
        this.flpBtnContainer.Margin = new System.Windows.Forms.Padding(0);
        this.flpBtnContainer.Name = "flpBtnContainer";
        this.flpBtnContainer.Size = new System.Drawing.Size(116, 91);
        this.flpBtnContainer.TabIndex = 0;
        // 
        // ucSwitchButtonList
        // 
        this.AutoScaleDimensions = new System.Drawing.SizeF(6F, 12F);
        this.AutoScaleMode = System.Windows.Forms.AutoScaleMode.Font;
        this.AutoSizeMode = System.Windows.Forms.AutoSizeMode.GrowAndShrink;
        this.Controls.Add(this.flpBtnContainer);
        this.DoubleBuffered = true;
        this.Font = new System.Drawing.Font("新細明體", 9F, System.Drawing.FontStyle.Regular, System.Drawing.GraphicsUnit.Point, ((byte)(136)));
        this.Name = "ucSwitchButtonList";
        this.Padding = new System.Windows.Forms.Padding(1);
        this.Size = new System.Drawing.Size(209, 202);
        this.Load += new System.EventHandler(this.ucSwitchButtonList_Load);
        this.ResumeLayout(false);

    }

    private System.Windows.Forms.Panel flpBtnContainer;
}

ucSwitchButtonList.cs

public partial class ucSwitchButtonList : UserControl
{
    ObservableCollection<Button> _ButtonList = new System.Collections.ObjectModel.ObservableCollection<Button>();
    [DesignerSerializationVisibility(DesignerSerializationVisibility.Content)]
    [Browsable(true), EditorBrowsable(EditorBrowsableState.Always),
    Editor(typeof(CollectionEditor), typeof(UITypeEditor))
    ]
    public ObservableCollection<Button> ButtonList
    {
        get => _ButtonList;
    }

    public ucSwitchButtonList()
    {
        InitializeComponent();
        flpBtnContainer.Padding = new Padding(0);
        flpBtnContainer.Size = this.Size;
        flpBtnContainer.Dock = DockStyle.Fill;
        _ButtonList.CollectionChanged += ButtonList_CollectionChanged;
        Size = new Size(80, 160);
    }

    private void ucSwitchButtonList_Load(object sender, EventArgs e)
    {

    }

    private void ButtonList_CollectionChanged(object sender, System.Collections.Specialized.NotifyCollectionChangedEventArgs e)
    {
        flpBtnContainer.SuspendLayout();
        flpBtnContainer.Controls.Clear();
        flpBtnContainer.Controls.AddRange(_ButtonList.ToArray());
        flpBtnContainer.ResumeLayout();

        foreach (Control ctl in flpBtnContainer.Controls)
        {
            if (ctl is Button btn)
            {
                btn.Click += Btn_Click;
                if (e.NewItems.Contains(btn))
                {
                    btn.BackColor = Color.White;
                }
            }
        }

        Invalidate(true);
    }

    private void Btn_Click(object sender, EventArgs e)
    {
    }

    protected override void OnPaint(PaintEventArgs e)
    {
        base.OnPaint(e);
        if (DesignMode && BorderStyle == BorderStyle.None)
        {
            var rect = Rectangle.Inflate(ClientRectangle, 0, 0);
            rect.Width--;
            rect.Height--;
            Color lc = ControlPaint.Dark(Parent.BackColor);
            if (lc == Parent.BackColor) lc = ControlPaint.LightLight(Parent.BackColor);

            using (Pen lp = new Pen(lc))
            {
                lp.DashStyle = System.Drawing.Drawing2D.DashStyle.Dash;
                lp.Alignment = System.Drawing.Drawing2D.PenAlignment.Center;
                e.Graphics.DrawRectangle(lp, rect);
            }
        }
    }

}

解决方案

要实现类似TabControl中TabPage的设计时行为(禁止拖动子控件但保留属性编辑),同时修复NullReferenceException异常,可采用以下方案:

方法一:自定义控件设计器(推荐)

通过自定义设计器拦截子控件的选中逻辑,确保设计时只能选中容器本身,无法拖动内部按钮:

  1. 添加设计器相关引用:
using System.ComponentModel.Design;
using System.Windows.Forms.Design;
  1. 为ucSwitchButtonList标记自定义设计器:
[Designer(typeof(ucSwitchButtonListDesigner))]
public partial class ucSwitchButtonList : UserControl
{
    // 原有代码保持不变
}
  1. 实现自定义设计器类:
public class ucSwitchButtonListDesigner : ParentControlDesigner
{
    protected override bool GetHitTest(Point point)
    {
        // 点击子控件时,将命中测试传递给容器,避免子控件被选中
        return false;
    }

    public override SelectionRules SelectionRules
    {
        get
        {
            // 保留容器的可调整大小和移动权限,同时禁止子控件的选择规则
            return SelectionRules.AllSizeable | SelectionRules.Moveable;
        }
    }
}

方法二:直接修改子控件的设计时属性

在添加按钮时,通过代码修改按钮的设计器规则,禁止其被选中和拖动:

修改ButtonList_CollectionChanged方法中的按钮初始化逻辑:

foreach (Control ctl in flpBtnContainer.Controls)
{
    if (ctl is Button btn)
    {
        btn.Click += Btn_Click;
        // 修复NullReferenceException:判断e.NewItems不为空再执行
        if (e.NewItems != null && e.NewItems.Contains(btn))
        {
            btn.BackColor = Color.White;
            
            // 设计时禁止按钮被选中拖动
            if (DesignMode)
            {
                var site = btn.Site;
                if (site != null)
                {
                    var designerHost = site.GetService(typeof(IDesignerHost)) as IDesignerHost;
                    if (designerHost != null)
                    {
                        var btnDesigner = designerHost.GetDesigner(btn) as ControlDesigner;
                        if (btnDesigner != null)
                        {
                            btnDesigner.SelectionRules = SelectionRules.None;
                        }
                    }
                }
            }
        }
    }
}

必做异常修复

原代码中e.NewItems.Contains(btn)在集合清空时会因e.NewItems为null抛出异常,上述代码已添加e.NewItems != null的空值判断,彻底解决该问题。


内容的提问来源于stack exchange,提问作者dw4dev

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.08.09 04:15:42