今回は、継承したListViewに行の高さを取得や設定するための、RowHeightプロパティを持たせようというお話。
行の高さの設定方法は、下記事に書いてあるので今回は行の高さの取得方法を考える。
今回もテストは.Net8で行っているが、多少表記が違うだけで.Net Frameworkでも実装できると思う。
目次
ListViewItem.Boundsを利用する
ListViewItemがない場合
時間のない人はこちら
ListViewItem.Boundsを利用する
前述の記事 の追記にあるように、ListViewItem.Bounds
を利用すると、項目に外接する四角形を取得できる。そこからHeight
の値を取得すれば行の高さが取得できそうだ。
上記説明にもある通り、これはlistView.GetItemRect(Index)
と同値であり、さらに言えばSendMessage(LVM_GETITEMRECT, index, ref rect)
を実行しているに他ならない。
つまり、ListViewItem.Bounds
を利用したい場合、項目のIndex
が必要となる。
では、項目数が0の場合、つまりListView.Items.count
が0
の時どうすればいいのか。
ListViewItemがない場合
恐らく最も簡単な方法は、適当な項目を追加して、測って、削除する、だと思う。
注意点は、VirtualMode
を使用している場合、安易に追加するとInvalidOperationException
が発生することだろうか。ただ、VirtualMode
の場合は項目を追加する必要は無く、VirtualListSize
を弄るだけで取得することができる。
以上を踏まえて、行の高さを取得するメソッドを作る
int getRowHeight()
{
var height = 0;
// 項目があるなら普通に取得
if (listView.Items.Count > 0)
height = listView.GetItemRect(0).Height;
// 仮想モードの場合、VirtualListSizeを使い計測
else if(listView.VirtualMode)
{
listView.VirtualListSize = 1;
height = listView.GetItemRect(0).Height;
listView.VirtualListSize = 0;
}
// 項目無しかつ仮想モードでもない場合、項目追加で計測
else
{
listView.Items.Add(new ListViewItem());
height = listView.GetItemRect(0).Height;
listView.Items.Clear();
}
// 高さは外接する四角形よりも1ピクセル小さい
return height - 1;
}
細かいことを書くと、listView.Columns
が設定されていない場合、上記メソッドは-1
を返す。カラムが未設定の場合でも高さを取得したい場合は、項目と同様一度カラムを追加して計測、その後削除を行う必要がある。
今回の内容と、前述の記事の内容を合わせて、RowHeight プロパティを持った継承ListViewのコードを下記に記して、今回の話は終了、お粗末様。
ソースコード
partial class ListViewEx : ListView
{
int rowHeight;
public int RowHeight
{
get
{
if (Items.Count > 0)
rowHeight = GetItemRect(0).Height;
else if (VirtualMode)
{
VirtualListSize = 1;
rowHeight = GetItemRect(0).Height;
VirtualListSize = 0;
}
else
{
Items.Add(new ListViewItem());
rowHeight = GetItemRect(0).Height;
Items.Clear();
}
return rowHeight - 1;
}
set
{
// 0以下には設定しない
if(value < 1) return;
// WM_MEASUREITEMで使われるrowHeightはBounds.Heightと同値にする
rowHeight = value + 1;
var size = Size;
var style = GetWindowLong(Handle, GWL_STYLE);
style |= LVS_OWNERDRAWFIXED;
SetWindowLong(Handle, GWL_STYLE, style);
Size = new Size(size.Width, size.Height + 1);
style ^= LVS_OWNERDRAWFIXED;
SetWindowLong(Handle, GWL_STYLE, style);
Size = size;
}
}
protected override void WndProc(ref Message m)
{
base.WndProc(ref m);
if (WM_MEASUREITEM == m.Msg)
{
Marshal.WriteInt32(m.LParam + (sizeof(uint) * 4), rowHeight);
m.Result = 1;
}
}
const int
GWL_STYLE = -16,
LVS_OWNERDRAWFIXED = 0x0400,
WM_MEASUREITEM = 0x002C + 0x2000;
[DllImport("user32.dll")]
static extern int GetWindowLong(IntPtr hWnd, int nIndex);
[DllImport("user32.dll")]
static extern int SetWindowLong(IntPtr hWnd, int nIndex, int dwNewLong);
}
以下使用方法
public partial class Form1 : Form
{
public Form1()
{
InitializeComponent();
var listView = new ListViewEx()
{
View = View.Details,
GridLines = true,
Location = new(12, 12),
Size = new(350, 150)
};
// カラムだけ用意
listView.Columns.Add(new ColumnHeader() { Text = "test01" });
Controls.Add(listView);
// サイズを取得する場合、これはデモなので値は廃棄('_')している
_ = listView.RowHeight;
// サイズ変更の場合、RowHeightに代入すると行の高さが変わる
listView.RowHeight = 10;
}
}