Follow @PDSAInc August 2012 - Posts - Paul Sheriff's Blog for the Real World

Paul Sheriff's Blog for the Real World

This blog is to share my tips and tricks garnered over 25+ years in the IT industry

Paul's Favorites

August 2012 - Posts

A Communication System for XAML Applications

In any application, you want to keep the coupling between any two or more objects as loose as possible. Coupling happens when one class contains a property that is used in another class, or uses another class in one of its methods. If you have this situation, then this is called strong or tight coupling. One popular design pattern to help with keeping objects loosely coupled is called the Mediator design pattern. The basics of this pattern are very simple; avoid one object directly talking to another object, and instead use another class to mediate between the two. As with most of my blog posts, the purpose is to introduce you to a simple approach to using a message broker, not all of the fine details.

IPDSAMessageBroker Interface

As with most implementations of a design pattern, you typically start with an interface or an abstract base class. In this particular instance, an Interface will work just fine. The interface for our Message Broker class just contains a single method “SendMessage” and one event “MessageReceived”.

public delegate void MessageReceivedEventHandler(
 object sender, PDSAMessageBrokerEventArgs e);

public interface IPDSAMessageBroker
{
  void SendMessage(PDSAMessageBrokerMessage msg);

  event MessageReceivedEventHandler MessageReceived;
}

PDSAMessageBrokerMessage Class

As you can see in the interface, the SendMessage method requires a type of PDSAMessageBrokerMessage to be passed to it. This class simply has a MessageName which is a ‘string’ type and a MessageBody property which is of the type ‘object’ so you can pass whatever you want in the body. You might pass a string in the body, or a complete Customer object. The MessageName property will help the receiver of the message know what is in the MessageBody property.

public class PDSAMessageBrokerMessage
{
  public PDSAMessageBrokerMessage()
  {
  }

  public PDSAMessageBrokerMessage(string name, object body)
  {
    MessageName = name;
    MessageBody = body;
  }

  public string MessageName { get; set; }

  public object MessageBody { get; set; }
}

PDSAMessageBrokerEventArgs Class

As our message broker class will be raising an event that others can respond to, it is a good idea to create your own event argument class. This class will inherit from the System.EventArgs class and add a couple of additional properties. The properties are the MessageName and Message. The MessageName property is simply a string value. The Message property is a type of a PDSAMessageBrokerMessage class.

public class PDSAMessageBrokerEventArgs : EventArgs
{
  public PDSAMessageBrokerEventArgs()
  {
  }

  public PDSAMessageBrokerEventArgs(string name,
    PDSAMessageBrokerMessage msg)
  {
    MessageName = name;
    Message = msg;
  }

  public string MessageName { get; set; }

  public PDSAMessageBrokerMessage Message { get; set; }
}

PDSAMessageBroker Class

Now that you have an interface class and a class to pass a message through an event, it is time to create your actual PDSAMessageBroker class. This class implements the SendMessage method and will also create the event handler for the delegate created in your Interface.

public class PDSAMessageBroker : IPDSAMessageBroker
{
  public void SendMessage(PDSAMessageBrokerMessage msg)
  {
    PDSAMessageBrokerEventArgs args;

    args = new PDSAMessageBrokerEventArgs(
      msg.MessageName, msg);

    RaiseMessageReceived(args);
  }

  public event MessageReceivedEventHandler MessageReceived;

  protected void RaiseMessageReceived(
    PDSAMessageBrokerEventArgs e)
  {
    if (null != MessageReceived)
      MessageReceived(this, e);
  }
}

The SendMessage method will take a PDSAMessageBrokerMessage object as an argument. It then creates an instance of a PDSAMessageBrokerEventArgs class, passing to the constructor two items: the MessageName from the PDSAMessageBrokerMessage object and also the object itself. It may seem a little redundant to pass in the message name when that same message name is part of the message, but it does make consuming the event and checking for the message name a little cleaner – as you will see in the next section.

Create a Global Message Broker

In your WPF application, create an instance of this message broker class in the App class located in the App.xaml file. Create a public property in the App class and create a new instance of that class in the OnStartUp event procedure as shown in the following code:

public partial class App : Application
{
  public PDSAMessageBroker MessageBroker { get; set; }

  protected override void OnStartup(StartupEventArgs e)
  {
    base.OnStartup(e);

    MessageBroker = new PDSAMessageBroker();
  }
}

Sending and Receiving Messages

Let’s assume you have a user control that you load into a control on your main window and you want to send a message from that user control to the main window. You might have the main window display a message box, or put a string into a status bar as shown in Figure 1.

Figure 1: The main window can receive and send messages

Figure 1: The main window can receive and send messages

The first thing you do in the main window is to hook up an event procedure to the MessageReceived event of the global message broker. This is done in the constructor of the main window:

public MainWindow()
{
  InitializeComponent();

  (Application.Current as App).MessageBroker.
     MessageReceived += new MessageReceivedEventHandler(
       MessageBroker_MessageReceived);
}

One piece of code you might not be familiar with is accessing a property defined in the App class of your XAML application. Within the App.Xaml file is a class named App that inherits from the Application object. You access the global instance of this App class by using Application.Current. You cast Application.Current to ‘App’ prior to accessing any of the public properties or methods you defined in the App class. Thus, the code (Application.Current as App).MessageBroker, allows you to get at the MessageBroker property defined in the App class.

In the MessageReceived event procedure in the main window (shown below) you can now check to see if the MessageName property of the PDSAMessageBrokerEventArgs is equal to “StatusBar” and if it is, then display the message body into the status bar text block control.

void MessageBroker_MessageReceived(object sender,
   PDSAMessageBrokerEventArgs e)
{
  switch (e.MessageName)
  {
    case "StatusBar":
      tbStatus.Text = e.Message.MessageBody.ToString();
      break;
  }
}

In the Page 1 user control’s Loaded event procedure you will send the message “StatusBar” through the global message broker to any listener using the following code:

private void UserControl_Loaded(object sender,
 RoutedEventArgs e)
{
  // Send Status Message
  (Application.Current as App).MessageBroker.
    SendMessage(new PDSAMessageBrokerMessage("StatusBar",
      "This is Page 1"));
}

Since the main window is listening for the message ‘StatusBar’, it will display the value “This is Page 1” in the status bar at the bottom of the main window.

Sending a Message to a User Control

The previous example sent a message from the user control to the main window. You can also send messages from the main window to any listener as well. Remember that the global message broker is really just a broadcaster to anyone who has hooked into the MessageReceived event.

In the constructor of the user control named ucPage1 you can hook into the global message broker’s MessageReceived event. You can then listen for any messages that are sent to this control by using a similar switch-case structure like that in the main window.

public ucPage1()
{
  InitializeComponent();

  // Hook to the Global Message Broker
  (Application.Current as App).MessageBroker.
    MessageReceived += new MessageReceivedEventHandler(
      MessageBroker_MessageReceived);
}

void MessageBroker_MessageReceived(object sender,
 PDSAMessageBrokerEventArgs e)
{
  // Look for messages intended for Page 1
  switch (e.MessageName)
  {
    case "ForPage1":
      MessageBox.Show(e.Message.MessageBody.ToString());
      break;
  }
}

Once the ucPage1 user control has been loaded into the main window you can then send a message using the following code:

private void btnSendToPage1_Click(object sender,
 RoutedEventArgs e)
{
  PDSAMessageBrokerMessage arg =
    new PDSAMessageBrokerMessage();

  arg.MessageName = "ForPage1";
  arg.MessageBody = "Message For Page 1";

  // Send a message to Page 1
  (Application.Current as App).MessageBroker.SendMessage(arg);
}

Since the MessageName matches what is in the ucPage1 MessageReceived event procedure, ucPage1 can do anything in response to that event. It is important to note that when the message gets sent it is sent to all MessageReceived event procedures, not just the one that is looking for a message called “ForPage1”. If the user control ucPage1 is not loaded and this message is broadcast, but no other code is listening for it, then it is simply ignored.

Remove Event Handler

In each class where you add an event handler to the MessageReceived event you need to make sure to remove those event handlers when you are done. Failure to do so can cause a strong reference to the class and thus not allow that object to be garbage collected. In each of your user control’s make sure in the Unloaded event to remove the event handler.

private void UserControl_Unloaded(object sender,
 RoutedEventArgs e)
{
  if (_MessageBroker != null)
    _MessageBroker.MessageReceived -=
        _MessageBroker_MessageReceived;
}

Problems with Message Brokering

As with most “global” classes or classes that hook up events to other classes, garbage collection is something you need to consider. Just the simple act of hooking up an event procedure to a global event handler creates a reference between your user control and the message broker in the App class. This means that even when your user control is removed from your UI, the class will still be in memory because of the reference to the message broker. This can cause messages to still being handled even though the UI is not being displayed. It is up to you to make sure you remove those event handlers as discussed in the previous section. If you don’t, then the garbage collector cannot release those objects.

Instead of using events to send messages from one object to another you might consider registering your objects with a central message broker. This message broker now becomes a collection class into which you pass an object and what messages that object wishes to receive. You do end up with the same problem however. You have to un-register your objects; otherwise they still stay in memory. To alleviate this problem you can look into using the WeakReference class as a method to store your objects so they can be garbage collected if need be. Discussing Weak References is beyond the scope of this post, but you can look this up on the web.

Summary

In this blog post you learned how to create a simple message broker system that will allow you to send messages from one object to another without having to reference objects directly. This does reduce the coupling between objects in your application. You do need to remember to get rid of any event handlers prior to your objects going out of scope or you run the risk of having memory leaks and events being called even though you can no longer access the object that is responding to that event.

NOTE: You can download the sample code for this article by visiting my website at http://www.pdsa.com/downloads. Select “Tips & Tricks”, then select “A Communication System for XAML Applications” from the drop down list.

A WPF Message Box you can Style

You go to great pains to add styles, colors, gradients, and a really cool look and feel to your WPF application only to have that ruined by the standard Windows message box as shown in Figure 1.

Figure 1: The normal Windows message box just does not look right on a styled WPF application.

Figure 1: The normal Windows message box just does not look right on a styled WPF application.

What would be nice is if Microsoft offered a styled message box. But, they don’t. So it is up to us to create a window that we can style and do whatever we want with it. Thus, I can up with a message box that looks like Figure 2 and Figure 3.

Figure 2: Creating your own dialog from a Window, a Text Block and a custom button control.

Figure 2: Creating your own dialog from a Window, a Text Block and a custom button control.

Figure 3: You can completely change the theme of your button through the use of resource dictionaries.

Figure 3: You can completely change the theme of your button through the use of resource dictionaries.

The PDSAMessageBoxView XAML

The first step in creating a custom message box is to add a new Window to your WPF project. You then need to set some styles to get a border-less window. You set the following attributes on your Window.

WindowStyle="None"
ShowInTaskbar="True"
ResizeMode=”NoResize”
AllowsTransparency="True"
Background="Transparent"

The WindowStyle attribute normally allows you to set a single border, three-D border, or a Tool Window border. Setting this attribute to None will eliminate the border. The ShowInTaskbar attribute is optional, but if you are doing a dialog window you probably want this window to show up in the Task Bar. Since this is a dialog window, you probably do not want to allow the user to resize this window, thus you set the ResizeMode to “NoResize”. The next two attributes, AllowsTransparency and Background work together. You must set AllowsTransparency to True to allow the Background to be set to Transparent. If you do not set these two attributes, then your border will still show up.

The listing that follows shows the complete XAML for the PDSAMessageBoxView.xaml file. This XAML file contains a Border, a Grid, a TextBlock for the message, and a StackPanel control with four PDSAucButton controls (see my previous blog post on how to create these buttons). All of the attributes for the border, the text block and the buttons are controlled via styles in a Resource Dictionary file. Using a resource dictionary allows you to create new resource dictionaries with different colors and other attributes to style the message box in any manner you see fit.

<Window x:Class="PDSA.WPF.PDSAMessageBoxView"
        ...
        xmlns:my="clr-namespace:PDSA.WPF"
        WindowStyle="None"
        AllowsTransparency="True"
        Background="Transparent"
        ResizeMode="NoResize"
        ShowInTaskbar="True"
        FontFamily="Segoe UI"
        WindowStartupLocation="CenterScreen"
        Height="300"
        Width="420"
        MouseLeftButtonDown="Window_MouseLeftButtonDown"
        Deactivated="Window_Deactivated" >
  <Window.Resources>
    <!-- Set style for PDSA Button -->
    <Style TargetType="my:PDSAucButton">
      <Setter Property="Effect"
       Value="{StaticResource pdsaMessageBoxButtonShadow}" />
      <Setter Property="Width"
              Value="80" />
    </Style>
  </Window.Resources>
  <Border Style="{StaticResource pdsaMessageBoxBorder}">
    <Grid>
      <Grid.RowDefinitions>
        <RowDefinition Height="*" />
        <RowDefinition Height="Auto" />
      </Grid.RowDefinitions>
      <TextBlock Name="tbMessage"
         Style="{StaticResource pdsaMessageBoxTextBlock}"
         Text="Message goes here..."
         TextWrapping="Wrap" />
      <StackPanel Grid.Row="1"
          Style="{StaticResource pdsaMessageBoxStackPanel}">
        <my:PDSAucButton Text="Yes"
                         x:Name="btnYes"
                         Click="btnYes_Click" />
        <my:PDSAucButton Text="No"
                         x:Name="btnNo"
                         Click="btnNo_Click" />
        <my:PDSAucButton Text="OK"
                         x:Name="btnOk"
                         Click="btnOk_Click" />
        <my:PDSAucButton Text="Cancel"
                         x:Name="btnCancel"
                         Click="btnCancel_Click" />
      </StackPanel>
    </Grid>
  </Border>
</Window>

Styles for PDSAMessageBox Class

As mentioned previously, all of the styles for the PDSAMessageBoxView class are contained within a resource dictionary. There is one resource dictionary in the PDSA.WPF DLL where the PDSAMessageBoxView class is located. However, there is an additional resource dictionary in the main project with a blue theme as shown in Figure 3. The listing below is the complete resource dictionary for the gray message box theme.

<ResourceDictionary ... >
  <!-- Background for Message Box -->
  <LinearGradientBrush x:Key="pdsaMessageBoxBackground"
                       StartPoint="0.5,0"
                       EndPoint="0.5,1">
    <GradientStop Color="DarkSlateGray"
                  Offset="0" />
    <GradientStop Color="LightGray"
                  Offset="0.70" />
    <GradientStop Color="DarkSlateGray"
                  Offset="1" />
  </LinearGradientBrush>
  <!-- Drop Shadow Effect for Message Box -->
  <DropShadowEffect Color="Black"
                    x:Key="pdsaMessageBoxDropShadow"
                    Opacity=".85"
                    ShadowDepth="16" />
  <!-- Drop Shadow for Message Box Buttons -->
  <DropShadowEffect x:Key="pdsaMessageBoxButtonShadow"
                    Color="Black"
                    ShadowDepth="8" />
  <!-- Border for Message Box -->
  <Style TargetType="Border"
         x:Key="pdsaMessageBoxBorder">
    <Setter Property="CornerRadius"
            Value="10" />
    <Setter Property="BorderBrush"
            Value="Black" />
    <Setter Property="BorderThickness"
            Value="3" />
    <Setter Property="Margin"
            Value="16" />
    <Setter Property="Effect"
        Value="{StaticResource pdsaMessageBoxDropShadow}" />
    <Setter Property="Background"
        Value="{StaticResource pdsaMessageBoxBackground}" />
  </Style>
  <!-- Buttons for Message Box -->
  <Style TargetType="Button"
         x:Key="pdsaMessageBoxButton">
    <Setter Property="Margin"
            Value="10" />
    <Setter Property="Width"
            Value="70" />
    <Setter Property="Height"
            Value="35" />
    <Setter Property="FontSize"
            Value="14" />
    <Setter Property="FontWeight"
            Value="SemiBold" />
    <Setter Property="Effect"
       Value="{StaticResource pdsaMessageBoxButtonShadow}" />
  </Style>
  <!-- Text Block for Message Box -->
  <Style TargetType="TextBlock"
         x:Key="pdsaMessageBoxTextBlock">
    <Setter Property="Foreground"
            Value="Black" />
    <Setter Property="Margin"
            Value="20,40,20,10" />
    <Setter Property="FontSize"
            Value="28" />
    <Setter Property="TextWrapping"
            Value="Wrap" />
  </Style>
  <!-- StackPanel for Message Box -->
  <Style TargetType="StackPanel"
         x:Key="pdsaMessageBoxStackPanel">
    <Setter Property="Orientation"
            Value="Horizontal" />
    <Setter Property="HorizontalAlignment"
            Value="Right" />
    <Setter Property="Margin"
            Value="8" />
  </Style>
</ResourceDictionary>

Feel free to modify any of the above styles to make the message box look like however you want.

The PDSAMessageBox Class

Just like .NET has the MessageBox class in order to display the dialog, I have created a PDSAMessageBox class with a Show method as well. I made the Show method with the same parameters as the MessageBox class in order to help you make an easier transition to the PDSAMessageBox class. However, I do not use any of the MessageBoxImage parameters, so you will need to remove any of these, or add an image to this view if you so desire. Below is the PDSAMessageBox class with the static Show methods defined.

public class PDSAMessageBox
{
  public static MessageBoxResult Show(string message)
  {
    return Show(message, string.Empty, MessageBoxButton.OK);
  }

  public static MessageBoxResult Show(string message,
    string caption)
  {
    return Show(message, caption, MessageBoxButton.OK);
  }

  public static MessageBoxResult Show(string message,
    string caption, MessageBoxButton buttons)
  {
    MessageBoxResult result = MessageBoxResult.None;
    PDSAMessageBoxView dialog = new PDSAMessageBoxView();

    dialog.Title = caption;
    dialog.tbMessage.Text = message;
    dialog.Buttons = buttons;
    // If just an OK button, allow the user to just
    // move away from the dialog
    if (buttons == MessageBoxButton.OK)
      dialog.Show();
    else
    {
      dialog.ShowDialog();
      result = dialog.Result;
    }

    return result;
  }
}

All of the Show methods end up calling just a single Show that takes care of displaying the PDSAMessageBoxView dialog. One thing I did change in my Show method is that if the user pops up a message box with just an “Ok” button, I do allow the user to navigate away from this dialog without pressing the Ok button. To me, it does not make sense to force the user to press a single button on the form. This could have side-effects however, if you have code immediately following the call to the Show method because this is no longer a modal dialog. Again, feel free to modify this code if you do not like this functionality.

Calling the PDSAMessageBox

Below is an example of calling the PDSAMessageBox. The Show method will return a MessageBoxResult enumeration. There was no need to change the return value from that of the normal MessageBox class. This code can be found in the sample that you download along with this blog post.

private void btnOKOnly_Click(object sender, RoutedEventArgs e)
{
  PDSAMessageBox.Show("This just displays an OK Button",
     "OK", MessageBoxButton.OK);
}

private void btnYesNo_Click(object sender, RoutedEventArgs e)
{
  MessageBoxResult result;

  result = PDSAMessageBox.Show("Do you want to Quit?",
      "Quit?", MessageBoxButton.YesNo);

  MessageBox.Show("Result is " + result.ToString());
}

Summary

Creating your own dialog box is very easy using just a little bit of XAML, some styles and a class with a few methods. Break up your styles into resource dictionaries in order to have the flexibility to modify the look and feel of your dialogs. Keep your dialog boxes and buttons in a separate DLL in order to make it easy to reuse your controls.

NOTE: You can download the sample code for this article by visiting my website at http://www.pdsa.com/downloads. Select “Tips & Tricks”, then select “A WPF Message Box you can Style” from the drop down list.

Posted: Aug 13 2012, 12:39 PM by psheriff | with 1 comment(s)
Filed under: ,
Create your own WPF Button User Controls

In Figure 1 you can see examples of the standard WPF Button controls. You can add a drop shadow and you can change the color, but you can’t change much else without creating a whole new control template. For example, you are unable to modify the BorderBrush or the BorderThickness properties of the Button control. Additionally you might want to use some other animation than the default, which again requires you to change the control template.

Sometimes all you want to do is to just have some simple buttons where you can modify the border brush and the thickness and have different color options via styles. I have found that instead of working with the whole control template thing, just creating a User Control is sometimes much easier.

Figure 1: The Normal WPF Buttons can not be styled very well

Figure 1: The normal WPF Buttons cannot be styled very well.

There are many ways to create custom buttons and there are advantages and disadvantages to each way. The purpose of this blog post is to present one method that is easily understood by almost any XAML programmer, and hopefully to those new to XAML as well. User controls have been around since the Visual Basic 4.0 days. Most developers understand the value of using user controls. With XAML user controls you can put these controls into a WPF User Control Library or a Silverlight Class Library and reference those DLLs from any WPF or Silverlight application. This gives you great reusability. By using resource dictionaries and appropriate use of styles you can make your user controls very customizable as shown in Figure 1. The 2nd row of buttons you see are the same button user control with just different styles applied.

The User Control

The XAML for your Button user control is actually very simple. You use a single Border control and a TextBlock control within that Border as shown in Listing 1.

<Border Name="hoverBorder"
        Style="{DynamicResource hoverBorder}"
        MouseDown="hoverBorder_MouseDown"
        MouseUp="hoverBorder_MouseUp"
        MouseEnter="hoverBorder_MouseEnter"
        MouseLeave="hoverBorder_MouseLeave"
        MouseLeftButtonUp="hoverBorder_MouseLeftButtonUp">
  <TextBlock Text="{Binding Path=Text}"
             Name="tbText"
             Style="{DynamicResource hoverTextBlock}" />
</Border>

Listing 1: The XAML for a Button user control is just a Border and TextBlock

The definition for this user control is in a DLL named PDSA.WPF. Notice that there are Style definitions for both the Border and the TextBlock. We used styles contained in a resource dictionary as opposed to setting up dependency properties for each individual attribute that we might want to set. This allows us to create a few different resource dictionaries, each with a different theme for the buttons. You can see the three different themes we created in Figure 1. The Gray Style button uses a resource dictionary that is contained in the PDSA.WPF DLL. The other two styles are in the main project and can be referenced from your App.xaml or from within the Window/User Control where you need the button.

Changing Colors in Response to Mouse Events

When a user moves over a button or presses a button you should give some visual feedback to that user. You can do this with animation using the Visual State Manager or Event Triggers in WPF. To keep things simple for this blog post, I simply respond to the various mouse events and change the Background property to a different color. When the mouse is pressed, I also change the Foreground color of the Text in the TextBlock control. The code for each of the mouse events is shown below.

private void pdsaButtonBorderStyle_MouseEnter(
 object sender, MouseEventArgs e)
{
  pdsaButtonBorderStyle.Background =
   (Brush)this.FindResource("pdsaButtonOverStyle");
}

private void pdsaButtonBorderStyle_MouseLeave(
 object sender, MouseEventArgs e)
{
  pdsaButtonBorderStyle.Background =
   (Brush)this.FindResource("pdsaButtonNormalStyle");
}

private void pdsaButtonBorderStyle_MouseDown(
 object sender, MouseButtonEventArgs e)
{
  // Save old Foreground Brush
  _TextBrush = tbText.Foreground;

  pdsaButtonBorderStyle.Background =
   (Brush)this.FindResource("pdsaButtonPressedStyle");
  tbText.Foreground =
   (SolidColorBrush)this.FindResource(
     "pdsaButtonTextBlockStylePressed");
}

private void pdsaButtonBorderStyle_MouseUp(
 object sender, MouseButtonEventArgs e)
{
  RestoreNormal();
}

private void RestoreNormal()
{
  pdsaButtonBorderStyle.Background =
   (Brush)this.FindResource("pdsaButtonNormalStyle");

  tbText.Foreground = _TextBrush;
}

Notice that in the code above that you use the FindResource() method instead of accessing the this.Resources[] collection. That is because you want to be able to set the resource dictionary at any level, not just on this user control. FindResource() will search upward through your UI tree looking for a resource that match the names you see that start with “pdsa”.

You will need a Click event that you can raise when the user clicks on the button. Here is the definition for that Click event.

private void pdsaButtonBorderStyle_MouseLeftButtonUp(
 object sender, MouseButtonEventArgs e)
{
  RaiseClick(e);
}

public delegate void ClickEventHandler(object sender,
 RoutedEventArgs e);

public event ClickEventHandler Click;

protected void RaiseClick(RoutedEventArgs e)
{
  if (null != Click)
    Click(this, e);

  RestoreNormal();
}

The Default Resource Dictionary

Below is the definition of the resource dictionary file contained in the PDSA.WPF DLL. This dictionary can be used as the default look and feel for any button control you add to a window. I have included two additional resource dictionaries in the main project as examples of how you can change the resources to give your buttons a different look. You need to keep the x:Key names the same, but you can change any of the attributes of color or thickness that you want. You can even change from Gradient colors to a SolidColorBrush as you can see I did when you look at the different resource dictionaries.

<ResourceDictionary xmlns="http://schemas.microsoft.com/
                           winfx/2006/xaml/presentation"
    xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml">
  <!-- Hover TextBlock Style -->
  <Style TargetType="TextBlock"
         x:Key="pdsaButtonTextBlockStyle">
    <Setter Property="Foreground"
            Value="GhostWhite" />
    <Setter Property="Margin"
            Value="10" />
    <Setter Property="FontSize"
            Value="14" />
  </Style>
  <!-- Hover Brush for Pressed Hover Button Text Block -->
  <SolidColorBrush x:Key="pdsaButtonTextBlockStylePressed"
                   Color="Black" />
  <!-- Hover Border Thickness -->
  <Thickness x:Key="pdsaButtonBorderStyleThickness"
             Bottom="0"
             Left="0"
             Right="0"
             Top="0" />
  <!-- Hover Border Brush -->
  <SolidColorBrush x:Key="pdsaButtonBorderBrushStyle"
                   Color="Black" />
  <!-- Style for when hovering over button -->
  <RadialGradientBrush x:Key="pdsaButtonOverStyle">
    <GradientStop Color="#FF5F5F5F"
                  Offset="0" />
    <GradientStop Color="#FFADADAD"
                  Offset="1" />
  </RadialGradientBrush>
  <!-- Style for when button is pressed -->
  <LinearGradientBrush EndPoint="1,0.5"
                       StartPoint="0,0.5"
                       x:Key="pdsaButtonPressedStyle">
    <GradientStop Color="#FF5F5F5F"
                  Offset="0" />
    <GradientStop Color="#FFADADAD"
                  Offset="1" />
  </LinearGradientBrush>
  <!-- Style for normal button -->
  <LinearGradientBrush EndPoint="0.5,1"
                       StartPoint="0.5,0"
                       x:Key="pdsaButtonNormalStyle">
    <GradientStop Color="#FF5F5F5F"
                  Offset="0" />
    <GradientStop Color="#FFADADAD"
                  Offset="1" />
  </LinearGradientBrush>
  <!-- Overall Style for Hover Button -->
  <Style TargetType="Border"
         x:Key="pdsaButtonBorderStyle">
    <Setter Property="Margin"
            Value="6" />
    <Setter Property="CornerRadius"
            Value="5" />
    <Setter Property="Background"
            Value="{StaticResource pdsaButtonNormalStyle}" />
    <Setter Property="BorderBrush"
            Value="{StaticResource
                    pdsaButtonBorderBrushStyle}" />
    <Setter Property="BorderThickness"
            Value="{StaticResource
                    pdsaButtonBorderStyleThickness}" />
  </Style>
</ResourceDictionary>

You can look at the other two Resource Dictionary files when you download the sample.

Using the Button Control

Once you make a reference to the PDSA.WPF DLL from your WPF application you will see the “PDSAucButton” control appear in your Toolbox. Drag and drop the button onto a Window or User Control in your application. I have not referenced the PDSAButtonStyles.xaml file within the control itself so you do need to add a reference to this resource dictionary somewhere in your application such as in the App.xaml.

<Application.Resources>
  <ResourceDictionary>
    <ResourceDictionary.MergedDictionaries>
      <ResourceDictionary
        Source="/PDSA.WPF;component/PDSAButtonStyles.xaml" />
    </ResourceDictionary.MergedDictionaries>
  </ResourceDictionary>
</Application.Resources>

This will give your buttons a default look and feel unless you override that dictionary on a specific Window/User Control or on an individual button. The “Gray Style” button shown in Figure 1 is what the default button looks like after setting the above code in your App.xaml and then dragging a button onto a Window.

If you wish to give a specific style to just a single button you can override the default by using the code below:

<my:PDSAucButton HorizontalAlignment="Left"
                  Margin="6"
                  Text="Flat Blue Style"
                  x:Name="btn2"
                  Click="btn2_Click"
                  VerticalAlignment="Top">
  <my:PDSAucButton.Resources>
    <ResourceDictionary Source="FlatBlueButtonStyles.xaml" />
  </my:PDSAucButton.Resources>
</my:PDSAucButton>

If you want to override a series of buttons within one specific StackPanel, or within a specific Window or User Control, you simply set the Resources section for that control as shown below:

<StackPanel Orientation="Horizontal"
            VerticalAlignment="Top">
  <!-- Set Styles for All Buttons.
       This overrides the App.xaml styles -->
  <StackPanel.Resources>
    <ResourceDictionary Source="FlatBlueButtonStyles.xaml" />
  </StackPanel.Resources>
  <my:PDSAucButton HorizontalAlignment="Left"
                    Margin="6"
                    Text="Blue Style"
                    x:Name="btn11"
                    Effect="{StaticResource mainDropShadow}"
                    VerticalAlignment="Top" />
  <!-- Can assign a custom set of styles -->
  <my:PDSAucButton HorizontalAlignment="Left"
                    Margin="6"
                    Text="Flat Blue Style"
                    x:Name="btn12"
                    VerticalAlignment="Top" />
</StackPanel>

In the above sample, all buttons within this StackPanel control are styled using the resource dictionary FlatBlueButtonStyles.xaml.

Summary

Creating your own button control can be done in a variety of ways. You can create a new Button control template, apply styles to change just a few things about a Button, create your own Custom control that inherits from Button, or build a User Control as in this blog post. Which method you choose depends a little on how comfortable you are with each method, and how much control you want over the final details of your new Button. The approach outlined in this blog post is simple and easy to understand and almost anyone can use this technique right away. Feel free to add your own animation to this control or maybe add dependency properties to control the BorderBrush, BorderThickness or any other properties you want.

NOTE: You can download the sample code for this article by visiting my website at http://www.pdsa.com/downloads. Select “Tips & Tricks”, then select “Create your own WPF Buttons” from the drop down list.

 

Posted: Aug 10 2012, 11:54 AM by psheriff | with 1 comment(s)
Filed under: ,
XML Activator

All too often I see people using switch/Select Case statements when using a Factory pattern. The problem with this is if you wish to add the ability to instantiate a new class in your Factory, you need to add a new “case” statement, re-compile the code and redeploy that DLL back out to your client machines, or your server(s). Another way to implement a Factory pattern is to use Reflection and Interfaces to dynamically create an instance of a class. This blog post will show you how to use an XML file, an Interface and the Assembly class to dynamically load a list of assemblies and classes to load into an application at runtime.

An XML File of Assembly/Class Names

You will need a data store to place a list of assembly names and class names that you wish to dynamically create. You could use a database table, but I am going to use an XML file that contains the Assembly Name to load and a Class Name to instantiate from that assembly. Any additional information you wish to add, feel free. In the XML listing below you can see a VendorId, VendorName as well as the AssemblyName and ClassName elements.

<Vendors>
  <Vendor>
    <VendorId>1</VendorId>
    <VendorName>Microsoft</VendorName>
    <AssemblyName>Vendor.Microsoft</AssemblyName>
    <ClassName>Vendor.Microsoft</ClassName>
  </Vendor>
  <Vendor>
    <VendorId>2</VendorId>
    <VendorName>Apple</VendorName>
    <AssemblyName>Vendor.Apple</AssemblyName>
    <ClassName>Vendor.Apple</ClassName>
  </Vendor>
  <Vendor>
    <VendorId>3</VendorId>
    <VendorName>Google</VendorName>
    <AssemblyName>Vendor.Google</AssemblyName>
    <ClassName>Vendor.Google</ClassName>
  </Vendor>
</Vendors>

Load the XML File into a Collection Class

When you have an XML file like that shown above, you will typically create a class with properties that match each element in the XML file. Below is a VendorClass that has properties for each element.

public class VendorClass
{
  public int VendorId { get; set; }
  public string VendorName { get; set; }
  public string AssemblyName { get; set; }
  public string ClassName { get; set; }
}

Next you create a collection class using the Generic List<VendorClass> class. In this class there is a LoadVendors() method. This method uses the XElement class to load the Vendors.xml file into memory. LINQ to XML is used to iterate over the XML elements and create a collection of VendorClass objects. This resulting collection of objects is added to the List using the AddRange() method.

public class VendorClasses : List<VendorClass>
{
  public VendorClasses LoadVendors()
  {
    // Load XML file into memory
    var xElem = XElement.Load(
      GetCurrentDirectory() +
      ConfigurationManager.AppSettings["VendorFile"]);

    // Read Vendors using LINQ to XML
    var vendors = from vend in xElem.Descendants("Vendor")
       select new VendorClass
       {
         VendorId =
           Convert.ToInt32(vend.Element("VendorId").Value),
         VendorName = vend.Element("VendorName").Value,
         AssemblyName = vend.Element("AssemblyName").Value,
         ClassName = vend.Element("ClassName").Value
        };

    // Add vendors read in to the List of Vendor classes
    this.AddRange(vendors.ToList());

    return this;
  }
}

To load an XML file you need to find the location on disk. There is a GetCurrentDirectory() method in this class. This method gets the current directory and removes any \bin\Debug from the end of the path if present. This allows you to get the current path whether you are running in Visual Studio or not.

private string GetCurrentDirectory()
{
  string path = null;
     
  path = AppDomain.CurrentDomain.BaseDirectory;
  if (path.IndexOf("\\bin") > 0)
  {
    path = path.Substring(0, path.LastIndexOf("\\bin"));
  }

  if(!path.EndsWith(@"\"))
    path += @"\";

  return path;
}

Create a Vendor Object Dynamically

Now that you have the list of assembly and class names loaded into a collection you can now create an instance of a specific class. In order to do this you must have an Interface or a base class. I have created an Interface called IVendor. This interface will need to be implemented by each Vendor class that you will be dynamically loading. This interface is located in a separate assembly from all other classes and from the main application. This assembly will need to be referenced by the main application as well as from each assembly where your Vendor classes are located.

public interface IVendor
{
  int VendorId { get; set; }
  string VendorName { get; set; }
  string GetVendorInfo();
}

Below is an example of a vendor class named “Apple” that implements the IVendor interface. The GetVendorInfo() method will simply return a string just so we can verify that the class did get created correctly.

public class Apple : IVendor
{
  public int VendorId { get; set; }
  public string VendorName { get; set; }

  public string GetVendorInfo()
  {
    return "Apple Corporation";
  }
}

Below is another example of a vendor class named “Microsoft” that also implements the IVendor interface.

public class Microsoft : IVendor
{
  public int VendorId { get; set; }
  public string VendorName { get; set; }

  public string GetVendorInfo()
  {
    return "Microsoft Corporation";
  }
}

Each of the above classes are located in separate assemblies as you can see by the AssemblyName element in the XML file. These DLLs do NOT need to be referenced from your project, and in fact, should not be. The Create() method shown below uses the Assembly.LoadFile() method to load the assembly from the current directory. That means that the assembly where each Vendor class is located must be in the current directory for this sample. After the DLL has been loaded, you use the CreateInstance() method on the loaded assembly to load the specific class name. Finally you take the VendorId and the VendorName and place it into the Vendor object’s appropriate properties. This newly created Vendor object is returned from this method.

public class VendorClass
{
  // Properties here…
   
  public IVendor Create()
  {
    Assembly assm;
    IVendor vendor = null;

    // Load Assembly File
    assm = Assembly.LoadFile(
      AppDomain.CurrentDomain.BaseDirectory +
       AssemblyName + ".dll");

    // Create new instance of Class
    vendor = (IVendor)assm.CreateInstance(ClassName);

    // Set properties in the Provider class from the XML file
    vendor.VendorId = VendorId;
    vendor.VendorName = VendorName;

    return vendor;
  }
}

The Sample to Test Activation

To test activating each of these vendor classes I have created a WPF application (Figure 1). The Load Vendors button will call the LoadVendors() method to build the collection of VendorClass objects from the XML file.

Figure 1: A sample to test our activation

Figure 1: A sample to test our activation

Below is the code in the btnLoad_Click event procedure that loads the collection of VendorClass objects.

private void btnLoad_Click(object sender, RoutedEventArgs e)
{
  VendorClasses classes = new VendorClasses();

  // Load all vendors in the XML file
  classes.LoadVendors();
  lstVendors.DataContext = classes;
}

Once the VendorClass collection is in the List Box you can click on the list box to fire the SelectionChanged event procedure. In this event procedure you will cast the SelectedItem property of the list box into a VendorClass object. You can then call the Create() method to instantiate the Vendor object by loading the assembly and creating an instance of the Vendor object. After the Vendor object has been created you can call the GetVendorInfo() method to verify that the assembly has been loaded and an instance of the class has been created.

private void lstVendors_SelectionChanged(object sender,
  SelectionChangedEventArgs e)
{
  VendorClass vendor;
  IVendor vend;

  // Get the currently selected vendor
  vendor = (VendorClass)lstVendors.SelectedItem;
  // Create an instance of a Vendor class
  vend = vendor.Create();

  tbVendorName.Text = vend.GetVendorInfo();
}

Summary

In this blog post you learned how to use LINQ to XML to load a collection of classes that contain information about other classes that you will eventually load. You saw how to use the Assembly class to load an assembly from disk and instantiate a class from a string value. An Interface is used to ensure that each class to be instantiated has the same set of methods and properties. Using this technique can eliminate a switch/Select case statement and allow you to dynamically add classes to any application.

NOTE: You can download the sample code for this article by visiting my website at http://www.pdsa.com/downloads. Select “Tips & Tricks”, then select “XML Activator” from the drop down list.

Posted: Aug 01 2012, 06:58 AM by psheriff | with 3 comment(s)
Filed under: , ,
More Posts