问题描述
在配置窗体中,为了更好地提高用户体验,当某个值发生变化时,需要向用户提供是否保存。如果没有变化则不需要。举例说明。如下图所示,在这个界面有四配置内容:通用参数配置、压力源配置、温度配置和传感器配置。在每类配置中,又有很多具体的配置项。左上角的保存默认是灰色的,如果修改了任何一个值,那么就需要将保存按键亮起,同时如果此后用户退出或关闭窗体,要有相应的提醒,告之用户数据已经修改,询问是否需要保存。
需求不难理解,但是问题是,在配置项很多时,一个个检测不仅代码量大,而且当控件发生变更时,又需要做相应的处理。所以,为了解决此问题,本文提出了一种简易的方法,能够以极少的代码解决这个问题。
解决方案
第1步:获得所有控件
解决问题的关键是对那些值可能变化的控件进行监听。如果要监听,那么第一步是获得所有控件。C#的窗体控件结构是一种树形的结构,所以可以写一个遍历函数以获得所有控件。由于带有编辑功能的控件都派生于 Control 类(包括Form),所以我们只需要对此进行遍历即可。主要代码如下所示。
/// <summary>
/// 返回指定控件中的所有控件。
/// </summary>
/// <param name="control">待遍历控件。</param>
/// <param name="controls">控件列表。</param>
/// <returns></returns>
public List<Control> GetAllControls(Control control)
{
List<Control> controls = new List<Control>();
if (control != null)
{
foreach (Control subControl in control.Controls)
{
controls.Add(subControl);
controls.AddRange(GetAllControls(subControl));
}
}
return controls;
}
第2步:对需要监控的控件进行处理
我们使用一个全局变量 bool Modified 进行记录数据是否发生变化,然后对所需要监听的控件进行处理,主要代码如下所示:
/// 对指定窗体进行绑定,用于监听编辑控件中的值的变化。
/// </summary>
/// <param name="control"></param>
public ControlValueChangedListener(Control control)
{
Modified = false;
foreach (var item in GetAllControls(control))
{
if (item is TextBox tb)
tb.TextChanged += (sender, e) => Modified = tb.Focused ? true : modified;
else if (item is ComboBox cb)
cb.TextChanged += (sender, e) => Modified = cb.Focused ? true : modified;
}
}
如上所示,我们为TextBox控件,ComboBox控件编写了相应的事件处理代码,在数据发生变更时进行记录。由于这个函数是对窗体所有的控件有效,所以窗体中所有的TextBox控件,ComboBox控件都加上了相应的代码。
通过以上代码的编写,我们即可实现了其基本功能。为了便于操作,源代码中还加入了事件(参见附录)。
使用方法
使用此类只需要两步:
- 在窗体内定义一个全局变量
ControlValueChangedListener listener;
- 在窗体Load事件中初始化此对象
listener = new ControlValueChangedListener(this);
设置完成以后,当窗体的数据被编辑改变后,listener.Modifed
的值会相应改变,所以需要判断是否有值被修改,只需检测 listener.Modifed
的值即可。
注意事项
- 请在窗体所有控件都加载完成以后再初始化此类。建议在Form_Load 最后执行第2步。
- 如果需要在Modifed变更后执行一些操作,可以使用 ModifedChanged事件。举例来说,如果值发生变化时,我希望保存按钮
btnSave.Enable = true
。具体用法为:
listener = new ControlValueChangedListener(this);
listener.ModifedChanged += (sender, arg) => tiSave.Enabled = true;
附录:源代码
using System;
using System.Collections.Generic;
using System.Windows.Forms;
namespace Hao
{
/// <summary>
/// 参数为旧值,新值和是否取消修改.
/// </summary>
public class ModifedChangedEventArgs : EventArgs
{
public bool OldValue { get; set; } = false;
public bool NewValue { get; set; } = false;
public bool IsCanceled { get; set; } = false;
}
/// <summary>
/// 属性发生变化代理
/// </summary>
/// <param name="sender"></param>
/// <param name="e">修改相关的参数。</param>
public delegate void ControlModificationEventHandler(object sender, ModifedChangedEventArgs e);
/// <summary>
/// 用于对窗体中的编辑事件进行监听。
/// </summary>
public class ControlValueChangedListener
{
/// <summary>
/// 在Modifed的值变化后触发。
/// </summary>
public event ControlModificationEventHandler ModifedChanged;
bool modified = false;
/// <summary>
/// 控件是否已经修改,同时会触发 ModifedChanged 事件。
/// </summary>
public bool Modified
{
get { return modified; }
set
{
Console.WriteLine("Old value: " + modified + "\tnew value: " + value);
if (modified != value && ModifedChanged != null)
{
ModifedChangedEventArgs args = new ModifedChangedEventArgs()
{
OldValue = modified,
NewValue = value,
IsCanceled = false
};
ModifedChanged.Invoke(this, args);
if (!args.IsCanceled)
modified = value;
}
}
}
/// <summary>
/// 对指定窗体进行绑定,用于监听编辑控件中的值的变化。
/// </summary>
/// <param name="control"></param>
public ControlValueChangedListener(Control control)
{
Modified = false;
foreach (var item in GetAllControls(control))
{
if (item is TextBox tb)
tb.TextChanged += (sender, e) => Modified = tb.Focused ? true : modified;
else if (item is ComboBox cb)
cb.TextChanged += (sender, e) => Modified = cb.Focused ? true : modified;
// 如果有更多的控件需要监听,可在此加入相应的代码
}
}
/// <summary>
/// 返回指定控件其子属性。
/// </summary>
/// <param name="control">待遍历控件。</param>
/// <param name="controls">控件列表。</param>
/// <returns></returns>
public List<Control> GetAllControls(Control control)
{
List<Control> controls = new List<Control>();
if (control != null)
{
foreach (Control subControl in control.Controls)
{
controls.Add(subControl);
controls.AddRange(GetAllControls(subControl));
}
}
return controls;
}
}
}