vb.net screensaver 安装为 .scr 后透明圆变灰怎么办
结论
不要依赖 WinForms 的 TransparencyKey 让屏幕保护程序“穿透”窗口并显示桌面。Windows 以屏保模式启动 .scr 时,程序可能位于屏保宿主窗口、预览子窗口或不同的桌面上下文中。透明像素显示的是当前窗口后面的合成表面,不一定是用户原来的桌面,所以透明圆后面可能出现深灰色的宿主背景。
更稳妥的做法是:
- 屏保启动时截取屏幕画面,或直接加载壁纸。
- 在窗体内绘制背景图。
- 绘制深色遮罩,并在遮罩中留出一个移动的圆形区域。
这种方式可以做出“透明洞”的视觉效果。窗口本身无须透明,显示结果也不会受屏保宿主窗口影响。
为什么直接运行正常,安装为 .scr 后却变灰
把 .exe 改名为 .scr 不会改变程序格式,却会改变 Windows 启动程序的方式。屏保通常会收到这些参数:
/s:全屏运行屏幕保护程序。/p <HWND>:嵌入系统提供的预览窗口。/c:打开配置窗口。
直接运行 .exe 时,窗体后面通常是普通桌面。作为屏保运行时,窗体后面可能是屏保宿主或其他背景表面。即使 TransparencyKey 正常生效,透明区域露出的也是这个表面,因此可能显示深灰色,而非原来的桌面内容。
TransparencyKey 还有两个限制。
它只支持颜色键透明
TransparencyKey 会把与指定颜色完全相同的像素设为透明:
Private ReadOnly TransparencyMagicColor As Color =
Color.FromArgb(1, 2, 3)
它不支持真正的逐像素 Alpha 混合,每个像素只能是“透明”或“不透明”。
抗锯齿会生成其他边缘颜色
代码启用了抗锯齿:
e.Graphics.SmoothingMode = SmoothingMode.AntiAlias
圆形边缘会混合 Color.FromArgb(1, 2, 3) 和背景色,生成一圈新的颜色。这些像素与 TransparencyMagicColor 不完全相同,不会被透明色键移除,因而容易出现灰边或暗色边缘。
将 SmoothingMode 改为 None 可以减轻颜色键造成的边缘问题,但圆形会出现锯齿,屏保宿主背景无法穿透的问题也依然存在。
推荐方案:在窗体内合成画面
先保存屏幕图像,再在每一帧中绘制这张图,并用深灰色遮罩覆盖整个窗体,只排除圆形区域。
增加一个字段:
Private capturedScreen As Bitmap
启动全屏模式时,在窗体覆盖屏幕前抓取对应的显示器:
Private Sub LaunchFullScreen()
Dim targetScreen As Screen = Screen.FromControl(Me)
capturedScreen = New Bitmap(
targetScreen.Bounds.Width,
targetScreen.Bounds.Height,
Imaging.PixelFormat.Format32bppArgb)
Using g As Graphics = Graphics.FromImage(capturedScreen)
g.CopyFromScreen(
targetScreen.Bounds.Left,
targetScreen.Bounds.Top,
0,
0,
targetScreen.Bounds.Size,
CopyPixelOperation.SourceCopy)
End Using
FormBorderStyle = FormBorderStyle.None
WindowState = FormWindowState.Normal
StartPosition = FormStartPosition.Manual
Bounds = targetScreen.Bounds
TopMost = True
DoubleBuffered = True
' 不再使用窗口透明色键
TransparencyKey = Color.Empty
Cursor.Hide()
animationTimer.Start()
End Sub
然后替换原来的 OnPaint:
Protected Overrides Sub OnPaint(e As PaintEventArgs)
MyBase.OnPaint(e)
e.Graphics.SmoothingMode = SmoothingMode.AntiAlias
e.Graphics.CompositingQuality = CompositingQuality.HighQuality
If capturedScreen IsNot Nothing Then
e.Graphics.DrawImage(capturedScreen, Me.ClientRectangle)
Else
e.Graphics.Clear(Color.Black)
End If
Dim circleRect As New Rectangle(
circleX - circleRadius,
circleY - circleRadius,
circleRadius * 2,
circleRadius * 2)
Using maskPath As New GraphicsPath(FillMode.Alternate)
maskPath.AddRectangle(Me.ClientRectangle)
maskPath.AddEllipse(circleRect)
Using maskBrush As New SolidBrush(Color.FromArgb(48, 48, 48))
e.Graphics.FillPath(maskBrush, maskPath)
End Using
End Using
End Sub
FillMode.Alternate 会把矩形与椭圆重叠的部分挖空。圆内显示之前绘制的屏幕截图,圆外则显示深灰色遮罩。圆移动时继续调用 Invalidate() 即可。
如果希望圆外区域呈现半透明的变暗效果,可以调整画刷的 Alpha 值:
Using maskBrush As New SolidBrush(Color.FromArgb(210, 0, 0, 0))
e.Graphics.FillPath(maskBrush, maskPath)
End Using
这个 Alpha 值只参与窗体内部的图像合成,不依赖窗口透明,通常比 TransparencyKey 更稳定。
关闭窗体时需要释放截图:
Protected Overrides Sub OnFormClosed(e As FormClosedEventArgs)
animationTimer.Stop()
If capturedScreen IsNot Nothing Then
capturedScreen.Dispose()
capturedScreen = Nothing
End If
MyBase.OnFormClosed(e)
End Sub
修正预览模式的窗口样式设置
原代码读取的是 GWL_STYLE:
Dim currentStyle As IntPtr =
GetWindowLongPtr(Me.Handle, GWL_STYLE)
写回时却传入了 0:
SetWindowLongPtr(Me.Handle, 0, New IntPtr(newStyle))
写回时也应使用 GWL_STYLE:
SetWindowLongPtr(Me.Handle, GWL_STYLE, New IntPtr(newStyle))
完整写法如下:
Dim currentStyle As IntPtr =
GetWindowLongPtr(Me.Handle, GWL_STYLE)
Dim newStyle As Long =
currentStyle.ToInt64() Or
WS_CHILD Or
WS_VISIBLE Or
WS_CLIPCHILDREN Or
WS_CLIPSIBLINGS
SetWindowLongPtr(
Me.Handle,
GWL_STYLE,
New IntPtr(newStyle))
这个错误会影响 /p 预览模式下的子窗口样式,但全屏模式的透明效果失效还有其他原因。
单独处理预览模式
在 /p 模式下,屏保窗体会通过 SetParent 成为控制面板预览窗口的子窗口,无法保证透过子窗口看到真实桌面。
预览模式可以使用固定背景图或程序启动时保存的缩略图,也可以只显示动画,不模拟真实桌面。还可以根据预览区域的尺寸单独生成背景位图。
如果预览模式仍使用 TransparencyKey,显示结果会同时受父窗口、系统合成和颜色键边缘影响,很难保持一致。
是否可以使用分层窗口
Win32 的 WS_EX_LAYERED、SetLayeredWindowAttributes 和 UpdateLayeredWindow 支持逐像素 Alpha,比 TransparencyKey 更灵活。不过,这些 API 改善的是窗口合成效果,无法保证透明区域后面就是用户原来的桌面。
屏保预览窗口仍是子窗口,其运行环境也可能不同于普通顶层窗口。要在圆形区域显示原桌面,使用截图或壁纸配合遮罩更容易控制。如果确实需要让整个窗口与当前桌面实时混合,并能接受不同 Windows 环境中的表现差异,再考虑使用分层窗口。
其他注意事项
10 ms的Timer间隔不代表能够稳定达到 100 FPS。WinFormsTimer会受到 UI 线程和系统计时精度影响,可以根据实际效果改为约16 ms或33 ms。- 在多显示器环境中,应为每个
Screen创建对应的窗体并分别截图,不要假设所有屏幕共用同一个坐标原点。 CopyFromScreen捕获的是程序启动瞬间的静态画面。圆内不会继续显示桌面动画、视频或之后发生的窗口变化。- 在锁屏、安全桌面、远程会话或受保护内容场景中,屏幕捕获可能得到黑屏或不完整的画面。程序应准备纯色或图片背景作为回退。
- 如果只需要固定壁纸,直接读取并绘制壁纸通常比截取屏幕更稳定。
- 不要通过全屏透明窗口实现真正的屏幕保护。屏幕保护程序应自行绘制完整画面,以免意外露出仍在运行的桌面内容。
备注:内容仅供参考。
