目新しいことはあまり出てきませんが、結構面倒くさいプログラムです。
ユーザーが自由に入力ができるようにしました。
各列による並び替えができるようになりました。この時「年齢」で並び替えを 行っても辞書式の並び替えでなく数値による並び替えができるようにしました。
各アイテムの編集、削除ができるようにしました。
では、プログラムを見てみましょう。
メニューとダイアログボックスのリソース・スクリプトです。// header03.rcの一部 ///////////////////////////////////////////////////////////////////////////// // // Menu // MYMENU MENU DISCARDABLE BEGIN POPUP "ファイル(&F)" BEGIN MENUITEM SEPARATOR MENUITEM "終了(&X)", IDM_END END MENUITEM "", 65535 POPUP "編集(&E)" BEGIN MENUITEM "データの追加(&A)", IDM_ADDITEM MENUITEM "データの削除(&D)", IDM_DELITEM MENUITEM "データ編集(&I)", IDM_EDITITEM END END ///////////////////////////////////////////////////////////////////////////// // // Dialog // ADDITEM DIALOG DISCARDABLE 0, 0, 143, 123 STYLE DS_MODALFRAME | DS_CENTER | WS_POPUP | WS_CAPTION | WS_SYSMENU CAPTION "データの追加" FONT 9, "MS Pゴシック" BEGIN EDITTEXT IDC_EDIT1,36,7,97,12,ES_AUTOHSCROLL EDITTEXT IDC_EDIT2,36,29,97,12,ES_AUTOHSCROLL EDITTEXT IDC_EDIT3,36,47,29,12,ES_AUTOHSCROLL | ES_NUMBER EDITTEXT IDC_EDIT4,36,63,99,33,ES_MULTILINE | ES_AUTOHSCROLL | ES_WANTRETURN DEFPUSHBUTTON "OK",IDOK,7,102,50,14 PUSHBUTTON "キャンセル",IDCANCEL,86,102,50,14 LTEXT "氏名:",IDC_STATIC,7,7,16,8 LTEXT "住所:",IDC_STATIC,7,29,16,8 LTEXT "年齢:",IDC_STATIC,7,49,16,8 LTEXT "備考:",IDC_STATIC,7,65,16,8 END
さて、ダイアログボックスのリソースは一つですが、
のように、別な目的のダイアログボックスとして利用できます。
このダイアログボックスはどちらも「年齢」以外のエディットコントロールに フォーカスが来ると自動的にIMEが起動します。
また、「年齢」のエディットコントロール では、半角数字以外入力を受け付けません。(ES_NUMBER)
備考欄には、改行も入力可能ですがリストビューに表示されるときは 1行で表示されます。
コモンコントロールの前準備(commctrl.hのインクルード、comctl32.libのリンクなど)と、 IMEの前準備(imm.hのインクルードとimm32.libのリンク)を忘れないでください。// header03.cpp #ifndef STRICT #define STRICT #endif #define ID_HEADER 100 #define ID_LIST 101 #define NO_OF_SUBITEM 4 #define UP 1 #define DOWN 2 #include <windows.h> #include <windowsx.h> #include <commctrl.h> #include <imm.h> #include "resource.h" LRESULT CALLBACK WndProc(HWND, UINT, WPARAM, LPARAM); int CALLBACK MyCompProc(LPARAM, LPARAM, LPARAM); ATOM InitApp(HINSTANCE); BOOL InitInstance(HINSTANCE, int); BOOL MySetItem(HWND); BOOL MySetListItem(HWND); LRESULT CALLBACK AddItemProc(HWND, UINT, WPARAM, LPARAM); LRESULT CALLBACK EditItemProc(HWND, UINT, WPARAM, LPARAM); char szClassName[] = "header03"; //ウィンドウクラス HINSTANCE hInst; int sortsubno[NO_OF_SUBITEM]; HWND g_hList; int no_of_item, param_of_item; int no_of_edititem; //編集するアイテム
いつもと同じです。int WINAPI WinMain(HINSTANCE hCurInst, HINSTANCE hPrevInst, LPSTR lpsCmdLine, int nCmdShow) { MSG msg; hInst = hCurInst; if (!InitApp(hCurInst)) return FALSE; if (!InitInstance(hCurInst, nCmdShow)) return FALSE; while (GetMessage(&msg, NULL, 0, 0)) { TranslateMessage(&msg); DispatchMessage(&msg); } return msg.wParam; } //ウィンドウ・クラスの登録 ATOM InitApp(HINSTANCE hInst) { WNDCLASSEX wc; wc.cbSize = sizeof(WNDCLASSEX); wc.style = CS_HREDRAW | CS_VREDRAW; wc.lpfnWndProc = WndProc; //プロシージャ名 wc.cbClsExtra = 0; wc.cbWndExtra = 0; wc.hInstance = hInst;//インスタンス wc.hIcon = LoadIcon(NULL, IDI_APPLICATION); wc.hCursor = LoadCursor(NULL, IDC_ARROW); wc.hbrBackground = (HBRUSH)GetStockObject(WHITE_BRUSH); wc.lpszMenuName = "MYMENU"; //メニュー名 wc.lpszClassName = (LPCSTR)szClassName; wc.hIconSm = LoadIcon(NULL, IDI_APPLICATION); return (RegisterClassEx(&wc)); } //ウィンドウの生成 BOOL InitInstance(HINSTANCE hInst, int nCmdShow) { HWND hWnd; hWnd = CreateWindow(szClassName, "猫でもわかるヘッダコントロール", //タイトルバーにこの名前が表示されます WS_OVERLAPPEDWINDOW, //ウィンドウの種類 CW_USEDEFAULT, //X座標 CW_USEDEFAULT, //Y座標 CW_USEDEFAULT, //幅 CW_USEDEFAULT, //高さ NULL, //親ウィンドウのハンドル、親を作るときはNULL NULL, //メニューハンドル、クラスメニューを使うときはNULL hInst, //インスタンスハンドル NULL); if (!hWnd) return FALSE; ShowWindow(hWnd, nCmdShow); UpdateWindow(hWnd); return TRUE; }
メインウィンドウのプロシージャです。//ウィンドウプロシージャ LRESULT CALLBACK WndProc(HWND hWnd, UINT msg, WPARAM wp, LPARAM lp) { int id, nHeaderh, nItem; static HWND hHeader, hList, hEdit; INITCOMMONCONTROLSEX ic; RECT rc; HDLAYOUT hdl; WINDOWPOS wpos; NMHDR *lpnmhdr; NMHEADER *lpnh; char szBuf[256], szName[64]; DWORD dwExStyle; switch (msg) { case WM_CREATE: ic.dwSize = sizeof(INITCOMMONCONTROLSEX); ic.dwICC = ICC_WIN95_CLASSES; InitCommonControlsEx(&ic); hHeader = CreateWindow(WC_HEADER, "", WS_CHILD | WS_BORDER | HDS_BUTTONS, 0, 0, 0, 0, hWnd, (HMENU)ID_HEADER, hInst, NULL); hList = CreateWindow(WC_LISTVIEW, "", WS_CHILD | WS_BORDER | WS_VISIBLE | LVS_REPORT | LVS_NOCOLUMNHEADER | LVS_EDITLABELS, 0, 0, 0, 0, hWnd, (HMENU)ID_LIST, hInst, NULL); dwExStyle = ListView_GetExtendedListViewStyle(hList); dwExStyle |= LVS_EX_FULLROWSELECT | LVS_EX_GRIDLINES; ListView_SetExtendedListViewStyle(hList, dwExStyle); g_hList = hList; MySetItem(hHeader); MySetListItem(hList); break; case WM_SIZE: rc.left = 0; rc.top = 0; rc.right = LOWORD(lp); rc.bottom = HIWORD(lp); hdl.pwpos = &wpos; hdl.prc = &rc; SendMessage(hHeader, HDM_LAYOUT, 0, (LPARAM)&hdl); SetWindowPos(hHeader, wpos.hwndInsertAfter, wpos.x, wpos.y, wpos.cx, wpos.cy, wpos.flags | SWP_SHOWWINDOW); GetWindowRect(hHeader, &rc); nHeaderh = rc.bottom - rc.top; MoveWindow(hList, 0, nHeaderh, LOWORD(lp), HIWORD(lp) - nHeaderh, TRUE); break; case WM_NOTIFY: lpnmhdr = (NMHDR *)lp; if (lpnmhdr->hwndFrom == hHeader) { switch (lpnmhdr->code) { case HDN_ITEMCLICK: lpnh = (NMHEADER *)lp; if (sortsubno[lpnh->iItem] == UP) sortsubno[lpnh->iItem] = DOWN; else sortsubno[lpnh->iItem] = UP; ListView_SortItems(hList, MyCompProc, lpnh->iItem); break; case HDN_ENDTRACK: lpnh = (NMHEADER *)lp; ListView_SetColumnWidth(hList, lpnh->iItem, lpnh->pitem->cxy); break; default: return (DefWindowProc(hWnd, msg, wp, lp)); } } else { return (DefWindowProc(hWnd, msg, wp, lp)); } break; case WM_COMMAND: switch (LOWORD(wp)) { case IDM_END: SendMessage(hWnd, WM_CLOSE, 0, 0); break; case IDM_ADDITEM: no_of_item = ListView_GetItemCount(hList); DialogBox(hInst, "ADDITEM", hWnd, (DLGPROC)AddItemProc); param_of_item++; break; case IDM_DELITEM: while (1) { nItem = ListView_GetNextItem(hList, -1, LVNI_ALL | LVNI_SELECTED); if (nItem == -1) break; ListView_GetItemText(hList, nItem, 0, szName, sizeof(szName)); wsprintf(szBuf, "「%s」の項目を削除してよろしいですか", szName); id = MessageBox(hWnd, szBuf, "項目の削除", MB_YESNO | MB_ICONQUESTION); if (id == IDYES) ListView_DeleteItem(hList, nItem); else //削除しない場合は選択状態を解除しないとまずい(いつまでも聞かれる) ListView_SetItemState(hList, nItem, 0, LVIS_SELECTED); } break; case IDM_EDITITEM: while (1) { nItem = ListView_GetNextItem(hList, -1, LVNI_ALL | LVNI_SELECTED); if (nItem == -1) break; no_of_edititem = nItem; //編集するアイテムNOをプロシージャからもわかるようにする DialogBox(hInst, "ADDITEM", hWnd, (DLGPROC)EditItemProc); } break; default: return (DefWindowProc(hWnd, msg, wp, lp)); } break; case WM_CLOSE: id = MessageBox(hWnd, "終了してもよいですか", "終了確認", MB_YESNO | MB_ICONQUESTION); if (id == IDYES) { DestroyWindow(hList); DestroyWindow(hHeader); DestroyWindow(hWnd); } break; case WM_DESTROY: PostQuitMessage(0); break; default: return (DefWindowProc(hWnd, msg, wp, lp)); } return 0; }
WM_CREATEメッセージが来たら、ヘッダーコントロールとリストビューを作るところは 同じですが、今回はリストビューに拡張スタイルを設定しています。 拡張スタイルについて忘れてしまった人は第198章を参照してください。
ヘッダーコントロールからHDN_ITEMCLICK通知メッセージが来たときの処理が増えました。
リストビューのソートについて忘れてしまった人は、第110章
を参照してください。
メニューからIDM_ADDITEMが選択されたときは、まず現在のアイテムの個数を調べてグローバル変数に 格納しておき、データ追加用のダイアログボックスを出します。そしてparam_of_itemの数を1増やします。 最初のうちはno_of_itemとparam_of_itemの数は一致しています。しかし、アイテムの削除を 行うとずれてきます。param_of_itemはアイテム特有の番号でアイテムごとに異なっていないと まずいからです。
メニューからIDM_DELITEMが選択されたら、ListView_DeleteItemマクロでアイテムを削除します。 このマクロを忘れてしまった人は第108章を参照してください。 さて、ここではいきなりアイテムを削除してしまうのではなく、メッセージボックスを出して 確認しています。削除した場合はよいのですが、削除しない場合アイテムの選択状態が続いていますので whileループを抜けることができません。そこでメッセージボックスで「いいえ」を選択した場合は アイテムの選択状態を解除する必要があります。これにはListView_SetItemStateマクロを使います。
hwndは、リストビューのハンドルを指定します。ListView_SetItemState( HWND hwnd, int i, UINT state, UINT mask );
iは、リストビューのアイテムのインデックスを指定します。-1だと全部指定したことになります。
stateには、アイテムの新しい状態ビットを指定します。maskで指定したビットが変更になります。
LVIS_CUT | アイテムはカット&ペースト操作にマークされている。 |
LVIS_DROPHILITED | アイテムはD&Dターゲットととして、ハイライトされている。 |
LVIS_FOCUSED | アイテムはフォーカスを持っています。 アイテムが複数選択されていてもフォーカスを持つアイテムは一つだけです。 |
LVIS_SELECTED | アイテムは選択されています。 |
maskには、変更したいパラメータービットを指定します。stateと同じLVIS_*で指定します。
これは使い方がちょっとわかりにくいです。
たとえば2番のアイテムを非選択状態にするには
ListView_SetItemState(hList, 2, 0, LVIS_SELECTED);
選択状態にしたいときは
ListView_SetItemState(hList, 2, LVIS_SELECTED, LVIS_SELECTED);
のようにします。
メニューからIDM_EDITITEMが選択されたときは、編集用のダイアログボックスを出します。 このダイアログボックスは、IDM_ADDITEMの時と同じものですが、プロシージャが異なる点に注意してください。 また、第108章の方法ではアイテム(「名前」)の編集はできますがサブアイテム(「住所」「年齢」「備考」) の編集はできません。そこで、編集用のダイアログボックスを出すことにしました。
「年齢」と「備考」が増えました。BOOL MySetItem(HWND hHeader) { HDITEM hi; hi.mask = HDI_FORMAT | HDI_TEXT | HDI_WIDTH; hi.pszText = "名 前"; hi.cxy = 100; hi.fmt = HDF_CENTER | HDF_STRING; hi.cchTextMax = strlen(hi.pszText); SendMessage(hHeader, HDM_INSERTITEM, 0, (LPARAM)&hi); hi.pszText = "住所"; hi.cxy = 200; hi.cchTextMax = strlen(hi.pszText); SendMessage(hHeader, HDM_INSERTITEM, 1, (LPARAM)&hi); hi.pszText = "年齢"; hi.cxy = 60; hi.cchTextMax = strlen(hi.pszText); SendMessage(hHeader, HDM_INSERTITEM, 2, (LPARAM)&hi); hi.pszText = "備 考"; hi.cxy = 260; hi.cchTextMax = strlen(hi.pszText); SendMessage(hHeader, HDM_INSERTITEM, 3, (LPARAM)&hi); return TRUE; }
サンプルデータがなくなりました。また、2番、3番のサブアイテムが増えました。BOOL MySetListItem(HWND hList) { LVCOLUMN lc; lc.mask = LVCF_WIDTH | LVCF_SUBITEM; lc.cx = 100; lc.iSubItem = 0; ListView_InsertColumn(hList, 0, &lc); lc.cx = 200; lc.iSubItem = 1; ListView_InsertColumn(hList, 1, &lc); lc.cx = 60; lc.iSubItem = 2; ListView_InsertColumn(hList, 2, &lc); lc.cx = 260; lc.iSubItem = 3; ListView_InsertColumn(hList, 3, &lc); return TRUE; }
リストビューのソートのための比較関数です。基本的には第110章と同じですが 「年齢」を対象にソートするときのためのプログラムが追加されています。// アプリケーション定義比較関数 int CALLBACK MyCompProc(LPARAM lp1, LPARAM lp2, LPARAM lp3) { static LVFINDINFO lvf; static int nItem1, nItem2; static char buf1[256], buf2[256]; lvf.flags = LVFI_PARAM; lvf.lParam = lp1; nItem1 = ListView_FindItem(g_hList, -1, &lvf); lvf.lParam = lp2; nItem2 = ListView_FindItem(g_hList, -1, &lvf); ListView_GetItemText(g_hList, nItem1, (int)lp3, buf1, sizeof(buf1)); ListView_GetItemText(g_hList, nItem2, (int)lp3, buf2, sizeof(buf2)); if (lp3 != 2) { if (sortsubno[(int)lp3] == UP) return(strcmp(buf1, buf2)); else return(strcmp(buf1, buf2) * -1); } else { if (sortsubno[(int)lp3] == UP) { //年齢の時は数値で比較 return (atoi(buf1) - atoi(buf2)); } else { return ((atoi(buf1) - atoi(buf2)) * -1); } } }
lp3は、ソートするサブアイテムが指定されるのでこれが2であるときはbuf1, buf2を整数値として比較します。
データ追加用のダイアログのプロシージャです。LRESULT CALLBACK AddItemProc(HWND hDlg, UINT msg, WPARAM wp, LPARAM lp) { char szName[64], szAddress[256], szAge[8], szMemo[512]; LVITEM li; HIMC hImc; static HWND hName, hAddress, hAge, hMemo; switch (msg) { case WM_INITDIALOG: hName = GetDlgItem(hDlg, IDC_EDIT1); hAddress = GetDlgItem(hDlg, IDC_EDIT2); hAge = GetDlgItem(hDlg, IDC_EDIT3); hMemo = GetDlgItem(hDlg, IDC_EDIT4); return TRUE; case WM_COMMAND: switch (LOWORD(wp)) { case IDOK: Edit_GetText(hName, szName, sizeof(szName)); Edit_GetText(hAddress, szAddress, sizeof(szAddress)); Edit_GetText(hAge, szAge, sizeof(szAge)); Edit_GetText(hMemo, szMemo, sizeof(szMemo)); li.mask = LVIF_TEXT | LVIF_PARAM; li.pszText = szName; li.iItem = no_of_item; li.lParam = param_of_item; li.iSubItem = 0; ListView_InsertItem(g_hList, &li); li.mask = LVIF_TEXT; li.pszText = szAddress; li.iItem = no_of_item ; li.iSubItem = 1; ListView_SetItem(g_hList, &li); li.pszText = szAge; li.iSubItem = 2; ListView_SetItem(g_hList, &li); li.pszText = szMemo; li.iSubItem = 3; ListView_SetItem(g_hList, &li); no_of_item++; EndDialog(hDlg, IDOK); return TRUE; case IDCANCEL: EndDialog(hDlg, IDCANCEL); return TRUE; case IDC_EDIT1: case IDC_EDIT2: case IDC_EDIT4: if (HIWORD(wp) == EN_SETFOCUS) { hImc = ImmGetContext((HWND)lp); ImmSetOpenStatus(hImc, TRUE); ImmReleaseContext((HWND)lp, hImc); return TRUE; } if (HIWORD(wp) == EN_KILLFOCUS) { hImc = ImmGetContext((HWND)lp); ImmSetOpenStatus(hImc, FALSE); ImmReleaseContext((HWND)lp, hImc); return TRUE; } return FALSE; } return FALSE; } return FALSE; }
OKボタンが押されたら、各エディットコントロールから文字列を読み出して、LVITEM構造体に セットします。サブアイテムの0番(アイテム)の時はlParamメンバもセットします。 これは、比較関数に渡されます。現在のアイテム数が4つあるなら、今度新しく加わるアイテムの IDは4番となります(IDは0から始まるので)。通常はIDとlParam値が一致しますが、 途中で3番のアイテムを削除したとします。次にアイテムを加えるときはこのアイテムのIDは やはり、4番となります。ここでlParamを4とするとlParamが4であるアイテムが2つあることにな り比較関数での結果がおかしくなります。
そこで、lParamは途中でアイテムの削除のあるなしに関係なくアイテムの追加のたびに1増やしておきます。 これで、すべてのアイテムは異なるlParam値を持つことになります。
この関係を間違うと、ソートの結果がおかしくなりバグ探しに大変苦労します。(筆者はこれを間違えたために 大変苦労しました)
「名前」「住所」「備考」のエディットコントロールがフォーカスを取得したら、IMEをオンにして 失ったらオフにしています。
エディットコントロールがキーボードフォーカスを受け取ると、その親に通知されます。EN_SETFOCUS LRESULT CALLBACK WindowProc( HWND hwnd, UINT uMsg, //WM_COMMAND WPARAM wParam, //ID, EN_SETFOCUS LPARAM lParam //エディットコントロールのハンドル );
wParamの下位ワード値はエディットコントロールのID、上位ワード値は通知メッセージを表します。
lParamはエディットコントロールのハンドルです。
親ウィンドウはWM_COMMANDメッセージの形で受け取ります。
エディットコントロールがキーボードフォーカスを失ったときに通知されます。 wParam, lParamはEN_SETFOCUSと同じです。EN_KILLFOCUS LRESULT CALLBACK WindowProc( HWND hwnd, UINT uMsg, // WM_COMMAND WPARAM wParam, // ID, EN_KILLFOCUS LPARAM lParam // エディットコントロールのハンドル );
IMEのオン、オフについては第278章を参照してください。
データの編集用ダイアログのプロシージャです。LRESULT CALLBACK EditItemProc(HWND hDlg, UINT msg, WPARAM wp, LPARAM lp) { char szName[64], szAddress[256], szAge[8], szMemo[512], szTitle[64]; LVITEM li_get, li; static HWND hName, hAddress, hAge, hMemo; HIMC hImc; switch (msg) { case WM_INITDIALOG: hName = GetDlgItem(hDlg, IDC_EDIT1); hAddress = GetDlgItem(hDlg, IDC_EDIT2); hAge = GetDlgItem(hDlg, IDC_EDIT3); hMemo = GetDlgItem(hDlg, IDC_EDIT4); ListView_GetItemText(g_hList, no_of_edititem, 0, szName, sizeof(szName)); ListView_GetItemText(g_hList, no_of_edititem, 1, szAddress, sizeof(szAddress)); ListView_GetItemText(g_hList, no_of_edititem, 2, szAge, sizeof(szAge)); ListView_GetItemText(g_hList, no_of_edititem, 3, szMemo, sizeof(szMemo)); Edit_SetText(hName, szName); Edit_SetText(hAddress, szAddress); Edit_SetText(hAge, szAge); Edit_SetText(hMemo, szMemo); wsprintf(szTitle, "「%s」の編集", szName); SetWindowText(hDlg, szTitle); return TRUE; case WM_COMMAND: switch (LOWORD(wp)) { case IDOK: Edit_GetText(hName, szName, sizeof(szName)); Edit_GetText(hAddress, szAddress, sizeof(szAddress)); Edit_GetText(hAge, szAge, sizeof(szAge)); Edit_GetText(hMemo, szMemo, sizeof(szMemo)); li_get.mask = LVIF_PARAM; li_get.iItem = no_of_edititem; li_get.iSubItem = 0; ListView_GetItem(g_hList, &li_get); li.mask = LVIF_TEXT | LVIF_PARAM; li.pszText = szName; li.iItem = no_of_edititem; li.lParam = li_get.lParam; li.iSubItem = 0; ListView_SetItem(g_hList, &li); li.mask = LVIF_TEXT; li.pszText = szAddress; li.iItem = no_of_edititem ; li.iSubItem = 1; ListView_SetItem(g_hList, &li); li.pszText = szAge; li.iSubItem = 2; ListView_SetItem(g_hList, &li); li.pszText = szMemo; li.iSubItem = 3; ListView_SetItem(g_hList, &li); ListView_SetItemState(g_hList, no_of_edititem, 0, LVIS_SELECTED); EndDialog(hDlg, IDOK); return TRUE; case IDCANCEL: ListView_SetItemState(g_hList, no_of_edititem, 0, LVIS_SELECTED); EndDialog(hDlg, IDCANCEL); return TRUE; case IDC_EDIT1: case IDC_EDIT2: case IDC_EDIT4: if (HIWORD(wp) == EN_SETFOCUS) { hImc = ImmGetContext((HWND)lp); ImmSetOpenStatus(hImc, TRUE); ImmReleaseContext((HWND)lp, hImc); return TRUE; } if (HIWORD(wp) == EN_KILLFOCUS) { hImc = ImmGetContext((HWND)lp); ImmSetOpenStatus(hImc, FALSE); ImmReleaseContext((HWND)lp, hImc); return TRUE; } return FALSE; } return FALSE; } return FALSE; }
AddItemProcと似ていますが、細かなところで異なります。
WM_INITDIALOGメッセージが来たら、各エディットコントロールのハンドルを スタティックな変数に格納しておきます。
そして編集するアイテムの各テキストを取得して、ダイアログのエディットコントロールに 表示します。
また、ダイアログのウィンドウタイトルを変更しておきます。
OKボタンが押されたら、各エディットコントロールの文字列を取得しておきます。
また、編集中のアイテムのlParamも取得しておきます。
LVITEM構造体のメンバをセットしてListView_SetItemマクロを使います。 「名前」もListView_InsertItemではなくListView_SetItemマクロを使う点に注意してください。
今回は目新しいことは、ほとんどありませんでしたがちょっと間違うと面倒なことになるプログラムでした。
ヘッダコントロールとリストビューを組み合わせても結局はリストビューのプログラミングが基本となります。
Update 28/Oct/2000 By Y.Kumei