4

As part of my open source DeskPins clone (two separate links, one for original, one for clone on GitHub), I need to allow user to interact with desktop. I figured the easiest way would be opening a new form and paint desktop contents on top of it. Then I should be able to easily set mouse cursor and give visual cues to user about windows that is currently in focus.

A more complicated alternative would be using p/invoke to SetSystemCursor and injecting custom code in other window's WM_PAINT event queue (and potentially other WinApi related work, for example, cursor cleaning would be an issue, if my program terminates abnormally). I'd prefer not to go this way.

The code I have below is working, the only issue is screen flicker. It got better after I set DoubleBuffered = true (instead of screen flicker it became parent form flicker), but still noticeable. So right now my form flickers every time an overlay form is opened.

Is there anything I can do to make it a smooth transition, i.e. as if a new window did not open? It's okay to have a "freeze" effect = any animation would be suspended.

public sealed partial class DesktopOverlayForm : Form
{
  public DesktopOverlayForm()
  {
    InitializeComponent();

    //make full screen
    //http://stackoverflow.com/a/2176683/897326
    Rectangle bounds = Screen.AllScreens
                      .Select(x => x.Bounds)
                      .Aggregate(Rectangle.Union);
    this.Bounds = bounds;

    //set desktop overlay image
    this.BackgroundImage = MakeScreenshot();
  }

  /// <remarks>
  ///  Based on this answer on StackOverflow:
  ///  http://stackoverflow.com/questions/1163761/capture-screenshot-of-active-window
  /// </remarks>
  private static Bitmap MakeScreenshot()
  {
    Rectangle bounds = Screen.GetBounds(Point.Empty);
    Bitmap image = new Bitmap(bounds.Width, bounds.Height);

    using (Graphics g = Graphics.FromImage(image))
    {
      g.CopyFromScreen(Point.Empty, Point.Empty, bounds.Size);
    }

    return image;
  }

  private void DesktopOverlayForm_KeyDown(object sender, KeyEventArgs e)
  {
    if (e.KeyCode == Keys.Escape)
    {
      this.Close();
    }
  }
}
Victor Zakharov
  • 25,801
  • 18
  • 85
  • 151

2 Answers2

2

I did this to my flickering Form in the past. For my case, double buffer didn't really work well.

//this.DoubleBuffered = true; //doesn't work
protected override CreateParams CreateParams { //Very important to cancel flickering effect!!
  get {
    CreateParams cp = base.CreateParams;
    cp.ExStyle |= 0x02000000;  // Turn on WS_EX_COMPOSITED
    //cp.Style &= ~0x02000000;  // Turn off WS_CLIPCHILDREN not a good idea when combined with above. Not tested alone
    return cp;
  }
}

The idea is to replace the CreateParams argument. Also see:

Community
  • 1
  • 1
Ian
  • 30,182
  • 19
  • 69
  • 107
  • Does not seem to work for me, i.e. the above is exact equivalent of DoubleBuffered behavior, so if I set DoubleBuffered, it has no effect (my parent form still flickers), if I don't set DoubleBuffered, this replaces the effect of it, i.e. instead of screen flicker it becomes parent form flicker. Is there anything else I can try? My OS is Windows 7, running inside a Windows 7 VM on VMWare, both 64 bit. – Victor Zakharov Dec 22 '15 at 16:55
  • I also tried applying this to both forms, applying to parent has no effect, only one that opens on top of it (the overlay), and has the same effect as setting DoubleBuffered, as I mentioned earlier. – Victor Zakharov Dec 22 '15 at 16:57
  • Oow, too bad.. sorry to hear that =( at this moment I do not have more any more ammunition either... – Ian Dec 22 '15 at 17:01
  • Can you specify details of environment(s) for which you have this tested? Maybe it can help others. Or perhaps me or someone else will suggest a way to adjust for mine. Also please describe your application, i.e. where this code is and how it improves flicker. More information is better. – Victor Zakharov Dec 22 '15 at 17:09
  • Sure, my OS was Windows 7, 64-bit. But I now run it in Windows 10 too, 64-bit. I did not run it in the VM for both though... – Ian Dec 22 '15 at 17:11
  • Ok, I just tried outside of my VM, same thing - parent form flickers. Can you describe your app? Perhaps, your is a different scenario, so that's why it worked. – Victor Zakharov Dec 22 '15 at 17:24
  • I ended up using this solution together with the accepted answer. Resharper highlights `this.DoubleBuffered = true;` as bad code inside constructor, so I implemented your override instead. Upvoted. – Victor Zakharov Dec 22 '15 at 21:07
  • 1
    Hei, thanks! Sorry for the late reply, it was already past midnight in my timezone when I posted my last comment. Glad that you find my answer partially helpful. And yes, I have new ammunition from your question and Ivan's answer too. Voted up. – Ian Dec 23 '15 at 01:22
2

DoubleBuffered mode is needed for sure. The reason for parent form flicker is because the overlay form is activated (thus the parent form is deactivated and need to visually indicate that) before the overlay form is painted.

In order to resolve that, the overlay form needs to be shown without being activated, and then to be activated just after the first paint. The first is achieved by overriding a rarely known virtual protected property Form.ShowWithoutActivation, and the second by hooking into OnPaint method and a form level flag. Something like this

public sealed partial class DesktopOverlayForm : Form
{
    public DesktopOverlayForm()
    {
        // ...
        this.DoubleBuffered = true;
    }

    protected override bool ShowWithoutActivation { get { return true; } }

    bool activated;

    protected override void OnPaint(PaintEventArgs e)
    {
        base.OnPaint(e);
        if (!activated)
        {
            activated = true;
            BeginInvoke(new Action(Activate));
        }
    }
}
Ivan Stoev
  • 195,425
  • 15
  • 312
  • 343