Windows API: запись на экран, как на экране

Я пишу (очень) маленькое приложение, которое просто выполняет некоторые второстепенные действия при запуске и должно выводить на экран сообщение, аналогичное экранному дисплею: затем исчезнуть.

Если возможно, я не хочу создавать для него окно.

Как правильно это сделать?

(Надеюсь, не требуются специальные инструменты, такие как DirectX, прямой доступ к графике и т. д.)


person divB    schedule 17.03.2015    source источник
comment
Создание окна было бы нормальным способом сделать это. Окна могут быть прозрачными, и им не нужно иметь видимую рамку и т. д., если это мешает вам их использовать.   -  person Jonathan Potter    schedule 17.03.2015
comment
Лучше всего использовать прозрачное окно верхнего уровня, но если вам абсолютно необходимо писать прямо на экране, вы можете использовать GetDC(0), чтобы получить дескриптор холста HDC для экрана, а затем рисовать на нем по мере необходимости.   -  person Remy Lebeau    schedule 17.03.2015
comment
Спасибо. Я думаю, я пойду к окну   -  person divB    schedule 17.03.2015


Ответы (2)


Как указано в комментариях, вы можете рисовать прямо на экране. GetDC предлагает вернуть соответствующий контекст устройства:

hWnd [in]

Дескриптор окна, контроллер домена которого необходимо получить. Если это значение равно NULL, GetDC извлекает контроллер домена для всего экрана.

Рендеринг непосредственно на экран создает как минимум две проблемы, которые необходимо решить:

  1. Экран DC является общим ресурсом. Всякий раз, когда кто-то еще выполняет рендеринг на экран (например, когда отображается окно), эта часть экрана перезаписывается.
  2. Рендеринг деструктивен. При рендеринге в контексте устройства исходное содержимое перезаписывается. Чтобы реализовать эффект затухания, вам придется сохранить исходное содержимое (и обновлять его динамически по мере отображения других окон).

Обе проблемы можно решить, создав вместо этого окно. Окно не обязательно должно иметь рамку, строку заголовка, системное меню или кнопки сворачивания/разворачивания/закрытия. Подходящими стилями окон являются WS_POPUP | WS_VISIBLE.

Чтобы окно отображалось поверх всего остального, его нужно пометить как самое верхнее (используя WS_EX_TOPMOST Расширенный стиль окна). Обратите внимание, что это помещает окно выше всех других не самых верхних окон в Z-порядке. Вам все равно придется сражаться с другими самыми верхними окнами (гонка вооружений, которую вы не можете выиграть).

Для реализации прозрачности окно также должно иметь стиль расширенного окна WS_EX_LAYERED, а также создать Многоуровневое окно. Затем включается альфа-прозрачность, вызывая SetLayeredWindowAttributes. Чтобы фон окна оставался полностью прозрачным независимо от альфа-прозрачности окна, вам также необходимо включить цветовую манипуляцию. Простой способ сделать это — установить hbrBackground элемент WNDCLASSEX. структуру на (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 ) {

Сначала нужно зарегистрировать класс главного окна приложения. Важным элементом является 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 );

Это весь код установки, необходимый для создания экземпляра окна и настройки его атрибутов. Альфа-прозрачность включена для подготовки к эффекту затухания, а цветовая манипуляция маскирует те области окна, которые не визуализируются.

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

Оконная процедура выполняет простой рендеринг. Чтобы продемонстрировать прозрачность альфа-канала и ключевого цвета, код отображает белый эллипс с клиентской областью в качестве ограничивающего прямоугольника. Кроме того, обрабатывается сообщение WM_NCHITTEST, чтобы обеспечить простой способ перетаскивания окна по экрану с помощью мыши или другого указывающего устройства. Обратите внимание, что ввод мыши передается в окно внизу для всех областей, которые полностью прозрачны.

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;
person IInspectable    schedule 17.03.2015
comment
Вообще это работает очень здорово. Однако, когда я отображаю текст с помощью DrawText, фон всегда белый, несмотря на то, что само окно прозрачно. Когда я использую SetBkMode(hDC, TRANSPARENT) перед DrawText, ничего не отображается. Есть ли исправление для этого? - person divB; 18.03.2015
comment
@divB: я предполагаю, что вы визуализировали свой текст, используя ключевой цвет (черный). Ключевой цвет будет заменен тем, что находится под окном. Текст будет казаться полностью прозрачным. Я обновил ответ, чтобы вместо этого включить текст рендеринга кода. - person IInspectable; 21.03.2015
comment
@IInspectable, можно ли добиться того же эффекта, но без настройки цветовой рирпроекции? Что, если я хочу использовать все цвета (таким образом, я не могу использовать любой цвет в качестве цвета прозрачности)? Можно ли настроить его таким образом, чтобы я мог указать значение альфа-канала, когда рисую что-то (чтобы я мог нарисовать нужную мне форму, а затем заполнить остальную часть окна кистью, у которой есть альфа-канал? или указать, что окно по умолчанию заполняется прозрачным цветом, а затем рисует фигуру поверх него?) - person Daniel; 26.01.2017

Я также хотел иметь возможность писать прямо на экран (в примере это отображало АКТУАЛЬНУЮ информацию о версии 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
person Mr. Jiggs    schedule 07.07.2021