Опубликован: 15.11.2010 | Доступ: свободный | Студентов: 643 / 18 | Оценка: 4.00 / 5.00 | Длительность: 17:30:00
Специальности: Программист
Лекция 12:

Вывод текста на экран и устройство в Windows

12.10. Приложение № IV

Использование полос прокрутки для вывода текста

Полосы прокрутки широко используются в приложениях Windows для просмотра текста или изображения, не помещающегося в окне. Полосы прокрутки бывают горизонтальными и вертикальными. Горизонтальная и вертикальная полосы прокрутки посылают в функцию родительского окна сообщения WM_HSCROLL и WM_VSCROLL соответственно. Поэтому функция обработки сообщений родительского окна должна обрабатывать эти сообщения. Параметр WParam сообщений несёт информацию о действии, которое выполнялось над полосой прокрутки.

Создание полосы прокрутки

Во-первых, Вы можете создать полосу прокрутки с помощью функции CreateWindow, указав предопределённый класс окна "scrollbar". Этот способ аналогичен способу создания кнопок или статических органов управления. Этот способ подробно рассматривается в книге [39 ]. Во-вторых, при создании окна на базе своего собственного класса Вы можете указать, что окно должно иметь горизонтальную, вертикальную или обе полосы прокрутки. Этот способ мы и рассмотрим.

Второй способ создания полос чрезвычайно прост, но с его помощью можно создать только одну горизонтальную и одну вертикальную полосы прокрутки, расположенные по краям окна. Для того чтобы эта полосы прокрутки появились в окне, при регистрации класса окна в третьем параметре функции CreateWindow необходимо указать стиль окна WS_VSCROLL, или WS_HSCROLL, или оба стиля вместе.

Пример 12.1.

hwnd = CreateWindow( szClassName, szWindowTitle,
  // Стиль окна
  WS_OVERLAPPEDWINDOW | WS_VSCROLL | WS_HSCROLL,
  CW_USEDEFAULT, CW_USEDEFAULT,
  CW_USEDEFAULT, CW_USEDEFAULT,
  0, 0, hInstance, NULL );

Сообщения от полосы прокрутки рассматриваются в таблице 12.6.

Таблица 12.6. Сообщения от полосы прокрутки
Константы WParam сообщений для полсы прокрутки
Флаг полосы просмотра Описание
SB_LEFT Сдвиг влево на начало документа (горизонтальная полоса прокрутки)
SB_TOP Сдвиг вверх на начало документа (вертикальная полоса прокрутки)
SB_LINELEFT Сдвиг влево на одну строку
SB_LINEUP Сдвиг вверх на одну строку
SB_PAGELEFT Сдвиг на одну страницу влево
SB_PAGEUP Сдвиг на одну страницу вверх
SB_RIGHT Сдвиг вправо в конец документа (горизонтальная полоса прокрутки)
SB_BOTTOM Сдвиг вниз в конец документа (вертикальная полоса прокрутки)
SB_LINERIGHT Сдвиг вправо на одну строку
SB_LINEDOWN Сдвиг вниз на одну строку
SB_PAGERIGHT Сдвиг на одну страницу вправо
SB_PAGEDOWN Сдвиг на одну страницу вниз
SB_THUMBPOSITION Сдвиг в абсолютную позицию. Текущая позиция определяется младшим словом параметра lParam
SB_ENDSCROLL Сообщение приходит в момент, когда вы отпускаете клавишу мыши после работы с полосой просмотра. Это сообщение обычно игнорируется (передаётся функции DefWindowProc )
SB_THUMBTRACK Перемещение ползунка просмотра. Текущая позиция определяется младшим словом параметра lParam

Инициализация полосы просмотра

Для полосы прокрутки определены понятия "текущая позиция" и "диапазон изменения значений позиции". При передвижении ползунка вдоль полосы прокрутки текущая позиция принимает дискретные значения внутри диапазона изменения значений позиции. Если ползунок находится в самом левом или самом верхнем положении, текущая позиция является минимальной. Если ползунок находится в самом правом или самом нижнем положении, текущая позиция является максимальной.

После того, как Вы создали полосу прокрутки, её необходимо проинициализировать, указав диапазон изменений значений позиции. Для этого необходимо вызвать функцию: SetScrollRange, описанную ниже:

ФУНКЦИЯ SETSCROLLRANGE
ОПРЕДЕЛЕНА В: <windows.h>
СИНТАКСИС: void WINAPI SetScrollRange( hwnd, fnBar, nMin, nMax, fRedraw ), где
  • HWND hwnd - определяет идентификатор окна, имеющего полосу прокрутки, или идентификатор полосы прокрутки, созданного как орган управления;
  • int fnBar - определяет один из типов полосы прокрутки. Его значения смотри в Таблице 12.7;
  • Целые (int) параметры nMin и nMax определяют соответственно минимальное и максимальное значение для диапазона позиций. Разность между nMax и nMin не должны превышать числа 32767;
  • BOOL fRedraw - Определяет, нужно ли перерисовывать полосу прокрутки для отражения изменений. Если значение равно TRUE, то полоса прокрутки будет перерисована, и FALSE - полоса прокрутки остаётся без изменений;
НАЗНАЧЕНИЕ: Функция устанавливает диапазон значений позиции для полосы прокрутки;
ОПИСАНИЕ:
ВОЗВРАЩАЕМОЕ ЗНАЧЕНИЕ: Функция не возвращает значений;
ПЕРЕНОСИМОСТЬ: Только Windows;

Всегда можно узнать диапазон для полосы просмотра, вызвав функцию GetScrollRange:

ФУНКЦИЯ GETSCROLLRANGE
ОПРЕДЕЛЕНА В: <windows.h>
СИНТАКСИС: void WINAPI SetScrollRange( hwnd, fnBar, lpnMin, lpnMax ), где
  • HWND hwnd - определяет идентификатор окна, имеющего полосу прокрутки, или идентификатор полосы прокрутки, созданного как орган управления;
  • int fnBar - определяет один из типов полосы прокрутки. Его значения смотри в Таблице 12.7;
  • В значения для дальних указателей на целые числа (int FAR*), lpnMin и lpnMax будет записано соответственно минимальное и максимальное значение для диапазона позиций. Разность между nMax и nMin не должны превышать числа 32767;
НАЗНАЧЕНИЕ: Функция выдаёт диапазон значений позиции для полосы прокрутки;
ОПИСАНИЕ:
ВОЗВРАЩАЕМОЕ ЗНАЧЕНИЕ: Функция не возвращает значений;
ПЕРЕНОСИМОСТЬ: Только Windows;

Другие функции, управляющие режимом отображения полос прокрутки, смотри в книге [39].

Таблица 12.7. Типы полос прокрутки (флаги)
Значения флага fnBar функций работы с полосами прокрутки
Значение Описание
SB_CTL Установка диапазона полосы прокрутки, созданной как орган управления класса "scrollbar". В этом случае параметр hwnd функции SetScrollRange и других должен содержать идентификатор органа управления, полученный при его создании функцией CreateWindow
SB_HORZ Установка диапазона горизонтальной полосы прокрутки, при создании которой был использован стиль окна WS_HSCROLL. Параметр функции hwnd должен содержать идентификатор окна, имеющий эту полосу прокрутки.
SB_VERT Установка диапазона вертикальной полосы прокрутки, при создании которой был использован стиль окна WS_VSCROLL. Параметр функции hwnd должен содержать идентификатор окна, имеющий эту полосу прокрутки.

12.11. Приложение № V

Пример программы на основе библиотек WinAPI для вывода текста в рабочее окно с вертикальной полосой прокрутки.

Данная программа оптимизирована под среду разработки Borland C/C++ 3.1.

/* Файл TEXTOUTS.H с объявлениями функций и переменных*/
#ifndef MAXTEXTBUFFSIZE
#define MAXTEXTBUFFSIZE  80 // Максимальная ширина строки в символах
#endif
/* #ifndef MAXTEXTSTRINGS
#define MAXTEXTSTRINGS  24 // Максимальное число строк в окне
#endif */
#ifndef __WINDOWS_H
#include <windows.h>
#endif
#ifndef __TEXTOUTS_H
#define __TEXTOUTS_H
// Методы
// "Удержание" позиции прокрутки в "правильных" границах
void  vnormalize( void );
void WINAPI setVScrollPos( int ); // Установка текущей позиции скроллинга
int WINAPI getVScrollPos();     // Выдача текущей позиции скроллинга
void WINAPI setMaxVScrol( int );  // Установка максимального количества строк
int WINAPI getMaxVScroll();    // Выдача максимального количества строк
void WINAPI TextMetricsInit( HDC ); // Установка параметров шрифтов
void WINAPI vPageUp( int ); // Перемещение рабочей области окна на экран вверх
void WINAPI vPageDown( int ); // Перемещение рабочей области окна на экран вниз
void WINAPI PrintHDC_scroll( HDC, const char * ); // Печать строки без переноса на новую строку
void WINAPI PrintLnHDC_scroll( HDC, const char * ); // Печать строки с переносом на новую строку
void WINAPI PrintInitHDC_scroll( void ); //  Данная функция инициализирует позиции вывода текста при обработке прерывания WM_PAINT
#endif
/* Файл View0000.CPP */
// ------------------------------------
// Вывод текста в рабочую область окна
// с вертикальной полосой прокрутки
// ----------------------------------------
#define STRICT
#include <windows.h>
#include <mem.h>
BOOL InitApp(HINSTANCE);
LRESULT CALLBACK _export WndProc(HWND, UINT, WPARAM, LPARAM);
char const szClassName[]   = "ViewerAppClass";
char const szWindowTitle[] = "Viewer0000 Application";
// =====================================
// Функция WinMain
// =====================================
#pragma argsused
int PASCAL
WinMain(HINSTANCE hInstance,
  HINSTANCE hPrevInstance,
  LPSTR     lpszCmdLine,
  int       nCmdShow)
{
  MSG  msg;   // структура для работы с сообщениями
  HWND hwnd;  // идентификатор главного окна приложени
  if(!InitApp(hInstance))
      return FALSE;
  hwnd = CreateWindow(
    szClassName,         // имя класса окна
    szWindowTitle,       // заголовок окна
    WS_OVERLAPPEDWINDOW | WS_VSCROLL, // стиль окна
    CW_USEDEFAULT,       // задаем размеры и расположение
    CW_USEDEFAULT,       // окна, принятые по умолчанию
    CW_USEDEFAULT,
    CW_USEDEFAULT,
    0,                   // идентификатор родительского окна
    0,                   // идентификатор меню
    hInstance,           // идентификатор приложени
    NULL);               // указатель на дополнительные
       // параметры
  if(!hwnd)
    return FALSE;
  ShowWindow(hwnd, nCmdShow);
  UpdateWindow(hwnd);
  while(GetMessage(&msg, 0, 0, 0))
  {
    DispatchMessage(&msg);
  }
  return msg.wParam;
}
// =====================================
// Функция InitApp
// =====================================
BOOL
InitApp(HINSTANCE hInstance)
{
  ATOM aWndClass; // атом для кода возврата
  WNDCLASS wc;    // структура для регистрации
                  // класса окна
  memset(&wc, 0, sizeof(wc));
  // Определяем стиль класса окна, при
  // использовании которого окно требует
  // перерисовки в том случае, если
  // изменилась его ширина или высота
  wc.style = CS_HREDRAW | CS_VREDRAW;
  wc.lpfnWndProc = (WNDPROC) WndProc;
  wc.cbClsExtra = 0;
  wc.cbWndExtra = 0;
  wc.hInstance = hInstance;
  wc.hIcon = LoadIcon(NULL, IDI_APPLICATION);
  wc.hCursor = LoadCursor(NULL, IDC_ARROW);
  wc.hbrBackground = (HBRUSH)(COLOR_WINDOW + 1);
  wc.lpszMenuName = (LPSTR)NULL;
  wc.lpszClassName = (LPSTR)szClassName;
  aWndClass = RegisterClass(&wc);
  return (aWndClass != 0);
}
/* Файл WINPROC.CPP с примерами функций */
// =====================================
// Функция WndProc
// =====================================
#define STRICT
#include <windows.h>
#include <stdio.h>
#include <string.h>
#include <stdlib.h>
#include "textouts.h"
#define MAXTEXTSTRINGS  20 // Переопределённое значение высоты экрана в строках
// Переменные
  static int  nVScrollPos;  // Текущая позиция вертикальной полосы прокрутки
  static int  nMaxVScrolled;  // Максимальное значение позиции
  static int  nxCurPos;     // Текущая горизонтальная позиция вывода в окне
  static int  nyCurPos;     // Текущая вертикальная позиция вывода в окне
  static int  cxChar;       // Ширина символов
  static int  cyChar;       // Высота строки с символами
  static char  otladka[80];  // Данные для отладки
LRESULT CALLBACK _export
WndProc(HWND hwnd, UINT msg, WPARAM wParam, LPARAM lParam)
{
  static WORD cxClient, cyClient;
  HDC hdc;              // индекс контекста устройства
  PAINTSTRUCT ps;       // структура для рисовани
  TEXTMETRIC  tm;     // Структура для записи метрик шрифта
  switch (msg)
  {
    case WM_CREATE:
    {
      // Получаем контекст отображения,
      // необходимый для определения метрик шрифта
      hdc = GetDC(hwnd);
      // Заполняем структуру информацией
      // о метрике шрифта, выбранного в
      // контекст отображени
      GetTextMetrics( hdc, &tm );
      // Запоминаем значение ширины для самого широкого символа
      cxChar = tm.tmMaxCharWidth;
      // Запоминаем значение высоты букв с учётом междустрочного интервала
      cyChar = tm.tmHeight + tm.tmExternalLeading;
      // Инициализируем текущую позицию вывода текста
      nxCurPos = cxChar/2;  // Текущая горизонтальная позиция вывода в окне
      nyCurPos = 0;  // Текущая вертикальная позиция вывода в окне
      // Задаём начальное значение позиции
      nVScrollPos = 0;
      nMaxVScrolled = MAXTEXTSTRINGS;
      // Освобождаем контекст
      ReleaseDC(hwnd, hdc);
      // Задаем диапазон изменения значений
      SetScrollRange(hwnd, SB_VERT, 0, nMaxVScrolled, FALSE);
      // Устанавливаем ползунок в начальную позицию
      SetScrollPos(hwnd, SB_VERT, nVScrollPos, TRUE);
      return 0;
    }
    // Определяем размеры внутренней области окна
    case WM_SIZE:
    {
      cxClient = LOWORD(lParam);
      cyClient = HIWORD(lParam);
      return 0;
    }
    // Сообщение от вертикальной полосы просмотра
    case WM_VSCROLL:
    {
      switch(wParam)
      {
  case SB_TOP:
  {
    nVScrollPos = 0;
          break;
        }
  case SB_BOTTOM:
  {
    nVScrollPos = nMaxVScrolled;
          break;
        }
  case SB_LINEUP:
  {
    nVScrollPos -= 1;
          break;
        }
  case SB_LINEDOWN:
  {
    nVScrollPos += 1;
          break;
        }
  case SB_PAGEUP:
  {
    nVScrollPos -= cyClient / cyChar;
          break;
        }
  case SB_PAGEDOWN:
  {
    nVScrollPos += cyClient / cyChar;
          break;
        }
  case SB_THUMBPOSITION:
  {
    nVScrollPos = LOWORD(lParam);
          break;
  }
  // Блокируем для того чтобы избежать
  // мерцания содержимого окна при
  // перемещении ползунка
  case SB_THUMBTRACK:
  {
    return 0;
  }
  default:
          break;
      }
      // Ограничиваем диапазон изменения значений
      vnormalize();
      // Устанавливаем ползунок в новое положение
      SetScrollPos(hwnd, SB_VERT, nVScrollPos, TRUE);
      // Обновляем окно
      InvalidateRect(hwnd, NULL, TRUE);
      return 0;
    }
    case WM_PAINT:
    {
      hdc = BeginPaint(hwnd, &ps);
      /* Эти операторы используется для тестировани
      sprintf( otladka, "nVScrollPos = %d\n", nVScrollPos );
      MessageBox( hwnd, otladka, NULL, MB_OK ); */
      // Инициализируем позицию вывода текста
      PrintInitHDC_scroll();
      // Выводим текст
      PrintLnHDC_scroll(hdc, "Это первая строка");
      PrintLnHDC_scroll(hdc, "Это следующая строка");
      PrintLnHDC_scroll(hdc, "Это следующая строка");
      PrintLnHDC_scroll(hdc, "Это следующая строка");
      PrintLnHDC_scroll(hdc, "Это следующая строка");
      PrintLnHDC_scroll(hdc, "Это следующая строка");
      PrintLnHDC_scroll(hdc, "Это следующая строка");
      PrintLnHDC_scroll(hdc, "Это следующая строка");
      PrintLnHDC_scroll(hdc, "Это следующая строка");
      PrintLnHDC_scroll(hdc, "Это следующая строка");
      PrintLnHDC_scroll(hdc, "Это следующая строка");
      PrintLnHDC_scroll(hdc, "Это следующая строка");
      PrintLnHDC_scroll(hdc, "Это следующая строка");
      PrintLnHDC_scroll(hdc, "Это следующая строка");
      PrintLnHDC_scroll(hdc, "Это следующая строка");
      PrintLnHDC_scroll(hdc, "Это следующая строка");
      PrintLnHDC_scroll(hdc, "Это следующая строка");
      PrintLnHDC_scroll(hdc, "Это следующая строка");
      PrintLnHDC_scroll(hdc, "Это следующая строка");
      PrintLnHDC_scroll(hdc, "А это последняя строка");
      EndPaint(hwnd, &ps);
      return 0;
    }
    // Обеспечиваем управление полосой просмотра
    // при помощи клавиатуры
    case WM_KEYDOWN:
    {
      // В зависимости от кода клавиши функция окна
      // посылает сама себе сообщения, которые
      // обычно генерируются полосой просмотра
      switch (wParam)
      {
  case VK_HOME:
  {
    SendMessage(hwnd, WM_VSCROLL, SB_TOP, 0L);
          break;
        }
  case VK_END:
  {
    SendMessage(hwnd, WM_VSCROLL, SB_BOTTOM, 0L);
          break;
        }
  case VK_UP:
  {
    SendMessage(hwnd, WM_VSCROLL, SB_LINEUP, 0L);
          break;
        }
  case VK_DOWN:
  {
    SendMessage(hwnd, WM_VSCROLL, SB_LINEDOWN, 0L);
          break;
        }
  case VK_PRIOR:
  {
    SendMessage(hwnd, WM_VSCROLL, SB_PAGEUP, 0L);
          break;
        }
  case VK_NEXT:
  {
    SendMessage(hwnd, WM_VSCROLL, SB_PAGEDOWN, 0L);
          break;
        }
      }
      return 0;
    }
    case WM_DESTROY:
    {
      PostQuitMessage(0);
      return 0;
    }
  }
  return DefWindowProc(hwnd, msg, wParam, lParam);
}
// "Удержание" позиции прокрутки в "правильных" границах
void  vnormalize( void )
{
  if(nVScrollPos < 0) nVScrollPos = 0;
  if( nVScrollPos > nMaxVScrolled ) nVScrollPos = nMaxVScrolled;
  return;
};
void WINAPI PrintHDC_scroll( HDC hdc, const char *str )
{
  char buf[MAXTEXTBUFFSIZE];
  char temp[MAXTEXTBUFFSIZE];
  int i, y;
// Обрезание входной строки при превышении её длины.
// Это нужно, чтобы не было ошибки "переполнение буфера"
  memset( temp, '\0', MAXTEXTBUFFSIZE );
  strncpy( temp, str, MAXTEXTBUFFSIZE );
  // Вычисляем начальную позицию для вывода
  y = nyCurPos + cyChar * (- nVScrollPos);
  // Подготавливаем в рабочем буфере
  // и выводим в окно, начиная с текущей
  // позиции название параметра
  sprintf(buf, "%s", temp);
  i = strlen(temp);
  TextOut(hdc,
    nxCurPos, y, buf, i);
  // Увеличиваем текущую позицию по
  // горизонтали на ширину символа
  nxCurPos += cxChar*i;
  return;
}
void WINAPI PrintLnHDC_scroll( HDC hdc, const char *str )
{
  char buf[MAXTEXTBUFFSIZE], temp[MAXTEXTBUFFSIZE];
  int i, y;
// Обрезание входной строки при превышении её длины.
// Это нужно, чтобы не было ошибки "переполнение буфера"
  memset( temp, '\0', MAXTEXTBUFFSIZE );
  strncpy( temp, str, MAXTEXTBUFFSIZE );
  // Вычисляем начальную позицию для вывода
  y = nyCurPos + cyChar * (- nVScrollPos);
  // Подготавливаем в рабочем буфере
  // и выводим в окно, начиная с текущей
  // позиции название параметра
  sprintf(buf, "%s", temp);
  i = strlen(temp);
  TextOut(hdc,
    nxCurPos, y, buf, i);
  // Увеличиваем текущую позицию по
  // вертикали на высоту символа
  // и переносим начало вывода на новую строку
  nyCurPos += cyChar;
  nxCurPos = cxChar/2;
  return;
}
void WINAPI PrintInitHDC_scroll( )
{
  /* Данная функция инициализирует позиции вывода текста при
     обработке прерывания WM_PAINT перед собственно выводом
     текста */
  nyCurPos = 0; // Начальная позиция вывода в окно 0
  nxCurPos = cxChar / 2; // Начальная горизонтальная позиция -- половина от ширины символа
  return;
}
/* Файл описания модулей VIEW0000.DEF */
; =====================================
; Файл определения модуля VIEW0000
; =====================================
NAME        VIEW0000
DESCRIPTION 'Приложение VIEW0000, (C) 2010, yudenisov'
EXETYPE     windows
STUB        'winstub.exe'
STACKSIZE   5120
HEAPSIZE    4096
CODE        preload moveable discardable
DATA        preload moveable multiple
Листинг .

Замечания к текстам примеров

Вывод текста в рабочую область экрана осуществляется только при обработке прерывания WM_PAINT. Собственно перед выводом текста необходимо провести так называемую "инициализацию окна", вызвав функцию: PrintInitHDC_scroll без параметров. Эта функция устанавливает начальную позицию вывода в рабочей области окна при её перерисовке (отступ сверху - 1 интервал, отступ слева - 0,5 от ширины литеры указанного шрифта). Это необходимо сделать, поскольку при каждом приёме сообщения WM_PAINT окно перерисовывается заново.

Затем идут собственно функции вывода текста в окно PrintHDC_scroll и PrintLnHDC_scroll.

Первая из этих функций выводит текст в окно без перехода на другую строку. Новый вывод будет производиться в той же строке, начальная позиция которой будет равна "числу напечатанных символов" * "максимальную ширину литера шрифта". В Бейсике это эквивалентно командам:

TAB("позиция"); PRINT "строка";

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

Обработка нажатия клавиш на клавиатуре и изменение позиции вертикальной полосы просмотра происходит отдельно от вывода текста. При этом изменяются только специальные переменные, содержащие дополнительную информацию для вывода текста. После этого происходит вызов сообщения: "WM_PAINT", и текст выводится в рабочую область окна. Остальные функции и параметры претерпели мало изменений относительно тех функций, описанных в приложениях №№ III - VI "Ввод-вывод с использованием WinAPI" данного курса.

Дмитрий Карпов
Дмитрий Карпов
Россия, Нижний Новгород
Олег Корсак
Олег Корсак
Латвия, Рига