Windows API: 像屏幕显示一样写入屏幕

Windows API: write to screen as on screen display

我正在编写一个(非常)小的应用程序,它在开始时只执行一些小的事情,并且应该在屏幕上写一条类似于屏幕显示的消息:大字母,没有任何 window , 在一切之上, 可见片刻然后消失。

如果可能的话,我不想为它创建 window。

正确的做法是什么?

(我希望不需要像 DirectX、直接图形访问等特殊工具包)

正如评论中所指出的,您可以直接在屏幕上绘图。 GetDC 向 return 提供适当的设备上下文:

hWnd [in]

A handle to the window whose DC is to be retrieved. If this value is NULL, GetDC retrieves the DC for the entire screen.

直接渲染到屏幕至少有两个问题需要解决:

  1. 屏幕 DC 是共享资源。每当其他人呈现到屏幕上时(例如,当显示 window 时),屏幕的那部分就会被覆盖。
  2. 渲染是破坏性的。当呈现到设备上下文中时,原始内容会被覆盖。要实现 fade-out 效果,您必须保存原始内容(并在显示其他 windows 时动态更新它们)。

这两个问题都可以通过创建 window 来解决。 window 不需要有边框、标题栏、系统菜单或 minimize/maximize/close 按钮。合适的 Window StylesWS_POPUP | WS_VISIBLE.

要使 window 显示在其他所有内容的前面,需要将其标记为最顶层(使用 WS_EX_TOPMOST Extended Window Style)。请注意,这将 window 置于 Z-order 中所有其他 non-topmost windows 之上。你还要和其他最顶尖的windows战斗(一场你赢不了的军备竞赛)。

要实现透明度,window 必须具有 WS_EX_LAYERED 扩展的 window 样式以及创建 Layered Window. Alpha transparency is then enabled calling SetLayeredWindowAttributes. To keep the window background fully transparent regardless of the window's alpha transparency you also need to enable color keying. A simple way to do this is to set the hbrBackground member of the WNDCLASSEX structure to (HBRUSH)GetStockObject(BLACK_BRUSH),并指定 RGB(0, 0, 0) 作为 crKey 调用中的参数 SetLayeredWindowAttributes


概念证明(为简洁起见省略了错误检查):

#define STRICT 1
#define WIN32_LEAN_AND_MEAN
#include <SDKDDKVer.h>
#include <windows.h>

// Forward declarations
LRESULT CALLBACK WndProc( HWND, UINT, WPARAM, LPARAM );

// Entry point
int APIENTRY wWinMain( HINSTANCE hInstance,
                       HINSTANCE /*hPrevInstance*/,
                       LPTSTR    /*lpCmdLine*/,
                       int       nCmdShow ) {

首先是注册主应用程序 window class。重要的部分是 hbrBackground 成员。这控制背景渲染,最终将完全透明。

    const wchar_t k_WndClassName[] = L"OverlayWindowClass";

    // Register window class
    WNDCLASSEXW wcex = { 0 };
    wcex.cbSize = sizeof( wcex );
    wcex.style          = CS_HREDRAW | CS_VREDRAW;
    wcex.lpfnWndProc    = WndProc;
    wcex.hInstance      = hInstance;
    wcex.hCursor        = ::LoadCursorW( NULL, IDC_ARROW );
    wcex.hbrBackground  = (HBRUSH)::GetStockObject( BLACK_BRUSH );
    wcex.lpszClassName  = k_WndClassName;
    ::RegisterClassExW( &wcex );

这是实例化 window 并调整其属性所需的所有设置代码。启用 Alpha 透明度以为 fade-out 效果做准备,同时颜色键控会屏蔽 window 未渲染到的那些区域。

    HWND hWnd = ::CreateWindowExW( WS_EX_TOPMOST | WS_EX_LAYERED,
                                   k_WndClassName,
                                   L"Overlay Window",
                                   WS_POPUP | WS_VISIBLE,
                                   CW_USEDEFAULT, CW_USEDEFAULT,
                                   800, 600,
                                   NULL, NULL,
                                   hInstance,
                                   NULL );
    // Make window semi-transparent, and mask out background color
    ::SetLayeredWindowAttributes( hWnd, RGB( 0, 0, 0 ), 128, LWA_ALPHA | LWA_COLORKEY );

wWinMain 的其余部分是样板 windows 应用程序代码。

    ::ShowWindow( hWnd, nCmdShow );
    ::UpdateWindow( hWnd );

    // Main message loop:
    MSG msg = { 0 };
    while ( ::GetMessageW( &msg, NULL, 0, 0 ) > 0 )
    {
        ::TranslateMessage( &msg );
        ::DispatchMessageW( &msg );
    }

    return (int)msg.wParam;
}

window程序执行简单渲染。为了演示 alpha 和关键颜色透明度,代码呈现了一个白色椭圆,其中客户区作为边界矩形。此外,还处理了 WM_NCHITTEST message,以提供一种使用鼠标或其他指针设备将 window 拖过屏幕的简单方法。请注意,对于完全透明的所有区域,鼠标输入都会传递到下面的 window。

LRESULT CALLBACK WndProc( HWND hWnd, UINT message, WPARAM wParam, LPARAM lParam )
{
    switch ( message )
    {
    case WM_PAINT:
        {
            PAINTSTRUCT ps = { 0 };
            HDC hDC = ::BeginPaint( hWnd, &ps );
            RECT rc = { 0 };
            ::GetClientRect( hWnd, &rc );
            HBRUSH hbrOld = (HBRUSH)::SelectObject( hDC,
                                                    ::GetStockObject( WHITE_BRUSH ) );
            ::Ellipse( hDC, rc.left, rc.top, rc.right, rc.bottom );
            ::SelectObject( hDC, hbrOld );
            ::EndPaint( hWnd, &ps );
        }
        return 0;

    case WM_NCHITTEST:
        return HTCAPTION;

    case WM_DESTROY:
        ::PostQuitMessage( 0 );
        return 0;

    default:
        break;
    }
    return ::DefWindowProc( hWnd, message, wParam, lParam );
}


替代 WM_PAINT 处理程序,输出文本。使用与键颜色不同的文本颜色很重要。如果您想使用黑色文本,则必须使用不同的键颜色。

    case WM_PAINT:
        {
            PAINTSTRUCT ps = { 0 };
            HDC hDC = ::BeginPaint( hWnd, &ps );
            RECT rc = { 0 };
            ::GetClientRect( hWnd, &rc );
            ::SetTextColor( hDC, RGB( 255, 255, 255 ) );
            ::SetBkMode( hDC, TRANSPARENT );
            ::DrawTextExW( hDC, L"Hello, World!", -1, &rc,
                           DT_SINGLELINE | DT_CENTER | DT_VCENTER, NULL );
            ::EndPaint( hWnd, &ps );
        }
        return 0;

我还希望能够直接写入屏幕(在示例中是显示实际 Windows 版本信息)。我做了很多搜索,终于能够拼凑代码……

Imports System.Environment

Public Class frmMain
    Dim stringFont As Font
    Dim string_format As New StringFormat()

    Dim my_WinName As String
    Const my_WinStatic As String = " ver. "
    Dim my_WinVersion As String
    Dim my_WinRelease As String
    Dim my_WinBuild As String
    Dim my_WinSubBuild As String
    Dim my_Delim1 As String
    Dim emb_Delim1 As Boolean
    Dim my_Delim2 As String
    Dim emb_Delim2 As Boolean
    Dim txt_Display_Ver As String
    Dim txt_Curr_Build As String
    Dim txt_UBR_Value As String
    Dim the_File As String
    Dim version_Complete As Boolean
    Dim current_Ver As String
    Dim previous_Ver As String

    Private Sub Form1_Load(sender As Object, e As EventArgs) Handles MyBase.Load
        Try
            If My.Settings.UpgradeRequired = True Then
                My.Settings.Upgrade()
                My.Settings.UpgradeRequired = False
                My.Settings.Save()
            End If
        Catch ex As Exception
            MessageBox.Show(ex.Message, "Upgrade from Previous Version Error")
        End Try

        conMenuAbout.Text = My.Application.Info.AssemblyName & "  (ver. " & My.Application.Info.Version.ToString & ")"
        prop_Grid.SelectedObject = My.Settings

        string_format.Alignment = StringAlignment.Far
        string_format.LineAlignment = StringAlignment.Near

        version_Complete = False

        form_Defaults()

        Me.WindowState = FormWindowState.Minimized

        nextTime.Interval = 500
        nextTime.Start()
    End Sub

    Private Sub nextTime_Tick(sender As Object, e As EventArgs) Handles nextTime.Tick
        Dim d As Graphics = Graphics.FromHwnd(IntPtr.Zero)

        Dim brush_Name As System.Drawing.Brush = New System.Drawing.SolidBrush(My.Settings.color_Win_Name)
        Dim brush_Static As System.Drawing.Brush = New System.Drawing.SolidBrush(My.Settings.color_static_Ver)
        Dim brush_WinVer As System.Drawing.Brush = New System.Drawing.SolidBrush(My.Settings.color_Win_Version)
        Dim brush_CharDelim As System.Drawing.Brush = New System.Drawing.SolidBrush(My.Settings.color_Char_Delim)
        Dim brush_Curr_Rel As System.Drawing.Brush = New System.Drawing.SolidBrush(My.Settings.color_Curr_Release)
        Dim brush_Curr_Bld As System.Drawing.Brush = New System.Drawing.SolidBrush(My.Settings.color_Curr_Build)
        Dim brush_UBR_Delim As System.Drawing.Brush = New System.Drawing.SolidBrush(My.Settings.color_UBR_Delim)
        Dim brush_UBR_Rev As System.Drawing.Brush = New System.Drawing.SolidBrush(My.Settings.color_UpdateBldRev)

        update_Version()

        Dim writeHere As New PointF(Screen.PrimaryScreen.Bounds.Right - 300, Screen.PrimaryScreen.Bounds.Bottom - 64)
        d.DrawString(my_WinName, stringFont, brush_Name, writeHere)

        writeHere.X += d.MeasureString(my_WinName, stringFont).Width - My.Settings.Space_1
        d.DrawString(my_WinStatic, stringFont, brush_Static, writeHere)

        writeHere.X += d.MeasureString(my_WinStatic, stringFont).Width - My.Settings.Space_2
        d.DrawString(my_WinVersion, stringFont, brush_WinVer, writeHere)

        writeHere.X += d.MeasureString(my_WinVersion, stringFont).Width - My.Settings.Space_3
        d.DrawString(my_Delim1, stringFont, brush_CharDelim, writeHere)

        writeHere.X += d.MeasureString(my_Delim1, stringFont).Width - My.Settings.Space_4
        d.DrawString(my_WinRelease, stringFont, brush_Curr_Rel, writeHere)

        writeHere.X += d.MeasureString(my_WinRelease, stringFont).Width - My.Settings.Space_5
        d.DrawString(my_Delim1, stringFont, brush_CharDelim, writeHere)

        writeHere.X += d.MeasureString(my_Delim1, stringFont).Width - My.Settings.Space_6
        d.DrawString(my_WinBuild, stringFont, brush_Curr_Bld, writeHere)

        writeHere.X += d.MeasureString(my_WinBuild, stringFont).Width - My.Settings.Space_7
        d.DrawString(my_Delim2, stringFont, brush_UBR_Delim, writeHere)

        writeHere.X += d.MeasureString(my_Delim2, stringFont).Width - My.Settings.Space_8
        d.DrawString(my_WinSubBuild, stringFont, brush_UBR_Rev, writeHere)

    End Sub

    Public Sub form_Defaults()

        Try
            nextTime.Interval = My.Settings.updateInterval
            Me.Opacity = My.Settings.Opacity / 100
            my_Delim1 = My.Settings.char_Delimiter
            emb_Delim1 = My.Settings.embolden_Char_Delim
            my_Delim2 = My.Settings.UBR_Delimiter
            emb_Delim2 = My.Settings.embolden_UBR_Char
        Catch ex As Exception
            MessageBox.Show(ex.Message)
        End Try

        update_Version()

        If current_Ver <> previous_Ver Then
            Try
                If Not String.IsNullOrEmpty(My.Settings.text_Version_File) Then
                    the_File = My.Settings.text_Version_File
                Else
                    ofd.FileName = "WinVer.txt"
                    If ofd.ShowDialog = DialogResult.OK Then
                        My.Settings.text_Version_File = ofd.FileName
                        the_File = ofd.FileName
                    End If
                End If
            Catch ex As Exception
                Dim str_Err = ex.Message
                ofd.FileName = "WinVer.txt"
                If ofd.ShowDialog = DialogResult.OK Then
                    My.Settings.text_Version_File = ofd.FileName
                    the_File = ofd.FileName
                End If
            End Try
            conMenuSaveVer_Click(Nothing, Nothing)
            previous_Ver = current_Ver
        End If

    End Sub

    Private Sub update_Version()
        Try
            Dim tmpStr As String = My.Computer.Registry.GetValue("HKEY_LOCAL_MACHINE\SOFTWARE\Microsoft\Windows NT\CurrentVersion", "ProductName", Nothing)
            my_WinName = Replace(tmpStr, "Windows ", "Win ")
            tmpStr = Replace(my_WinName, "Professional", "Pro")
            my_WinName = Replace(tmpStr, "Enterprise", "Pro")
            my_WinVersion = My.Computer.Registry.GetValue("HKEY_LOCAL_MACHINE\SOFTWARE\Microsoft\Windows NT\CurrentVersion", "CurrentVersion", Nothing)
            my_WinRelease = My.Computer.Registry.GetValue("HKEY_LOCAL_MACHINE\SOFTWARE\Microsoft\Windows NT\CurrentVersion", "DisplayVersion", Nothing)
            my_WinBuild = My.Computer.Registry.GetValue("HKEY_LOCAL_MACHINE\SOFTWARE\Microsoft\Windows NT\CurrentVersion", "CurrentBuild", Nothing)
            Dim myKey As Microsoft.Win32.RegistryKey = Microsoft.Win32.RegistryKey.OpenBaseKey(Microsoft.Win32.RegistryHive.LocalMachine, Microsoft.Win32.RegistryView.Registry64)
            Dim myKey2 As Object = myKey.OpenSubKey("SOFTWARE\Microsoft\Windows NT\CurrentVersion")
            Dim myVal As Int64 = Convert.ToInt64(myKey2.GetValue("UBR").ToString)
            my_WinSubBuild = myVal
            current_Ver = my_WinName & my_WinVersion & my_WinRelease & my_WinBuild & my_WinSubBuild
            stringFont = New Font(My.Settings.useFont, My.Settings.useFont.Style)
            version_Complete = True
        Catch ex As Exception
            MessageBox.Show(ex.Message)
        End Try
    End Sub

    Private Sub conMenuExit_Click(sender As Object, e As EventArgs) Handles conMenuExit.Click
        Application.Exit()
    End Sub

    Private Sub conMenuSettings_Click(sender As Object, e As EventArgs) Handles conMenuSettings.Click
        Me.WindowState = FormWindowState.Normal
    End Sub

    Private Sub frmMain_Resize(sender As Object, e As EventArgs) Handles MyBase.Resize
        If Me.WindowState = FormWindowState.Minimized Then
            Me.sysTrayIcon.Visible = True
            Me.ShowInTaskbar = False
        Else
            Me.ShowInTaskbar = True
            Me.sysTrayIcon.Visible = False
        End If
    End Sub

    Private Sub frmMain_FormClosing(sender As Object, e As FormClosingEventArgs) Handles MyBase.FormClosing
        e.Cancel = True
        Me.WindowState = FormWindowState.Minimized
    End Sub

    Private Sub conMenuSaveVer_Click(sender As Object, e As EventArgs) Handles conMenuSaveVer.Click
        If version_Complete = False Then
            Exit Sub
        End If

        Try
            My.Computer.FileSystem.WriteAllText(the_File, current_Ver, False)
        Catch ex As Exception
            MessageBox.Show(Me, ex.Message, "Save Version Error")
        End Try

        Try
            If Not String.IsNullOrEmpty(current_Ver) Then
                If Not String.IsNullOrWhiteSpace(current_Ver) Then
                    Clipboard.SetText(current_Ver)
                End If
            End If
        Catch ex As Exception
            MessageBox.Show(Me, ex.Message, "Clipboard Error")
        End Try
    End Sub

End Class