Собственные программы ScreenSaver и закрепим максимум из пройдённого материала на полезных в реальной жизни примерах

 

Глава 25. Сплошная практика .................................................................................................. 606

 

 

25.1. Создание ScreenSaver .................................................................................................... 607

 

25.2. Компоненты в runtime ................................................................................................... 612

 

25.3. Тест на прочность.......................................................................................................... 617

 

25.4. Сохранение и загрузка теста. ....................................................................................... 628

 

25.5. Тестер. ............................................................................................................................ 631

 

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

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

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

Достаточно много дополнительного материала ты найдёшь на диске к этой книге. Я понимаю, что читать с монитора не так уж удобно, но я не хочу, чтобы эта книга была слишком дорогой. Моя Библия должна стать доступной каждому. При этом я постарался дать в ней максимум информации, при этом не просто заполнить компакт диск всякой ерундой, а наполнить его полезными вещами. Так что не поленись, и после прочтения книги загляни в директорию «Документация».

25.1. Создание ScreenSaver

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

главное, что никаких особых усилий не понадобится.

С одной стороны, эта часть должна была идти в главе по графике, потому что мы будем много рисовать. Но я решил её вставить сюда, потому что используемые здесь графические функции мы уже изучили. А вот само программирование будет очень интересным.

Для того, чтобы Delphi мог создать хранитель экрана, ты должен сделать несколько установок для своей формы:

1. 1. Перед объявлением типов вставить вот такую строку: {$D SCRNSAVE Saver}. Здесь Saver – имя нашего проекта, я его сохранил под именем Saver.dpr.

2. Свойство формы BorderStyle поменять на bsNone. Это означает, что у формы не должно быть никаких заголовков и обрамлений.

3. Все параметры в Border Icons установить в False.

2. 4. Свойство формы WindowState поменять на wsMaximized, чтобы окно появлялось максимизированным на весь экран.

 

5. Создать событие OnKeyPress и вставить туда всего лишь одну процедуру -Close()

– закрытие хранителя экрана по нажатию любой клавиши.

6. На событие FormActivate значения Left и Top нужно установить в ноль.

Теперь Delphi будек компилировать запускной файл, совместимый с хранителем экрана. Что такое ScreenSaver? Это та же программа, только с расширением scr. Так что остаётся одна деталь – изменить расширение на scr. Ты можешь после компиляции программы переименовать файл в *.scr или возложить этот тяжёлый труд на Delphi. Для этого необходимо выбрать пункт Option из меню Project и на закладке Application в строке Target file extension написать scr (рисунок 25.1.1). В этом случае Delphi сам подставить это расширение.

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

Для моего примера понадобится объявить три переменные в разделе private:

private { Private declarations } BGbitmap:TBitmap; DC : hDC; BackgroundCanvas : TCanvas;

Затем посмотрим на мой обработчик события OnCreate:

Procedure TForm1.FormCreate(Sender: TObject); begin BGbitmap:=TBitmap.Create;//Инициализация

DC := GetDC (0); BackgroundCanvas := TCanvas.Create; BackgroundCanvas.Handle…

Procedure TSaverForm.FormShow(Sender: TObject); begin if ParamCount>0 then begin if ParamStr(1)='/p' then

end; if ParamStr(1)[2]='c' then

Procedure TForm1.FormMouseDown(Sender: TObject; Button: TMouseButton; Shift: TShiftState; X, Y: Integer); var TempPanel:TPanel;//Объявляю переменную для панели

begin //Создаю панель. В скобках у Create указан будущий владелец TempPanel:=TPanel.Create(Form1);

TempPanel.Left:=X; //Устанавливаю левую и правую координату TempPanel.Top:=Y; //в X и Y позицию, где нажата кнопка мыши

TempPanel.Width:=20; //Устанавливаю ширину TempPanel.Height:=20; //Устанавливаю высоту

//Добавляю панель в контейнер CompList (CompList.Add) //и сохраняю результат в TempPanel.Tag TempPanel.Tag:=CompList.Add(TempPanel); Form1.InsertControl(TempPanel); //Вставляю панель на форму end; Для начала вспомним, что это за свойство Tag у компонента TPanel. Это просто целое значение, которое ты можешь…

Begin

end;

Обязательно нужно следить, чтобы количество и тип параметров точно совпадали с необходимыми. У каждого обработчика свои параметры и при объявлении процедуры вручную, нужно относиться к этому вопросу очень внимательно. Именно поэтому я советую тебе первый способ, с временной панелью, когда Delphi сам создаст обработчик для одной панели, а ты будешь использовать его для других.

Всё, панель готова и её надо сохранить в нашем контейнере CompList. Для этого нужно выполнить метод Add нашего контейнера, в качестве параметра передать ему нашу панель:

CompList.Add(TempPanel)

Этот метод добавит панель в контейнер и вернёт нам индекс компонента в контейнере. Этот индекс я сохраняю в свойстве Tag нашей панели TempPanel. Это свойство абсолютно не влияет на сам компонент, а мне этот индекс пригодится.

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

procedure TForm1.PanelMouseDown(Sender: TObject; Button: TMouseButton;

Shift: TShiftState; X, Y: Integer);

begin Label1.Caption:=IntToStr(TPanel(CompList.Items[TPanel(Sender).Tag]).Left); Label2.Caption:=IntToStr(TPanel(Sender).Left);

end;

Здесь две строки. Обе строки выполняют одно и тоже, но по-разному. Обе строки записывают в свой ТLabel левую позицию панели, по которой ты щёлкнул.

Первая строка, чтобы получить левую позицию панели использует CompList, а вторая работает с панелью напрямую. Рассмотрим сначала вторую строку. В ней основным является выражение TPanel(Sender).Left. Sender -передаётся нам процедурой обработчиком PanelMouseDown. В нём записан указатель на объект, который сгенерировал событие OnMouseDown. В нашем случае это будет указатель на панель, по которой ты щёлкнул. Так как мы точно уверены, что это панель, то мы так и показываем TPanel(Sender). Этим мы приводим Sender к TPanel и теперь ты можешь использовать все свойства и методы панели, для примера нам достаточно свойства Left. Если бы мы знали точное имя панели, то этого писать не пришлось бы. Но это невозможно, потому что все создаваемые в Runtime панели (а их можно создать любое количество) у нас используют один обработчик нажатия мышкой, и мы не знаем, по какой именно панели был произведён щелчок. Получив значение левой позиции, мы переводим целое значение левой позиции в строку с помощью IntToStr.

Первоя строка очень похожа на вторую, только внутри TPanel() мы используем не Sender, а CompList.Items[TPanel(Sender).Tag], т.е. значение из конейнера. Чтобы получить первое значение из контейнера, нужно написать CompList.Items[0], для второго CompList.Items[1], для третьего CompList.Items[2] и т.д. Но по какой именно панели произведён щелчок? Чтобы это узнать я пишу TPanel(Sender).Tag, то есть получаю свойство Tag (там хранятся индекс панели) панели сгенерировавшей событие. Далее, всё происходит так же.

Запусти пример и пощёлкай по форме. По каждому щелчку будут создаваться панели. Потом попробуй пощёлкать по самим панелям. На двух TLabel будут появляться значения левой позиции панель, по которым ты щёлкал.

Теперь я хочу тебе показать ещё несколько интересных свойств и методов, которые есть у контейнера TList:

Count – в этом свойстве храниться количество элементов в контейнере.

Items – здесь хранятся ссылки на элементы контейнера. Для доступа к ссылкам

нужно написать Items[Индекс элемента]. Clear – очистить список. Delete – удалить элемент из списка. В качестве единственного параметра нужно

указать индекс удаляемого элемента. Exchange – поменять в контейнере местами два элемента. Здесь два параметры – индексы меняемых местами компонентов. First – получить указатель на первый элемент списка. Это то же самое, что и записать Items[0].

IndexOf – получить индекс указанного в качестве параметра объекта. Допустим, что ты знаешь объект (TPanel) и хочешь узнать, под каким индексом он расположен в контейнере. В этом случае ты можешь написать следующий код: CompList.IndexOf(Panel1). Если такой панели не найдено в списке, то тебе будет возвращено значение –1, иначе правильный индекс указанной панели.

Insert – вставить новый элемент. У этого метода два параметра – индекс, под которым надо вставить элемент и сам элемент. Last – получить последний элемент списка. Это то же самое, что использовать свойство Items[Count - 1]. Move – переместить элемент в новое место. У метода два параметра – индекс элемента, который надо переместить и индекс, который должен получить элемент.

Pack – при удалении элементов из контейнера, они просто помечаются, как нулевые. Выполняя этот метод, все нулевые элементы уничтожаются, и занятая ими память освобождается.

Remove – удалить элемент. В качестве параметра нужно указывать элемент, который надо удалить, например CompList.Remove(Panel1).

Давай добавим в наш пример возможность удаления панели с формы и из контейнера. Это будет происходить по нажатию правой кнопки мышки по компоненту. Добавь с конец процедуры PanelMouseDown следующий код:

If Button=mbRight then

for i:=index to CompList.Count -1 do… В разделе varэтой процедуры нужно объявить две переменные index и i. Обе они будут числами целого типа.

Private

{ Private declarations }

procedure ShowHint(Sender: TObject);

Теперь нажми Ctrl+Shift+C, чтобы создать заготовку для этой процедуры. В ней нужно написать следующее:

Procedure TTestEditorForm.ShowHint(Sender: TObject); begin

Здесь я отображаю текст текущей подсказки, который находиться в свойстве Hint объекта Application на нулевой панели строки состояния. У меня строка… Теперь сделай нашу главную форму многодокументной. Для этого в свойстве… Можешь сразу же создать окно «О программе». Я это не буду описывать, потому что тут фантазия у каждого работает…

Procedure TEditQuestionForm.NewResultButtonClick(Sender: TObject); var

Str:String; begin

Str:='';

If InputQuery('Новый ответ', 'Введите текст ответа:', Str) then

Здесь я объявил одну строковую переменную. В первой строке кода ей присваивается пустая строка. Затем я отображаю на экране стандартный диалог ввода… 1. 1. Текст, который будет отображаться в заголовке окна. 2. 2. Текст, который будет отображаться в окне, рядом со строкой ввода.

Добавляю в структуру варианты ответов for i:= 0 to NewQuest.ResultCount-1 do

begin NewQuest.ResiltText[i]:=EditQuestionForm.ResultListBox.Items.Strings[i]; NewQuest.ResiltValue[i]:=EditQuestionForm.ResultListBox.Checked[i];

end; QuestionList.Add(NewQuest);

Добавляю новый элемент в дерево вопросов with QuestionTreeView.Items.Add(nil, NewQuest.Name) do

end; end; В самом начале я очищаю элементы управления окна EditQuestionForm. Потом я отображаю это окно, и если пользователь…

Запускаю цикл, по которому заполняются данные списка for i:=0 to PQuestion(node.Data).ResultCount-1 do with ResultView.Items.Add do

begin SubItems.Add('Да'); ImageIndex:=2;

If NewTestForm.TestTypeBox.ItemIndex=0 then

+QuestionResultForm.ProjectName; end; end; Здесь в первой строке я показываю окно создания нового проекта. Если… Обработчики события кнопок «Редактировать» и «Удалить» вопросы я расписывать не буду, а только приведу их код с…

PQuestion(QuestionList[QuestionTreeView.Selected.Index]).Name

По нажатию кнопки «Удалить» ты должен написать следующий код: procedure TQuestionResultForm.DeleteButtonClick(Sender: TObject); var index,… //Подтверждение удаления if Application.MessageBox(PChar('Вы действительно хотите удалить - '+…

Begin //Если актиыное дочернее окно равно нулю //(нет активных окон), то выход if ActiveMDIChild=nil then exit; //Если окно имеет имя QuestionResultForm, то это //вопрос-варианты ответов и вызываем для сохранения //процедуру SaveTest1. if ActiveMDIChild.Name='QuestionResultForm' then

SaveTest1; end;

Свойство ActiveMDIChild всегда указывает на активное в данный момент дочернее окно. Прежде чем использовать это свойство, его желательно сравнивать со значением nil, потому что в данный момент может вообще не быть ни одного дочернего окна. В этом случае, при обращении к свойству может произойти критическая ошибка.

Процедура SaveTest1 должна выглядеть следующим образом:

procedure TTestEditorForm.SaveTest1;

var fs:TFileStream; i:Integer; Str:String[5];

Begin //Если у активного окна в свойстве FileName пусто, //то нет имени файла и нужно вызвать обработчик события //меню "Сохранить как...", чтобы появилось окно ввода //имени файла if TQuestionResultForm(ActiveMDIChild).FileName='' then

begin
SaveAsMenuClick(nil);
exit;

end;

//Создаю новый файл. Если он уже существовал, то его
//содержимое будет уничтожено
fs:=TFileStream.Create(TQuestionResultForm(ActiveMDIChild).FileName, fmCreate);

//Сохраняю в начале файла текст "Тест", чтобы по нему потом
//определить к чему относиться данный файл.
Str:='Тест';
fs.Write(Str, SizeOf(Str));

//Сохранить имя проекта fs.Write(TQuestionResultForm(ActiveMDIChild).ProjectName, sizeof(TQuestionResultForm(ActiveMDIChild).ProjectName));

try
//Сохранить количество вопросов
fs.Write(TQuestionResultForm(ActiveMDIChild).QuestionList.Count,

sizeof(TQuestionResultForm(ActiveMDIChild).QuestionList.Count));

//Запускаю цикл, в котором сохраняються все вопросы. for i:=0 to TQuestionResultForm(ActiveMDIChild).QuestionList.Count-1 do fs.Write(PQuestion(TQuestionResultForm(ActiveMDIChild).QuestionList[i])^, sizeof(TQuestion));

finally
//Закрыть файл
fs.Free;

end; end;

Здесь всё очень просто и с кодом можно разобраться по комментариям. Единственное, на что я хочу обратить внимание – наша структура PQuestion находится в динамической памяти, поэтому при сохранении нужно указывать знак разыменования ^. Если этого знака не указать, то в файл сохраниться адрес структуры, а не сама структура. В этом случае при чтении данных из файла мы прочитаем адрес, но по этому адресу ничего хорошего не будет, потому что после первой же перезагрузки программы память очиститься и сама структура уничтожиться. Поэтому, для сохранения данных по адресу, а не самого адресу нужно указывать знак ^.

Обработчик события для пункта меню «Сохранить как…» ещё проще:

Procedure TTestEditorForm.SaveAsMenuClick(Sender: TObject); begin if SaveDialog1.Execute then

end; end; Здесь я отображаю окно выбора имени файла. Если пользователь что-то выбрал, то… Теперь посмотри на обработчик события OnClick для кнопки «Открыть» проект:

Создаю новый элемент в дереве with QuestionResultForm.QuestionTreeView.Items.Add(nil, NewQuest.Name) do

end; end;

Type PQuestion=^TQuestion; TQuestion=record

Name: String[255];
ResultCount:Integer;
ResiltText: array[0..10] of String[255];
ResiltValue: array[0..10] of boolean;

end;

В разделе privateобъяви следующие переменные:

Private

QuestionList:TList; Question, QuestionNumber, FalseNumber:Integer; FileName:String; Разберём, для чего нужны эти переменные:

Procedure TTestForm.RunButtonClick(Sender: TObject); begin

QuestionNumber:=0; FalseNumber:=0; NextButton.Enabled:=true;