Palm Programmer's Laboratory
Palm OS Programmer's Companion Volume I/4-9
4-9 カテゴリ
カテゴリを使えば、レコードを論理的に分類してリストにすることができます。ユーザーインターフェースにおいては、カテゴリは通常フォームのタイトルバーや、単一レコードを編集するダイアログにポップアップリストとして表示されます。
他のポップアップリストを作成するのと同じように、カテゴリポップアップリストを作成することができます。リストリソースを作成し、ポップアップトリガーのリソースを作成して、幅を 0 に、トリガーのリスト ID にリストリソースの ID を設定します。カテゴリポップアップリストの操作はカテゴリ API を使用して行ないます。「 Palm OS Programmer's API Reference 」の “Categories” の章を参照して下さい。
ほとんどの場合、カテゴリポップアップリストは以下の関数コールで処理できます。
- データベースの作成時に CategoryInitialize をコールします。下の“データベースカテゴリの初期化”を参照して下さい。
- フォームのオープン時に CategorySetTriggerLabel をコールしてカテゴリポップアップトリガーのラベルを設定します。“カテゴリポップアップトリガーの初期化”を参照して下さい。
- カテゴリポップアップトリガーを選択した時に CategorySelect をコールします。“カテゴリポップアップリストの処理”を参照して下さい。
カテゴリトリガーを選択した時に起きる事象をより細かく制御したいと考えない限り、Category.h で宣言されているその他の関数を使う必要は通常ありません。
このセクションでは、カテゴリのユーザーインターフェースについて説明します。カテゴリがデータベース内にどのように保存され、どのように管理されるかについては、「 6 ファイルとデータベース 」を参照して下さい。
データベースカテゴリの初期化
カテゴリ API を使用する前に、データベースを適切に設定する必要があります。カテゴリ関数は特定の位置で情報を探します。そこに必要な情報がなければ、関数は失敗します。
カテゴリ情報はデータベースのアプリケーション情報ブロック内にある AppInfoType 構造体に保存されます。本書の 「 6 ファイルとデータベース 」で説明しているように、アプリケーション情報ブロックはデータベースが必要とする情報を全て格納することができます。カテゴリ API を使いたい場合、アプリケーション情報ブロックの最初のフィールドは AppInfoType 構造体でなければなりません。
AppInfoType 構造体はカテゴリ名とカテゴリインデックスおよびカテゴリのユニーク ID を対応付けます。カテゴリ名はユーザーインターフェース上に表示されます。カテゴリインデックスは、データベースレコードをカテゴリに関連付けるためのものです。つまり、データベースレコードの attribute フィールドは、そのレコードが所属するカテゴリのインデックスを保持しています。カテゴリのユニーク ID はデスクトップコンピュータとの同期の際に使用されます。
AppInfoType 構造体を初期化するためには、CategoryInitialize をコールしてカテゴリ名の一覧を含む文字列リソースを渡します。この関数は必要な数のカテゴリインデックスとユニーク ID を作成します。必要なことは、データベースを最初に作成したとき、またはデータベースのアプリケーション情報ブロックを新規に設定したときにこの関数をコールすることだけです。
文字列リストリソースは appInfoStringsRsc ('tAIS') タイプのリソースです。このリソースには、ユーザーがアプリケーションを最初に起動したときにユーザーが目にする定義済みのカテゴリが含まれています。CategoryInitialize のコールが、appInfoStringsRsc リソースを使用する唯一の場所であることに注意して下さい。このリソースを作成する際、以下のガイドラインに従って下さい。
- ユーザーに編集させたくないカテゴリはリストの最初に配置します。例えば、少なくとも「未分類」という名前の編集不可のカテゴリを1つ使用するのが一般的ですが、これはリストの最初になければなりません。
- 文字列リストは 16 個のエントリを持たなければなりません。通常は、16 種類のカテゴリを事前に定義したいとは思わないでしょう。1つか2つのカテゴリを定義し、残りは空にしておくことができます。使わないカテゴリは長さ0にしておきます。
- カテゴリ数には 16 という制限があることを忘れないで下さい。これには、事前に定義されたカテゴリとユーザーが作成するカテゴリの両方が含まれます。
- カテゴリ名には定数 dmCategoryLength で定義される最大長があります(現在は 16 バイトになっています)。
- 文字列“All” と “Edit Categories.” を含めないで下さい。これら2つはカテゴリリストに表示されますが、実際にはカテゴリではなく、カテゴリ関数はこれらを別扱いします。
リスト 4.3 に、データベースを作成してアプリケーション情報ブロックで初期化する関数の例を示します。アプリケーション情報ブロックはデータベースに保存されるため、メモリのアロケートには MemHandleNew ではなく DmNewHandle を使用することに注意して下さい。
リスト 4.3 アプリケーション情報ブロックを使ったデータベースの作成
typedef struct { AppInfoType appInfo; UInt16 myCustomAppInfo; } MyAppInfoType; Err CreateAndOpenDatabase(DmOpenRef *dbPP, UInt16 mode) { Err error = errNone; DmOpenRef dbP; UInt16 cardNo; MemHandle h; LocalID dbID; LocalID appInfoID; MyAppInfoType *appInfoP; // Create the database. error = DmCreateDatabase (0, MyDBName, MyDBCreator, MyDBType, false); if (error) return error; // Open the database. dbP = DmOpenDatabaseByTypeCreator(MyDBType, MyDBCreator, mode); if (!dbP) return (dmErrCantOpen); // Get database local ID and card number. We need these to // initialize app info block. if (DmOpenDatabaseInfo(dbP, &dbID, NULL, NULL, &cardNo, NULL)) return dmErrInvalidParam; // Allocate app info in storage heap. h = DmNewHandle(dbP, sizeof(MyAppInfoType)); if (!h) return dmErrMemError; // Associate app info with database. appInfoID = MemHandleToLocalID (h); DmSetDatabaseInfo(cardNo, dbID, NULL, NULL, NULL, NULL, NULL, NULL, NULL, &appInfoID, NULL, NULL, NULL); // Initialize app info block to 0. appInfoP = MemHandleLock(h); DmSet(appInfoP, 0, sizeof(MyAppInfoType), 0); // Initialize the categories. CategoryInitialize((AppInfoPtr)appInfoP, MyLocalizedAppInfoStr); // Unlock the app info block. MemPtrUnlock(appInfoP); // Set the output parameter and return. *dbPP = dbP; return error; }
カテゴリポップアップトリガーの初期化
フォームをオープンした際、表示されるカテゴリポップアップトリガーにテキストを指定する必要があります。そのためには、CategoryGetName 関数を使用して AppInfoType 構造体内の名前を検索し、次に CategorySetTriggerLabel をコールしてポップアップトリガーに設定します。
アプリケーションのメインフォームでは、通常、選択されているカテゴリのインデックスをプリファレンスに保存しておき、次にアプリケーションが起動した際にそれを復元します。
単一レコードの情報を表示するようなフォームでは、レコードのカテゴリをポップアップリストで表示するべきです。データベースの各レコードは attribute フィールドに自身のカテゴリを保存しています。レコードの属性は DmRecordInfo 関数を使用して取得でき、それと dmRecAttrCategoryMask の論理積をとることでカテゴリインデックスを取得できます。
リスト 4.4 に、特定のデータベースレコードのカテゴリにマッチするラベルをトリガーに設定する方法を示します。
リスト 4.4 カテゴリトリガーラベルの設定
UInt16 attr, category; Char categoryName [dmCategoryLength]; ControlType *ctl; // If current category is All, we need to look // up category. if (CurrentCategory == dmAllCategories) { DmRecordInfo (AddrDB, CurrentRecord, &attr, NULL, NULL); category = attr & dmRecAttrCategoryMask; } else category = CurrentCategory; CategoryGetName(AddrDB, category, categoryName); ctl = FrmGetObjectPtr(frm,FrmGetObjectIndex(frm, objectID)); CategorySetTriggerLabel(ctl, categoryName);
カテゴリポップアップリストの処理
カテゴリポップアップトリガーがタップされたら、CategorySelect をコールします。つまり、カテゴリトリガーの ID にマッチする ID が格納された ctlSelectEvent に応答して CategorySelect をコールするということです。CategorySelect 関数はポップアップリストを表示し、ユーザーによる選択を処理し、必要に応じて「カテゴリの編集」ダイアログを表示し、ユーザーの選択したカテゴリ名をポップアップトリガーのラベルに設定します。
CategorySelect のコール
典型的な CategorySelect の呼び出しを以下に示します。
リスト 4.5 CategorySelect の呼び出し
categoryEdited = CategorySelect(AddrDB, frm, ListCategoryTrigger, ListCategoryList, true, &category, CategoryName, 1, categoryDefaultEditCategoryString);
この例では、以下のパラメータを指定しています。
- AddrDB はカテゴリを表示する対象のデータベースです。
- frm、ListCategoryTrigger、ListCategoryList はフォーム、ポップアップトリガー、リストの各リソースを示しています。
- true はリストに“All”を含めることを指示しています。“All”は複数のレコードを表示するフォームでのみ使用されるべきです。単一のレコードを表示するフォームでは“All”を選択することには何の意味もありませんから、そのような場合には表示するべきではありません。
- category と categoryName は現在選択されているカテゴリのインデックスと名前のポインタです。この関数を呼び出す際、これら2つのパラメータはその時点でポップアップトリガーに表示されているカテゴリを指定しなければなりません。“Unfiled”がデフォルトです。
- 数値の 1 は編集不可のカテゴリの数を表しています。CategorySelect は、“Edit Categories”を選択した際にこの情報を必要とします。編集不可のカテゴリはカテゴリ編集ダイアログには表示されるべきではありません。編集不可のカテゴリはカテゴリリストの先頭にあるはずなので、このパラメータに 1 を渡すことは CategorySelect 関数がユーザーにインデックス 0 のカテゴリを編集させないことを意味します。
- categoryDefaultEditCategoryString は、カテゴリ編集のためのアイテムをリストに追加し、その名前にはデフォルトの文字列( U.S.ROM では“Edit Categories”)を使用することを意味する定数です。別の名前を(例えばデフォルトの文字列を使用するスペースがないなどの理由で)指定するには、指定したい名前を含む文字列リソースの ID を渡します。場合によっては、カテゴリ編集のためのアイテムを含めたくないこともあります。そのような場合には定数 categoryHideEditCategory を渡します。
- NOTE
- 定数 categoryDefaultEditCategoryString と categoryHideEditCategory は 3.5 新フィーチャセットが存在する場合のみ定義されます。より詳細な互換性情報については「 Palm OS Programmer's API Reference 」の CategorySelect 関数の説明を参照して下さい。
復帰値の解釈
CategorySelect の復帰値は少しばかり複雑です。CategorySelect はユーザーがカテゴリリストを編集した場合 true を返し、それ以外の場合は false を返します。つまり、カテゴリ編集アイテムを選択してカテゴリ名の追加、編集、削除をした場合、関数は true を返します。カテゴリ編集が選択されなければ、関数は false を返します。ほとんどの場合、カテゴリを編集することなくリスト内の既存アイテムを選択するだけなので、そのような場合には CategorySelect は false を返します。
このことは、なんらかのアクションを実行する必要性を確認するのに復帰値に単純に頼れないことを意味します。そのかわり、CategorySelect 関数に渡すカテゴリインデックス(のコピー)を保存しておき、関数が返すインデックスと比較する必要があります。以下に例を示します。
リスト 4.6 CategorySelect の復帰値
Int16 category; Boolean categoryEdited; category = CurrentCategory; categoryEdited = CategorySelect(AddrDB, frm, ListCategoryTrigger, ListCategoryList, true, &category, CategoryName, 1, categoryDefaultEditCategoryString); if ( categoryEdited || (category != CurrentCategory)) { /* user changed category selection or edited category list. Do something. */ }
別のカテゴリが選択された場合、以下の2つのうち、どちらかをすることになるでしょう。
- そのカテゴリに含まれるレコードだけが表示されているため、表示を更新する。サンプルコードとして、アドレス帳アプリケーションサンプルの ListViewUpdateRecords 関数を参照して下さい。
- カレントレコードのカテゴリを新しく選択されたカテゴリに変更する。サンプルコードとして、アドレス帳アプリケーションサンプルの EditViewSelectCategory 関数を参照して下さい。
CategorySelect 関数はカテゴリ編集ダイアログに従ってユーザーデータベースを変更することに注意して下さい。具体的には、データベースの AppInfoType 構造体の要素を追加、削除、リネームします。レコードを含むカテゴリが削除されると、これらのレコードは Unfiled カテゴリに移動されます。既存のカテゴリが別の既存カテゴリと同じ名前に変更されると、ユーザー確認の上で、古いカテゴリから新しいカテゴリにレコードが移動されます。そのため、CategorySelect 呼び出し後にカテゴリリストの管理について心配する必要はありません。