※コメントにてコードの誤りをご指摘いただいたのでコード修正しています。
半年前に iOSやAndroidで入力しやすい記号だけを含むようにランダムパスワードを作る という記事を書きましたが、このときはシェルスクリプトによる実装や KeePass の設定調整の例を紹介しました。
しかし、どちらの方法も iOS や Android の実機で動かすことはできません。それはイマイチなので、iOS や Android 向けのネイティブアプリを Delphi で書いてみることにしました。
なお、Delphi で iOS と Android 向けにアプリを実装する場合は基本的にコードをほとんど共用できるのですが、OSやデバイスに依存する処理は個別対応が必要な場合があります。今回の場合は等幅フォントの選択に関する部分でOS依存を IFDEF で書き分けています。またDelphiでWindows向けの開発経験がある場合にモバイル向け開発で注意すべき点についても触れています。
要件
こんな要件で動くアプリを作ることにします。
- iOS, Android で動くアプリとして実装する
- 指定した文字列長のランダム文字列なパスワードを生成する
- パスワード候補を複数件出力する
- 出力された候補の中から選んだ文字列は自動的にクリップボードにコピーする
実際につくってみたもの
iOSとAndroid向けに、こんな画面のアプリを試作してみました。
画面上部の3つの数値は、生成するパスワードの文字数のセレクターです。大抵の場合は8文字、16文字、32文字のパスワードを生成すれば事足りますので、その3種類を生成できるようにしました。
その下の1行は、選択した文字列を表示するフィールドとしています。
そして画面の大半の部分には、候補となるパスワード文字列を列挙してみました。この候補から選んだものがクリップボードに自動的にコピーされます。気に入らない場合は画面上部のセレクターを再度クリックすれば、候補が再生成されます。
Delphi の編集画面と、コンポーネントの配置
Delphi の IDE 上では、アプリの画面はこんなふうにデザインされています。
画面デザインの部品(コンポーネント)は、以下のように配置しています。
実装上の注意
今回の実装は大きく分けると下記の3つが行われています。
- OSやコンパイラ依存の書き分け
- 生成したランダム文字列を ListBox に追加する処理
- 選択された文字列をクリップボードへのコピーする処理
OS依存の書き分け
OS 依存の書き分けとして、使用するフォントをOSごとに変えています。これはそれぞれのOSでサポートされている等幅フォントを適切に選択することを意図しています。
const
// アルファベットを等幅フォントで表示するための調整
// Android は serif-monospace、
// iOS では Courier New を用いる
{$IF Defined(Android)}
FONTFAMILY='serif-monospace';
{$ELSE}
FONTFAMILY='Courier New';
{$ENDIF}
end;
DelphiでWindows向け開発経験がある方向けの注意
文字列に対する操作は配列やPosではなく TStringHelper を使うようにしてください。
というのも、DelphiでWindowsアプリを開発する場合は、文字列のインデックスは 1 から始まりましたが、モバイル向けのコンパイラではインデックスが 0 から始まるのです。
この違いを IFDEF で吸収しようとすると大変面倒ですが、これを良い感じにやってくれるヘルパークラスとして、TStringHelper が用意されています。ヘルパークラスを適切に用いて書いたコードは、Delphi がサポートする全てのプラットフォーム(Windows, macOS, Linux, iOS, Android) で同じように動くことが保証されます。
その他にも、DelphiでWindows向けに書かれたコードをiOS/Android向けでも使えるようにする場合の注意点はいろいろありますが、それは下記のページに出ています。こちらもご一読頂くとよいでしょう。
そして今後のことを考えると、Windows でしか使用しないアプリの開発でもヘルパークラスを用いることが望まれます。
実際のコード
特に複雑な箇所は有りません。ButtonのOnClickでランダム文字列を作ったり、ListBoxのOnClickでクリップボードにコピーしているだけです。
unit Unit1;
interface
uses
System.SysUtils, System.Types, System.UITypes, System.Classes, System.Variants,
FMX.Types, FMX.Controls, FMX.Forms, FMX.Graphics, FMX.Dialogs, FMX.StdCtrls,
FMX.EditBox, FMX.NumberBox, FMX.Edit, FMX.ScrollBox, FMX.Memo,
FMX.Controls.Presentation,FMX.Platform, FMX.Layouts, FMX.ListBox;
type
TForm1 = class(TForm)
Panel1: TPanel;
Edit1: TEdit;
ListBox1: TListBox;
Button1: TButton;
Button2: TButton;
Button3: TButton;
procedure ListBox1ItemClick(const Sender: TCustomListBox;
const Item: TListBoxItem);
procedure Button1Click(Sender: TObject);
procedure Button2Click(Sender: TObject);
procedure Button3Click(Sender: TObject);
private
procedure GeneratePassword(PasswordLength: Integer);
{ private 宣言 }
public
{ public 宣言 }
const
// アルファベットを等幅フォントで表示するための調整
// Android6 は serif-monospace、
// iOS では Courier New を用いる
{$IF Defined(Android)}
FONTFAMILY='serif-monospace';
{$ELSE}
FONTFAMILY='Courier New';
{$ENDIF}
end;
var
Form1: TForm1;
implementation
{$R *.fmx}
// Reload ボタンが押されたらパスワード文字列を再生成する
procedure TForm1.Button1Click(Sender: TObject);
begin
GeneratePassword(8);
end;
procedure TForm1.Button2Click(Sender: TObject);
begin
GeneratePassword(16);
end;
procedure TForm1.Button3Click(Sender: TObject);
begin
GeneratePassword(32);
end;
procedure TForm1.GeneratePassword(PasswordLength: Integer);
var
SourceString: String;
ExcludeString: String;
RandomCharacter: String;
RandomPassword: String;
NumPassword: Integer;
begin
Randomize;
// 本来は const 扱いにすべきであるが、
// もうちょっと真面目にパスワード生成ツールとして実装するならば
// これらは可変文字列となり得るので、ここでは変数で扱うことにする。
SourceString := 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789:!?''"()@';
ExcludeString := '0O1lI|';
if Length(SourceString) = 0 then exit;
ListBox1.Items.Clear;
// 20件のランダム文字列を生成してListBoxに追加する
for NumPassword := 1 to 20 do
begin
RandomPassword := '';
repeat
// SourceStrigからランダムに1文字ピックアップする処理
// Windows向けの古い Delphi の実装ではこのように書きたくなるけど、配列ではなく SubString を使う。
// RandomCharacter := SourceString[Random(Length(SourceString)) + 1 ];
RandomCharacter := SourceString.Substring(Random(Length(SourceString)),1);
// 除外指定の文字が含まれていない場合はパスワード文字列として採用する
// Windows向けの古い Delphi の実装だとこう書きたくなるけど、今後は Pos ではなく IndexOf を使う方がよい。
// if Pos( RandomCharacter, ExcludeString) = 0 then
if ExcludeString.IndexOf(RandomCharacter) = -1 then
RandomPassword := RandomPassword + RandomCharacter;
until (Length(RandomPassword) = PasswordLength);
ListBox1.Items.Add( RandomPassword );
end;
// ListBox の各要素のフォント設定を変える
for NumPassword :=0 to ListBox1.Items.Count-1 do
with ListBox1.ListItems[NumPassword] do
begin
StyledSettings := [];
TextSettings.Font.Family := FONTFAMILY;
TextSettings.Font.Size := 20;
end;
end;
// ListBox で選択した項目をクリップボードにコピーする処理
procedure TForm1.ListBox1ItemClick(const Sender: TCustomListBox;
const Item: TListBoxItem);
var
ClipboardService: IFMXClipboardService;
begin
Edit1.Text := ListBox1.Items[ListBox1.ItemIndex];
if TPlatformServices.Current.SupportsPlatformService(IFMXClipboardService, IInterface(ClipboardService)) then
ClipboardService.SetClipboard( Edit1.Text );
end;
end.
iOS, Android向けのビルド
Delphi Professional + mobile Add-on pack があれば iOS, Android 向けにビルドして実機で動かすことが可能です。この場合の環境構築や手順は下記ページで紹介されているビデオやブログ記事を参照してください。