2013年10月8日火曜日

MoEに至るプロセス-京大カードからPoICまで


まえおき


つかさです。
僕が京大カードに出会ってから、どういう経緯でMoEに至ったかを追ってみたいと思う。おおざっぱには

  • 京大カード→PoIC→(カードからPCデータへの移行)→「超」整理法→MoE

という過程で進んでいるのだが、試行錯誤や行ったり来たりとか同時並行だとかがあるのでもちろんスムーズに来たわけではない。
この種の技術にはある程度普遍性があると思うので、誰かの参考になればいいかなと思ってます。

京大カード


僕が知的生産術に手を出したのは、梅棹忠夫『知的生産の技術』を読んだのがきっかけだった。
「京大カード」あるいは「情報カード」と呼ばれるB6のカードを手に入れ、ここに思いついたことを書き込んでいく。


書式はこのように。日付、タイトル、本文から構成。また、一つのカードには一項目のことのみを記述するようにする。裏面は使わない。書けたら、そのカードをカードボックスに入れる。

カードが相当の枚数たまったら、それを「くる」ことによって発想を促す。自分が書いた個々のカードがランダムに現れる状況を作ることで、知的生産を実行するのだ。

たしかに個々のカードは、経験や知識の記録である。しかし、それをカードにしたのは、知識を分類して貯蔵するのが目的なのではない。何万枚ものカードも、死蔵していたのではなんにもならない。それは活用しなければならないのだ。カードを活用するとはどういうことか。それは、カードを操作して、知的生産の作業をおこなうこということである。
操作できるというところが、カードの特徴なのである。蓄積と貯蔵だけなら、ノートで十分だ。ノートにかかれた知識は、しばしば死蔵の状態におちいりやすいので、カードにしようというのではなかったか。カードの操作のなかで、いちばん重要なことは、くみかえ操作である。知識と知識とを、いろいろにくみかえてみる。あるいはならべかえてみる。そうするとしばしば、一見なんの関係もないようにみえるカードとカードのあいだに、おもいもかけぬ関連が存在することに気がつくのである。そのときには、すぐにその発見をもカード化しよう。そのうちにまた、おなじ材料からでも、くみかえによって、さらにあたらしい発見がもたらされる。これは、知識の単なる集積作業ではない。それは一種の知的創造作業なのである。カードは、蓄積の装置というよりはむしろ、創造の装置なのだ。(梅棹忠夫『知的生産の技術』P57)


このシステムは、カードが相当枚数たまることを前提としている。そしてカードがたまればそれだけ、有用性は高まるという思想を持っている。
くりかえし強調するが、カードは分類することが重要なのではない。くりかえしくることがたいせつなのだ。いくつかをとりだして、いろいろなくみあわせをつくる。それをくりかえせば、何万枚のカードでも、死蔵されることはない。(梅棹忠夫『知的生産の技術』P59)

ノートでも同じだが、カードはとくに、長年つづけてやらねば効果はすくない。いわば蓄積効果の問題なのだから、一時的におもいついてやってみても、なんのためにこんなことをするのか、わからぬうちにいやになる。(梅棹忠夫『知的生産の技術』P63)


京大カードの画期的な点


「アイデアは個々バラバラになっているほうが使いやすいのではないか」というアイデアが、京大カードの基礎にある。そしてこの発想は、一般に用いられている文房具とは潮流を異にするものである。

この手の技術について疎い人の場合、論文を書くなり何なりで知的生産をしたいというとき何をするかというと、ノートを利用するわけだ。ノートに思いつきを書き、それを元にして論文を作ろうとするだろう。だが、そのような試みはたいてい失敗する。ノートの場合、書き込んだアイデアを組み替えることができない。関係するもの同士を並列したいのなら、新しくノートを購入してそこに書き直すくらいしかない。が、これはそれに非常に手間がかかるという理由で、たいていは破綻する。
そこで、あらかじめアイデアを最初から個々バラバラで組み替え可能なようにしているのが、このカードシステムなのである。アイデアを個別に分離し、それを蓄積して利用する。このための具体的方法論を提出したという点で非常に画期的である。

また、これは論文を書くというのは所詮技術の問題にすぎない、それがうまくいかないのはそれがつたなかったからだ、というように言い切ったという点でも意義がある。この転換がなければ、論文が書けないのは自分の頭が悪いからであり、精神的に未だ未熟だからと総括して無意味な方法を続けることになっただろう。
技術というものは、原則として没個性的である。だれでもが、順序をふんで練習してゆけば、かならず一定の水準に到達できる、という性質をもっている。それに対して、研究だとか勉強とかの精神活動は、しばしばもっとも個性的・個人的ないとなみであって、普遍性がなく、公開不可能なものである、というかんがえかたがあるのである。それは、個性的な個人の精神の、奥ぶかい秘密の聖域でいとなまれる作業であって、他人にみせるべきものではない… …。
しかし、いろいろとしらべてみると、みんなひじょうに個性的とおもっているけれど、精神の奥の院でおこなわれている儀式は、あんがいおなじようなものがおおいのである。おなじようなくふうをして、おなじような失敗をしている。それなら、おもいきって、そういう話題を公開の場にひっぱりだして、おたがいに情報を交換するようにすれば、進歩もいちじるしいであろう。そういうようにしようではないか、というのが、このような本をかくことの目的なのである。(梅棹忠夫『知的生産の技術』P8)


京大カードの限界


このシステムを用いていると、途中で突き当たる壁がある。それは、「死蔵」という問題である。この問題は、カードを大量にためてそれを利用するシステムである限り、ずっと付きまとってくるものである。

このシステムは、取り扱うカードが少ないうちは問題なく機能する。たとえばA4数枚のレジュメだとか、本を読んでそれをまとめるくらいだったらいい。レジュメだったら、何か思いついたことなり調べたことなりを片っ端からカードに書いていき、あとでそれを並び変えて作成する。本を読んでそこで気づいたこと、思いついたことをカードに何枚か書きとめる。そうして読み終わったら、そのカードを並び替え、それを元に文章を書いていけばいい。漠然と本を読むなり思いつきなりだけでいきなり文章を書き始めるよりは、格段に質のいいものが短時間でできるはずだ。

必要なアイデアメモが取り出せない


だが、これは扱う対象が増え、複数の事柄について書いたカードが蓄積し、カードの枚数が増えていくと機能しなくなる。例えば何かのテーマについて書く必要があり、そのために以前に書いたメモを参考にしようと思って、カードをくるとしよう。すると出てくるのは、個々バラバラの事態について思いついたアイデアだ。そのテーマに関係の無いメモはノイズとなり、かつ過去へと遡れば遡るほど、賞味期限が切れて意味をなさなくなったアイデアのかけらに出会うことになる。

アイデアは、それを思いついた当初は意味を持っていたとしても、時間の経過によって劣化し意味のわからないものになる。結果、最近のカードから使えそうなアイデアをタイトルなどから関係してそうなもののみ抜き出し、それを元に考察をするだろう。これは、それらを一つのデータベースとしてためていることに意味がないということを示す。

発想支援装置にもならない


アイデアの発生装置としても役には立たない。たとえば僕が過去に使っていたカードボックスには、FPSでの特定マップの攻略アイデアと気になったレシピと何かの思いつきの断片と何かの記録とというようなものが混じっている。これらを見比べて何か意義のあることが思いつくことなどできるわけがない。

結局は、最近のカードをめくって関係するもののみを取り出してそれを持ち歩き、それを元にして発想をするということをしていた。これは結局、それ以前のカードが死蔵し、それの作成のためにかけた労力が無になったのと同じことである。

京大カードの次へ


複数の情報を並列することで発想する。これは、その対象が少なくとも相当練り上げられ、情報として洗練されているものでないと意味がない。アイデア段階のものがそのまま打ち捨てられた状態のものが並んでいたとしても、それはノイズにしかならないのである。

新しい刺激が欲しいのであれば、誰かに話を聞きにいくなり町に出て新しい経験をするなり誰かの本を読むなりすればいい。それを自分のアイデアで、データベースとしていちいち手間をかけて作成する意味はない。カードがたまればたまるだけ、それが有効に機能するという根幹にある思想自体が誤っているのではないかと思うのだ。アイデアを出す。それを個別に切り離せる形で保存する。ここまではうまくいく。アイデアはバラバラになっているほうが使いやすいというのは確かにそうだ。ただ、それをどのように運用するかという問題が、それを実際に大量にためたあとに現れるのである。

これへの対策としてPoICのタスクフォースがある。

PoICのタスクフォース


PoICというのは、京大カードを用いた方法論を体系的に整備したものである。
wiki形式になっており、梅棹以降の理論や、考案者の実際の試行錯誤に基づく修正などが盛り込まれており、非常にレベルが高い。梅棹は方法論としてはそれほど詳しくは書いてないので、実際にカードを使ってみたいなら、梅棹でだいたい思想をつかんで、このサイトをみてやり方を学ぶ、というようにするといいとおもう。

この過程の一つに、タスクフォースがある。それは、ためたカードを元に再生産をするプロセスである。

PoIC - 再生産する

広い場所を作り、そこにカードを並べていく。もし似たようなことについて扱っているカードがあれば、それは重ねる。このようにして、似たことについて扱っているカードのグループを作る。そうしてできたまとまりに、そのまとまりの内容を書いたふせんを貼る。

このようにしてできたまとまりを元にして文章を作る。その作業の途中で新しいことを思いつけば、新しくカードを書く。その作業で内容を反映されたカードのグループは、お役御免として、カードボックスとは別の場所にしまう。こうして、カードボックスの中身は、以前よりも精選されたアイデアになる。

タスクフォースの意義


その特徴は以下の二点である。

1.再生産という過程を組み込んだこと

京大カードの限界点を、「アイデアから再生産する過程」を意識的に作り出さなかったことに求める。そしてその方法論を、タスクフォースという形で具体的に提示しているのだ。
ちなみに梅棹だと、この過程はない。アイデアはひたすらたまっていく限りで、その内容をどうこうしようという発想はできないのである。

2.カードボックスの秩序を一定に保つ方法論を提示したこと

カードの死蔵が起こるのは、時間が経過しカードが増えることによってカードボックスがカオスになるからだ。そこで、カードをまとめ、束ね、廃棄し、という過程を組み込むことで、カードボックスの秩序を一定にしようとしている。*1


時系列でカードを蓄積していくと、自然の法則に従って、システムの中のエントロピー(情報の乱雑さ)は一方的に増えていきます。分類しない時系列で は、なおさらです。このままでは、PoIC は破綻しそうにも思えます。私自身、カードが増えるにしたがって、このまま行ったらどうなるのだろうか、と心配になったことがありました。
この自然の法則に逆らってエントロピーを減らそうとする場合、人間の「努力」が必要になります。図書館や博物館では、「つねに分類する努力」によってこれを実現しています。そのために、これらの公共施設では高いコスト(人件費、時間)を払っています。しかし、前述のように、PoIC では積極的に(?)検索・分類しません。では、どのようにしてシステムの破綻を防ぐのでしょうか。
答えは簡単で、やはり検索・分類するのです。従来の方法と違うのは、これが一番最後に来ることです。PoIC において、カードを書くのは、個人の知識のデータベースを構築することです。しかし、これはまだ準備段階です。PoIC の本当の目標は、このシステムを使って、新しい知恵・知識・成果を再生産することです。そうして初めて "Get things Done!" となります。(PoIC - 時系列スタック法)

タスクフォースの限界


僕はカード法で挫折したあと、この過程を組み込んだのだが、それでもやはり、死蔵という問題は解消されなかった。それは、タスクフォースの過程に手間がかかる、という理由による。

まず、広い場所を用意しなければならない。気楽に机の上程度の大きさでやるということはできない(ただ使うカードの大きさにも依拠するかもしれない。僕はB6を使っていた)。これを実現するには畳数枚程度の広さは必要になる。
また、この作業は一度に行わなければならない。そうそう何度も、カードをすべて調べて並び替えるということはできないし、どのカードとどのカードが内容がかぶってるかを判別するというのは、時間をあければ忘れてしまう。
ふせんを買ったりとか場所を作ったりとかいろいろ用意はしたが、結局は何回かやってやめてしまった。月に一度は日を決めてやろうだとか、まとまりを途中まで作ったままそのままの状態でまとめ、一時中断もできるようにするだとかいろいろ試みたが、結局やらなかった。
「カードボックス内の秩序を一定に保つ必要がある」この発想自体は正しいのかもしれない。だが、それを実現する方法の煩雑さによって、実際に機能するまでに至らないのである。実行するための負担が大きすぎるのだ。

そうしてここでも失敗し、やがてカードを使う方法をすて、PCでの方法を模索することになるのである。

…続く


*1:梅棹だと、カードは基本的にすべて残すことになる。それの対処として対応するのは、「カードを書くときには論文を書くようにする」、つまりアイデアとして半端なものをそもそも流入させないという方法である。
カードは、他人がよんでもわかるように、しっかりと、完全な文章でかくのである。「発見の手帳」についてのべたときに、豆論文を執筆するのだといったが、その原則はカードについてもまったく同じである。カードは、メモではない。(梅棹忠夫『知的生産の技術』P55)

2013年8月20日火曜日

Win32APIで仮想リストビューを使ってみる

あいさつ


もともと、苦しんで覚えるC言語を読んで、試しに何か作ってみようと思ったのがWin32APIに手を出したきっかけだった。そのあと『猫でもわかるWindowsプログラミング』というのを買って読み、作ってみたのが前回のeClip風の何かと今回のfenrirとかEverything風の見た目の何か。



途中まではすいすいうまいこといってたのだが、なんかある程度自分で考えたり検索しまくったり時間をおいたりしないとわからなげな、深入りしそうな段階に入った気配がするのでいったん中断する。ただ本当にそのままほっといたら今までやったことを忘れてしまいそうなので、ここまでできたソースだけメモとしてブログにあげておきます。

コード


lview.cpp

#define ID_EDIT 101

#include <windows.h>
#include <windowsx.h>
#include <commctrl.h>

#pragma comment (lib, "comctl32.lib")

typedef struct {
    TCHAR  filename[MAX_PATH];    // ファイル名
    DWORD size;                    // ファイルサイズ
    DWORD attr;                    // ファイルの属性
    TCHAR  type[256];            // 種類
    int   icon;                    // アイコン
    FILETIME writedate;            // 更新日
} filedata;

ATOM InitApp(HINSTANCE);
BOOL InitInstance(HINSTANCE, int);
HWND CreateListView(HWND hWnd);
LRESULT CALLBACK WndProc(HWND, UINT, WPARAM, LPARAM);
LRESULT CALLBACK MyEditProc(HWND, UINT, WPARAM, LPARAM);
int DoFind(HWND hWnd);

TCHAR szClassName[] = TEXT("Cecil");  // ウィンドウクラス
HINSTANCE hInst;
HWND hMain;             // メインウィンドウのハンドル
HWND hSubList;
WNDPROC OrgEditProc;   // プロシージャアドレスを格納するための変数
static filedata *lpData = NULL;   // ファイル情報のリストを格納するための変数

// メイン
int WINAPI WinMain(HINSTANCE hCurInst, HINSTANCE hPrevInst,
                   LPSTR lpsCmdLine, int nCmdShow)
{
    MSG msg;
    BOOL bRet;

    hInst = hCurInst;
    if (!InitApp(hCurInst))
        return FALSE;
    if (!InitInstance(hCurInst, nCmdShow))
        return FALSE;
    while ((bRet = GetMessage(&msg, NULL, 0, 0)) != 0) {
        if (bRet == -1) {
            break;
        } else {
            TranslateMessage(&msg);
            DispatchMessage(&msg);
        }
    }
    return (int)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 = NULL;
    wc.hCursor = (HCURSOR)LoadImage(NULL, MAKEINTRESOURCE(IDC_ARROW),
                    IMAGE_CURSOR, 0, 0, LR_DEFAULTSIZE | LR_SHARED);
    wc.hbrBackground = (HBRUSH)GetStockObject(WHITE_BRUSH);
    wc.lpszMenuName = NULL;    // メニュー名
    wc.lpszClassName = szClassName;
    wc.hIconSm = NULL;

    return (RegisterClassEx(&wc));
}

// ウィンドウの生成
BOOL InitInstance(HINSTANCE hInst, int nCmdShow)
{
    HWND hWnd;

    hWnd = CreateWindow(szClassName, TEXT("Cecil"),
        WS_OVERLAPPEDWINDOW & ~WS_CAPTION, CW_USEDEFAULT,
        CW_USEDEFAULT, 500, 300, NULL, NULL, hInst, NULL);
    if (!hWnd)
        return FALSE;
    hMain = hWnd;
    ShowWindow(hWnd, nCmdShow);
    UpdateWindow(hWnd);
    return TRUE;
}

// ウィンドウプロシージャ
LRESULT CALLBACK WndProc(HWND hWnd, UINT msg, WPARAM wp, LPARAM lp)
{
    static HWND edit;
    static HWND hList;

    switch (msg) {
        case WM_CREATE:
            hList = CreateListView(hWnd);
            hSubList = hList;
            ListView_SetItemState(hSubList, 0,LVIS_FOCUSED|LVIS_SELECTED,LVIS_FOCUSED|LVIS_SELECTED);
            edit = CreateWindow(
                TEXT("EDIT") , NULL , 
                WS_CHILD | WS_VISIBLE | WS_BORDER ,
                0 , 0 , 0 , 0 , hWnd , (HMENU)ID_EDIT, hInst, NULL);
            // エディットコントロールをサブクラス化
            OrgEditProc = (WNDPROC)SetWindowLongPtr(
                edit, GWL_WNDPROC, (LONG)MyEditProc);
            break;
        case WM_NOTIFY:
            if (((LPNMHDR)lp)->code == LVN_GETDISPINFO) {
                TCHAR        szBuf[256];
                NMLVDISPINFO *lpDispInfo = (NMLVDISPINFO *)lp;

                switch(lpDispInfo->item.iSubItem){
                    case 0:{
                            if (lpDispInfo->item.mask & LVIF_TEXT) {
                                wsprintf(szBuf, lpData[lpDispInfo->item.iItem].filename);
                                lstrcpy(lpDispInfo->item.pszText, szBuf);
                            }
                            if (lpDispInfo->item.mask & LVIF_IMAGE) {
                                lpDispInfo->item.iImage = lpData[lpDispInfo->item.iItem].icon;
                            }        
                        }
                           break;
                    case 1:{
                            if (lpDispInfo->item.mask & LVIF_TEXT) {
                                wsprintf(szBuf, lpData[lpDispInfo->item.iItem].type);
                                lstrcpy(lpDispInfo->item.pszText, szBuf);
                            }
                            }
                            break;
                }
            }
            return 0;
        case WM_SIZE:    
            // ウィンドウサイズを調整
            MoveWindow(edit, 0, 0, LOWORD(lp), 30, TRUE);
            MoveWindow(hList, 0, 30, LOWORD(lp), HIWORD(lp), TRUE);
            break;
        case WM_SETFOCUS:
            SetFocus(edit);
            break;
        case WM_CLOSE:
             DestroyWindow(hList);
             DestroyWindow(hWnd);
            break;
        case WM_DESTROY:
            PostQuitMessage(0);
            break;
        default:
            return (DefWindowProc(hWnd, msg, wp, lp));
    }
    return 0;
}

// 仮想リストビューを作る
HWND CreateListView(HWND hWnd) {
    int i;
    HWND hList;
    HIMAGELIST           himl;
    SHFILEINFO           fileInfo;
    LVCOLUMN             column;
    INITCOMMONCONTROLSEX ic;
    DWORD dwStyle;

    ic.dwSize = sizeof(INITCOMMONCONTROLSEX);
    ic.dwICC  = ICC_LISTVIEW_CLASSES;
    InitCommonControlsEx(&ic);
        
    hList = CreateWindowEx(0, WC_LISTVIEW, TEXT(""), WS_CHILD 
        | WS_VISIBLE | LVS_REPORT | LVS_OWNERDATA 
        | LVS_SHOWSELALWAYS | LVS_SINGLESEL, 
        0, 0, 0, 0, hWnd, (HMENU)ID_EDIT, hInst, NULL);
    dwStyle = ListView_GetExtendedListViewStyle(hList);
    dwStyle |= LVS_EX_FULLROWSELECT;
    ListView_SetExtendedListViewStyle(hList, dwStyle);
    column.mask    = LVCF_WIDTH | LVCF_TEXT;
    column.cx      = 300;
    column.pszText = TEXT("Name");
    ListView_InsertColumn(hList, 0, &column);
        
    column.mask    = LVCF_WIDTH | LVCF_TEXT;
    column.cx      = 200;
    column.pszText = TEXT("Type");
    ListView_InsertColumn(hList, 1, &column);

    himl = (HIMAGELIST)SHGetFileInfo((LPCTSTR)TEXT("C:\\"), 
        0, &fileInfo, sizeof(SHFILEINFO), SHGFI_SYSICONINDEX | SHGFI_SMALLICON);
    ListView_SetImageList(hList, himl, LVSIL_SMALL);

    i = DoFind(hList);

    ListView_SetItemCountEx(hList, i, LVSICF_NOINVALIDATEALL);

    return hList;
}

// 実行フォルダ配下のファイルを全て検索する
int DoFind(HWND hList)
{
    int             i = 0;
    int             count = 0;
    HANDLE hFind;
    WIN32_FIND_DATA fd;
    SHFILEINFO      fileInfo;
    LVITEM          item;

    /* ファイル数のカウント */
    hFind = FindFirstFile(TEXT("*.*"), &fd);
    do {

        if (lstrcmp(fd.cFileName, TEXT("..")) != 0 && lstrcmp(fd.cFileName, TEXT(".")) != 0) {
            count++;
        }
    } while(FindNextFile(hFind, &fd));

    /* カウント終了 */
    FindClose(hFind);

    /* 動的メモリ確保 */
    lpData = (filedata *)HeapAlloc(GetProcessHeap(), 0, count * sizeof(filedata));

    /* 最初のファイル検索 */
    hFind = FindFirstFile(TEXT("*.*"), &fd);

    do {

        if (lstrcmp(fd.cFileName, TEXT("..")) != 0 && lstrcmp(fd.cFileName, TEXT(".")) != 0) {
            SHGetFileInfo(fd.cFileName, 0, &fileInfo, sizeof(SHFILEINFO), SHGFI_SYSICONINDEX | SHGFI_TYPENAME);

            item.mask     = LVIF_TEXT | LVIF_IMAGE;
            item.iItem    = i;
            item.iSubItem = 0;
            // ファイルパス
            item.pszText  = fd.cFileName;
            lstrcpy(lpData[i].filename,fd.cFileName);
            // ファイルの属性
            lpData[i].attr = fd.dwFileAttributes;
            // 更新日
            lpData[i].writedate = fd.ftLastWriteTime;
            // ファイルタイプ
            lstrcpy(lpData[i].type, fileInfo.szTypeName);
            // アイコン    
            item.iImage   = fileInfo.iIcon;
            lpData[i].icon = fileInfo.iIcon;
            ListView_InsertItem(hList, &item);

            item.iSubItem = 1;
            item.pszText  = fd.cAlternateFileName;
            ListView_SetItem(hList, &item);

            i++;
        }
    } while(FindNextFile(hFind, &fd));
    /* 検索終了 */
    FindClose(hFind);

    return count;
}

// エディットボックスのプロシージャ
LRESULT CALLBACK MyEditProc(HWND hEdit, UINT msg, WPARAM wp, LPARAM lp)
{
    int i = 0 ,n;
    switch (msg) {
        case WM_KEYDOWN:
            if ( wp == VK_DOWN )
            {
                i = ListView_GetNextItem(hSubList,-1, LVNI_ALL | LVNI_SELECTED);

                if (i == ListView_GetItemCount(hSubList) - 1)
                {
                    ListView_SetItemState(hSubList,0,LVIS_FOCUSED|LVIS_SELECTED,LVIS_FOCUSED|LVIS_SELECTED);
                    ListView_EnsureVisible(hSubList, 0, TRUE);
                }
                else
                {
                    ListView_SetItemState(hSubList,i + 1,LVIS_FOCUSED|LVIS_SELECTED,LVIS_FOCUSED|LVIS_SELECTED);
                    ListView_EnsureVisible(hSubList, i + 1, TRUE);
                }
                break;
            }
            else if ( wp == VK_UP )
            {
                i = ListView_GetNextItem(hSubList,-1, LVNI_ALL | LVNI_SELECTED);
                if (i == 0)
                {
                    ListView_SetItemState(hSubList,ListView_GetItemCount(hSubList) - 1,
                        LVIS_FOCUSED|LVIS_SELECTED,LVIS_FOCUSED|LVIS_SELECTED);
                    ListView_EnsureVisible(hSubList, ListView_GetItemCount(hSubList) - 1, TRUE);
                }
                else
                {
                    ListView_SetItemState(hSubList,i - 1,LVIS_FOCUSED|LVIS_SELECTED,LVIS_FOCUSED|LVIS_SELECTED);
                    ListView_EnsureVisible(hSubList, i - 1, TRUE);
                }
                break;
            }
            else if ( wp == VK_ESCAPE )
            {
                DestroyWindow(hEdit);
                DestroyWindow(hSubList);
                DestroyWindow(hMain);
                break;
            }
            else if ( wp == VK_RETURN )
            {
                int nItem;

                nItem = ListView_GetNextItem(
                        hSubList, -1, LVNI_ALL | LVNI_SELECTED);
                ShellExecute(NULL, TEXT("open"), lpData[nItem].filename, NULL, NULL, SW_SHOW);
                break;
            }
            return 0;
        case WM_CHAR:           // 文字入力が発生したとき
            for (n = 0; n < ListView_GetItemCount(hSubList); n++){
                ;
            }
            break;
    }

    return CallWindowProc(OrgEditProc, hEdit, msg, wp, lp);
}

説明とか


Visual C++ 2008 Express Editionのインストールの「cppファイルにコードを書く」過程で、上のコードをコピペしたら多分動くと思う。

コントロールの作成


エディットボックスとリストコントロールを作成。
リストコントロールは仮想リストビューを使っている。データの管理と描画を分離するのがこっちで、処理が早いらしい。

エディットボックスのキーバインド


エディットボックスでカーソル上下をしたら、リストコントロールのカーソルが上下するように設定している。
また、Enterを押したらデフォルトの実行。Escでプログラムを閉じる。

アイテムの登録


実行ファイルのあるフォルダのパスを取得し、表示させている。

次にやること


  • ファイルから一行ずつ読み込み
  • ある文字列にある文字列が含まれているかどうかを調べる

あたりです。また気が向いたら続きしよう。

2013年8月19日月曜日

Win32APIでListBoxを使ってみる

挨拶


つかさです。
Win32APIでリストボックスを使ったサンプル。勉強のために、eClipっぽいのとfenrirっぽいのの二つを作ってみたのですがそのひとつ。



今日はこのようなどっかで見た気がしないでもないGUIを作って、エディットボックスでカーソル移動できるまで…といっても試しに作ってみただけなので、次回はおそらくないです。
ちなみに同じことをPythonでやったのがwxPythonでAA管理ツールを作ろう!第二回 ListBoxの操作です。参考までに。

コード


lbox.cpp

#define ID_EDIT 101

#include <windows.h>
#include <windowsx.h>

LPCTSTR strText[] = {
    TEXT("tukasa") ,
    TEXT("kagami") ,
    TEXT("konata") ,
    TEXT("homuhomu") ,
    TEXT("madoka")
};

LRESULT CALLBACK WndProc(HWND, UINT, WPARAM, LPARAM);
LRESULT CALLBACK MyEditProc(HWND, UINT, WPARAM, LPARAM);
ATOM InitApp(HINSTANCE);
BOOL InitInstance(HINSTANCE, int);

TCHAR szClassName[] = TEXT("SETZER2");  // ウィンドウクラス
HINSTANCE hInst;
HWND hMain;             // メインウィンドウのハンドル
HWND hSubsortList;
WNDPROC OrgEditProc;   // プロシージャアドレスを格納するための変数
int CursorDown(HWND hEdit, HWND hsortList);
int CursorUp(HWND hEdit, HWND hsortList);

int WINAPI WinMain(HINSTANCE hCurInst, HINSTANCE hPrevInst,
                   LPSTR lpsCmdLine, int nCmdShow)
{
    MSG msg;
    BOOL bRet;

    hInst = hCurInst;
    if (!InitApp(hCurInst))
        return FALSE;
    if (!InitInstance(hCurInst, nCmdShow))
        return FALSE;
    while ((bRet = GetMessage(&msg, NULL, 0, 0)) != 0) {
        if (bRet == -1) {
            break;
        } else {
            TranslateMessage(&msg);
            DispatchMessage(&msg);
        }
    }
    return (int)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 = NULL;
    wc.hCursor = (HCURSOR)LoadImage(NULL, MAKEINTRESOURCE(IDC_ARROW),
                    IMAGE_CURSOR, 0, 0, LR_DEFAULTSIZE | LR_SHARED);
    wc.hbrBackground = (HBRUSH)GetStockObject(WHITE_BRUSH);
    wc.lpszMenuName = NULL;    // メニュー名
    wc.lpszClassName = szClassName;
    wc.hIconSm = NULL;

    return (RegisterClassEx(&wc));
}

// ウィンドウの生成
BOOL InitInstance(HINSTANCE hInst, int nCmdShow)
{
    HWND hWnd;

    hWnd = CreateWindow(szClassName, TEXT("SETZER2"),
        WS_OVERLAPPEDWINDOW & ~WS_CAPTION, CW_USEDEFAULT,
        CW_USEDEFAULT, 230, 300, NULL, NULL, hInst, NULL);
    if (!hWnd)
        return FALSE;
    ShowWindow(hWnd, nCmdShow);
    UpdateWindow(hWnd);
    hMain = hWnd;
    return TRUE;
}

// ウィンドウプロシージャ
LRESULT CALLBACK WndProc(HWND hWnd, UINT msg, WPARAM wp, LPARAM lp)
{
    static HWND sortList, edit;
    int i;

    switch (msg) {
        case WM_CREATE:
            sortList = CreateWindow(TEXT("LISTBOX"), TEXT(""),
                WS_CHILD | WS_VISIBLE | LBS_STANDARD,
                0, 0, 0, 0, hWnd, (HMENU)ID_EDIT, hInst, NULL);
            hSubsortList = sortList;
            for (i = 0 ; i < 5 ; i++)
                SendMessage(sortList , LB_ADDSTRING , 0 , (LPARAM)strText[i]);
            SendMessage(sortList , LB_SETCURSEL , 0 , 0);
            edit = CreateWindow(
                TEXT("EDIT") , NULL , 
                WS_CHILD | WS_VISIBLE | WS_BORDER ,
                0 , 0 , 0 , 0 , hWnd , (HMENU)ID_EDIT, hInst, NULL);
            // エディットコントロールをサブクラス化
            OrgEditProc = (WNDPROC)SetWindowLongPtr(
                edit, GWL_WNDPROC, (LONG)MyEditProc);
            break;
        case WM_SIZE:    
            // ウィンドウサイズを調整
            MoveWindow(edit, 0, 0, LOWORD(lp), 25, TRUE);
            MoveWindow(sortList, 0, 25, LOWORD(lp), HIWORD(lp), TRUE);
            break;
        case WM_SETFOCUS:
            SetFocus(edit);
            break;
        case WM_CLOSE:
             DestroyWindow(sortList);
             DestroyWindow(hWnd);
            break;
        case WM_DESTROY:
            PostQuitMessage(0);
            break;
        default:
            return (DefWindowProc(hWnd, msg, wp, lp));
    }
    return 0;
}

// エディットボックスのプロシージャ
LRESULT CALLBACK MyEditProc(HWND hEdit, UINT msg, WPARAM wp, LPARAM lp)
{
    switch (msg) {
        case WM_KEYDOWN:
        if( wp == VK_DOWN )
        {
            CursorDown(hEdit, hSubsortList);
            break;
        }
        else if ( wp == VK_UP )
        {
            CursorUp(hEdit, hSubsortList);
            break;
        }
        else if ( wp == VK_ESCAPE )
        {
            DestroyWindow(hEdit);
            DestroyWindow(hSubsortList);
            DestroyWindow(hMain);
        }
    }
    return CallWindowProc(OrgEditProc, hEdit, msg, wp, lp);
}

// リストボックスのカーソルを上に
int CursorUp(HWND hEdit, HWND hsortList)
{
    int i;

    i = SendMessage(hsortList , LB_GETCURSEL , 0 , 0);
    if( i == 0 )
    {
        SendMessage(hsortList , LB_SETCURSEL , SendMessage(hsortList , LB_GETCOUNT , 0 , 0) - 1 , 0);
    }
    else
    {
        SendMessage(hsortList , LB_SETCURSEL , i - 1 , 0);
    }
    return 0;
}

// リストボックスのカーソルを下に
int CursorDown(HWND hEdit, HWND hsortList)
{
    int i;

    i = SendMessage(hsortList , LB_GETCURSEL , 0 , 0);
    if( i == SendMessage(hsortList , LB_GETCOUNT , 0 , 0) - 1 )
    {
        SendMessage(hsortList , LB_SETCURSEL , 0 , 0);
    }
    else
    {
        SendMessage(hsortList , LB_SETCURSEL , i + 1 , 0);
    }
    return 0;
}

解説


前回の記事の「cppファイルにコードを書く」過程で、上のコードをコピペしたら多分動くと思う。
エディットボックスでカーソル上下したらそれに連動して、リストボックスの選択カーソルが上下するようになってます。またEscを押したらプログラムを終了します。

Win32APIは、メッセージを受け取ったらそれにあわせて何か動作をするという仕組みらしい。コードは長いが、処理の中心はWndProcとかMyEditProcとかなので、そこらへんを追っていけばだいたいわかるかなと。ウィンドウの登録とかウィンドウの生成とかはどのプログラムでも同じなので、あまり気にしなくてもいいらしい。

2013年8月17日土曜日

Visual C++ 2008 Express Editionのインストール

あいさつ


つかさです。
Win32APIをしばらくいじってたので忘れないうちにそのメモ。今回は環境のインストールから実行ファイルを作成するまで。

インストール


Visual C++ 2008 Express Editionをインストール。場所はNonSoft - Visual Studio 2008 Expressのダウンロードとインストールを参考にした。
ネットを利用してインストールする方法と、ディスクイメージを落としてインストールする方法の二つがあるらしい。
ディスク版だと使用期限とか聞かれなくていいらしいので、僕はこっちを利用した。

プロジェクトの作り方


まずはプロジェクトを作る。

メニューバーから

  • ファイル→新規作成→プロジェクト

新しいプロジェクトダイアログが出るので、Win32プロジェクトを選択。また適当なプロジェクト名を入力する。





あとは「次へ」をクリックすればいいが、「アプリケーションの設定」で

  • 追加オプション→空のプロジェクト

にチェックを入れる。これで、「ソースファイル」「ヘッダーファイル」「リソースファイル」の三つの空フォルダが作成される。



実行ファイルの作り方


ソースフォルダを右クリックして

  • 追加→新しい項目

を選択。C++ファイル(.cpp)を選択し、適当な名前を入力して「追加」を押す。




追加されたcppファイルにコードを書く。

書き終えたら、メニューバーから

  • デバッグ→デバッグなしで開始

を選択。エラーがなければ、コンパイルして実行ファイルを開いてくれる。

2013年7月15日月曜日

PPxで外部ソフト起動メニュー

つかさです。
PPxから外部ソフトを起動する方法。



いつも僕はこのランチャの用途にはfenrirを用いるのだが、それができない場合のためにメモしておく。

やり方


以下を編集して取込。Ctrl+Lで外部ソフト起動用のメニューが表示されます。

M_exec = { ** comment **
AzPainter = %Ob D:\bin\azpt212\AzPainter2.exe
EasyShot = %Ob D:\bin\EasyShot\EasyShot.exe
emacs = %Ob D:\bin\emacs\bin\runemacs.exe
Kifu for Win = %Ob "C:\Program Files (x86)\Kifu for Windows V7\KifuW.exe"
MPC = %Ob D:\bin\MPC-HC\mpc-hc.exe
SETZER = %Ob D:\bin\SETZER\SETZER.exe
}

KC_main = { ; PPcメイン窓
^L ,%M_exec
}

カーソル位置のパスを投げるだとか引数をつけるだとかそういうのはまったく関係なくただ起動するだけのメニューです。ひねりとかそういうの全く無さすぎてあれなんだけどたまにはいいよね
ポイントは起動したいパスの前に%Obをつけることと、パスに空白がある場合は""でくくるということです。

他のやり方


後、
%Ob D:\bin\azpt212\AzPainter2.exe


*launch D:\bin\azpt212\AzPainter2.exe
*start D:\bin\azpt212\AzPainter2.exe
%Z D:\bin\azpt212\AzPainter2.exe

と書いても同じ事が(おそらく)できるのでこっちのが直感的に分かりやすそうだという人は試してもいいかもしれない。僕も使い始めのころはこの%ってなんだろうとかOって何の略だろうとか無駄に考えてはまってた気がする。何が違うか気になる人はHelpを見て研究しましょう。

2013年7月12日金曜日

ChangeLogをPPxで閲覧する

やってみた感想


ChangeLogでいくつか文章を読みながら、それのメモを書くということをやってみたがかなりいい。プリントの余白に書き込んでいた時みたいに、書き込む量を気にしないですむのでどんどん書いていける。
気になる箇所があれば、C-x 4 aでChangeLog.txtを開き、メモをする。書き終えたらC-x 0で閉じる。

PPvを並列する


次に、どうやってこのメモを活用するかという話になる。
やりたいのは、本文とこのメモとを見比べる環境を作ること。そのときには、対応箇所の一致ができればなおよい。

見出しをGrepでまとめるとか一々ChangeLogを手動で編集し直すとかそれ以前にメモの書式を変えるだとかいくつかやりようはあると思うが、単純にPPvを左右に並べるだけでいいのではないかと思った。




一々これを手動でやるのも面倒なので、PPv上で実行すれば

  1. 同フォルダのChangelog.txtを別IDのPPvで開き
  2. 右にくっつける
コマンドを作る。以下をカスタマイザーで編集して取込。
KV_main = { ; PPvメイン窓
\C ,%Obi %0\PPVW.EXE -bootid:Z  %1\ChangeLog.txt %: *fitwindow %N,%NVZ,6
}

PPvで文章を閲覧してて、そのメモを見たいという場合は、Shift+Cを押せば右にそのメモが表示されるので、見比べることができます。
PPXWIN.DLLが必要なので、ない場合はTORO's Libraryからダウンロードしましょう。

メモの反映作業


PPvで閲覧時に



Shfit+CをPPv上で押して、ChangeLog.txtを表示する



編集するときは、PPv上でEを押してエディタで開き、メモを元にして修正する。メモを書くときにどの見出しと対応しているかわかるようにしておいて、個々の見出しごとに修正するのがいい。

修正し終えたら、最後にメモをざっと見て反映させてない箇所が残ってないかの確認をする。

特定の章だけを抜き出したい


ChangeLogを使うと、時系列でどんどん書き連ねることになる。だが、実際にメモとして活用したいときは、カテゴリーごとにまとまっていたほうがいい。ではどうやってそれを実装するかということになる。

これも単純に、PPvでクリップボードの内容を表示させて対処すればいいのではないかと思った。

PPvをアクティブにしてCtrl+Vを押すと、クリップボードの内容を表示することができるのでこれを利用。先のChangeLogの閲覧時、その一部だけを表示したいとしましょう。




例えばこの黄色く囲んだ箇所だけを表示したいなら、範囲選択してCtrl+C、Ctrl+Vとすると


このようになる。

で、同じカテゴリーのことがいろいろな場所に散らばっている場合のをまとめて一覧表示させたいなら、それらをクリップボードに追加コピーしてPPvで表示させればいい。
そのためのスクリプトを用意。

ClipAdd.js

//!*script

PPx.Clipboard = PPx.Clipboard + "\n\n" + PPx.Extract("%*selecttext");

PPvの選択範囲をクリップボードに追加コピーするスクリプトです。
このファイルをScriptフォルダに保存してから、以下を編集して取込。

KV_main = { ; PPvメイン窓
&C ,*script %0\Script\ClipAdd.js
}

これで、Alt+Cで、選択範囲をクリップボードに追加コピーできるようになる。

例えば、以下のファイルの黄色く囲んだ箇所のみを表示したい場合は



最初の範囲を選択してCtrl+Cでコピー



次の範囲を選択してAlt+Cで追加コピー


PPv上でCtrl+V


単純すぎるというか原始的すぎるような気がしないでもないが、とりあえずこれでやってみる。

思ったこととか


プリントを使っていた時は、反映作業をするときはPCの横に書き込み済みのプリントを書見台とかで立てかけて、それを見ながらということをしてた。それと比べるとこっちのが色々とスムーズですね。プリントを使うメリットは、PCの前でなくても作業ができる、ことくらいしかなさそう。

今、ネックになるんじゃないかなーっと思ってるのは、ChangeLogの古いメモがずっと残されること。
例えばプリントだったら、それを参照して本文を修正してしまったら、後は書き残したことが無いかざっとメモを閲覧したあと、プリントごと捨ててしまうということをしている。反映された以上、そのメモはもう使うことはないしこの方がいい。残っていてもノイズにしかならない。

だが、今回のChangeLogだとずっとそれが残ってしまう。2日とか3日とか、メモをしばらくの間書きためておいてあとでまとめて修正作業をする、というのだったらこの仕組はいいのだが、その後がね。捨てるという過程を組み込めない。

2013年7月2日火曜日

ChangeLogでメモ?

問題意識


一つの文章をどう作っていくか、という問題がある。
メモをためることはMoEの手法を使えばできるが、それを材料にしてどのように一つの文章を作っていくか、ということについてはしっくりくるものはできてなかった。

今、僕がやっているのはプリントを介する方法である。

  • 文書データをプリントアウトする
  • そのプリントに書き込みをする
  • 元のデータに反映させる

ざっとこのようなことをしていた。
この方法の不満点は以下の点。

  • プリントを介すること。まず思いついたことを紙に書き込んだ後にデータにするのだから、二度手間である
  • それまでにためたメモデータの利用ができない。プリントを使って考察する環境において、参照することが基本できないから

この後者については、今までは関係しそうなメモのみをClipMarkFile.jsでまとめてプリントアウトして、参考にしたりはしてたが、あまり使えなかった。

PCの制限


じゃあPC上でやれよと思うかもしれないが、それでだいたい失敗してきたからこのようなことをしているのである。たいていは、集中が続かない、という仕方で破綻した。
なぜだろうかと考えたが、それは「文章を読みながらメモをする」という作業が必須なのに今までそれを実装してなかったからじゃないか、と思い至った。

プリントを使う場合は、基本、その文章を読み、そこで思いついたことを余白に書き込み、あとでそれから何か文章を作り直すということをしている。

  • 文章を読む→思いつきをメモする→それをいくつか溜める→それらを元に新たに文章を作り直す

という過程をとっている。
だがPCにおいては、これを実装していないため、

  • 文章を読む→新たに文章を作り出す

このようにメモを作って溜める過程を飛ばして、いきなり手がかりもない状態で文章を思いつき、作り出せということになる。このことがそもそも無理で、結局破綻してたのではないかと思うのだ。

今の枠組み


今のところシステムとして用意しているのは、howm形式のメモである。これが、この用途には適していないのではないか。

まず、今の形式では、本来の目的である保存のためのメモと、文章の作成過程での思いつきを書くためのメモを区別できない。
また、この用途のためにはhowmメモの仕組みは大掛かり過ぎる。別ファイルを開き、タイトルを書いて閉じて、という作業はもともとしていた文章を読むという行為をぶったぎってしまう。

一つのファイルを再利用して、何度も編集して使う、というのであればそのファイルを探す手間が生じるし、複数のファイルに分割してやるのではそれらをまとめて見る手軽な手段がない。

つまり、今までのhowmでやってたメモとは違った種類のメモが要請されてたのに、そのように認識せずそれに対応したものを用意していなかったのが問題なのではないかと思うのだ。

要請されるもの


今までのメモが

  • 保存して再利用するためのメモ

だとすれば、ここで要請されるのは

  • 一時的な、文章を読んでいるときに思いついたことを書くためのメモ

である。

この用途に即したものを実装したいわけであるが、要請されるのはざっと以下のようになるだろう。

  • 文章とメモファイルの対応
  • どの箇所についてのメモなのかの参照が容易
  • 文章を読む作業を妨げない。すぐ開け、すぐ閉じれる
  • 全体についてのメモと個々の箇所についてのメモとが分離できる

ChangeLog?


一番この用途に近いのは、ChangeLogではないかと思う。これはプログラムの変更履歴を書く形式の一つらしい。僕は今までは、プログラム作成時に思いつきを吐き出すのにこれを利用していた。

僕が使っているのはJunk ScriptsのChangelog.l

  • C-x 4 a

で新たな項目が追加される仕組み。

だが、これだと

  • どの箇所についてのメモなのかの参照が容易
  • 章ごとにメモを並列することができない。時系列のみ

の二点では求めるものとは違っている。
というわけで、しばらくはこの方面で何か工夫してできないかを考えてみたい。とりあえずEmacsのChangeLogを試してみようかな。

これがPC上でできれば、プリントを使う際に必ずネックになり、構造的に乗り越え不可そうな
  • 書き込みによる視認の低下
  • 元の文章が書き込みが増えると読みにくくなる
  • 余白の大きさに規定される

などは克服されるので、考えてみる価値はありそうかなと思っている。