文系未経験から一人前のプログラマーになるまでの日記

文系未経験でプログラマーとして採用してもらえたものの、プログラミング業務ができずに伸び悩んでいるプログラマーが会社で役に立てるようになるまでの日記です。

仮想リストビューの作成の仕方(※学習途中)

はてなブログでの文章のフォーマットに問題あり。
はてな記法ではシンタックスハイライトのやり方に問題あり。
解決:VC++の場合は,
">|cpp|"

"||<"
でソースを囲み、
コードブロックは、行末ではなく、その行の最初の文字にし、
">|cpp|"

"||<"
を書くときは必ず行頭に書く。
Markdownでは改行するときに問題あり。
目標:以下の学習を可能な限り進める。
イベントハンドラの引数の意味調査
・ON NOTIFYの処理調べる
・pDispInfo調べる
→ 仮説:これらは仮想リストビューの処理内容に含まれる模様なので、
仮想リストビューのサンプルを作成してみたら、その過程で理解できると思われる。
 
【仮想リストビューの作り方】
1. Owner DataをTrueにしてリストビューを仮想リストビューに設定する。
f:id:SkyWing:20200227063441p:plain

 2. 仮想リストビューで扱う要素数を設定する。

BOOL CSampleVirtualListControlDlg::OnInitDialog()
{
	CDialogEx::OnInitDialog();

	// "バージョン情報..." メニューをシステム メニューに追加します。

	// IDM_ABOUTBOX は、システム コマンドの範囲内になければなりません。
	ASSERT((IDM_ABOUTBOX & 0xFFF0) == IDM_ABOUTBOX);
	ASSERT(IDM_ABOUTBOX < 0xF000);

	CMenu* pSysMenu = GetSystemMenu(FALSE);
	if (pSysMenu != nullptr)
	{
		BOOL bNameValid;
		CString strAboutMenu;
		bNameValid = strAboutMenu.LoadString(IDS_ABOUTBOX);
		ASSERT(bNameValid);
		if (!strAboutMenu.IsEmpty())
		{
			pSysMenu->AppendMenu(MF_SEPARATOR);
			pSysMenu->AppendMenu(MF_STRING, IDM_ABOUTBOX, strAboutMenu);
		}
	}

	// このダイアログのアイコンを設定します。アプリケーションのメイン ウィンドウがダイアログでない場合、
	//  Framework は、この設定を自動的に行います。
	SetIcon(m_hIcon, TRUE);			// 大きいアイコンの設定
	SetIcon(m_hIcon, FALSE);		// 小さいアイコンの設定

	// TODO: 初期化をここに追加します。

	// 仮想リストビューで扱う要素数を設定する
	m_list.SetItemCount(100);

	return TRUE;  // フォーカスをコントロールに設定した場合を除き、TRUE を返します。
}

 
仮想リストビュー作成のために使用すると思われる構造体:

typedef struct _LVITEM
{
    UINT   mask;          // 有効メンバを示すフラグ
    int    iItem;         // アイテムのインデックス
    int    iSubItem;      // サブアイテムインデックス
    // 以下省略
} LVITEM, FAR *LPLVITEM;

 
考察:
UINT mask; // 有効メンバを示すフラグ
上記は条件文で
== 0x0001 (LVIF_TEXT) pszText
 
参考URL:
http://chokuto.ifdef.jp/urawaza/struct/LVITEM.html

2020/03/04(水) 6:33
クラスウィザードを使ってダイアログクラスにリストコントロールのメンバ変数を追加しようとしたら、ダイアログクラスのIDが見つからないというエラーが発生した。
対策としては、新規プロジェクトを作成し直したら、リストコントロールのメンバ変数を追加できた。

f:id:SkyWing:20200304063640p:plain

2020/03/07(土) 6:14
f:id:SkyWing:20200307061339p:plain 

OnInitDlg関数に上記のソースをコピペしたら、
引数不一致のエラーが出た。
なので、MSDNのドキュメントの以下の部分を読解中。
→ 以下大雑把に理解完了。

int InsertColumn(
    int nCol,
    LPCTSTR lpszColumnHeading,
    int nFormat = LVCFMT_LEFT,
    int nWidth = -1,
    int nSubItem = -1);

パラメーター
nCol
新しい列のインデックス。
lpszColumnHeading
列の見出しを含む文字列のアドレス。
nFormat
列の配置を指定する整数。 次のいずれかの値を指定できます。LVCFMT_LEFT、LVCFMT_RIGHT、または LVCFMT_CENTER。
nWidth
列の幅 (ピクセル単位)。 このパラメーターが-1 の場合、列の幅は設定されません。
nSubItem アイテム
列に関連付けられているサブ項目のインデックス。 このパラメーターが-1 の場合、サブ項目は列に関連付けられません。
戻り値
成功した場合は新しい列のインデックス。それ以外の場合は-1。
Remarks
リストビューコントロールの左端の列は左揃えにする必要があります。
LVCOLUMN構造体には、レポートビューの列の属性が含まれています。 また、列に関する情報を受信するためにも使用されます。 この構造体については、Windows SDK を参照してください。


結論:
引数不一致のエラーは、新しい列のインデックス(lpszColumnHeading)が""で囲った文字列を設定されていたから。
正しくは、_T("")で囲う必要があった。
理由:
恐らくコンパイラが文字列はいつもユニコードかマルチバイトで変換するか考えてコンパイルしているから。
→ _T("")で文字列を囲うと、ユニコードで設定しているなら、ユニコードとして処理、
マルチバイトで設定しているなら、マルチバイトで処理するので、文字列を設定するときは、_T("")を使うと良さそう。

マルチバイトとユニコードについては、いつも学習しては忘れてしまうので、備忘録として
ブログを書く。
tobeabletoprogramformycompany.hatenadiary.jp

Insert関数でカラムを2つ追加した後、
通常のListViewは、これにアイテムを追加していけば良いが、仮想ListViewにするつもりなので、アイテムは追加しない。
という記載を読んだが、改めて仮想ListViewってなんだ??って思った。
おおよその理解は、まだ実体化していない、言うなれば透明のビューを作って、
大きなカラムや行を入れる処理をしても高速で処理できる程度のものだが、
「仮想ListViewにするつもりなので、アイテムは追加しない。」
とあったので、「アイテムを追加しない...ビューが仮想ListViewなのか??」
仮想ListViewがまだ実体化していないビューならアイテムを追加してもよいものだが...
と思って改めて仮想ListViewって何かよくわからなくなった。

また、「仮想ListViewでは、自分でデータを管理することになる。」
とあったが、どういうことか?
データ管理の仕方だと、表を作るのを自分でしたりとか...データベースを作成したりとか...
それって通常のListViewでも同様のことをする気がするが...

まあ、後で出てくるかもしれない。次に進もう。


次は、
m_mylist.SetItemCountEx(9999, LVSICF_NOINVALIDATEALL);
の処理の説明があった。
SetItemCountExは、名前からして、
・アイテム(エクセルで言う、行のこと)を設定する
・アイテムの数を数える。
ことをする関数かと思われるが、
この「Ex」は何かな?と思った。
っあ!仮想リストビューに対する処理のことか!!なるほど!!

第2引数は、
項目数をリセットした後のリストビューコントロールの動作を指定する
MSDNにあった。

「項目数をリセットした後」とはどういう意味だろう...?
項目数はアイテム(エクセルで言う、行のこと)のことだから、
アイテム数をリセットした後、
つまり、SetItemCountExの第1引数で設定したアイテム数に設定(リセット)
した後ということか..?

あと、
「リストビューコントロールの動作を指定する」
の動作の指定がよくわからない。
これは以下の設定値を理解すれば、どのような動作を指定するのかわかるだろう。

設定値は2つあるようだ。

LVSICF_NOINVALIDATEALL
リストビューコントロールは、影響を受ける項目が現在表示されている場合を除き、再描画されません。 これが既定値です。
「リストビューコントロールは、影響を受ける項目」....って何だろう?
リストビューコントロールの項目は..項目はアイテム(エクセルで言う、行のこと)のことだから、
ああ! ダイアログ上で表示されていない領域(スクロールバーで動かさないと見えない領域)は
再描画されないということか!!

LVSICF_NOSCROLL 項目数が変更されても、リストビューコントロールはスクロール位置を変更しません。
これは、例えば項目数を10 → 100に変更しても、リストビューコントロールは項目数が10のときと
スクロールがある位置が変わらないということ??
でもそれだとあたりまえじゃん...位置が例えば100にしたら、スクロールできる範囲の真ん中くらいに行ってしまったら
使いにくいし....
よくわからないから、使ってみて動作を考えるか!
→ よくわからない....見た目上はLVSICF_NOINVALIDATEALLと同じように見えた。

仮想ListViewは、リストの表示のみを行い、データは管理しない。
→ なるほどなるほど。データも管理しちゃうと処理遅くなっちゃうしね。

表示に必要な時だけ、イベントでデータを要求するので、必要なデータを渡してやれば良い。
通常、データはプログラムが持っているのが普通なので、むしろ仮想ListViewの方がやりやすかったりする。
「データはプログラムが持っているのが普通なので、むしろ仮想ListViewの方がやりやすかったりする。」
はどういうことだろう...まあ、構造体やクラスのインスタンス等でプログラムがデータを持っているという
部分は理解できる。だから..データを仮想リストビューに渡してやるだけで表示処理は作れるし、高速だから
やりやすい(良い処理を作りやすい)ということか?

「ListViewにデータを渡す」について

「仮想ListViewでは、ListViewからのアイテム番号、カラム番号の問合せに対して、対応するデータを返せば良い。
具体的には、LVN_GETDISPINFOを処理する関数を定義する。」
→ 「ListViewからのアイテム番号、カラム番号の問合せに対して」の部分がわからない。
疑問:
・ListViewを作るの?
→ 作んじゃね?問い合わせ元がないとそもそも問い合わせが発生しないし。
・ListViewからのアイテム番号、カラム番号の問い合わせって具体的に何?
→ ああ、Getなんちゃら関数で取得すること...かな?

SDKなら、WM_NOTIFYイベントを捕まえて、lparamに入ってるポインタから、LVN_GETDISPINFO要求であることを確認しなきゃいけないので、かなり楽だ。」
→ ああ、MFCならこれしなくて良いってことね。

line.Left( pDispInfo->item.cchTextMax );
Return the substring consisting of the leftmost ‘nCount’ characters
Leftは、左端のn文字の切り取った文字を返す。
→ item.cchTextMaxがアイテムの文字列のサイズを表すので、
このソースの意味は、
line文字列(行)の文字数を行の左端から数えて行の文字列のサイズまでとする
ということだと思う。

f:id:SkyWing:20200308151145p:plain

f:id:SkyWing:20200308151324p:plain

結果:
反省:
次回: