J P Johansson On Applied .NET

May 25, 2015

ASP.NET MVC: Proper model binding with dynamic form

Filed under: ASP.NET MVC,C#,JQuery — Patric Johansson @ 10:04 PM
Tags: , ,

ASP.NET MVC automatically maps values entered in a form by a user to properties of a ViewModel object. This is done by matching property names to field names and is commonly known as model binding. Correctly done it will aid in decoupling your controller from a view.

We will use an employee database as our domain. The goal is to allow us to create employees with a name and phone numbers. Let’s start with an EmployeeViewModel class with a Name and a PhoneNumber property.

public class EmployeeViewModel 
{     
    public string Name { get; set; }     
    public string PhoneNumber { get; set; } 
}

Next we need a form defined in view Create.cshtml with appropriately named fields to automatically create an EmployeeViewModel object.

@model EmployeeDemo.Models.EmployeeViewModel

<form action="/Employees/Create" method="post">     
    <p>         
        Name         
        <input type="text" name="Name" />     
    </p>     
    <p>         
        Phone Number         
        <input type="text" name="PhoneNumber" />     
    </p>     
    <p>         
         <input type="submit" name="btnSubmit" value="Create new employee" />     
    </p> 
</form>

Notice that the names of the HTML fields matches the names of the viewmodel properties. In the EmployeesController we would have a Create post method which takes the viewmodel as argument.

// POST: Employees/Create
[HttpPost] 
public ActionResult Create(EmployeeViewModel employeeViewModel) 
{
    if (!ModelState.IsValid)     
    {         
        return View("Create", employeeViewModel);     
    }

    // Save the created employee object

    return RedirectToAction("Index"); 
}

Model binding can also handle nested objects. Let’s introduce a Phone class to demonstrate.

public class EmployeeViewModel 
{
    public string Name { get; set; }
    public Phone PrimaryPhone { get; set; } 
} 

public class Phone 
{
    public enum Types 
    { 
        Business,
        Cell, 
        Home
    }

    public Types Type { get; set; } 
    public string Number { get; set; } 
}

We need to modify our form to allow users to input phone type. We will use a dropdown list (select field) to enter phone type. Notice the names of the phone fields specifying how they are nested.

@model EmployeeDemo.Models.EmployeeViewModel

<form action="/Employees/Create" method="post">
<p>
    Name
    <input type="text" name="Name" />
</p>
<p>
    Phone Type
    <select name="PrimaryPhone.Type"><option value="Business">Business</option><option value="Cell">Cell</option><option value="Home">Home</option></select>
    Phone Number
    <input type="text" name="PrimaryPhone.Number" />
</p>
<p>
    <input type="submit" name="btnSubmit" value="Create new employee" />
</p>
</form>

No changes will need to be done to our controller. The controller is not tightly coupled to the view.

A straightforward requirement for an Employee class would be multiple phone numbers. Let us start with allowing users to enter two phones for an employee. Phone class is defined as above. We add a collection property of Phone objects to EmployeeViewModel class.

public class EmployeeViewModel 
{ 
    public string Name { get; set; } 
    public List<Phone> Phones { get { return _phones; } }
    private List<Phone> _phones = new List<Phone>(); 
}

Our form needs now two sets of fields for defining phone numbers.

@model EmployeeDemo.Models.EmployeeViewModel

<form action="/Employees/Create" method="post">
<p>
    Name
    <input type="text" name="Name" />
</p>
<p>
    Phone Type
    <select name="Phones[0].Type"><option value="Business">Business</option><option value="Cell">Cell</option><option value="Home">Home</option></select>
    Phone Number
    <input type="text" name="Phones[0].Number" />
</p>
<p>
    Phone Type
    <select name="Phones[1].Type"><option value="Business">Business</option><option value="Cell">Cell</option><option value="Home">Home</option></select>
    Phone Number
    <input type="text" name="Phones[1].Number" />
</p>
<p>
    <input type="submit" name="btnSubmit" value="Create new employee" />
</p>
</form>

The model binding will create a List with two phones. It is important that names have consecutive indexes or model binding will not work properly. Allowing only a fixed number of phones in our form is not a practical solution, we need to allow user to add any number of phones, meaning zero or more. For this we will use jQuery to allow client-side logic for adding and removing fields in our form depending on number of phones for a given employee.

We will need two jQuery functions, one for adding rows and one for removing rows. We will add links for user to add and remove rows. A row will consists of fields for entering one phone with a phone type and number.

Let’s see an image of our form in action. In the image we can see that the user wants to create an employee with three phones.
EmployeeForm

The add function will append a new row to bottom of the list of phones. It will get index to use for field names by counting existing rows.

The remove function will remove the row with the clicked Remove link as well as rename all remaining field names using consecutive indexes. As mentioned before, names of fields need to have consecutive indexes.

The view for the create action uses HtmlHelper methods for HTML generation. The div with id phoneList is the list of phones. It contains zero or more divs of class phoneRow which contains the dropdown list and input field.

@using EmployeeDemo.Models;
@model EmployeeDemo.Models.EmployeeViewModel

<form action="/Employees/Create" method="post">
    <p>
        @Html.LabelFor(model => model.Name)
        @Html.TextBoxFor(model => model.Name)
    </p>

    <div id="phoneList">
        @if (Model.Phones.Count > 0)
        {
            <span>Phone Type Phone Number</span>
        }

        @for (int i = 0; i < Model.Phones.Count; i++)
        {
            <div class="phoneRow">
                @Html.DropDownList("Phones[" + i + "].Type", Model.Phones[i].PhoneTypeSelectListItems, new { @class = "phoneType" })
                @Html.TextBoxFor(model => Model.Phones[i].Number, new { @class = "phoneNumber" })
                <a href="javascript:void(0);" class="remRow">Remove</a>
            </div>
        }
    </div>
    
    <p>
        <a href="javascript:void(0);" class="addRow">Add row</a>
    </p>

    <p>
        <input type="submit" name="btnSubmit" value="Create new employee" />
    </p>

</form>

PhoneTypeSelectListItems is a new property on our Phone class which returns the enum for phone types as a list of SelectListItems. We also add some data validations attributes to the properties of the class.

public class Phone
{
    public enum Types
    {
        Business,
        Cell,
        Home
    }

    [Required]
    public Types Type { get; set; }

    [Required]
    [Phone]
    public string Number { get; set; }

    public IEnumerable<SelectListItem> PhoneTypeSelectListItems
    {
        get
        {
            foreach(Types type in Enum.GetValues(typeof(Types)))
            {
                SelectListItem selectListItem = new SelectListItem();
                selectListItem.Text = type.ToString();
                selectListItem.Value = type.ToString();
                selectListItem.Selected = Type == type;
                yield return selectListItem;
            }
        }
    }
}

When the Create view is first opened, Model.Phones.Count is zero. Model.Phones.Count will only be non-zero after user hit submit with an invalid model state, for example empty name or invalid phone number. Let’s take a look at our jQuery functions for adding and removing rows in the form.

<script type="text/javascript" src="../../Scripts/jquery-1.10.2.min.js"></script>
<script type="text/javascript">

$(document).ready(function () {

    $(".addRow").click(function () {

        var rowCount = $('.phoneRow').length;
        $("#phoneList").append('<div class="phoneRow"><select name="Phones[' + rowCount + '].Type" class="phoneType">@foreach (SelectListItem item in new Phone().PhoneTypeSelectListItems){<option value="@item.Value">@item.Text</option>}</select><input type="text" name="Phones[' + rowCount + '].Number" class="phoneNumber" /><a href="javascript:void(0);" class="remRow">Remove</a></div>');
    });

    $("#phoneList").on('click', '.remRow', function () {

        $(this).closest('.phoneRow').remove();

        $('.phoneType').each(function (index) {
            $(this).attr('name', 'Phones[' + index + '].Type');
        });

        $('.phoneNumber').each(function (index) {
            $(this).attr('name', 'Phones[' + index + '].Number');
        });
    })
});

</script>

The addRow function appends some HTML for a new phoneRow div to the phoneList div. This HTML is the same as generated by our HtmlHelper methods used in the form. The new fields will have names with an index equal to existing number of phoneRow divs so we have consecutively named fields. The remRow function removes the phoneRow div it belongs to. Next it iterates over all fields and renames them so we get consecutively named fields. It finds them using class names phoneType and phoneNumber.

Usually when working with dynamic forms and lists, a FormCollection object is used to get field values in the controller method. The Create method in the EmployeesController would look something like this:

// POST: Employees/Create 
[HttpPost] 
public ActionResult Create(FormCollection formData) 
{        
    string name = formData["Name"];
    string phones0Type = formData["Phones[0].Type"];
    string phones1Number = formData["Phones[0].Number"];
    . . .

The drawback is of course that we now need to construct our EmployeeViewModel from a string collection and cast it to correct type. Any changes to EmployeeViewModel or form will break the controller method, like changing Name property to first and last name properties. Instead, with just some minor work in the form making sure that all fields have proper names and relying on ASP.NET MVC model binding, we get a fully constructed object and a controller which is not tightly coupled to its view.

On the web there are many examples how to do a dynamic list in a form, but all I seen rely on using an HTML table instead of nested divs. Tables are for tabular data and not for layout. A list of fields is not tabular data so we use nested divs instead.

CSS styling of any kind is intentionally left out, in order to focus on model binding using dynamic forms.

The attached Visual Studio 2013 C# solution contains the above code with some minor additions. Validation messages are displayed. The EmployeeViewModel class has an additional ID property. Beside the Create view we also have an Edit view for editing employees. The form is identical to the Create view, but added for to give demonstrate how edit is done with model binding. An Index view is the Home view for viewing employees and contains links to create and edit employees. There’s no true model for storing employees and no checks that a requested employee exists, employees are stored in a list and we assume employee ID is the index for that employee object in the list.

Download full VS2013 project

July 13, 2013

Simple JQuery expander

Filed under: JQuery — Patric Johansson @ 2:23 PM
Tags:

Image
Figure: simple expander using jQuery

<html>
  <head>

  <script src="Scripts/jquery-1.7.1.js"></script>
  <script>
    $(document).ready(function () {
    $(".showHide").wrapInner("<div class=showHideContent></div>");
    $(".showHideContent").hide().before("<button class=showHideButton>Show</button>");
    $(".showHideButton").click(function (event) {
      if ($(event.target).next().is(":visible")) {
      $(event.target).next().hide();
      $(event.target).html('Show');
      }
      else {
      $(event.target).next().show();
      $(event.target).html('Hide');
      }
    });
    });
  </script>

  <style media="screen" type="text/css">
    .showHide
    {
      border: 2px solid black;
    }
  
    .showHideButton
    {
      width:100%;
      border-style: none;
    }
  </style>

  </head>
  <body>
  
  <div class="showHide">
	  <ul> 
		  <li>A</li>
		  <li>B</li>
		  <li>C</li>
	  </ul>
  </div>

  


  <div class="showHide">
    Some text	
  </div>

  


  <button>Random button</button> 

  </body>
</html>

The jQuery script adds a Show/Hide button to any html element of class showHide. User can click on button to expand or collapse the html element. I also added some simple CSS styling to show how it can be done.

The jQuery script wraps the content of any elements of class showHide with a div with class showHideContent. Next it inserts a button labeled Show before the inserted div. It also hides the inserted div.

We hook up a click event to the button. We get access to the button through the event.target property. Using $(event.target).next() we get access to the inserted div which is wrapped around the content. Remember the button was inserted before the div, so using the next() function on the button we get access to the div. Next we hide or show the div depending on if it is visible or not. We also update the label of the button.

Below is the result of the script for the list element in runtime.

<div class=showHide>
  <button class=showHideButton>Hide</button>
  <div class=showHideContent>
  <ul>
  <li>A</li> 
  <li>B</li> 
  <li>C</li>
  </ul>
  </div>
</div>

The above code assumes you put the JScript file for jQuery (jquery-1.7.1.js) in a folder called Scripts.

April 3, 2012

Download themes on demand in a Silverlight app

Filed under: C#,Silverlight — Patric Johansson @ 8:57 PM

A common requirement for Silverlight applications is to support multiple themes. However if the themes are part of the application they will increase the application’s size and the time to download it. The solution is of course to download the themes on demand and not at startup.

For themes we use the themes from the Silverlight toolkit. This sample project uses Visual Studio 10, .NET 4.0, Silverlight 5 and Silverlight toolkit. The result is a Silverlight application which allows users to select one of several themes which will then be downloaded from the web server.

Figure 1: The Theme demo application.

Create a new Silverlight application and website to host it in using the Silverlight Application template in Visual Studio. I named the new solution ThemeDemo.

We start working on the web server. It will have a web service to query for available themes as well as a repository of themes to download. First lets add the themes. Right click on the ClientBin folder and select Add existing item… from popup menu. Next add all theme dlls from the SIlverlight toolkit, mine where in the following folder:

%ProgramFiles%\Microsoft SDKs\Silverlight\v5.0\Toolkit\dec11\Themes

Change the Copy to output directory property for all theme dlls to Copy if newer.

Next we add the web service. Add a new item and select the Web Service template. Call the new service ThemeWebService. The web service should return a list of available themes so lets implement one that does:

[WebMethod]
public string[] GetThemes()
{
    string path = Path.GetDirectoryName(Assembly.GetExecutingAssembly().CodeBase);
    path += @"\ClientBin";
    path = path.Replace(@"file:\", "");

    string[] themeFiles = Directory.GetFiles(path, "System.Windows.Controls.Theming.*.dll");
    return themeFiles.Select(f => Path.GetFileName(f).Replace(".dll", "")).ToArray();
}

That is it for the web server.

Figure 2: Web project.

Let’s move on to the client. Add a reference to System.Windows.Controls.Theming.Toolkit dll, mine are located here:

%ProgramFiles%\Microsoft SDKs\Silverlight\v5.0\Toolkit\dec11\Bin\System.Windows.Controls.Theming.Toolkit.dll

Also add a service reference to our web service. I named the service reference ThemeServiceReference.

Add some random controls to the MainPage. One should be a combobox for themes. The Toolkit comes with a Theme control which allows us to bind a Uri for a theme. Your code for  mainpage should look something like this:

<UserControl x:Class="ThemeDemo.MainPage"
    xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
    xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
    xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
    xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
    xmlns:toolkit="http://schemas.microsoft.com/winfx/2006/xaml/presentation/toolkit"
    mc:Ignorable="d"
    d:DesignHeight="300" d:DesignWidth="300">

    <toolkit:Theme ThemeUri="{Binding Path=ThemeLoader.SelectedThemeUri}">
        <Grid x:Name="LayoutRoot">
            <Grid.RowDefinitions>
                <RowDefinition Height="Auto" />
                <RowDefinition Height="Auto" />
                <RowDefinition Height="Auto" />
                <RowDefinition Height="Auto" />
            </Grid.RowDefinitions>
            <Grid.ColumnDefinitions>
                <ColumnDefinition Width="Auto" />
            </Grid.ColumnDefinitions>

            <Slider Grid.Row="0" />

            <CheckBox Grid.Row="1" Content="Check me" />

            <Button Grid.Row="2" Content="Click me" />

            <StackPanel Grid.Row="3" Orientation="Horizontal">
                <TextBlock Text="Themes: " />
                <ComboBox
                    ItemsSource="{Binding Path=ThemeLoader.ThemeNames}"
                    SelectedItem="{Binding Path=ThemeLoader.SelectedThemeName}"
                    SelectionChanged="OnChangeTheme"
                    Width="200" />
            </StackPanel>

        </Grid>
    </toolkit:Theme>

</UserControl>
[public partial class MainPage : UserControl
{
    public MainPage()
    {
        InitializeComponent();

        ThemeLoader = new ThemeLoader("ShinyBlue");

        DataContext = this;
    }

    public ThemeLoader ThemeLoader { get; private set; }

    private void OnChangeTheme(object sender, SelectionChangedEventArgs e)
    {
        string theme = (sender as ComboBox).SelectedItem as string;
        ThemeLoader.SelectTheme(theme);
    }
}

Figure 3: Application project.

What is this ThemeLoader class? It is a utility class which contains necessary client side code to download themes.

public class ThemeLoader : INotifyPropertyChanged
{
    public ThemeLoader()
    {
        _proxy.GetThemesCompleted += OnGetThemesCompleted;
        _proxy.GetThemesAsync();
    }

    public ThemeLoader(string defaultThemeName)
        : this()
    {
        _defaultThemeName = defaultThemeName;
    }

    public IEnumerable ThemeNames
    {
        get { return _themes.Keys; }
    }

    private Uri _selectedThemeUri;
    public Uri SelectedThemeUri
    {
        get { return _selectedThemeUri; }
        private set
        {
            _selectedThemeUri = value;
            if (PropertyChanged != null)
            {
                PropertyChanged(this, new PropertyChangedEventArgs("SelectedThemeUri"));
            }
        }
    }

    private string _selectedThemeName;
    public string SelectedThemeName
    {
        get { return _selectedThemeName; }
        private set
        {
            _selectedThemeName = value;
            if (PropertyChanged != null)
            {
                PropertyChanged(this, new PropertyChangedEventArgs("SelectedThemeName"));
            }
        }
    }

    #region INotifyPropertyChanged Members
    public event PropertyChangedEventHandler PropertyChanged;
    #endregion INotifyPropertyChanged Members

    public void SelectTheme(string themeName)
    {
        if (!_themes.ContainsKey(themeName))
        {
            return;
        }

        string theme = _themes[themeName];

        Assembly[] loadedAssemblies = AppDomain.CurrentDomain.GetAssemblies();
        if (loadedAssemblies.Any(a => GetName(a) == theme))
        {
            SelectedThemeUri = new Uri(string.Format("{0};component/Theme.xaml", theme), UriKind.RelativeOrAbsolute);
            SelectedThemeName = themeName;
            return;
        }

        Action onThemeLoaded =
            () =>
            {
                SelectedThemeUri = new Uri(string.Format("{0};component/Theme.xaml", theme), UriKind.RelativeOrAbsolute);
                SelectedThemeName = themeName;
            };
        WebClient webClient = new WebClient();
        webClient.OpenReadCompleted += new OpenReadCompletedEventHandler(OnAssemblyOpened);
        webClient.OpenReadAsync(new Uri(string.Format("{0}.dll", theme), UriKind.Relative), onThemeLoaded);
    }

    private void OnGetThemesCompleted(object sender, GetThemesCompletedEventArgs e)
    {
        _themes = e.Result.ToDictionary(k => k.Replace("System.Windows.Controls.Theming.", ""), v => v);

        if (PropertyChanged != null)
        {
            PropertyChanged(this, new PropertyChangedEventArgs("ThemeNames"));
        }

        if (_themes.ContainsKey(_defaultThemeName))
        {
            SelectTheme(_defaultThemeName);
        }
    }

    private static void OnAssemblyOpened(object sender, OpenReadCompletedEventArgs e)
    {
        AssemblyPart assemblyPart = new AssemblyPart();
        Assembly assembly = assemblyPart.Load(e.Result);

        string theme = GetName(assembly);
        Action onThemeLoaded = e.UserState as Action;
        if (onThemeLoaded != null)
        {
            onThemeLoaded();
        }
    }

    private static string GetName(Assembly assembly)
    {
        return new AssemblyName(assembly.FullName).Name;
    }

    private ThemeWebServiceSoapClient _proxy = new ThemeWebServiceSoapClient();
    private Dictionary _themes = new Dictionary();
    private string _defaultThemeName;
}

ThemeLoader implements INotifyPropertyChanged to be bindable. The class has a constructor which takes a default theme. ThemeLoader uses our web service to find out which themes are available for download.

A view uses the method SelectTheme() to load a theme. If the theme is already loaded then it will be used directly, otherwise it will be asynchronously downloaded using OpenReadAsync.

Once the theme is downloaded, the properties SelectedThemeUri and SelectedThemeName are updated. These are the properties that the view will bind to.

Download full code sample: Skydrive

July 8, 2011

Marching Ants border for WPF

Filed under: C#,WPF — Patric Johansson @ 5:13 PM

Marching Ants is a famous visualisation to show some kind of selection. It is commonly used in various image editing software but can also be useful in other scenarios to draw attention to some part of the screen. WPF uses a dotted border to show which control has focus, but in a highly themed applications it is often hard to detect those little dots. Instead lets use Marching Ants to show which control has focus.


Below is a ContentControl called MarchingAntsBorder which draws a border with a Marching Ants animation when its content has focus. The class has a couple of properties to tweak its appereance.

using System;
using System.Linq;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Media;
using System.Windows.Media.Animation;
using System.Windows.Shapes;

namespace MarchingAntsDemo
{
    [TemplatePart(Name = "PART_Rectangle", Type = typeof(Rectangle))]
    public class MarchingAntsBorder : ContentControl
    {
        static MarchingAntsBorder()
        {
            DefaultStyleKeyProperty.OverrideMetadata(typeof(MarchingAntsBorder),
                                                     new FrameworkPropertyMetadata(typeof(MarchingAntsBorder)));
        }

        public Brush BorderBrush
        {
            get { return (Brush)GetValue(BorderBrushProperty); }
            set { SetValue(BorderBrushProperty, value); }
        }

        public static readonly DependencyProperty BorderBrushProperty =
            DependencyProperty.Register("BorderBrush", typeof(Brush), typeof(MarchingAntsBorder), new UIPropertyMetadata(Brushes.Red));

        public double BorderThickness
        {
            get { return (double)GetValue(BorderThicknessProperty); }
            set { SetValue(BorderThicknessProperty, value); }
        }

        public static readonly DependencyProperty BorderThicknessProperty =
            DependencyProperty.Register("BorderThickness", typeof(double), typeof(MarchingAntsBorder), new UIPropertyMetadata(1D));

        public double CornerRadius
        {
            get { return (double)GetValue(CornerRadiusProperty); }
            set { SetValue(CornerRadiusProperty, value); }
        }

        public static readonly DependencyProperty CornerRadiusProperty =
            DependencyProperty.Register("CornerRadius", typeof(double), typeof(MarchingAntsBorder), new UIPropertyMetadata(0D));

        // Only DoubleCollections with one single item are supported.
        public DoubleCollection StrokeDashArray
        {
            get { return (DoubleCollection)GetValue(StrokeDashArrayProperty); }
            set { SetValue(StrokeDashArrayProperty, value); }
        }

        public static readonly DependencyProperty StrokeDashArrayProperty =
            DependencyProperty.Register("StrokeDashArray", typeof(DoubleCollection), typeof(MarchingAntsBorder), new UIPropertyMetadata(new DoubleCollection() {2}));

        protected override void OnIsKeyboardFocusWithinChanged(DependencyPropertyChangedEventArgs e)
        {
            Rectangle PART_Rectangle = this.GetTemplateChild("PART_Rectangle") as Rectangle;
            if (PART_Rectangle != null)
            {
                if ((bool)e.NewValue)
                {
                    PART_Rectangle.Visibility = Visibility.Visible;
                   
                    DoubleAnimation doubleAnimation = new DoubleAnimation();
                    doubleAnimation.From = 0.0;
                    doubleAnimation.To = StrokeDashArray.Single() * 2;
                    doubleAnimation.Duration = new Duration(TimeSpan.FromSeconds(1.0));
                    doubleAnimation.AutoReverse = false;
                    doubleAnimation.RepeatBehavior = RepeatBehavior.Forever;
                    PART_Rectangle.BeginAnimation(Shape.StrokeDashOffsetProperty, doubleAnimation);
                }
                else
                {
                    PART_Rectangle.Visibility = Visibility.Hidden;
                    PART_Rectangle.BeginAnimation(Shape.StrokeDashOffsetProperty, null);
                }
            }

            base.OnIsKeyboardFocusWithinChanged(e);
        }
    }
}

Add its default template to a resource directory named Generic.xaml under a Themes folder.
This is the standard place for WPF to look for styles defining a template when it loads a control.

xmlns="<a href="http://schemas.microsoft.com/winfx/2006/xaml/presentation">http://schemas.microsoft.com/winfx/2006/xaml/presentation</a>"
                    xmlns:x="<a href="http://schemas.microsoft.com/winfx/2006/xaml">http://schemas.microsoft.com/winfx/2006/xaml</a>"
                    xmlns:local="clr-namespace:MarchingAntsDemo">

   
        <Setter Property="FocusVisualStyle" Value="{x:Null}" />
        <Setter Property="Template">
            <Setter.Value>
                <ControlTemplate TargetType="{x:Type local:MarchingAntsBorder}">
                    <Grid>
                        <ContentPresenter
                            Content="{TemplateBinding Content}" />
                        <Rectangle
                            Name="PART_Rectangle"
                            Visibility="Hidden"
                            Fill="Transparent"
                            Stroke="{TemplateBinding BorderBrush}"
                            StrokeThickness="{TemplateBinding BorderThickness}"
                            RadiusX="{TemplateBinding CornerRadius}"
                            RadiusY="{TemplateBinding CornerRadius}"
                            StrokeDashArray="{TemplateBinding StrokeDashArray}" />                      
                    </Grid>
                </ControlTemplate>
            </Setter.Value>
        </Setter>
    </Style>

</ResourceDictionary>

Example of usage:

<MarchingAntsDemo:MarchingAntsBorder BorderBrush="Yellow" BorderThickness="2" CornerRadius="5" StrokeDashArray="2">
<Button Content="Button with Marching Ants border to indicate focus" FocusVisualStyle="{x:Null}" />
</MarchingAntsDemo:MarchingAntsBorder>

Finally, some tips. You should set FocusVisualStyle to null in your default styles for controls so you don’t have to set it directly on each control like above. The MarchingAntsBorder can also be added to the control template in your default styles so you don’t need to wrap each control with one.

November 1, 2005

Commenting code

Filed under: Uncategorized — Patric Johansson @ 3:36 PM

Today’s Daily WTF is about code commenting where the comment describes what the code does. What’s wrong with that? First of all, code should be self describing as much as possible. Keep your code well structured and as simple as possible, use declarative variable and method names so it can be read and understood without any explaining comments. This is possible with most code, except some really tricky algorithm implementations which will be easier to read with some guiding comments. I usually find it valuable in these cases to add a full example with some values showing how the algorithm transforms these values, step by step.

So does that mean I don’t comment my code? Not at all, I comment a lot. But I try to add comments that instead of explaining what the code does, instead explains why it does it. You should assume that anyone reading your code understands the language, so you don’t need to explain that part of the program for them. For example, this is an example of an unnecessary comment:
m_nRow++; // Increase m_nRow with 1.
Instead, here’s a more useful comment:
m_nRow++; // m_nRow comes from a zero based index and this class assumes that first row is row number one and not zero.

Instead of describing what to do we describe why we do it. Programmers who don’t get the increment operator should not be let near your code anyway.

Another useful place for comments is to help programmers see hard to spot pitfalls when doing changes to the code. When writing code we always do tradeoffs because of workload or deadlines and usually our classes are highly specialized for its precise purpose. There are usually a lot of cases that are not handled, since these cases can not occur. However, when someone later changes the code, these cases just might be something that suddenly is not just theoretical problems. A common example is of course threading, that your class is not threadsafe since its not used by more than one thread, but later this is changed and no one know that your class wasn’t threadsafe.

So our code comments should serve three purposes: explain why the code is how it is, point out pitfalls and finally explain difficult code parts. Out of these, the two first comment types are the most common.

Comments can serve one more purpose, and that is when changing other programmer’s code. I usually add comments to other’s code before changing it. This way forces me to really go through the code and understand it before I attempt to change it. I usually read it while debugging it to make sure it works as I think it does. The commenting forces me to express my understanding of the code in words which will make it very obvious if I understand it or not. It’s hard to write a descriptive comment about something you don’t get. The better a program is structured; the easier it is to write comments for it and of course, the opposite is true as well. But after adding the comments I comprehend the code better than if I just had read it.

October 23, 2005

Can Linq be successful?

Filed under: Uncategorized — Patric Johansson @ 5:20 PM

I developed a couple of applications with relational databases using the traditional way of sending SQL strings to the database. The problem with this approach is of course that your SQL strings are never verified at compile time, instead first at runtime will you know if they are syntactically correct. If you have chosen to work with a strongly typed language like C# you most likely favor compile time verification before runtime verification.

In one project where we had strong financial backing and high demands of quality we eliminated this risk with SQL strings by using a code generator called Persistence which would generate access classes to manipulate our database. This meant we would use classes with proper methods to update and manipulate rows and columns in the database tables without having to use any SQL strings. These SQL strings were of course there, but they were automatically generated from the database and encapsulated in these access classes. I’m not sure if Persistence still exists as a product or as a company. The company behind the product, Persistence Software was acquired last year by Progress Software. I’m not sure if they killed Persistemce or what happened to it.

Microsoft has an excellent database in SQL Server and some first-rate programming languages, but no good mapping between them. Since Microsoft owns these languages it didn’t need to do it the Persistence way with code generators, instead Microsoft can and will add access capabilities directly to these languages or more correctly to the CLR. This will be part of the next C# version (3.0) and will be part of the Visual Studio code named Orca and is called Linq which comes in different implementations. So far there’s DLinq for querying relational data and XLinq for querying XML.The only existing DLinq implementation is the one for SQL Server. I believe that for DLinq to be really successful there need to be an Oracle implantation as well. I’m sure there’s possible to hack one together using ODBC or some other Oracle API, but to get good performance it need to be built into its query engine, that is directly into Oracle. This is of course nothing that Microsoft or any third party can do on their own; this is a job for Oracle themselves. However, Oracle is not the most supportive of Microsoft and will likely try to hinder DLinq from getting popular. Unless there’s a huge demand for integrated DLinq support in Oracle by some large users, I don’t see Oracle helping out Microsoft voluntarily. And without DLinq fully working with Oracle I don’t see it as becoming the standard way of developing database applications for Windows. Microsoft must get Oracle to add full integrated DLinq support in its database.

So Microsoft have a real dilemma here. In order to make DLinq a success it needs Oracle to support it. However Oracle will only support it if it’s already a success and its user demands it. A classic catch 22 situation. Let’s hope that Microsoft and Oracle can work together on this one.

October 22, 2005

Charles Petzold asks if Visual Studio Rot your mind?

Filed under: Uncategorized — Patric Johansson @ 12:09 AM

Yesterday, Charles Petzold spoke at the NYC .NET Developers Group on the topic "Does Visual Studio Rot the Mind?". I was of course there to hear everyone’s favorite Win32 book author. It was a very entertaining lecture, held totally without any use of PowerPoint or any code examples. Instead he discussed the differences between how we program .NET today with Visual Studio and how it was done in the days of 16bits Windows using C and a simple text editor. Petzold made a point that Visual Studio will force you to program in a certain way and you don’t really have a choice. IntelliSense will suggest what code you want to type next. Code generators and wizards will create your classes and implement large parts of them for you. If your application needs a GUI you will use the Form Designer to draw your form without writing any code. Petzold continued with describing how he missed having to do all the coding himself and be in full control of the resulting code. However, he pointed out we have no choice today. The first Windows API contained around 400 methods. The API for Windows95 contained little over 1 000. .NET 2.0 framework contains around 60 000 public methods and properties. So IntelliSense is a must to find a suitable methods to use, there’s no way anyone can learn them all like before. Automatic code generation and using form designers is also a must for a programmer to be competitive today, everyone else are using them so you will just be considered to be a slow coder instead of a careful programmer who’s in control of his code if you don’t use them.

Petzold said he just finished writing a book called "Programming Microsoft Windows Forms" and just started on his next project, a book about the Windows Presentation Foundation (WPF). Petzold said that he fought WPF was the future but not yet with Visual Studio 2005. Lots of things are missing still like the common dialogs, Open File dialog for example. Even if this is fixed in future versions, for smaller projects WinForms will still be the prefered way, just because dealing with XAML is just not worth it. WinForms are good enough in many cases.

September 16, 2005

Expand MDI child windows to not overlap each others

Filed under: C#,WinForms — Patric Johansson @ 6:08 PM

MDI applications are great for applications with many windows which need to be displayed at the same time next to each other. Normally the user will have to size and arrange the child windows so they don’t cover each other but uses maximum available space. No one likes any grey wasted space in between, desktop real estate is expensive

A class called MDIChildExpander will handle the work of calculating how much exactly we can expand each child window. Let’s start with some screenshots so we understand the concept. Below is a picture of a normal MDI application where there is lots of wasted space in between each child window.

We expand the middle window, in this application it is done by double clicking on the window’s client area, see second picture below.

We continue and expand each window and end up with the following layout, see last picture below.

No more grey dead wasted space! And its easy to use. In the class for a child window, add a private variable for an MDIChildExpander object:

private MDIChildExpander expander;

Next, in the constructer of the child window class, create it:

expander = new MDIChildExpander(this);

Then allow the user somehow to expand the window and then call the following code:

expander.Expand();

Not so hard to use, don’t you agree? The MDIChildExpander is however more complex, but I tried to make it more understandable by some well placed comments. And now with no further ado, I give to you the :MDIChildExpander class.

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

public class MDIChildExpander
{
    private Form child;

       public MDIChildExpander(Form child)
       {
        this.child = child;
    }

    static private bool Overlap(int x1, int x2, int y1, int y2)
    {
        // Returns true if two lines overlap each other. x1 and x2 is start and end position for first line.
        // y1 and y2 is start and end position for second line.

        return (x1 >= y1 && x1 < y2) || 
                (x2 > y1 && x2 <= y2) ||
                (y1 >= x1 && y1 < x2) || 
                (y2 > x1 && y2 <= x2);
    }

    static private bool HorizontalOverlap(Rectangle rect1, Rectangle rect2)
    {
        // Returns true if rect1 is right above or right below rect2. The below two
        // rectangles horizontally overlap each other.
        //
        //   XXX
        //   XXX
        //
        //      XXX 
        //      XXX 

        return Overlap(rect1.Left, rect1.Right, rect2.Left, rect2.Right);
    }

    static private bool VerticalOverlap(Rectangle rect1, Rectangle rect2)
    {
        // Returns true if rect1 is directly to the left or right of rect2. The below two
        // rectangles vertically overlap each other.
        //
        //   XXX   XXX
        //   XXX   XXX
                   
        return Overlap(rect1.Top, rect1.Bottom, rect2.Top, rect2.Bottom);
    }

    public void Expand()
    {
        // We will modify a Rectangle object which is a copy of the child’s bounds to fill the biggest
        // possible space without overlapping any other child window. If the child window already overlaps
        // another child window, we will continue to overlap it.

        // childCausesHorizontalScrollBar and childCausesVerticalScrollBar will be true if the child window is 
        // the only window outside the visible area of the MDI container main window and is responsible for 
        // causing the main window to display a scrollbar. In that case when resizing the child window we will
        // move it and resize it to fit within the visible area. This will of course remove the need of a scrollbar.
        // However, if some other child window is also outside the visible are, the scrollbar will still be there
        // after axpansion and the corresponding variable will be false. So they will only be true if the child window
        // is the only cause for a scrollbar.

        Rectangle childRect = child.Bounds;

        // Adjust top if possible.

        int topNew = 0;;
        foreach (Control control in child.Parent.Controls)
        {
            Form otherForm = control as Form;
            
            if (otherForm != null && otherForm != child)
            {
                // Check if child overlaps horizontally and that the child is below the other form and if that other form
                // is below any previous found windows.

                if (HorizontalOverlap(childRect, otherForm.Bounds) &&
         childRect.Top >= otherForm.Bottom &&
         otherForm.Bottom > topNew)
                {
                    topNew = otherForm.Bottom;
                }
            }
        }

        // Increase height and move window.

        childRect.Height += childRect.Top – topNew; 
        childRect.Y = topNew;
    
        bool childCausesHorizontalScrollBar = 
           child.Left < 0 ||
           child.Right > child.Parent.Bounds.Right; 

        // Adjust bottom if possible.

        int bottomNew = child.Parent.ClientSize.Height;
        foreach (Control control in child.Parent.Controls)
        {
            Form otherForm = control as Form;
            if (otherForm != null && otherForm != child)
            {
                // Check if forms overlap horizontally and that the form is above the other form and if that other form
                // is above any previous found windows.

                if (HorizontalOverlap(childRect, otherForm.Bounds) && 
          childRect.Bottom <= otherForm.Top && 
          otherForm.Top < bottomNew)
                {
                    bottomNew = otherForm.Top;
                }

                childCausesHorizontalScrollBar &= 
           otherForm.Left >= 0 && 
           otherForm.Right <= child.Parent.Bounds.Right; ;
            }
        }

        // Increase height.

        childRect.Height += bottomNew – child.Bottom; 

        if (childCausesHorizontalScrollBar && 
      bottomNew == child.Parent.ClientSize.Height)
        {
            // We need to expand it some more.

                childRect.Height += 
           System.Windows.Forms.SystemInformation.HorizontalScrollBarHeight;
        }

        // Adjust left side if possible.

        int leftNew = 0;
        foreach (Control control in child.Parent.Controls)
        {
            Form otherForm = control as Form;
            if (otherForm != null && otherForm != child)
            {
                // Check if forms overlap vertically and that the form is right of the other form and if that other form
                // is more right than any previous found windows.

                if (VerticalOverlap(childRect, otherForm.Bounds) &&
          childRect.Left >= otherForm.Right &&
          otherForm.Right > leftNew)
                {
                    leftNew = otherForm.Right;
                }
            }
        }

        // Increase width and move window.

        childRect.Width += childRect.Left – leftNew; 
        childRect.X = leftNew;
    
        bool childCausesVerticalScrollBar = 
           child.Top < 0 || 
           child.Bottom > child.Parent.Bounds.Bottom; 

        // Adjust right side if possible.

        int rightNew = child.Parent.ClientSize.Width;
        foreach (Control control in child.Parent.Controls)
        {
            Form otherForm = control as Form;
            if (otherForm != null && otherForm != child)
            {
                // Check if forms overlap vertically and that the form is left of the other form and if that other form
                // is more left than any previous found windows.

                if (VerticalOverlap(childRect, otherForm.Bounds) &&
          childRect.Right <= otherForm.Left &&
          otherForm.Left < rightNew)
                {
                    rightNew = otherForm.Left;
                }

                childCausesVerticalScrollBar &=  
                        otherForm.Top >= 0 ||
                        otherForm.Bottom <= child.Parent.Bounds.Bottom; 
            }
        }

        // Increase width.

        childRect.Width += rightNew – child.Right; 

        if (childCausesVerticalScrollBar &&
      rightNew == child.Parent.ClientSize.Width)
        {
            // We need to expand it some more.

            childRect.Width += 
         System.Windows.Forms.SystemInformation.VerticalScrollBarArrowHeight;
        }

        // Update child window with new bounds.

        child.Bounds = childRect;

        if (childCausesHorizontalScrollBar && 
      child.Right != childRect.Right &&
      child.Width > childRect.Width)
        {
            // When moving the child window from the non visible part of the window and resizing it, we may end
            // up trying to resize the window to smaller than Windows allows. This can only happen when the window
            // was previously partly hidden and solely caused a scrollbar to be displayed. We need to move it some
            // more then to fit within the main MDI container window.

            child.Left -= child.Width – childRect.Width;
        }

        if (childCausesVerticalScrollBar && 
      child.Bottom != childRect.Bottom && 
      child.Height > childRect.Height)
        {
            // When moving the child window from the non visible part of the window and resizing it, we may end
            // up trying to resize the window to smaller than Windows allows. This can only happen when the window
            // was previously partly hidden and solely caused a scrollbar to be displayed. We need to move it some

            // more then to fit within the main MDI container window.

            child.Top -= child.Height – childRect.Height;
        }
    }
}

August 16, 2005

MDI windows toolbar

Filed under: C#,WinForms — Patric Johansson @ 9:00 AM

Previously we discussed .NET support for MDI applications and how to build on to that by adding layout functions as part of an MDI windows menu. The problem with a menu is that it needs to be pulled down in order to let users view which child windows are open. A toolbar with a button per window would be a solution since the toolbar is always visible. Below is a class which implements such a toolbar, see figure for an example. The toolbar needs to know about each new child window so whenever one is created and shown, you must call its RegisterChildWindow method. The signature of the method looks like this:

RegisterChildWindow(Form childForm, string name, string toolTipText, int imageIndex)

First parameter is the child form, second the text of the toolbar button, third text of button’s tooltip and finally an index into an image list. Both text of the toolbar button and its tooltip can be left blank. If no image is wanted on the button, then set imageIndex to = -1. In the figure some windows have text on the buttons and some uses images. I think it all depends on how long your child windows name is if you should display it or just its icon. Don’t forget to set the toolbar’s ImageList property to your image list if you uses images. As you can see in the figure the toolbar has a dropdown button labeled Layout. Its dropdown menu is the MDIContextMenu and is added to the toolbar if you call AddLayoutButton method from your main MDI form. Left mouse clicking on a toolbar button will of course active the child window while right clicking with the mouse will restore the child window if minimized and then display its system menu. See code for details.

public class MDIToolBar : System.Windows.Forms.ToolBar
{
    // pushedButton is currently pushed button and is used 
    // when another button is pushed to know which one to set to 
    // not pushed anymore.

    private ToolBarButton pushedButton = new ToolBarButton();
    
    bool hasLayoutButton = false;

    public void AddLayoutButton(Form parent)
    {
        ToolBarButton buttonSeparator = new ToolBarButton();
        buttonSeparator.Style = ToolBarButtonStyle.Separator;
        Buttons.Add(buttonSeparator);          

        ToolBarButton buttonLayout = new ToolBarButton();
        buttonLayout.Text = "Layout";
        buttonLayout.Style = ToolBarButtonStyle.DropDownButton;
        buttonLayout.DropDownMenu = new MDIContextMenu(parent);
        Buttons.Add(buttonLayout);

        hasLayoutButton = true;
    }

    public void RegisterChildWindow(Form childForm, string name, string toolTipText, int imageIndex)
    {
        childForm.Closed += new EventHandler(child_Closed);
        childForm.Activated +=new EventHandler(child_Activated);  

        ToolBarButton toolBarButton = new ToolBarButton();
        toolBarButton.Text = name;
        toolBarButton.Tag = childForm;
        toolBarButton.ToolTipText = toolTipText;

        toolBarButton.ImageIndex = imageIndex;

        if (hasLayoutButton)
        {
            // We want out layout dropdown menu and a separator to always be displayed
            // last in the toolbar so we add any new buttons before these two buttons.

            Buttons.Insert(Buttons.Count – 2, toolBarButton);
        }
        else
        {
            Buttons.Add(toolBarButton);
        }

        // Change currently pushed button to the new window’s button.

        pushedButton.Pushed = false;
        toolBarButton.Pushed = true;
        pushedButton = toolBarButton;

    }

    private void child_Closed(object sender, EventArgs e)
    {
        // Windows is closed, remove its button. 
        // Another window will automatically be activated if closed window was the active one
        // and child_Activated method will be called.

        foreach (ToolBarButton button in Buttons)
        {
            if (button.Tag == sender)
            {
                Buttons.Remove(button);
                break;
            }
        }
    }

    private void child_Activated(object sender, EventArgs e)
    {
        // Another window is activated, we need to change which
        // button is pushed.

        pushedButton.Pushed = false;

        foreach (ToolBarButton button in Buttons)
        {
            if (button.Tag == sender)
            {
                button.Pushed = true;
                pushedButton = button;
                break;
            }
        }
    }
    
    protected override void OnButtonClick(ToolBarButtonClickEventArgs e)
    {
        // User (left) clicked on a toolbar button.

        Form childForm = e.Button.Tag as Form;
        if (childForm != null)
        {
            // The button’s tag is set to a form so this means we should activate the window.

            childForm.Activate();
        }

        base.OnButtonClick(e);
    }

    protected override void OnMouseUp(MouseEventArgs e)
    {
        if (e.Button == MouseButtons.Right)
        {
            // Find clicked button.

            Point mouseClickLocation = new Point(e.X, e.Y);

            foreach (ToolBarButton button in Buttons)
            {
                if (button.Rectangle.Contains(mouseClickLocation))
                {
                    Form childForm = button.Tag as Form;
                    if (childForm != null)
                    {
                        // User right clicked on a toolbar button with tag set to a form.
                        // We should activate the window, restore it and display its system menu by 
                        // sending the key sequence ALT – which is how you display an MDI child’s 
                        // system menu.

                        childForm.Activate();
                        childForm.WindowState = FormWindowState.Normal;
                        SendKeys.Send("%-");
                        break;
                    }
                }
            }
        }

        base.OnMouseUp(e);
    }        
}

August 12, 2005

MDI windows menu

Filed under: C#,WinForms — Patric Johansson @ 6:16 PM

MDI applications have lost their popularity the last few years mainly because people are nowadays used to web applications running in a browser which are SDI by default. The last draw was when Microsoft Office stopped being a MDI application with Office 2003. You can still enable MDI functionality in Office, see KB 291313. The problem with SDI applications is that windows you want to use together, that is have open and often switch between are harder to find if they are not contained within an MDI container window. Also, with many SDI applications your taskbar fills up which makes switching between apps using CTRL+TAB very strenuous. I’m a fan of MDI applications except maybe for the most trivial applications. MDI applications done with .NET are dead simple, just state which window is the main container window by setting the following property:

IsMdiContainer = true;

Then whenever you create an MDI child window you need to set the property called MdiParent to the container window before calling the Show() method.

This normally looks something like this when done from the main container window, the class FormChild is of course a Form class we want to instantiate as a MDI child window object.

Form child = new FormChild();
child.MdiParent = this;
child.Show();

The support for MDI in .NET doesn’t stop with that, it even provides with a Windows menu which will display all open child windows and allow you to switch in between them easily. Just add a menu item to your window’s main menu and set the following property:

MdiList = true;

I have written a menu class which adds some layout functionality to this Windows menu which you can merge with MDI windows menu. So create a Form class which will be your main MDI container window, set IsMdiContainer to true, add a menu item with the property MdiList set to true and then in the contructur of the Form class, add the following code:

MDIContextMenu mDILayoutMenu = new MDIContextMenu(this);
menuItemWindows.MergeMenu(mDILayoutMenu);

menuItemWindows is of course the menu item with MdiList set to true. The menu class is called MDIContextMenu and it will add all these layout functions like Minimize all or Tile Vertical you are used to from other high class Windows applications.

public class MDIContextMenu : System.Windows.Forms.ContextMenu
{
    private Form mDIForm;

       public MDIContextMenu(Form mDIForm)
       {
            this.mDIForm = mDIForm;

            MenuItem menuItemMinimizeAll = new MenuItem("Minimize All");
            menuItemMinimizeAll.Enabled = false;
            menuItemMinimizeAll.Click += new EventHandler(menuItemMinimizeAll_Click);

            MenuItem menuItemMaximizeAll = new MenuItem("Maximize All");
            menuItemMaximizeAll.Enabled = false;
            menuItemMaximizeAll.Click += new EventHandler(menuItemMaximizeAll_Click);

            MenuItem menuItemCloseAll = new MenuItem("Close All");
            menuItemCloseAll.Enabled = false;
            menuItemCloseAll.Click += new EventHandler(menuItemCloseAll_Click);

            MenuItem menuItemArrangeIcons = new MenuItem("Arrange Icons");
            menuItemArrangeIcons.Enabled = false;
            menuItemArrangeIcons.Click += new EventHandler(menuItemArrangeIcons_Click);  

            MenuItem menuItemCascadeAll = new MenuItem("Cascade All");            
            menuItemCascadeAll.Enabled = false;
            menuItemCascadeAll.Click +=new EventHandler(menuItemCascadeAll_Click); 

            MenuItem menuItemTileVertical = new MenuItem("Tile Vertical");
            menuItemTileVertical.Enabled = false;
            menuItemTileVertical.Click += new EventHandler(menuItemTileVertical_Click);    
            MenuItem menuItemTileHorizontal = new MenuItem("Tile Horizontal");
            menuItemTileHorizontal.Enabled = false;
            menuItemTileHorizontal.Click += new EventHandler(menuItemTileHorizontal_Click);

            MenuItems.AddRange(new MenuItem[]{menuItemMinimizeAll, 
                                              menuItemMaximizeAll,
                                              menuItemCloseAll,
                                              menuItemArrangeIcons,
                                              menuItemCascadeAll,
                                              menuItemTileVertical,
                                              menuItemTileHorizontal});

        mDIForm.MdiChildActivate +=new EventHandler(mDIForm_MdiChildActivate);
    }

    private void menuItemMinimizeAll_Click(object sender, EventArgs e)
    {
        Form childActive = mDIForm.ActiveMdiChild;

        foreach (Form childForm in mDIForm.MdiChildren)
        {
            childForm.WindowState = FormWindowState.Minimized;    
        }

        if (childActive != null)
        {
            childActive.Activate();
        }
    }

    private void menuItemMaximizeAll_Click(object sender, EventArgs e)
    {
        Form childActive = mDIForm.ActiveMdiChild;

        foreach (Form childForm in mDIForm.MdiChildren)
        {
            childForm.WindowState = FormWindowState.Maximized;    
        }

        if (childActive != null)
        {
            childActive.Activate();
        }
    }

    private void menuItemCloseAll_Click(object sender, EventArgs e)
    {
        for (int i = mDIForm.MdiChildren.Length – 1; i >= 0; i–)
        {
            Form childForm = mDIForm.MdiChildren[i] as Form;
            if (childForm != null)
            {
                childForm.Close();
            }
        }
    }      

    private void menuItemArrangeIcons_Click(object sender, EventArgs e)
    {
        mDIForm.LayoutMdi(MdiLayout.ArrangeIcons);
    }

    private void menuItemCascadeAll_Click(object sender, EventArgs e)
    {
        mDIForm.LayoutMdi(MdiLayout.Cascade);
    }

    private void menuItemTileVertical_Click(object sender, EventArgs e)
    {
            mDIForm.LayoutMdi(MdiLayout.TileVertical);
    }

    private void menuItemTileHorizontal_Click(object sender, EventArgs e)
    {
        mDIForm.LayoutMdi(MdiLayout.TileHorizontal);
    }

    private void mDIForm_MdiChildActivate(object sender, EventArgs e)
    {
        Form form = mDIForm.ActiveMdiChild;
        foreach (MenuItem menuItem in MenuItems)
        {
            menuItem.Enabled = form != null;
        }
    }
}
Next Page »

Blog at WordPress.com.