Шаг 81.
Библиотека OWL.
Хранитель экрана

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

    Студент 4 курса (2010-11 уч.год) Черемисов Сергей создал хранитель экрана средствами бибилиотеки OWL.

    Чтобы запустить хранитель экрана, создайте проект из приведённых ниже файлов и откомпилируйте его. У полученного исполняемого файла измените расширение на .scr и поместите его в папку windows\system32\. После этого хранитель экрана будет доступен в стандартном окне настроек экрана Windows.

    Приведём текст программы. Следуя традициям объектно-ориентированного программирования, описание классов поместим в файлы с расширением .h, а реализацию - в .cpp.

    Файл main.cpp:

#include "tapp.h"

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

    Описание класса TApp - файл tapp.h:

#ifndef _TAPP_h_
#define _TAPP_h_
#include <owl\applicat.h>
#include "twndw.h"
#include "tdlg.h"

class TApp: public TApplication
{
  public:
	 TApp() : TApplication() {}
	 void InitMainWindow();
  protected:
	 TWndw *SaverWnd;
	 TDialog *dialog;
	 BOOL IdleAction (long);
};
#endif

    Реализация класса TApp - файл tapp.cpp:

#include "tapp.h"

void TApp::InitMainWindow()
{
  SaverWnd = NULL;
  char temp[80];
  if (_argv[1]!=NULL) // есди есть параметр, то
  strcpy(temp,_argv[1]); // копирование строки параметров
  else *temp='\0'; //иначе установить первым символом символ конца строки
  if ( !*temp || ((temp[0] == '/') || (temp[0] == '-')) &&
	  ( (temp[1] == 'c') || (temp[1] == 'C') ))
  //если параметр /c или нет параметра, то ...
  {
	 dialog = new TDlg(0, DIALOG_1);//создаём диалоговое окно настроек
	 TFrameWindow *wndw = new TFrameWindow (0, 0, dialog, TRUE);
	 //диалоговое окно должно иметь родительское окно
	 SetMainWindow(wndw); //устанавливаем главное окно приложения
  }
  else
  //если другой параметр (/s), то ...
  {
    SaverWnd = new TWndw(0, 0, 0); //создаём окно скринсейвера
    SetMainWindow(SaverWnd); //устанавливаем его в качестве главного окна приложения
  }
}

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

    Описание класса TDlg - файл tdlg.h:

#ifndef _TDLG_h_
#define _TDLG_h_
#include <owl\dialog.h>
#include <owl\combobox.h>
#include <owl\checkbox.h>
#include "saver.rc"

class TDlg : public  TDialog
{
  public:
	 TDlg (TWindow *parent, TResId resId);
  protected:
	 char LBStrg [3];
	 struct
	 {
		TListBoxData lbData;
		BOOL check;
	 } Buf;
	 TListBox *listbox;
	 TCheckBox *checkbox;
	 void SetupWindow();
	 void CmOk();
	DECLARE_RESPONSE_TABLE (TDlg);
};
#endif

    Реализация класса TDlg - файл tdlg.cpp:

#include "tdlg.h"

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

TDlg::TDlg (TWindow *parent, TResId resId):
		TDialog (parent, resId)
{
  listbox = new TListBox (this, IDC_LISTBOX1); //создать объект-список
  checkbox = new TCheckBox(this, IDC_CHECKBOX); //создать объект-флажок
  TransferBuffer = &Buf; //установить адрес буфера обмена для диалогового окна
}

//функция, вызывающаяся перед первым отображением окна на экране
void  TDlg::SetupWindow() 
{
  TDialog::SetupWindow(); 
  Buf.lbData.AddString("1"); // 
  Buf.lbData.AddString("2"); // 
  Buf.lbData.AddString("3"); //
  Buf.lbData.AddString("5"); //
  Buf.lbData.AddString("10");// установка значений списка
  Buf.lbData.AddString("15");//
  Buf.lbData.AddString("20");//
  Buf.lbData.AddString("40");//
  TransferData(tdSetData);  // перенести данные из буфера в диалоговое окно
  HKEY res; //дескриптор раздела реестра
  DWORD disp, sizestr=3, sizemode=1; 
  char mode=0; //режим скринсейвера по умолчанию
  if(RegCreateKeyEx(HKEY_CURRENT_USER, "Software\\FigureScreenSaver\\", 0,
			  NULL, REG_OPTION_NON_VOLATILE, KEY_ALL_ACCESS,
			  NULL, &res, &disp)==ERROR_SUCCESS)
  // создание/открытие раздела реестра windows 
{
	 if (disp==REG_CREATED_NEW_KEY) //если раздел был создан
	 // то создаём ключи и записываем в них значения по умолчанию
	 {
		RegSetValueEx(res,"Number",0, REG_SZ,(BYTE*)"20",3); 
		RegSetValueEx(res,"Mode",0, REG_BINARY, (BYTE*)&mode, 1);
		listbox->SetSelIndex(6); // по умолчанию выбрано значение "20" 
	 }
	 else //если раздел был открыт
	 {
		char numb[3]; 
		//читаем значение ключа с количеством отрисовываемых фигур
		RegQueryValueEx(res, "Number", 0, NULL, (BYTE*)numb, &sizestr);
		listbox->SetSelString(numb, -1);//устанавливаем выбранное значение
		// читаем значение ключа с режимом работы скринсейвера
		RegQueryValueEx(res, "Mode", 0, NULL, (BYTE*)&mode, &sizemode);
		// устанавливаем флажок в зависимости от выбранного режима
		checkbox->SetCheck(mode? BF_CHECKED: BF_UNCHECKED);
	 }
	 RegCloseKey(res); //завершаем работу с разделом
  }
  //если произошла ошибка при работе с реестром, то устанавливаем выбор по умолчанию
  else listbox->SetSelIndex(6); 
}

void TDlg::CmOk()
{
  TransferData(tdGetData); //переносим данные из диалога в буфер
  HKEY res;
  Buf.lbData.GetSelString(LBStrg, sizeof(LBStrg));//получаем выбранную строку
  char mode=Buf.check? 1: 0;//устанавливаем нужный режим
  //создаём/открываем раздел реестра
  if(RegCreateKeyEx(HKEY_CURRENT_USER, "Software\\FigureScreenSaver\\", 0,
			  NULL, REG_OPTION_NON_VOLATILE, KEY_ALL_ACCESS,
			  NULL, &res, NULL)==ERROR_SUCCESS)
  //если раздел успешно создан/открыт, то записываем выбранные значения в реестр
  {
	RegSetValueEx(res,"Number",0,REG_SZ, (BYTE*)LBStrg, strlen(LBStrg)+1);
	RegSetValueEx(res,"Mode",0, REG_BINARY, (BYTE*)&mode, 1);
	RegCloseKey(res); //завершаем работу с разделом
  }
  else MessageBox("Невозможно сохранить настройки", "Внимание", MB_OK | MB_ICONHAND);
  TDialog::CmOk();
}

    Описание класса TWndw - файл twndw.h:

#ifndef _TWNDW_h_
#define _TWNDW_h_
#include <owl\framewin.h>
#include "figure.h"

typedef Figure *Figure_p; // тип-указатель на объект класса Figure

class TWndw: public TFrameWindow
{
  public:
	 TWndw(TWindow *parent, const char far *title, TWindow *client);
	 ~TWndw ();
	 void DoSaver();
  protected:
	 TPoint mouseXY; 
	 Figure_p *fff; //указатель на массив указателей на фигуры
	 int Num; // количество отрисовываемых фигур
	 char mode; //режим работы скринсейвера
	 void GetWindowClass (WNDCLASS &wndClass);
	 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);
	 char far *GetClassName();
	DECLARE_RESPONSE_TABLE (TWndw);
};
#endif

    Реализация класса TWndw - файл twndw.cpp:

#include "twndw.h"

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;

TWndw::TWndw (TWindow *parent, const char far *title,
  TWindow *client):TFrameWindow(parent, title, client)
{
  HKEY res;
  //чтение настроек скринсейвера из реестра или установка настроек по умолчанию
  if(RegOpenKeyEx(HKEY_CURRENT_USER, "Software\\FigureScreenSaver\\", 0,
					  KEY_QUERY_VALUE,&res)==ERROR_SUCCESS)
  {
    char numb[3];
    DWORD sizestr=3, sizemode=1;
    if (RegQueryValueEx(res, "Number", 0, NULL, (BYTE*)numb, &sizestr)==ERROR_SUCCESS)
       Num=atoi(numb);
    else Num=20;
    if (RegQueryValueEx(res, "Mode", 0, NULL, (BYTE*)&mode, &sizemode)!=ERROR_SUCCESS)
       mode=0;
    RegCloseKey(res);
  }
  else Num=20;
  randomize();
  GetCursorPos(mouseXY);//запоминаем положение курсора мыши
  ShowCursor(FALSE);//скрываем курсор
  Attr.Style = WS_POPUP;//стиль окна
  Attr.X = 0;
  Attr.Y = 0;
  Attr.W = GetSystemMetrics (SM_CXSCREEN);//
  Attr.H = GetSystemMetrics (SM_CYSCREEN);// размер окна на весь экран
  fff=new Figure_p[Num]; // выделяем память под массив указателей на фигуры
  // создаём фигуры и помещаем их адреса в массив
  for(int i=0; i<Num;i++) fff[i]=new Figure(Attr.W/2, Attr.H/2, TColor(random(256),
				random(256),random(256)), 180-random(360));
}

TWndw::~TWndw ()
{
  ShowCursor(TRUE); //показываем курсор
  for(int i=0; i<Num;i++) delete fff[i]; //освобождаем память из-под фигур
  delete [] fff; // освобождаем память из-под массива
}

void TWndw::GetWindowClass (WNDCLASS &wndClass)
//функция, заполняющая структуру оконного класса
{
  TFrameWindow::GetWindowClass (wndClass);
  wndClass.hbrBackground = (HBRUSH) GetStockObject(BLACK_BRUSH);
}

char far *TWndw::GetClassName()
//функция, возвращающая имя класса окна
{
  return   "WindowsScreenSaverClass";
}

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

void TWndw::EvLButtonDown(UINT, TPoint&)
//функция, вызывающаяся при нажатии левой кнопки мыши
{
  PostMessage(WM_CLOSE);
}

void TWndw::EvMButtonDown(UINT, TPoint&)
//функция, вызывающаяся при нажатии средней кнопки мыши
{
  PostMessage(WM_CLOSE);
}

void TWndw::EvRButtonDown (UINT, TPoint&)
//функция, вызывающаяся при нажатии правой кнопки мыши
{
  PostMessage(WM_CLOSE);
}

void TWndw::EvActivate (UINT active, BOOL, HWND)
//функция, вызывающаяся при активации/деактивации окна
{
  if (!active) PostMessage (WM_CLOSE);
}

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

void TWndw::EvKeyDown(UINT, UINT, UINT)
{
  PostMessage(WM_CLOSE);
}

void TWndw::EvSysKeyDown(UINT, UINT, UINT) 
//функция, вызывающаяся при нажатии системной клавиши
{
  PostMessage(WM_CLOSE);
}

void TWndw::EvSysCommand(UINT cmdType, TPoint&)
//проверка на запуск второго скринсейвера
{
  if ((cmdType & 0xFFF0) != SC_SCREENSAVE) DefaultProcessing();
}

void TWndw::DoSaver()
//действия по отрисовке фигур
{
	TMemoryDC ddb; //создать контекст в памяти
	TClientDC ddd(HWindow); //создать контекст для окна скринсейвера
	//получить прямоугольник клиентской области окна
	TRect ss = ddd.GetClipBox(); 
	TSize sz = ss.Size(); // получить его размеры 
	// создать растр для контекста в памяти,
	//совместимый с контекстом главного окна
	TBitmap bitmap(ddd, sz.cx, sz.cy, FALSE);
	ddb.SelectObject(bitmap); //выбрать созданный растр для контекста ddb
	//в зависимости от режима перенести изображение с экрана в память
	if (mode) ddb.BitBlt(0, 0,  sz.cx, sz.cy,ddd, 0, 0);
	for(int i=0; i<Num;i++) fff[i]->Saver(ddb); //отрисовать все фигуры
	// перенести изображение из памяти на экран 
	ddd.BitBlt(0, 0,  sz.cx, sz.cy,ddb, 0, 0);
}

    Описание класса Figure - файл figure.h:

#ifndef _FIGURE_h_
#define _FIGURE_h_
#include <owl\dc.h>
#include <math.h>
#include <time.h>

class Figure
{
  private:
	 float a, b, c, //углы поворота фигуры
	 gx, //размер
	 xc, yc, //координаты центра
	 da, db, dc; //приращения углов поворота
	 TColor col; //цвет
	 int fi, //направление движение фигуры
	 mx, my; // горизонтальное/вертикальное отражение направления движения
	 void Draw(int, float ,float, float, float, float, TDC &);
	 void Drawer(int, float, float, TDC& );
  public:
	 void Saver(TDC &);
	 Figure(float, float, TColor, int );
};
#endif

    Реализация класса Figure - файл figure.cpp:

#define PI 3.14159
#include <time.h>
#include "figure.h"
#define randangle 0.05-float(random(1000))/float(10000);

Figure::Figure(float x, float y, TColor cc, int f)
{
  //установка начальных значений параметров фигуры
  xc=x;
  yc=y;
  b=c=0;
  a=3*PI;
  da=db=dc=0.05;
  col=cc;
  fi=f;
  mx=my=1;
  gx=random(15)+5;
}

void Figure::Draw(int i, float x,float y,float z, float x0, float y0, TDC &ddb)
//функция отрисовки очередной линии фигуры
{
  float  y1, z1;
  //преобразование координат производится матрицей поворота
  y1=cos(c)*(x*sin(a)+y*cos(a))-sin(c)*(sin(b)*(x*cos(a)-y*sin(a))+z*cos(b));
  z1=sin(c)*(x*sin(a)+y*cos(a))+cos(c)*(sin(b)*(x*cos(a)-y*sin(a))+z*cos(b));
  if ((y==-3 && i) || (x==-3 && !i)) ddb.MoveTo(x0+int(y1*gx), y0-int(z1*gx));
  else ddb.LineTo(x0+int(y1*gx), y0-int(z1*gx));
}

void Figure::Drawer(int i, float x0, float y0, TDC& ddb)
//функция отрисовки полуфигуры
{
	float x, y, z;
	x=-3;
	//рисование "продольных" линий
	do
	{
	  y=-3;
	  do
	  {
		 z=float(i)*sqrt(18-x*x-y*y); //Уравнение полусферы
		 Draw(1, x, y, z, x0, y0, ddb);
		 y++;
	  } while (y<=3);
	  x++;
	}
	while (x<=3);
	y=-3;
	//рисование "поперечных" линий
	do
	{
	  x=-3;
	  do
	  {
		 z=float(i)*sqrt(18-x*x-y*y);
		 Draw(0, x, y, z, x0, y0, ddb);
		 x++;
	  } while (x<=3);
	  y++;
	}
	while (y<=3);
}

void Figure::Saver(TDC &ddb)
{
	TPen pen(col, 1, PS_SOLID);//создать перо нужного цвета
	ddb.SelectObject(pen);//выбрать перо

	Drawer(1, xc, yc, ddb); //отрисовать верхнюю половину фигуры
	Drawer(-1, xc, yc, ddb);//отрисовать нижнюю половин

	TRect ss = ddb.GetClipBox(); //получить прямоугольник клиентской области окна
	TSize sz = ss.Size(); // получить его размеры

	randomize();

	if(a<=2*PI) a+=da; else a=0; //
	if(b<=2*PI) b+=db; else b=0; // "поворачиваем" фигуру
	if(c<=2*PI) c+=dc; else c=0; //

	xc+=float(mx)*6*cos(fi*PI/180); // "передвигаем" фигуру
	yc+=float(my)*6*sin(fi*PI/180); // 

	if (fabs(xc)<gx*5 || fabs(xc-sz.cx)<gx*5) 
	// если фигура достигла левого/правого края экрана
	{
	  mx*=-1; // отражаем движение по горизонтали
	  da=randangle; //
	  db=randangle; // устанавливаем случайные приращение углов
	  dc=randangle; //
	}
	else if (fabs(yc)<gx*5 || fabs(yc-sz.cy)<gx*5)
	// если фигура достигла верхнего/нижнего края экрана
	{
	  my*=-1; // отражаем движение по вертикали
	  da=randangle; //
	  db=randangle; // устанавливаем случайные приращение углов
	  dc=randangle; //
	}
}

    Файл ресурсов saver.rc:

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

#define  DIALOG_1        200
#define IDC_LISTBOX1     102
#define IDC_CHECKBOX     103

#ifdef RC_INVOKED
DIALOG_1 DIALOG 101, 83, 109, 113
STYLE WS_CHILD | WS_VISIBLE | WS_CAPTION
CAPTION "Screensaver FIGURES"
LANGUAGE LANG_RUSSIAN, 0
{
 PUSHBUTTON "OK", IDOK, 10, 90, 40, 14, WS_TABSTOP
 PUSHBUTTON "Cancel", IDCANCEL, 60, 90, 39, 14
 LTEXT "Number of figures", -1, 20, 9, 70, 10
 LISTBOX IDC_LISTBOX1, 20, 20, 70, 40, LBS_NOTIFY | WS_BORDER | 
         WS_BORDER | WS_VSCROLL
 AUTOCHECKBOX """Psyho"" mode", IDC_CHECKBOX, 20, 71, 71, 10
}
#endif
Текст этого приложения можно взять здесь.

    При вызове настроек скринсейвера появляется окно:


Рис.1. Окно настроек хранителя экрана

в котором можно выбрать количество рисуемых фигур и режим их рисования.

    Следует обратить внимание на WinAPI-функции работы с реестром.

  LONG RegOpenKeyEx(HKEY hKey, LPCTSTR lpSubKey, DWORD ulOptions,
                REGSAM samDesired, PHKEY phkResult) 

    Функция открывает раздел реестра. Ее параметры:

    Если открытие произошло успешно, функция вернет ERROR_SUCCESS, в противном случае вернет ненулевой код ошибки, определенный в Winerror.h.

  LONG RegCreateKeyEx(HKEY hKey, LPCTSTR lpSubKey, DWORD Reserved,
      LPTSTR lpClass, DWORD dwOptions, REGSAM samDesired,
      LPSECURITY_ATTRIBUTES lpSecurityAttributes, PHKEY phkResult,
      LPDWORD lpdwDisposition);

    Функция создает раздел реестра. Если раздел уже существует, функция открывает его. Имена разделов не чувствительны к регистру.

    Если открытие произошло успешно, функция вернет ERROR_SUCCESS, в противном случае вернет ненулевой код ошибки, определенный в Winerror.h.

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

  LONG RegCloseKey(HKEY hKey); 
hKey - описатель открытого раздела, который подлежит закрытию.

    Если описатель успешно освобожден, функция возвращает ERROR_SUCCESS, в противном случае вернет ненулевой код ошибки, определенный в Winerror.h.

  LONG RegQueryValueEx(HKEY hKey, LPCTSTR lpValueName, 
    LPDWORD lpReserved, LPDWORD lpType, LPBYTE lpData, LPDWORD lpcbData) 

    Функция возвращает информацию о параметре раздела и значение этого параметра.

    Если lpData - NULL, а параметр lpcbData не нулевой, функция возвращает ERROR_SUCCESS и сохраняет размер данных в переменной, на которую указывает lpcbData.

    Если функция выполнена успешно, возвращается ERROR_SUCCESS, в противном случае возвращается ненулевой код ошибки, определенный в Winerror.h.

  LONG RegSetValueEx(HKEY hKey, LPCTSTR lpValueName, 
    DWORD Reserved, DWORD dwType, const BYTE* lpData, DWORD cbData) 

    Функция устанавливает значение параметра.

    Если функция выполнена успешно, возвращается ERROR_SUCCESS, в противном случае возвращается ненулевой код ошибки, определенный в Winerror.h.

    На следующем шаге мы рассмотрим создание приложения, изменяющего параметры шрифта.




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