Subscribe

RSS Feed (xml)

Use a Drag-and-Drop Operation

A drag-and-drop operation allows the user to transfer information from one place to another by clicking an item and "dragging" it to another location. A drag-and-drop operation consists of the following three basic steps:

  1. The user clicks a control, holds the mouse button down, and begins dragging. If the control supports the drag-and-drop feature, it sets aside some information.

  2. The user drags the mouse over another control. If this control accepts the dragged type of content, the mouse cursor changes to the special drag-and-drop icon (arrow and page). Otherwise, the mouse cursor becomes a circle with a line drawn through it.

  3. When the user releases the mouse button, the data is sent to the control, which can then process it appropriately.

To support drag and drop, you must handle the DragEnter, DragDrop, and (typically) the MouseDown events. This example uses two text boxes. Here's the code needed to attach the event handlers we'll use:

this.TextBox2.MouseDown += new MouseEventHandler(this.TextBox_MouseDown);
this.TextBox2.DragDrop += new DragEventHandler(this.TextBox_DragDrop);
this.TextBox2.DragEnter += new DragEventHandler(this.TextBox_DragEnter);

this.TextBox1.MouseDown += new MouseEventHandler(this.TextBox_MouseDown);
this.TextBox1.DragDrop += new DragEventHandler(this.TextBox_DragDrop);
this.TextBox1.DragEnter += new DragEventHandler(this.TextBox_DragEnter);

To start a drag-and-drop operation, you call the source control's DoDragDrop method. At this point you submit the data and specify the type of operations that will be supported (copying, moving, and so on). The following recipe example initiates a drag-and-drop operation when the user clicks a text box:

private void TextBox_MouseDown(object sender, 
  System.Windows.Forms.MouseEventArgs e) {

    TextBox txt = (TextBox)sender;
    txt.SelectAll();
    txt.DoDragDrop(txt.Text, DragDropEffects.Copy);
}

Controls that can receive dragged data must have the AllowDrop property set to true. These controls will receive a DragEnter event when the mouse drags the data over them. At this point, you can examine the data that's being dragged, decide whether the control can accept the drop, and set the DragEventArgs.Effect property accordingly, as shown here in this code:

private void TextBox_DragEnter(object sender, 
  System.Windows.Forms.DragEventArgs e) {

    if (e.Data.GetDataPresent(DataFormats.Text)) {
        e.Effect = DragDropEffects.Copy;
    }
    else {
        e.Effect = DragDropEffects.None;
    }
}

The final step is to respond to the DragDrop event, which occurs when the user releases the mouse button.

private void TextBox_DragDrop(object sender, 
  System.Windows.Forms.DragEventArgs e) {

    TextBox txt = (TextBox)sender;
    txt.Text = (string)e.Data.GetData(DataFormats.Text);
}

Using the code presented so far, you can create a simple drag-and-drop test application (see below figure) that allows text to be dragged from one text box to another. You can also drag text from another application and drop it into either text box.

textbox.JPG

Technorati :

Validate an Input Control

There are a number of ways that you can perform validation in a Windows- based application. One approach is to respond to control validation events and prevent users from changing focus from one control to another if an error exists. A less invasive approach is to simply flag the offending control in some way so that the user can review all the errors at once. You can use this approach in .NET with the ErrorProvider control.

The ErrorProvider is a special provider control that displays error icons next to invalid controls. You show the error icon next to a control by using the ErrorProvider.SetError method and specifying the appropriate control and a string error message. The ErrorProvider will then show a warning icon automatically to the right of the control. When the user hovers the mouse above the warning icon, the detailed message appears. Below figure shows how the ErrorProvider will indicate an input error for a TextBox control.

user-input.JPG

You only need to add one ErrorProvider control to your form, and you can use it to display an error icon next to any control. To add the ErrorProvider, drag it into the component tray or create it manually in code. The following form code checks the content of the text box every time a key is pressed. The code validates this text box using a regular expression with checks to see if the value corresponds to a valid e-mail address. If validation fails, the ErrorProvider is used to display an error message. If the text is valid, any existing error message is cleared from the ErrorProvider. Finally, the Click event handler for the OK button steps through all the controls on the form and verifies that none of them have errors before allowing the application to continue.

using System;
using System.Windows.Forms;
using System.Text.RegularExpressions;

public class ErrorProviderValidation : System.Windows.Forms.Form {

    // (Designer code omitted.)

    private void txtEmail_TextChanged(object sender, System.EventArgs e) {
    
        Regex regex;
        regex = new Regex(@"\S+@\S+\.\S+");

        Control ctrl = (Control)sender;
        if (regex.IsMatch(ctrl.Text)) {
            errProvider.SetError(ctrl, "");
        }
        else {
            errProvider.SetError(ctrl, "This is not a valid e-mail address.");
        }
    }

    private void cmdOK_Click(object sender, System.EventArgs e) {
    
        string errorText = "";
        bool invalidInput = false;

        foreach (Control ctrl in this.Controls) {
        
            if (errProvider.GetError(ctrl) != "")
            {
                errorText += "   * " + errProvider.GetError(ctrl) + "\n";
                invalidInput = true;
            }
        }
            
        if (invalidInput) {
        
            MessageBox.Show(
              "The form contains the following unresolved errors:\n\n" +
              errorText, "Invalid Input", MessageBoxButtons.OK,
              MessageBoxIcon.Warning);
        }
        else {
            this.Close();
        }
    }
}

Technorati :

Create an Animated System Tray Icon

The .NET Framework makes it easy to show a system tray icon with the NotifyIcon control. You simply need to add this control to a form, supply an icon by setting the Icon property, and, optionally, you can add a linked context menu through the ContextMenu property. The NotifyIcon control automatically displays its context menu when it's right-clicked, unlike most other controls.

You can animate a system tray icon by simply swapping the icon periodically. For example, the following program uses eight icons, each of which shows a moon graphic in a different stage of fullness. By moving from one image to another, the illusion of animation is created.

using System;
using System.Windows.Forms;
using System.Drawing;

public class AnimatedSystemTrayIcon : System.Windows.Forms.Form {

    // (Designer code omitted.)

    Icon[] images;
    int offset = 0;

    private void Form1_Load(object sender, System.EventArgs e) {
    
        // Load the basic set of eight icons.
        images = new Icon[8];
        images[0] = new Icon("moon01.ico");
        images[1] = new Icon("moon02.ico");
        images[2] = new Icon("moon03.ico");
        images[3] = new Icon("moon04.ico");
        images[4] = new Icon("moon05.ico");
        images[5] = new Icon("moon06.ico");
        images[6] = new Icon("moon07.ico");
        images[7] = new Icon("moon08.ico");
    }

    private void timer_Elapsed(object sender, 
      System.Timers.ElapsedEventArgs e) {
    
        // Change the icon.
        // This event handler fires once every second (1000 ms).
        notifyIcon.Icon = images[offset];
        offset++;
        if (offset > 7) offset = 0;
    }
}

Technorati :

Make a Borderless Form Movable

Borderless forms omit a title bar, which makes it impossible for a user to move them. You can compensate for this shortcoming by adding a control to the form that serves the same purpose. For example, below figure shows a form that includes a label to support dragging. The user can click this label, and then drag the form to a new location on the screen while holding down the mouse button. As the user moves the mouse, the form is automatically moved correspondingly, as though it's "attached" to the mouse pointer.

movable.JPG

To implement this solution, you need take the following steps:

  1. Create a form-level Boolean variable that tracks whether or not the form is currently being dragged.

  2. When the label is clicked, the code sets the flag to indicate that the form is in drag mode. At the same time, the current mouse position is recorded. You add this logic to the event handler for the Label.MouseDown event.

  3. When the user moves the mouse over the label, the form is moved correspondingly so that the position of the mouse over the label is unchanged. You add this logic to the event handler for the Label.MouseMove event.

  4. When the user releases the mouse button, the dragging mode is switched off. You add this logic to the event handler for the Label.MouseUp event.

Here's the complete form code:

using System;
using System.Windows.Forms;
using System.Drawing;

public class DragForm : System.Windows.Forms.Form {

    // (Designer code omitted.)

    // Tracks whether the form is in drag mode. If it is, mouse movements
    // over the label will be translated into form movements.
    private bool dragging;

    // Stores the offset where the label is clicked.
    private Point pointClicked;

    private void lblDrag_MouseDown(object sender,
      System.Windows.Forms.MouseEventArgs e) {
    
        if (e.Button == MouseButtons.Left) {
        
            dragging = true;
            pointClicked = new Point(e.X, e.Y);
        }
        else {
        
            dragging = false;
        }
    }

    private void lblDrag_MouseMove(object sender,
      System.Windows.Forms.MouseEventArgs e) {
    
        if (dragging) {
        
            Point pointMoveTo;

            // Find the current mouse position in screen coordinates.
            pointMoveTo = this.PointToScreen(new Point(e.X, e.Y));

            // Compensate for the position the control was clicked.
            pointMoveTo.Offset(-pointClicked.X, -pointClicked.Y);

            // Move the form.
            this.Location = pointMoveTo;
        }   
    }

    private void lblDrag_MouseUp(object sender,
      System.Windows.Forms.MouseEventArgs e) {
    
        dragging = false;
    }

    private void cmdClose_Click(object sender, System.EventArgs e) {
    
        this.Close();
    }
}

Technorati :

Immovable Forms Creation

You can create a borderless form by setting the FormBorderStyle property to None. Borderless forms can't be moved. However, they also lack any kind of border-if you want the customary blue border, you'll need to add it yourself either with manual drawing code or by using a background image.

There's one other approach to creating an immovable form that provides a basic control-style border. First, set the ControlBox, MinimizeBox, and MaximizeBox properties of the form to false. Then set the Text property to an empty string. The form will have a raised gray border or black line (depending on the FormBorderStyle option you use), similar to a button. Below figure shows both types of immovable forms.

immovable.JPG

Technorati :

Making Multilingual Form in C#

The .NET Framework includes built-in support for localization through its use of resource files. The basic idea is to store information that's locale-specific (for example, button text) in a resource file. You can then create multiple resource files for multiple different cultures and compile them into satellite assemblies. When you run the application, .NET will automatically use the correct satellite assembly based on the locale settings of the current computer.

You can read to and write from resource files manually. However, Visual Studio .NET also includes extensive design-time support for localized forms. It works like this:

  1. First set the Localizable property of the form to true using the Properties window.

  2. Set the Language property of the form to the locale for which you would like to enter information. (See Figure1) Then configure the localizable properties of all the controls on the form. Instead of storing your changes in the designer-generated code for the form, Visual Studio .NET will actually create a new resource file to hold your data.

  3. Repeat step 2 for each language that you want to support. Each time, a new resource file will be generated. If you change the Language property to a locale you have already configured, your previous settings will reappear, and you'll be able to modify them.

    figure1.JPG
You can now compile and test your application on differently localized systems. Visual Studio .NET will create a separate directory and satellite assembly for each resource file in the project. You can select Project | Show All Files from the Visual Studio .NET menu to see how these files are arranged, as shown in Figure2.
figure2.JPG

As a testing shortcut, you can also force your application to adopt a specific culture by modifying the Thread.CurrentUICulture property of the application thread. However, you must modify this property before the form has loaded.

using System;
using System.Windows.Forms;
using System.Threading;
using System.Globalization;

public class MultiLingualForm : System.Windows.Forms.Form {

private System.Windows.Forms.Label label1;

// (Designer code omitted.)

static void Main() {

Thread.CurrentThread.CurrentUICulture = new CultureInfo("fr");
Application.Run(new MultiLingualForm());
}
}

Using Part of a Main Menu for a Context Menu

In many applications, a control's context-sensitive menu duplicates a portion of the main menu. However, .NET does not allow you to create a MenuItem instance that's contained in more than one menu at a time.

The solution is to make a duplicate copy of a portion of the menu using the CloneMenu method. The CloneMenu method not only copies the appropriate MenuItem items (and any contained submenus), it also registers each MenuItem object with the same event handlers. Thus, when a user clicks a cloned menu item in a context menu, the same event handler will be triggered as if the user clicked the duplicate menu item in the main menu.

For example, consider the test application shown in the following Figure. In this example, the context menu for the text box shows the same entries that are found in the File menu. Technically, these are duplicate copies of the appropriate MenuItem objects. However, if the user clicks on one of these entries, the same event handler is executed.

combo-box.JPG

Here's the form code you need to create this example. It duplicates the entries in the main menu when the form first loads. (Unfortunately, it's not possible to perform the same operation at design time.)

using System;
using System.Windows.Forms;
using System.Drawing;

public class ContextMenuCopy : System.Windows.Forms.Form {

    // (Designer code omitted.)

    private void ContextMenuCopy_Load(object sender, System.EventArgs e) {
    
        ContextMenu mnuContext = new ContextMenu();

        // Copy the menu items from the File menu into a context menu.
        foreach (MenuItem mnuItem in mnuFile.MenuItems) {

            mnuContext.MenuItems.Add(mnuItem.CloneMenu());
        }

        // Attach the context menu to the text box.
        TextBox1.ContextMenu = mnuContext;
    }

    private void TextBox1_MouseDown(object sender,
      System.Windows.Forms.MouseEventArgs e) {
   
        if (e.Button == MouseButtons.Right){

            TextBox1.ContextMenu.Show(TextBox1, new Point(e.X, e.Y));
        }
    }

    private void mnuOpen_Click(object sender, System.EventArgs e) {
    
        MessageBox.Show("This is the event handler for Open.");
    }

    private void mnuSave_Click(object sender, System.EventArgs e) {
    
        MessageBox.Show("This is the event handler for Save.");
    }

    private void mnuClick_Click(object sender, System.EventArgs e) {
    
        MessageBox.Show("This is the event handler for Exit.");
    }
}

Technorati :

How To Link a Context Menu to a Control

You can link a control to a context menu by settings the control's ContextMenu property. However, this is only a convenience-to display the context menu you must retrieve the menu and call its Show method, supplying both a parent control and a pair of coordinates. Usually, you implement this logic in an event handler for the MouseDown event.

The good news is that the logic for showing a context menu is completely generic, no matter what the control is. Every control supports the ContextMenu property (which is inherited from the base Control class), which means you can easily write a generic event handler that will display context menus for all controls.

For example, consider a form with a label, a picture box, and a text box. You can write a single event handler that responds to the MouseDown event for all these objects. Here's how the designer code would look if you connected all these events to an event handler named Control_MouseDown:

this.label1.MouseDown += new MouseEventHandler(this.Control_MouseDown);
this.pictureBox1.MouseDown += new MouseEventHandler(this.Control_MouseDown);
this.textBox1.MouseDown += new MouseEventHandler(this.Control_MouseDown);

The event-handling code is completely generic. It just casts the sender to a Control, checks for a linked context menu, and displays it.

private void Control_MouseDown(object sender,
  System.Windows.Forms.MouseEventArgs e) {

    if (e.Button == MouseButtons.Right) {
    
        // Get the source control.
        Control ctrl = (Control)sender;

        if (ctrl.ContextMenu != null) {
        
            // Show the linked menu over the control.
            ctrl.ContextMenu.Show(ctrl, new Point(e.X, e.Y));
        }
    }
}

Technorati :

How to Sort a List View by Any Column

The ListView control provides a Sort method that orders items alphabetically based on the text in the first column. If you want to sort based on other column values or order items in any other way, you need to create a custom implementation of the IComparer interface that can perform the work.

The IComparer interface defines a single method named Compare, which takes two objects and determines which one should be ordered first. Here is a custom ListViewItemComparer class that implements IComparer. It provides two additional properties: Column and Numeric. Column indicates the column that should be used for sorting, and Numeric is a Boolean flag that can be set to true if you want to perform number-based comparisons instead of alphabetic comparisons.

using System;
using System.Collections;
using System.Windows.Forms;

public class ListViewItemComparer : IComparer {

    private int column;
    private bool numeric = false;

    public int Column {
    
        get {return column;}
        set {column = value;}
    }
        
    public bool Numeric {
    
        get {return numeric;}
        set {numeric = value;}
    }
        
    public ListViewItemComparer(int columnIndex) {
    
        Column = columnIndex;
    }

    public int Compare(object x, object y) {
    
        ListViewItem listX = (ListViewItem)x;
        ListViewItem listY = (ListViewItem)y;

        if (Numeric) {
        
            // Convert column text to numbers before comparing.
            // If the conversion fails, just use the value 0.
            decimal listXVal, listYVal;
            try {
                listXVal = Decimal.Parse(listX.SubItems[Column].Text);
            }
            catch {
                listXVal = 0;
            }

            try {
                 listYVal = Decimal.Parse(listY.SubItems[Column].Text);
            }
            catch {
                 listYVal = 0;
            }

            return Decimal.Compare(listXVal, listYVal);
        }
        else {
        
            // Keep the column text in its native string format
            // and perform an alphabetic comparison.
            string listXText = listX.SubItems[Column].Text;
            string listYText = listY.SubItems[Column].Text;

            return String.Compare(listXText, listYText);
        }
    }
}

Now to sort the list view, you simply need to create a ListViewItemComparer instance, configure it appropriately, and then set it in the ListView.ListViewItemSorter property before you call the ListView.Sort method.

The following form demonstrates a simple test of the ListViewItemComparer. Every time the user clicks a column header in the list view, a new ListViewItemComparer is created and used to sort the list based on the clicked column.

using System;
using System.Windows.Forms;

public class ListViewItemSort : System.Windows.Forms.Form {

    // (Designer code omitted.)

    private void ListView1_ColumnClick(object sender, 
      System.Windows.Forms.ColumnClickEventArgs e) {
    
        ListViewItemComparer sorter = new ListViewItemComparer(e.Column);
        ListView1.ListViewItemSorter = sorter;
        ListView1.Sort();
    }
}

Technorati :

How to Use an Autocomplete Combo Box

There are many different variations to the autocomplete control. Sometimes, the control fills in values based on a list of recent selections (as Microsoft Excel does when entering cell values) or might display a drop-down list of near matches (as Microsoft Internet Explorer does when typing a URL). You can create a basic autocomplete combo box by handling the KeyPress and TextChanged events, or by creating a custom class that derives from ComboBox and overrides the OnKeyPress and OnTextChanged methods. This recipe takes the latter approach.

In the OnKeyPress method, the combo box determines whether or not an autocomplete replacement should be made. If the user pressed a character key (such as a letter) the replacement can be made, but if the user has pressed a control key (such as the backspace key, the cursor keys, and so on), no action should be taken. The OnTextChanged method performs the actual replacement after the key processing is complete. This method looks up the first match for the current text in the list of items, and then adds the rest of the matching text. After the text is added, the combo box selects the characters between the current insertion point and the end of the text. This allows the user to continue typing and replace the autocomplete text if it isn't what the user wants.

Here's the full code for the AutoCompleteComboBox class:

using System;
using System.Windows.Forms;

public class AutoCompleteComboBox : ComboBox {

    // Track if a special key is pressed
    // (in which case the text replacement operation will be skipped).
    private bool controlKey = false;

    // Determine whether a special key was pressed.
    protected override void OnKeyPress(
      System.Windows.Forms.KeyPressEventArgs e) {

        base.OnKeyPress(e);

        if (e.KeyChar == (int)Keys.Escape) {
        
            // Clear the text.
            this.SelectedIndex = -1;
            this.Text = "";
            controlKey = true;
        }
        else if (Char.IsControl(e.KeyChar)) {
        
            controlKey = true;
        }
        else {
        
            controlKey = false;
        }
    }

    // Perform the text substitution.
    protected override void OnTextChanged(System.EventArgs e) {
    
        base.OnTextChanged(e);

        if (this.Text != "" && !controlKey) {
        
            // Search for a matching entry.
            string matchText = this.Text;
            int match = this.FindString(matchText);

            // If a matching entry is found, insert it now.
            if (match != -1) {
            
                this.SelectedIndex = match;

                // Select the added text so it can be replaced
                // if the user keeps typing.
                this.SelectionStart = matchText.Length;
                this.SelectionLength = this.Text.Length - this.SelectionStart;
            }
        }
    }
}

To test the AutoCompleteComboBox, you can create a simple Windows client that adds the combo box to a form and fills it with a list of words. In this example, the list of words is retrieved from a text file and the control is added manually. You could also compile the AutoCompleteComboBox class to a separate class library assembly and then add it to the Toolbox, so you could add it to a form at design time.

using System;
using System.Windows.Forms;
using System.Drawing;
using System.IO;

public class AutoCompleteComboBoxTest : System.Windows.Forms.Form {

    // (Designer code omitted.)

    private void AutoCompleteComboBox_Load(object sender, 
      System.EventArgs e) {
    
        // Add the combo box to the form.
        AutoCompleteComboBox combo = new AutoCompleteComboBox();
        combo.Location = new Point(10,10);
        this.Controls.Add(combo);

        // Add the word list.
        FileStream fs = new FileStream("words.txt", FileMode.Open);
        using (StreamReader r = new StreamReader(fs)) {

            while (r.Peek() > -1) {
        
                string word = r.ReadLine();
                combo.Items.Add(word);
            }
        }
    }
}

Technorati :

Restrict a Text Box to Numeric Input

The best way to correct invalid input is to prevent it from being entered in the first place. This approach is easy to implement with the text box because it provides a KeyPress event that occurs after the keystroke has been received but before it's displayed. You can use the KeyPressEventArgs event parameter to effectively "cancel" an invalid keystroke by setting the Handled property to true.

To allow only numeric input, you must allow a keystroke only if it corresponds to a number (0 through 9) or a special control key (such as delete or the arrow keys). The keystroke character is provided to the KeyPress event through the KeyPressEventArgs.KeyChar property. You can use two static methods of the System.Char class-IsDigit and IsControl-to quickly test the character.

Here's the event handler you would use to prevent non-numeric input:

private void textBox1_KeyPress(object sender, 
  System.Windows.Forms.KeyPressEventArgs e) {

    if (!Char.IsDigit(e.KeyChar) && !Char.IsControl(e.KeyChar)) {
        e.Handled = true;
    }
}

Notice that this code rejects the decimal separator. If you need to allow this character (for example, to permit the user to enter a fractional currency amount), you'll have to modify the code slightly, as shown here:

// Get the decimal separator character on this platform
// (typically a "." for US-English).
string decimalString =
  Thread.CurrentThread.CurrentCulture.NumberFormat.CurrencyDecimalSeparator;
char decimalChar = Convert.ToChar(decimalString);

if (Char.IsDigit(e.KeyChar) || Char.IsControl(e.KeyChar)) {}
else if (e.KeyChar == '.' && textBox1.Text.IndexOf(".") == -1) {}
else {
    e.Handled = true;
}

This code allows only a single decimal point, but it makes no restriction about how many significant digits can be used. It also doesn't allow the entry of negative numbers. (You could change this behavior by allowing the minus sign (-) provided it's the first character.) Remember that this code also assumes you have imported the System.Threading namespace.

Technorati :

Force a List Box to Scroll

In some cases, you might have a list box that stores a significant amount of information or one that you add information to periodically. It's often the case that the most recent information, which is added at the end of the list, is more important than the information at the top of the list. One solution is to scroll the list box so that recently added items are visible.

The following example form includes a list box and a button that adds 20 items to the list and then scrolls to the last full page using the TopIndex property.

using System;
using System.Windows.Forms;

public class ListBoxScrollTest : System.Windows.Forms.Form {

    // (Designer code omitted.)

    int counter = 0;

    private void cmdTest_Click(object sender, System.EventArgs e) {
    
        for (int i = 0; i < 20; i++) {
        
            counter++;
            listBox1.Items.Add("Item " + counter.ToString());
        }
        listBox1.TopIndex = listBox1.Items.Count - 1;
    }
}

Technorati :

Save the Size and Location of a Form

The Windows registry is an ideal place for storing position and size information for a form. Typically, you'll store information about each form in a separate key, perhaps using the class name of the form. These keys will be stored under an application-specific key.

To automate this process, it helps to create a dedicated class that saves and retrieves form settings. The FormSettingStore class shown in the following example fills this role. This class provides a SaveSettings method that accepts a form and writes its size and position information to the registry, as well as an ApplySettings method that accepts a form and applies the settings from the registry. The registry key path and the name of the form subkey are stored as class member variables.

using System;
using System.Windows.Forms;
using Microsoft.Win32;

public class FormSettingStore {

    private string regPath;
    private string formName;
    private RegistryKey key;

    public string RegistryPath {
        get {return regPath;)
    }

    public string FormName {
        get {return formName;}
    }

    public FormSettingStore(string registryPath, string formName) {
    
        this.regPath = registryPath;
        this.formName = formName;

        // Create the key if it doesn't exist.
        key = Registry.LocalMachine.CreateSubKey(registryPath + formName);
    }

    public void SaveSettings(System.Windows.Forms.Form form) {
    
        key.SetValue("Height", form.Height);
        key.SetValue("Width", form.Width);
        key.SetValue("Left", form.Left);
        key.SetValue("Top", form.Top);
    }

    public void ApplySettings(System.Windows.Forms.Form form) {
    
        // If form settings are not available, the current form settings
        // are used instead.
        form.Height = (int)key.GetValue("Height", form.Height);
        form.Width = (int)key.GetValue("Width", form.Width);
        form.Left = (int)key.GetValue("Left", form.Left);
        form.Top = (int)key.GetValue("Top", form.Top);
    }
}

To use the FormSettingStore class, simply add the event-handling code shown here to any form. This code saves the form properties when the form closes and restores them when the form is loaded.

private FormSettingStore formSettings;

private void Form1_Load(object sender, System.EventArgs e) {

    formSettings = new FormSettingStore(@"Software\MyApp\", this.Name);
    formSettings.ApplySettings(this);
}

private void Form1_Closed(object sender, System.EventArgs e) {

    formSettings.SaveSettings(this);
}

Technorati :

Find All MDI Child Forms

The .NET Framework includes two convenient shortcuts for managing MDI applications: the MdiChildren and the MdiParent properties of the Form class. You can investigate the MdiParent property of any MDI child to find the containing parent window. You can use the MdiChildren collection of the parent to find all the MDI child windows.

For example, consider the following example, which displays simple child windows. Each child window includes a label with some date information, and a button. When the button is clicked, the event handler walks through all the child windows and displays the label text that each one contains. Each window also exposes the label text through a read- only property.

Here's the form code for the child window:

public class MDIChild : System.Windows.Forms.Form {

    private System.Windows.Forms.Button cmdShowAllWindows;
    private System.Windows.Forms.Label label;

    // (Designer code omitted.)

    public string LabelText {
    
        get {
            return label.Text;
        }
    }

    private void cmdShowAllWindows_Click(object sender, System.EventArgs e) {

        // Walk through the collection of child windows.
        foreach (Form frm in this.MdiParent.MdiChildren) {
        
            // Cast the generic Form to the expended derived class type.
            MDIChild child = (MDIChild)frm;
            MessageBox.Show(child.LabelText, frm.Text);
        }
    }

    private void MDIChild_Load(object sender, System.EventArgs e){

        label.Text = DateTime.Now.ToString();
    }
}

Check that when the code walks through the collection of child forms, it must convert the generic Form reference to the custom derived MDIChild form class so that it can use the LabelText property.

Track the Visible Forms in an Application

NET does not provide any way of determining what forms are currently being displayed in an application. If you want to determine what forms are in existence, or what forms are displayed, or you want one form to call the methods or set the properties of another form, you'll need to keep track of form instances on your own.

One useful approach is to create a class consisting of static members. This class can track open forms using a collection, or dedicated properties. Here's an example of a class that can track two forms:

public class OpenForms {

    public static Form MainForm;
    public static Form SecondaryForm;
}

Now when either the main or the secondary form is shown, it can register itself with the OpenForms class. A logical place to put this code is in the Form.Load event handler.

private void MainForm_Load(object sender, EventArgs e) {

    // Register the newly created form instance.
    OpenForms.MainForm = this;
}

You can use similar code to remove the reference when the form is closed.

private void MainForm_Unload(object sender, EventArgs e) {

    // Remove the form instance.
    OpenForms.MainForm = null;
}

Now another form can interact with this form through the OpenForms class. For example, here's how the main form would hide the second form:

if (OpenForms.SecondaryForm != null) {
    OpenForms.SecondaryForm.Hide();
}

In this approach, we assume that every form is created only once. If you have a document-based application where the user can create multiple instances of the same form, you should track these forms using a collection. Here's an example with an ArrayList collection:

public class OpenForms {

    public static Form MainForm;
    public static ArrayList DocForms = new ArrayList();
}

Forms can then add themselves to the document collection as needed, as shown in the following code:

private void DocForm_Load(object sender, EventArgs e) {

    // Register the newly created form instance.
    OpenForms.DocForms.Add(this);
}

Process All the Controls on a Form

You can iterate through the controls on a form using the Form.Controls collection, which includes all the controls that are placed directly on the form surface. However, if any of these controls are container controls (such as GroupBox, Panel, or TabPage), they might contain more controls. Thus, it's necessary to use recursive logic that searches the Controls collection of every control on the form.

The following example shows a form that performs this recursive logic to find every text box on a form and clears the text they contain. The form tests each control to determine whether it's a text box by using the typeof operator.

using System;
using System.Windows.Forms;

public class ProcessAllControls : System.Windows.Forms.Form {

    // (Designer code omitted.)

    private void cmdProcessAll_Click(object sender, System.EventArgs e) {
        ProcessControls(this);
    }

    private void ProcessControls(Control ctrl) {
    
        // Ignore the control unless it's a text box.
        if (ctrl.GetType() == typeof(TextBox)) {
            ctrl.Text = "";
        }
        
        // Process controls recursively. 
        // This is required if controls contain other controls
        // (for example, if you use panels, group boxes, or other
        // container controls).
        foreach (Control ctrlChild in ctrl.Controls) {
            ProcessControls(ctrlChild);
        }
    }
}

Link Data to a Control in C#

Every class that derives from System.Windows.Forms.Control provides a Tag property that you can use to store a reference to any type of object. The Tag property isn't used by the control or the Microsoft .NET Framework. Instead, it's reserved as a convenient storage place for application-specific information. In addition, some other non-Control-derived classes provide a Tag property. Examples include the ListViewItem and TreeNode classes (which represent items in a ListView or a TreeView control). One class that does not provide a Tag property is MenuItem.

The Tag property is defined as a generic Object type, which means that you can use it to store any value type or reference type, from a simple number or string to a custom object you have defined. When retrieving data from the Tag property, you'll need to cast the object to its original type.

The following example adds a list of files to a list view. The corresponding FileInfo object for each file is stored in the Tag property. When a user double- clicks one of the list items, the code retrieves the FileInfo object from the Tag property and displays the file size in a MessageBox.

using System;
using System.Windows.Forms;
using System.IO;

public class TagPropertyExample : System.Windows.Forms.Form (

    // (Designer code omitted.)

    private void TagPropertyExample_Load(object sender, System.EventArgs e) {
    
        // Get all the files in the directory.
        DirectoryInfo directory = new DirectoryInfo("C:\\");
        FileInfo[] files = directory.GetFiles();

        // Display all the files in the ListView.
        foreach (FileInfo file in files) {
        
            ListViewItem item = listView.Items.Add(file.Name);
            item.ImageIndex = 0;
            item.Tag = file;
        }
    }

    private void listView_ItemActivate(object sender, System.EventArgs e) {
    
        // Get the file's size from the linked FileInfo.
        ListViewItem item = ((ListView)sender).SelectedItems[0];
        FileInfo file = (FileInfo)item.Tag;
        string info = file.FullName + " is " + file.Length + " bytes.";

        // Display the file's size.
        MessageBox.Show(info, "File Information");
    }
}

Add a Control Programmatically in C#

In a .NET Windows-based application, there's really no difference between creating a control at design time and creating it at runtime. When you create controls at design time (using a tool like Microsoft Visual Studio .NET), the necessary code is added to your form class, typically in a special method named InitializeComponent. You can use the same code in your application to create controls on the fly. Just follow these steps:

  1. Create an instance of the appropriate control class.

  2. Configure the control properties accordingly (particularly the size and position coordinates).

  3. Add the control to the form or another container.

  4. In addition, if you need to handle the events for the new control, you can wire them up to existing methods.

Every control provides a Controls property that references a ControlCollection that contains all of its child controls. To add a child control, you invoke the ControlCollection.Add method. The following example demonstrates this by dynamically creating a list of check boxes. One check box is added for each item in an array. All the check boxes are added to a panel that has its AutoScroll property set to true, which gives basic scrolling support to the check box list.

using System;
using System.Windows.Forms;

public class DynamicCheckBox : System.Windows.Forms.Form {

    // (Designer code omitted.)

    private void DynamicCheckBox_Load(object sender, System.EventArgs e) {
    
        // Create the array.
        string[] foods = {"Grain", "Bread", "Beans", "Eggs",
          "Chicken", "Milk", "Fruit", "Vegetables",
          "Pasta", "Rice", "Fish", "Beef"};

        int topPosition = 10;
        foreach (string food in foods)
        {
            // Create a new check box.
            CheckBox checkBox = new CheckBox();
            checkBox.Left = 10;
            checkBox.Top = topPosition;
            topPosition += 30;
            checkBox.Text = food;

            // Add the check box to the form.
            panel.Controls.Add(checkBox);
        }
    }
}

Perform an XSL Transform

XSLT (or XSL transforms) is an XML-based language designed to transform one XML document into another document. XSLT can be used to create a new XML document with the same data but arranged in a different structure or to select a subset of the data in a document. It can also be used to create a different type of structured document. XSLT is commonly used in this manner to format an XML document into an HTML page.

XSLT is a rich language, and creating XSL transforms is beyond the scope of this book. However, you can learn how to create simple XSLT documents by looking at a basic example. This recipe transforms the orders.xml document shown in the post into an HTML document with a table and then displays the results. To perform this transformation, you'll need the following XSLT stylesheet:

<?xml version="1.0" encoding="UTF-8" ?>
<xsl:stylesheet xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
    version="1.0" >

  <xsl:template match="Order">
    <html><body><p>
    Order <b><xsl:value-of select="Client/@id"/></b>
    for <xsl:value-of select="Client/Name"/></p>
    <table border="1">
    <td>ID</td><td>Name</td><td>Price</td>
    <xsl:apply-templates select="Items/Item"/>
    </table></body></html>
  </xsl:template>
 
  <xsl:template match="Items/Item">
    <tr>
    <td><xsl:value-of select="@id"/></td>
    <td><xsl:value-of select="Name"/></td>
    <td><xsl:value-of select="Price"/></td>
    </tr>
  </xsl:template>

</xsl:stylesheet>

Essentially, every XSL stylesheet consists of a set of templates. Each template matches some set of elements in the source document and then describes the contribution that the matched element will make to the resulting document.

The orders.xslt stylesheet contains two template elements (as children of the root stylesheet element). The first template matches the root Order element. When the XSLT processor finds an Order element, it outputs the tags necessary to start an HTML table with appropriate column headings and inserts some data about the client using the value-of command, which outputs the text result of an XPath expression. In this case, the XPath expressions (Client/@id and Client/Name) match the id attribute and the Name element.

Next the apply-templates command is used to branch off and perform processing of any contained Item elements. This is required because there might be multiple Item elements. Each Item element is matched using the XPath expression Items/Item. The root Order node isn't specified because Order is the current node. Finally, the initial template writes the tags necessary to end the HTML document.

If you execute this transform on the sample orders.xml file shown in this post, you'll end up with the following HTML document:

<html>
  <body>
    <p>
    Order <b>ROS-930252034</b>
    for Remarkable Office Supplies</p>
    <table border="1">
      <td>ID</td>
      <td>Name</td>
      <td>Price</td>
      <tr>
        <td>1001</td>
        <td>Electronic Protractor</td>
        <td>42.99</td>
      </tr>
      <tr>
        <td>1002</td>
        <td>Invisible Ink</td>
        <td>200.25</td>
      </tr>
    </table>
  </body>
</html>

To apply an XSLT stylesheet in .NET, you use the XslTransform class. The following code shows a Windows-based application that programmatically applies the transformation and then displays the transformed file in a Web browser window. In this example, the code uses the overloaded version of the Transform method that saves the result document directly to disk, although you could receive it as a stream and process it inside your application instead.

using System;
using System.Windows.Forms;
using System.Xml.Xsl;

public class TransformXml : System.Windows.Forms.Form {

    private AxSHDocVw.AxWebBrowser webBrowser;

     // (Designer code omitted.)

    private void TransformXml_Load(object sender, System.EventArgs e) {

        XslTransform transform = new XslTransform();
            
        // Load the XSL stylesheet.
        transform.Load("orders.xslt");
            
        // Transform orders.xml into orders.html using orders.xslt.
        transform.Transform("orders.xml", "orders.html", null);

        object var = null;
        webBrowser.Navigate(
          "file:///" + Application.StartupPath + @"\orders.html",
          ref var, ref var, ref var, ref var);
    }
}

Technorati :

Generate a Class from a Schema

Previous Post introduced the xsd.exe command-line utility, which can be used to generate schemas based on class definitions. The reverse operation-generating C# source code based on an XML schema document-is also possible. This is primarily useful if you want to write a certain format of XML document, but you don't want to manually create the document by writing individual nodes with the XmlDocument or XmlTextWriter classes. Instead, by using xsd.exe, you can generate a set of full .NET objects. You can then serialize these objects to the required XML representation using the XmlSerializer, as described in recipe 5.9.

To generate source code from a schema, you simply need to supply the filename of the schema document and add the /c parameter to indicate that you want to generate the required classes. For example, consider the schema shown in this post. You can generate C# code for this schema with the following command-line:

xsd ProductCatalog.xsd /c

This will generate one file (ProductCatalog.cs) with two classes: product and productCalalog.

Technorati :

Create a Schema for a .NET Class

Previous Post demonstrated how to use the XmlSerializer to serialize .NET objects to XML, and deserialize XML into .NET objects. But if you want to use the XML as a way to interact with other applications, business process, or non-Framework applications, you'll need an easy way to validate the XML before you attempt to deserialize it. You'll also need to define an XML schema document that defines the structure and data types used in your XML format, so that other applications can work with it. One quick solution is to generate an XML schema using the xsd.exe command-line utility.

The xsd.exe utility is included with the .NET Framework. If you've installed Microsoft Visual Studio .NET, you'll find it in a directory like C:\Program Files\Microsoft Visual Studio .NET\FrameworkSDK\Bin. The xsd.exe utility can generate schema documents from compiled assemblies. You simply need to supply the filename and indicate the class that represents the XML document with the /t:[TypeName] parameter.

For example, consider the ProductCatalog and Product classes shown in this post. You could create the XML schema for a product catalog with the following command line:

xsd Recipe5-09.exe /t:ProductCatalog

You need to specify only the ProductCatalog class on the command line because this class represents the actual XML document. The generated schema in this example will represent a complete product catalog, with contained product items. It will be given the default filename schema0.xsd. You can now use the XmlValidatingReader shown in this post to test whether the XML document can be successfully validated with the schema.

Technorati :

Use XML Serialization with Custom Objects

The XmlSerializer class allows you to convert objects to XML data, and vice versa. This process is used natively by Web services and provides a customizable serialization mechanism that won't require a single line of custom code. The XmlSerializer class is even intelligent enough to correctly create arrays when it finds nested elements.

The only requirements for using XmlSerializer are as follows:

  • The XmlSerializer only serializes properties and public variables.

  • The classes you want to serialize must include a default zero-argument constructor. The XmlSerializer uses this constructor when creating the new object during deserialization.

  • All class properties must be readable and writable. This is because XmlSerializer uses the property get accessor to retrieve information, and the property set accessor to restore the data after deserialization.

To use XML serialization, you must first mark up your data objects with attributes that indicate the desired XML mapping. These attributes are found in the System.Xml.Serialization namespace and include the following:

  • XmlRoot Specifies the name of the root element of the XML file. By default, XmlSerializer will use the name of the class. This attribute can be applied to the class declaration.

  • XmlElementIndicates the element name to use for a property or public variable. By default, XmlSerializer will use the name of the property or public variable.

  • XmlAttributeIndicates that a property or public variable should be serialized as an attribute, not an element, and specifies the attribute name.

  • XmlEnumConfigures the text that should be used when serializing enumerated values. If you don't use XmlEnum, the name of the enumerated constant will be used.

  • XmlIgnoreIndicates that a property or public variable should not be serialized.

Here's the class code that you might use:

using System;
using System.Xml.Serialization;

[XmlRoot("productCatalog")]
public class ProductCatalog {

    [XmlElement("catalogName")]
    public string CatalogName;
    
    // Use the date data type (and ignore the time portion in the 
    // serialized XML).
    [XmlElement(ElementName="expiryDate", DataType="date")]
    public DateTime ExpiryDate;
    
    // Configure the name of the tag that holds all products,
    // and the name of the product tag itself.
    [XmlArray("products")]
    [XmlArrayItem("product")]
    public Product[] Products;

    public ProductCatalog() {
        // Default constructor for deserialization.
    }

    public ProductCatalog(string catalogName, DateTime expiryDate) {
        this.CatalogName = catalogName;
        this.ExpiryDate = expiryDate;
    }
}

public class Product {

    [XmlElement("productName")]
    public string ProductName;
    
    [XmlElement("productPrice")]
    public decimal ProductPrice;
    
    [XmlElement("inStock")]
    public bool InStock;
    
    [XmlAttributeAttribute(AttributeName="id", DataType="integer")]
    public string Id;

    public Product() {
        // Default constructor for serialization.
    }

    public Product(string productName, decimal productPrice) {
        this.ProductName = productName;
        this.ProductPrice = productPrice;
    }
}

Notice that these classes use the XML serialization attributes to rename element names (using Pascal casing in the class member names, and camel casing in the XML tag names), indicate data types that aren't obvious, and specify how <product> elements will be nested in the <productCatalog>.

Using these custom classes and the XmlSerializer object, you can translate XML into objects and vice versa. Here's the code you would need to create a new ProductCatalog object, serialize the results to an XML document, deserialize the document back to an object, and then display the XML document.

using System;
using System.Xml;
using System.Xml.Serialization;
using System.IO;

public class SerializeXml {

    private static void Main() {

        // Create the product catalog.
        ProductCatalog catalog = new ProductCatalog("New Catalog",
          DateTime.Now.AddYears(1));
        Product[] products = new Product[2];
        products[0] = new Product("Product 1", 42.99m);
        products[1] = new Product("Product 2", 202.99m);
        catalog.Products = products;

        // Serialize the order to a file.
        XmlSerializer serializer = new XmlSerializer(typeof(ProductCatalog));
        FileStream fs = new FileStream("ProductCatalog.xml", FileMode.Create);
        serializer.Serialize(fs, catalog);
        fs.Close();

        catalog = null;

        // Deserialize the order from the file.
        fs = new FileStream("ProductCatalog.xml", FileMode.Open);
        catalog = (ProductCatalog)serializer.Deserialize(fs);

        // Serialize the order to the Console window.
        serializer.Serialize(Console.Out, catalog);
        Console.ReadLine();
    }
}

Technorati :

Validate an XML Document Against a Schema

An XML schema defines the rules that a given type of XML document must follow. The schema includes rules that define

  • the elements and attributes that can appear in a document.

  • the data types for elements and attributes.

  • the structure of a document, including what elements are children of other elements.

  • the order and number of child elements that appear in a document.

  • whether elements are empty, can include text, or require fixed values.

XML schema documents are beyond the scope of this chapter, but much can be learned from a simple example. This example uses the product catalog first presented in post1.

At its most basic level, XML Schema Definition (XSD) is used to define the elements that can occur in an XML document. XSD documents are themselves written in XML, and you use a separate predefined element (named <element>) in the XSD document to indicate each element that's required in the target document. The type attribute indicates the data type. Here's an example for a product name:

<xsd:element name="productName" type="xsd:string" />

And here's an example for the product price:

<xsd:element name="productPrice" type="xsd:decimal" />

The basic schema data types are defined at http://www.w3.org/TR/xmlschema-2. They map closely to .NET data types and include string, int, long, decimal, float, dateTime, boolean, and base64Binary, to name a few of the most frequently used types.

Both the productName and productPrice are simple types because they contain only character data. Elements that contain nested elements are called complex types. You can nest them together using a <sequence> tag, if order is important, or an <all> tag if it's not. Here's how you might model the <product> element in the product catalog. Notice that attributes are always declared after elements, and they aren't grouped with a <sequence> or <all> tag because order is never important.

<xsd:complexType name="product">
  <xsd:sequence>
    <xsd:element name="productName" type="xsd:string"/>
    <xsd:element name="productPrice" type="xsd:decimal"/>
    <xsd:element name="inStock" type="xsd:boolean"/>
  </xsd:sequence>
  <xsd:attribute name="id" type="xsd:integer"/>
</xsd:complexType>

By default, a listed element can occur exactly one time in a document. You can configure this behavior by specifying the maxOccurs and minOccurs attributes. Here's an example that allows an unlimited number of products in the catalog:

<xsd:element name="product" type="product" maxOccurs="unbounded" />

Here's the complete schema for the product catalog XML:

<?xml version="1.0"?>
<xsd:schema xmlns:xsd="http://www.w3.org/2001/XMLSchema">

   <!-- Define the complex type product. -->
   <xsd:complexType name="product">
      <xsd:sequence>
         <xsd:element name="productName" type="xsd:string"/>
         <xsd:element name="productPrice" type="xsd:decimal"/>
         <xsd:element name="inStock" type="xsd:boolean"/>
      </xsd:sequence>
      <xsd:attribute name="id" type="xsd:integer"/>
   </xsd:complexType>

   <!-- This is the structure the document must match.
        It begins with a productCatalog element that nests other elements. -->
   <xsd:element name="productCatalog">
      <xsd:complexType>
         <xsd:sequence>
            <xsd:element name="catalogName" type="xsd:string"/>
            <xsd:element name="expiryDate" type="xsd:date"/>

            <xsd:element name="products">
               <xsd:complexType>
                  <xsd:sequence>
                     <xsd:element name="product" type="product"
                      maxOccurs="unbounded" />
                  </xsd:sequence>
               </xsd:complexType>
            </xsd:element>
         </xsd:sequence>
      </xsd:complexType>
   </xsd:element>

</xsd:schema>

The XmlValidatingReader class enforces all these schema rules-ensuring the document is valid-and it also checks that the XML document is well formed (which means there are no illegal characters, all opening tags have a corresponding closing tag, and so on). To check a document, you read through it one node at a time by calling the XmlValidatingReader.Read method. If an error is found, XmlValidatingReader raises a ValidationEventHandler event with information about the error. If you wish, you can handle this event and continue processing the document to find more errors. If you don't handle this event, an XmlException will be raised when the first error is encountered and processing will be aborted. To test only if a document is well formed, you can use the XmlValidatingReader without a schema.

The next example shows a utility class that displays all errors in an XML document when the ValidateXml method is called. Errors are displayed in a console window, and a final Boolean variable is returned to indicate the success or failure of the entire validation operation.

using System;
using System.Xml;
using System.Xml.Schema;

public class ConsoleValidator {

    // Set to true if at least one error exists.
    private bool failed;

    public bool Failed {
        get {return failed;}
    }

    public bool ValidateXml(string xmlFilename, string schemaFilename) {

        // Create the validator.
        XmlTextReader r = new XmlTextReader(xmlFilename);
        XmlValidatingReader validator = new XmlValidatingReader(r);
        validator.ValidationType = ValidationType.Schema;

        // Load the schema file into the validator.
        XmlSchemaCollection schemas = new XmlSchemaCollection();
        schemas.Add(null, schemaFilename);
        validator.Schemas.Add(schemas);

        // Set the validation event handler.
        validator.ValidationEventHandler += 
          new ValidationEventHandler(ValidationEventHandler);
            
        failed = false;
        try {

            // Read all XML data.
            while (validator.Read())
            {}
        }catch (XmlException err) {

            // This happens if the XML document includes illegal characters
            // or tags that aren't properly nested or closed.
            Console.WriteLine("A critical XML error has occurred.");
            Console.WriteLine(err.Message);
            failed = true;
        }finally {
            validator.Close();
        }

        return !failed;
    }

    private void ValidationEventHandler(object sender, 
      ValidationEventArgs args) {

        failed = true;

        // Display the validation error.
        Console.WriteLine("Validation error: " + args.Message);
        Console.WriteLine();
    }
}

Here's how you would use the class to validate the product catalog:

using System;

public class ValidateXml {

    private static void Main() {

        ConsoleValidator consoleValidator = new ConsoleValidator();
        Console.WriteLine("Validating ProductCatalog.xml.");

        bool success = consoleValidator.ValidateXml("ProductCatalog.xml",
          "ProductCatalog.xsd");
        if (!success) {
            Console.WriteLine("Validation failed.");
        }else {
            Console.WriteLine("Validation succeeded.");
        }

        Console.ReadLine();
    }
}

If the document is valid, no messages will appear, and the success variable will be set to true. But consider what happens if you use a document that breaks schema rules, such as the ProductCatalog_Invalid.xml file shown here:

<?xml version="1.0" ?>
<productCatalog>
    <catalogName>Acme Fall 2003 Catalog</catalogName>
    <expiryDate>Jan 1, 2004</expiryDate>

    <products>
        <product id="1001">
            <productName>Magic Ring</productName>
            <productPrice>$342.10</productPrice>
            <inStock>true</inStock>
        </product>
        <product id="1002">
            <productName>Flying Carpet</productName>
            <productPrice>982.99</productPrice>
            <inStock>Yes</inStock>
        </product>
    </products>
</productCatalog>

If you attempt to validate this document, the success variable will be set to false and the output will indicate each error:

Validating ProductCatalog_Invalid.xml.

Validation error: The 'expiryDate' element has an invalid value according to
 its data type. [path information truncated] 

Validation error: The 'productPrice' element has an invalid value according to
 its data type. [path information truncated]

Validation error: The 'inStock' element has an invalid value according to its
 data type. [path information truncated]

Validation failed.

Finally, if you want to validate an XML document and then process it, you can use XmlValidatingReader to scan a document as it's read into an in-memory XmlDocument. Here's how it works:

XmlDocument doc = new XmlDocument();
XmlTextReader r = new XmlTextReader("ProductCatalog.xml");
XmlValidatingReader validator = new XmlValidatingReader(r);

// Load the schema into the validator.
validator.ValidationType = ValidationType.Schema;
XmlSchemaCollection schemas = new XmlSchemaCollection();
schemas.Add(null, "ProductCatalog.xsd");
validator.Schemas.Add(schemas);

// Load the document and validate it at the same time.
// Don't handle the ValidationEventHandler event. Instead, allow any errors
/// to be thrown as an XmlSchemaException.
try {
    doc.Load(validator);
    // (Validation succeeded if you reach here.)
}catch (XmlSchemaException err) {
    // (Validation failed if you reach here.)
}

Technorati :

Read and Write XML Without Loading an Entire Document into Memory

The XmlTextWriter and XmlTextReader classes read or write XML directly from a stream one node at a time. These classes don't provide the same features for navigating and manipulating your XML as XmlDocument, but they do provide higher performance and a smaller memory footprint, particularly if you need to deal with extremely large XML documents.

To write XML to any stream, you can use the streamlined XmlTextWriter. It provides Write methods that write one node at a time. These include

  • WriteStartDocument, which writes the document prologue, and WriteEndDocument, which closes any open elements at the end of the document.

  • WriteStartElement, which writes an opening tag for the element you specify. You can then add more elements nested inside this element, or you can call WriteEndElement to write the closing tag.

  • WriteElementString, which writes an entire element, with an opening tag, a closing tag, and text content.

  • WriteAttributeString, which writes an entire attribute for the nearest open element, with a name and value.

Using these methods usually requires less code than creating an XmlDocument by hand, as demonstrated in posts1 and post2

To read the XML, you use the Read method of the XmlTextReader. This method advances the reader to the next node, and returns true. If no more nodes can be found, it returns false. You can retrieve information about the current node through XmlTextReader properties, including its Name, Value, and NodeType.

To find out if an element has attributes, you must explicitly test the HasAttributes property and then use the GetAttribute method to retrieve the attributes by name or index number. The XmlTextReader class can access only one node at a time, and it can't move backward or jump to an arbitrary node, which gives much less flexibility than the XmlDocument class.

The following console application writes and reads a simple XML document using the XmlTextWriter and XmlTextReader classes. This is the same XML document created in posts1 and post2 using the XmlDocument class.

using System;
using System.Xml;
using System.IO;
using System.Text;

public class ReadWriteXml {

    private static void Main() {

        // Create the file and writer.
        FileStream fs = new FileStream("products.xml", FileMode.Create);
        XmlTextWriter w = new XmlTextWriter(fs, Encoding.UTF8);

        // Start the document.
        w.WriteStartDocument();
        w.WriteStartElement("products");

        // Write a product.
        w.WriteStartElement("product");
        w.WriteAttributeString("id", "1001");
        w.WriteElementString("productName", "Gourmet Coffee");
        w.WriteElementString("productPrice", "0.99");
        w.WriteEndElement();

        // Write another product.
        w.WriteStartElement("product");
        w.WriteAttributeString("id", "1002");
        w.WriteElementString("productName", "Blue China Tea Pot");
        w.WriteElementString("productPrice", "102.99");
        w.WriteEndElement();

        // End the document.
        w.WriteEndElement();
        w.WriteEndDocument();
        w.Flush();
        fs.Close();

        Console.WriteLine("Document created. " +
         "Press Enter to read the document.");
        Console.ReadLine();

        fs = new FileStream("products.xml", FileMode.Open);
        XmlTextReader r = new XmlTextReader(fs);

        // Read all nodes.
        while (r.Read()) {
 
           if (r.NodeType == XmlNodeType.Element) {

                Console.WriteLine();
                Console.WriteLine("<" + r.Name + ">");

                if (r.HasAttributes) {

                    for (int i = 0; i < r.AttributeCount; i++) {
                        Console.WriteLine("\tATTRIBUTE: " +
                          r.GetAttribute(i));
                    }
                }
            }else if (r.NodeType == XmlNodeType.Text) {
                Console.WriteLine("\tVALUE: " + r.Value);
            }
        }
        Console.ReadLine();
    }
}

Technorati :

Find Elements with an XPath Search

The XmlNode class defines two methods that perform XPath searches: SelectNodes and SelectSingleNode. These methods operate on all contained child nodes. Because the XmlDocument inherits from XmlNode, you can call XmlDocument.SelectNodes to search an entire document.

For example, consider the following XML document, which represents an order for two items. This document includes text and numeric data, nested elements, and attributes, and so is a good way to test simple XPath expressions.

<?xml version="1.0"?>
<Order id="2004-01-30.195496">
  <Client id="ROS-930252034">
    <Name>Remarkable Office Supplies</Name>
  </Client>

  <Items>
    <Item id="1001">
      <Name>Electronic Protractor</Name>
      <Price>42.99</Price>
    </Item>
    <Item id="1002">
      <Name>Invisible Ink</Name>
      <Price>200.25</Price>
    </Item>
  </Items>
</Order>

Basic XPath syntax uses a path-like notation. For example, the path /Order/Items/Item indicates an <Item> element that is nested inside an <Items> element, which, in turn, in nested in a root <Order> element. This is an absolute path. The following example uses an XPath absolute path to find the name of every item in an order.

using System;
using System.Xml;

public class XPathSelectNodes {

    private static void Main() {

        // Load the document.
        XmlDocument doc = new XmlDocument();
        doc.Load("orders.xml");

        // Retrieve the name of every item.
        // This could not be accomplished as easily with the
        // GetElementsByTagName() method, because Name elements are
        // used in Item elements and Client elements, and so
        // both types would be returned.
        XmlNodeList nodes = doc.SelectNodes("/Order/Items/Item/Name");
            
        foreach (XmlNode node in nodes) {
            Console.WriteLine(node.InnerText);
        }
     
        Console.ReadLine();
    }
}

The output of this program is as follows:

Electronic Protractor
Invisible Ink

XPath provides a rich and powerful search syntax, and it's impossible to explain all the variations you can use in a short example. However, Table 5.1 outlines some of the key ingredients in more advanced XPath expressions and includes examples that show how they would work with the order document. For a more detailed reference, refer to the W3C XPath recommendation at http://www.w3.org/TR/xpath.

Table 5.1: XPath Expression Syntax

Expression

Description

/

Starts an absolute path that selects from the root node.

/Order/Items/Item selects all Item elements that are children of anItems element, which is itself a child of the root Order element.

//

Starts a relative path that selects nodes anywhere.

//Item/Name selects all the Name elements that are children of an Item element, regardless of where they appear in the document.

@

Selects an attribute of a node.

/Order/@id selects the attribute named id from the root Order element.

*

Selects any element in the path.

/Order/* selects both Items and Client nodes because both are contained by a root Order element.

|

Combines multiple paths.

/Order/Items/Item/Name|Order/Client/Name selects the Name nodes used to describe a Client and the Name nodes used to describe an Item.

.

Indicates the current (default) node.

If the current node is an Order, the expression ./Items refers to the related items for that order.

..

Indicates the parent node.

//Name/.. selects any element that is parent to a Name, which includes the Client and Item elements.

[ ]

Define selection criteria that can test a contained node or attribute value.

/Order[@id="2004-01-30.195496"] selects the Order elements with the indicated attribute value.

/Order/Items/Item[Price > 50] selects products above $50 in price.

/Order/Items/Item[Price > 50 and Name="Laser Printer"] selects products that match two criteria.

starts-with

This function retrieves elements based on what text a contained element starts with.

/Order/Items/Item[starts-with(Name, "C")] finds all Item elements that have a Name element that starts with the letter C.

position

This function retrieves elements based on position.

/Order/Items/Item[position ()=2] selects the second Item element.

count

This function counts elements. You specify the name of the child element to count or an asterisk (*) for all children.

/Order/Items/Item[count(Price) = 1] retrieves Item elements that have exactly one nested Price element.

Note

XPath expressions and all element and attribute names that you use inside them are always case sensitive, as XML itself is case sensitive.

Technorati :

Get XML Nodes in a Specific XML Namespace

Many XML documents contain nodes from more than one namespace. For example, an XML document that represents a scientific article might use a separate type of markup for denoting math equations and vector diagrams, or an XML document with information about a purchase order might aggregate client and order information with a shipping record. Similarly, an XML document that represents a business-to-business transaction might include portions from both companies, written in separate markup languages.

A common task in XML programming is to retrieve the elements that are found in a specific namespace. You can perform this task with the overloaded version of the XmlDocument.GetElementsByTagName method that requires a namespace name. You can use this method to find tags by name, or to find all the tags in the specified namespace if you supply an asterisk for the tag name parameter.

As an example, consider the following compound XML document that includes order and client information, in two different namespaces (http://mycompany/OrderML and http://mycompany/ClientML).

<?xml version="1.0" ?>
<ord:order xmlns:ord="http://mycompany/OrderML"
 xmlns:cli="http://mycompany/ClientML">

  <cli:client>
    <cli:firstName>Sally</cli:firstName>
    <cli:lastName>Sergeyeva</cli:lastName>
  </cli:client>

  <ord:orderItem itemNumber="3211"/>
  <ord:orderItem itemNumber="1155"/>

</ord:order>

Here's a simple console application that selects all the tags in the http://mycompany/OrderML namespace:

using System;
using System.Xml;

public class SelectNodesByNamespace {

    private static void Main() {

        // Load the document.
        XmlDocument doc = new XmlDocument();
        doc.Load("Order.xml");

        // Retrieve all order tags.
        XmlNodeList matches = doc.GetElementsByTagName("*",
          "http://mycompany/OrderML");

        // Display all the information.
        Console.WriteLine("Element \tAttributes");
        Console.WriteLine("******* \t**********");

        foreach (XmlNode node in matches) {

            Console.Write(node.Name + "\t");
            foreach (XmlAttribute attribute in node.Attributes) {
                Console.Write(attribute.Value + "  ");
            }
            Console.WriteLine();
        }
 
        Console.ReadLine();
    }
}

The output of this program is as follows:

Element         Attributes
*******         **********
ord:order       http://mycompany/OrderML  http://mycompany/ClientML
ord:orderItem   3211
ord:orderItem   1155

Technorati :

Find Specific Elements by Name

The XmlDocument class provides a convenient GetElementsByTagName method that searches an entire document for nodes that have the indicated element name. It returns the results as a collection of XmlNode objects.

This code demonstrates how you could use GetElementsByTagName to calculate the total price of items in a catalog by retrieving all elements with the name "productPrice":

using System;
using System.Xml;

public class FindNodesByName {

    private static void Main() {

        // Load the document.
        XmlDocument doc = new XmlDocument();
        doc.Load("ProductCatalog.xml");

        // Retrieve all prices.
        XmlNodeList prices = doc.GetElementsByTagName("productPrice");

        decimal totalPrice = 0;
        foreach (XmlNode price in prices) {

            // Get the inner text of each matching element.
            totalPrice += Decimal.Parse(price.ChildNodes[0].Value);
        }

        Console.WriteLine("Total catalog value: " + totalPrice.ToString());
        Console.ReadLine();
    }
}

You can also search portions of an XML document by using the XmlElement.GetElementsByTagName method. It searches all the descendant nodes looking for matches. To use this method, first retrieve an XmlNode that corresponds to an element. Then cast this object to an XmlElement. The following example demonstrates how to find the price node under the first product element.

// Retrieve a reference to the first product.
XmlNode product = doc.GetElementsByTagName("products")[0];

// Find the price under this product.
XmlNode price = ((XmlElement)product).GetElementsByTagName("productPrice")[0];
Console.WriteLine("Price is " + price.InnerText);

If your elements include an attribute of type ID, you can also use a method called GetElementById to retrieve an element that has a matching ID value.

Technorati :

Quickly Append Nodes in an XML Document

Inserting a single element into an XmlDocument requires several lines of code. There are several ways that you can shorten this code. One approach is to create a dedicated helper class with higher-level methods for adding elements and attributes. For example, you could create an AddElement method that generates a new element, inserts it, and adds any contained text-the three operations needed to insert most elements.

Here's an example of one such helper class:

using System;
using System.Xml;

public class XmlHelper {

    public static XmlNode AddElement(string tagName, 
      string textContent, XmlNode parent) {

        XmlNode node = parent.OwnerDocument.CreateElement(tagName);
        parent.AppendChild(node);

        if (textContent != null) {

            XmlNode content;
            content = parent.OwnerDocument.CreateTextNode(textContent);
            node.AppendChild(content);
        }
        return node;
    }

    public static XmlNode AddAttribute(string attributeName,
      string textContent, XmlNode parent) {

        XmlAttribute attribute;
        attribute = parent.OwnerDocument.CreateAttribute(attributeName);
        attribute.Value = textContent;
        parent.Attributes.Append(attribute);

        return attribute;
    }
}

You can now condense the XML-generating code from previous post with the simpler syntax shown here:

public class GenerateXml {

    private static void Main() {

        // Create the basic document.
        XmlDocument doc = new XmlDocument();
        XmlNode docNode = doc.CreateXmlDeclaration("1.0", "UTF-8", null);
        doc.AppendChild(docNode);
        XmlNode products = doc.CreateElement("products");
        doc.AppendChild(products);

        // Add two products.
        XmlNode product = XmlHelper.AddElement("product", null, products);
        XmlHelper.AddAttribute("id", "1001", product);
        XmlHelper.AddElement("productName", "Gourmet Coffee", product);
        XmlHelper.AddElement("productPrice", "0.99", product);

        product = XmlHelper.AddElement("product", null, products);
        XmlHelper.AddAttribute("id", "1002", product);
        XmlHelper.AddElement("productName", "Blue China Tea Pot", product);
        XmlHelper.AddElement("productPrice", "102.99", product);

        // Save the document (to the Console window rather than a file).
        doc.Save(Console.Out);
        Console.ReadLine();
    }
}

Alternatively, you might want to take the helper methods such as AddAttribute and AddElement and make them instance methods in a custom class you derive from XmlDocument.

Another approach to simplifying writing XML is to duplicate nodes using the XmlNode.CloneNode method. CloneNode accepts a Boolean deep parameter. If you supply true, CloneNode will duplicate the entire branch, with all nested nodes.

Here's an example that creates a new product node by copying the first node.

// (Add first product node.)

// Create a new element based on an existing product.
product = product.CloneNode(true);

// Modify the node data.
product.Attributes[0].Value = "1002";
product.ChildNodes[0].ChildNodes[0].Value = "Blue China Tea Pot";
product.ChildNodes[1].ChildNodes[0].Value = "102.99";

// Add the new element.
products.AppendChild(product);

Notice that in this case, certain assumptions are being made about the existing nodes (for example, that the first child in the item node is always the name, and the second child is always the price). If this assumption isn't guaranteed to be true, you might need to examine the node name programmatically.

Technorati :

Archives

LocalsAdda.com-Variety In Web World

Fun Mail - Fun in the Mail