【问题标题】:How to ignore user clicks in WinForms?如何忽略 WinForms 中的用户点击?
【发布时间】:2012-06-23 12:43:32
【问题描述】:

当用户点击一个按钮时,它会启动一些任务。我不想阻塞主应用程序线程,所以我在一个单独的线程中运行它。现在我需要禁止用户单击按钮,直到我的任务完成。

我可以设置

button.Enabled = false;

,但我正在寻找一些方法来忽略点击它。

我可以在点击事件处理程序中添加一些检查:

if (executingThread != null) return;

,但我必须为每个处理程序都这样做,这是个坏主意。

我知道有一些方法可以过滤用户的消息。你能指点我怎么做吗?而且我不想过滤掉所有消息,因为其他一些按钮必须保持可点击状态,我需要过滤掉特定控件(按钮、网格等)的消息。

解决方案

internal class MessagesFilter: IMessageFilter
{
    private readonly IntPtr ControlHandler;

    private const int WM_KEYUP = 0x0101;

    public MessagesFilter(IntPtr ControlHandler)
    {
        this.ControlHandler = ControlHandler;
    }

    #region IMessageFilter Members

    public bool PreFilterMessage(ref Message m)
    {
        // TODO:  Add MessagesFilter.PreFilterMessage implementation
        if (m.Msg == WM_KEYUP)
        {
            if (m.HWnd == ControlHandler)
            {
                Keys k = ((Keys) ((int) m.WParam));

                if (k == Keys.Enter)                    
                    return true;
            }
        }

        return false;
    }

    #endregion
}

【问题讨论】:

    标签: winforms multithreading background-process messages


    【解决方案1】:

    与往常一样,UI 应该以这样一种方式呈现,即用户了解应用程序正在做什么,并且 should talk to the user 带有 UI 元素。

    正如 Adam Houldsworth 建议的那样,我也希望保持 button 处于禁用或启用状态,但我还建议按钮的标题应向用户传达新线程启动时正在进行长时间处理的消息..所以button 的标题应该立即更改为"Processing..Please wait..." 之类的东西(除了被禁用或者即使你想保持启用它),然后如果你保持启用按钮,只需检查click 事件上的按钮标题(或 isProcessing bool 标志),如果它显示“正在处理..请稍候...”或(isProcessing == true),则返回。

    许多帮助用户上传files/images的网站将Upload按钮的标题更改为"Uploading..Please wait...",以通知用户等待上传完成,另外一些网站还disable上传按钮,以便用户无法再次点击上传按钮。

    当线程完成长时间处理时,您还需要将标题恢复正常。

    可能还有其他高级方法,但我们的想法是使其尽可能简单和基本。

    查看Threading in Windows Forms 上的这个示例,它显示在多线程时禁用按钮。

    【讨论】:

    • CSharpVJ,Adam Houldsworth 感谢您的回复,但我需要我所要求的。告诉用户长时间的进程运行,将光标更改为 WaitCursor 等 - 很清楚。我可以检查正在执行的线程,而不是检查按钮的标题,这是个坏主意。但我需要更灵活的解决方案。我看到的唯一选择是取消事件处理程序,但我认为过滤掉消息会是更优雅的解决方案。
    • @Igor 我用Threading 上的链接更新了我的答案。我的想法是通过更改按钮的标题来额外通知用户,使 UI 设计更直观,检查标题只是检查isProcessing 标志的替代方法..
    【解决方案2】:

    到目前为止的所有建议+1。正如CSharpVJ 建议的那样——My idea was to additionally inform the user by changing the button's caption making the UI design more intuitive

    这可以通过 Winforms [No hassles code] 中的 Backgroundworker 组件优雅地实现。只需复制粘贴并按 F5(在创建一个带有按钮和标签的新 Winforms 项目之后)!

    您不必在此处检查与按钮相关的任何内容。一切都将由适当的事件处理程序处理。只是你必须在相应的事件处理程序中做正确的事情。试试吧!

    using System.ComponentModel;
    using System.Windows.Forms;
    
    namespace WindowsFormsApplication1
    {
        public partial class Form3 : Form
        {
            private BackgroundWorker _worker;
    
            public Form3()
            {
                InitializeComponent();
                InitWorker();
            }
    
            private void InitWorker()
            {
                if (_worker != null)
                {
                    _worker.Dispose();
                }
    
                _worker = new BackgroundWorker
                {
                    WorkerReportsProgress = true,
                    WorkerSupportsCancellation = true
                };
                _worker.DoWork += DoWork;
                _worker.RunWorkerCompleted += RunWorkerCompleted;
                _worker.ProgressChanged += ProgressChanged;
            }
    
            /// do time consuming work here... 
            void DoWork(object sender, DoWorkEventArgs e)
            {
                int highestPercentageReached = 0;
                if (_worker.CancellationPending)
                {
                    e.Cancel = true;
                }
                else
                {
                    double i = 0.0d;
    
                    for (i = 0; i <= 199990000; i++)
                    {
                        // Report progress as a percentage of the total task.
                        var percentComplete = (int)(i / 199990000 * 100);
                        if (percentComplete > highestPercentageReached)
                        {
                            highestPercentageReached = percentComplete;
                            // Report UI abt the progress  
                            _worker.ReportProgress(percentComplete);
                            _worker.CancelAsync();
                        }
                    }
    
                }
            }
    
            void RunWorkerCompleted(object sender, RunWorkerCompletedEventArgs e)
            {
                button1.Enabled = true;
                if (e.Cancelled)
                {
                    // Display some message to the user that task has been
                    // cancelled
                    label1.Text = "Cancelled the operation";
                }
                else if (e.Error != null)
                {
                    // Do something with the error
                }
    
                button1.Text = "Start again";
            }
    
            void ProgressChanged(object sender, ProgressChangedEventArgs e)
            {
                label1.Text =  string.Format("Result {0}: Percent {1}",e.UserState, e.ProgressPercentage);
            }
    
            private void OnStartClick(object sender, System.EventArgs e)
            {
                _worker.RunWorkerAsync();
                button1.Text = "Processing started...";
                button1.Enabled = false;
            }
    
        }
    }
    

    【讨论】:

      【解决方案3】:

      正如其他答案中提到的,可能有比您要求的更好的解决方案。

      要直接回答您的问题,请查看IMessageFilter interface

      创建您的过滤器,让它抑制您不需要的鼠标消息,必要时使用Application.AddMessageFilter() 应用它。

      类似的东西(这应该可以编译...):

      public class MouseButtonFilter : IMessageFilter
      {
      
          private const int WM_LBUTTONDOWN            = 0x0201;
          private const int WM_LBUTTONUP              = 0x0202;
          private const int WM_LBUTTONDBLCLK          = 0x0203;
          private const int WM_RBUTTONDOWN            = 0x0204;
          private const int WM_RBUTTONUP              = 0x0205;
          private const int WM_RBUTTONDBLCLK          = 0x0206;
          private const int WM_MBUTTONDOWN            = 0x0207;
          private const int WM_MBUTTONUP              = 0x0208;
      
          bool IMessageFilter.PreFilterMessage(ref Message m)
          {
              switch (m.Msg)
              {
                  case WM_LBUTTONDOWN:
                  /* case ... (list them all here; i'm being lazy) */
                  case WM_MBUTTONUP:
                      return true;
              }
      
              return false;
          }
      }
      

      【讨论】:

      • 谢谢你,罗肯!我没有足够的声望来投票,但这就是答案!
      • @Igor 很高兴听到!无论您的代表是什么,您都应该能够接受答案为“答案”。
      猜你喜欢
      • 1970-01-01
      • 2016-12-27
      • 2013-03-12
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2021-04-13
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多