Лекции по С++. Часть 2

1  Программирование в Windows

1.1  Создание приложений. Функция OwlMain

Любая программа, использующая библиотеку Object Windows Library 2.x (OWL), начинается с первого оператора внутри функии OwlMain. Типичная функция OwlMain выглядит следующим образом:

int
OwlMain(int argc, char *argv[])
{
  TApplication app("Appname"); //Создание объекта приложения
  return app.Run();            //Запуск приложения
}
OwlMain выполняет две основные обязанности: конструирует объект приложения класса TApplication (или производного от него класса) и вызывает функцию - элемент Run этого объекта. OwlMain получает 2 аргумента argc и argv - аналоги аргументов в функции main для языка C. Параметр argc - число принятых аргументов - по крайней мере один, так как argv[0] адресует строку с маршрутным именем программы. В Borland C++ любая функция может получить доступ к аргументам программы с помощью глобальных переменных argc и argv. Так как эти переменные доступны во всех частях программы, нет основания передавать параметры OwlMain в качестве аргументов другим функциям.

Пример на использование глобальных переменных argc и argv.

#include <owl\applicat.h>
#include <owl\framewin.h>
class TArgsApp:public TApplication { //класс приложение
public:
     TArgsApp(const char far *title):TApplication(title){};
     void InitMainWindow(); //создание основного окна
                            //приложения
};
void TArgsApp::InitMainWindow()
{
     char far* title = "[Untitled]";
     if (_argc > 1)  title = _argv[1];
     //создание основного окна приложения
     MainWindow = new TFrameWindow(0,title);
}
int OwlMain (int argc,char * argv[])
{
    TArgsApp app(argv[0]); //Создание объекта приложения
    return app.Run(); // вызов функции-члена для запуска
                      // приложения
}
В приведенном выше примере заголовок главного окна программы будет иметь значение имени файла, который был указан в качестве входного параметра при запуске программы или значение Üntitled" - если входные параметры не были указаны.

1.2  Создание и инициализация объекта приложения

В функции OwlMain создается объект приложения, который является экземпляром класса TApplication (или, что чаще, производного от него), определенного в OWL.

Класс TApplication инкапсулирует основные функции и данные приложения. TApplication производится от TModule и вводит дополнительные компоненты, необходимые только для приложений Windows. Класс TApplication объявляет два конструктора, простейшим из которых является следующий:

    TApplication(const char far* name=0,
                 TModule*& gModule=::Module);
Параметры конструктора: name - строка, задающая имя приложения (может быть опущен); gModule - ссылается на модyль приложения (может быть опущен).

Так как оба параметра конструктора специфицированы по умолчанию, то можно создавать и безымянный объект:

    TApplication app.

Альтернативная форма конструктора выглядит следующим образом:

TApplication(const char far* name,
   HINSTANCE hInstance,    //дискриптор текущего экземпляра
                           // программы
   HINSTANCE hPrevInstance,  //дискриптор предыдущего экз.
                             //программы
   const char far * cmdLine, //не анализированная командная
                             //строка
   int    cmdShow,           //отображать окно нормально или
                             //в минимизированном состоянии
   TModule*& gModule=::Module);
Альтернативный конструктор введён для совместимости с ранними версиями OWL, где использовалась вместо OwlMain функция WinMain , имеющая аналогичные аргументы.

Вызов конструктора TApplication активирует три основные виртуальные функции-члены, которые можно переопределить, для того чтобы включиться в стартовую фазу программы. Эти функции вызываются автоматически - в своём приложении нет их явного вызова; их нужно переопределить в производном классе от TApplication. Вызов этих функций неявно осуществляет OWL. Вот эти функции, в порядке их вызова:

        virtual void InitApplication();
        virtual void InitInstance();
        virtual void InitMainWindow();
Owl вызывает InitApplication() только для первого представителя программы. Она используется для выполнения глобальных инициализаций, требующихся один и только один раз, независимо от того сколько копий программы запущено в операционной системе Windows. InitApplication() вызывается перед вызовом InitInstance().

InitInstance() вызывается для каждого представителя программы, выполняет локальную инициализацию и другие действия, необходимые для каждого запуска программы.

Owl вызывает InitMainWindow() для создания объекта главного окна приложения. InitMainWindow() вызывается только после успешных вызовов InitApplication() и InitInstance().

Чтобы выяснить, является ли приложение первым экземпляром или одним из последующих, нужно в InitInstance() проверить дескриптор hPrevInstance, наследуемый от TApplication.

Пример:

  ......
  if (hPrevInstance == 0)
        //код для первого представителя
  else
        //код для последующего представителя
  ......
Вне производного от TApplication класса дескриптор hPrevInstance не доступен непосредственно. Найти этот дескриптор можно следующим образом:

  .....
  //получить указатель на объект приложение
  TApplication *app = GetApplicationObject();

  if ( app->hPrevInstance == 0 )
  //Функция Windows API отображающая диалог с сообщением
       MessageBox(app->GetName(),"First Instance");
  else
       MessageBox(app->GetName(),"Subsequent Instance");
  .....
GetName() - это функция-член класса TApplication, которая возвращает строку, переданную конструктору TApplication, т.е. имя приложения.

В InitInstance() обычно загружают ресурс таблицы акселераторов, назначающей функциональные клавиши программы.

Пример. Загрузка таблицы акселераторов.

void TMyApp::InitInstance() // переопределение функции
{
  TApplication::InitInstance(); //вызвать функцию родителя
  HAccTable = LoadAccelerators(ID_Accel);//загрузить таблицу
}
В первой строке переопределенной функции TMyApp::InitInstance() вызывается аналогичная функция родительского класса для того, чтобы выполнить основную работу по инициализации приложения.

HAccTable - элемент данных типа HACCEL (дискриптор ресурса таблицы), объявлен в TApplication.

ID Accel - идентификатор ресурса таблицы акселераторов (константа определенная в программе).

Приведем полный текст программы, позволяющей пояснить, как происходит инициализация Windows - приложения при одновременном запуске нескольких копий одной и той же программы. Текст состоит из трех различных файлов, включенных в один проект. В данном примере будет определена таблица акселераторов такая, что при нажатии комбинации клавиш Alt+x или Alt+X запущенное приложение прекратит работу.

Работает данная программа следующим образом. В функции OwlMain создается экземпляр класса TStartApp и, следовательно, вызывается конструктор данного класса. В конструкторе
TStartApp::TStartApp(...) создается объект string, который инициализируется строкой Öther Instance". Указатель на созданный объект сохраняется во внутренней переменной title класса TStartApp. При первом запуске программы будет вызвана переопределенная функция InitApplication() класса TStartApp, в которой содержимое объекта string (на который указывет title) изменяется на значение "First Instance". В функции InitMainWindow() создается объект главного окна приложения, текст заголовка которого устанавливается значением строки title:

  MainWindow = new TFrameWindow(0,title->c_str()).

Таким образом, при первом запуске программы заголовок окна будет иметь значение "First Instance". При последующих запусках программы вызовов функции InitApplication() не будет, позтому у всех последующих запущенных экземпляров данной программы окна будут именоваться Öther Instance". Пример:

//------------------------------------------------------
//APPSTART.RH -- Заголовочный файл для файла ресурсов
//------------------------------------------------------

#define ID_ACCEL 100 // Определение идентификатора таблицы
                     // акселераторов
//---------------------------------------------------------
//APPSTART.RC -- Файл ресурсов
//---------------------------------------------------------
#include <owl\window.rh> // заголовки ресурсов Windows
#include "appstart.rh"

ID_ACCEL ACCELERATORS  //Определение таблицы акселераторов
{
  "x",CM_EXIT,ASCII,ALT
  "X",CM_EXIT,ASCII,ALT
}
//---------------------------------------------------------
//APPSTART.CPP -- исходный текст программы
//---------------------------------------------------------
#include <owl\applicat.h>
#include <owl\framewin.h>
#include <cstring.h> //заголовок для класса string
#pragma hdrstop  // указание компилятору, что с этого места
//должны перекомпилироваться заголовочные файлы

#include "appstart.rh"

//Определене класса приложения
class TStartApp:public TApplication {
public:
  TStartApp(const char far *name); //конструктор
  ~TStartApp();                    //деструктор
  void InitApplication();
  void InitInstance();
  void InitMainWindow();
private:
  string *title;                  //заголовок окна
};
//реализация конструктора
TStartApp::TStartApp(const char far * name)
      :TApplication(name)
{
 //Инициализация указателя на строку
 title = new string("Other Instance");
}
//реализация деструктора
TStartApp:: ~TStartApp() { delete title; }

void TStartApp::InitApplication()
{
  TApplication::InitApplication();
  *title = "First Instance";
  EnableBWCC(); //Использовать стиль кнопок Borland
  Ctl3dEnabled();//Трехмерный вид окон
}

void TStartApp::InitInstance()
{
  //максимизировать окно на весь экран
  nCmdShow = SW_SHOWMAXIMIZED;
  //Вызов функции родительского класса
  TApplication::InitInstance();
  //Загрузка таблицы акселераторов
  HAccTable = LoadAccelerators(ID_ACCEL);
}

void TStartApp::InitMainWindow()
{
  //создание и инициализация главного окна приложения
  MainWindow = new TFrameWindow(0,title->c_str());
}

#pragma argsused  //подавление предупреждения о 
                  //неиспользуемых параметрах
int OwlMain(int argc, char *argv[])
{
 //создание объекта прикладная программа
 TStartApp app("AppStart");
 //запуск приложения
 return app.Run();
}
В данном примере таблица акселераторов находится в файле ресурсов приложения (*.RC). В файл ресурсов обычно помещают ресурсы приложения: таблицы акселераторов, ресурсы оконных диалогов и меню, иконки, таблицы строк и др. Файл ресурсов должен быть включен в общий проект, тогда он автоматически компилируется компилятором ресурсов и присоединяется к исполняемому модулю. Создается файл ресурсов с помощью любого текстового редактора или с помощью Borland Resource WorkShop (workshop.exe - приложение поставляемого с IDE Borland C++).

2  Главное окно приложения

Класс приложение (производный от TApplication) перекрывает функцию InitMainWindow, конструирующую объект главного окна. У приложения только одно окно может быть главным. В приведенных ранее примерах в качестве главного окна приложения создавался объект типа TFrameWindow. Конструктор класса TFrameWindow в качесте первого входного параметра принимает указатель на родительское окно. Окно родителя контролирует дочерние окна. При закрытии родительского окна автоматически закрываются все его дочерние окна. Главное окно приложения не имеет родителя, поэтому в предыдущих примерах в качестве первого аргумента конструктору TFrameWindow передавался 0. Обычно для придания главному окну приложения большей функциональности приходится порождать новый класс главного окна приложения от класса TFrameWindow.

2.1  Конструирование объекта окна

Для конструирования объекта окна необходимо произвести новый класс от TFrameWindow. Как минимум, производный класс должен объявлять конструктор, в котором размещаются инициализирующие операторы.

Пример:

//объявление класса главного окна
class TMyWin:public TFrameWindow{
 public:
    // конструктор окна
    TMyWin(TWindow *parent, const char *title);
    ....
};
//реализация конструктора с предварительным вызовом
//конструкторов базовых классов
TMyWin::TMyWin(TWindow* parent, const char far* title):
        TFrameWindow(parent,title),TWindow(parent,title)
{
   //Инициализация объекта окна
   Attr.X = GetSystemMetrics(SM_CXSCREEN)/8; //левый верхнй
   Attr.Y = GetSystemMetrics(SM_CYSCREEN)/8; //угол окна
   Attr.H = Attr.Y*6;  //высота окна
   Attr.W = Attr.X*6;  //ширина окна
   ....
}
Класс TFrameWindow является виртуальным производным от TWindow, поэтому конструктор TMyWin вызывает как конструктор TFrameWindow (непосредственного предка), так и конструктор виртуального базового класса TWindow.

Вслед за конструированием объекта окна OWL вызывает виртуальную функцию-член класса окна типа void - SetupWindow(), наследуемую от TFrameWindow, которую используют для дополнения инициализирующих действий при создании объектов окон (например для инициализации разверток окна). Первоначальные размеры главного окна устанавливыаются путем изменения соответствующих полей структуры Attr, наследуемой от TFrameWindow. В приведенном выше примере главное окно всегда располагается по центру экрана и занимает 6/8 его площади вне завсимости от разрешения монитора. Достигается это за счет использования функции Windows API GetSystemMetrics(...).

2.2  Закрытие объекта окна

Все производные от класса TWindow наследуют виртуальную функцию CanClose, которая определяет можно ли закрыть объект окна.

В классе TMyWin, определенном в предыдушем разделе, нужно перекрыть унаследованную функцию BOOL CanClose(). Если функция возвращает FALSE, то окно не может быть закрыто, в противном случае окно закрывается и вызывается деструктор окна.

Пример: реализация функции CanClose.

BOOL TMyWin::CanClose()
{
  if (condition) return FALSE; else  return TRUE;
}

2.3  Характеристики окна

Для изменения характеристик окна необходимо внести изменения в один из параметров класса окна, зарегистрированного в Windows классом OWL, выведенным из TWindow.

В Windows для определения параметров окна определена структура wndClass типа WNDCLASS, которая передается функции TWindow::GetWindowClass. Для того, чтобы изменения были зарегистрированны в операционной системе, необходимо переопределить функцию GetClassName(), возвращающую уникальное имя оконного класса.

Пример: Применение GetWindowClass и GetClassName для создания окна со светлосерым фоном.

//Определение класса окна со светлосерым фоном
class TGrayWin : public TFrameWindow {
public:
 TGrayWin(TWindow* parent, const char far* title) :
      TFrameWindow(parent,title) {}
protected:
 virtual void GetWindowClass(WNDCLASS& Class);
 virtual char far* GetClassName() { return "TGrayWin"; }
};
 ...

void
TGrayWin::GetWindowClass(WNDCLASS& wndClass)
{
 //вызов функции базового класса
 TFrameWindow::GetWindowClass(wndClass);
 //установка новых значений атрибутам окна
 wndClass.hbrBackground = 
           (HBRUSH) GetStockObject(LTGRAY_BRUSH);
}

2.4  Таблицы отклика

Обычно, последним компонентом оконного класса, производного из TFrameWindow или TWindow, является макрос следующего вида:

            DECLARE_RESPONSE_TABLE(TAnyWin)
TAnyWin - имя оконного класса, который объявляет одну или более функций-членов отклика на сообщения Windows. Макрос определяет свой собственный статус доступа и поэтому может размещаться после любого из спецификаторов доступа public, protected или private. Операционная система Windows управляет приложениями путем посылки сообщений (нажатий клавиш, перемещения мыши и т.п.) главным окнам этих приложений. Макрос вводит в класс несколько частных и публичных элементов, которые позволяют таблице отклика вызывать функции-члены оконного класса в ответ на сообщения Windows.

Таблица отклика - таблица идентификаторов сообщений Windows и адресов функций-членов оконного класса. Когда окно получает сообщение от Windows, оно автоматически вызывает соответствующую функцию-элемент.

Для реализации таблицы отклика используется другой макрос следующего вида:

    DEFINE_RESPONSE_TABLEx (TAnyWin,...)
    ...
    END_RESPONSE_TABLE;
Реализация таблицы отклика должна находится в исходном файле, а не в заголовке, и не может появлятся внутри функции, класса или любого другого объявления.

Символ x в DEFINE RESPONSE TABLEx заменяется на число базовых классов в списке наследования, которые также имеют таблицы отклика.

Пример:

  class Base1 : public TFrameWindow {
  .....
    DECLARE_RESPONSE_TABLE(Base1);
  };
  class Base2 {...};
  clase Base3 {...};
  ...
    DECLARE_RESPONSE_TABLE(Base3);
  };
  clase Derived : public Base1, public Base2, public Base3 {
    public:
      ...
    protected:
      ...
    private:
      DECLARE_RESPONSE_TABLE(Derived);
  };
В данном примере только 2 базовых класса (Base1, Base3) имеют таблицы отклика. Поэтому реализация таблицы отклика для класса Derived выглядит следующим образом:

  DEFINE_RESPONSE_TABLE2(Derived,Base1,Base3)
  ...
  END_RESPONSE_TABLE;
Внутри таблицы отклика находятся другие макросы, задающие связь определенных значений сообщений с функциями-членами. Приведем пример реализации таблицы отклика на сообщения.

DEFINE_RESPONSE_TABLE2(Derived,Base1,Base3)
  EV_COMMAND(CM_DEMO_TEST, CmDemoTest),
  EV_WM_SIZE,    // вызов функции отклика по умолчанию
                // void EvSize(UINT sizeType,TSize&)
  EV_COMMAND(ID_OK_BUTTON,OK Button Response),
  EV_WM_TIMER,  //вызов по умолчанию EvTimer(UINT timerID)
  EV_WM_LBUTTONDBLCLK, //событие по двойному щелчку левой
                       //кнопки мыши
  EV_LBN_DBLCLK(ID_LISTBOX,SelectAnItem),   //уведомляющее
               //сообщение панели списка о выборе элемента
               // (определено в WINDOWEV.H)
END_RESPONSE_TABLE;
В данном примере используются макросы без параметров такие, как EV WM SIZE и EV WM TIMER. Это - макросы "дробилки" сообщений, определенные в OWL. Для них по умолчанию определены функции отклика на сообщение Windows, которые должны быть определены в оконном классе. Для установления правильного соответствия не следует изменять типы, имена и описания входных параметров этих функций.

2.5  Добавление скроллинга к окну

Для увеличения рабочей области окна можно использовать развертки.

Пусть определен класс окна, тогда для добавления разверток к окну, внесем изменения в конструктор класса.

  TMyWin::TMyWin(TWindow* parent, const char far* title):
         TFrameWindow(parent,title),TWindow(parent,title)
   {
      ....
      //Изменить поле стиль аттрибутов окна -- установить
      //у окна вертикальную и горизонтальную прокрутку
      Attr.Style |= WS_VSCROLL | WS_HSCROLL;
      //Создать объект скроллер размером 640X480
      Scroller = new TScroller(this,10,   10,   64,    48);
      ....                        //XUnit,YUnit,XRange,YRange
    };
Scroller - наследуемый указатель на объект типа TScroller.

TScroller содержит значения, которые определяют размер прокручиваемой области окна. Эти значения хранятся в компонентах данных класса TScroller:

XUnit - шаг (единица перемещения) по X;

YUnit - шаг (единица перемещения) по Y;

XRange - максимальное расстояние, которое ползунок может пройти от одного конца развертки до другого (по X);

YRange - максимальное расстояние, которое ползунок может пройти от одного конца развертки до другого (по Y);

XLine, YLine - число единиц на которое нужно переместить ползунок при смещении на 1 строку (по X или Y) (выражаются в шагах развертки);

XPage, YPage - число единиц перемещения на страницу (выражаются в шагах развертки);

XPos, YPos - относительное положение ползунка в диапазоне заданного перемещения;

1  Функции-члены TScroller и примеры их использования

Приведем примеры использования инкапсулированных функций объекта TScroller.
Y = Scroller->YPos;  // получить позицию вертикального
                       // ползунка
Scroller->SetUnits(16,8);  // изменить XUnit, YUnit
Scroller->SetUnits(Scroller->XUnit,8);//изменить только XUnit
Scroller->XPage = 8; //вряд ли потребуется, так как SetUnit
//автоматически постраивает размеры через вызов SetPageSize
Scroller->SetRange(documentWidth, documentHeight);
        //построить развертку
Scroller->ScrollTo(0,0);  //вернуть окно в верхнее положение
Scroller->ScrollTo(0,Scroller->YRange);//развернуть документ
                                       //в конце
Scroller->ScrollBy(0,-1);  //Моделирование операции развертки
                           //вверх
// ScrollBy вызывает ScrollTo для перемещения документа 
//(в данном случае на 1 шаг вверх с текущей позиции). 
//Обычно используется для реализации развёртки с 
// использованием клавиатуры

2  Автопрокрутка и отслеживание местоположения

Объект TScroller по умолчанию обладает возможностью автопрокрутки, она может быть выключена установкой

   Scroller->AutoMode = FALSE;
и прокрутка может осуществлятсятолько с помощью линеек прокрутки.

Отслеживание местоположения - другая автоматическая функция развёртки. Обычно, когда двигается кнопка ползунка, автоматически развёртывается содержимое окна в соответствии с перемещением мыши. В некоторых ситуациях (когда происходит "заикание развёртки") отслеживание лучше отключить:

   Scroller->TrackMode = FALSE;

2.6  Отрисовка графики в окне

Windows - многозадачная операционная система с графическим интерфейсом пользователя. Для перерисовки содержимого окна Windows посылает окну, требующему перерисовку, сообщение
WM PAINT. Сообщение WM PAINT посылается окну, когда программа создает и отображает новое окно, когда освобождается недействующая область окна, закрытая ранее поверхлежащим окном, или когда минимизированное окно востанавливается в нормальном размере. Поэтому каждое окно Windows-приложения должно уметь востанавливать (перерисовывать) свое содержимое при получении сообщения WM PAINT от операционной системы. Принцип - "вначале запомни, потом нарисуй" - является одним из ключевых механизмов программирования управляемых событиями графических приложений. Это означает, что все что отображается в окне, должно храниться в предварительно определенных структурах и отрисовываться в окне по мере необходимости.

Класс TWindow объявляет виртуальную функцию-член Paint, которую нужно использовать для обновления содержимого окна. Функция Paint является откликом на сообщение WM PAIN и должна быть переопределена в производном оконном класса. Заметим, что функция Paint является не прямым откликом на сообщение WM PAINT, а вызывается из функции EvPaint, определенной в TWindow, с подготовленными для нее входными параметрами. Поэтому для функции Paint не требутся определять связь с сообщением WM PAINT в таблице откликов. Формат функции Paint имеет следуюющий вид:

  void Paint(TDC& dc, BOOL erase, TRect& rect)  ,

где

dc - ссылка на контекст устройства оконного объекта;
erase - флажок, показыващий необходимость очистки (закраски цветом окна) перерисовываемой области;
rect - обрамляющий прямоугольник, включающий в себя все области окна, подлежащие перерисовке.

Функция Paint вводит новое понятие - класса контекста устройства (Device Context), инкапсулирующего функции интерфейса графического устройства Windows (GDI, graphics device interface). Функции GDI Windows действуют независимо от аппаратуры вывода - это главная характеристика графики не зависящей от устройства.

Таким образом, для обновления содержимого окна, необходимо заменять в оконном классе унаследованную функцию Paint и реализовывать в ней те операторы рисования, которые требуются.

Приведем пример программы, в которой демонстрируются следующие возможности.

  1. Подключение к основному окну программы меню. Для этого в файле заголовков ресурсов (Shapes.RH) определяется идентификатор ресурса меню. В файле ресурсов (Shapes.RC) определяется ресурс меню. В файле (Shapes.CPP) для подключения меню к оконному классу в конструкторе класса TShapeWin:: TShapeWin() вызывается унаследованная функция:
        AssignMenu(ID_MENU);//Назначить меню для окна
    

  2. Cоздание иерархии классов фигур от абстрактного базового класса TShape и использование класса шаблона
    TIArrayAsVector < ... > из библиотеки CLASSLIB, разработанной фирмой Borland. Шаблон TIArrayAsVector применяется для конструирования действительного класса-массива, способного хранить указатели на объекты типа TShape. В оконном класса TShapeWin объявляется контейнер shapes, как частный элемент данных:
      TIArrayAsVector<TShape> shapes;
    

    а инициализация контейнера осуществляется в конструкторе TShapeWin::TShapeWin(), после вызова конструкторов базовых классов. Для добавления нового указателя на TShape к контейнеру в функции TShapeWin::SetupWindow() используется метод shape.Add. Используется контейнер как и любой массив в языке C. Отметим, что для cоздания класса-массива по шаблону TIArrayAsVector < ... > необходимо определить оператор сравнения в классе TShape.

  3. Добавление линеек прокрутки к окну и настройку скроллеров при изменении размеров окна. В начальный момент времени, когда окно отображено на экране скроллеров у окна нет. При уменьшении размеров у окна появятся скроллеры, с помощью которых можно скроллировать невидимую часть окна. Настройка скроллеров производится в функции
    TShapeWin::AdjustScrollers().

  4. Реализацию функций отклика на сообщения поступающие окну от манипулятора мышь. Функции отклика на нажатие, перемещение и отпуск клавиши мыши получают в качестве входного параметра ссылку на объект TPoint, в котором хранятся координаты положения указателя мыши, вычисленные относительно клиентской области окна, без учета положения ползунков скроллера. Объект TScroller доступный через наследуемый указатель Scroller автоматически корректирует (согласно положению ползунков) координаты вывода графических объектов при их отрисовке в окне. Поэтому в функциях отклика требуется учитывать положение ползунков скроллера при определении областей перерисовки в окне.

  5. Ну и наконец, отрисовку графических объектов (элипсов и прямоугольников) с помощью функций контекста устройства окна. В приведенном ниже примере иллюстрируется техника "закулисной" перерисовки (или перерисовки в памяти). Отрисованные в окне объекты могут перекрывать друг друга. Нажав на любом объекте в окне и удерживая в нажатом положении левую кнопку мыши, при перемещении мыши выбранный объект плавно переместится вслед за изменением положения указателя мыши, не портя изображения "под собой" и "над собой" других объектов. Можно было бы использовать альтернативную технику отрисовки. А именно: хранить для каждого объекта в памяти изображение закрытое объектом. При передвижении объекта вначале востановить закрытое изображение, потом сохранить изображение с другого места и наконец вывести на это место сам объект. Однако, это потребовало бы во-первых, большего расхода памяти, а во-вторых - плавного перемещения объекта не получилось бы. Дело в том, что при такой технике существует момент времени, когда перемещаемого объекта нет в окне. Именно это и фиксируется челевеческим глазом. В итоге возникает заметное мерцание перемещаемого объекта. В приведенном ниже примере этот эффект отсутствует благодаря тому, что вначале определяется общая область (старое положение и новое положение объекта), нуждающаяся в перерисовке. Потом, в памяти создается Bitmap требуемого размера, на котором отрисовываются все объекты, пересекающиеся с этой областью. И только после этого общая картина отрисовывается в окне.

Изучение более подробно деталей реализации предлагается читателю самостоятельно.

Пример отрисовки графических объектов.

//------------------------------------------------------------
//  Файл Shapes.rh  - файл заголовков ресурсов
//------------------------------------------------------------
#define ID_ACCEL  100 // Идентификатор ресурса таблицы отклика
#define ID_MENU   101 // Идентификатор ресурса меню
//------------------------------------------------------------
//  Файл Shapes.rc - файл ресурсов
//------------------------------------------------------------
#include <owl\window.rh> // заголовки ресурсов Windows
#include "shapes.rh"

ID_ACCEL ACCELERATORS    //Определение таблицы акселераторов
{
  "x",CM_EXIT,ASCII,ALT
  "X",CM_EXIT,ASCII,ALT
}

ID_MENU MENU //Определение меню
BEGIN
  POPUP "&Demo"
  BEGIN
     MENUITEM "E&xit", CM_EXIT
  END
END
//------------------------------------------------------------
//  Файл Shapes.cpp
//------------------------------------------------------------
#include <owl\applicat.h>
#include <owl\framewin.h>
#include <owl\dc.h>
#include <owl\scroller.h>
//Для использования контейнерных классов
#include <classlib\arrays.h>
#include <stdlib.h>
#pragma hdrstop

#include "shapes.rh"

//максимальное число объектов
const int MAX_SHAPES = 100;
//Перемещение по X для скролирования
const int X_STEP = 10;
//Перемещение по Y для скролирования
const int Y_STEP = 10;

//Абстрактный базовый класс для объектов
class TShape {
private:
    int  w, //ширина объекта
         h, //высота объека
         sizePen,//толщина контура
         x,//текущая координата по X
         y;//текущая координата по Y
    TColor ColorPen,//цвет контура
           ColorBrush;//цвет внутренней области
public:
  //конструктор
  TShape(int aw, int ah, int szPen = 1,
    TColor clPen = TColor::Black, TColor clBrush = TColor::White)
  {
     w = aw; h = ah; sizePen = szPen; ColorPen = clPen;
     ColorBrush = clBrush; x = y = 0;
  }
  //функции-члены для доступа к данным
  int GetX() {return x;}
  int GetY() {return y;}
  int GetW() {return w;}
  int GetH() {return h;}
  //функции-члены для инициализации данных
  void SetX(int X) {x = X;}
  void SetY(int Y) {y = Y;}
  void SetW(int W) {w = W;}
  void SetH(int H) {h = H;}
  //Нарисовать объект
  void Show(int ax, int ay, TDC& dc);
  //чистые функции - необходимо переопределить в 
    //производных классах
  virtual void Draw(TDC& dc) = 0;
  //Принадлежит ли точка объекту?
  virtual BOOL InArea(TPoint pt) = 0;
  //Получит обрамляющий прямоугольник
  TRect GetRect(void){TRect rt(x-sizePen,y-sizePen,
      x+w+sizePen,y+h+sizePen);    return rt;}
  //Переместить объект
  void Move(TPoint pt) {x = pt.x; y = pt.y;}
  //Оператор сравнения, необходим для 
  //контейнера TIArrayAsVector<TShape>
  int operator==(const TShape& sh) const
       { return (*this == sh) ? 1:0; }
};

void TShape::Show(int ax, int ay, TDC& dc)//Нарисовать объект
{
  TPen   pen(ColorPen, sizePen);//создать объект перо
  TBrush br(ColorBrush);//Создать объект кисть
  dc.SelectObject(pen);//Выбрать перо в контекст устройства
  dc.SelectObject(br);//Выбрать кисть в контекст устройства
  //Учесть смещение объекта относительно области перерисовки
  x -= ax; y -= ay;
  Draw(dc);//Вызвать переопределенный метод отрисовки
  //Востановить положение объека в памяти
  x += ax; y += ay;
  dc.RestorePen();//Востановить перо в контексте
  dc.RestoreBrush();//Востановить кисть
}

//Прямоугольник
class TRectangle : public TShape {
public:
 TRectangle(int aw = 10, int ah = 10, int szPen = 1,
 TColor clPen=TColor::Black, TColor clBrush = TColor::White):
   TShape(aw, ah, szPen, clPen, clBrush) {}
  virtual void Draw(TDC& dc);//Переопределена
  virtual BOOL InArea(TPoint pt); //Переопределена
};

void TRectangle::Draw(TDC &dc)//Нарисовать прямоугольник
{
  TSize sz;
  TPoint pt;
  //Начальная точка
  pt.x = GetX(); pt.y = GetY();
  //Приращение
  sz.cx = GetW(); sz.cy = GetH();
  //Отрисовка прямоуголика
  dc.Rectangle(pt, sz);
}

BOOL TRectangle::InArea(TPoint pt)//Принадлежность точки
{
  //Получить обрамляющий прямоугольник
  TRect rt = GetRect();
  //Принадлежит ли точка прямоугольнику
  return rt.Contains(pt);
}

//В общем случае Эллипс
class TCircle : public TShape {
public:
  TCircle(int aw = 10, int ah = 10, int szPen = 1,
   TColor clPen = TColor::Black, TColor clBrush = TColor::White):
   TShape(aw, ah, szPen, clPen, clBrush) {}
  virtual void Draw(TDC& dc);
  virtual BOOL InArea(TPoint pt);
};

void TCircle::Draw(TDC &dc)
{
  TSize sz;
  TPoint pt;
  pt.x = GetX(); pt.y = GetY();
  sz.cx = GetW(); sz.cy = GetH();
  //Нарисовать эллипс
  dc.Ellipse(pt, sz);
}

BOOL TCircle::InArea(TPoint pt)
{
  TRect rt = GetRect();
  return rt.Contains(pt);
}

//Класс основного окно приложения
class TShapeWin : public TFrameWindow {
private:
//Шаблон косвенного контейнерного класса описывает массив
//shapes для хранения указателей на TShape
  TIArrayAsVector<TShape> shapes;
  TPoint   LastPoint;  //Предыдущая позиция указателя мыши
  int      ActiveNumber; //Номер активного объекта
  BOOL     Drawing;  //Флаг для отрисовки при перемещении мыши
  int      Width,Height; //Размеры рабочей области
  void AdjustScrollers(); //Настройка линеек прокрутки
public:
  //Конструктор
  TShapeWin(TWindow* parent, const char far* title);
  //Вызывается после конструктора для инициализации
  void SetupWindow();
  //Обработчик события WM_PAINT
  void Paint(TDC& dc, BOOL erase, TRect& rect);
  //Изменение размеров окна
  void EvSize(UINT sizeType, TSize& size);
  //Нажатие левой кнопки мыши
  void EvLButtonDown(UINT modKeys, TPoint& point);
  //Перемещение мыши
  void EvMouseMove (UINT modKeys, TPoint& point);
  //Отпуск левой кнопки мыши
  void EvLButtonUp (UINT modKeys, TPoint& point);
  //Таблица отклика
  DECLARE_RESPONSE_TABLE(TShapeWin);
};
// Реализация таблицы отклика на сообщения
DEFINE_RESPONSE_TABLE1(TShapeWin,TFrameWindow)
  EV_WM_LBUTTONDOWN,
  EV_WM_MOUSEMOVE,
  EV_WM_LBUTTONUP,
  EV_WM_SIZE,
END_RESPONSE_TABLE;

//Конструктор оконного класса вызывает конструкторы базовых 
//классов и инициализирует массив указателей shapes(a,b,c),
// где  a - начальное число элементов в массиве
//   b - начальный номер индексации в массиве
//   c - кол-во элементов для приращения массива
TShapeWin::TShapeWin(TWindow* parent,const char far* title)
  :TFrameWindow(parent, title), TWindow(parent,title), 
                                        shapes(10,0,10)
{
    AssignMenu(ID_MENU);//Назначить меню для окна
    // установить координаты и размеры окна
    Attr.X = GetSystemMetrics(SM_CXSCREEN)/8;
    Attr.Y = GetSystemMetrics(SM_CYSCREEN)/8;
    Attr.H = Attr.Y * 6;
    Attr.W = Attr.X * 6;
    //Добавить скролеры к окну
    Attr.Style |= WS_VSCROLL | WS_HSCROLL;
    //Создание объекта TScroller
    Scroller = new TScroller(this, 0, 0, 0, 0);
    //Инициализация переменных
    ActiveNumber = -1;
    Drawing  = 0;
}
//Дополнительная инициализация оконного объекта
void TShapeWin::SetupWindow()
{
  //Вызвать функцию наследуемого класса
  TFrameWindow::SetupWindow();

  TRect rt;
  TShape* sh;
  //Получить и сохранить размера клиентскй области окна
  rt = GetClientRect();
  Width  = rt.Width();
  Height = rt.Height();
  //создание набора графических объектов
  randomize();
  for (int i = 0; i < MAX_SHAPES; i++) {
   if (random(2))
    sh = new TRectangle(
        random(rt.right/10),
        random(rt.bottom/10),
        3,
        TColor(random(256),random(256),random(256)),
        TColor(random(256),random(256),random(256)));
   else sh = new TCircle(
        random(rt.right/10),
        random(rt.bottom/10),
        3,
        TColor(random(256),random(256),random(256)),
        TColor(random(256),random(256),random(256)));

   sh->SetX(random(rt.right - sh->GetW()));
   sh->SetY(random(rt.bottom - sh->GetH()));
   //Добавить к массиву указателей
   shapes.Add(sh);
  }
}

//Функция настройки скроллеров
void TShapeWin::AdjustScrollers()
{
  TRect  clientRect = GetClientRect();

  BOOL Fit = (clientRect.Width()  >= Width) &&
             (clientRect.Height() >= Height);

  Scroller->SetUnits(X_STEP, Y_STEP);
  if (Fit)
     Scroller->SetRange(0, 0);
  else {
     TPoint Range(Max( (Width-clientRect.Width()  )/X_STEP, 0),
                  Max( (Height-clientRect.Height())/Y_STEP, 0));
     Scroller->SetRange(Range.x, Range.y);
  }
  SetScrollPos(SB_HORZ, (int)Scroller->XPos, TRUE);
  SetScrollPos(SB_VERT, (int)Scroller->YPos, TRUE);
}

//Изменение размеров окна
void TShapeWin::EvSize(UINT sizeType, TSize& size)
{
 TFrameWindow::EvSize(sizeType, size);
 if(!IsIconic()) { //Если не минимизировано
  AdjustScrollers();
  //Требование обновить все окно, не очищая предварительно
  //его содержимое (параметр - FALSE)
  Invalidate(FALSE);
 }
}
//При нажатии на левую кнопку мыши в области окна вызывается
//функция EvLButtonDown(UINT modKeys, TPoint& point), где
//   modKeys - удерживаемая при нажатии клавиша
//   point   - координаты указателя мыши относительно 
//            клиентской области окна
void TShapeWin::EvLButtonDown(UINT /*modKeys*/, TPoint& point)
{
 TPoint pt = point;
 //Значение pt - в координатах окна, поэтому добавим смещение с
 //учетом положения скроллеров
 pt.x += (int)Scroller->XPos*Scroller->XUnit;
 pt.y += (int)Scroller->YPos*Scroller->YUnit;

 ActiveNumber = -1;
 Drawing = 0;
 //Определить активный объект с учетом Z-положения
 for (int i = shapes.GetItemsInContainer()-1; i>=0; i--) {
   if ( shapes[i]->InArea(pt) ) {
     LastPoint = pt; //Сохранить позицию мыши
     ActiveNumber = i;
     break;
   }
 }
 if (ActiveNumber >= 0) {
   Drawing = 1;
   //Захватить мышь, т.е. все события от мыши
   //будет обрабатывать только наше окно
   GetCapture();
 }
}
#pragma argsused //Для подавления предупреждения
                 //о неиспользуемых параметрах
//Отклик на перемещение мыши
void TShapeWin::EvMouseMove (UINT modKeys, TPoint& point)
{
 if (!Drawing) return;
 //Получить обрамляющий прямоугольник активного объекта
 TRect rt = shapes[ActiveNumber]->GetRect();
 //Учесть положение скроллеров, т.к. TScroller добавит
 //смещение автоматически, то приходится перед вызовом
 //InvalidateRect() смещать прямоугольную область
 rt.Offset(-(int)Scroller->XPos*Scroller->XUnit, 
           -(int)Scroller->YPos*Scroller->YUnit);
//Сместить прямоугольник по X и Y
//Требование обновить участок окна, без предварительной 
//закраски этой области цветом окна
 InvalidateRect(rt,FALSE);

 //Вычисление нового положения объекта в памяти
 TPoint pt= point;
 pt.x += (int)Scroller->XPos*Scroller->XUnit;
 pt.y += (int)Scroller->YPos*Scroller->YUnit;

 TPoint p1;
 p1.x = shapes[ActiveNumber]->GetX() + (pt.x-LastPoint.x);
 p1.y = shapes[ActiveNumber]->GetY() + (pt.y-LastPoint.y);
 shapes[ActiveNumber]->Move(p1); 
                //перемещение объекта в памяти
 //Требование обновления нового участка окна
 rt = shapes[ActiveNumber]->GetRect();
 rt.Offset(-(int)Scroller->XPos*Scroller->XUnit,
           -(int)Scroller->YPos*Scroller->YUnit);
 InvalidateRect(rt,FALSE);

 LastPoint = pt;
}
//Отклик на отпускание кнопки мыши
void TShapeWin::EvLButtonUp(UINT /*modKeys*/,
                            TPoint& /*point*/)
{
 if (Drawing) {
        Drawing = 0;
        ReleaseCapture();//Освободить мышь
 }
}
#pragma argsused
//Функция вызывается при получении окном сообщения WM_PAINT
// входные параметры:
// dc    - контекст устройства оконного объекта
// erase - необходимость очистки области перед отрисовкой
// rect - область, которую необходимо обновить, 
// содержащит все требуемые для обновления участки
void TShapeWin::Paint(TDC& dc, BOOL erase, TRect& rect)
{
  //Создать контекст устройства в памяти, совместимый с dc
  TMemoryDC memDC(dc);
  TRect r = rect;
//Создать Bitmap в памяти совместимый с dc
  TBitmap *bmap = new TBitmap(dc,r.right-r.left+1,
                               r.bottom-r.top+1);
  //Выбрать в memDC созданный Bitmap
  memDC.SelectObject(*bmap);
  //Выбрать в memDC кисть цвета окна
  memDC.SelectObject(TBrush(GetSysColor(COLOR_WINDOW)));
  int x = rect.left;
  int y = rect.top;
  int w = rect.right-x+1;
  int h = rect.bottom-y+1;
  //Закрасить Bitmap в memDC цветом окна
  memDC.PatBlt(0,0,w,h,PATCOPY);
//Нарисовать на Bitmapе в memDC объекты, попадающие 
//в область перерисовки
  for (int i = 0; i < shapes.GetItemsInContainer(); i++) {
    //Проверка попадания в область перерисовки
    if ( r.Touches(shapes[i]->GetRect()) )
      shapes[i]->Show(x,y,memDC); //нарисовать объект
  }
//Скопировать отрисованный в памяти кусок на окно
  dc.BitBlt(x,y,w,h,memDC,0,0,SRCCOPY);
  delete bmap; //Удалить Bitmap
}

class TShapeApp : public TApplication {
public:
  TShapeApp(const char far* name):TApplication(name){}
  void InitInstance();
  void InitMainWindow();
};

void TShapeApp::InitInstance()
{
  TApplication::InitInstance();
  //Загрузка таблицы акселераторов
  HAccTable = LoadAccelerators(ID_ACCEL);
}

void TShapeApp::InitMainWindow()
{MainWindow = new TShapeWin(0,"Пример отрисовки графики");}

#pragma argsused
int OwlMain(int argc, char* argv[])
{
  TShapeApp app("Shapes");
  return app.Run();
}

3  Диалоговые окна

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

3.1  Стандартные диалоги Windows

Так как для большинства программ требуются одни и те же средства для операций таких как открытия, сохранения, печати файла, выбора цвета и т.п. в Windows имеется набор стандартных диалогов. В OWL разработан абстрактный базовый класс TCommonDialog производный от TDialog, из которого выводятся классы стандартных диалогов. Каждый стандартный диалог использует класс TData, в котором хранятся аттрибуты диалога и выбранные пользователем данные. В таблице приводятся классы стандартных диалогов и заголовочные файлы.

Table 1: Классы стандартных диалогов


Имя класса Базовый класс Файл заголовка Назначение
TCommonDialog TDialog COMMDIAL.H Базовый класс
TChooseColorDialog TCommonDialog CHOOSECO.H Выбор цвета
TChooseFontDialog TCommonDialog CHHOSEFO.H Выбор шрифта
TPrintDialog TCommonDialog PRINTDIA.H Печать
TOpenSaveDialog TCommonDialog OPENSAVE.H Базовый класс для TFileOpenDialog и TFileSaveDialog
TFindReplaceDialog TCommonDialog FINDREPL.H Базовый класс для TFindDialog и TReplaceDialog
TFileOpenDialog TOpenSaveDialog OPENSAVE.H Выбор существующего файла
TFileSaveDialog TOpenSaveDialog OPENSAVE.H Сохранение файла
TFindDialog TFindReplaceDialog FINDREPL.H Не модальный диалог поиска
TReplaceDialog TFindReplaceDialog FINDREPL.H Не модальный диалог поиска/замены

Приведем пример работы со стандартными диалогами. Изучая данный пример, обратите внимание на следующее.

  1. Инициализацию данных для диалоговых окон в конструкторе TDialogsWin и функции SetupWindow().
  2. Использование класса TFilterValidator в функции CmInputNumber() для корректности ввода числового значения.
  3. Подключение в файле русурсов dial.rc заголвков и ресурсов для диалога ввода TInputDialog.
  4. Вызов метода Execute() для объекта, наследуемого от TDialog, отображает панель диалога, если объект сконструирован правильно, активизирует и возвращает IDOK, если выбрана кнопка OK. Объект диалоговой панели закрывается и удаляется так же функцией Execute(), поэтому не нужно освобождать пямять выделенную оператором new.

Пример стандартных диалогов.

//-----------------------------------------------
// dial.rh - Заголовки ресурсов
//-----------------------------------------------
#include <owl\window.rh>
#define MENU_1 1
#define CM_INPUT_STRING 105
#define CM_INPUT_NUMBER 106
#define CM_INPUT_DIALOG 105
#define CM_CHOOSE_FONT  104
#define CM_CHOOSE_COLOR 103
#define CM_FILE_OPEN    101
#define CM_FILE_SAVE    102
//-----------------------------------------------
// DIAL.RC - Файл ресурсов
//-----------------------------------------------
#include <owl\window.rh> // заголовки ресурсов Windows
//Заголовки и ресурсы для диалогов ввода
#include <owl\inputdia.rh>
#include <owl\inputdia.rc>

#include "dial.rh"
MENU_1 MENU
{
 POPUP "&Dialogs"
 {
  MENUITEM "File &Oрen",    CM_FILE_OPEN
  MENUITEM "File &Save",    CM_FILE_SAVE
  MENUITEM "&Choose Color", CM_CHOOSE_COLOR
  MENUITEM "Choose &Font",  CM_CHOOSE_FONT
  MENUITEM SEPARATOR
  MENUITEM "Input &String", CM_INPUT_STRING
  MENUITEM "Input &Number", CM_INPUT_NUMBER
  MENUITEM SEPARATOR
  MENUITEM "&Exit",         CM_EXIT
 }
}
//------------------------------------------------------------
//  Файл dial.cpp
//------------------------------------------------------------
#include <owl\applicat.h>
#include <owl\framewin.h>
#include <owl\dc.h>
#include <owl\opensave.h>
#include <owl\chooseco.h>
#include <owl\choosefo.h>
#include <owl\inputdia.h>
#include <owl\validate.h>

#include <cstring.h>
#include <string.h>
#include <windows.h>

#pragma hdrstop
#include "dial.rh"

//Класс основного окно приложения
class TDialogsWin : public TFrameWindow {
private:
  //Данные для диалога файлов
  TOpenSaveDialog::TData fileOpenData;
  TColor ColorBox; //Цвет
  TFont* Font;     //Шрифт
  TColor FontColor;//Цвет шрифта
  //Данные для диалога шрифтов
  TChooseFontDialog::TData FontData;
  //Данные для диалога ввода
  string InputString;//Строка ввода;
  int    InputNumber;//число

  void CmFileOpen();
  void CmFileSave();
  void CmChooseColor();
  void CmChooseFont();
  void CmInputString();
  void CmInputNumber();
  BOOL CanClose();
public:
  //Конструктор
  TDialogsWin(TWindow* parent, const char far* title);
  void SetupWindow();
  ~TDialogsWin() { delete Font; } //Деструктор
  void Paint(TDC& dc, BOOL erase, TRect& rect);
  DECLARE_RESPONSE_TABLE(TDialogsWin); //Таблица отклика
};
// Реализация таблицы отклика на сообщения
DEFINE_RESPONSE_TABLE1(TDialogsWin,TFrameWindow)
 EV_COMMAND(CM_FILE_OPEN,CmFileOpen),
 EV_COMMAND(CM_FILE_SAVE,CmFileSave),
 EV_COMMAND(CM_CHOOSE_COLOR,CmChooseColor),
 EV_COMMAND(CM_CHOOSE_FONT,CmChooseFont),
 EV_COMMAND(CM_INPUT_STRING,CmInputString),
 EV_COMMAND(CM_INPUT_NUMBER,CmInputNumber),
END_RESPONSE_TABLE;

TDialogsWin::TDialogsWin(TWindow* parent, 
                           const char far* title)
      :TFrameWindow(parent, title), TWindow(parent,title),
      fileOpenData(OFN_FILEMUSTEXIST|OFN_PATHMUSTEXIST,
      "All File (*.*)|*.*|Text File (*.txt)|*.txt|",
      0,"","*"),
      ColorBox(TColor::White),FontColor(TColor::LtBlue)
{
 AssignMenu(MENU_1);
 // установить координаты и размеры окна
 Attr.X = GetSystemMetrics(SM_CXSCREEN)/8;
 Attr.Y = GetSystemMetrics(SM_CYSCREEN)/8;
 Attr.H = Attr.Y * 6;
 Attr.W = Attr.X * 6;
 InputString = "Input String";
 InputNumber = 0;
}

void TDialogsWin::SetupWindow() {
  TFrameWindow::SetupWindow();

 //Получить дескриптор контекста устройства
 HDC hdc = GetDC(HWindow);
 //Создать фонт
 Font = new TFont (
   "Arial Cyr",//facename
   -::MulDiv(20,::GetDeviceCaps(hdc,LOGPIXELSY),72),//height
   0,0,0,// width, escapement, orientation
   FW_NORMAL,// weight
   FIXED_PITCH,//pidchAndFamily
   FALSE,FALSE,FALSE,//italic,underline,strikout
   ANSI_CHARSET,//charSet
   OUT_TT_PRECIS,//outputPricision
   CLIP_DEFAULT_PRECIS,//cpilPrecision
   PROOF_QUALITY//quality
 );
 //Освободить контекст устройства
 ReleaseDC(HWindow,hdc);
 //Инициализация структуры данных для диалога выбора шрифта
 FontData.DC = 0;
 FontData.Flags = CF_EFFECTS | CF_FORCEFONTEXIST 
                             | CF_SCREENFONTS;
 FontData.Color = FontColor;
 FontData.Style = 0;
 FontData.FontType = SCREEN_FONTTYPE;
 FontData.SizeMin = 0;
 FontData.SizeMax = 0;
}

BOOL TDialogsWin::CanClose() {
 if ( MessageBox("Do you want Exit?", "Dialogs exit.",
       MB_YESNO | MB_ICONQUESTION) == IDYES ) return TRUE;
 else return FALSE;
}
//Открытие файла
void TDialogsWin::CmFileOpen() {
  if ( (new TFileOpenDialog(this,fileOpenData))->Execute()
                                             == IDOK ) {
   Invalidate(TRUE);
  }
}
//Сохранение файла
void TDialogsWin::CmFileSave() {
  if ( (new TFileSaveDialog(this,fileOpenData))->Execute() 
                                              == IDOK  )  {
  string prompt = "File ";
     prompt += fileOpenData.FileName;
     prompt += " saved!";
   MessageBox(prompt.c_str(), "Save File", MB_OK 
                                    | MB_ICONINFORMATION);
  }
}
//Выбор цвета
void TDialogsWin::CmChooseColor() {
  TChooseColorDialog::TData colors;
  //Определение добавленных пользователем цветов
  static TColor custColors[16] =
  {
    0xFFFFFFL, 0xFFFFFFL, 0xFFFFFFL, 0xFFFFFFL,
    0xFFFFFFL, 0xFFFFFFL, 0xFFFFFFL, 0xFFFFFFL,
    0xFFFFFFL, 0xFFFFFFL, 0xFFFFFFL, 0xFFFFFFL,
    0xFFFFFFL, 0xFFFFFFL, 0xFFFFFFL, 0xFFFFFFL
  };
  //Инициализация панели диалога
  colors.Flags = CC_RGBINIT;
  colors.Color = ColorBox;
  colors.CustColors = custColors;

  if ( (new TChooseColorDialog(this, colors))->Execute() 
                                               == IDOK ) {
    ColorBox = colors.Color;
    Invalidate(TRUE);
  }
}
//Выбор шрифта
void TDialogsWin::CmChooseFont() {
  if (Font) {
   //Заполнить Структуру диалога
   Font->GetObject(FontData.LogFont);
//Использовать данные из структуры для инициализации диалога
   FontData.Flags |= CF_INITTOLOGFONTSTRUCT;
   FontData.Color = FontColor;
   if (TChooseFontDialog(this, FontData).Execute()==IDOK) {
     delete Font;
     Font = new TFont(&FontData.LogFont);
     FontColor = FontData.Color;
   }
  }
  Invalidate();
}
//Ввод строки
void TDialogsWin::CmInputString() {
  char buf[100] = "";
  strcpy(buf, InputString.c_str());
  if ( TInputDialog(this, "String", "Введите строку",
    buf, sizeof(buf)).Execute() == IDOK ) {
    InputString = buf;
    Invalidate();
  }
}
//Ввод Числа
void TDialogsWin::CmInputNumber() {
  char buf[10] = "";
  //Преобразовать число в строку
  itoa(InputNumber,buf,10);
  if ( TInputDialog(this, "Number", "Введите Число",
         buf, sizeof(buf), 0,
         new TFilterValidator("0-9")).Execute() == IDOK ) {
    InputNumber = atoi(buf);
    Invalidate();
  }
}

#pragma argsused
void TDialogsWin::Paint(TDC& dc, BOOL erase, TRect& rect)
{
  TEXTMETRIC tm;
  dc.TextOut(10,10, fileOpenData.FileName);

  TBrush br(ColorBox);
  dc.SelectObject(br);
  dc.Rectangle(10,30,100,80);
  dc.RestoreBrush();

  if (Font) {
    LOGFONT logFont;
    //Заполнить структуру и напечатать имя шрифта
    if (Font->GetObject(logFont)) dc.TextOut(10,90,
                                      logFont.lfFaceName);
    //Установить цвет и выбрать шрифт
    dc.SetTextColor(FontColor);
    dc.SelectObject(*Font);
    string NumStr = "Input Number:";
    char str[10];
    //преобразовать число в строку
    itoa(InputNumber,str,10);
    NumStr += str;
    dc.TextOut(10,110, NumStr.c_str());
    //Получить метрику шрифта
    dc.GetTextMetrics(tm);
    dc.TextOut(10,110 + tm.tmHeight, InputString.c_str());
    dc.RestoreFont();
  }
}

class TDialogsApp : public TApplication {
public:
  TDialogsApp(const char far* name):TApplication(name){}
  void InitMainWindow();
};

void TDialogsApp::InitMainWindow()
{
  EnableCtl3d(TRUE);//Трехмерный вид окон
  MainWindow = new TDialogsWin(0,"Стандартные диалоги");
}

#pragma argsused
int OwlMain(int argc, char* argv[])
{
  TDialogsApp app("Dialogs");
  return app.Run();
}

В заключение отметим, что приведенные примеры программирования стандартных диалогов создают модальные диалоги, которые требуют от пользователя закрыть окно диалога до выбора других комманд или операций программы. Для выполнения модального диалога всегда вызывается функция Execute() класса TDialog. Что бы создать немодальный диалог нужно вместо вызова функции Execute() вызывать функцию Create() класса TDialog. Функция Create() создает окно диалога и, если у окна установлен атрибут WS VISIBLE, то отображает его (по умолчанию Resource WorkShop устанавливает именно этот атрибут окна). Если у окна атрибут WS VISIBLE не установлен, то после вызова Create() окно диалога будет создано, но не отображено на экране. Для отображения окна вызывается наследуемая функция ShowWindow(SW SHOW). Ответственность за разрушение объекта диалога, в этом случае, несет программист.

3.2  Программируемые диалоги. Механизмы передачи данных

Разработка собственных диалоговых панелей и реализация программного интерфейса с ними производится в несколько этапов.

1. Вначале, с помощью Resource WorkShop или путем ввода соответствующих команд в файл ресурсов программы (*.RC), создается вид диалоговой панели, содержащей различные элементы управления (кнопки, статический текст, элементы редактирования и т. п.). Причем сам диалог и элементы управления идентифицируются строкой или численным значением. Пусть для примера файл заголовка ресурсов и ресурсов диалога выглядят следующим образом.

//-----------------------------------------------------
// Файл mydlg.rh - заголовки ресурсов
//-----------------------------------------------------
#include <owl\window.rh>
#define DIALOG_1     1
#define IDC_FAM      101
#define IDC_MEN      104
#define IDC_WOMEN    105
#define IDC_LASTNAME 103
#define IDC_NAME     102
#define MENU_1       1
#define CM_PERSON    101
//-----------------------------------------------------
// Файл mydlg.rc - ресурс
//-----------------------------------------------------
#include "mydlg.rh"

MENU_1 MENU
{
 POPUP "&File"
 {
  MENUITEM "&Person", CM_PERSON
  MENUITEM SEPARATOR
  MENUITEM "&Exit", CM_EXIT
 }

}

MENU_1 ACCELERATORS
{
 "x", CM_EXIT, ASCII, ALT
}
DIALOG_1 DIALOG 6, 15, 191, 154
STYLE WS_POPUP | WS_VISIBLE | WS_CAPTION | WS_SYSMENU
CLASS "bordlg"
CAPTION "Информация о персоне"
FONT 8, "MS Sans Serif"
{
 DEFPUSHBUTTON "OK", IDOK, 12, 130, 50, 14
 PUSHBUTTON "Cancel", IDCANCEL, 125, 130, 50, 14
 LTEXT "Фамилия", -1, 17, 13, 37, 8
 LTEXT "Имя", -1, 33, 30, 20, 8
 LTEXT "Отчество", -1, 17, 48, 34, 8
 EDITTEXT IDC_FAM, 59, 13, 110, 11
 EDITTEXT IDC_NAME, 58, 30, 110, 11
 EDITTEXT IDC_LASTNAME, 58, 48, 110, 11
 CONTROL "Мужской", IDC_MEN, "BUTTON", BS_AUTORADIOBUTTON,
         22, 96, 49, 14
 CONTROL "Женский", IDC_WOMEN, "BUTTON", BS_AUTORADIOBUTTON,
         121, 96, 42, 14
 CONTROL "Пол", -1, "BorShade", BSS_GROUP | BSS_CAPTION |
          BSS_LEFT | WS_CHILD | WS_VISIBLE, 14, 76, 161, 44
 CONTROL "", -1, "BorShade", BSS_GROUP | BSS_CAPTION |
         BSS_LEFT | WS_CHILD | WS_VISIBLE, 12, 5, 164, 62
}

Панель диалога включает в себя две кнопки (OK, Cancel), три статических текста ("Фамилия","Имя","Отчество"), три элемента TEdit для ввода соответствующих значений, и два переключателя (TRadioButton) для указания пола ("мужской","женский"). Текст привенный в файлах MyDlg.rh и MyDlg.rc сгенерирован с помощью Resource WorkShop, хотя он мог бы быть создан с помощью любого текстового редактора.

2. В исходном тексте программы (файл *.CPP) описывается глобальная структура, которая определяет буфер для передачи данных управляющим элементам диалогового окна и обратно. Для нашего примера переменная PersData будет является такой структурой, объявленной следующим образом:

//Буфер для передачи данных
struct {
  char fam[50],name[50]; //Фамилия, Имя
  char lastname[50]; //Отчество
  WORD man,women; //Пол
} PersData;

Структура содержит по одной записи для каждого дочернего элемента управления диалогового окна. Каждый элемент управления требует собственного типа данных инициализации. В таблице показан формат данных инициализации для каждого типа элемента управления.

Буфер передачи обычно является глобальной или статической структурой. После закрытия диалового окна OWL автоматически считывает значения управляющих элементов в буфер передачи.

Элемент управления Тип инициализации
TButton не требуется
TStatic char[]
TEdit char[]
TListBox TListBoxData*
TComboBox TComboBoxData*
TScrollBar TScrollBarData*
TCheckBox WORD
TRadioButton WORD



3. Описывается класс диалогового окна, производного от TDialog, содержащий указатели на дочерние элементы управления. Определение класса выглядит следующим образом.

//Класс диалогового окна
class TPersonDlg : public TDialog {
        TEdit*  edFam;
        TEdit*  edName;
        TEdit*  edLastName;
        TRadioButton* rbMen;
        TRadioButton* rbWomen;
public:
        TPersonDlg(TWindow*);
};

Класс объявляет указатели только на те элементы управления, которые посылают сообщения в диалоговое окно или требуют инициализации. Реализация конструктора класса приведена ниже.

TPersonDlg::TPersonDlg(TWindow* parent):
            TDialog(parent,DIALOG_1) {
  //Создание элементов управления
  edFam  = new TEdit(this, IDC_FAM, 50);
  edName = new TEdit(this, IDC_NAME, 50);
  edName = new TEdit(this, IDC_LASTNAME, 50);
  rbMen = new TRadioButton(this,IDC_MEN, 0);
  rbWomen = new TRadioButton(this,IDC_WOMEN, 0);
  //Установка буфера передачи данных
  //для управляющих элементовй
  SetTransferBuffer(&PersData);
}

В начале конструктор динамически распределяет свои дочерние элементы управления. В конструкторе диалогового окна элементы управления должны распределятся в том же порядке, как расположены данные в структуре PersData. Если нет точного соответствия между структурой инициализации и элементами управления конструктора класса, то OWL будет инициализировать данные неправильно. После распределения элементов управления, конструктор вызывает унаследованную функцию SetTransferBuffer, передавая ей адрес структуры инициализации, для установки буфера передачи данных. Больше для передачи данных элементам управления и получения измененных данных от них, ничего не требуется. Все остальное OWL берет на себя. Полный исходный текст OWL-приложения, которое использует и инициализирует диалоговое окно, приведен ниже.

#include <owl\framewin.h>
#include <owl\dialog.h>
#include <owl\applicate.h>
#include <owl\edit.h>
#include <owl\radiobut.h>

#include "mydlg.rh"

//Буфер для передачи данных
struct {
  char fam[50]; //Фамилия
  char name[50]; //Имя
  char lastname[50]; //Отчество
  WORD man,women; //Пол
} PersData;

//Класс диалогового окна
class TPersonDlg : public TDialog {
 TEdit*  edFam;
 TEdit*  edName;
 TEdit*  edLastName;
 TRadioButton* rbMen;
 TRadioButton* rbWomen;
public:
 TPersonDlg(TWindow*);
};

TPersonDlg::TPersonDlg(TWindow* parent)
                   :TDialog(parent,DIALOG_1) {
  //Создание управляющих элементов
  edFam  = new TEdit(this, IDC_FAM, 50);
  edName = new TEdit(this, IDC_NAME, 50);
  edName = new TEdit(this, IDC_LASTNAME, 50);
  rbMen = new TRadioButton(this,IDC_MEN, 0);
  rbWomen = new TRadioButton(this,IDC_WOMEN, 0);
  //Установка буфера передачи данных в элементы
  //и из элементов управления
  SetTransferBuffer(&PersData);
}

class TPersWin : public TFrameWindow {
public:
  TPersWin (TWindow* parent, const char far* title) :
  TFrameWindow(parent, title),TWindow(parent,title) {
      AssignMenu(MENU_1);
  }
  void CmOpenDlg() { TPersonDlg(this).Execute(); }
  DECLARE_RESPONSE_TABLE(TPersWin);
};

DEFINE_RESPONSE_TABLE1(TPersWin,TFrameWindow)
 EV_COMMAND(CM_PERSON,CmOpenDlg),
END_RESPONSE_TABLE;


class TPersApp : public TApplication {
public:
  TPersApp() : TApplication("MyDlg") {}
  virtual void InitMainWindow();
};

void TPersApp::InitMainWindow() {
  EnableCtl3d(TRUE);//Трехмерный вид окон
  MainWindow = new TPersWin(NULL, "Person Dialog");
}

int OwlMain(int, char**) {
  return TPersApp().Run();
}

4  Графика в Windows

4.1  Введение в GDI

Для предоставления приложениям функциональных возможностей работы с графикой Windows имеет набор функций, называемых интерфейс с графическими устройствами (GDI, Graphics Device Interface). GDI можно рассматривать как графический пакет, предоставляемый MicroSoft, для работы с графикой в операционной системе Windows. Функции GDI дают приложению возможности рисования, не зависящие от используемого устройства представления, будь то поверхность экрана, операционная память, принтер, плоттер и т.д. Независимость от устройств достигается с помощью использования драйверов устройств, переводящих вызовы функций GDI в команды понятные использующемуся устройству вывода.

4.2  Контекст устройства (DC)

В отличие от DOS программ, Windows-программы осуществляют запись не прямо в экранную память, а в логический объект - контекст устройства (DC, Device Contecs). Контекст устройства - это виртуальная поверхность с соответствующими атрибутами ("перо", "кисть", шрифт, цветовая палитра, ...). При вызове функций GDI для рисования в DC, драйвер устройства, связанный с этим контекстом представления, преобразует действия рисования в соответствующие команды.

При разработке Windows-приложений, основанных на OWL, можно использовать не классы контекстов устройств, разработанных Borland, а напрямую обращаться к функциям Windows GDI. Однако это требует более ответственного программирования. Приведем пример использования функций GDI для работы с графикой.

Перед выводом графики в окно необходимо получить дескриптор (Handle) контекста устройства, связанного с этим окном.

    HDC hdc = GetDC(HWindow);
Функция GetDC() возвращает дискриптор контекста устройства для графического вывода, который должен передаваться функциям Windows GDI. Дескриптор - это безнаковое целое (число), которое служит уникальным идентификатором данного ресурса в операционной системе. Входной параметр HWindow - дескриптор окна, в которое будет осуществлятся графический вывод. Все оконные классы в OWL наследуют этот дескриптор от TWindow. После получения дескриптора контекста устройства, можно выводить графику в окно, используя функции Windows GDI, например:

    LineTo(hdc,30,45);
    Rectangle(hdc,100,100,200,200);
    TextOut(hdc,20,20,"Sample text",11);
После завершения вывода текста и графики необходимо освободить контекст устройства, так как контекст устройства - это общий ресурс, разделяемый всеми приложениями запущенными в Windows.

    ReleaseDC(HWindow,hdc);
В Windows 3.11 имеется только 5 элементов контекста устройства, доступных во время одного сеанса Windows (в Windows 95 это ограничение существенно ослаблено).

При использовании OWL и порождении основного окна приложения от TFrameWindow, можно обрабатывать весь графический вывод в переопределенной функции Paint, синтаксис объявления которой выглядит следующим образом.

    void Paint(TDC& dc, BOOL erase, TRect& rect);
Функция Paint не является непосредственным откликом на сообщение WM PAINT. Как уже отмечалось, OWL вызывает функцию EvPaint(), определенную в TWindow, всякий раз, когда Windows посылает сообщение WM PAINT в окно. Функция EvPaint() внутри себя получает контекст устройства и вызывает функцию Pain(...), после вызова которой освобождает полученный ресурс. Поэтому функция Pain(...) вызывается с верным DC, и в ней нет необходимости запрашивать DC; rect - ссылка на объект TRect, содержащий информацию об области экрана, которую необходимо перерисовать.

При необходимости обрабатывать сообшение WM PAINT непосредственно собственной функцией отклика можно поступить следующим образом.

  1. Определить в окне функцию EvPaint() и таблицу отклика.
      class TMyWin : TFrameWindow {
      public:
        ...
        void EvPait();
        DECLARE_RESPONSE_TABLE(TMyWin);
      };
    

  2. Добавить в реализацию таблицы отклика следующий макрос:
      DEFINE_RESPONSE_TABLE1(TMyWin,TFrameWindow)
        ...
        EV_WM_PAINT,
      END_RESPONSE_TABLE;
    

  3. Написать реализацию функции EvPaint() в следующем виде.
     void TMyWin::EvPaint() {
       //создать объект контекста устройства
       TPaintDC dc(*this)
    
     }
    

Замечание. Можно принудительно создать событие WM PAINT для оконного класса путем вызова функций UpdateWindow, Invalidate или InvalidateRect.

4.3  Устойчивый DC

Если получен дискриптор DC и изменены атрибуты в нём, то эти изменения будут потеряны при освобождении DC. Существует способ создания DC локального для окна, так что содержимое DC хранится до тех пор пока окно не будет уничтожено. Для этого необходимо изменить атрибуты оконного класса переопределив в нем функцию GetWindowClass следующим образом.

   // Предположим, что определен класс
   // TMyWindow : public TWindow {...}
   void  TMyWindow::GetWindowClass(WNDCLASS& wc)
   {
     TWindow::GetWindowClass(wc);
     wc.style |= CS_OWNDC; /
В результате окно, связанное с каждым экземпляром класса TMyWindow, имеет свой собственный локальный DC. Для получения дискриптора этого DC нужно вызвать GetDC (или BeginPaint), однако для освобождения ВС не нужно вызывать ReleaseDC (или EndPaint). Плата за удобство - 800 байт памяти под DC для каждого окна со стилем CS OWNDC.

4.4  Использование DC для графического вывода

Если графический вывод в окно выполняется не в переопределенной функции Paint, то для графического вывода необходимо выполнять следующую последовательность действий.

1. Получить DC.

2. Установить графические атрибуты.

3. Вызвать функции GDI для рисования.

4. Освободить DC.

Установка графических атрибутов включает выбор одного из шести объектов рисования в DC:

1. Перо - управляет внешним видом линий, границ прямоугольников, эллипсов, многоугольников.

2. Кисть - предоставляет образец закраски, используемый для рисования закрашенных фигур (внутренностей графических объектов).

3. Шрифт - определяет внешний вид и размер текстового вывода.

4. Палитра (массив цветов) - массив индексов, идентифицирующий каждый цвет.

5. Битовая карта - используется для рисования растровых изображений.

6. Область - сочетание прямоугольников, эллипсов и многоугольников, которые можно использовать для рисования или отсечения.

В любой момент времени DC может иметь одну копию одного типа графического объекта. Для выбора графического объекта в DC используется функция SelectObject. Для упрощения работы с графическими объектами в OWL 2.x имеются специально разработанные классы графических объектов.

4.5  Классы контекстов устройств

Для упрощения работы с DC в Borland OWL 2.x разработан набор классов контекстов устройств. Классы контекстов устройств инкапсулируют функции Windows GDI. TDC - является основным классом, на котором базируются все другие классы. В таблице приведены классы контекстов устройств.

Table 2: Классы контекстов устройств


Имя класса Базовый класс Использование
TDC нет Базовый класс для других классов контекста устройства; инкапсулирует в качестве элементов функции GDI
TIC TDC Только информационный контекст устройства не используется для вывода графики
TWindowDC TDC Обеспечивает доступ к целому окну
TScreenDC TDC Обеспечивает доступ ко всему экрану
TDesktop TDC Рисование в области клиента на "рабочем столе"
TClientDC TDC Рисование в области клиента в окне
TPaintDC TDC Рисование в ункции Paint или в ответ на сообщение
TWindowDC TDC Обеспечивает доступ к целому окну

TScreenDC TDC Обеспечивает доступ ко всему экрану
TDesktop TDC Рисование в области клиента на "рабочем столе"
TClientDC TDC Рисование в области клиента в окне
TPaintDC TDC Рисование в ункции Paint или в ответ на сообщение WM Paint.
TMemoryDC TDC Рисование в битой матрице вне экрана
TDibDC TDC Обеспечивает доступ к битовым матрицам не зависящим от устройства
TIC TDC Только информационный контекст устройства не используется для вывода графики
TWindowDC TDC Обеспечивает доступ к целому окну
TScreenDC TDC Обеспечивает доступ ко всему экрану
TDesktop TDC Рисование в области клиента на "рабочем столе"
TClientDC TDC Рисование в области клиента в окне
TPaintDC TDC Рисование в ункции Paint или в ответ на сообщение WM Paint.
TMemoryDC TDC Рисование в битой матрице вне экрана
TDibDC TDC Обеспечивает доступ к битовым матрицам не зависящим от устройства
TMeteFileDC TDC Обеспечивает доступ к контексту устройства ассоциированному с метафайлом.
TPrintDC TDC Обеспечивает доступ к принтеру.



Процесс использования для графического вывода одного из классов контекста устройства OWL включает 4 шага:

1. Получить или создать объект контекста устройства используя один из классов, приведенных в таблице.

2. Сконструировать графический объект (TPen, TBrush, ...). Выбрать его в контекст устройства с помощью функции-члена SelectObject.

3. Вызовить одну или несколько функций-членов DC для рисования.

4. Восстановить первоначальные перья, кисти и другие графические объекты, выбранные в контексте устройства.

Пример:

  void  TMyWindow:DrawRedBox()
  {
    TClientDC dc(HWindow);
    TRect rect(45,65,250,200);
    TColor color(255,0,0);
    TPen pen(color);
    dc.Pectangle(rect);
    dc.RestorePen();
  };

4.6  Средства рисования

Для выполнения рисования DC поддерживает 3 основные стредства: перо, кисть и шрифт. Для назначения атрибутов средству рисования Windows-приложение выбирает имеющееся средство или логическое в DC. Имеющееся средство - существующее, определённое в Windows. Логическое средство - созданное программой.

Имеющиеся средства рисования.

Кисти     - WHITE_BRUSH, LIGRAY_BRUSH, GRAY_BRUSH,
            DKGRAY_BRUSH, BLACK_BRUSH,
            NULL_BRUSH, HOLLOW_BRUSH
Перья     - WHITE_PEN, BLACK_PEN, NULL_PEN
Шрифты    - OEM_FIXED_FONT, ANSI_FIXED_FONT, ANSI_VAR_FONT,
            DEVICE_DEFAULD_FONT, SYSTEM_FIXED_FONT
Палитра   - DEFAULT_PALETTE
Получить дескриптор имеющего средства рисования можно с помощью функции GDI GetStockObject, например:

 HBRUSH TheBrush;
 ....
   TheBrush = GetStockObject(LIGRAY_BRUSH);
 ....
В отличии от логических средств, имеющиеся средства не должны удалятся после использования. Для удаления логических средств используется функция DeleteObject.

4.7  Логические перья

Логические перья создаются с помощью функций GDI CreatPen или CreatIndirect, например:

     ThePen = CreatPen(PS_DOT,3,RGB(0,0,210));
     ThePen = CreatPenIndirect(&AlogPen);
AlogPen - структура типа LOGPEN, определение которой приводится ниже
  typedef struct tagLOGPEN{
    WORD lopnStyle;
    POINT lopnWidth;
    DROWD lopnColor;
  }LOGPEN;

После стиля lopnStyle содержит константу, определяющую один из следующих стилей: PS SOLID, PS DASH, PS DOT, PS DASHDOT, PS DASHDOTDOT, PS NULL

поле ширины lopnWidth содержит точку, координата X которой (целое число) - ширина линии в координатах устройства, координата Y - игнорируется. Поле цвета lopnColor содержит цвет (RGB(0,0,0)), RGB(255,0,0) и так далее)

4.8  Логические кисти

Создаются с помощью функций GDI CreatHatchBrush, CreatPatternBrush, CreatDIBPatternBrush или CreatBrushIndirect, например:

  TheBrush = CreatHatchBrush(HS_VERTICAL, RGB(0,255,0));
  TheBrush = CreatBrushIndirect(&AlogBrush);
Определение структуры LOGBRUSH следующее:

  type struct tagLOGBRUSH {
    WORD lbStyle;
    POINT lbColor;
    DWORD lbHath;
  }LOGBRUSH;
Поле lbStyle содержит константу указывающую стиль:

BS DIBPATTERN - указывает, что кисть определённая шаблоном, определяется независимо от устройства побитовым отображением;

BS HATCHED - задаёт один из предопределённых штрихованных шаблонов (см.lbHath);

BS HOLLOW - задаёт пустую кисть;

BS PATTERN - использует левый верхний угол, размером 8 на 8 пикселей, побитового отображения, находящегося в данный момент в памяти;

BS SOLID - задаёт непрерывную кисть;

Поле lbColor - содержит значение цвета;

Поле lbHatch - содержит целую константу, указывающую штрихованный шаблон для кистей со стилем BS HATCHED. Если используется стиль BS DIBPATTERN, lbHatch содержит дискриптор побитового отображения.

4.9  Логический шрифт

Логический шрифт - это запрос, с помощью которого программа описывает необходимый для использования физический шрифт, определённый в Windows. Логические шрифты создаются двумя функциями GDI CreatFont или CreatFontIndirect. Определение структуры LOGFONT

  type struct tagLOGFONT {
    int lfHeight;
    int lfWifth;
    int lfEscapenement;
    int lfOrientation;
    int lfWeight;
    BYTE lfItalic;
    BYTE lfUnderline;
    BYTE lfStrikeOut;
    BYTE lfCharSet;
    BYTE lfOutPrecision;
    BYTE lfClipPrecision;
    BYTE lfQuality;
    BYTE lfPitchAndFamily;
    BYTE lfFaceName[LF_FACESIZE];
  }LOGFONT;
Описание полей структуры.

lfHeight - высота шрифта ( = 0 определяет стандартный размер).

lfWidth - ширина в единицах устройства ( = 0, сохраняется коэффициент сжатия или средняя ширина символов в шрифте).

lfEscapement - значение угла поворота текста против часовой стрелки в десятых долях градуса.

lfOrientation - аналогичный поворот для каждого символа.

lfWeight - желаемая чёткость ( можно использовать константы FW LIGTH, FW NORMAL, FW BOLD, FW DONTCARE или значение от 0 до 1000).

lfItalic, lfUnderline, lfStrikeOut - атрибуты шрифта (курсив, подчёркнутый и зачёркнутый), задаются с помощью ненулевых значений.

lfCharSet - специальный набор символов ANSI CHARSET, OEM CHARSET или SYMBOL CHARSET (OEM CHARSET - системно зависимый).

lfOutPresision - указывает, как точно шрифт, предоставляемый Windows, должен соответствовать запрошенным размеру и позиции (OUT DEFAULT PRECIS - стандартное значение).

lfClipPresicion - указывает, как усекать частично вводимые символы
(CLIP DEFAULT PRECIS - стандартное значение).

lfQuality - указывает, как точно шрифт, предоставляемый Windows, соответствует запрошенным атрибутам шрифта. В этом поле могут быть записаны DEFAULT QUALITY, DRAFI QUALITY или PROOF QUALITY. Если жирные, подчеркнутые или зачеркнутые шрифты не явлаются доступными, они синтезируются со значением PROOF QUALITY.

lfPichAndFamily - запрашивает шаг и семейство шрифта. Оно может быть результатом поразрядной операции ИЛИ ('||') константы шага и одной константы семейства.



Table 3:


Константы шага Константы семейства Основные шрифты Windows
DEFAULT PITCH FF MODERN Helv
FIXED PITCH FF ROMAN Modern
VARIABLE PITCH FF SCRIPT Roman
FF SWISS Script
FF DECORATIVE Symbol
FF PONTCARE System
Terminal
Tms Rmn

lfFaceName - строка, которая задает требуемое начертание. (Если = 0, то получится начертание базирующееся на знечении в других полях LOGFONT)

Пример: (переменная TheFont описана в классе TMyWindow)

void TMyWindow::N\MakeFont()
{
  LOGFONT MyLogFont;
  MyLogFont.lfHeigth = 0;
  MyLogFont.lfWidth = 0;
  MyLogFont.lfEscapement = 0;
  MyLogFont.lfOrientation = 0;
  MyLogFont.lfWeight = FW_NORMAL;
  MyLogFont.lfItalic = TRUE;
  MyLogFont.lfUnderline = TRUE;
  MyLogFont.lfStrikeOut = 0;
  MyLogFont.lfCharSet = ANSI_CHARSET;
  MyLogFont.lfOutPrecision = OUT_DEFAULT_PRECIS;
  MyLogFont.lfCliPrecision = VLIP_DEFAULT_PRECIS;
  MyLogFont.lfQuality = DEFAULT_QUALITY;
  MyLogFont.lfPitchAndFamily = FIXED_PITCH | FF_DONTCARE;
  strcpy(MyLogFont.lfFaceName, "Courier");
  TheFont = CreateFontIndirect(&MyLogFont);
};
Процедура GetTextExtent вычисляет размер строки текста, используя шрифт, помещённый в данное время в контекст устройства.

Пример:

Получить ширину и высоту текста.
DWORD dwSize;
WORD yHeight, xWidth;
  dwSize = GetTextExtent(hDC,"Device Independent",18);
  yHeight = HIWORD(dwSize);
  xWidth = LOWORD(dwSize);
Параметры процедуры:

1. hDC - логический номер контекста устройства.

2. LPSTR - длинный указатель на строку текста.

3. Количество символов.

Макросы HIWORD, LOWORD - определены в windows.h.

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

Пример: Получение метрики шрифта, помещённого в данный момент в устройство.

  TEXTMETRIC tm;
  GetTextMetrics(hDC,&tm);

1  Ключевые поля структуры TEXTMETRIC

tmHeight - размер символов в пикселах (если используется режим отображения MM TEXT) или в элементах выбранного режима.

tmInternalLeading - размер области для диакретических знаков (знаки ударения, умляуты и т.п.).

tmExternalLeading - интервал между строками.

Пример: Стандартный способ определения интервалов между строками.

  int yLineHeight; TEXTMETRIC tm;
  GetTextMetrics(hDC,&tm);
  yLineHeitght = tm.tmHeight = tm.tmExternalLeading;

4.10  Цветовые палитры

Обычно палитры применяются в выходных устройствах, которые могут поддерживать много цветов, но не все из них в одно и тоже время. Высококачественный дисплей может показывать большое количество цветовых оттенков, но только 256 из них одновременно. На программном уровне цвет задаётся не как значение (R, G, B), а как индекс в массиве с 256 элементами.

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

Иллюстрация сказанного.

4.11  Создание 256 цветовой палитры

Приведем пример создания собственной цветовой палитры в приложении.
#include <color.h>
#define NUMCOLOR 256    // Число цветов
.....
TPaletteEntry* palentry;  // объявить указатель,
          // где-нибудь, например в *.CPP файле
....
palentry = (TPalettEntry*) // выделить память под цвета
  new char[sizeof(TPalleteEntry)*NUMCOLOR];
....
for (i=0; i<NUMCOLOR; i++) { //определение цветов
  palentry[i].pe_Red = i;
  palentry[i].pe_Green = i;
  palentry[i].pe_Blue = i;
  palentry[i].pe_Flags = PC_RESERVED;
}

PC RESRVED - означает, что цвет подвергается частым изменениям;

PC NOCOLLAPSE - требует поместить цвет в неиспользуемую ячейку физической палитры;

PC EXPLICIT - показывает, что младшее слово цвета является индексом аппаратной палитры.

Создать логическую палитру

  TPallette* pallete = new TPallete(palentry,NUMCOLOR);
Выбрать палитру в контекст устройства

  dc.SelectObject(*pallete);
  // Отобразить входы логической палитры на цвета
  // в физической
  dc.RealizePallette();

Отрисовка с использованием созданной палитры

  TColor color(3); TRect rect (10,10,100,100);
  dc.FillRect(rect,color);
Программа может иметь одну или несколько собственных цветовых палитр. В любом случае перед отрисовкой графики необходимо установить в контекст устройства требуемую цветовую палитру.