首頁
編程語言
C語言|JAVA編程
Python編程
網頁編程
ASP編程|PHP編程
JSP編程
數據庫
MYSQL數據庫|SqlServer數據庫
Oracle數據庫|DB2數據庫
您现在的位置: 程式碼之家 >> 編程語言 >  >> .NET 網頁程式設計 >> C# >> 關於C#

C# 從 XML 動態載入 WinForms 選單完整實作

     編輯:關於C#

前言

動態選單系統允許不重新編譯程式即可調整選單結構。本文示範在 WinForms 中讀取 XML 定義,透過反射動態建立 MenuStrip 並綁定事件處理器的完整架構。

XML 選單定義 (Menu.xml)




  
    
      開啟檔案
    
    
      關閉檔案
    
  
  
    
      剪下
    
    
      複製
    
    
      貼上
    
  

選單載入核心類別 (Menu.cs)


using System;
using System.IO;
using System.Reflection;
using System.Windows.Forms;
using System.Xml;

namespace DynamicMenuDemo
{
    public class Menu
    {
        private string _path;
        public string Path 
        { 
            get => _path; 
            set => _path = value; 
        }

        public bool FileExists()
        {
            return File.Exists(_path);
        }

        public void LoadAllMenu(MenuStrip menuStrip)
        {
            if (!FileExists())
                throw new FileNotFoundException("XML 選單檔案不存在", _path);

            using (XmlTextReader reader = new XmlTextReader(_path))
            {
                while (reader.Read())
                {
                    // 處理主選單
                    if (!reader.IsEmptyElement && reader.Name == "MainMenu")
                    {
                        string id = reader.GetAttribute("id");
                        string title = reader.GetAttribute("title");
                        
                        var menuItem = new ToolStripMenuItem
                        {
                            Name = $"Item{id}",
                            Text = title
                        };
                        
                        if (!string.IsNullOrEmpty(title))
                            menuStrip.Items.Add(menuItem);
                    }
                    // 處理子選單
                    else if (!reader.IsEmptyElement && reader.Name == "SubMenu")
                    {
                        string id = reader.GetAttribute("id");
                        string parentId = id.Substring(0, 2); // 前兩碼為父選單 ID
                        
                        // 讀取子選單標題
                        string title = "";
                        using (XmlReader subReader = reader.ReadSubtree())
                        {
                            while (subReader.Read())
                            {
                                if (!subReader.IsEmptyElement && subReader.Name == "Title")
                                {
                                    title = subReader.ReadElementContentAsString();
                                    break;
                                }
                            }
                        }
                        
                        if (string.IsNullOrEmpty(title))
                            continue;
                        
                        var subItem = new ToolStripMenuItem
                        {
                            Name = $"Item{id}",
                            Text = title
                        };
                        
                        subItem.Click += ToolSubItem_Click;
                        
                        // 尋找父選單並加入
                        var parentItem = menuStrip.Items[$"Item{parentId}"] as ToolStripMenuItem;
                        parentItem?.DropDownItems.Add(subItem);
                    }
                }
            }
        }

        private void ToolSubItem_Click(object sender, EventArgs e)
        {
            if (sender is ToolStripMenuItem item)
            {
                // 透過反射呼叫對應方法
                var methodClass = new MenuMethod();
                MethodInfo method = methodClass.GetType().GetMethod(item.Name);
                
                if (method != null)
                {
                    method.Invoke(methodClass, null);
                }
                else
                {
                    MessageBox.Show($"未實作方法:{item.Name}", "提示");
                }
            }
        }
    }
}

選單動作實作類別 (MenuMethod.cs)


using System;
using System.Windows.Forms;

namespace DynamicMenuDemo
{
    public class MenuMethod
    {
        public void Item101()
        {
            MessageBox.Show("執行:開啟檔案", "動作");
        }

        public void Item102()
        {
            MessageBox.Show("執行:關閉檔案", "動作");
        }

        public void Item111()
        {
            MessageBox.Show("執行:剪下", "動作");
        }

        public void Item112()
        {
            MessageBox.Show("執行:複製", "動作");
        }

        public void Item113()
        {
            MessageBox.Show("執行:貼上", "動作");
        }
    }
}

主表單整合 (Form1.cs)


using System;
using System.Windows.Forms;

namespace DynamicMenuDemo
{
    public partial class Form1 : Form
    {
        public Form1()
        {
            InitializeComponent();
        }

        private void Form1_Load(object sender, EventArgs e)
        {
            var menu = new Menu
            {
                Path = Path.Combine(Application.StartupPath, "Menu.xml")
            };

            try
            {
                if (menu.FileExists())
                {
                    menu.LoadAllMenu(this.menuStrip1);
                }
                else
                {
                    MessageBox.Show("找不到 Menu.xml 設定檔", "錯誤");
                }
            }
            catch (Exception ex)
            {
                MessageBox.Show($"選單載入失敗:{ex.Message}", "錯誤");
            }
        }
    }
}

架構改進:支援多層巢狀選單


// 遞迴處理任意深度選單
public void LoadMenuRecursive(XmlReader reader, ToolStripItemCollection parentCollection)
{
    while (reader.Read())
    {
        if (reader.NodeType == XmlNodeType.Element)
        {
            if (reader.Name == "MenuItem" || reader.Name == "SubMenu")
            {
                string id = reader.GetAttribute("id");
                string title = reader.GetAttribute("title") ?? "";
                
                // 讀取子標題 (若有)
                if (reader.Name == "SubMenu" && reader.HasAttributes)
                {
                    // 移至 Title 元素
                    while (reader.Read() && reader.Name != "Title") ;
                    if (reader.Name == "Title")
                        title = reader.ReadElementContentAsString();
                }
                
                var item = new ToolStripMenuItem
                {
                    Name = $"Item{id}",
                    Text = title,
                    Tag = id // 儲存 ID 供後續使用
                };
                
                item.Click += ToolSubItem_Click;
                parentCollection.Add(item);
                
                // 遞迴處理子選單
                if (reader.Name == "SubMenu" && !reader.IsEmptyElement)
                {
                    LoadMenuRecursive(reader.ReadSubtree(), item.DropDownItems);
                }
            }
        }
    }
}

改良版 XML 結構 (支援多層、圖示、快捷鍵)




  
    
    
    
    
    
  
  
    
    
  

現代化架構:MVVM 模式與命令綁定


// ViewModel 基礎
public class MenuItemViewModel : INotifyPropertyChanged
{
    public string Id { get; set; }
    public string Text { get; set; }
    public string IconPath { get; set; }
    public ICommand Command { get; set; }
    public List Children { get; set; } = new();
    
    public event PropertyChangedEventHandler PropertyChanged;
}

// 命令實作
public class RelayCommand : ICommand
{
    private readonly Action _execute;
    private readonly Func _canExecute;
    
    public RelayCommand(Action execute, Func canExecute = null)
    {
        _execute = execute;
        _canExecute = canExecute;
    }
    
    public bool CanExecute(object parameter) => _canExecute?.Invoke() ?? true;
    public void Execute(object parameter) => _execute();
    public event EventHandler CanExecuteChanged;
}

// XML 解析為 ViewModel
public List ParseMenuXml(string xmlPath)
{
    var doc = XDocument.Load(xmlPath);
    return doc.Root.Elements("MenuItem").Select(ParseMenuItem).ToList();
}

private MenuItemViewModel ParseMenuItem(XElement element)
{
    var item = new MenuItemViewModel
    {
        Id = element.Attribute("id")?.Value,
        Text = element.Attribute("text")?.Value,
        IconPath = element.Attribute("image")?.Value,
        Command = new RelayCommand(() => ExecuteAction(element.Attribute("action")?.Value))
    };
    
    item.Children = element.Elements("MenuItem").Select(ParseMenuItem).ToList();
    return item;
}

最佳實踐總結

面向建議
XML 路徑使用 Application.StartupPath 或設定檔,避免硬編碼
錯誤處理XML 解析、反射呼叫皆需 try-catch,提供友善錯誤訊息
效能應用程式啟動時一次性載入,避免重複讀取
擴充性採用遞迴解析支援任意層級,預留 Tag/Action 機制
維護性分離 XML 定義、載入邏輯、動作實作三層
現代化新專案建議採用 MVVM + Command Pattern,配合 DI 容器

總結

XML 動態選單透過「定義與程式碼分離」實現靈活配置。核心流程:XML 解析 → 建立 ToolStripMenuItem 階層 → 反射/命令綁定動作。傳統 WinForms 使用反射呼叫對應命名方法,現代架構建議改用 MVVM + Command Pattern,獲得更好的測試性、解耦合與維護性。無論採用何種架構,錯誤處理、路徑管理、擴充性設計都是關鍵成功因素。

  1. 上一篇文章:
  2. 下一篇文章:
Copyright © 程式碼之家 All Rights Reserved