桌面屏幕覆盖 - 新窗体闪烁问题
Desktop screen overlay - new form flicker issue
作为我的开源 DeskPins clone 的一部分(两个单独的链接,一个用于原始链接,一个用于 GitHub 上的克隆),我需要允许用户与桌面交互。我认为最简单的方法是打开一个新表单并在其上绘制桌面内容。然后我应该能够轻松设置鼠标光标并向用户提供有关当前焦点的 windows 的视觉提示。
一个更复杂的替代方案是使用 p/invoke 到 SetSystemCursor 并在其他 window 的 WM_PAINT 事件队列中注入自定义代码(以及可能的其他 WinApi 相关工作,例如,如果我的程序异常终止,光标清理将是一个问题)。我不想走这条路。
我下面的代码可以正常工作,唯一的问题是屏幕闪烁。在我设置 DoubleBuffered = true
之后它变得更好了(而不是 屏幕闪烁 它变成了 父表单闪烁 ),但仍然很明显。所以现在每次打开覆盖表单时我的表单都会闪烁。
我可以做些什么来使它成为一个 平滑的 过渡,即好像一个新的 window 没有打开?可以有一个 "freeze" 效果 = 任何动画都会暂停。
public sealed partial class DesktopOverlayForm : Form
{
public DesktopOverlayForm()
{
InitializeComponent();
//make full screen
//
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 Whosebug:
///
/// </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();
}
}
}
我以前对我的闪烁 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
参数。另见:
- Winforms Double Buffering
肯定需要 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));
}
}
}
作为我的开源 DeskPins clone 的一部分(两个单独的链接,一个用于原始链接,一个用于 GitHub 上的克隆),我需要允许用户与桌面交互。我认为最简单的方法是打开一个新表单并在其上绘制桌面内容。然后我应该能够轻松设置鼠标光标并向用户提供有关当前焦点的 windows 的视觉提示。
一个更复杂的替代方案是使用 p/invoke 到 SetSystemCursor 并在其他 window 的 WM_PAINT 事件队列中注入自定义代码(以及可能的其他 WinApi 相关工作,例如,如果我的程序异常终止,光标清理将是一个问题)。我不想走这条路。
我下面的代码可以正常工作,唯一的问题是屏幕闪烁。在我设置 DoubleBuffered = true
之后它变得更好了(而不是 屏幕闪烁 它变成了 父表单闪烁 ),但仍然很明显。所以现在每次打开覆盖表单时我的表单都会闪烁。
我可以做些什么来使它成为一个 平滑的 过渡,即好像一个新的 window 没有打开?可以有一个 "freeze" 效果 = 任何动画都会暂停。
public sealed partial class DesktopOverlayForm : Form
{
public DesktopOverlayForm()
{
InitializeComponent();
//make full screen
//
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 Whosebug:
///
/// </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();
}
}
}
我以前对我的闪烁 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
参数。另见:
- Winforms Double Buffering
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));
}
}
}