基于 XML Feed 在运行时绘制 Windows 控件
如何基于 XML Feed 在运行时绘制 Windows 控件。
引言
我们通常使用像 Visual Studio 这样的 IDE,通过拖放组件到表单上来设计 Windows 原生应用程序。这没什么新鲜的——每个 Windows 开发者都熟悉这种方式。它能够实现快速应用程序开发,并使我们有更多时间专注于高效地实现业务规则,从而驱动整个系统。到目前为止一切都很好。但在其他情况下,我们可能需要在运行时添加这些小部件。其中一个原因可能是我们希望系统尽可能灵活。在运行时绘制这些组件既有趣又具有挑战性。本文重点介绍设计一个简单的 GUI,其中表单组件是在运行时动态添加的,而不是在设计时添加的。
XML 数据源
对于这个应用程序,我们将使用 XML 文档来描述其内容和内容类型。这允许我们从单个表单启动不同的应用程序。例如,您可以创建将来可能需要的安装应用程序。XPath 用于遍历它。XML 文档如下所示
<?xml version="1.0"?>
<MenuItem BasePath="c:\SampleApplication">
  <SideMenuItem Title="Documents">
    <Item ID="Document"
          LinkText="PDF Document"
          DetailText="View my pdf document"
          LinkType="pdf"
          Icon="\Icons\pdf.ico"
          ResourceLocation="\Resources\sampleDoc.pdf"
          />
   </SideMenuItem>
  <SideMenuItem Title="Softwares">
    <Item ID="CoolApp"
          LinkText="Next Cool Application"
          DetailText="Launch Cool Application"
          LinkType="exe"
          Icon="\Icons\exe.ico"
          ResourceLocation="="\Resources\CoolApp.exe"
          />
    <Item ID="KillerApp"
          LinkText="Next Killer application"
          DetailText="Launch Killer Application"
          LinkType="exe"
          Icon="\Icons\exe.ico"
          ResourceLocation="\Resources\KillerApp.exe "
          />
   </SideMenuItem>
  <SideMenuItem Title="Support">
    <Item ID="SupportSite"
          LinkText="Support Site"
          DetailText="Visit Support Site"
          LinkType="url"
           Icon="\Icons\ie.ico"
          ResourceLocation="\Resources\support.htm"
          />
  </SideMenuItem>
</MenuItem>
上面的 XML 文档描述了两个类别——Document 和 Software。在每个类别中,它都有两个项目。Category 或 “MainLink” 标签创建侧边菜单项,而 “Item” 标签描述了 Windows 表单的内容和内容链接。XPath 遍历每个节点并检索必要的值。
解析 XML 文档
XmlDocument 类用于加载 XML 数据源。
XmlDocument  xDoc = new XmlDocument();
加载到 xDoc 对象后,以下 XPath 表达式检索所有 “MainLink” 项。
获取所有 “<MainLink>”
XmlNodeList nodeSideMenuItems = doc.SelectNodes("MenuItem/SideMenuItem");
然后它继续遍历其中的项目。
XmlNodeList nodes = sNode.SelectNodes("Item");
SideMenuItem 是代表 XML 文档的类。在 XML 解析期间,所有相关节点值都存储在 List<SideMenuItem> 中。类定义如下所示
public class SideMenuItem
    {
        public string TagName { get; set; }
        public List<ContentMenuItem> MenuItemList { get; set; }
    }
public class ContentMenuItem
    {
        public string TagName { get; set; }
        public string LinkText { get; set; }
        public string DetailText { get; set; }
        public string LinkType { get; set; }
        public string IconLocation { get; set; }
        public string ResourceLocation { get; set; }
    }
运行时控件
我们已经完成了准备工作,通过解析 Feed.xml 文档并填充了 List<SideMenuItem> 列表。有趣的部分现在开始了。我们将把控件添加到侧边和内容面板,并注册所需的事件。负责的主要方法简要说明如下。
- GenerateSidePanelControls()代码片段——将类型为 “- Panel” 类的 “- Label” 控件添加到侧边面板,并注册 Click、MouseEnter 和 MouseLeave 事件。- int topPosition = 15; foreach (SideMenuItem sItem in _sideMenuItemList) { Label objLabel = new Label(); objLabel.Name = sItem.TagName; objLabel.Text = sItem.TagName; objLabel.Left = 15; objLabel.Top = topPosition; objLabel.Font = _normalFont; sidePanel.Controls.Add(objLabel); topPosition += 25; objLabel.Click += new System.EventHandler(SideLabel_Click); objLabel.MouseEnter += new System.EventHandler(SideLabel_MouseEnter); objLabel.MouseLeave += new System.EventHandler(SideLabel_MouseLeave); } 
- SideLabel_Click()代码片段——当用户点击侧边菜单项时,添加主要内容控件。- Label objLabel = (Label)sender; objLabel.Font = _boldFont; //Make rest of the Side Label have normal font foreach (Control ctrl in sidePanel.Controls) { if (ctrl is Label) { if (ctrl.Name != objLabel.Name) { ctrl.Font = _normalFont; } } } GenerateContentPanelControls(objLabel.Name);
- GenerateContentPanelControls(string sTagName)代码片段——根据侧边菜单项标题或标签名,当用户点击侧边菜单项时,添加主要内容控件。- //Get the side menu item based on tagName SideMenuItem sMenuItem = null; foreach (SideMenuItem sItem in _sideMenuItemList) { if (sItem.TagName == sTagName) { sMenuItem = sItem; break; } } contentPanel.Controls.Clear(); Label spacer = new Label(); spacer.Height = 10; spacer.Width = 562; contentPanel.Controls.Add(spacer); foreach (ContentMenuItem cItem in sMenuItem.MenuItemList) { FlowLayoutPanel flowLayoutPanel = new FlowLayoutPanel(); flowLayoutPanel.Width = 550; flowLayoutPanel.AutoSize = true; // <IconLocation> tag control string iconLocation = cItem.IconLocation; PictureBox icon = new PictureBox(); icon.Size = new Size(50, 50); icon.Image = new Bitmap(iconLocation); icon.SizeMode = PictureBoxSizeMode.CenterImage; flowLayoutPanel.Controls.Add(icon); // innerFlowPanel FlowLayoutPanel innerFlowPanel = new FlowLayoutPanel(); innerFlowPanel.Width = 500; // <LinkText> tag control string linkText = cItem.LinkText; Label lblLinkText = new Label(); lblLinkText.Name = cItem.TagName; lblLinkText.Text = linkText; lblLinkText.Font = _normalFont; lblLinkText.ForeColor = Color.Blue; lblLinkText.AutoSize = true; innerFlowPanel.Controls.Add(lblLinkText); // linebreak Label lineBreak = new Label(); lineBreak.Height = 0; lineBreak.Width = 562 - (lblLinkText.Width + icon.Width); innerFlowPanel.Controls.Add(lineBreak); // <DetailText> string detailText = cItem.DetailText; Label lblDetailText = new Label(); lblDetailText.Text = detailText; lblDetailText.Font = _normalFont; lblDetailText.AutoSize = true; innerFlowPanel.Controls.Add(lblDetailText); innerFlowPanel.Height = lblLinkText.DisplayRectangle.Height + lblDetailText.DisplayRectangle.Height + 5; flowLayoutPanel.Controls.Add(innerFlowPanel); contentPanel.Controls.Add(flowLayoutPanel); //Register events lblLinkText.Click += new System.EventHandler(ContentLabel_Click); lblLinkText.MouseEnter += new System.EventHandler(ContentLabel_MouseEnter); lblLinkText.MouseLeave += new System.EventHandler(ContentLabel_MouseLeave); } 
UI 界面
根据 feed.xml,以下组件将在运行时绘制到表单上
 
 
结论
在运行时添加控件提供了不同的好处。当内容更改时,我们不必一遍又一遍地设计表单。我们只需注入不同的 XML 数据源即可。
让我们分享想法。
历史
- 2011 年 7 月 31 日:初始版本




