電子ノートの全体的な流れは次のようになっています。画面下に表示してあるのは、それぞれ使用しているTable名になっています。

図2-1 システム構成図
プログラムを起動した際に、この画面が現れます。ここからノートを作成する画面、教科情報を登録する画面、検索画面、ノートの内容を印刷する画面、メール送信用の画面にそれぞれ移動することができます。
このフォームでは、講義内容を入力・保存ができます。日付はMaskEditを用いたことで、入力ミスを防ぐことができます(後述)。ほかにノートに題をつける欄や、キーワードを入力する欄を5つ設けてあります。また、簡単な文字飾り機能やマージンなどの編集機能をつけることで、ノートの内容にアクセントをつけられるようになりました。
ここで、教科名・教員名・コードを登録しておくことで、ノート作成時にコンボボックスで選択が可能になります。
ここでは登録した教科名・教員名・日付などにより、膨大な量のデータであっても各項目ごとに絞り込んでノートを検索することができます。また、こちらの画面から、キーワード検索画面へ移動できます。
ノートの内容をレポート形式にして、印刷することができます。印刷する時間を表示させるようにしました。
ノートの内容をメールで送信することができます。教員に提出物などがあるときに使います。
調べたい単語があった時に講義名や日付などを覚えていなくても、ノートを作成する際、キーワード項目にいくつかポイントとなる単語を入力しておけば,この画面でそのキーワードが含まれているノートを探し出すことができます。
このシステムではノートの内容、教科情報、メールアドレス、そして検索用の空のテーブルとして4つのテーブルを使用しています。ノート内容が格納されるテーブル構造は次のようになります。

図2-2 notebook.db
subject には教科名がはいります。日本語で入力すると1文字につき2バイト必要と成りますので、現在社会情報学部において、教科名が一番長い「情報ネットワーク・マルチメディア論」を対象にすると文字数は17文字となりますが、今後新たに講座が開講されることなども考慮して、20文字に設定してあります。
teacherには教員名が入ります。これはフルネームで入力できるように10文字で設定してあります。
memoフィールドは書式が含まれるため、型も書式付メモ型を使用します。
また、メールアドレスが保存されるTableは次のようになります。

図2-3 mail.db
また、検索用の空のテーブルはnotebook.db(図2-2)とまったく同じ構造になっており、教科情報のテーブルはsubject,teacher,s_codeの3つで構成されています。
ここでは3つのEditにそれぞれ、教科名、教員名、コードを入力していきます。もし1つでも入力されていないEditがあればメッセージボックスが現れ、再度入力させるプログラムになっています。また、コードについては重複が許されないので、データベース内のデータと照合させ、なければ登録となります。プログラムは次のようになります。
コードは2文字以内の英小文字で入力します。FileNameはノート作成画面内において、コードと日付から作成されます。ユーザーがコードを覚えていなくてもノートの検索は行えますが、FileNameがわかればテキスト文書が他のアプリケーションでも使用可能となります。そのため、ユーザー側にとっても把握できるように教科名に関連づいたコードの登録が求められます。
この画面において講義内容の全ての情報が登録できます。はじめに教科名をコンボボックスで選択できるようにするためのプログラムの解説をします。
このコンボボックスは、教科登録画面で登録したデータが入っているTable3を参照しており、Addを用いてItemsに値を代入しています。教員名の場合、重複することがあるので、同じ教員名は表示させないようにfor文を用いてItemsに代入する値をを制限しています。また、日付ではMaskEditを使用していますが、このコンポーネントはマスク編集コントロールをフォーム上に配置できます。これにより、日付の入力ミスを防ぐことができます。
次に保存ボタンを押した時の処理の解説をします。
procedure TNoteForm.RichEdit1Change(Sender: TObject);
begin
Caption := RichEdit1.Lines.Strings[0];
end;
// ----------------------------------------------------------------------------
// 保存ボタンを押したときの処理
procedure TNoteForm.Button1Click(Sender: TObject);
var
FileName, Path, day :AnsiString;
ans: Word;
begin
// 実行されたFile名のフルパスを得る
Path := ExtractFilePath( Application.ExeName );
// カレントディレクトリを実行ディレクトリに移動する
ChDir(Path);
if IOResult <> 0 then
MessageDlg('Cannot Find directory' , mtWarning, [mbOK], 0);
// リッチテキストを保存するフォルダがあるかを調べて、なければ作成
if not DirectoryExists('Text') then
mkdir('Text');
Chdir('Text');
// FileNameを決める
DataModule1.Query1 . Open;
day := StringReplace(MaskEdit1.Text, '/', '', [rfReplaceAll] );
FileName := Edit3.Text + day +'.rtf';
// FileNameと同じFile名があるかどうか調べる
if FileExists(FileName) then
begin
ans:= MessageDlg('同じファイルが存在します。上書き保存してもいいですか?',
mtWarning, mbYesNoCancel, 0);
case Ans of
mrYes : // 上書き保存
begin
RichEdit1.PlainText := False;
RichEdit1.Lines.SaveToFile(FileName);
DataModule1.Query1.Close;
end;
mrCancel:
begin
exit;
noteform.Free;
close;
end;
mrNo:
end;
end else
begin // 同じファイル名がないときの保存処理
DataModule1.Query1 . Open;
DataModule1.Query1.Append;
DataModule1.Query1.Edit;
DataModule1.Query1.FieldByName('FileName') . AsString := FileName;
DataModule1.Query1.FieldByName('title').AsString := Edit6.Text;
DataModule1.Query1.FieldByName('subject').AsString := Combobox2.Text;
DataModule1.Query1.FieldByName('s_code').AsString := Edit3.Text;
DataModule1.Query1.FieldByName('teacher').AsString := ComboBox3.Text;
DataModule1.Query1.FieldByName('keyword1').AsString := Edit7.Text;
DataModule1.Query1.FieldByName('keyword2').AsString := Edit8.Text;
DataModule1.Query1.FieldByName('keyword3').AsString := Edit9.Text;
DataModule1.Query1.FieldByName('keyword4').AsString := Edit10.Text;
DataModule1.Query1.FieldByName('keyword5').AsString := Edit11.Text;
DataModule1.Query1.FieldByName('day').AsString := MaskEdit1.Text;
RichEdit1.PlainText := False;
RichEdit1.Lines.SaveToFile(FileName);
DataModule1.Query1.Post;
DataModule1.Query1.Close;
end;
DataModule1.Query1 . close;
noteForm.close;
end;
書式付のデータを保存する際に、直接データベースに格納しても書式の内容までは保存されません。そのため、リッチテキスト形式としてSaveToFileを用いて一度別のディレクトリ内に保存し、そのデータのファイル名のみをデータベースに格納します。ファイル名(FileName)は教科のコードと日付から作られるため、AnsiStringで定義しておきます。AnsiStringは8ビットのANSI文字列でほとんどの用途でもっともよく使用される型です。
ExtractFilepathを用いることで、実行されたFileName の名前と拡張子をパス情報と区切るコロンまたは円記号までの左側の文字列が取り出せます。そしてChDirによってカレントディレクトリを実行ディレクトリに移動します。
保存する際に、同じファイル名がないかをDirectoryExistsでチェックし、なければ講義情報とファイル名をデータベースに格納します。Appendによって、データベースに新たにレコードが追加されます。そして、Editメソッドを呼び出すと,データセット内のアクティブレコードを編集可能にすることができます。Edit はデータセットの現在の状態を変更します。編集が終了したらPostメソッドを呼ぶことで変更されたレコードがデータベースに書き込まれます。
PlainTextというのは、ファイルのテキストストリームの入出力処理において書式付編集コントロールがテキストを単なるテキストとして扱うか、書式付テキストとして扱うかを制御するものです。このPlainTextをFalseに設定することで、ファイルにエンコードされている書式情報をそのままファイルに保存します。
また、ノート内容を追加・変更する場合にもこの画面が表示されるため、MessageDlgで保存の確認を行います。
そしてRichEditの文字装飾について解説します。
procedure TNoteForm.RichEdit1SelectionChange(Sender: TObject);
var
Attr : TTextAttributes;
Para : TparaAttributes;
begin
Attr := RichEdit1.SelAttributes;
ComboBox1.ItemIndex :=ComboBox1.Items.IndexOf(Attr.Name);
ToolButton1.Down :=fsBold in Attr.Style;
ToolButton2.Down :=fsItalic in Attr.Style;
ToolButton3.Down :=fsUnderLine in Attr.Style;
UpDown1.Position :=Attr.Size;
Para := RichEdit1.Paragraph;
ToolButton5.Down :=Para.Alignment = taLeftJustify;
ToolButton6.Down :=Para.Alignment = taCenter;
ToolButton7.Down :=Para.Alignment = taRightJustify;
UpDown2.Position := Para.FirstIndent;
end;
// ----------------------------------------------------------------------------
procedure TNoteForm.ComboBox1Click(Sender: TObject);
begin
RichEdit1.SelAttributes.Name := ComboBox1.Text;
end;
// ----------------------------------------------------------------------------
procedure TNoteForm.ToolButton1Click(Sender: TObject);
begin
if ToolButton1.Down then
RichEdit1.SelAttributes.Style := RichEdit1.SelAttributes.style + [fsBold]
else
RichEdit1.SelAttributes.Style := RichEdit1.SelAttributes.style - [fsBold]
end;
// ----------------------------------------------------------------------------
procedure TNoteForm.ToolButton2Click(Sender: TObject);
begin
if ToolButton2.Down then
RichEdit1.SelAttributes.Style := RichEdit1.SelAttributes.style + [fsItalic]
else
RichEdit1.SelAttributes.Style := RichEdit1.SelAttributes.style - [fsItalic]
end;
// ----------------------------------------------------------------------------
procedure TNoteForm.ToolButton3Click(Sender: TObject);
begin
if ToolButton3.Down then
RichEdit1.SelAttributes.Style := RichEdit1.SelAttributes.style + [fsUnderline]
else
RichEdit1.SelAttributes.Style := RichEdit1.SelAttributes.style - [fsUnderline]
end;
// ----------------------------------------------------------------------------
procedure TNoteForm.ParaSet(Sender: TObject);
var
Tag : Integer;
begin
Tag := (Sender as TToolButton).Tag;
case Tag of
1 : RichEdit1.Paragraph.Alignment := TaLeftJustify;
2 : RIchEdit1.Paragraph.Alignment := taCenter;
3 : RichEdit1.Paragraph.Alignment := taRightJustify;
end;
end;
// ----------------------------------------------------------------------------
procedure TNoteForm.Edit1Change(Sender: TObject);
begin
RichEdit1.SelAttributes.Size := UpDown1.Position;
end;
// ----------------------------------------------------------------------------
procedure TNoteForm.Edit2Change(Sender: TObject);
begin
RichEdit1.Paragraph.FirstIndent := UpDown2.Position;
end;
// ----------------------------------------------------------------------------
procedure TNoteForm.FormClose(Sender: TObject; var Action: TCloseAction);
begin
RichEdit1.Lines.Clear;
Edit3.Clear;
Edit6.Clear;
Edit7.Clear;
Edit8.Clear;
Edit9.Clear;
Edit10.Clear;
Edit11.Clear;
end;
TDBRichEditオブジェクトでは、書式オプションをユーザーが利用できるようにするユーザーインターフェースコンポーネントを提供しないため、テキスト機能を持足せるためにコンポーネントを実装しなければなりません。そこで、今回のシステムでは文字飾りとして、太字・イタリック・アンダーラインとサイズ変更、また左・右寄せ、センタリング機能をつけました。
SelAttributesプロパティは、現在選択されているテキストについて同じフォントの部分を探したり、同じフォントに設定したりできます。現在選択しているテキストの書式を変更するには、SelAttributesで属性を設定します。また、Alignmentプロパティによってテキスト位置をどのように揃えるかを制御することができます。
検索機能は、教科名や日付などの項目による検索と、キーワード検索の2種類用意しました。ここでは項目による検索の解説をします。
今回は検索の容易さからSQLを使用したため、ノート内容はQueryにセットされています。検索にはFilterプロパティを使うことで、データセットフィルタを指定できます。フィルタ処理をデータセットに適用すると,フィルタの条件に適合するレコードだけがアプリケーションで利用できるようなります。Filter にはフィルタ条件を示す文字列を指定します。次のプログラムは教科名の検索方法です。
Case RadioGroup1.ItemIndex of
0: if ComboBox1.text = '' then
Application.MessageBox('項目を入力してください','入力エラー',MB_ICONEXCLAMATION)
else
begin
DataModule1.Query1.SQL.Clear;
DataModule1.Query1.SQL.Add('select * From Notebook.db');
DataModule1.Query1.Open;
DataModule1.Query1.Filtered := False;
DataModule1.Query1.Filter := 'subject='''+ComboBox1.Text+'*''';
DataModule1.Query1.Filtered := True;
if (DBGrid2.DataSource.DataSet.Fields[1].asString = '') then
begin
showMessage(ComboBox1 . Text +'に合致するデータは見つかりませんでした。');
end;
end;
ここで問題なのがFilterはボタン1つにつき1つしか設定できないため、if文を用いて入れ子にすることはできないということです。そこでRadioGroupを使い、検索したい項目を先に設定させることで、Filterを使用しての検索を可能にしました。
この検索後はGridにあてはまるレコードのみが表示されます。もし希望のノートが見つからない場合や、検索をやり直したい場合には、一覧表示ボタンをクリックすることでGridには全てのノートが表示され、RadioGroup・ComboBox・MaskEdit内の表示も初期化されます。プログラムは次のようになります。
// 一覧表示
procedure TListForm.Button5Click(Sender: TObject);
var
Query1: TQuery;
begin
Query1 := DataModule1.Query1;
Query1.Filtered := False;
Query1.SQL.Clear;
Query1.SQL.Add('select * From Notebook.db');
Query1.Open ;
ComboBox1.Text := '';
ComboBox2.Text := '';
MaskEdit1.Text := '';
RadioGroup1.ItemIndex := -1;
end;
次にノートの表示方法について説明します。
// 明細を開いたときの処理
procedure TListForm.TabSheet1Show(Sender: TObject);
var
FileName, Path :AnsiString;
begin
Path := ExtractFilePath( Application.ExeName);
FileName := DataModule1 . Query1['FileName'];
ChDir(Path);
Chdir('Text');
if IOResult <> 0 then
MessageDlg('Cannot Find directory' , mtWarning, [mbOK], 0);
if FileExists(FileName) = true then
begin
RichEdit1.PlainText := False;
RichEdit1.Lines.LoadFromFile(FileName);
end;
end;
保存の処理と同じようにPathを取り出し、ディレクトリを移動します。保存の時とは異なり、ファイルを呼び出すためにはLoadFromFileを使います。
2つめの検索方法として、キーワードによる検索が行えます。ここではノート情報と同じフィールドが含まれる空のテーブルを用意します。
begin
DataModule1.Query1.Open;
DataModule1.Query1.First;
DataModule1.Query1.Filtered := False;
while not DataModule1.Query1.Eof do
begin
if DataModule1.Query1.FieldByName('keyword1').AsString=(Edit1.Text) then
begin
Dainyu;
DataModule1.Query1.Next;
end
// 検索開始
else if DataModule1.Query1.FieldByName('keyword2').AsString = (Edit1.Text) then
begin
Dainyu;
DataModule1.Query1.Next;
end
else if DataModule1.Query1.FieldByName('keyword3').AsString = (Edit1.Text) then
begin
Dainyu;
DataModule1.Query1.Next;
end
この検索では1つのノートにキーワードが最高5つまで入力できるため、Eofを用いてファイルの最後になるまでif文を5回繰り返し検索していきます。検索により該当するファイルは、空のテーブルに順次代入されていき、検索結果はkeywordFormのGridに表示されます。
// 空のテーブルに代入
procedure TWordForm.Dainyu;
begin
DataModule1.Table1.Open;
DataModule1.Table1.Append;
DataModule1.Table1.Edit;
DataModule1.Table1.FieldByName('subject').AsString:=DataModule1.Query1.FieldByName('subject').AsString;
DataModule1.Table1.FieldByName('s_code').AsString:=DataModule1.Query1.FieldByName('s_code').AsString;
DataModule1.Table1.FieldByName('teacher').AsString:=DataModule1.Query1.FieldByName('teacher').AsString;
DataModule1.Table1.FieldByName('day').AsString:=DataModule1.Query1.FieldByName('day').AsString;
DataModule1.Table1.FieldByName('title').AsString:=DataModule1.Query1.FieldByName('title').AsString;
DataModule1.Table1.FieldByName('FileName').AsString:=DataModule1.Query1.FieldByName('FileName').AsString;
DataModule1.Table1.FieldByName('memo').AsString:=DataModule1.Query1.FieldByName('memo').AsString;
DataModule1.Table1.FieldByName('keyword1').AsString:=DataModule1.Query1.FieldByName('keyword1').AsString;
DataModule1.Table1.FieldByName('keyword2').AsString:=DataModule1.Query1.FieldByName('keyword2').AsString;
DataModule1.Table1.FieldByName('keyword3').AsString:=DataModule1.Query1.FieldByName('keyword3').AsString;
DataModule1.Table1.FieldByName('keyword4').AsString:=DataModule1.Query1.FieldByName('keyword4').AsString;
DataModule1.Table1.FieldByName('keyword5').AsString:=DataModule1.Query1.FieldByName('keyword5').AsString;
DataModule1.Table1.Post;
end;
キーワード検索画面は検索画面とほぼ同じつくりになっています。キーワード検索画面では、閉じる際に検索結果を代入したテーブルを再び空に戻しておくことで、次の検索ができるようにしてあります。