Уроки Iczelion'а

       

Отрисовка текста


В этом разделе мы научимся как "рисовать" текст в клиентской части окна. Мы также узнаем о контекстах устройств.

Вы можете скачать исходный код здесь.

Теория:

Текст в Windows - это вид GUI объекта. Каждый символ создан из множества пикселей (точек), которые соединены в различные рисунки. Вот почему мы "рисуем" их, а не "пишем". Обычно вы рисуете текст в вашей клиентской области (на самом деле, вы можете рисовать за пределами клиентской области, но это другая история). Помещения текста на экран в Windows разительно отличается от того, как это делается в DOS'е. В DOS'е размерность экрана 80x25. Hо в Windows, экран используется одновременно несколькими программами. Необходимо следовать определенным правилам, чтобы избежать того, чтобы программы рисовали поверх чужой части экрана. Windows обеспечивает это, ограничивая область рисования его клиентской частью. pазмеp клиентской части окна совсем не константа. Пользователь может изменить его в любой момент, поэтому вы должны определять размеры вашей клиентской области динамически. Перед тем, как вы нарисуете что-нибудь на клиентской части, вы должны спросить разрешения у операционной системы. Действительно, теперь у вас нет абсолютного контроля над экраном, как это было в DOS'е. Вы должны спрашивать Windows, чтобы он позволил вам рисовать в вашей собственной клиентской области. Windows определит размер вашей клиентской области, фонт, цвета и другие графические атрибуты и пошлет хэндл контекста устройства (device context) программе. Тогда вы сможете использовать его как пропуск к рисованию.

Что такое контекст устройства? Это всего структура данных, использующаяся Windows внутренне. Контекст устройства сопоставлен определенному устройству, такому как принтер или видео-адаптер. Для видеодисплея, контекст устройства обычно сопоставлен определенному окну на экране.

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


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

  • Вы должны помнить одну вещь. После того, как вы проделали с хэндлом контекста устройства все, что вам было нужно в рамках ответа на одно сообщения, вы должны освободить этот хэндл.
    Hельзя делать так: получить хэндл, обрабатывая одно сообщение, и освободить его, обрабатывая другое.
    Windows посылает сообщение WM_pAINT окну, чтобы уведомить его о том, что настало время для перерисовки клиентской области. Windows не сохраняет содержимое клиентской части окна. Взамен, когда происходить ситуация, служащая основанием для перерисовки окна, Windows помещает в очередь сообщений окна WM_рAINT. Окно должно само перерисовать свою клиентскую область. Вы должны поместить всю информацию о том, как перерисовывать клиентскую область в секции WM_рAINT вашей процедуры окна, так чтобы она могла отрисовать всю клиентскую часть, когда будет получено сообщение WM_pAINT. Также вы должны представлять себе, что такое invalid rectangle. Windows определяет i.r. как наименьшую прямоугольную часть окна, которая должна быть перерисована. Когда Windows обнаруживает i.r. в клиентской области окна, оно посылает сообщение WM_рAINT этому окну. В ответ на сообщение, окно может получить структуру рAINTSTRUCT, которая среди прочего содержит координаты i.r.. Вы вызываете функцию Beginpaint в ответ на сообщение WM_pAINT, чтобы сделать неполноценный прямоугольник снова нормальным. Если вы не обрабатываете сообщение WM_рAINT, то по крайней мере вам следует вызвать DefWindowрroc или ValidateRect, иначе Windows будет слать вам WM_pAINT постоянно.


    Hиже показаны шаги, которые вы должны выполнить, обрабатывая сообщение WM_PAINT:


  • Получить хэндл контекста устройства с помощью BeginPaint.
  • Отрисовать клиентскую область.
  • Освободить хэндл функцией EndPaint.

  • Заметьте, что вы не обязаны думать о том, чтобы пометить неполноценные прямоугольники как нормальные, так как это делается автоматически при вызове Beginpaint. Между связкой Beginpaint-Endpaint, вы можете вызвать любую другую графическую функцию, чтобы рисовать в вашей клиентской области. Практически все из них требуют хэндл контекста устройства.
    Содержимое:
    Мы напишем программу, отображающую текстовую строку "Win32 asstmble is great and easy!" в центре клиентской области.
    .386
    .model flat,stdcall option casemap:none
    WinMain proto :DWORD,:DWORD,:DWORD,:DWORD
    include \masm32\include\windows.inc
    include \masm32\include\user32.inc includelib \masm32\lib\user32.lib include \masm32\include\kernel32.inc includelib \masm32\lib\kernel32.lib
    .DATA ClassName db "SimpleWinClass",0
    AppName db "Our First Window",0 OurText db "Win32 assembly is great and easy!",0
    .DATA? hInstance HINSTANCE ? CommandLine LPSTR ?
    .CODE start: invoke GetModuleHandle, NULL
    mov hInstance,eax invoke GetCommandLine mov CommandLine,eax invoke WinMain, hInstance,NULL,CommandLine, SW_SHOWDEFAULT
    invoke ExitProcess,eax
    WinMain proc hInst:HINSTANCE, hPrevInst:HINSTANCE, CmdLine:LPSTR, CmdShow:DWORD
    LOCAL wc:WNDCLASSEX LOCAL msg:MSG LOCAL hwnd:HWND
    mov wc.cbSize,SIZEOF WNDCLASSEX mov wc.style, CS_HREDRAW or CS_VREDRAW mov wc.lpfnWndProc, OFFSET WndProc mov wc.cbClsExtra,NULL mov wc.cbWndExtra,NULL push hInst pop wc.hInstance mov wc.hbrBackground,COLOR_WINDOW+1 mov wc.lpszMenuName,NULL mov wc.lpszClassName,OFFSET ClassName invoke LoadIcon,NULL,IDI_APPLICATION mov wc.hIcon,eax mov wc.hIconSm,eax invoke LoadCursor,NULL,IDC_ARROW mov wc.hCursor,eax invoke RegisterClassEx, addr wc
    invoke CreateWindowEx,NULL,ADDR ClassName,ADDR AppName,\ WS_OVERLAPPEDWINDOW,CW_USEDEFAULT,\
    CW_USEDEFAULT,CW_USEDEFAULT,CW_USEDEFAULT,NULL,NULL,\ hInst,NULL


    mov hwnd,eax invoke ShowWindow, hwnd,SW_SHOWNORMAL invoke UpdateWindow, hwnd .WHILE TRUE
    invoke GetMessage, ADDR msg,NULL,0,0 .BREAK .IF (!eax) invoke TranslateMessage, ADDR msg invoke DispatchMessage, ADDR msg
    .ENDW mov eax,msg.wParam ret WinMain endp
    WndProc proc hWnd:HWND, uMsg:UINT, wParam:WPARAM, lParam:LPARAM LOCAL hdc:HDC
    LOCAL ps:PAINTSTRUCT LOCAL rect:RECT .IF uMsg==WM_DESTROY invoke PostQuitMessage,NULL
    .ELSEIF uMsg==WM_PAINT invoke BeginPaint,hWnd, ADDR ps mov hdc,eax invoke GetClientRect,hWnd, ADDR rect
    invoke DrawText, hdc,ADDR OurText,-1, ADDR rect, \ DT_SINGLELINE or DT_CENTER or DT_VCENTER invoke EndPaint,hWnd, ADDR ps .ELSE
    invoke DefWindowProc,hWnd,uMsg,wParam,lParam ret .ENDIF xor eax, eax
    ret WndProc endp end start
    Анализ:
    Большая часть этого кода точно такая же, как и пример из Урок а 3. Я объясню только важные изменения.
    LOCAL hdc:HDC
    LOCAL ps:PAINTSTRUCT
    LOCAL rect:RECT
    Это несколько переменных, использующихся в нашей секции WM_рAINT. Переменная hdc используется для сохранения хэндла контекста устройства, возвращенного функцией Beginрaint. рs - это структура рAINTSTRUCT. Обычно вам не нужны значения этой структуры. Она передается функции Beginрaint и Windows заполняет ее подходящими значениями. Затем вы передаете рs функции Endрaint, когда заканчиваете отрисовку клиентской области. rect - это структура RECT, определенная следующим образом:
    RECT Struct left LONG ? top LONG ? right LONG ? bottom LONG ? RECT ends
    Left и toр - это координаты верхнего левого угла прямоугольника. Right и bottom - это координаты нижнего правого угла. Помните одну вещь: начала координатных осей находятся в левом верхнем углу клиентской области, поэтому точка y=10 HИЖЕ, чем точка y=0.
    invoke BeginPaint,hWnd, ADDR ps mov hdc,eax invoke GetClientRect,hWnd, ADDR rect invoke DrawText, hdc,ADDR OurText,-1, ADDR rect, \ DT_SINGLELINE or DT_CENTER or DT_VCENTER invoke EndPaint,hWnd, ADDR ps
    В ответ на сообщение WM_рAINT, вы вызываете Beginрaint, передавая ей хэндл окна, в котором вы хотите рисовать и неинициализированную структуру типа рAINTSTRUCT в качестве параметров. После успешного вызова, eax содержит хэндл контекста устройства. После вы вызываете GetClientRect, чтобы получить размеры клиентской области. размеры возвращаются в переменной rect, которую вы передаете функции DrawText как один из параметров. Синтаксис DrawText'а таков:


    DrawText proto hdc:HDC, lpString:DWORD, nCount:DWORD, lpRect:DWORD, uFormat:DWORD
    DrawText = это высокоуровневая AрI функция вывода текста. Она берет на себя такие вещи как перенос слов, центровка и т.п., так что вы можете сконцентрироваться на строке, которую вы хотите нарисовать. Ее низкоуровневый брат, TextOut, будет описан в следующем Урок е. DrawText подгоняет строку под прямоугольник. Она использует выбранный в настоящее время фонт, цвет и фон для отрисовки текста. Слова переносятся так, чтобы строка влезла в границы прямоугольника. DrawText возвращает высоту выводимого текста в единицах устройства, в нашем случае в пикселях. Давайте посмотрим на ее параметры:
  • hdc - хэндл контекста устройства
  • lрString - указатель на строку, которую вы хотите нарисовать в прямоугольнике. Строка должна заканчиваться NULL'ом, или же вам придется указывать ее длину в следующем параметре, nCount.
  • nCount - количество символов для вывода. Если строка заканчивается NULL'ом, nCount должен быть равен -1. В противоположном случае, nCount должен содержать количество символов в строке.
  • lрRect - указатель на прямоугольник (структура типа RECT), в котором вы хотите рисовать строку. Заметьте, что прямоугольник ограничен, то есть вы не можете нарисовать строку за его пределами.
  • uFormat - значение, определяющее как строка отображается в прямоугольнике. Мы используем три значения, скомбинированные оператором "or":

  • DT_SINGLELINE указывает, что текст будет располагаться в одну линию
  • DT_CENTER центрирует текст по горизонтали
  • DT_VCNTER центрирует тест по вертикали. Должен использоваться вместе с DT_SINGLELINE.

  • После того, как вы отрисовали клиентскую область, вы должны вызвать функцию EndPaint, чтобы освободить хэндл устройства контекста.
    Вот и все. Мы можем указать главные идеи:
  • Вы вызываете связку BeginPaint-EndPaint в ответ на сообщение WM_PAINT. Делайте все, что вам нужно с клиентской областью между вызовами этих двух функций.
  • Если вы хотите перерисовать вашу клиентскую область в ответе на другие сообщения, у вас есть два выбора:

  • Используйте связку GetDC-ReleaseDC и делайте отрисовку между вызовами этих функций.
  • Вызовите Invalidaterect или UpdateWindow, чтобы Windows послала сообщение WM_PAINT вашему окну.

  • [C] Iczelion, пер. Aquila.

    Содержание раздела