桌面屏幕覆盖 - 新形式闪烁问题

4
作为我开源的DeskPins 克隆(两个不同的链接,一个是原始链接,一个是GitHub上的克隆链接)的一部分,我需要允许用户与桌面进行交互。我想最简单的方法是打开一个新窗体并在其上绘制桌面内容。然后,我应该能够轻松设置鼠标指针,并向用户提供有关当前焦点窗口的视觉提示。
更复杂的替代方案是使用p/invoke来SetSystemCursor并注入自定义代码到其他窗口的WM_PAINT事件队列中(如果我的程序异常终止,则可能需要进行其他WinApi相关工作,例如光标清除将成为一个问题)。我宁愿不这样做。
我下面的代码是有效的,唯一的问题是屏幕闪烁。在我设置DoubleBuffered = true之后(而不是屏幕闪烁,它变成了父窗体闪烁),但仍然很明显。所以现在每次打开叠加窗体时,我的窗体都会闪烁。
有什么我可以做的,使它变得“平稳”,就像没有打开新窗口一样? 可以使用“冻结”效果 = 任何动画都会暂停。
public sealed partial class DesktopOverlayForm : Form
{
  public DesktopOverlayForm()
  {
    InitializeComponent();

    //make full screen
    //https://dev59.com/eEvSa4cB1Zd3GeqPgbBl#2176683
    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:
  ///  https://dev59.com/j3M_5IYBdhLWcg3w8H82
  /// </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
2个回答

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虚拟机,都是64位的。 - Victor Zakharov
我也尝试将其应用于两种形式,将其应用于父级没有效果,只有打开在其上方的一个(叠加层),并且具有与设置DoubleBuffered相同的效果,就像我之前提到的那样。 - Victor Zakharov
哦,太糟糕了...很抱歉听到这个消息 =( 目前我也没有更多的弹药了... - Ian
好的,我刚刚在我的虚拟机外尝试了一下,情况一样 - 父窗体闪烁。你能描述一下你的应用程序吗?也许,你的情况不同,所以它可以工作。 - Victor Zakharov
1
嘿,谢谢!很抱歉我回复晚了,当我发表上一个评论时,我的时区已经过了午夜。很高兴你发现我的答案在某种程度上有所帮助。是的,你的问题和Ivan的回答给了我新的思路。投了一票。 - Ian
显示剩余3条评论

2

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

非常好用,谢谢Ivan。这是我第一次了解ShowWithoutActivation属性。 :) - Victor Zakharov

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接