【问题标题】:Desktop screen overlay - new form flicker issue桌面屏幕覆盖 - 新表单闪烁问题
【发布时间】:2026-02-09 12:20:03
【问题描述】:

作为我的开源 DeskPins clone 的一部分(两个单独的链接,一个用于原始链接,一个用于 GitHub 上的克隆),我需要允许用户与桌面交互。我认为最简单的方法是打开一个新表单并在其上绘制桌面内容。然后我应该能够轻松设置鼠标光标并向用户提供有关当前焦点窗口的视觉提示。

更复杂的替代方法是使用 p/invoke 到 SetSystemCursor 并在其他窗口的 WM_PAINT 事件队列中注入自定义代码(以及可能的其他 WinApi 相关工作,例如,如果我的程序异常终止,光标清理将是一个问题)。我不想走这条路。

我下面的代码正在运行,唯一的问题是屏幕闪烁。在我设置DoubleBuffered = true 后它变得更好(而不是屏幕闪烁,它变成了父窗体闪烁),但仍然很明显。所以现在我的表单每次打开覆盖表单时都会闪烁。

我可以做些什么来使它成为一个平滑过渡,即好像没有打开一个新窗口一样?可以有“冻结”效果 = 任何动画都会暂停。

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

    //make full screen
    //http://*.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 *:
  ///  http://*.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();
    }
  }
}

【问题讨论】:

  • 反对者 - 请解释一下。如果您认为此问题缺少某些内容,最好留下评论。

标签: c# winforms graphics screenshot gdi+


【解决方案1】:

DoubleBuffered 模式是肯定需要的。 父窗体闪烁的原因是因为覆盖窗体在绘制覆盖窗体之前已激活(因此父窗体已停用并需要直观地指示)。

为了解决这个问题,覆盖表单需要在没有被激活的情况下显示,然后在第一次绘制之后被激活。第一个是通过覆盖一个鲜为人知的虚拟受保护属性Form.ShowWithoutActivation 来实现的,第二个是通过挂钩OnPaint 方法和一个表单级别标志来实现的。像这样的

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));
        }
    }
}

【讨论】:

  • 像魅力一样工作,谢谢伊万。我第一次了解 ShowWithoutActivation 属性。 :)
【解决方案2】:

我过去对我闪烁的Form 做过这个。就我而言,双缓冲效果并不好。

//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;
  }
}

这个想法是替换CreateParams 参数。另见:

【讨论】:

  • 似乎对我不起作用,即上面的行为完全等同于 DoubleBuffered 行为,所以如果我设置 DoubleBuffered,它没有效果(我的父窗体仍然闪烁),如果我不设置DoubleBuffered,这取代了它的效果,即代替屏幕闪烁,它变成了父窗体闪烁。还有什么我可以尝试的吗?我的操作系统是 Windows 7,在 VMWare 上的 Windows 7 VM 中运行,均为 64 位。
  • 我也尝试将它应用于两种表单,应用于父级没有效果,只有一个在它上面打开(覆盖),并且与设置 DoubleBuffered 的效果相同,正如我之前提到的.
  • 噢,太糟糕了.. 很遗憾听到这个消息=(此时我也没有更多的弹药了...
  • 您能否详细说明您测试过的环境?也许它可以帮助别人。或者也许我或其他人会建议一种方法来适应我的。还请描述您的应用程序,即此代码在哪里以及它如何改善闪烁。更多信息更好。
  • 嘿,谢谢!抱歉回复晚了,当我发表最后一条评论时,我所在的时区已经过了午夜。很高兴您发现我的回答部分有帮助。是的,我从你的问题和伊万的回答中得到了新的弹药。投票赞成。