Saturday, July 23, 2011

Writing a Reusable WPF Control with Design Support

Code reusablity is one of the major concern to many of us. When dealing with large projects, modularizing your project is one of the primary thing that you should look for. I have talked about many of the approaches that you can use to deal with modularizing your code, eg, Prism. In this post our intent is not to talk hard on some pattern, rather I will discuss how your WPF application supports code reusability. There are a number of approaches that WPF supports to deal with reusable component. We can use Resources to define a Resource or even use CodeBehind to write our reusable component or even load the XAML from a file using XAML Loader. Each of the approaches has its own pros and cons. Today I will show you another cool technique to write a reusable XAML for your project which will allow you to easily use Visual Studio design surface to design the component.

Note : It is a very basic article, if you want to know about details on WPF, please read my tutorial on it.

Steps to Create your Reusable Component


Lets create a series of steps to create a reusable component for your application to make it easier for you to write one yourself.




Step 1 : Start Visual Studio and Create a new WPF project.
Step 2 : Add a new Page to the application and call it as GreetPanel. Just like the figure below.


Once you are done lets change the code for the Page.


Step 3 : Change the root element of the page to Grid to make it a Reusable grid.


Note : Currently there is no Visual Studio File Template for writing a loose XAML component, hence I have used Page to do the same.

Step 4: After doing this, you also need to go to the Code -behind for the Page and change it to inherit from Grid. Now you have a complete reusable component (UserControl) which can use the design surface too, to define its components.

Now lets define the control with most basic code :

<Grid x:Class="WritingLooseXAML.GreetPanel"
      xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
      xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml">
    <Grid.RowDefinitions>
        <RowDefinition />
        <RowDefinition />
    </Grid.RowDefinitions>
    <Grid.ColumnDefinitions>
        <ColumnDefinition />
        <ColumnDefinition />
    </Grid.ColumnDefinitions>
    <Label Grid.Row="0" Grid.Column="0" >
        Write Name
    </Label>
    <TextBox Grid.Column="1" x:Name="txtName" />
    <Button Content="Greet Me" Click="Button_Click" Grid.Row="1" Grid.Column="0" />
    <TextBlock x:Name="tbResult" Grid.Row="1" Grid.Column="1"/>
    
</Grid>

Here I define a TextBox and a Button which has a click Handler too. You can define the ClickHandler in the codebehind for the GridPanel too. The Button will display a message in a textblock called tbResult.

The Codebehind looks like :
public partial class GreetPanel : Grid
{
    public GreetPanel()
    {
        InitializeComponent();
    }

    private void Button_Click(object sender, RoutedEventArgs e)
    {
        this.tbResult.Text = string.Format("Hello {0}, Welcome!", this.txtName.Text);
    }
}

Step 5 : Now once you are done defining your GridPanel, you can use it in any Window. Just use xmlns to declare the namespace for the control, here I used the local namespace, and define the controls.

<Window x:Class="WritingLooseXAML.MainWindow"
        xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
        xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
        xmlns:local="clr-namespace:WritingLooseXAML"
        Title="MainWindow" Height="350" Width="525">
    <WrapPanel Orientation="Horizontal">
        <local:GreetPanel x:Name="gPanel1" />
        <local:GreetPanel x:Name="gPanel2" />
        <local:GreetPanel x:Name="gPanel3" />
    </WrapPanel>
</Window>

You can access each properties of the Grid from your GreetPanel as it is derived from Grid. Now compile your code and run, you will see your greetpanel working.

Adding a RoutedEvent on the control

As you know RoutedEvent is one of the base for events in WPF application, lets see how you can add one on the panel just declared. Now as you might already know that RoutedEvent can be of three types, one that Bubbles, one Tunnels and one Direct. To declare a RoutedEvent, you need to use EventManager.RegisterRoutedEvent. This command will register the event to the XAML and hence allows you to use Event in EventSetters, styles etc.

In our previous code, lets add an event which will fire when the Result box is altered. To do this, lets add this to the code page of GreetPanel.


static readonly RoutedEvent ResutlAlteredEvent = 
    EventManager.RegisterRoutedEvent("ResultAltered", 
    RoutingStrategy.Bubble, 
    typeof(RoutedEventHandler), 
    typeof(GreetPanel));

public event RoutedEventHandler ResultAltered
{
    add { AddHandler(ResutlAlteredEvent, value); }
    remove { RemoveHandler(ResutlAlteredEvent, value); }
}

// This method raises the ResultAltered event
void RaiseResultAlteredEvent()
{
    RoutedEventArgs newEventArgs = new RoutedEventArgs(GreetPanel.ResutlAlteredEvent);
    RaiseEvent(newEventArgs);
}

private void Button_Click(object sender, RoutedEventArgs e)
{
    this.tbResult.Text = string.Format("Hello {0}, Welcome!", this.txtName.Text);
    this.RaiseResultAlteredEvent();
}

Now from the MainWindow.xaml (where you add this control you can find the ResultAltered event directly.

<WrapPanel Orientation="Horizontal">
        <local:GreetPanel x:Name="gPanel1" ResultAltered="gPanel_ResultAltered" />
        <local:GreetPanel x:Name="gPanel2" ResultAltered="gPanel_ResultAltered" />
        <local:GreetPanel x:Name="gPanel3" ResultAltered="gPanel_ResultAltered" />
    </WrapPanel>

And in the code-behind we added the eventhandler as :

private void gPanel_ResultAltered(object sender, RoutedEventArgs e)
{
    GreetPanel panel = sender as GreetPanel;

    MessageBox.Show(string.Format("You have altered result of {0}", panel.Name));
}

Thus when the button is clicked on the Control it shows up a messagebox shows up.

Conclusion

By this way you can derive any control directly from an existing control. But if you are likely to build a completely new control you can use UserControl as its base, which will ensure that the UI has nothing. This is very easy. In my next post I will talk about Custom Controls.



I hope this tutorial helps you.

Thank you for reading.

No comments:

Post a Comment

Please make sure that the question you ask is somehow related to the post you choose. Otherwise you post your general question in Forum section.

Author's new book

Abhishek authored one of the best selling book of .NET. It covers ASP.NET, WPF, Windows 8, Threading, Memory Management, Internals, Visual Studio, HTML5, JQuery and many more...
Grab it now !!!