Блокнот - шифратор
Далее обработаем команды меню "Правка". Они очень простые, всего на команду придется ввести по одной строке кода, так что дополнительные комментарии не нужны. Меню "Правка->Отменить":
procedure TfMain.EditCancelClick(Sender: TObject); begin Memo1.Undo; end; "Правка->Вырезать" не сложнее: procedure TfMain.EditCutClick(Sender: TObject); begin Memo1.CutToClipboard; end; "Правка->Копировать": procedure TfMain.EditCopyClick(Sender: TObject); begin Memo1.CopyToClipboard; end; "Правка->Вставить": procedure TfMain.EditPasteClick(Sender: TObject); begin Memo1.PasteFromClipboard; end; "Правка->Удалить": procedure TfMain.EditDeleteClick(Sender: TObject); begin Memo1.ClearSelection; end; "Правка->Выделить всё": procedure TfMain.EditSelectAllClick(Sender: TObject); begin Memo1.SelectAll; end;
С пунктом "Правка" разобрались, вернемся к пункту "Формат". У нас остались необработанными два подпункта - "Шрифт" и "Цвет". Процедура OnClick для пункта "Формат->Шрифт":
procedure TfMain.FormatFontClick(Sender: TObject); begin //сначала диалогу присваиваем шрифт как у Memo: FD.Font:= Memo1.Font; //если диалог прошел успешно, меняем шрифт у Memo: if FD.Execute then Memo1.Font:= FD.Font; end;
Здесь мы сначала свойству Font диалога присвоили такой же шрифт, как и у Memo1. Зачем нужно было это делать? Если бы мы этого не сделали, диалог не знал бы, какой шрифт считается "текущим", там была бы пустая строка. И пользователю сложно было бы разобраться, какой шрифт, какого размера он сейчас использует. Теперь же, при вызове диалога, сразу будет выделен наш шрифт Times New Roman, 12. Если же пользователь изменит этот шрифт, то свойству Font компонента Memo1 будет присвоен новый шрифт.
Примерно также обрабатываем и пункт "Формат->Цвет", только здесь мы обращаемся к свойству Color и диалогу CD:
procedure TfMain.FormatColorClick(Sender: TObject); begin //сначала устанавливаем цвет диалога, как у Мемо: CD.Color:= Memo1.Color; //если диалог прошел успешно, меняем цвет у Memo: if CD.Execute then Memo1.Color:= CD.Color; end;
Теперь перейдем к более сложному пункту меню "Файл". Сначала самая простая команда, "Файл->Выход":
procedure TfMain.FileExitClick(Sender: TObject); begin Close; end;
Команда "Файл->Сохранить как" чуть сложнее:
procedure TfMain.FileSaveAsClick(Sender: TObject); begin {Переписываем заголовок окна диалога, иначе он выйдет на английском. Если сохранение произошло, то свойство Modified у Memo переводим в false, так как все изменения уже сохранены} SD.Title:= 'Сохранить как'; if SD.Execute then begin Memo1.Lines.SaveToFile(Utf8ToSys(SD.FileName)); Memo1.Modified:= false; end; //if end;
Здесь мы вначале изменили заголовок. Почему-то диалог, хоть и имеет по умолчанию текст в Title на русском языке, в работающей программе все равно выходит по-английски, хотя диалоги шрифта и цвета выводят нормальный русский заголовок. Может быть, в будущих версиях Lazarus эта недоработка будет устранена? Пока же будем менять заголовки внутри кода. Далее мы сохраняем текст из Memo1 в файл, причем делаем это не так, как обычно:
Memo1.Lines.SaveToFile(SD.FileName);
а так:
Memo1.Lines.SaveToFile(Utf8ToSys(SD.FileName));
Зачем такие усложнения? Дело в том, что имя файла (свойство FileName диалога) сохраняется в формате UTF8, а процедура SaveToFile требует формата ANSI. Это не проблема, если вы будете давать файлу имя исключительно латинскими буквами или цифрами. Но если вы попробуйте сохранить файл с именем под русскими символами (или любыми неанглийскими), то получится то, что называют "кракозябрами" - непонятная абракадабра вместо имени. Такой файл и открыть потом не получится. Если же преобразовать имя файла в нужный формат, это гарантирует правильное сохранение (загрузку) этого файла, на каком бы языке вы не написали его имя.
Далее мы устанавливаем в False свойство Modified компонента Memo1. Это важно! Свойство Modified становится True, если в Memo1 есть какие-то изменения. Если же мы эти изменения сохранили в файл, будем считать, что других изменений пока нет. Нам придется еще проверять состояние этого свойства.
Теперь обработаем команду "Файл->Сохранить":
procedure TfMain.FileSaveClick(Sender: TObject); begin {если имя файла известно, то не нужно вызывать диалог SaveDialog, просто вызываем метод SaveToFile. } if SD.FileName <> '' then begin Memo1.Lines.SaveToFile(Utf8ToSys(SD.FileName)); //устанавливаем Modified в false, так как изменения уже сохранили: Memo1.Modified:= false; end //if //иначе имя файла не известно, вызываем Сохранить как...: else FileSaveAsClick(Sender); end;
Смотрите, что тут происходит. Мы смотрим, есть ли какой-нибудь текст в свойстве FileName диалога SD. Если текст есть (свойство FileName не равно пустой строке), значит, диалог SD уже вызывался, или же открывался существующий файл. В любом случае, мы знаем имя файла, куда нужно сохранять данный текст. Сохраняем, переводим в False свойство Modified нашего Memo1. Если же там текста нет, значит, текст новый, еще ни разу не сохранялся. В этом случае, нам нужно вызвать метод OnClick команды "Файл->Сохранить как". Обратите внимание, как мы это делаем:
FileSaveAsClick(Sender);
Дело в том, что в качестве параметра в метод нужно передать объект, откуда этот метод был вызван. Переменная Sender имеет тип TObject и обычно указывает на этот объект. Мы могли бы указать и какой то конкретный объект, например, так:
FileSaveAsClick(Memo1);
В данном случае не будет никакой разницы, команда все равно сработает одинаково.
Команда "Файл->Создать" чуть сложнее:
procedure TfMain.FileCreateClick(Sender: TObject); begin {Если есть изменения текста, спросим пользователя, не хочет ли он сохранить их перед созданием нового текста} if Memo1.Modified then begin //если пользователь согласен сохранить изменения: if MessageDlg('Сохранение файла', 'Текущий файл был изменен. Сохранить изменения?', mtConfirmation, [mbYes, mbNo, mbIgnore], 0) = mrYes then FileSaveClick(Sender); end; //if //теперь очищаем Мемо, если есть текст: if Memo1.Text <> '' then Memo1.Clear; //в SaveDialog убираем имя файла. это будет означать, что файл не сохранен: SD.FileName:= ''; end;
Когда пользователь выбирает команду "Файл->Создать", в Memo уже мог быть какой-то текст. Он нужен пользователю, или нам просто нужно очистить Memo, и подготовить его к новому тексту? Для этого, в случае, если у Memo есть изменения, мы выводим запрос-сообщение MessageDlg(). Если пользователь желает сохранить старый текст, мы вызываем команду "Файл->Сохранить". И в любом случае, затем мы очищаем Memo, если там что-то есть, и очищаем свойство FileName у диалога SD (TSaveDialog). Ведь текст новый, и мы еще не знаем, куда пользователь захочет его сохранить, верно?
Код команды "Файл->Открыть" самый длинный, хотя и в нем ничего сложного нет:
procedure TfMain.FileOpenClick(Sender: TObject); begin //проверка необходимости сохранения файла, как в Файл->Создать: if Memo1.Modified then begin //изменения есть //если пользователь согласен сохранить изменения: if MessageDlg('Сохранение файла', 'Текущий файл был изменен. Сохранить изменения?', mtConfirmation, [mbYes, mbNo, mbIgnore], 0) = mrYes then FileSaveClick(Sender); end; //if //очищаем имя файла у диалога OpenDialog, изменяем заголовок, и //вызываем метод LoadFromFile, если диалог состоялся OD.FileName:= ''; OD.Title:= 'Открыть существующий файл'; if OD.Execute then begin //очищаем Мемо, если есть текст: if Memo1.Text <> '' then Memo1.Clear; //читаем из файла Memo1.Lines.LoadFromFile(Utf8ToSys(OD.FileName)); //копируем имя файла в диалог SaveDialog, чтобы потом знать, //куда сохранять: SD.FileName:= OD.FileName; end; //if end;
В первой части процедуры мы смотрим, нет ли изменений в Memo, ведь пользователь мог редактировать один файл, а затем захотел открыть другой! Нужно ли сохранять эти изменения, если они есть? Как и в прошлом примере, для этого мы выводим запрос MessageDlg(). Сохраняем, если пользователь этого хочет.
Далее мы очищаем свойство FileName у диалога открытия файла. Зачем? Ладно, если этот диалог вызывается первый раз, тогда это свойство будет пусто. А если это не первый файл, который открывается пользователем? Если свойство не очистить, то при вызове диалога прежний файл будет там по умолчанию. Но пользователь то хочет открыть другой файл! Поэтому мы предварительно очищаем это свойство, а уж потом вызываем диалог. В коде мы изменяем заголовок и у этого диалога, иначе он тоже выйдет на английском языке.
Затем мы чистим Memo, если там есть старый текст, и загружаем новый текст из нового файла, после чего копируем имя этого файла в диалог сохранения, чтобы знать, куда сохранять изменения при выборе команды "Файл->Сохранить".
Теперь мы с вами должны предусмотреть еще вот что. Допустим, пользователь что-то записал в блокнот, а потом решил его закрыть. Изменения есть, а надо ли их сохранять? Нужно спросить об этом у пользователя. Это можно было бы сделать в команде "Файл->Выход", но мы этого не сделали. Почему? А если пользователь закроет программу не этой командой, а кнопкой с крестиком в верхней правой части окна? Или кнопками <Alt+F4>? Вот чтобы обработать закрытие программы, каким бы образом пользователь её не закрывал, воспользуемся событием OnClose формы главного окна, которое возникает ПЕРЕД закрытием программы. Проблема в том, что компонент Memo1 закрывает всю форму, а нам нужно ее выделить. Выделить форму fMain можно либо в верхней части окна Инспектора объектов, где все объекты отображены в виде дерева - форма там расположена в самом верху, как главный, родительский компонент. Либо можно сделать проще - выделите Memo1 и нажмите <Esc>, при этом выделение перейдет на внешний к Memo1 компонент, то есть, к форме. Затем перейдите на вкладку События Инспектора объектов, и сгенерируйте событие OnClose. Его код вам уже знаком по предыдущему коду:
procedure TfMain.FormClose(Sender: TObject; var CloseAction: TCloseAction); begin {Если есть изменения текста, спросим пользователя, не хочет ли он сохранить их перед созданием нового текста} if Memo1.Modified then begin //если пользователь согласен сохранить изменения: if MessageDlg('Сохранение файла', 'Текущий файл был изменен. Сохранить изменения?', mtConfirmation, [mbYes, mbNo, mbIgnore], 0) = mrYes then FileSaveClick(Sender); end; //if end;Листинг .
Теперь каким бы способом пользователь не закрывал программу, будет проверяться - нет ли изменений в Memo, и если есть, выйдет запрос - сохранять ли их. Практически всё, что умеет делать стандартный Блокнот, умеет теперь и наша программа. Самое время заняться шифрованием.
Код "Кодирование->Шифровать" очень простой:
procedure TfMain.CoderCodeClick(Sender: TObject); begin //сначала очистим ключ: MyCript.MyPassword:= ''; if InputQuery('Ввод ключа', 'Введите ключевое слово (фразу):', MyCript.MyPassword) then Memo1.Text:= MyCript.Write(MyCript.Encrypt(Memo1.Text)); end;
Вначале мы очищаем ключ. Делается это на всякий случай, вдруг это уже не первое шифрование, тогда в глобальной переменной MyPassword будет храниться предыдущий ключ. Или представьте такую ситуацию: пользователь вводил личный текст, ему понадобилось отлучиться, и он его зашифровал. Если ключ в переменной сохраняется, любой может подойти и расшифровать его. Элементарная безопасность требует, чтобы мы всегда очищали ключ.
Затем, с помощью InputQuery() мы выводим запрос, в котором пользователь может ввести пароль - ключевое слово или фразу. Этот ключ мы помещаем в переменную MyPassword, после чего вызываем шифрацию текста Memo так, как было указано в рекомендациях модуля MyCript. Дешифрация ("Кодирование->Дешифровать") происходит похожим образом:
procedure TfMain.CoderDecodeClick(Sender: TObject); begin //сначала очистим ключ: MyCript.MyPassword:= ''; if InputQuery('Ввод ключа', 'Введите ключевое слово (фразу):', MyCript.MyPassword) then Memo1.Text:= MyCript.Decrypt(MyCript.Read(Memo1.Text)); end;
Вот и весь шифратор. Сохраните его, скомпилируйте и опробуйте в работе. Имейте в виду, что если ввести неправильный пароль, то текст будет дешифрован неправильно и не прочитается. Обратного действия не предусмотрено в тех же целях безопасности. И ещё: осталась необработанной команда меню "Справка->О программе", мы вернемся к ней в другой лекции, где будем изучать многооконные приложения, так что не удаляйте пока этот проект. Он нам также понадобится в лекциях "Создание справочной системы" и "Создание инсталлятора" , где мы будем создавать для этого проекта справочную систему и инсталлятор.