WinForms 內建的 ListView 雖支援 OwnerDraw 自繪,但並未提供「在特定欄位直接顯示進度條」的開箱即用功能。本文示範如何繼承 ListView 並覆寫 OnDrawSubItem,以最少程式碼實現「欄位文字為數值時自動繪製進度條、支援自訂顏色、百分比文字置中」的效果,並搭配 Timer 展示動態更新。
public partial class ListViewEx : ListView
{
// 進度條欄位索引(-1 表示不啟用)
public int ProgressColumnIndex { get; set; } = -1;
// 進度條顏色
public Color ProgressColor { get; set; } = Color.Red;
// 進度文字顏色
public Color ProgressTextColor { get; set; } = Color.Black;
public ListViewEx()
{
InitializeComponent();
// 必須開啟 OwnerDraw 並設為 Details 檢視
this.OwnerDraw = true;
this.View = View.Details;
}
protected override void OnDrawColumnHeader(DrawListViewColumnHeaderEventArgs e)
{
e.DrawDefault = true; // 標題列維持預設繪製
base.OnDrawColumnHeader(e);
}
protected override void OnDrawSubItem(DrawListViewSubItemEventArgs e)
{
// 非目標欄位 → 預設繪製
if (e.ColumnIndex != ProgressColumnIndex)
{
e.DrawDefault = true;
base.OnDrawSubItem(e);
return;
}
// 目標欄位:嘗試解析數值
string text = e.Item.SubItems[e.ColumnIndex].Text;
if (IsNumeric(text))
{
float percent = float.Parse(text);
if (percent >= 1f) percent /= 100f; // 支援 0~100 或 0~1
DrawProgressBar(e.Graphics, e.Bounds, percent);
}
else
{
e.DrawDefault = true;
base.OnDrawSubItem(e);
}
}
// 簡易數值判斷(含小數點)
private bool IsNumeric(string s)
{
foreach (char c in s)
if (!"0123456789.".Contains(c)) return false;
return true;
}
// 實際繪製進度條
private void DrawProgressBar(Graphics g, Rectangle bounds, float percent)
{
if (bounds.Height <= 2 || bounds.Width <= 2) return;
if (bounds.Top < 0 || bounds.Top > this.Height ||
bounds.Left < this.Left || bounds.Left > this.Width) return;
// 進度填充區
int fillWidth = (int)(bounds.Width * percent);
Rectangle fillRect = new Rectangle(bounds.Left + 1, bounds.Top + 1,
Math.Max(0, fillWidth - 2), bounds.Height - 2);
using (Brush b = new SolidBrush(ProgressColor))
g.FillRectangle(b, fillRect);
// 外框
Rectangle borderRect = new Rectangle(bounds.Left + 1, bounds.Top + 1,
bounds.Width - 2, bounds.Height - 2);
g.DrawRectangle(Pens.RoyalBlue, borderRect);
// 百分比文字置中
StringFormat sf = new StringFormat
{
Alignment = StringAlignment.Center,
LineAlignment = StringAlignment.Center,
Trimming = StringTrimming.EllipsisCharacter
};
using (Brush tb = new SolidBrush(ProgressTextColor))
g.DrawString((percent * 100).ToString("F1") + "%", this.Font, tb, borderRect, sf);
}
private void InitializeComponent() { /* 設計工具自動產生 */ }
}
private void Form1_Load(object sender, EventArgs e)
{
// 建立測試資料:四欄,第 2 欄 (Index=1) 為進度
var item = new ListViewItem("5");
item.SubItems.Add("0"); // 進度欄位,初始 0%
item.SubItems.Add("3");
item.SubItems.Add("100");
listViewEx1.Items.Add(item);
// 設定進度欄位與顏色
listViewEx1.ProgressColumnIndex = 1;
listViewEx1.ProgressColor = Color.YellowGreen;
listViewEx1.ProgressTextColor = Color.Red;
// 啟動 Timer 每 100ms 遞增進度
timer1.Interval = 100;
timer1.Start();
}
private void timer1_Tick(object sender, EventArgs e)
{
var sub = listViewEx1.Items[0].SubItems[1];
int val = int.Parse(sub.Text);
if (val <= 100)
{
sub.Text = (val + 1).ToString();
listViewEx1.Invalidate(); // 觸發重繪
}
else timer1.Stop();
}
View = View.Details,否則 OnDrawSubItem 不會被呼叫。ProgressColumnIndex 可在執行期動態切換,適合多欄位輪流顯示進度的情境。0~100 視為百分比整數,0~1 視為小數,自動換算。Invalidate() 觸發重繪,而非直接呼叫 DrawSubItem,避免閃爍。OnDrawItem、OnDrawColumnHeader 等事件。
圖中第二欄顯示綠色進度條與紅色百分比文字,隨 Timer 從 0% 平滑遞增至 100%。
BackgroundWorker 或 Task.Run 執行長時間作業,透過 Progress<int> 回報進度,UI 執行緒只負責更新 SubItem.Text 並 Invalidate()。ListViewEx 封裝為元件(.dll)並加入工具箱,即可在設計拖曳使用,提升複用性。DataGrid + DataTemplate 實現更靈活的進度條樣式。