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]

Манипулатор на прозореца, чийто DC трябва да бъде извлечен. Ако тази стойност е NULL, GetDC извлича DC за целия екран.

Изобразяването директно на екрана създава поне два проблема, които трябва да бъдат решени:

  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