0

Here is a piece of sample code to demonstrate the problem. I start a BackGroundWorker for the time-cost function DoSomething.

The Button is disabled at first. Then the worker begins to work. Finally, the button is re-enabled after the work has been finished.

BackgroundWorker w = new BackgroundWorker();

/* Disable the button at first */
Button.Enabled = false;

w.WorkerReportsProgress = false;
w.WorkerSupportsCancellation = false;

w.DoWork += new DoWorkEventHandler((s, e) =>
{
    DoSomething();  // It may take 10 ms to 10 s
});
w.RunWorkerCompleted += new RunWorkerCompletedEventHandler((s, e) =>
{
    /* Enable the button finally */
    Button.Enabled = true;
});

w.RunWorkerAsync();

However, the time-cost function DoSomething may returns immediately in some case. Then, Button disable and enable may cause the Button to flicker. It is observed that setting Disable and Enable, forces the Button to be repainted.

Even:

private void AnotherButton_Clicked(object sender, EventArgs e)
{
    for (int i = 0; i < 40; i++)
    {
        Button.Enabled = false;
        Button.Enabled = true;
    }
}

causes the flicker (please press the button as quickly as possible). Is there a better solution that can reduce the button flicker problem caused by enable and disable?

guan boshen
  • 724
  • 7
  • 15
  • what does the time of DoSomething depend on? Maybe you can for shot time use skip the disabling part – Denis Schaf Jul 05 '19 at 12:39
  • Disabling and, right after, enabling a Button won't make it *flicker*, unless you have video drivers in software mode (or it's a custom Button with additional rendering). But, any short delay will cause a *flicker*. You could unwire the Button's handler, so clicking it won't trigger its action; but, of course, there will be no clues that it's *disabled*. – Jimi Jul 05 '19 at 12:56
  • Unless you do want to use a custom Button, which could start a StopWatch when it's asked to disable itself and only performs this action after a certain laps of time. Aborts if re-enabled within the defined time. It *delays it*, in practice. Won't, possibly, prevent quick-fingered users to double-click it. Could be coupled with handler unwring thing. – Jimi Jul 05 '19 at 13:06
  • `setting Disable and Enable, forces the Button to be repainted.` that's expected. You can't change how a button looks without repainting it. The operation is so fast though that there shouldn't be any flicker. There was no flicker back in the 90s. On the other hand, if the UI thread is busy doing work, it *can't* repaint the button. The solution is to *not* do heavy stuff on the UI thread – Panagiotis Kanavos Jul 05 '19 at 13:36
  • `Even .. causes the flicker`. No. No repro. There's no visible change at all. After all, repainting can only start after the method finishes so the application will receive both change messages at the same time – Panagiotis Kanavos Jul 05 '19 at 13:39
  • @DenisSchaf It depends on hardware and cannot be estimated. – guan boshen Jul 05 '19 at 13:48
  • 1
    As suggested already dont work on the ui thread. Other than that dint disable the button right away start a timer that will disable the button after something like 500 ms if your pc is faster you can cancel the timer if not youre fine (but thats kinda hacky) – Denis Schaf Jul 05 '19 at 13:51
  • @PanagiotisKanavos Thank you for your reply. In MFC (VC), the button won't be repainted just like you said. However in C#, to my surprise also, it is refreshed just after Enabled is changed. – guan boshen Jul 05 '19 at 13:51
  • @DenisSchaf Yes. Thank you, this may work certianly but a little hacky :) – guan boshen Jul 05 '19 at 13:53
  • @guanboshen on the contrary, *it will*. Since you mentioned MFC, you know that MFC doesn't repaint anything. The *OS* sends the repaint messages in response to events. If the *OS* decides the button's visual representation has to change, it will send a repaint event, MFC or no – Panagiotis Kanavos Jul 05 '19 at 13:53
  • @guanboshen and again, there was no flicker at all in the 1990s. There's no flicker now. I can't reproduce what you claim. The only way what you say can happen is if you keep the UI thread so busy it can't repaint itself on time. In MFC you'd have the *same* behavior if the UI thread was blocked and you moved the window – Panagiotis Kanavos Jul 05 '19 at 13:55
  • @guanboshen what does `DoSomething` do? Does it try to modify the UI? An `Invoke` or `BeginInvoke` will run its delegate on the UI thread. If that code is too heavy, the UI won't be repainted – Panagiotis Kanavos Jul 05 '19 at 13:57
  • @PanagiotisKanavos It calls some hardware API. It can be reproduced in this way: in `button1_Clicked(...)` disable a batch of buttons and re-enable them. – guan boshen Jul 05 '19 at 14:03
  • Or disable and re-enable the same button several times. – guan boshen Jul 05 '19 at 14:09
  • @guanboshen no it can't. I already tried it and as expected, there was no flicker. In fact, there was no detectable change. You'd need a machine from the 2000s for redrawing to be so *slow* that it would be noticable. Or failing hardware – Panagiotis Kanavos Jul 05 '19 at 14:09
  • @PanagiotisKanavos OK It could be my PC's problem. I am using .Net FW 3.5. Please pressing button1 as quickly as possible. – guan boshen Jul 05 '19 at 14:11
  • Doesn't matter. .NET 3.5 is the one that introduced WPF by the way, and hardware acceleration for *animation and videos*. Complex redrawing was a non-issue by that time. Unless of course you use something strange like a big bitmap as a button background, shrunk to fit a small button. With a simple button, all the OS has to do is paint it grey and then write some text on it – Panagiotis Kanavos Jul 05 '19 at 14:11
  • @PanagiotisKanavos Good morning. https://stackoverflow.com/questions/487661/how-do-i-suspend-painting-for-a-control-and-its-children The answer in this link suspends the drawing and resumes it after enable/disable changes, which prevent controls from flickering. – guan boshen Jul 06 '19 at 01:29
  • Have you considered what happens if something is moved on top of the control while `WM_SETREDRAW` is set to `false`? Or the Form is minimized, or anything else happens that forces a redraw of the parent? Your Button will disappear. Also, it's rendering is *freezed* to what it is the moment you disable the refresh of it's content. Moreover, it's still missing the *minimum-elasped-time* feature. – Jimi Jul 06 '19 at 14:18
  • @Jimi Hi, thank you for your reply. Yes, it is freezed and does no help. I therefore think Denis Schaf's answer seems to be the best solution to my question, though hacky. – guan boshen Jul 07 '19 at 14:43
  • Won't work either (with just a timer), if a user clicks the button twice (as a double click). As I suggested before, unwire the handler (so the Button cannot be clicked more than once before the procedure has returned), activate a time-out and wire back the handler in the end. If you build a custom control that delays the `Enabled` property `set`, you can handle all the logic (very little) in a single place and you also have a reusable component ready for a similar task. – Jimi Jul 07 '19 at 14:57
  • @guanboshen read that link carefully. It's from 2009, it's *NOT* about flickering, its about smooth redrawing of *multiple* controls without causing delays, especially when the layout changes. When you know you're going to modify multiple controls, there's no reason to redraw the UI for each change. It's better to wait for all modifications to take place. That's a common technique used in MFC and VB6 too. – Panagiotis Kanavos Jul 08 '19 at 07:45
  • A far more effective technique is to use double buffering [which is on by default](https://learn.microsoft.com/en-us/dotnet/framework/winforms/advanced/how-to-reduce-graphics-flicker-with-double-buffering-for-forms-and-controls). You have to *try* to cause flickering in a Windows Forms application – Panagiotis Kanavos Jul 08 '19 at 07:45

0 Answers0