|
|||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
Announcements
Want a new Job?
Chapters
Services
Feature Zones
|
Latest News:The name is now official: MyXaml!!! MyXaml now supports Styles! (see revision history). Interested in participating in the development of this technology? Join the open-source project! This project is NOT intended to emulate Longhorn's markup language nor the MSAvalon namespace. Rather, MyXAML supports namespaces without using a DOM backbone, instead using the property setters defined in the namespace classes directly. Contents
If you rate this article, please explain your rating! Revision History3/10/04
An example of a style specification is: <xgg:Style def:Name='XP'> <xgg:StyleProperties> <xgg:PropertyStyle FlatStyle='System'/> </xgg:StyleProperties> </xgg:Style> <xgg:Style def:Name='ButtonStyle' BasedOn='{XP}'> <xgg:StyleProperties> <xgg:PropertyStyle Size='80, 25'/> </xgg:StyleProperties> </xgg:Style> And the usage is straight forward: <Button def:Name='OKButton' Style='{ButtonStyle}' Location='225, 10' Visible='true' Click='OnOK' Text='&OK'/> A couple notes about Style: Derived styles supersede properties defined in the "BasedOn" style. Because attributes are parsed from left to right, an object with a Style attribute can override a the property value by specifying the property value to the right of the Style attribute. Refactoring Your Previous Markup To review the changes made to the markup itself, see the body of this article. Basically, the old form was: <Canvas Name="Foo" FormBase="Form"..></Canvas> The new markup structure is: <Form Name="Foo"...></Form> More information can be found on the http://www.myxaml.com/ website--click on Tutorials. 2/25/04 - Many new features
2/16/04 - Added support for properties defined as attributes and for form events 2/13/04 - Initial Release As per Leppie's suggestion, I have modified the form generator to handle XML attributes as a means of specifying property values. The property model architecture is still required for more complicated property types however. This admittedly makes the XML considerably more readable! Currently, both modalities are supported (using attributes vs. using nodes), so you can mix and match as you please, but that may change in the future. IntroductionThis is a complete rewrite of the article. Rather than going into code examples, I'm going to illustrate each of the features of this library. The XMLFirst off, let's start with the fun part, which is specifying the form in XML. I'll use the above screenshot to demonstrate the various features of the generator. After this section, I'll have some architectural diagrams that illustrate different use models. General ArchitectureAn important consideration is that the nodes in the XML map directly to properties of the current context instance. For example, when a form is instantiated with a form base of "Form", the context instance is a Another important consideration is that, rather than inlining a control's attributes and children, you can at any point specify Canvas as an attribute, and define a new type for that specific control. We'll see example of this later. What that means is that, for the specific control, you may define code-behind instances, as all root nodes allow for code-behind instances. Third Party Components, Namespaces, and the XML HeaderTo begin with, let's inspect the XML header: <?xml version="1.0" encoding="utf-8"?> <UI xmlns="System.Windows.Forms, System.Windows.Forms, Version=1.0.5000.0, Culture=neutral, PublicKeyToken=b77a5c561934e089" xmlns:ka="OutlookBar, OutlookBarAssembly, Version=1.0.1.2, Culture=neutral, PublicKeyToken=null" xmlns:def="Definition"> <Form Name='PropertyTest' Location='10, 10' Size='600, 330' Visible='false' BackColor='LightSteelBlue' Closing='OnCloseEvent' Load='PropertyTestLoaded' Text='Simple Test Form'>Notice the use of namespaces to provide fully qualified names for the assemblies. This allows you to utilize third party components in your XML definition. Rather than using Microsoft's XAML namespace, I am choosing to code the assembly information directly in the namespace. This information has to reside somewhere, and this isn't XAML. Defining A Form, Properties and EventsIn XAML, the root node is typically a form in which you specify the coordinates of the child objects, so I chose to use the same name. The form is specified as: <Form Name='PropertyTest' Location='10, 10' Size='600, 330' Visible='false' BackColor='LightSteelBlue' Closing='OnCloseEvent' Load='OnLoad' Text='Simple Test Form'>Here, various properties are being set, and the "Load" event is being wired to an event handler. The event handler is a method name that is available in the target instance provided to the XmlGuiGenerator. For example:public class Demo { Form form; public Demo() { form=(Form)Generator.LoadForm("propertyTest.xml", "PropertyTest", this, null); form.ShowDialog(); } public void OnLoad(object sender, EventArgs e) { MessageBox.Show("OnLoad", sender.ToString()); } } Similar to the Notice that the FormBase is 'Form'. The form base can be anything, really, and we'll discuss that later on. A root node is special in that it can also have a code-behind or inline code instance that is compiled and instantiated at runtime. Several examples of this will be discussed later. Defining A Menu StructureIf the root node or child control supports the <Menu> <MenuItems> <MenuItem Text='&File' Popup='OnPopup'> <MenuItems> <MenuItem Text='New'/> <MenuItem Text='-'/> <MenuItem Text='E&xit' Click='ExitApp'/> </MenuItems> </MenuItem> <MenuItem Text='&Edit'/> <MenuItem Text='&View'/> </MenuItems></Menu> ControlsControls are added to the Controls collection, for example:<Controls> <Button Location='225, 100' Size='80, 25' Visible='true' BackColor='Control' Click='OnMyEvent'>Click Event!</Button> <Label Location='225, 50' Size='120, 40' Visible='true' Font='French Script MT, 22pt, style=Bold'>Font Test!</Label> <Label Location='10, 12' Size='50, 15' Visible='true'>Edit 1:</Label> <Label Location='10, 37' Size='50, 15' Visible='true'>Edit 2:</Label> <TextBox Location='60, 10' Size='100, 20' Visible='true'/> <TextBox Location='60, 35' Size='100, 20' Visible='true' Enabled='false'> Disabled</TextBox> <Panel BorderStyle='Fixed3D' Top='95' Left='20' Size='200, 178' Visible='true' BackgroundImage='jojo.jpg' Anchor='Bottom, Left'/> <GroupBox Location='225, 130' Size='120, 60' Visible='true' Text='First Group Box' Font='MS Sans Serif, 8pt, style=Bold'> <Controls> <RadioButton Location='10, 15' Size='70, 15' Visible='true' Font='MS Sans Serif, 8pt' Load='InitialRadioButtonState'> Like</RadioButton> <RadioButton Location='10, 30' Size='70, 15' Visible='true' Font='MS Sans Serif, 8pt'>Don't Like</RadioButton> </Controls> </GroupBox> ... This is a good example of inlined controls, in which the group box specifies child controls that it contains. Canvased ControlsThe second group box in the demo is canvased, meaning that it is defined in a separate root node. In the parent form XML, it declared as: <GroupBox Canvas="GroupBox2"/> A separate canvas defines the group box attributes and collections: <GroupBox Name='GroupBox2' Location='225, 200' Size='120, 60' Visible='true' Text='Second Group Box' Font='MS Sans Serif, 8pt, style=Bold'> <Controls> <RadioButton Location='10, 15' Size='70, 15' Visible='true' Font='MS Sans Serif, 8pt' Load='InitialRadioButtonState'> To Click</RadioButton> <RadioButton Location='10, 30' Size='100, 15' Visible='true' Font='MS Sans Serif, 8pt'>Or Not To Click</RadioButton> </Controls> </GroupBox> Note that the events are handled by the target context declared by the parent form, which in this case is the class instance passed in to the generator as described above. (More on event context later). Tab ControlSimilarly, tab pages can be inlined or canvased: <TabControl Location='350, 10' Size='240, 250' Visible='true'> <TabPages Canvas='TabPage1'/> <TabPages Canvas='TabPage2'/> <TabPages Name='TabPage3' Text='ListView' Load='PopulateListView'> <Controls> <ListView Location='5, 10' Size='222, 210' HeaderStyle='Nonclickable' FullRowSelect='true' GridLines='true' View='Details'> <Columns Width='150' TextAlign='Left'>State</Columns> <Columns Width='50' TextAlign='Center'>Abbr.</Columns> </ListView> </Controls> </TabPages> <TabPages Canvas='TabPage4'/> </TabControl> (In fact, the generator will allow you to specify a canvas as well as inlined collections, but that feature should not be relied upon). Third Party ControlsRemember the namespace "ka" in the XML header. He's how my Outlook Bar control is implemented in the XML: <TabPage Name='TabPage1' Text='Custom Control'> ... <Controls> <ka:OutlookBar Location='10, 10' Size='150, 210' Visible='true' BorderStyle='FixedSingle' Load='MyForm.OnLoad'/> </Controls> </TabPage> Note that the Now, what you ask, is in-between that "...", and what's "MyForm.OnLoad"??? Well, read-on. Inline CodeThe tab pages illustrate different means of implementing code-behind. The first is inlining. The full canvas for TabPage1 is defined as: <TabPage Name='TabPage1' Text='Custom Control'> <def:Code language="'C#'"> <reference assembly="System.Drawing.dll"/> <reference assembly="System.Windows.Forms.dll"/> <reference assembly="OutlookBarAssembly.dll"/> <![CDATA[ using System; using System.ComponentModel; using System.Diagnostics; using System.Drawing; using System.Windows.Forms; using OutlookBar; class MyForm { private OutlookBar.OutlookBar outlookBar; // controls in the tab page do not exist yet when the code-behind is // instantiated! public MyForm() {} public void OnLoad(object sender, EventArgs e) { outlookBar=(OutlookBar.OutlookBar)sender; IconPanel iconPanel1=new IconPanel(); IconPanel iconPanel2=new IconPanel(); IconPanel iconPanel3=new IconPanel(); outlookBar.AddBand("Outlook Shortcuts", iconPanel1); outlookBar.AddBand("My Shortcuts", iconPanel2); outlookBar.AddBand("Other Shortcuts", iconPanel3); iconPanel1.AddIcon("Outlook Today", Image.FromFile("img1.ico"), new EventHandler(PanelEvent)); iconPanel1.AddIcon("Calendar", Image.FromFile("img2.ico"), new EventHandler(PanelEvent)); iconPanel1.AddIcon("Contacts", Image.FromFile("img3.ico"), new EventHandler(PanelEvent)); iconPanel1.AddIcon("Tasks", Image.FromFile("img4.ico"), new EventHandler(PanelEvent)); outlookBar.SelectBand(0); } public void PanelEvent(object sender, EventArgs e) { Control ctrl=(Control)sender; PanelIcon panelIcon=ctrl.Tag as PanelIcon; MessageBox.Show("#"+panelIcon.Index.ToString(), "Panel Event"); } } ]]> </def:Code> <Controls> <ka:OutlookBar Location='10, 10' Size='150, 210' Visible='true' BorderStyle='FixedSingle' Load='MyForm.OnLoad'/> </Controls> </TabPage> Defining ReferencesAny inline and code-behind code must declare the assemblies that it is referencing for the JIT compiler to correctly generate the assembly. Wiring Events To Event HandlersThe generator allows you to define as many classes as you like in the code-behind. It manages the instance of each class, and since there can only be one instance per class (at least that it knows of), you can specify the instance that will handle events by prefixing the class name to the method name, in the format <className>.<methodName>, as illustrated above in "Load='MyForm.OnLoad'. If you leave off the class name, the event will instead be wired to the event context passed in to the generator when the form is created. Notice that the source language is also defined in the XML, allowing you to mix different languages for your code-behind source! Code-Behind CodeIf you would rather work with a .cs or .vb file and take advantage of syntax checking and automated help, you can specify the code behind as residing in a file rather than inline. This is demonstrated in the second tab page: <TabPage Name='TabPage2' FormBase='TabPage' Text='ListBox'> <def:CodeBehind Src="InitDataSource.cs"> <reference assembly="System.Windows.Forms.dll"/> </def:CodeBehind> <Controls> <ListBox Name='States' Location='10, 10' Size='130, 210' DataSource='ListBoxDS.DataSource' DisplayMember='LongName' ValueMember='ShortName' SelectedValueChanged='ListBoxDS.ShowValue'/> <Label Location='150, 10' Size='60, 15' Visible='true'>By event:</Label> <TextBox Name='ByEvent' Location='150, 25' Size='75, 20' Visible='true' ReadOnly='true'/> <Label Location='150, 55' Size='60, 15' Visible='true'> By binding:</Label> <TextBox Location='150, 70' Size='75, 20' Visible='true' ReadOnly='true'> <DataBindings>Text, ListBoxDS.DataSource, ShortName</DataBindings> </TextBox> </Controls> </TabPage> This also illustrates DataBinding. Read on. DataBindingControls that support the <DataBindings>Text, ListBoxDS.DataSource, ShortName</DataBindings> You specify the field to bind to, the data source, and the property name in data source that will return the data. Note that the data source must be obtained from a property from a context instance--either the code-behind or the instance supplied to the generator. Inspecting the C# code that comprises the code behind, the public class ListBoxDS { // the DataSource property can handle any IList derived object private ArrayList dataSource; // must be a property! (not a field!!!) public ArrayList DataSource { get {return dataSource;} } public ListBoxDS() { dataSource=new ArrayList(); dataSource.Add(new DataItem("AL", "Alabama")); dataSource.Add(new DataItem("WA", "Washington")); dataSource.Add(new DataItem("WV", "West Virginia")); dataSource.Add(new DataItem("CT", "Connecticut")); dataSource.Add(new DataItem("CA", "California")); dataSource.Add(new DataItem("RI", "Rhode Island")); dataSource.Add(new DataItem("NY", "New York")); } public void ShowValue(object sender, EventArgs e) { ListBox lb=(ListBox)sender; TabPage tp=(TabPage)lb.Parent; TextBox tb=(TextBox)FormHelper.FindControl(tp, "ByEvent"); tb.Text=lb.SelectedValue.ToString(); } } The DataItem class is defined as: public class DataItem { private string shortName; private string longName; public DataItem(string shortName, string longName) { this.shortName=shortName; this.longName=longName; } public string ShortName { get {return shortName;} } public string LongName { get {return longName;} } public override string ToString() { return LongName+" ("+ShortName+")"; } } Inline DataSourceAlternatively, you may have a short list that you simply wish to hardcode members for, rather than creating programmatically. This is accomplished by specifying the <MyCodeBehindClass Name='TabPage4' Text='Inherit' Load='MyCodeBehindClass.OnLoad'> <def:CodeBehind Src="MyCodeBehind.cs"> <reference assembly="System.Windows.Forms.dll"/> </def:CodeBehind> <Controls> <Label Location='10, 10' Size='150, 15' Visible='true'>Test</Label> <Label Location='10, 42' Size='50, 15' Visible='true'>Colors</Label> <ComboBox Name='Colors' Location='60, 40' Size='100, 100' DisplayMember='text' ValueMember='value'> <DataSource> <item value='1' text='Red'/> <item value='2' text='Green'/> <item value='3' text='Blue'/> </DataSource> </ComboBox> </Controls> </MyCodeBehindClass> Note that the items must be specified to have "value" and "text" properties. Architecture And UsageThe XmlGuiGenerator is a very flexible thing (in fact, not limited to forms/controls at all!), and I thought I'd put together some usage scenarios diagrams. Hopefully they'll give you a flavor for the richness of this tool. A Simple Example
In this scenario, there is no code behind and the XML doesn't specify any events itself. Here, the application must wire up any event handlers programmatically, just as is done in regular development. Auto Event Wiring
In this example, the generator manages the wiring of events automatically. The event is specified in the XML along with the method name in a class instance specified by the application. Of course, nothing prevents the application from programmatically wiring up other events. Code Behind Event Handlers
In this example, the application does not provide an event context. It must wire up events programmatically. Also, since there is code-behind defined in the XML, the events can be handled by the code-behind instead of the application. Code Behind And Application Context Events
In this example, both the application and the code-behind event handlers are wired by events specified in the XML, and both participate in handling the events. Child Controls
This is an example of how child controls (or collections in general) are instantiated, and how events can be handled by both the application and code-behind context for both the main form and the child controls. This diagram assumes that all children are specified inline. Multiple Canvases
This example is a bit more complicated. Here, we have child controls that are specified with separate canvases, allowing the child to have unique code-behind for managing events. Inheriting From Code-Behind
Finally, instead of specifying the form base as a class in the DebuggingDebugging XML generated GUI's is not easy. There are a couple projects in the works to make your life easier. One is a Form2Xml converter, that parses an existing Visual Studio Designer developed form. Another is a generic form layout tool (http://www.divil.co.uk/net/articles/designers/hosting.asp). Anyone willing to work on those or if you have better ideas, please contact me. CreditsCredits go to CPian Leppie for his many suggestions, CPian tditiecher for finding a bug, Kent Roylance for his patience in working with the CVS repository of this effort (contact me if you are interested in contributing, BTW), creating a Form2Xml utility and pointing me to a generic form designer, jconwell for his excellent article on Dot Net Script, and Roy Davis for his many excellent suggestions. Other LinksIt has been pointed out that this is a lot like XAML that is planned in the release of Longhorn. The funny thing is, I didn't even now about XAML when I started this project. Oh well. Ignorance is bliss. For further reading on the XAML reference: http://longhorn.msdn.microsoft.com/lhsdk/port_ref_elements.aspx. LicensingUse of this GUI generator must comply with the terms of the GNU General Public License. ConclusionConsider that this kind of a generator is not just restricted to windows form controls. It can be easily applied to web form controls, and in fact generalized to construct and initialize any kind of object! Using reflection, it would be possible to provide setters and getters of control values into specific class instance members as well. There's a lot you can do with this.
| ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||