Wednesday 18 April 2012

Drawing Windows Controls During Runtime Based on XML Feed


Published in CodeProject.com on 01/Aug/2011


Introduction

We design Windows native application by dragging and dropping components on the form directly using an IDE like Visual Studio. Nothing new – every Windows developer is familiar. It enables rapid application development and gives us more time to concentrate on implementing business rules efficiently that drives the whole system. All is good and fine so far. In other times, we might have to add these very widgets during runtime. One of the reasons might be that we want our system as flexible as possible. Drawing these components during runtime is fun and equally challenging. This article focuses on designing a simple GUI where the form components are added dynamically rather than during design time.

XML Feed

For this application, we will use XML document to describe its content and content–type. This allows us to launch different applications from a single form. One of the examples can be installation application that you might want to create in future. XPath is used to traverse through it. XML document looks like this:


<?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>
The above XML document describes two categories – Document and Software. Within each category, it has two items each. Category or “MainLink” tag makes side menu item and “Item” tag describes content and content link for our Windows Form. XPath traverses each node and retrieves necessary values.

Parsing XML Document

XmlDocument class is used to load XML feed.


XmlDocument  xDoc = new XmlDocument();
Once loaded in xDoc object, the following XPath expression retrieves all “MainLink” items.

Get all “<MainLink>”:


XmlNodeList nodeSideMenuItems = doc.SelectNodes("MenuItem/SideMenuItem");
Then it carries on traversing items within.


XmlNodeList nodes = sNode.SelectNodes("Item");
SideMenuItem is the class representing XML document. During XML parsing, all relevant node values are stored in List<SideMenuItem>. Class definition is shown below:


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; }
    }

Controls at Runtime

We have done our homework by parsing Feed.xml document and populated List<SideMenuItem> lists. The fun part begins now. We will add the controls both to side and content panel and register required events. Main methods responsible are briefly explained.

Code snippet for GenerateSidePanelControls() – Adds “Label” control to side panel of type “Panel” class and registers Click, Mouse Enter and Mouse Leave events.

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);
         }
Code snippet for SideLabel_Click() – Adds main content controls when use clicks on the side menu items.

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);
Code snippet for GenerateContentPanelControls(string sTagName) – Adds main content controls when user clicks on side menu item based side menu item title or tag name.

//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 Look

As per the feed.xml, the following components are drawn on the form at run-time:



Conclusion

Adding controls at runtime provides a different benefit. We don’t have to design the form again and again when the content changes. We simply inject different XML feed.

Let’s share ideas. Thank you for finding time to read.

Cheers,
Milan Gurung

No comments:

Post a Comment