[Winform] 動態產生下拉式表單的做法、問題與解決

在 Windows Form 中要以動態方式新增/移除選單項目是一件很容易的工作。簡單的講, 我們先在視窗上方加入一個 ToolStripMenuItem (在功能表與工具列中找到 ToolStrip 控制項, 再拉進視窗裡面), 接著, 加入一個 ToolStripMenuItem (假設將該項目命名為 "menuFile"), 然後我們就可以在程式中對它進行操作了...

在 Windows Form 中要以動態方式新增/移除選單項目是一件很容易的工作。簡單的講, 我們先在視窗上方加入一個 ToolStripMenuItem (在功能表與工具列中找到 ToolStrip 控制項, 再拉進視窗裡面), 接著, 加入一個 ToolStripMenuItem (假設將該項目命名為 "menuFile"), 然後我們就可以在程式中對它進行操作了。

在實際展示做法之前, 我們必須先準備資料來源。假設我們要從某個資料夾中取出符合副檔名為 .PDF 的檔案列表, 並把它們加到上述的下拉式選單底下; 以下我以 IEnumerable<FileInfo> 型別將結果傳回:

public IEnumerable<FileInfo> pdfPresent()
{
    DirectoryInfo dinfo = new DirectoryInfo(pdfDirectory);
    if (Directory.Exists(pdfDirectory))
    {
        IEnumerable<FileInfo> found = dinfo.EnumerateFiles("*.pdf", SearchOption.TopDirectoryOnly);
        return found;
    }
    else return null;
}

程式中 pdfDirectory 變數代表該資料夾的路徑。

接著, 我們就可以在程式中對選單進行操作了:

IEnumerable<FileInfo> list = pdfPresent();
foreach (FileInfo file in list)
{
    ToolStripMenuItem item = new ToolStripMenuItem(file.Name);
    item.Click += pdfSelected; // Assgin the event handler
    menuFile.DropDownItems.Insert(menuFile.DropDownItems.Count - 2, item);
}

程式中 pdfSelected 代表將要處理新增項目的事件處理函式, 這個你得自己寫。此處我們利用 menuFile.DropDownItems.Count 取得項目總數, 並且把新項目插入到所有項目的倒數第二項 (我在這個 File 選單中的最下面有一個固定項目叫做 Exit, 我希望把新項目插入到這個 Exit 項目的上面, 所以必須減 2)。

很不幸的, 這個程式一跑完, 你會發現所有項目的插入順序都是相反的。為什麼會這樣呢? 我 trace 程式幾次之後, 發現一個奇怪的現象: 照理說在 foreach 迴圈裡面我們已經使用 Insert 指令把 menuFile 的 DropDownItem 項目加上去了, 那麼 DropDownItems.Count 的值應該會逐次加一才對。然而, 它並不會加一。換句話說, 假設這個值是 3, 那麼無論你執行 Insert 指令幾次, 這個值始終維持 3。

依照程式的邏輯, 它應該把新增項目逐次加在 2, 3, 4, 5... 的位置上才對, 但是由於上述的問題, 它實際上每次都把新項目加在 2, 2, 2, 2... 的位置上, 所以難怪順序會相反了。

相對的, 使用相同的邏輯, 如果你改用 Add 指令而非 Insert 指令, 然後逐步 trace 程式的執行過程的話, 你會發現 DropDownItems.Count 的值是會逐次加一的。可惜 Add 指令無法指定從哪裡加上去, 它只能把項目加到最底下, 所以我無法以 Add 指令來取代 Insert。

我認定這應該算是 Windows Form 的一個 bug, 所以我已經向微軟回報。但是根據我過去的無數次經驗, 他們頂多只會回一個 As Design 或類似 Not A Bug 的回應 (死不認錯是程式設計師的「美德」之一, 微軟也不例外)。沒關係, 山不轉路轉, 像這種動態修改 MenuStrip 控制項的事情又不是隨時會做的事情, 我就大方一點, 給資料來源下一個反向排序不就得了!? 經修改後的程式如下:

IEnumerable<FileInfo> list = pdfPresent();
list = list.Reverse();
foreach (FileInfo file in list)
{
    ToolStripMenuItem item = new ToolStripMenuItem(file.Name);
    item.Click += pdfSelected; // Assgin the event handler
    menuFile.DropDownItems.Insert(menuFile.DropDownItems.Count - 2, item);
}

我承認這也許並不是很優美 (Graceful) 的解決辦法, 但它確實可以解決問題; 在微軟修好這個 bug 之前, 就將就著用吧!


Dev 2Share @ 點部落