【问题标题】:Can I assign a method to multiple Form-based Events?我可以将一个方法分配给多个基于表单的事件吗?
【发布时间】:2012-07-02 21:04:49
【问题描述】:

我正在构建一个表单,它有几个 numericUpDown 控件、几个复选框控件和一些文本框等。每个控件都有一个触发的事件方法(CheckedChanged、ValueChanged 等),但我的主要问题是:

我想做的是运行一个方法来更新表单上的文本字段,但目前我只重复了 24 次。这行得通,但我觉得必须有更好的方法......下面是我到目前为止的一个例子。

    private void button3_Click(object sender, EventArgs e)
    {
    // Code Specific to the Buton3_Click
    UpdateTextLabel();
    }
    private void checkBox1_CheckedChanged(object sender, EventArgs e)
    {
    // Code Specific to the checkBox1_CheckChanged
    UpdateTextLabel();
    }
    private void numericUpDown1_ValueChanged(object sender, EventArgs e)
    {
    // numericUpDown1 specific code ....
    UpdateTextLabel();
    }
    private void comboBox1_SelectedIndexChanged(object sender, EventArgs e)
    {
    // comboBox1 specific stuff ...
    UpdateTextLabel();
    }
   // .... and so on for every method ....

有没有更好的方法来实现这一点?我想说“如果单击或更改了任何控件……请执行此“UpdateTextLabel()”操作”,但不知道该怎么做。很高兴被引导到答案,因为我在搜索中输入的问题似乎不是“正确的问题”......

谢谢!

【问题讨论】:

    标签: c# winforms delegates controls


    【解决方案1】:

    是的,任何控件的任何事件都可以共享相同的事件处理方法,只要它们的事件处理委托相同,在这种情况下,这些控件的事件处理委托都是“EventHandler”类型(没有返回值和 2 个参数:对象发送者和 EventArgs e)。

    private void UpdateTextLabel(object sender, EventArgs e)
    {
        //your original UpdateTextLabel code here
    }
    
    button3.Click += UpdateTextLabel;
    checkBox1.CheckedChanged += UpdateTextLabel;
    numericUpDown1.ValueChanged += UpdateTextLabel;
    comboBox1.SelectedIndexChanged += UpdateTextLabel;
    

    【讨论】:

    • 如果事件处理程序中的逻辑通常相同,我更喜欢这种方法。
    【解决方案2】:

    当然!您可以使用 lambdas 轻松处理未使用的参数:

    button3.Click += (sender, args) => UpdateTextLabel();
    checkBox1.CheckedChanged += (sender, args) => UpdateTextLabel();
    numericUpDown1.ValueChanged += (sender, args) => UpdateTextLabel();
    comboBox1.SelectedIndexChanged += (sender, args) => UpdateTextLabel();
    

    或者按照一些开发者的趋势,如果您不关心 args,您可以使用下划线“忽略”它们以提高可读性:

    button3.Click += (_, __) => UpdateTextLabel();
    checkBox1.CheckedChanged += (_, __) => UpdateTextLabel();
    numericUpDown1.ValueChanged += (_, __) => UpdateTextLabel();
    comboBox1.SelectedIndexChanged += (_, __) => UpdateTextLabel();
    

    强大的 Jon Skeet 曾经教过我,这远远优于 Visual Studio 的默认命名方案 CONTROLNAME_EVENTNAME,因为您可以轻松阅读“单击按钮 3 时,更新文本标签”或“更改组合框时” ,更新文本标签”。它还释放了您的代码文件以消除一堆无用的方法包装器。 :)

    编辑:如果你让它重复 24 次,从设计的角度来看,这似乎有点奇怪。 ... 再次阅读哦,该死的。我错过了 cmets,您想要运行特定代码以及更新文本框。好吧,您可以注册多个事件:

    button3.Click += (_, __) => SubmitForm();
    button3.Click += (_, __) => UpdateTextLabel();
    

    问题在于,技术上,事件监听器不能保证按顺序触发。但是,对于这个简单的案例(特别是如果您不使用 -= 并结合事件处理程序),您应该可以很好地保持执行顺序。 (我假设你需要 UpdateTextLabelSubmitForm 之后触发

    或者您可以将UpdateTextLabel 调用移动到您的按钮处理程序中:

    button3.Click += (_, __) => SubmitForm();
    
    private void SubmitForm(object sender, EventArgs e)
    {
        //do submission stuff
        UpdateTextLabel();
    }
    

    这会让你陷入同样的​​境地(尽管方法命名更好)。也许您应该将UpdateTextLabel 移动到您的表单的一般“重新绑定”中:

    button3.Click += (_, __) => SubmitForm();
    
    private void SubmitForm(object sender, EventArgs e)
    {
        //do submission stuff
        Rebind();
    }
    
    private void Rebind()
    {
        GatherInfo();
        UpdateTextLabel();
        UpdateTitles();
    }
    

    这样,如果您除了更新文本标签之外还需要做额外的工作,那么您的所有代码都会调用一个通用的Rebind(或任何您想调用的名称)并且更容易更新。

    EDITx2:我意识到,另一种选择是使用面向方面的编程。使用PostSharp 之类的东西,您可以修饰方法来执行编译后的特殊代码。我 99% 确定 PostSharp 允许您附加到事件(尽管我从未专门这样做过):

    button3.Click += (_, __) => SubmitForm();
    
    [RebindForm]
    private void SubmitForm(object sender, EventArgs e)
    {
        //do submission stuff
    }
    
    [Serializable]
    public class RebindFormAttribute : OnMethodBoundaryAspect
    {
        public override void OnSuccess( MethodExecutionArgs args )
        {
            MyForm form = args.InstanceTarget as MyForm; //I actually forgot the "InstanceTarget" syntax off the top of my head, but something like that is there
            if (form != null)
            {
                form.Rebind();
            }
        }
    }
    

    因此,即使我们没有在任何地方显式调用Rebind(),只要方法成功调用,属性和面向方面编程最终都会在那里运行额外的代码OnSuccess

    【讨论】:

    • 另一种表明你不关心参数的方式是使用匿名方法:button3.Click += delegate { UpdateTextLabel(); };
    • 通过这种方式,我基本上不只是重复我的方法相同的次数吗?
    • @JonSkeet 我可以...这并不完全是一种糟糕的方式,尤其是在您有大量参数的情况下。我必须记住这一点。我认为,当参数很少时,我会坚持使用 lambda 语法。谢谢。 :)
    • @Sisyphus 是的,我才意识到这一点。检查我的编辑,看看是否有帮助,抱歉。
    • 所以为了确保我理解,我仍然需要为我想要触发更新事件的表单上的每个控件执行此操作。是吗?
    【解决方案3】:

    是的,您不想编写这样的代码。您不必这样做,Application.Idle 事件是更新 UI 状态的理想选择。它每次在 Winforms 从消息队列中检索到所有待处理消息后运行。所以保证在您当前订阅的任何事件之后运行。让它看起来像这样:

        public Form1() {
            InitializeComponent();
            Application.Idle += UpdateTextLabel;
            this.FormClosed += delegate { Application.Idle -= UpdateTextLabel; };
        }
    
        void UpdateTextLabel(object sender, EventArgs e) {
            // etc..
        }
    

    【讨论】:

    • 但它会在每个操作返回空闲后运行?这听起来非常接近我的想法......必须测试它!
    • 好吧,这似乎一直在运行 - 很酷,但可能有点矫枉过正......我在更新标签中添加了一个小的随机数,直到我停止鼠标,它才会发疯。我可以放慢一点吗?
    • 你为什么要担心什么都没有发生时运行的代码?您可以在 Idle 事件处理程序中重新启动计时器,但这没什么意义。使用 taskmgr.exe,进程选项卡并在移动鼠标时检查进程上的 cpu 负载。超过 1% 是不寻常的。
    • 对此很陌生,让表单“工作”并几乎不断地重新计算似乎很奇怪。我想我可以将表单变量设置为 reCalculationNeeded = false;并简单地将它设置为 true 对于我需要进行重新计算的所有事件......但我想这并不比只输入 Update 方法 24 次更好......我有很多东西要学。)
    • 测试和设置属性值只需要几纳秒。鼠标被人推来推去,它会在几毫秒内报告位置变化。慢了一百万倍。唯一真正的成本是重绘控件。但这只发生在属性实际发生变化并且不是由 Idle 事件完成时。 从不优化不需要优化的代码。
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2018-06-14
    • 2020-12-31
    • 1970-01-01
    • 2013-04-22
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多