偶然看到一個問題,『Listview 預設選取某行』, 內容大意是說如何當 ListView.Focused = fasle 的狀態下,還能夠保持反白。
最簡單的解法,當然就是 Hideselection = false,但這樣在失去焦點的狀態背景會變成很淺的灰色,老人家眼睛不好可能會看不出來。
另外一個解法,就是自己畫那些 ListViewItem,其實也不是太難,我們一步步來做這個簡單的試驗。
就擺上一個 ListView 、Button、TextBox 以及 CheckBox,主角是 ListView,其他只是為了測試焦點而已。
為了避免講不清楚,我們不在設計畫面上調整屬性,直接寫一個 InitialListView() method 來初始化設定與資料。
private void InitialListView()
{
listView1.View = View.Details;
listView1.GridLines = true;
listView1.LabelEdit = false;
listView1.FullRowSelect = true;
listView1.Columns.Add("編號", 100);
listView1.Columns.Add("測試文字", 100);
for (int i = 0; i < 10; i++)
{
var item = new ListViewItem($"No.{i}");
item.SubItems.Add($"文字{i}");
listView1.Items.Add(item);
}
}
然後在 Form 的建構式中呼叫它
public Form1()
{
InitializeComponent();
InitialListView();
}
接著執行這個程式,先隨便在 ListView 選擇一個 Row
然後把焦點移向另外一個控制項,比方 Button,你會發現原來被選擇的那一項背景又恢復成預設的樣式。
為了達成在失去焦點時還能保持背景樣式,我們得要自己畫 ListView 上的項目。第一個步驟是設定 ListView 的 OwnerDraw 屬性為 true,這表示我們要自己畫;一旦設定了這個屬性為 true,除了項目以外,Column Header 也得自己畫。我們需要處理兩個 Draw 的行為,(1) 當 DrawSubItem 事件被引發時會呼叫相對應的事件委派函式畫 SubItems (2) 當 DrawColumnHeader 事件被引發的時候會呼叫相對應的事件委派函式畫 Column Header。因此將 InitialListView() 修改如下:
private void InitialListView()
{
listView1.View = View.Details;
listView1.GridLines = true;
listView1.LabelEdit = false;
listView1.FullRowSelect = true;
listView1.Columns.Add("編號", 100);
listView1.Columns.Add("測試文字", 100);
for (int i = 0; i < 10; i++)
{
var item = new ListViewItem($"No.{i}");
item.SubItems.Add($"文字{i}");
listView1.Items.Add(item);
}
listView1.OwnerDraw = true;
listView1.DrawSubItem += ListView1_DrawSubItem;
listView1.DrawColumnHeader += ListView1_DrawColumnHeader;
}
這一篇先不管 Column Header,因此維持原來系統的渲染方式,只要寫下 e.DrawDefault = true; 即可。
private void ListView1_DrawColumnHeader(object sender, DrawListViewColumnHeaderEventArgs e)
{
e.DrawDefault = true;
}
公堂上先假設一下,當一個列被選擇的時候,背景是 SkyBlue,文字顏色是白色;當列不在選擇狀態的時候,恢復原設定背景,文字為黑色。
private void ListView1_DrawSubItem(object sender, DrawListViewSubItemEventArgs e)
{
if (e.Item.Selected)
{
e.Graphics.FillRectangle(new SolidBrush(Color.SkyBlue), e.Bounds);
e.Graphics.DrawString(e.Item.Text, listView1.Font, new SolidBrush(Color.White), e.Bounds.Location);
}
else
{
e.DrawBackground();
e.Graphics.DrawString(e.Item.Text, listView1.Font, new SolidBrush(Color.Black), e.Bounds.Location);
}
}
請注意,一定要先畫背景再畫文字,否則你的字會被蓋住。執行結果如下圖:
接著,我們希望當 ListView 沒有取得焦點的時候,背景變成紅色,該怎麼修改 ListView1_DrawSubItem 方法呢 ? 其實很簡單,只要判斷 ListView.Focused 屬性做不同的處理即可。
private void ListView1_DrawSubItem(object sender, DrawListViewSubItemEventArgs e)
{
if (e.Item.Selected)
{
if (((ListView)sender).Focused)
{
e.Graphics.FillRectangle(new SolidBrush(Color.SkyBlue), e.Bounds);
}
else
{
e.Graphics.FillRectangle(new SolidBrush(Color.Red), e.Bounds);
}
e.Graphics.DrawString(e.Item.Text, listView1.Font, new SolidBrush(Color.White), e.Bounds.Location);
}
else
{
e.DrawBackground();
e.Graphics.DrawString(e.Item.Text, listView1.Font, new SolidBrush(Color.Black), e.Bounds.Location);
}
}
這樣就完成了一個簡易的自訂 ListView 渲染行為,很簡單吧。
程式碼範例位於 https://github.com/billchungiii/DrawListViewSamples