Шаг 59.
Библиотека OWL.
Создание плавающих панелей инструментов

    На этом шаге мы рассмотрим алгоритм создания плавающих панелей инструментов.

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

    Создание плавающей панели инструментов не очень сильно отличается от создания стандартной панели. Различие заключается лишь в том, что вместо использования окна-клиента в качестве родительского, плавающая панель инструментов имеет в качестве такового окно класса TFloatingFrame. В следующем примере показано, каким образом это делается.

#include <owl\owlpch.h>
#include <owl\applicat.h>
#include <owl\decframe.h> 
#include <owl\floatfra.h>
#include <owl\toolbox.h> 
#include <owl\buttonga.h> 
#include "pr59_1.rc"
 
// Класс приложения.
class TApp: public TApplication
{
  public:
	 TApp():TApplication() {}
	 void InitMainWindow();
};

// Класс окна-клиента.
class TCWndw : public TWindow
{
  public:
	  TCWndw (TWindow *parent, const char far *title);
  protected:
          TToolBox *toolBox;
          TFloatingFrame *toolFrame;
	  int lineWidth, lineColor;
	  BOOL button;
	  TPen *pen;
	  TClientDC *lineDC;

	  void EvLButtonUp (UINT, TPoint &point);
	  void EvLButtonDown (UINT, TPoint &point);
	  void EvMouseMove (UINT, TPoint &point);
	  LRESULT PmChangeColor (WPARAM color, LPARAM);
	  LRESULT PmChangeLine (WPARAM width, LPARAM);

	  DECLARE_RESPONSE_TABLE (TCWndw);
};

DEFINE_RESPONSE_TABLE1 (TCWndw, TWindow)
	 EV_WM_LBUTTONUP,
	 EV_WM_LBUTTONDOWN,
	 EV_WM_MOUSEMOVE,
	 EV_MESSAGE (PM_CHANGELINE, PmChangeLine),
	 EV_MESSAGE (PM_CHANGECOLOR, PmChangeColor),
END_RESPONSE_TABLE;

// Класс плавающего обрамления. 
class TFFrame : public TFloatingFrame
{
  public:
    TFFrame (TWindow *parent, char *title,
        TWindow *clienTWndw, BOOL ShrinkToCllent, 
        int CaptionHeight, BOOL EnablePalette);
  protected:
        void  SetupWindow(); 
        void  CmLine (WPARAM Id); 
        void  CmColor (WPARAM Id);

        DECLARE_RESPONSE_TABLE  (TFFrame);
};

DEFINE_RESPONSE_TABLE1 (TFFrame, TFloatingFrame)
	EV_COMMAND_AND_ID (CM_LINE1, CmLine),
	EV_COMMAND_AND_ID (CM_LINE2, CmLine),
	EV_COMMAND_AND_ID (CM_LINE3, CmLine),
	EV_COMMAND_AND_ID (CM_LINE4, CmLine),
	EV_COMMAND_AND_ID (CM_BLACK, CmColor),
	EV_COMMAND_AND_ID (CM_BLUE,  CmColor),
	EV_COMMAND_AND_ID (CM_RED,   CmColor),
	EV_COMMAND_AND_ID (CM_GREEN, CmColor),
	EV_COMMAND_AND_ID (CM_YELLOW,CmColor),
	EV_COMMAND_AND_ID (CM_PURPLE,CmColor),
END_RESPONSE_TABLE;

//***********************************
// Реализация окна TCWndw.
//***********************************
// TCWndw::TCWndw()
// Это конструктор окна-клиента.
TCWndw::TCWndw (TWindow *parent, const char far *title):
		  TWindow (parent, title)
{
  TButtonGadget *b; 
  TSeparatorGadget *s;

  // Создать новый объект панели инструментов.
  toolBox = new TToolBox (0);
  // Добавить приспособления в панель инструментов. 
  b = new TButtonGadget (BMP_LINE1, CM_LINE1, TButtonGadget::Exclusive,
        TRUE, TButtonGadget::Down); 
  toolBox->Insert (*b);
  b = new TButtonGadget (BMP_LINE2, CM_LINE2, TButtonGadget::Exclusive,
        TRUE, TButtonGadget::Up); 
  toolBox->Insert (*b);
  b = new TButtonGadget (BMP_LINE3, CM_LINE3, TButtonGadget::Exclusive,
        TRUE, TButtonGadget::Up); 
  toolBox->Insert (*b);
  b = new TButtonGadget (BMP_LINE4, CM_LINE4, TButtonGadget::Exclusive,
		  TRUE, TButtonGadget::Up);
  toolBox->Insert (*b);

  s = new TSeparatorGadget (10);
  toolBox->Insert (*s);
  s = new TSeparatorGadget (10);
  toolBox->Insert (*s);

  b = new TButtonGadget (BMP_BLACK, CM_BLACK, TButtonGadget::Exclusive,
		 TRUE, TButtonGadget::Down);
  toolBox->Insert (*b);
  b = new TButtonGadget (BMP_BLUE, CM_BLUE, TButtonGadget::Exclusive,
		 TRUE, TButtonGadget::Up);
  toolBox->Insert (*b);
  b = new TButtonGadget (BMP_RED, CM_RED, TButtonGadget::Exclusive,
		 TRUE, TButtonGadget::Up);
  toolBox->Insert (*b);
  b = new TButtonGadget (BMP_GREEN, CM_GREEN, TButtonGadget::Exclusive,
		 TRUE, TButtonGadget::Up);
  toolBox->Insert (*b);
  b = new TButtonGadget (BMP_YELLOW, CM_YELLOW, TButtonGadget::Exclusive,
		 TRUE, TButtonGadget::Up);
  toolBox->Insert (*b);
  b = new TButtonGadget (BMP_PURPLE, CM_PURPLE, TButtonGadget::Exclusive,
		 TRUE, TButtonGadget::Up);
  toolBox->Insert (*b);
  // Построить плавающее окно обрамления с 
  // панелью инструметов как окно-клиент.
  toolFrame = new TFFrame (this, "Toolbox", toolBox, TRUE, 
       TFloatingFrame::DefaultCaptionHeight, TRUE);
  // Инициализировать переменные.
  lineWidth = 1;
  lineColor = 0;
  button = FALSE;
  pen = NULL;
  lineDC = NULL;
}

// TCWndw::EvLButtonDown()
// Эта функция обрабатывает сообщения WM_LBUTTONDOWN,
// которые Windows посылает приложению, когда
// пользователь производит нажатие левой кнопки
// мыши внутри окна пользователя.
void TCWndw::EvLButtonDown (UINT, TPoint &point)
{
  // Если это новое нажатие кнопки...
  if (!button)
  {
	 // Получить контекст устройства и стандартное перо.
	 lineDC = new TClientDC (HWindow);
	 pen = new TPen (lineColor, lineWidth, PS_SOLID);
	 lineDC->SelectObject (*pen);
	 // Направить все вводимое мышью в наше окно.
	 SetCapture();
	 // Привязать начало линии к координатам курсора мыши.
	 lineDC->MoveTo (point);
	 // Установить флаг кнопки мыши.
	 button = TRUE;
  }
}

// TCWndw::EvLButtonUp()
// Эта функция обрабатывает сообщения WM_LBUTTONUP,
// которые Windows посылает приложению, когда
// пользователь отпускает левую кнопку мыши.
void TCWndw::EvLButtonUp (UINT, TPoint&)
{
  // Освободить контекст устройства.
  delete lineDC;
  lineDC = NULL;
  // Удалить объект стандартного пера.
  delete pen;
  // Выключить флаг кнопки мыши.
  button = FALSE;
  // Освободить захват мыши для дальшейшей работы.
  ReleaseCapture();
}

// TCWndw::EvMouseMove()
// Эта функция обрабатывает сообщения WM_MOUSEMOVE,
// которые Windows посыпает приложению, когда
// пользователь перемещает мыщь.
void TCWndw::EvMouseMove (UINT, TPoint &point)
{
  // Если нажата левая кнопка мыши, то чертить
  // линию к новой точке.
  if (button) lineDC->LineTo (point);
}

// TCWndw::PmChangeColor()
// Эта функция реагирует на определяемое пользователем
// сообщение PM_CHANGECOLOR установкой нового цвета линии.
LRESULT TCWndw::PmChangeColor (WPARAM color, LPARAM)
{
  lineColor = color;
  return 1;
}

// TCWndw::PmChangeLine()
// Эта функция реагирует на определяемое пользователем
// сообщение PM_CHANGELINE установкой новой толщины линии.
LRESULT TCWndw::PmChangeLine (WPARAM width, LPARAM)
{
  lineWidth = width;
  return 1;
}

//***********************************
// Реализация класса TFFrame.
//***********************************
// TFFrame::TFFrame()
// Это конструктор плавающего окна обрамления.
TFFrame::TFFrame (TWindow *parent, char *title, TWindow *clienTWndw, 
     BOOL shrinkToClient, int captionHeight, BOOL enablePalette) : 
     TFloatingFrame (parent, title, clienTWndw, shrinkToClient, 
        captionHeight,enablePalette)
{
}

// TFFrame::SetupWindow()
// Эта функция, переопределяющая функцию
// базового класса SetupWindow(), обеспечивает
// завершение установки плавающего окна обрамления.
void TFFrame::SetupWindow() 
{
  // Должна быть вызвана функция SetupWindow()
  // базового класса.
  TFloatingFrame::SetupWindow();
  // Обеспечить появление плавающего 
  // обрамленного окна в приемлемом 
  // месте на экране. 
  TRect rect;
  Parent->GetWindowRect(rect); 
  SetWindowPos (0, rect.left+20, rect.top+20, 
    0, 0, SWP_NOSIZE | SWP_NOZORDER);
}

// TFFrame::CmLine()
// Эта функция реагирует на любые кнопки установки
// параметров линии панели инструментов, направляя
// сообщение в окно-клиент. Сообщение информирует окно-клиент о том, что
// толщина  пера должна быть изменена.
void TFFrame::CmLine (WPARAM Id)
{
  Parent->PostMessage (PM_CHANGELINE, Id-100, 0);
}

// TFFrame::CmColor()
// Эта функция реагирует на любую кнопку изменения
// цвета в панели инструментов, направляя сообщение
// в окно-клиент. Сообщение информирует окно-клиент,
// что цвет пера должен быть изменен.
void TFFrame::CmColor (WPARAM Id)
{
  Parent->PostMessage (PM_CHANGECOLOR, Id-200, 0);
}

//***********************************
// Реализация класса TApp.
//***********************************
// TApp::InitMainWindow()
// Эта функция создает главное окно приложения.
void TApp::InitMainWindow()
{
  // Создать окно-клиент.
  TWindow *client = new TCWndw (0, 0);
  // Создать окно обрамления.
  TDecoratedFrame *frame = new TDecoratedFrame 
     (0, "Плавающая панель инструментов", client);
  // Добавить меню к главному окну. 
  frame->AssignMenu (MENU_1);
  // Определить расположение и размеры главного окна.
  frame->Attr.X = 50;
  frame->Attr.Y = 50;
  frame->Attr.W = GetSystemMetrics (SM_CXSCREEN) / 1.5;
  frame->Attr.H = GetSystemMetrics (SM_CYSCREEN) / 1.5;
  // Установить главным окном окно обрамления. 
  SetMainWindow(frame);
} 

int OwlMain(int,char *[])
{
  return TApp().Run();
}

    Файл ресурсов:

#ifndef WORKSHOP_INVOKED
#include "windows.h"
#endif

#define MENU_1      100 
#define CM_EXIT     24310
#define BMP_LINE1   1
#define BMP_LINE2   2
#define BMP_LINE3   3
#define BMP_LINE4   4
#define BMP_BLACK   5
#define BMP_BLUE    6
#define BMP_RED     7
#define BMP_GREEN   8
#define BMP_YELLOW  9
#define BMP_PURPLE  10
#define CM_LINE1    101
#define CM_LINE2    102
#define CM_LINE3    103
#define CM_LINE4    105
#define CM_BLACK    200
#define CM_BLUE     204
#define CM_RED      201
#define CM_GREEN    202
#define CM_YELLOW   203
#define CM_PURPLE   205
#define PM_CHANGECOLOR  WM_USER
#define PM_CHANGELINE   WM_USER+1

#ifdef RC_INVOKED


MENU_1 MENU
{
  POPUP "&File"
  {
	  MENUITEM "E&xit", CM_EXIT
  }
}

BMP_LINE1  BITMAP "line1.bmp" 
BMP_LINE2  BITMAP "line2.bmp" 
BMP_LINE3  BITMAP "line3.bmp" 
BMP_LINE4  BITMAP "line4.bmp" 
BMP_BLUE   BITMAP "blue.bmp" 
BMP_BLACK  BITMAP "black.bmp" 
BMP_RED    BITMAP "red.bmp" 
BMP_GREEN  BITMAP "green.bmp" 
BMP_YELLOW BITMAP "yellow.bmp" 
BMP_PURPLE BITMAP "purple.bmp"

#endif
Текст этого приложения вместе с дополнительными файлами можно взять здесь.

    Когда вы запустите эту программу, вы увидите окно, приведенное на рисунке 1.


Рис.1. Результат работы приложения

    Теперь панель инструментов находится внутри плавающего окна, которое вы можете двигать по всему экрану. Панель инструментов может выходить за пределы окна-хозяина. Независимо от того, куда вы помещаете панель инструментов, все управляющие элементы работают точно так же, как и в стационарной версии панели инструментов. Отличительная особенность этой программы состоит в том, что, как и в предыдущей версии, здесь отсутствует функция Paint(); поэтому настоящее окно не может перерисовать свою рабочую область. Это означает, что если вы помещаете панель инструментов поверх нарисованного, то изображение не восстанавливается, когда панель инструментов перемещается с этого места.

    Хотя в этой программе и добавляется окно TFloatingFrame, в то же время используется только два оконных класса. Это объясняется тем, что программа теперь может использовать в качестве окна обрамления окно общего вида TDecoratedFrame, а вся работа с кнопками панели инструментов передается новому окну TFloatingFrame, которое и содержит эту панель инструментов. К тому же, работа по созданию панели инструментов была передана классу окна-клиента TCWndw. За исключением этих дополнительных обязанностей класс окна-клиента изменился очень мало. Он все еще управляет всем рисованием и отвечает на сообщения пользователя PM_CHANGECOLOR и PM_CHANGELINE.

class TCWndw : public TWindow
{
  public:
	  TCWndw (TWindow *parent, const char far *title);
  protected:
          TToolBox *toolBox;
          TFloatingFrame *toolFrame;
	  int lineWidth, lineColor;
	  BOOL button;
	  TPen *pen;
	  TClientDC *lineDC;

	  void EvLButtonUp (UINT, TPoint &point);
	  void EvLButtonDown (UINT, TPoint &point);
	  void EvMouseMove (UINT, TPoint &point);
	  LRESULT PmChangeColor (WPARAM color, LPARAM);
	  LRESULT PmChangeLine (WPARAM width, LPARAM);

	  DECLARE_RESPONSE_TABLE (TCWndw);
};

DEFINE_RESPONSE_TABLE1 (TCWndw, TWindow)
	 EV_WM_LBUTTONUP,
	 EV_WM_LBUTTONDOWN,
	 EV_WM_MOUSEMOVE,
	 EV_MESSAGE (PM_CHANGELINE, PmChangeLine),
	 EV_MESSAGE (PM_CHANGECOLOR, PmChangeColor),
END_RESPONSE_TABLE;

    Как видно из объявления класса, взаимодействие пользователя с панелью инструментов теперь управляется новым классом TFFrame, производным от OWL-класса TFloatingFrame, что имеет смысл, поскольку это новое плавающее окно является владельцем панели инструментов:

class TFFrame : public TFloatingFrame
{
  public:
    TFFrame (TWindow *parent, char *title,
        TWindow *clienTWndw, BOOL ShrinkToCllent, 
        int CaptionHeight, BOOL EnablePalette);
  protected:
        void  SetupWindow(); 
        void  CmLine (WPARAM Id); 
        void  CmColor (WPARAM Id);

        DECLARE_RESPONSE_TABLE  (TFFrame);
};

DEFINE_RESPONSE_TABLE1 (TFFrame, TFloatingFrame)
	EV_COMMAND_AND_ID (CM_LINE1, CmLine),
	EV_COMMAND_AND_ID (CM_LINE2, CmLine),
	EV_COMMAND_AND_ID (CM_LINE3, CmLine),
	EV_COMMAND_AND_ID (CM_LINE4, CmLine),
	EV_COMMAND_AND_ID (CM_BLACK, CmColor),
	EV_COMMAND_AND_ID (CM_BLUE,  CmColor),
	EV_COMMAND_AND_ID (CM_RED,   CmColor),
	EV_COMMAND_AND_ID (CM_GREEN, CmColor),
	EV_COMMAND_AND_ID (CM_YELLOW,CmColor),
	EV_COMMAND_AND_ID (CM_PURPLE,CmColor),
END_RESPONSE_TABLE;

    Помимо своего конструктора, этот класс перенял функции CmLine() и CmColor(), а также добавил свою собственную функцию SetupWindow(). Функции CmColor() и CmLine() работают точно так же, как и в предыдущей версии программы, посылая сообщения PM_CHANGECOLOR и PM_CHANGELINE, когда пользователь нажимает кнопки на панели инструментов.

    Как и в большинстве объектно-ориентированных программ, оконные классы в программе организованы в иерархию, причем главное окно находится на вершине иерархии, за ним следует окно-клиент, а затем - плавающее окно обрамления, которое ответственно за панель инструментов. Теперь главное окно этой программы является окном общего вида TDecoratedFrame, и поэтому создается в функции приложения InitMainWindow():

void TApp::InitMainWindow()
{
  // Создать окно-клиент.
  TWindow *client = new TCWndw (0, 0);
  // Создать окно обрамления.
  TDecoratedFrame *frame = new TDecoratedFrame 
     (0, "Плавающая панель инструментов", client);
  // Добавить меню к главному окну. 
  frame->AssignMenu (MENU_1);
  // Определить расположение и размеры главного окна.
  frame->Attr.X = 50;
  frame->Attr.Y = 50;
  frame->Attr.W = GetSystemMetrics (SM_CXSCREEN) / 1.5;
  frame->Attr.H = GetSystemMetrics (SM_CYSCREEN) / 1.5;
  // Установить главным окном окно обрамления. 
  SetMainWindow(frame);
} 

    Чтобы создать главное окно, программа сначала должна создать окно-клиент главного окна. Хотя при вызове конструктора окна-клиента в качестве указателя на родительское окно передается "ноль", вызов конструктора окна обрамления, которому передается новое окно-клиент в качестве параметра, установит значение члена-данного Parent этого окна-клиента. Так как в этой программе окно обрамления не имеет собственного конструктора, то InitMainWindow() устанавливает меню окна, его размер и расположение, используя указатель этого окна для доступа к этим полям.

    На самом деле именно окно-клиент строит панель инструментов в своем конструкторе. Сначала программа строит новую пустую панель инструментов:

  toolBox = new TToolBox (0);

    Так как родительское окно панели инструментов не было создано, программа в качестве указателя на родителя панели инструменов передает ноль. После создания панели инструментов программа добавляет приспособления точно так же, как и в первой версии этой программы. На этот раз, однако, после установки приспособлений, панель инструментов не добавляется к родительскому окну, а используется как окно-клиент для окна TFFrame:

  toolFrame = new TFFrame (this, "Toolbox", toolBox, TRUE, 
       TFloatingFrame::DefaultCaptionHeight, TRUE);

    Затем вызывается конструктор нового плавающего окна TFFrame:

TFFrame::TFFrame (TWindow *parent, char *title, TWindow *clienTWndw, 
     BOOL shrinkToClient, int captionHeight, BOOL enablePalette) : 
     TFloatingFrame (parent, title, clienTWndw, shrinkToClient, 
        captionHeight,enablePalette)
{
}

    Так как класс TFFrame является производным от OWL-класса TFloatingFrame, то его конструктор должен вызвать конструктор TFloatingFrame с соответствующими аргументами. Этими аргументами являются указатель на родительское окно, заголовок окна, указатель на окно-клиент, булево значение, которое указывает, должно ли сжаться окно до размеров окна пользователя, высота строки заголовка окна и булево значение, которое указывает, должно ли окно быть создано в виде плавающей палитры. Плавающая палитра - это окно TFloatingFrame с небольшим заголовком и границей окна, но без кнопок максимизации и минимизации. Она не имеет системного меню, но снабжена закрывающей кнопкой, которая закрывает палитру в ответ на однократное нажатие.

    Так как плавающее окно обрамления может всплывать в любом месте экрана, вам надо настроить его координаты так, чтобы панель инструментов появлялась поверх главного окна приложения. Это можно проделать в функции плавающего обрамления SetupWindow():

void TFFrame::SetupWindow() 
{
  // Должна быть вызвана функция SetupWindow()
  // базового класса.
  TFloatingFrame::SetupWindow();
  // Обеспечить появление плавающего 
  // обрамленного окна в приемлемом 
  // месте на экране. 
  TRect rect;
  Parent->GetWindowRect(rect); 
  SetWindowPos (0, rect.left+20, rect.top+20, 
    0, 0, SWP_NOSIZE | SWP_NOZORDER);
}

    Здесь программа сначала вызывает функцию SetupWindow() базового класса. Затем обращение к OWL-версии GetWindowRect() из Windows получает координаты на экране главного окна. Эти координаты, которые возвращаются в объект TRect, затем используются в вызове функции SetWindowPos() для размещения окна. Функция SetWindowPos() имеет шесть аргументов, которыми являются: дескриптор окна, после которого должно быть упорядочено это окно, координаты X и Y окна, ширина и высота окна, и целое беззнаковое число, содержащее битовые флаги, указывающие, как интерпретировать значения размеров и положения окна. Флаг SWP_NOSIZE указывает признак Windows игнорировать данную ширину и высоту, а флаг SWP_NOZORDER указывает Windows игнорировать первый параметр (не меняет порядок окон).

    Со следующего шага мы начнем знакомиться с классом Clipboard.




Предыдущий шаг Содержание Следующий шаг