如何阻止设计时拖动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异常,可采用以下方案:
方法一:自定义控件设计器(推荐)
通过自定义设计器拦截子控件的选中逻辑,确保设计时只能选中容器本身,无法拖动内部按钮:
- 添加设计器相关引用:
using System.ComponentModel.Design; using System.Windows.Forms.Design;
- 为
ucSwitchButtonList标记自定义设计器:
[Designer(typeof(ucSwitchButtonListDesigner))] public partial class ucSwitchButtonList : UserControl { // 原有代码保持不变 }
- 实现自定义设计器类:
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
相关产品推荐
相关产品推荐

