Шаг 71.
Библиотека OWL.
Создание хранителя экрана

    На этом шаге мы рассмотрим создание хранителя экрана.

    На этом шаге вы научитесь использовать ObjectWindows для создания хранителя экрана. Рассмотренный на этом шаге пример хранителя экрана покажет вам, как выполнить следующие действия:

    Чтобы создать хранитель экрана, выполните следующее:

    После этого можно установить созданный хранитель экрана стандартным образом.

    Приведем тексты программ.

#include <owl\applicat.h>
#include <owl\framewin.h>
#include <owl\dialog.h>
#include <owl\listbox.h>
#include <owl\dc.h>
#include <string.h>
#include "pr71_1.rc"

// Класс главного окна.
class TWndw: public TFrameWindow
{
  public:
    TWndw(TWindow *parent, const char far *title, TWindow *client); 
    ~TWndw (); 
    void DoSaver();
  protected:
    TPoint mouseXY; 
    int count, cnt;
    TColor *color;

    void GetWindowClass (WNDCLASS &wndClass);
    char far *GetClassName();
    void EvLButtonDown (UINT, TPoint&); 
    void EvMButtonDown (UINT, TPoint&);
    void EvRButtonDown (UINT, TPoint&); 
    void EvMouseMove (UINT, TPoint &point);
    void EvSysCommand (UINT, TPoint&); 
    void EvActivate (UINT, BOOL, HWND); 
    void EvActivateApp (BOOL, HTASK); 
    void EvSysKeyDown (UINT, UINT, UINT); 
    void EvKeyDown (UINT, UINT, UINT);

   DECLARE_RESPONSE_TABLE (TWndw);
};

DEFINE_RESPONSE_TABLE1(TWndw,TFrameWindow)
    EV_WM_LBUTTONDOWN,
    EV_WM_MBUTTONDOWN,
    EV_WM_RBUTTONDOWN,
    EV_WM_MOUSEMOVE,
    EV_WM_SYSCOMMAND,
    EV_WM_ACTIVATE,
    EV_WM_ACTIVATEAPP,
    EV_WM_SYSKEYDOWN,
    EV_WM_KEYDOWN, 
END_RESPONSE_TABLE;

// Объявление класса приложения.
class TApp: public TApplication 
{
  public:
    TApp() : TApplication() {}
    void InitMainWindow();
  protected:
    TWndw *bubWnd;
    TDialog *dialog;

    BOOL IdleAction (long);
};


// Класс диалогового окна.
class TDlg : public  TDialog
{
  public:
    TDlg (TWindow *parent, TResId resId);
  protected:
    char LBStrg [10];
    TListBoxData lbData;
    TListBox *listbox;

    void SetupWindow();
    void CmOk();

   DECLARE_RESPONSE_TABLE (TDlg);
};

DEFINE_RESPONSE_TABLE1 (TDlg, TDialog)
  EV_COMMAND (IDOK, CmOk), 
END_RESPONSE_TABLE;

//*****************************
// Реализация класса TWndw.
//*****************************
// TWndw::TWndw()
// Это конструктор главного окна.
TWndw::TWndw (TWindow *parent, const char far *title,
  TWindow *client):TFrameWindow(parent, title, client) 
{
  // Сохранить положение указателя мыши и выключить мышь.
  GetCursorPos(mouseXY);
  ShowCursor(FALSE);
  // Установить стиль окна, расположение и размеры.
  Attr.Style = WS_POPUP;
  Attr.X = 0;
  Attr.Y = 0;
  Attr.W = GetSystemMetrics (SM_CXFULLSCREEN);
  Attr.H = GetSystemMetrics (SM_CYFULLSCREEN) +
			GetSystemMetrics (SM_CYCAPTION);
  // Прочитать максимальное количество пузырьков,
  // установленное пользователем.
  count = GetPrivateProfileInt ("Screen Saver.Bubbles", "Count", 
           1000, "CONTROL.INI");
  // Запустить генератор случайных чисел.
  randomize();
  // Инициализировать счетчик и цвет пузырька.
  cnt = 0;
  color = new TColor(random(256), random(256), random(256));
}

// TWndw::TWndw()
// Это деструктор главного окна.
TWndw::~TWndw ()
{
  // Удалить объект TColor.
  delete color;
  // Отобразить снова указатель мыши.
  ShowCursor(TRUE);
}

// TWndw::GetWindowClass()
// Эта функция устанавливает новый оконный класс.
void TWndw::GetWindowClass (WNDCLASS &wndClass)
{
  // Начать со значений, установленных по умолчанию для класса.
  TFrameWindow::GetWindowClass (wndClass);
  // Придать окну черный фон.
  wndClass.hbrBackground = (HBRUSH) GetStockObject (BLACK_BRUSH);
}

// TWndw::GetClassName()
// Эта функция возвращает имя нового класса.
char far *TWndw::GetClassName()
{
  return   "BubblesWnd";
}

// TWndw::EvMouseMove()
// Эта функция реагирует на перемещение мыши
// пользователем, в результате которого генерируется
// сообщение WM_MOUSEMOVE.
void TWndw::EvMouseMove(UINT, TPoint& point)
{
  // Если мышь находится в новом положении,
  // закрыть хранитель экрана.
  if (point != mouseXY) PostMessage(WM_CLOSE);
}

// TWndw::EvLButtonDown()
// Эта функция реагирует на нажатие пользователем
// левой кнопки мыши, в результате чего генерируется
// сообщение WM_LBUTTONDOWN.
void TWndw::EvLButtonDown(UINT, TPoint&)
{
  // Закрыть хранитель экрана.
  PostMessage(WM_CLOSE);
}

// TWndw::EvMButtonDown()
// Эта функция реагирует на нажатие пользователем
// средней кнопки мыши, в результате которого
// генерируется сообщение WM_MBUTTONDOWN.
void TWndw::EvMButtonDown(UINT, TPoint&)
{
  // Закрыть хранитель экрана.
  PostMessage(WM_CLOSE);
}

// TWndw::EvRButtonDown()
// Эта функция реагирует на нажатие пользователем
// правой кнопки мыши, в результате которого
// генерируется сообщение WM_RBUTTONDOWN.
void TWndw::EvRButtonDown (UINT, TPoint&)
{
  // Закрыть хранитель экрана.
  PostMessage(WM_CLOSE);
}

// TWndw::EvActivate()
// Эта функция реагирует на сообщение WM_ACTIVATE,
// которое появляется при активизации или деактивизации окна.
void TWndw::EvActivate (UINT active, BOOL, HWND)
{
  // Если это окно не активизировано, закрыть
  // хранитель экрана.
  if (!active) PostMessage (WM_CLOSE);
}

// TWndw::EvActivateApp()
// Эта функция реагирует на сообщение WM_ACTIVATEAPP,
// которое появляется при активизации или деактивизации
// окон различных приложений.
void TWndw::EvActivateApp (BOOL active, HTASK)
{
  // Если это окно деактивизируется, закрыть
  // хранитель экрана.
  if (!active) PostMessage (WM_CLOSE);
}

// TWndw::EvKeyDown()
// Эта функция реагирует на сообщение WM_KEYDOWN,
// которое возникает, когда пользователь нажимает
// не системную клавишу.
void TWndw::EvKeyDown(UINT, UINT, UINT)
{
  // Закрыть хранитель экрана.
  PostMessage(WM_CLOSE);
}

// TWndw::EvSysKeyDown()
// Эта функция реагирует на сообщение WM_SYSKEYDOWN,
// которое возникает, когда пользователь нажимает
// системную клавишу.
void TWndw::EvSysKeyDown(UINT, UINT, UINT)
{
  // Закрыть хранитель экрана.
  PostMessage(WM_CLOSE);
}

// TWndw::EvSysCommand()
// Эта функция реагирует на сообщение
// WM_SYSCOMMAND, проверяет, происходит ли попытка запуска
// хранителя экрана в то время, когда он уже активен.
void TWndw::EvSysCommand(UINT cmdType, TPoint&)
{
  // Если хранитель экрана не пытается
  // перезагрузиться, то передать
  // сообщение обратно в Windows для обработки.
  if ((cmdType & 0xFFF0) != SC_SCREENSAVE) DefaultProcessing();
}

// TWndw::DoSaver()
// Эта функция рисует графические изображения хранителя экрана.
// Они вызываются объектом приложения хранителя экрана,
// когда очередь сообщений отсутствует.
void TWndw::DoSaver()
{
  // Приращение счетчика изображения.
  ++cnt;
  // Если пора менять цвет, то делать это.
  if (cnt == count)
  {
	 // Удалить старый цвет.
	 delete color;
	 // Получить новый цвет.
	 color = new TColor(random(256), random(256), random(256));
	 // Сбросить текущий счетчик.
	 cnt = 0;
  }
  // Построить контекст устройства (DC) для
  // рабочего окна и новый объект кисти.
  TBrush brush(*color);
  TClientDC clientDC(HWindow);
  // Выбрать в контекст новую кисть.
  clientDC.SelectObject(brush);
  // Получить случайное положение следующего круга.
  int x = random(GetSystemMetrics (SM_CXFULLSCREEN));
  int y = random(GetSystemMetrics (SM_CYFULLSCREEN) +
         GetSystemMetrics (SM_CYCAPTION));
  // Рисовать новый круг.
  clientDC.Ellipse(x-5, y-5, x+15, y+15);
}

//*****************************
// Реализация класса TDlg.
//*****************************
// TDlg::TDlg()
// Это конструктор диалогового окна.
TDlg::TDlg (TWindow *parent, TResId resId):
      TDialog (parent, resId) 
{
  // Создать OWL-объект окна списка.
  listbox = new TListBox (this, ID_LISTBOX);
  // Задать адрес буфера обмена.
  TransferBuffer = &lbData;
}

// TDlg::SetupWindow()
// Эта функция обеспечивает последние установки
// окна, включая добавление новых строк к окну
// списка диалогового окна и инициализацию строки,
// выбранную пользователем из окна списка.
void  TDlg::SetupWindow()
{
  // Произвести основные установки окна.
  TDialog::SetupWindow();
  // Добавить выбираемые значения к содержимому буфера обмена. 
  lbData.AddString("1") ; 
  lbData.AddString("10");
  lbData.AddString("100"); 
  lbData.AddString("200"); 
  lbData.AddString("400"); 
  lbData.AddString("1000"); 
  lbData.AddString("2000");
  lbData.AddString("4000");
  // Поместить эти значения в окно списка.
  TransferData(tdSetData);
  // Считать максимальное число пузырьков, заданное пользователем.
  GetPrivateProfileString("Screen Saver.Bubbles", "Count", 
         "1000", LBStrg, sizeof(LBStrg), "CONTROL.INI");
  // Установить текущие установки пользователя 
  // как заданные по умолчанию в окне списка. 
  listbox->SetSelString(LBStrg, -1);
}

// TDlg::IdOk()
// Эта функция реагирует на выбор пользователем
// кнопки Ok в диалоговом окне. Она записывает
// новую установку счетчика в файл CONTROL.INI.
void TDlg::CmOk()
{
  // Передать данные из окна списка.
  TransferData(tdGetData);
  // Получить выбранную строку.
  lbData.GetSelString(LBStrg, sizeof(LBStrg));
  // Записать строку в файл с расширением INI.
  WritePrivateProfileString("Screen Saver.Bubbles", "Count", 
       LBStrg, "CONTROL.INI");
  // Вызвать функцию Ok() базового класса, что
  // обеспечивает правильное закрытие диалогового окна.
  TDialog::CmOk();
}

//*****************************
// Реализация класса TApp.
//*****************************
// TApp::InitMainWindow()
// Эта функция создает главное окно приложения.
void TApp::InitMainWindow()
{
  // Окна хранителя экрана еще нет.
  bubWnd = NULL;
  // Если пользователь желает задать соответствующие
  // параметры для хранителя экрана, сделать
  // диалоговое окно конфигурации главным окном.
  char temp[80];
  // Размещение параметра.
  strcpy(temp,_argv[1]);
  if ( ((temp[0] == '/') || (temp[0] == '-')) &&
	  ( (temp[1] == 'c') || (temp[1] == 'C') ))
  {
    dialog = new TDlg(0, DIALOG_1);
    TFrameWindow *wndw = new TFrameWindow (0, 0, dialog, TRUE);
    SetMainWindow(wndw);
  }
  else
  // ...если пользователь не запрашивает диалоговое окно
  // конфигурации, то, должно быть, пора запускать 
  // хранитель экрана.
  {
    bubWnd = new TWndw(0, 0, 0); 
    SetMainWindow(bubWnd);   
  }
}

// TApp::IdleAction()
// Эта функция вызывается всякий раз, когда очередь 
// сообщений для всех приложений пуста.
BOOL TApp::IdleAction(long)
{
  // Если было создано окно хранителя экрана,
  // рисовать на экране следующую фигуру.
  if (bubWnd)	
  {
    bubWnd->DoSaver();
    return TRUE; 
  } 
  else return FALSE;
}

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

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

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

#define  DIALOG_1   200
#define  ID_LISTBOX 201

#ifdef RC_INVOKED
DIALOG_1 DIALOG 100, 80, 92, 86
STYLE WS_CHILD | WS_VISIBLE | WS_CAPTION
CAPTION "Bubbles Setup"
{
  PUSHBUTTON "OK", IDOK, 4, 68, 40, 14, WS_TABSTOP
  PUSHBUTTON "Cancel", IDCANCEL, 49, 68, 39, 14
  LISTBOX ID_LISTBOX, 31, 28, 31, 33,
	  LBS_NOTIFY | WS_BORDER | WS_VSCROLL | WS_TABSTOP
  LTEXT "Set number of bubbles before color change:",
    -1, 9, 4, 77, 18, WS_CHILD | WS_VISIBLE | WS_GROUP
}
#endif
Текст этого приложения можно взять здесь.

    Чтобы посмотреть работу программы-хранителя экрана, вы можете либо подождать в течение времени, указанного в редактируемом поле Интервал диалогового окна Свойства: Экран, либо просто нажать кнопку Просмотр. Если вы хотите сконфигурировать созданный хранитель экрана, нажмите кнопку Настройка; при этом появится диалоговое окно, показанное на рисунке 1.


Рис.1. Окно конфигурации

    В окне списка вы можете задать число "пузырей", которое должна нарисовать программа до изменения цветов. По-видимому, лучше всего подходят значения от 1000 до 2000; однако вы можете попробовать поупражняться с любыми значениями.

    После того как вы установили хранитель экрана и закрыли панель управления, программа автоматически запускается после простоя системы в течение установленного вами времени. После запуска хранителя экрана для его выключения вам требуется только переместить мышь, нажать одну из ее кнопок или нажать любую клавишу на клавиатуре.

    Хранитель экрана (Screen Saver) является не чем иным, как программой Windows. Подобно другим программам она имеет свой класс приложения. Именно реализация этого класса и делает эту программу совместимой с программами-хранителями экрана Windows.

    Чтобы воспользоваться преимуществами встроенных в Windows средств поддержки программ-хранителей, программа должна отвечать на ключи командной строки (или ) и /s (или -s). Ключ командной строки информирует программу-хранитель о том, что пользователь желает увидеть диалоговое окно конфигурации. Ключ /s командует программе-хранителю экрана запуститься.

    На самом деле пользователю не надо набирать эти командные строки. Windows автоматически запускает программу-хранитель с ключами командной строки /s или , когда пользователь нажимает кнопки Просмотр или Настройка диалогового окна Свойства: Экран, соответственно. Windows также запускает хранитель экрана с ключом /s, когда система "простояла" заданное время задержки. Так как приведенная программа должна создавать свое главное окно в зависимости от ключей и /s, командная строка должна проверяться в функции InitMainWindow() класса приложения.

void TApp::InitMainWindow()
{
  // Окна хранителя экрана еще нет.
  bubWnd = NULL;
  // Если пользователь желает задать соответствующие
  // параметры для хранителя экрана, сделать
  // диалоговое окно конфигурации главным окном.
  char temp[80];
  // Размещение параметра.
  strcpy(temp,_argv[1]);
  if ( ((temp[0] == '/') || (temp[0] == '-')) &&
	  ( (temp[1] == 'c') || (temp[1] == 'C') ))
  {
    dialog = new TDlg(0, DIALOG_1);
    TFrameWindow *wndw = new TFrameWindow (0, 0, dialog, TRUE);
    SetMainWindow(wndw);
  }
  else
  // ...если пользователь не запрашивает диалоговое окно
  // конфигурации, то, должно быть, пора запускать 
  // хранитель экрана.
  {
    bubWnd = new TWndw(0, 0, 0); 
    SetMainWindow(bubWnd);   
  }
}

    Здесь функция сначала устанавливает указатель bubWnd, показывая, что еще нет окна хранителя экрана. Затем она проверяет командную строку (которая находится в первом элементе массива _argv[]) на наличие в ней ключа . Если этот ключ присутствует, то программа строит диалоговое окно TDlg и делает его окном-клиентом главного окна. В этом случае главным окном является немодифицированное окно TFrameWindow.

    Если ключ в командной строке не равен , программа создает окно хранителя экрана класса TWndw и делает его главным окном приложения. Программа сохраняет указатель этого окна в bubWnd, так что приложение может вызвать функцию окна DoSaver() (которая описывается ниже).

    Windows автоматически запускает хранитель экрана, когда пользователь ничего не делает в течение времени, заданного в поле Интервал диалогового окна Свойства: Экран (конечно в том числе, если хранители экрана не заблокированы). Однако, если пользователь не производит никаких действий, это не означает, что все приложения также простаивают. Предположим, например, что вы загружаете из сети большой файл. Хотя вы можете и не нажимать кнопку мыши или клавиатуры в процессе загрузки, система не находится в состоянии полного простоя. Ваша программа передачи данных по сети связи собирает данные с максимально возможной быстротой и запоминает их на вашем диске. Эта деятельность не должна прерываться. Когда запускается хранитель экрана, он не должен мешать другим приложениям, работающим в фоновом режиме.

    Короче говоря, хранитель экрана должен рисовать на экране только тогда, когда система простаивает. А в Windows "простаивание" означает, что в очереди запросов нет сообщений от какого-либо приложения.

    Определение этого времени простоя сводится к переопределению функции IdleAction() в классе приложения:

BOOL TApp::IdleAction(long)
{
  // Если было создано окно хранителя экрана,
  // рисовать на экране следующую фигуру.
  if (bubWnd)	
  {
    bubWnd->DoSaver();
    return TRUE; 
  } 
  else return FALSE;
}

    Функция IdleAction() класса TApplication вызывается всякий раз при отсутствии сообщений для какого-либо приложения. Здесь функция сначала проверяет, является ли правильным указатель bubWnd и не равен ли он нулю; это означает, что главное окно приложения в самом деле является окном хранителя экрана, а не диалоговым окном конфигурации. Функция IdleAction() также вызывается, когда на экран выведено диалоговое окно конфигурации. В этом случае программа не желает вызывать DoSaver(). Если bubWnd является правильным указателем, вызывается функция окна DoSaver(), рисующая на экране один круг ("пузырек").

    С функцией DoSaver() вы познакомитесь ниже. Но сначала вам надо узнать, как обращаться с диалоговым окном конфигурации хранителя экрана.

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

    Как вы уже видели, диалоговое окно конфигурации приведенной программы содержит окно списка для выбора количества "пузырьков", а также две кнопки - ОК и Cancel. Данное диалоговое окно описывается в файле ресурсов, а его OWL-объект реализуется в классе TDlg:

class TDlg : public  TDialog
{
  public:
    TDlg (TWindow *parent, TResId resId);
  protected:
    char LBStrg [10];
    TListBoxData lbData;
    TListBox *listbox;

    void SetupWindow();
    void CmOk();

   DECLARE_RESPONSE_TABLE (TDlg);
};

DEFINE_RESPONSE_TABLE1 (TDlg, TDialog)
  EV_COMMAND (IDOK, CmOk), 
END_RESPONSE_TABLE;

    Защищенный член-данное этого класса LBStrg является символьным массивом, который содержит строку, выбираемую пользователем из окна списка диалогового окна. Другими членами-данными являются lbData, объект класса TListBoxData, который является буфером обмена окна списка, и listbox, который является указателем на окно списка диалогового окна. Кроме того, этот класс имеет конструктор и две функции, которые устанавливают окно диалога и реагируют на команды его управляющих элементов. Первая из этих функций, SetupWindow(), инициализирует управляющий элемент - окно списка.

void  TDlg::SetupWindow()
{
  // Произвести основные установки окна.
  TDialog::SetupWindow();
  // Добавить выбираемые значения к содержимому буфера обмена. 
  lbData.AddString("1") ; 
  lbData.AddString("10");
  lbData.AddString("100"); 
  lbData.AddString("200"); 
  lbData.AddString("400"); 
  lbData.AddString("1000"); 
  lbData.AddString("2000");
  lbData.AddString("4000");
  // Поместить эти значения в окно списка.
  TransferData(tdSetData);
  // Считать максимальное число пузырьков, заданное пользователем.
  GetPrivateProfileString("Screen Saver.Bubbles", "Count", 
         "1000", LBStrg, sizeof(LBStrg), "CONTROL.INI");
  // Установить текущие установки пользователя 
  // как заданные по умолчанию в окне списка. 
  listbox->SetSelString(LBStrg, -1);
}

    Эта функция сначала вызывает TDialog::SetupWindow(), чтобы выполнить установки для базового окна. Затем она вызывает функцию AddString() объекта lbData для каждой строки, которая должна быть отображена в окне списка диалогового окна, после чего обращение к функции TransferData() копирует выбранную информацию в окно списка. Затем программа вызывает функцию Windows API GetPrivateProfileString() для чтения текущей установки пользователя из файла CONTROL.INI. Это инициализирует LBStrg значением по умолчанию.

    Параметрами функции GetPrivateProfileString() являются:

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

    Наконец, программа вызывает функцию окна списка SetSelString(), используя строку конфигурации пользователя, чтобы установить выбор в окне списка по умолчанию; эта выбранная по умолчанию строка и будет выделена при появлении окна списка.

    После выхода из TDlg::SetupWindow(), Windows выводит на экран диалоговое окно конфигурации, и пользователь может воспользоваться его управляющими элементами. Если пользователь нажмет кнопку Cancel, то функция CmCancel() класса TDlg (унаследована от класса TDialog) закрывает диалоговое окно. Если пользователь осуществляет выход по кнопке ОК, то OWL вызывает функцию CmOk() класса TDlg, которая переопределяет функцию CmOk() класса TWindow:

void TDlg::CmOk()
{
  // Передать данные из окна списка.
  TransferData(tdGetData);
  // Получить выбранную строку.
  lbData.GetSelString(LBStrg, sizeof(LBStrg));
  // Записать строку в файл с расширением INI.
  WritePrivateProfileString("Screen Saver.Bubbles", "Count", 
       LBStrg, "CONTROL.INI");
  // Вызвать функцию Ok() базового класса, что
  // обеспечивает правильное закрытие диалогового окна.
  TDialog::CmOk();
}

    Здесь программа сначала вызывает функцию TransferData() для копирования содержимого окна списка в буфер обмена. Затем она вызывает функцию GetSelString() объекта lbData (унаследованную от TListBoxData) для возврата строки, выделенной пользователем. Аргументами функции GetSelString() являются адрес символьного массива, в котором надо сохранить строку, и размер этого символьного массива.

    После получения строки программа вызывает функцию Windows API WritePrivateProfileString(), чтобы сохранить эту строку в файле CONTROL.INI. Первым параметром этой функции является тот раздел INI-файла, в котором сохраняется строка. В приведенном примере этим разделом является "ScreenSaver.Bubbles". Если этот раздел еще отсутствует в этом файле, Windows его создает. Вторым параметром является ключевое имя, которое вы желаете присвоить этой строке; в предыдущем примере этим именем является "Count". Третьим параметром является строка, содержащая строку, которая будет сохраняться (LBStrg). Наконец, четвертым параметром является имя INI-файла, в котором будет сохраняться строка. Как говорилось выше, для программы-хранителя экрана этим файлом является файл CONTROL.INI.

    После закрытия упомянутой выше функции (сначала, конечно, вызывается версия базового класса функции CmOk()), файл пользователя CONTROL.INI содержит раздел, который выглядит так:

    [Screen Saver.Bubbles] 
    Count=1000

    Теперь, когда вы знаете, как конфигурировать хранитель экрана, пора узнать, как его запустить. Эта задача решается классом главного окна хранителя TWndw.

class TWndw: public TFrameWindow
{
  public:
    TWndw(TWindow *parent, const char far *title, TWindow *client); 
    ~TWndw (); 
    void DoSaver();
  protected:
    TPoint mouseXY; 
    int count, cnt;
    TColor *color;

    void GetWindowClass (WNDCLASS &wndClass);
    char far *GetClassName();
    void EvLButtonDown (UINT, TPoint&); 
    void EvMButtonDown (UINT, TPoint&);
    void EvRButtonDown (UINT, TPoint&); 
    void EvMouseMove (UINT, TPoint &point);
    void EvSysCommand (UINT, TPoint&); 
    void EvActivate (UINT, BOOL, HWND); 
    void EvActivateApp (BOOL, HTASK); 
    void EvSysKeyDown (UINT, UINT, UINT); 
    void EvKeyDown (UINT, UINT, UINT);

   DECLARE_RESPONSE_TABLE (TWndw);
};

DEFINE_RESPONSE_TABLE1(TWndw,TFrameWindow)
    EV_WM_LBUTTONDOWN,
    EV_WM_MBUTTONDOWN,
    EV_WM_RBUTTONDOWN,
    EV_WM_MOUSEMOVE,
    EV_WM_SYSCOMMAND,
    EV_WM_ACTIVATE,
    EV_WM_ACTIVATEAPP,
    EV_WM_SYSKEYDOWN,
    EV_WM_KEYDOWN, 
END_RESPONSE_TABLE;

    Этот класс содержит четыре защищенных члена-данных. Первый, mouseXY, является объектом класса TPoint, в котором программа сохраняет положение указателя мыши лри запуске хранителя экрана. Второе и третье поля, count и cnt, являются связанными. Count - это переменная, которая содержит значение конфигурации, хранящееся в разделе хранителя экрана файла CONTROL.INI. С другой стороны, cnt отслеживает количество уже нарисованных "пузырьков". Наконец, color - это указатель на объект класса TColor, который используется программой для хранения текущего цвета "пузырьков". Цвет изменяется, когда cnt равен count.

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

TWndw::TWndw (TWindow *parent, const char far *title,
  TWindow *client):TFrameWindow(parent, title, client) 
{
  // Сохранить положение указателя мыши и выключить мышь.
  GetCursorPos(mouseXY);
  ShowCursor(FALSE);
  // Установить стиль окна, расположение и размеры.
  Attr.Style = WS_POPUP;
  Attr.X = 0;
  Attr.Y = 0;
  Attr.W = GetSystemMetrics (SM_CXFULLSCREEN);
  Attr.H = GetSystemMetrics (SM_CYFULLSCREEN) +
			GetSystemMetrics (SM_CYCAPTION);
  // Прочитать максимальное количество пузырьков,
  // установленное пользователем.
  count = GetPrivateProfileInt ("Screen Saver.Bubbles", "Count", 
           1000, "CONTROL.INI");
  // Запустить генератор случайных чисел.
  randomize();
  // Инициализировать счетчик и цвет пузырька.
  cnt = 0;
  color = new TColor(random(256), random(256), random(256));
}

    Здесь программа сначала вызывает функцию GetCursorPos(), унаследованную от TWindow, чтобы получить текущее положение указателя мыши, которое сохраняется в mouseXY. (Функция GetCursorPos() является OWL-версией функции Windows API с тем же именем.) Затем курсор мыши отключается функцией Windows API ShowCursor(). (Когда вы вызываете эту функцию, параметр FALSE выключает указатель мыши, а значение TRUE включает его.) После обработки указателя мыши функция устанавливает стиль окна WS_POPUP. Окно, только что получившее такой стиль, представляет собой большой пустой прямоугольник. Другими словами, у него нет ни управляющих элементов, ни каких-либо других графических элементов; это невидимое окно.

    После того, как функция устанавливает стиль окна, она присваивает ему размер всего экрана. Вы можете просчитать ширину окна, вызвав функцию GetSystemMetrics() с параметром SM_CXFULLSCREEN, который возвращает наибольшую допустимую ширину рабочей области окна. Чтобы вычислить высоту рабочей области окна, вам надо вызвать функцию GetSystemMetrics() дважды, один раз с параметром SM_CYFULLSCREEN, который возвращает наибольшую допустимую высоту рабочей области окна, и один раз со значением SM_CYCAPTION, которое возвращает высоту строки заголовка окна. Вы должны сложить эти два значения и получить полную высоту экрана.

    После определения размеров окна конструктор считывает из файла CONTROL.INI конфигурационные установки пользователя. Это достигается путем обращения к функции Windows API GetPrivateProfileInt(), которая считывает из INI-файла некоторое целое значение. Первым параметром этой функции является тот раздел файла, в котором содержится указанное значение. Вторым параметром является ключевое имя этого значения. Третий параметр - это значение, принятое по умолчанию, которое будет использоваться, если запрашиваемое значение не найдено. Наконец, четвертым параметром является имя INI-файла. После этого вызова count содержит то значение, которое пользователь сохранил при задании конфигурации хранителя экрана (или принятого по умолчанию значения 1000).

    Наконец, конструктор TWndw запускает генератор случайных чисел, вызывая randomize() и инициализирует начальное значение "пузырька" (cnt) и его цвет (color).

    Когда вы запускаете хранитель экрана, вы видите, что до того, как начнется рисование "пузырьков", экран становится черным. Это происходит потому, что цвет фона окна в функции GetWindowClass() устанавливается черным, а не стандартным белым:

void TWndw::GetWindowClass (WNDCLASS &wndClass)
{
  // Начать со значений, установленных по умолчанию для класса.
  TFrameWindow::GetWindowClass (wndClass);
  // Придать окну черный фон.
  wndClass.hbrBackground = (HBRUSH) GetStockObject (BLACK_BRUSH);
}

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


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

    При рассмотрении класса приложения вы видели, что всякий раз при очередном простое системы вызывается функция хранителя экрана DoSaver():

void TWndw::DoSaver()
{
  // Приращение счетчика изображения.
  ++cnt;
  // Если пора менять цвет, то делать это.
  if (cnt == count)
  {
	 // Удалить старый цвет.
	 delete color;
	 // Получить новый цвет.
	 color = new TColor(random(256), random(256), random(256));
	 // Сбросить текущий счетчик.
	 cnt = 0;
  }
  // Построить контекст устройства (DC) для
  // рабочего окна и новый объект кисти.
  TBrush brush(*color);
  TClientDC clientDC(HWindow);
  // Выбрать в контекст новую кисть.
  clientDC.SelectObject(brush);
  // Получить случайное положение следующего круга.
  int x = random(GetSystemMetrics (SM_CXFULLSCREEN));
  int y = random(GetSystemMetrics (SM_CYFULLSCREEN) +
         GetSystemMetrics (SM_CYCAPTION));
  // Рисовать новый круг.
  clientDC.Ellipse(x-5, y-5, x+15, y+15);
}

    Эта функция попросту рисует круги со случайным цветом в случайных местах на экране. Во-первых, cnt получает приращения и проверяется его равенство параметру count, который, как вы помните, является значением, считываемым из конфигурации пользователя. Если эти значения равны, то пора менять цвет круга ("пузыря"). Программа реализует это, удаляя объект старого цвета и создавая новый объект со случайными значениями красного, зеленого и голубого цветов. Следуя новой установке объекта цвета, программа опять устанавливает cnt равным нулю.

    После проверки cnt программа создает новый объект кисти текущего цвета, читает контекст устройства (DC) рабочей области, выбирает кисть в этот контекст и рисует круг в случайных координатах на экране.

    Как видите, это сравнительно простой хранитель экрана. Однако вы можете добавить разные звуки, расширив функцию DoSaver(), которая является сердцем графики хранителя экрана. В самом деле, при создании собственных программ-хранителей вам может понадобиться полностью переписать функцию DoSaver().

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

    Чтобы избежать запуска нескольких экземпляров хранителей, главное окно программы должно наблюдать за сообщениями WM_SYSCOMMAND, которые сигнализируют о некоторых активных действиях системы, включая запуск экранных хранителей. В приведенной программе это сообщение захватывается функцией EvSysCommand():

void TWndw::EvSysCommand(UINT cmdType, TPoint&)
{
  // Если хранитель экрана не пытается
  // перезагрузиться, то передать
  // сообщение обратно в Windows для обработки.
  if ((cmdType & 0xFFF0) != SC_SCREENSAVE) DefaultProcessing();
}

    Здесь эта функция проверяет установку сmdType на сообщение SC_SCREENSAVE, которое Windows посылает, когда хочет запустить хранитель экрана. Если эта функция обнаруживает сообщение SC_SCREENSAVE, она бездействует; это означает, что Windows никогда не получает сообщения о запуске хранителя. Любые другие сообщения WM_SYSCOMMAND должны быть переданы обратно в Windows функцией DefaultProcessing() (унаследованной от TWindow).


    Замечание. В соответствии с руководством Windows, параметр wParam сообщения WM_SYSCOMMAND (которое OWL помещает в параметр cmdType функции EvSysCommand()), должен подвергаться логическому умножению с маской 0хFFF0 перед его проверкой. Это обусловлено тем, что для сохранения дополнительной информации Windows иногда использует четыре младших бита wParam.

    Если вы хотите узнать, что происходит, когда хранитель не захватывает сообщение SC_SCREENSAVE, закомментируйте строку с if в предыдущей функции, оставив вызов функции DefaultProcessing(). Затем перекомпилируйте программу и установите новую версию экранного хранителя, задавая задержку срабатывания хранителя в одну минуту. Понаблюдайте за экраном. Спустя минуту Windows запустит экранный хранитель. Каждую следующую минуту Windows снова запускает хранитель!

    Таким образом, чтобы предотвратить запуск Windows нескольких экземпляров вашего хранителя, надо предусмотреть в вашей программе обработку сообщения WM_SYSCOMMAND. Когда к вашей программе посылается сообщение WM_SYSCOMMAND с параметром wParam, равным SC_SCREENSAVE, вы не должны возвращать это сообщение в Windows.

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

    WM_LBUTTONDOWN,    WM_MBUTTONDOWN,    WM_RBUTTONDOWN,    
    WM_MOUSEMOVE, WM_ACTIVATE, WM_ACTIVATEAPP,   
    WM_SYSKEYDOWN,   WM_KEYDOWN

    Как видно из приведенного примера, многие функции отклика на сообщения лишь обеспечивают вызов функции PostMessage (WM_CLOSE), заставляя закрываться окно хранителя экрана. Однако для некоторых сообщений Windows требуется небольшая дополнительная обработка. Например, если пользователь получает сообщение WM_MOUSEMOVE, которое обрабатывается функцией EvMouseMove():

void TWndw::EvMouseMove(UINT, TPoint& point)
{
  // Если мышь находится в новом положении,
  // закрыть хранитель экрана.
  if (point != mouseXY) PostMessage(WM_CLOSE);
}

    Перед тем, как ответить на это сообщение, функция должна проверить, совпадает ли текущее положение мыши с тем положением, которое было при запуске хранителя экрана. Если эти положения совпадают, мышь не перемещалась. Окно хранителя попросту содержит сообщение WM_MOUSEMOVE в своей очереди в момент запуска программы. В этом случае функция EvMouseMove() бездействует.

    С другой стороны, если два набора координат не совпадают, то мышь перемещалась и программа вызывает функцию PostMessage(), которая закрывает окно экранного хранителя, посылая в Windows сообщение WM_CLOSE. Если вы забудете обработать сообщение WM_MOUSEMOVE в программе хранителя экрана, программа-хранитель закрывает окно сразу же после пуска, точно так же, как если бы пользователь переместил мышь.

    Хранитель экрана должен также произвести проверку сообщений WM_ACTIVE и WM_ACTIVEATEAPP, так как эти сообщения указывают на то, что другое приложение пытается открыть окно. Когда открывается другое окно, хранитель должен "уступить дорогу", закрывая свое окно и пропуская на экран другую программу. Если параметр wParam для этих сообщений отличен от нуля, то окно пытается открыться, В функциях EvActivate(), EvActivateApp() OWL помещает это значение wParam в параметр active:

void TWndw::EvActivate (UINT active, BOOL, HWND)
{
  // Если это окно не активизировано, закрыть
  // хранитель экрана.
  if (!active) PostMessage (WM_CLOSE);
}

void TWndw::EvActivateApp (BOOL active, HTASK)
{
  // Если это окно деактивизируется, закрыть
  // хранитель экрана.
  if (!active) PostMessage (WM_CLOSE);
}

    Последнее, что должен предпринять хранитель экрана - это удалить объект color и восстановить указатель.мыши. В приведенной программе такие задачи выполняются в деструкторе TWndw, который вызывает OWL непосредственно перед разрушением окна:

TWndw::~TWndw ()
{
  // Удалить объект TColor.
  delete color;
  // Отобразить снова указатель мыши.
  ShowCursor(TRUE);
}

    На следующем шаге мы рассмотрим простейший текстовый редактор.




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