【问题标题】:Disabled Button fires events in WPF禁用按钮在 WPF 中触发事件
【发布时间】:2012-03-02 03:03:12
【问题描述】:
private void but_Click(object sender, RoutedEventArgs e)
{
    (sender as Button).IsEnabled = false;
    doSomeThing();//e.g run for more than 20 seconds
    (sender as Button).IsEnabled = true;
}

当我第一次按下按钮时,它会禁用。然后启动doSomeThing(),它包含 UI 代码或一些更新的 UI 变量。 我的意思是,如果我在doSomeThing() 正在进行时再次按下按钮,那么在此按钮启用后but_Click 事件会再次触发。 它维护触发的事件队列,即我按了 n 次。

那么,当按钮被禁用时如何防止触发事件呢? 请考虑在这种情况下“doSomething”包含绑定到代码的 UI 控件。所以在这种情况下我们不能运行后台线程。 帮我解决。

【问题讨论】:

  • 该按钮在单击时是否实际上变为禁用的视觉外观?如果不是,您的问题可能类似于this question
  • 是的,它会将视觉外观更改为禁用

标签: c# wpf events


【解决方案1】:

似乎有些澄清是为了......

  • 控件(例如 - 按钮)已正确禁用。
  • 只要主线程正在处理原始点击事件,后续事件就不会被处理 - 而是排队。
  • 一旦第一次点击事件完成,就会处理下一个排队的事件。
  • 此时,由于控件已启用,排队的单击事件将再次触发事件处理程序。

有几种可能的解决方案,我认为@Amit 走在正确的轨道上 - 使用线程可能是最好的方法。

这是对我有用的简化版本:

private void Button1_Click(object sender, EventArgs e)
{
    disableControls(); // e.g.- Button1.Enabled = false;

    // run "doSomething" in a separate thread
    new Thread(new ThreadStart(doSomething)).Start();
}

private void doSomething()
{
    // do something... make sure it's thread-safe!!
    // ...

    enableControls();  // a thread safe enabling of relevant controls
}

为了更好地解释为什么不使用Application.DoEvents()look here

有关控件状态的线程安全操作,请参阅: How to: Make Thread-Safe Calls to Windows Forms Controls

【讨论】:

  • 只有在 dosomething() 不需要 UI 操作的情况下,您在上述查询中给出的解决方案才是可能的。如果 dosomething() 方法包含 UI 操作怎么办。
【解决方案2】:

此代码的问题是 doSomeThing() 方法正在 UI 线程中运行。因此,按钮未正确禁用。如果我们重构代码以便doSomeThing() 方法在不同的线程中运行,它就可以正常工作。这是一个使用BackgroundWorker的简单示例;但是我们的想法是我们不应该在 UI 线程中运行耗时的东西。这是重构后的代码:

public partial class ButtonEnableTest : Window
{
    private BackgroundWorker worker = new BackgroundWorker();

    public ButtonEnableTest()
    {
        InitializeComponent();
        this.worker.DoWork += new DoWorkEventHandler(worker_DoWork);
        this.worker.RunWorkerCompleted += new RunWorkerCompletedEventHandler(worker_RunWorkerCompleted);
    }

    void worker_RunWorkerCompleted(object sender, RunWorkerCompletedEventArgs e)
    {
        if (this.btn.IsEnabled == false)
        {
            this.btn.IsEnabled = true;
        }
    }

    void worker_DoWork(object sender, DoWorkEventArgs e)
    {
        doSomeThing();
    }

    private void doSomeThing()
    {
        int i = 5;
        while ( i > 0)
        {
            Thread.Sleep(TimeSpan.FromMilliseconds(2000));
            System.Diagnostics.Debug.WriteLine("Woke up " + i);
            i--;
        }
    }

    private void button1_Click(object sender, RoutedEventArgs e)
    {
        Button btn = (Button) sender;
        System.Diagnostics.Debug.WriteLine("at ButtonClick");
        if (btn.IsEnabled)
        {
            btn.IsEnabled = false;
            this.worker.RunWorkerAsync();
        }
    }
}

我在这里没有遵循任何编码转换,因为我只是想分享我的想法。请注意,我将 WPF 按钮命名为“btn”。

【讨论】:

  • 只有在 dosomething() 不需要 UI 操作的情况下,您在上述查询中给出的解决方案才是可能的。如果 dosomething() 方法包含 UI 操作怎么办。
【解决方案3】:

另一种方法是使用PeekMessage API函数,如下代码所示。

using System.Runtime.InteropServices;
using System.Security;

[StructLayout(LayoutKind.Sequential)]
public struct NativeMessage
{
    public IntPtr handle;
    public uint msg;
    public IntPtr wParam;
    public IntPtr lParam;
    public uint time;
    public System.Drawing.Point p;
}

[SuppressUnmanagedCodeSecurity]
[return: MarshalAs(UnmanagedType.Bool)]
[DllImport("User32.dll", CharSet = CharSet.Auto, SetLastError = true)]
public static extern bool PeekMessage(out NativeMessage message,
    IntPtr handle, uint filterMin, uint filterMax, uint flags);
private const UInt32 WM_MOUSEFIRST = 0x0200;
private const UInt32 WM_MOUSELAST = 0x020D;
public const int PM_REMOVE = 0x0001;

// Flush all pending mouse events.
private void FlushMouseMessages()
{
    NativeMessage msg;
    // Repeat until PeekMessage returns false.
    while (PeekMessage(out msg, IntPtr.Zero,
        WM_MOUSEFIRST, WM_MOUSELAST, PM_REMOVE))
        ;
}

此代码包含一堆 API 函数及其参数的声明。 (您还需要为 System.Runtime.InteropServices 和 System.Security 命名空间添加 using 语句。下载示例了解详细信息。)

FlushMouseMessages 方法调用 PeekMessage 告诉它丢弃 WM_MOUSELAST 到 PM_REMOVE 范围内的任何消息。代码重复调用 PeekMessage 直到返回 false 表示没有此类消息。

以下按钮事件处理程序调用 FlushMouseMessage,因此您无法在其代码仍在运行时单击该按钮。

private void but_Click(object sender, RoutedEventArgs e)
{
    (sender as Button).IsEnabled = false;
    doSomeThing();//e.g run for more than 20 seconds
    (sender as Button).IsEnabled = true;
    FlushMouseMessages();
}

我从网站上选择了上面的代码 http://csharphelper.com/blog/2015/08/flush-click-events-in-c/ 这对我有用。

【讨论】:

    【解决方案4】:
    private void but_Click(object sender, RoutedEventArgs e)
    {
        Button but = (sender as Button)
        if(but.IsEnabled)
        {
           but.IsEnabled = false;
           doSomeThing();
           but.IsEnabled = true;
        }
    }
    

    【讨论】:

    • 按钮启用后似乎触发了事件。所以,but.IsEnabled 将一直为真
    • 你在doSomething()方法中做了什么?
    • 大部分时间它在做 Thread.Sleep(50) 和一些计算
    • 您是否正在启动不同的线程?还是在不同的上下文中执行代码?
    【解决方案5】:

    我在 TextChanged 事件中遇到了类似的问题,并使用该代码解决了它:

    private void TB_box_TextChanged(object sender, TextChangedEventArgs e)
    {
       //detach event
       TB_box1.TextChanged -= TB_box_TextChanged;
       TB_box2.TextChanged -= TB_box_TextChanged;
    
       //change my value
       TB_box1.Text = "a"
       TB_box2.Text = "b"
    
       //attach event     
       TB_box1.TextChanged += TB_box_TextChanged;
       TB_box2.TextChanged += TB_box_TextChanged;
    
    }
    

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2023-03-03
      • 2021-11-01
      • 1970-01-01
      • 2013-07-08
      • 1970-01-01
      相关资源
      最近更新 更多