I just wanted to set focus to a TextBox on a modal dialog created using the ModalPopupExtender. How hard can it be?

As a Physics and Math major, I spent my student years using logic to attack problems and solve puzzles. So I was a little concerned when my daughters learned the "guess and check" method in Math class. How scientific or logical is that? I later came to appreciate its usefulness in some circumstances. For example, on the SAT (college entrance) tests it may be faster to "guess" each of the multiple choice items as correct and "check" them than to figure out the answer.

What does "guess and check" have to do with ASP.NET? Often it seems that there is no obvious solution to accomplish a specific design requirement. There is no obvious control property, method call or programming logic that achieves the desired result. The "guess and check" method is the only way to go:

  • Guess a key word or two describing what you are trying to accomplish
  • Goggle/Bing
  • Use the results to make a guess on some code
  • Run (F5) to check
  • Repeat for several hours until something miraculously works

This was definitely the case for what seemed like such a simple task: set the focus to a control in a modal dialog. It's just control.focus(), right? Yes, but where would you put this code?

The problem here is that the modal dialog is initially hidden. You can't set focus to the control when the page is loaded. Rather, you need to set the focus when the modal dialog is shown. There are no onShown or onVisible attributes for a panel/div/span. So on to "guess and check".

Because the vast majority of the sample code I found via Bing that was supposed to provide a solution for this did not work (at least not for me), here is a solution that did work.

1) Follow the steps here to define a modal dialog on one of your Web pages.

2) Add the BehaviorId attribute to the ModalPopupExtender in the ASP.NET page. The BehaviorId provides a mechanism to access the extender from script code.

In HTML:

<!-- Define the modal dialog -->
<asp:ModalPopupExtender ID="SecurityQuestionModalPopupExtender" runat="server"
  BehaviorID="SecurityQuestionModalBehavior"
  TargetControlID="SecurityQuestionLink"
  PopupControlID="SecurityQuestionModalPopup"
  CancelControlID="CancelButton"
  OnCancelScript="OnSecurityQuestionCancel();"/>

3) Add this script code to your ASP.NET page:

In java script:

<script language="javascript" type="text/javascript">
    /* On page load */
    function pageLoad() {
        var modalPopup = $find("SecurityQuestionModalBehavior");
        modalPopup.add_shown(OnPopupShow);
    }

    /* Set the focus to the correct control */
    function OnPopupShow() {
        var tb = $get("QuestionTextBox");
        tb.focus();
    }

</script>

The first function executes when the page is loaded. Since the ModalPopupExtender has a BehaviorId, you can use the $find method to find it. You can then use the add_shown method and pass it a function to call when the modal popup is shown.

The OnPopupShow function then sets the focus when the modal popup is shown by finding the desired control and setting focus to it.

The result:

image

Yea, it seems like this add_shown method was pulled out of thin air. Without Bing, there is no way I would have found this method. It is not in any documentation I have read nor does it appear in intellisense.

The only place I was able to find any documentation (if you would call it that) is in the ModalPoupBehavior.js file provided with the Ajax toolkit source code. Is it just me or is it TOTALLY LAME to have to read the Ajax source code to get something this basic to work?

NOTE: Most of the examples I found during my "guess and check" used add_showing instead of add_shown. This generated the following error during my "check" phase: "Can't move focus to the control because it is invisible, not enabled, or of a type that does not accept the focus."

If you need to write this code in VB/C# code behind instead of javascript in the ASP.NET page, see this link.

Use this technique any time you need to set focus to one of the controls on a modal dialog. And use the "guess and check" method every time you need to do anything beyond the basics with ASP.NET.

Enjoy!

with 2 comment(s)
Filed under: , , , ,

In my last post, I demonstrated how to build a modal dialog using the AJAX control in ASP.NET. As soon as you add validation to your modal dialog you will find the same issue that I did: Cancelling the dialog does not clear the content or validation messages.

Take this user scenario for example.

1) User clicks Security Question to open the security question dialog.

2) User enters the question, but no answer and clicks OK.

image

3) User clicks Cancel.

image

4) Sometime later on this same page, the user clicks Security Question again.

image

Notice that the entered text and validation message are still there.

Yes, this may be a far fetched scenario, but as you work with the ModalPopupExtender, you will see this issue come up again and again. In some cases, leaving the prior entered value may be OK. But in other cases, you want to open this modal dialog in a reset state.

There are several ways to attack this problem and this post covers two of them:

  • Use server-side code in the Cancel button to clear the dialog.
  • Use client-side script for the Cancel button to clear the dialog.

Using Server-Side Code

Using server-side script requires the following steps:

1) Remove the CancelControlID from the ModalPopupExtender control in the ASP.NET code. This allows the Cancel button to be processed by the server-side code.

2) Add a Click event handler for the Cancel button. Follow a technique similar to the OK button in my last post to set up the event handler.

NOTE: For C# ONLY, be sure to add the onclick attribute:

<asp:Button ID="CancelButton" runat="server"
    Text="Cancel" ToolTip="Click to cancel any changes" 
    CausesValidation="false"
    onclick="CancelButton_Click"/>

3) Add the desired code to the Cancel button Click event handler in the code behind file.

In C#:

Be sure to set a reference to the System.Web.UI namespace.

protected void CancelButton_Click(object sender, EventArgs e)
{
    // Clear the validators
    foreach (IValidator ctrl in Validators)
    {
        ctrl.IsValid=true;
    }

    // Clear the control contents
    QuestionTextBox.Text = String.Empty;
    AnswerTextBox.Text = String.Empty;

    //Close the popup
    SecurityQuestionModalPopupExtender.Hide();
}

In VB:

Private Sub CancelButton_Click(ByVal sender As Object, _
         ByVal e As System.EventArgs) Handles CancelButton.Click
    ' Clear the validators
    For Each ctrl As IValidator In Validators
        ctrl.IsValid = True
    Next

    ' Clear the control contents
    QuestionTextBox.Text = String.Empty
    AnswerTextBox.Text = String.Empty

    ' Close the popup
    SecurityQuestionModalPopupExtender.Hide()
End Sub

This code first clears any validation controls. It then clears the text from both of the TextBoxes. Finally, it closes the modal dialog.

The down side of this approach is that it is server side. This means that it requires hitting the server to clear the dialog. A more performant approach is to use client-side scripting.

Using Client-Side Code

Using client-side script requires the following steps:

1) Ensure the ModalPopupExtender has both the CancelControlID and OnCancelScript attributes set:

<asp:ModalPopupExtender ID="SecurityQuestionModalPopupExtender"
   runat="server"
  TargetControlID="SecurityQuestionLink"
  PopupControlID="SecurityQuestionModalPopup"
  CancelControlID="CancelButton"
  OnCancelScript="OnSecurityQuestionCancel();"/>

The OnCancelScript attribute must be set to the name of the javascript function defined in the next step.

2) Write the javascript code:

<script language="javascript" type="text/javascript">
    /* On cancel of the Signin dialog, clear the fields */
    function OnSecurityQuestionCancel() {
        $get("QuestionTextBox").value = "";
        $get("AnswerTextBox").value = "";
        $get("QuestionValidator").innerHTML = "";
        $get("AnswerValidator").innerHTML = "";
    }
</script>

For this example, I added this javascript code to the head tag of the ASP.NET page.

This code uses the Ajax $get shortcut for the getElementById to find both TextBoxes and both validators. It then clears them. Because this is javascript, it has to work with the controls that are rendered. For the TextBox controls, these are HTML input controls that have a value attribute. For the validation controls, these are the HTML span controls. To clear those, you need to clear the innerHTML attribute.

Use one of these techniques any time you want to clear a modal dialog.

Enjoy!

with no comments
Filed under: , , ,

There are often times when your Web design includes a modal dialog. For example:  a login form, a dialog for entering basic information, a search form, or a data entry form. This post covers how to build a modal dialog in ASP.NET.

There are several advantages to using a modal dialog instead of another Web page:

  • The user does not have to navigate away from the current page. This allows the user to focus on the main information, yet enter criteria or other parameters in a modal dialog.
  • The code has better control over the dialog operations. That is to say that the user must provide some response to the modal dialog. However, the user can still navigate to another page or close the browser.

I mentioned in a recent post that after being away from ASP.NET for a while, I was surprise at how little had changed. But there is one area where ASP.NET has changed significantly: AJAX. If you have not downloaded the ASP.NET AJAX  toolkit, check it out here.

This post focuses on the ModalPopupExtender control that is part of the AJAX toolkit. In this example, the user sets a security question and answer in a modal dialog. If the user clicks on the Security Question link in the upper right corner, the Security Question modal dialog appears for entry of the question and answer.

image

As with most modal dialogs, this one implements validation. If the user clicks on OK without entering the required data, validation messages appear:

image

I wrote this example in both VB and C#. But since the only line of code that is different is the Page tag that defines the language, I am only showing the ASP.NET code one time.

In HTML:

<%@ Page Language="C#" AutoEventWireup="true" CodeBehind="Default.aspx.cs" Inherits="SampleWebCSharp._Default" %>
<%@ Register Assembly="AjaxControlToolkit" Namespace="AjaxControlToolkit" TagPrefix="asp" %>

<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">

<html xmlns="http://www.w3.org/1999/xhtml" >
<head id="Head1" runat="server">
    <title>Sample Web Page</title>
</head>

<body style="background-image: url(Images/Gradient2.jpg);background-repeat: repeat-x;">
    <form id="form1" runat="server">
        <asp:ScriptManager ID="sm" runat="server"/>


        <!-- Right side Link bar --> 
        <div style="text-align:right"> 
            <!-- Signin Area --> 
            <asp:HyperLink ID="SigninLink" runat="server" 
                style="color:White;cursor:pointer" 
                ToolTip="Click to sign into the application" 
                Text="Sign in" /> 
            <label style="margin-left:8px;
                   margin-right:8px;color:White">|</label> 
            <asp:HyperLink ID="SecurityQuestionLink" 
                runat="server" 
                style="color:White;cursor:pointer" 
                ToolTip="Click to set the security question" 
                Text="Security Question" /> 
        </div> 
         <!-- Security Question Popup --> 
        <asp:Panel ID="SecurityQuestionModalPopup" 
            runat="server" 
            Style="display:none;width:300px;
                   background-color:White; 
                   border-width:3px;border-style:solid;
                   border-color:#B92217;color:#7C1810;
                   padding:3px;"> 
            <asp:Label ID="Label1" runat="server" 
                Text="Security Question" width="300px"/><br /> 
            <br /> 
            <table style="width: 100%;"> 
                <tr> 
                    <td align="right"> 
                        <asp:Label ID="Label2" 
                         runat="server" 
                         Text="Question:"/> 
                    </td> 
                    <td> 
                        <asp:TextBox ID="QuestionTextBox" 
                         runat="server" Width="150"/> 
                    </td> 
                </tr> 
                <tr> 
                    <td align="right"> 
                        <asp:Label ID="Label3" 
                         runat="server" 
                         Text="Answer:"/>
                    </td> 
                    <td> 
                        <asp:TextBox ID="AnswerTextBox" 
                         runat="server" Width="150"/> 
                    </td> 
                </tr> 
                <tr> 
                    <td colspan="2"> 
                        <asp:RequiredFieldValidator 
                          ID="QuestionValidator" 
                          runat="server" 
                          ControlToValidate="QuestionTextBox" 
                          Display="Dynamic" 
            ErrorMessage="Please enter your security question.<br/>"/> 
                      <asp:RequiredFieldValidator 
                          ID="AnswerValidator" 
                          runat="server"
                          ControlToValidate="AnswerTextBox" 
                          Display="Dynamic" 
            ErrorMessage="Please enter the answer to your security question.<br/>"/> 
                    </td> 
                </tr> 
           </table> 
            <br /> 
            <!-- Bottom Buttons --> 
            <div style="text-align:right"> 
                <asp:Button ID="OKButton" runat="server" 
                Text="OK" 
                ToolTip="Click to save the entered question/answer"/>  
                 <asp:Button ID="CancelButton" runat="server" 
                Text="Cancel"

CausesValidation="false"
                ToolTip="Click to cancel any changes"/> 
            </div> 
        </asp:Panel>

        <!-- Define the modal dialog --> 
        <asp:ModalPopupExtender 
            ID="SecurityQuestionModalPopupExtender" runat="server" 
            TargetControlID="SecurityQuestionLink" 
            PopupControlID="SecurityQuestionModalPopup" 
            CancelControlID="CancelButton" /> 
    </form>
</body>
</html>

Notice at the very top of the code the Register tag registers the Ajax Control Toolkit. This line is added to your code automatically if you drag an Ajax control onto your WebForm.

The body tag uses a gradient image as shown in this prior post.

The ScriptManager control manages all of the Ajax features and is required on any page that uses Ajax controls.

The first div element contains the code to display the links in the upper right corner. No special code is required on these elements. The HyperLink control does not have an attribute to display the modal dialog. Rather, the ModalPopupExtender identifies the control that will cause it to popup. (More on this later in this post.)

The Panel control contains all of the controls that appear in the modal dialog. It also defines the style of the popup including its background color, foreground color, and border. Notice that the display style is set to none. This ensures that the popup does not display when the page is first rendered.

In this example, the modal dialog contains a title at the top, a table to layout the Labels and TextBoxes in the middle, and OK and Cancel buttons on the bottom. Notice that the Cancel button has its CausesValidation attribute set to false. This ensures that pressing the Cancel button won't perform the validation and display the validation messages.

An additional row in the table provides an area to display validation messages. The validation controls have their Display attribute set to Dynamic so that space is not allocated to the message text unless there is a validation error.

Finally, the key part of this code: the ModalPopupExtender. The key attributes of this control in this example are:

  • TargetControlID: Id of the control that causes display of the modal dialog. In this case, it is the Security Question Hyperlink control at the top right of the page.
  • PopupControlID: Id of the control that is the modal dialog. In this case, it is the Panel control.
  • CancelControlID: Id of the button on the modal dialog that causes a cancel operation. By default, this closes the modal dialog.

Notice that the OKControlID attribute is not set for this control. By default, setting the OKControlID also closes the modal dialog, so validation messages won't appear.

If you don't set the OKControlID attribute, you can instead write code for the OK button in the code behind. This allows for the display of the validation messages. You can also perform processing, such as additional server-side validation or saving the entered values.

In C#:

using System;

namespace SampleWebCSharp
{
    public partial class _Default : System.Web.UI.Page
    {

        protected void OKButton_Click(object sender, EventArgs e)
        {
            // Do any additional processing, such as saving the values

            //Close the popup
            SecurityQuestionModalPopupExtender.Hide();
        }

    }
}

In VB:

Partial Public Class _Default
    Inherits System.Web.UI.Page

    Private Sub OKButton_Click(ByVal sender As Object, _
               ByVal e As System.EventArgs) Handles OKButton.Click
        ' Do any additional processing, such as saving the values

        ' Close the popup
        SecurityQuestionModalPopupExtender.Hide()
    End Sub
End Class

NOTE: The technique you use to set up the events is very different in VB than in C#.

In C#:

  • Double-click on the control.
    In this example, this is not an easy task because the panel containing the button is hidden. So to use this method, you would need to temporarily make the panel not hidden, double-click on the button, and make the panel hidden again.
  • OR, manually type in the event signature in the code behind file.

With the first technique, Visual Studio automatically changes the Button attributes in your ASP.NET code as follows:

<asp:Button ID="OKButton" runat="server"
     Text="OK" ToolTip="Click to save the entered question/answer"
     onclick="OKButton_Click"/>

With the second technique, you need to add the onclick attribute yourself.

In VB:

  • Double-click on the control.
    Again, not so easy because the control is hidden.
  • OR, open the code behind file and use the two ComboBoxes at the top of the code editor. Select the control from the first ComboBox on the left. Select the event from the second ComboBox on the right. This is the best technique to use in this example.

NOTE: VB does NOT require a change in the ASP.NET code.

Use this technique any time you want to display a modal dialog from your Web page.

Enjoy!

with 2 comment(s)
Filed under: , , , ,

Creating nice looking buttons for ASP.NET is still in the category of "I can't believe it is STILL this hard".

As more and more normal people use great looking Web sites, Silverlight apps and even their IPhones, they experience some really nice user interfaces. I'd like to do those things in my Web application as well. I don't want my Web pages looking like VB3 gray looking Windows forms.

So I got to the point where I needed a Login button on my form. Hmmm. How do I build a nice looking button in ASP.NET?

After extensive research, work with Bing, and some trial and error, I came to these choices:

image

As you could tell, I am going with a Red/White color scheme for the Web application, you could pick other colors.

The ASP.NET source code I used to display this list of buttons is as follows:

In HTML:

<!-- Normal buttons-->
<asp:Button ID="Button1" runat="server"
    Text="Button1" /><asp:Label runat="server" Text=" <- Standard Button" /><br /><br />
<asp:Button ID="Button10" runat="server"
    Text="Button1" forecolor="#7C1810"/><asp:Label runat="server" Text=" <- Standard Button with Colored Text" /><br /><br />
<asp:Button ID="Button11" runat="server"
    Text="Button1" forecolor="#7C1810" BorderColor="#7C1810"/><asp:Label runat="server" Text=" <- Standard Button with Border" /><br /><br />

The first set of buttons are standard ASP.NET buttons. Notice the following:

  • The simple standard button looks a standard WinForms button and is not very exciting. It does, however, have a built in roll-over effect that you can see if you put one in your code.
  • Adding a font color adds a little something while still retaining the built in roll-over effect.
  • As soon as you add a border to the button, the button becomes more square and flat, and you lose the roll-over effect.

In HTML:

<asp:Button ID="Button2" runat="server"
    Text="Button1" BackColor="#B92217" ForeColor="White"/><asp:Label runat="server" Text=" <- Standard Button with Backcolor" /><br /><br />
<asp:Button ID="Button3" runat="server"
    Text="Button1" BackColor="#B92217" ForeColor="White"
    BorderStyle="Groove" />  <asp:Label runat="server" Text=" <- Standard Button with Backcolor and Groove" /><br /><br />
<asp:Button ID="Button5" runat="server"
    Text="Button1" BackColor="#B92217" ForeColor="White"
    BorderStyle="Inset" />  <asp:Label runat="server" Text=" <- Standard Button with Backcolor and Inset" /><br /><br />
<asp:Button ID="Button6" runat="server"
    Text="Button1" BackColor="#B92217" ForeColor="White"
    BorderStyle="Outset" /> <asp:Label runat="server"  Text=" <- Standard Button with Backcolor and Outset" /><br /><br />

The above set of buttons are standard ASP.NET buttons with a background color set. Notice the following:

  • They each have different border style properties.
  • In all of these cases, the buttons are more square and you lose the roll-over effect.

In HTML:

<!-- Normal buttons with Border color -->
<asp:Button ID="Button7" runat="server"
    Text="Button1" BackColor="#B92217" ForeColor="White"
    BorderColor="#7C1810" BorderStyle="Groove" />
    <asp:Label runat="server"  
  Text=" <- Standard Button with Backcolor, Border and Groove" /><br /><br />
<asp:Button ID="Button8" runat="server"
    Text="Button1" BackColor="#B92217" ForeColor="White"
    BorderColor="#7C1810"  BorderStyle="Inset" /> <asp:Label runat="server"  Text=" <- Standard Button with Backcolor, Border and Inset" /><br /><br />
<asp:Button ID="Button9" runat="server"
    Text="Button1" BackColor="#B92217" ForeColor="White"
    BorderColor="#7C1810" BorderStyle="Outset" /> <asp:Label runat="server"  Text=" <- Standard Button with Backcolor, Border and Outset" /><br /><br />

The above set of buttons are standard ASP.NET buttons with a border. Notice the following:

  • They each have different border style properties.
  • In all of these cases, the buttons are more square and you lose the roll-over effect.
  • With the border, the button also becomes more flat looking.

In HTML:

<!-- Image Buttons -->
<asp:ImageButton ID="ImageButton1" runat="server"
    ImageUrl="Images/RedButton.jpg" width="65" height="20" BorderColor="Black"/><asp:Label runat="server" Text=" <- Image Button (with Silverlight image)" /><br /><br />
<asp:ImageButton ID="ImageButton2" runat="server"
    ImageUrl="Images/Gradient2.jpg" width="65" height="20"
    style="background-repeat: repeat-x" BorderColor="Black"/><asp:Label runat="server" Text=" <- Image Button with Gradient" /><br /><br />

The above set of buttons are ASP.NET image buttons. Notice the following:

  • The first button looks nice, but because it is an image, it has no effects.
  • I built this button in Silverlight, took a screen shot of it using SnagIt, and saved it as an image file.
  • To give it a roll-over effect I would need to do a second screen shot with the roll-over effect on and then replace the original image with this image as the user hovers over it.
  • If you want to use images in this manner, you would have to build one for every set of text.
  • If you are localizing the application, that means a new button image file for every set of text for every language.
  • OR, you could do an image without text and use CSS styles to write text over the image.
  • The second button does not quite look like a button.
  • It uses a gradient image and the background-repeat set to repeat it through the button.
  • You would need to use CSS styles to write text over the image.

Another option along the lines of using images, is to use separate images for the pieces of the button: One for the upper left corner, one for the top, one for the upper right corner, and so on. That way you can have rounded corner images that can expand to the size of your text. To manage the set of image pieces and get the text on the image, create a custom control. There are instructions for creating a custom control for this purpose in the article: "Dynamically Expandable Rounded Cornered Button".

In HTML:

<!-- Rounded Panel -->
<asp:Panel ID="Panel1" runat="server" BackColor="#B92217"
    Width="68" height="15">
    <asp:Button ID="InsideButton" runat="server"
        BackColor="#B92217" Text="Button1" 
        BorderStyle="None" ForeColor="White"/>
</asp:Panel>
<asp:Label runat="server" Text=" ^- Standard Button inside Rounded Panel" /><br />
<asp:RoundedCornersExtender ID="RoundedCornersExtender3" runat="server"
    BorderColor="#7C1810" Corners="All" Radius="6"
    TargetControlID="Panel1">
</asp:RoundedCornersExtender>

<asp:Panel ID="Panel2" runat="server"
    BackImageUrl="Images/Gradient2.jpg"
    Width="68" height="15">
    <asp:Button ID="Button4" runat="server"
        BackColor="#B92217" Text="Button1" 
        BorderStyle="None" ForeColor="White"/>
</asp:Panel>
<asp:Label runat="server" Text="^- Standard Button inside Rounded Panel with Background Image" /><br />
<asp:RoundedCornersExtender ID="RoundedCornersExtender4" runat="server"
    Corners="All" Radius="6"
    TargetControlID="Panel2">
</asp:RoundedCornersExtender>

This last set of buttons are in rounded panels. Notice the following:

  • The panels define the background color.
  • The RoundedCornersExtender control (part of the Ajax Toolkit) rounds the corners of the panel.
    NOTE: You cannot use the RoundedCornersExtender to simply round the corners of the button. But that would be nice, won't it?
  • The buttons in the panel have the same background color and no border so it appears as if they are rounded instead of being within a rounded container.
  • If the user does click on an edge of the button outside of the actual button itself, the click event won't occur.
  • The second button has an image background. Notice that the corners are then no longer rounded. Also, unless the image in the button exactly matches the image in the panel, you can see the button within the panel.
  • This basically means that you won't be able to have a rounded corner button and a image (like a gradient image) background using this technique.

So no good options here. :-(

The good news is … I am not alone.

Look at the buttons that Twitter uses. It looks like the standard HTML button to me:

image

Amazon has nice buttons. No! Wait! Look more closely and you'll see they are not buttons at all. They are graphics with href links on them! Notice the underline under Cart (I had my mouse over it). Is that cheating? :-)

image

And this one from Amazon is just an image with no roll-over or click effect.

image

Facebook? It just has square buttons:

image

Blizzard, maker of World of Warcraft (WOW), on the other hand has really nice buttons. Anyone know how they did it? (Looking at the View Source, it appears to be done by CSS.)

image

If you know of other choices, I'd love to hear about them.

Enjoy!

with 1 comment(s)
Filed under: , , , ,

My first Xaml/Silverlight post was on gradients, it seems only right that my first ASP.NET post should cover the same topic.

I started Web development in the 90's using VB 5/6 and classic ASP and learned all about HTML, CSS, Javascript, DOM, XML, XSL, and all of the other related technologies. This lead to writing a book: "Doing Web Development: Client-Side Techniques" back in 2002 that provides an introduction to those technologies including HTML, CSS, JavaScript, and XML.

When .NET came out, I jumped over to ASP.NET, enjoying its power but struggling with its quirks.

I then got busy with WinForms and Silverlight development and ignored much of ASP.NET for the past few years. I just got back to it this week and was excited to see what has changed. Amazingly, not that much. I am surprised how many quirky things are still there.

Which brings us back to the topic at hand. 10+ years ago we achieved gradient backgrounds on our Web pages using a pencil thin gradient graphic repeated across the screen. Today, we achieve gradient backgrounds on our Web pages using a pencil thin gradient graphic repeated across the screen.

Funny, many of the forum posts on this topic these days say "use Silverlight instead", like that is something we can just decide to do. Well, maybe you can, but as a consultant I certainly can't. And if you work in a corporate environment, I bet you can't either.

So, here is the tried and true way to get a gradient background for your pages.

1) Create/buy/borrow an image file (such as a .jpg file) that contains a very thin sliver of the desired gradient.

My image file looks like this:

image

Yes, that is a little hard to see. But it is basically a very thin gradient line going from Red to White.

I found an online tool to help create this image file: SecretGeek's Gradien-Maker. It looks a little loud when you first access the page, but it allows you to easily create a gradient. You can get the gradient just like you want it, then Save Background As to generate the image file with a pencil thin piece of the gradient. It worked great.

NOTE: This does not allow for setting specific gradient stops or for anything but a linear gradient, but it works for simple vertical or horizontal gradients.

2) Define the style

Define the style in a cascading stylesheet (CSS) like this:

In CSS:

.GradientBackground
{
    background-image: url(Images/Gradient2.jpg);
    background-repeat: repeat-x;
}

[If you are not familiar with HTML or CSS, see the above referenced book.]

This sets the URL of the background image to my gradient image file. It then sets it to repeat across the x-axis. This causes the sliver to be repeating horizontally across the page.

3) Set the new style on the desired HTML element in the ASP.NET page.

I added it to the Body element as follows:

In HTML:

<body class="GradientBackground">

OR

You can forget about step #2 and put the style directly in the ASP.NET page:

In HTML:

<body style="background-image: url(Images/Gradient2.jpg);background-repeat: repeat-x;">

In either case, the result is something like this:

image

You can make the gradient longer or shorter by creating a new gradient image file with a different size. Or you can use a similar technique to create a horizontal gradient repeated vertically across the page.

Until we are all doing Silverlight or ASP.NET comes out with gradient features on its controls, this is the best technique I've found to put gradients on Web pages.

Enjoy!

with 2 comment(s)
Filed under: , , , ,

Yes, it is that time of the year when we put together our holiday wish list and hope Santa has us on his "nice" list. Here are some of the accessories that I really wish I would get for my Silverlight.

NOTE: These are based on the Silverlight 3.0 release. I have not checked whether Silverlight 4.0 beta already has these items hanging in my stocking.

1) Curved Text

I was quite amazed that this was not a feature I already had. I was so convinced that I had this feature, I hunted around for a while. But several minutes of Bing later I found that it was just not there.

So why not add a Path property to one of the text controls?

Or cooler yet, why not give a text property to some of the less fortunate graphic controls such as lines, rectangles, and ellipses? Wouldn't it be cool for an ellipse to have a Text property? And then a TextAlign property where you can define inside or outside of the ellipse border?

2) ComboBox ValueMemberPath

Yea, I have already covered this one. If you have not seen it, it starts here.

3) Text Outline/Shadow

This was another one I was surprised about. I wanted to display color names in their appropriate color. But some colors, such as yellow, just don't show up very well. So I wanted to add an outline or drop shadow. No go.

4) Easier Editing of Chart Tooltips

When building charts, much of the data is numeric: 50 of type with ID of 1, 60 of type with ID of 2, 42 of type with ID of 3 and so on. But having a tooltip of: 1 50 does not tell the user anything. Something like "Corporate Customers: 50" is much more useful. But try to update the tooltip to something like that. After turning my graph orange (which, according to the forums, is more common than you would think), I gave up.

5) Allowing Different Colors in Column Charts

Surprisingly, the scenarios used when building the Column Chart features did not include one to show different colors, like a Pie Chart does. Rather, you only have one color for all of the columns. There are ways around this, none of which are easy and I was not successful within the time frame I had. So I built a separate series for each of the columns. Looked OK for the prototype, but won't cut it for the real application.

6) Better Image Error Messages

Yea, I covered this one to. If it does not work with bmp's it should either not allow you to pick them or it should give you an error message if you try to use them. I have more information on this one here.

7) Commenting XAML Blocks

This is not so much a Silverlight feature, but a XAML feature. I would like to see a new commenting character, like "//", that allows blocking out a set of XAML without messing with the internal <!-- --> comments. Right now, you have to remove all internal comments in a block of XAML before you can comment out that block. With the amount of trial and error required to accomplish anything when you are first learning Silverlight, this would be really useful.

8) Expanded WCF RIA Services

I know Santa's workshop has been busy with this one.

9) Interaction with the Desktop

This one I almost didn't put on the list because I peeked and know I am getting these in Silverlight 4.0: Clipboard support, Printing support, COM support, File accessing and so on.

10) Peace on Earth and Good Will to All Persons

Can Silverlight do that?

Enjoy and Happy Holiday!

with 4 comment(s)
Filed under: , , , ,

When last we saw our Silverlight ComboBox in this prior post, it was correctly populating, but as we paged through the records in our DataForm, the SelectedItem was not set correctly.

 

In XAML:

<ComboBox ItemsSource=
    "{Binding Data, Source={StaticResource CustomerTypeSource}}"
     DisplayMemberPath="CodeText"
     SelectedItem="{Binding CustomerTypeId, Mode=TwoWay}"/>

Regardless of the customer type for a Customer, in this example the SelectedItem is always set to the first item on the list.

The basic problem is that the data bound to the ComboBox has both a display member (the CodeText) and a value member (the CodeId). We want to bind the Customer object's CustomerTypeId property to the Code object's Code Id.

The ComboBox has a DisplayMemberPath property so the ComboBox does display the CodeText properly. It also has a SelectedItem property, which is expecting a Code object. But the Binding statement does not allow us to specify a Code object, only a property name (CustomerTypeId in this case).

Because the ComboBox does not have a ValueMemberPath property, there is no easy way to tell the control that it should map the CustomerTypeId to the CodeId. But there is a hard way using value converters.

A value converter is basically what it sounds like: it converts one value to another value. Use a value converter any time that you want to reformat or change a value in any way.

For the ComboBox SelectedItem to work correctly, we need to "convert" the CustomerTypeId to an appropriate Code object. Basically we need to use the CustomerTypeId to find and return the Code object with a matching CodeId.

Building a converter is not very hard once you know the basics. Just build a class that implements IValueConverter. Then write the code in the associated Convert and ConvertBack methods. I added this class directly to my Silverlight project.

NOTE: Be sure to import the System.Windows.Data namespace.

In C#:

using System.Windows.Controls;
using System.Windows.Data;

namespace SLCSharp
{
    public class CustomerTypeIdConverter : IValueConverter
    {

        public DomainDataSource ItemsSource { get; set; }

        public object Convert(object value,
            System.Type targetType,
            object parameter,
            System.Globalization.CultureInfo culture)
        {
            // Set a default return value
            int custTypeId = (int)value;
            BoCSharp.Code returnValue = null;

            // Look for the value in the list of items
            foreach (var item in ItemsSource.Data)
            {
                BoCSharp.Code codeObject = (BoCSharp.Code)item;
                if (codeObject.CodeId == custTypeId)
                {
                    returnValue = codeObject;
                    break;
                }
            }
            return returnValue;
        }

        public object ConvertBack(object value,
            System.Type targetType, object parameter,
            System.Globalization.CultureInfo culture)
        {
            // Set a default return value
            BoCSharp.Code codeObject = (BoCSharp.Code)value;
            int returnValue = 0;

            if (codeObject != null)
            {
                returnValue = codeObject.CodeId;
            }
            return returnValue;
        }
    }
}

In VB:

Imports System.Windows.Data

Public Class CustomerTypeIdConverter
    Implements IValueConverter

    Private _ItemsSource As DomainDataSource
    Public Property ItemsSource() As DomainDataSource
        Get
            Return _ItemsSource
        End Get
        Set(ByVal value As DomainDataSource)
            _ItemsSource = value
        End Set
    End Property

    Public Function Convert(ByVal value As Object, _
               ByVal targetType As System.Type, _
               ByVal parameter As Object, _
               ByVal culture As System.Globalization.CultureInfo)  _
          As Object _
          Implements System.Windows.Data.IValueConverter.Convert
        ' Set a default return value
        Dim custTypeId As Integer = CType(value, Integer)
        Dim returnValue As BoVB.Code = Nothing

        ' Look for the value in the list of items
        For Each item In ItemsSource.Data
            Dim codeObject As BoVB.Code = DirectCast(item, BoVB.Code)
            If codeObject.CodeId = custTypeId Then
                returnValue = codeObject
                Exit For
            End If
        Next

        Return returnValue
    End Function

    Public Function ConvertBack(ByVal value As Object, _
               ByVal targetType As System.Type, _
               ByVal parameter As Object, _
               ByVal culture As System.Globalization.CultureInfo) _
          As Object _
          Implements System.Windows.Data.IValueConverter.ConvertBack
        ' Set a default return value
        Dim codeObject As BoVB.Code = DirectCast(value, BoVB.Code)
        Dim returnValue As Integer = 0

        If codeObject IsNot Nothing Then
            returnValue = codeObject.CodeId
        End If

        Return returnValue
    End Function
End Class

This code defines a property for the DomainDataSource. This allows you to pass in the Codes data source so you can find the appropriate Code object based on the CustomerTypeId.

The Convert function "converts" the CustomerTypeId to the associated Code object. When the ComboBox SelectedItem is set to a particular CustomerTypeId, the CustomerTypeId is passed to the Convert function. The code in the Convert function first converts the passed in value to an integer. It then loops through the ItemsSource to find the Code object with a CodeId that matches the passed in CustomerTypeId. It then returns the found Code object.

NOTE: Instead of the loop, you could use LINQ instead:

In C#:

var returnValue = ItemsSource.Data.Cast<BoCSharp.Code>().Where(
    item => item.CodeId == custTypeId).FirstOrDefault();

In VB:

Dim returnValue = ItemsSource.Data.Cast(Of BoVB.Code). _
         Where(Function(item) item.CodeId = custTypeId).FirstOrDefault

The ConvertBack function converts back from a Code object to a CustomerTypeId. This one is easy. As long as a valid Code object is based in, the CustomerTypeId is just the Code object's Code Id.

There is one more required step: you need to modify the XAML to define the converter as a resource and associate it with the ComboBox.

In the UserControl.Resources section, add the following to define the value converter:

In XAML:

<UserControl.Resources>
    <local:CustomerTypeIdConverter x:Key="CustomerTypeIdConverter"
                  ItemsSource="{StaticResource CustomerTypeSource}"/>
</UserControl.Resources>

Notice how this sets the ItemsSource property to the CustomerTypeSource, which contains our customer type codes.

Then replace the ComboBox item in the DataForm with this:

In XAML:

<ComboBox ItemsSource=
    "{Binding Data, Source={StaticResource CustomerTypeSource}}"
     DisplayMemberPath="CodeText"
     SelectedItem="{Binding CustomerTypeId, Mode=TwoWay,
             Converter={StaticResource CustomerTypeIdConverter}}"/>

This references the converter from the UserControl resources.

Voila! Paging through the DataForm now shows the correct values!

image

Dang it! It works until you hit the back button.

image

Then all of the values are off by one (the value from the prior Customer).

This MUST be a bug in the DataForm. BUMMER!

Now what?

You have several choices:

1) Remove the pager control.

In a "real" application, you don't want your users to page through 500 customers. Rather you will have a selection box or search feature for the user to find the customer to edit. Without the pager control, this technique works great!

2) Subclass the ComboBox control and add your own SelectedValue.

Rocky shows how to do this on his blog here.

3) Buy a suite of Silverlight controls that includes a ComboBox from a third party vendor.

In my application, I went with Option #1. I have a DataGrid that displays data for the customers and allows searching and sorting. Double-clicking on in customer in the grid row then displays this DataForm (WITHOUT the pager control). All is well.

Enjoy!

with 3 comment(s)
Filed under: , , , ,

Building a DataForm is quick and easy and is detailed in this prior post. Even customizing it is a breeze as was also shown in that prior post. But now the design calls for a ComboBox. Ready to walk off a cliff?

It is surprisingly difficult to modify a DataForm to display a working ComboBox that is bound to a DomainDataSource. And it is even harder because so many of the available examples use a Fields collection that disappeared in July of 2009.

This post provides the steps for adding a ComboBox control to a DataForm. It retrieves the values for the ComboBox via RIA Services, but you could bind the ComboBox to any collection of data.

The ComboBox used in this example is the one from this prior post. To use it in this example, follow these steps:

1. Add a DomainDataSource for the codes that will populate the ComboBox.

2. Add the ComboBox and bind it to the DomainDataSource.

Before adding the ComboBox to the DataForm, let's try the above steps adding the ComboBox directly to the UserControl.

In XAML:

<UserControl xmlns:dataFormToolkit="clr-namespace:System.Windows.Controls;assembly=System.Windows.Controls.Data.DataForm.Toolkit"  x:Class="SLVB.CustomerSummaryUC"
    xmlns="
http://schemas.microsoft.com/winfx/2006/xaml/presentation"
    xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
    xmlns:data="clr-namespace:System.Windows.Controls;assembly=System.Windows.Controls.Data"
    xmlns:riaControls="clr-namespace:System.Windows.Controls;assembly=System.Windows.Ria.Controls" 
    xmlns:domain="clr-namespace:SLVB.Web">

    <Grid x:Name="LayoutRoot" Background="BlanchedAlmond">
        <riaControls:DomainDataSource x:Name="CustomerSource"
               QueryName="GetCustomers" AutoLoad="True">
            <riaControls:DomainDataSource.DomainContext>
                <domain:CustomerContext/>
            </riaControls:DomainDataSource.DomainContext>
        </riaControls:DomainDataSource>

        <riaControls:DomainDataSource x:Name="CustomerTypeSource" 
               QueryName="GetCustomerTypes" AutoLoad="True">
            <riaControls:DomainDataSource.DomainContext>
                <domain:CodeContext/>
            </riaControls:DomainDataSource.DomainContext>
        </riaControls:DomainDataSource>

        <StackPanel Margin="5,5,200,5">
            <ComboBox ItemsSource=
                 "{Binding Data, ElementName=CustomerTypeSource}"
                  DisplayMemberPath="CodeText"/>
            <dataFormToolkit:DataForm
                  ItemsSource=
                  "{Binding Data, ElementName=CustomerSource}">
            </dataFormToolkit:DataForm>
        </StackPanel>
    </Grid>
</UserControl>

The CustomerTypeSource is the new DomainDataSource. The ComboBox binds to this data source and displays the customer types.

The results are as follows:

image

The ComboBox appears above the DataForm and partially covers it when it is open. So now we know that the ComboBox works. The next step is to insert it instead into the DataForm.

There is no way to change the type of one field on the DataForm without then specifying every field on the DataForm. So if we want to specify that the Customer Type is a ComboBox, we can no longer use the auto field generation and must instead manually define every field.

The DataForm XAML code is then significantly longer.

In XAML:

<StackPanel Margin="5,5,200,5">
    <dataFormToolkit:DataForm
              ItemsSource="{Binding Data, ElementName=CustomerSource}">
        <dataFormToolkit:DataForm.EditTemplate>
            <DataTemplate>
                <StackPanel
                       dataFormToolkit:DataField.IsFieldGroup="True">
                    <dataFormToolkit:DataField>
                        <TextBox Text=
                           "{Binding FirstName, Mode=TwoWay}" />
                    </dataFormToolkit:DataField>
                    <dataFormToolkit:DataField>
                        <TextBox Text=
                           "{Binding LastName, Mode=TwoWay}" />
                    </dataFormToolkit:DataField>
                    <dataFormToolkit:DataField>
                        <ComboBox ItemsSource=
                      "{Binding Data, ElementName=CustomerTypeSource}"
                      DisplayMemberPath="CodeText"/>
                    </dataFormToolkit:DataField>
                    <dataFormToolkit:DataField>
                        <TextBox Text=
                           "{Binding EmailAddress, Mode=TwoWay}" />
                    </dataFormToolkit:DataField>
                </StackPanel>
            </DataTemplate>
        </dataFormToolkit:DataForm.EditTemplate>
    </dataFormToolkit:DataForm>
</StackPanel>

A DataField element defines each field on the form. Within the DataField element is the control to display for that data field. In most case, this is a TextBox control. The Text property of the TextBox is bound to the appropriate field in the data source. The mode is TwoWay to provide review and edit.

So far, the ComboBox code is the same code defined earlier when the ComboBox was above the DataForm. But now it is in the desired location within the DataForm.

Let's give this a try and see what we have:

image

Well, the ComboBox is there … but it is empty. We simply copied the working ComboBox from above the DataForm to inside the DataForm and now it no longer populates. Hmmm.

After spending several hours Bing'ing about this … I ran across this post and thought it might be relevant. But instead of building a proxy, I thought I would just move the DomainDataSource into a UserControl resource.

So I removed the CustomerTypeSource DomainDataSource from under the Grid element and instead added it to a resources section. I then changed the x:Name to x:Key.

In XAML:

<UserControl.Resources>
    <riaControls:DomainDataSource x:Key="CustomerTypeSource"
                   QueryName="GetCustomerTypes"
                   AutoLoad="True">
        <riaControls:DomainDataSource.DomainContext>
            <domain:CodeContext/>
        </riaControls:DomainDataSource.DomainContext>
    </riaControls:DomainDataSource>
</UserControl.Resources>

This required changing the binding on the ComboBox to use this as a StaticResource. The other change was setting the SelectedItem to the CustomerTypeId.

In XAML:

<ComboBox ItemsSource=
    "{Binding Data, Source={StaticResource CustomerTypeSource}}"
     DisplayMemberPath="CodeText"
     SelectedItem="{Binding CustomerTypeId, Mode=TwoWay}"/>

And the result:

image

Whohoo!!

Now the only problem is that the SelectedItem is not correct. Regardless of the customer type for a Customer, the SelectedValue is always set to the first item on the list. How do we fix this? Yes, another cliff.

The data bound to the ComboBox has both a display member (the CodeText) and a value member (the CodeId). We want to bind the CustomerTypeId property to the Code Id.

Because the ComboBox does not have a ValueMemberPath property, there is no easy way to tell the control that it should map the CustomerTypeId to the CodeId. But there is a hard way using Converters.

The fact that the ComboBox is missing a ValueMemberPath seems like a bug that I hope will be corrected.

But I have to start baking Thanksgiving pies. So more on this in a future post.

Happy Thanksgiving!

Enjoy!

When building line of business (LOB) applications, a common feature is create, review, update, and delete (CRUD) operations on the data in the application. An easy way to provide this feature is using the Silverlight DataForm.

[For an introduction to Silverlight and RIA Services, start here.]

NOTE: The example used here is a continuation of the example in this prior post.

Updating the Domain Service Class

The first step in building a DataForm is to ensure that your Domain Service classes support create, update, and delete operations. The Domain Service class for this example is a CustomerService and resides in the Web project created when the Silverlight project was created.

NOTE: If you don't add these methods, you will get exceptions when trying to work with the DataGrid such as "Unhandled Error in Silverlight Application Code: 4004" with a message "Editing items is not supported by the IEditableCollection."

In C#:

namespace SLCSharp.Web
{
    using BoCSharp;
    using System.Collections.Generic;
    using System.Web.Ria;
    using System.Web.DomainServices;

    [EnableClientAccess()]
    public class CustomerService : DomainService
    {
        public IEnumerable<Customer> GetCustomers()
        {
            return Customers.Retrieve();
        }

        public void InsertCustomer(Customer currentPerson)
        {
        }

        public void UpdateCustomer(Customer currentPerson)
        {
        }

        public void DeleteCustomer(Customer currentPerson)
        {
        }
    }
}

In VB:

Imports System.Web.DomainServices
Imports System.Web.Ria

<EnableClientAccess()> _
Public Class CustomerService
    Inherits DomainService

    Public Function GetCustomers() As IEnumerable(Of Customer)
        Return Customers.Retrieve()
    End Function

    Public Sub InsertCustomer(ByVal currentPerson As Customer)

    End Sub

    Public Sub UpdateCustomer(ByVal currentPerson As Customer)

    End Sub

    Public Sub DeleteCustomer(ByVal currentPerson As Customer)

    End Sub
End Class

Notice that the Insert, Update, and Delete operations don't contain any code. The code for these will be provided in a later post. In this post, the focus is on binding to the DataForm and trying out the DataForm features.

Having the methods in place, even though they are empty, allow the in-memory data to be updated and retained. This allows you to fully try out the user interface. But since there is no code within these methods, no changes are retained in the database.

Building the XAML

The XAML for building the DataForm requires two steps:

1) Defining the DomainDataSource for the data.

2) Defining the DataForm.

In XAML:

<UserControl x:Class="SLVB.CustomerSummaryUC"
    xmlns:dataFormToolkit="clr-namespace:System.Windows.Controls;assembly=System.Windows.Controls.Data.DataForm.Toolkit"  
    xmlns="
http://schemas.microsoft.com/winfx/2006/xaml/presentation"
    xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
    xmlns:data="clr-namespace:System.Windows.Controls;assembly=System.Windows.Controls.Data"
    xmlns:riaControls="clr-namespace:System.Windows.Controls;assembly=System.Windows.Ria.Controls"
    xmlns:domain="clr-namespace:SLVB.Web">
    <Grid x:Name="LayoutRoot" Background="BlanchedAlmond">
        <riaControls:DomainDataSource x:Name="CustomerSource"
                 QueryName="GetCustomers" AutoLoad="True">
            <riaControls:DomainDataSource.DomainContext>
                <domain:CustomerContext/>
            </riaControls:DomainDataSource.DomainContext>
        </riaControls:DomainDataSource>
        <StackPanel>
            <dataFormToolkit:DataForm
                ItemsSource=
                     "{Binding Data, ElementName=CustomerSource}">
            </dataFormToolkit:DataForm>
        </StackPanel>
    </Grid>
</UserControl>

Using the DomainDataSource requires adding two namespaces and the DataForm requires adding a third namespace to the UserControl:

1) xmlns:riaControls is needed for the RIA DomainDataSource control.

NOTE: You won't find the DomainDataSource control in the toolbox. You need to type it into the XAML manually.

2) xmlns:domain is the namespace for your Web project that launches your Silverlight application.

3) xmlns:dataFormToolkit contains the DataForm control.

Define a name for the DomainDataSource using the x:Name property. This is the name used in the binding. Also set the QueryName property to the name of the Domain Service class method that gets the data for this data source. In this case, it is the GetCustomers method.

The DomainDataSource also requires a DomainContext. This is where you define the name of the data context used by the data source. If you don't see an appropriate context using intellisense, try rebuilding your application. Otherwise you can type in the name. It will be the same name as your Domain Service class but replacing "Service" with "Context". Since this example Domain Service class is CustomerService, the context is CustomerContext.

The next set of XAML builds a DataForm inside a StackPanel. The ItemsSource property of the DataForm defines the binding to the name of the DomainDataSource.

The result is as follows:

image

That could look a little nicer. It would be easier to use if the label names were correct words, if there was some help text, and the fields were in a more natural order. And does the user really need to see the CustomerId?

Updating the Business Objects

There are several ways to adjust the layout of the DataForm. The most unique technique is to update the business objects themselves. The idea behind this technique is that as the business objects change over time, they can ensure that the associated UI also changes over time. Regardless of whether you agree with this idea, here's how you do it.

Simply add attributes to the associated properties.

In C#:

using System.ComponentModel.DataAnnotations;
namespace BoCSharp
{
    public class Customer
    {
        [Display(AutoGenerateField=false)]
        [Key()]
        public int CustomerId { get; set; }

        [Display(Name="Customer Type",
             Description="Select the type of customer",
             Order=3)]
        public int CustomerTypeId { get; set; }

        [Display(Name = "First Name",
             Description = "Enter the customer's first name",
             Order = 1)]
        public string FirstName { get; set; }

        [Display(Name = "Last Name",
             Description = "Enter the customer's last name",
             Order = 2)]
        public string LastName { get; set; }

        [Display(Name = "Email",
            Description = "Enter the customer's primary email address",
            Order = 4)]
        public string EmailAddress { get; set; }

        public Customer()
        {
        }
     }
}

In VB:

Imports System.ComponentModel.DataAnnotations
Public Class Customer

    Private _CustomerId As Integer
    <Display(AutoGenerateField:=False)> _
    <Key()> _
    Public Property CustomerId() As Integer
        Get
            Return _CustomerId
        End Get
        Set(ByVal value As Integer)
            _CustomerId = value
        End Set
    End Property

    Private _CustomerTypeId As Integer
    <Display(Name:="Customer Type", _
             Description:="Select the type of customer", _
             Order:=3)> _
    Public Property CustomerTypeId() As Integer
        Get
            Return _CustomerTypeId
        End Get
        Set(ByVal value As Integer)
            _CustomerTypeId = value
        End Set
    End Property

    Private _FirstName As String
    <Display(Name:="First Name", _
             Description:="Enter the customer's first name", _
             Order:=1)> _
    Public Property FirstName() As String
        Get
            Return _FirstName
        End Get
        Set(ByVal value As String)
            _FirstName = value
        End Set
    End Property

    Private _LastName As String
    <Display(Name:="Last Name", _
             Description:="Enter the customer's last name", _
             Order:=2)> _
    Public Property LastName() As String
        Get
            Return _LastName
        End Get
        Set(ByVal value As String)
            _LastName = value
        End Set
    End Property

    Private _EmailAddress As String
    <Display(Name:="Email", _
           Description:="Enter the customer's primary email address", _
           Order:=4)> _
    Public Property EmailAddress() As String
        Get
            Return _EmailAddress
        End Get
        Set(ByVal value As String)
            _EmailAddress = value
        End Set
    End Property
End Class

Notice that each property now has a Display attribute. The key named parameters within this attribute are:

  • AutoGenerateField: Set to false, this will hide the field so it does not appear on the data form. If not set, the value is true.
  • Description: This is the text that will appear as a tooltip (see screen shot below). If not set, no tooltip appears.
  • Name: This is the name that will appear as the label for the field. If not set, the field name is used.
  • Order: This defines the order of the fields on the data form.

The result is as follows:

image

MUCH nicer!

NOTE: I also added a Margin ="5,5,200,5" to the StackPanel to ensure there was space to the right to see the information tooltip. No other changes were made to the XAML.

NOTE: When I made the changes to the business object and ran the application, the DataForm layout did not change. I recompiled the application and ran again before I saw the changes.

The only thing left to do is change the Customer Type text field to a ComboBox. But amazing, that is a significant amount of work. Better left for a future post.

Enjoy!

Every application has some type of codes: customer types, reason codes, states, and so on. Sometimes these codes can be hard-coded in an application as Enum values. But other times these codes are more readily stored in a table. This post details how to work with code tables through RIA Services.

[For details on how to work with Enum values through RIA Services, see this prior post.]

NOTE: This example continues from this prior post that introduces RIA Services and your business objects.

Building the Business Layer Classes

To manage the codes in your application without having to build a class for each type of code, you can build a more generalized Code class in your business layer. This class is added to the project containing the Customer and Customers classes from this prior post.

In C#:

using System.ComponentModel.DataAnnotations;

namespace BoCSharp
{
    public class Code
    {
        [Key()]
        public int CodeId { get; set; }
        public string CodeText { get; set; }
    }
}

In VB:

Imports System.ComponentModel.DataAnnotations

Public Class Code

    Private _CodeId As Integer
    <Key()> _
    Public Property CodeId() As Integer
        Get
            Return _CodeId
        End Get
        Set(ByVal value As Integer)
            _CodeId = value
        End Set
    End Property

    Private _CodeText As String
    Public Property CodeText() As String
        Get
            Return _CodeText
        End Get
        Set(ByVal value As String)
            _CodeText = value
        End Set
    End Property
End Class

Each code has an Id and a text value. The Key() attribute on the CodeId ensures that this class is set up to use RIA Services.

A separate class in the business layer then tracks the sets of all codes.

In C#:

using System.Collections.Generic;

namespace BoCSharp
{
    public class Codes
    {
        public static List<Code> RetrieveCustomerTypes()
        {
            List<Code> custTypeList = new List<Code>
                {new Code()
                    {CodeId=1,
                     CodeText="Individual"},
                new Code()
                    {CodeId=2,
                     CodeText="Corporate"},
                new Code()
                    {CodeId=3,
                     CodeText="Government"},
                new Code()
                    {CodeId=4,
                     CodeText="Education"}};
            return custTypeList;
        }
    }
}

In VB:

Public Class Codes

    Public Shared Function RetrieveCustomerTypes() As List(Of Code)
        Dim custTypeList As New List(Of Code)
        custTypeList.Add(New Code With { _
                         .CodeId = 1, _
                         .CodeText = "Individual"})
        custTypeList.Add(New Code With { _
                         .CodeId = 2, _
                         .CodeText = "Corporate"})
        custTypeList.Add(New Code With { _
                         .CodeId = 3, _
                         .CodeText = "Government"})
        custTypeList.Add(New Code With { _
                         .CodeId = 4, _
                         .CodeText = "Education"})
        Return custTypeList
    End Function
End Class

Two things to note about this class:

  1. It will have a Retrieve method for each kind of code (customer types, reason codes, states, and so on).
  2. In the "real" application, it will get these values from the database instead of hard-coded values. (Hard-coded values are used here so you don't have to set up a database to try these techniques.)

Finally, let's modify the Customer class to add the customer type property. Each customer has a customer type.

In C#:

using System.ComponentModel.DataAnnotations;
namespace BoCSharp
{
    public class Customer
    {
        [Key()]
        public int CustomerId { get; set; }
        public int CustomerTypeId { get; set; }
        public string FirstName { get; set; }
        public string LastName { get; set; }
        public string EmailAddress { get; set; }

        public Customer()
        {
        }
     }
}

In VB:

Imports System.ComponentModel.DataAnnotations
Public Class Customer

    Private _CustomerId As Integer
    <Key()> _
    Public Property CustomerId() As Integer
        Get
            Return _CustomerId
        End Get
        Set(ByVal value As Integer)
            _CustomerId = value
        End Set
    End Property

    Private _CustomerTypeId As Integer
    Public Property CustomerTypeId() As Integer
        Get
            Return _CustomerTypeId
        End Get
        Set(ByVal value As Integer)
            _CustomerTypeId = value
        End Set
    End Property

    Private _FirstName As String
    Public Property FirstName() As String
        Get
            Return _FirstName
        End Get
        Set(ByVal value As String)
            _FirstName = value
        End Set
    End Property

    Private _LastName As String
    Public Property LastName() As String
        Get
            Return _LastName
        End Get
        Set(ByVal value As String)
            _LastName = value
        End Set
    End Property

    Private _EmailAddress As String
    Public Property EmailAddress() As String
        Get
            Return _EmailAddress
        End Get
        Set(ByVal value As String)
            _EmailAddress = value
        End Set
    End Property
End Class

Though only the CustomerTypeId is new, all of the code from the Customer class (originally defined in this prior post) is shown above.

Building the Domain Service Class

The next step is to build a Domain Service class for the Codes class so that the Silverlight project can access the codes.

In C#:

namespace SLCSharp.Web
{
    using BoCSharp;
    using System.Collections.Generic;
    using System.Web.Ria;
    using System.Web.DomainServices; 

    [EnableClientAccess()]
    public class CodeService : DomainService
    {
        public IEnumerable<Code> GetCustomerTypes()
        {
            return Codes.RetrieveCustomerTypes();
        }
    }
}

In VB:

Imports System.Web.DomainServices
Imports System.Web.Ria

<EnableClientAccess()> _
Public Class CodeService
    Inherits DomainService

    Public Function GetCustomerTypes() As IEnumerable(Of Code)
        Return Codes.RetrieveCustomerTypes()
    End Function
End Class

This class will have a function to expose each type of code. For now, it provides a function to get the customer types.

Accessing the Codes from Silverlight

One of the common ways to use the codes in Silverlight is to present them in a ComboBox. This involves two steps:

  1. Add a DomainDataSource that accesses the Domain Service class.
  2. Add a ComboBox that binds to the DomainDataSource.

In XAML:

<UserControl x:Class="SLVB.CodesExampleUC"
    xmlns="
http://schemas.microsoft.com/winfx/2006/xaml/presentation"
    xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
    xmlns:riaControls="clr-namespace:System.Windows.Controls;assembly=System.Windows.Ria.Controls"
    xmlns:domain="clr-namespace:SLVB.Web"
    Width="400" Height="300">
    <Grid x:Name="LayoutRoot" Background="White">
        <riaControls:DomainDataSource x:Name="CustomerTypeSource"
                    QueryName="GetCustomerTypes" AutoLoad="True">
            <riaControls:DomainDataSource.DomainContext>
                <domain:CodeContext/>
            </riaControls:DomainDataSource.DomainContext>
        </riaControls:DomainDataSource>
        <StackPanel Margin="10">
            <ComboBox ItemsSource=
                 "{Binding Data, ElementName=CustomerTypeSource}"
                 DisplayMemberPath="CodeText"/>
        </StackPanel>
    </Grid>
</UserControl>

Using the DomainDataSource requires adding two namespaces to the UserControl:

1) xmlns:riaControls is needed for the RIA DomainDataSource control.

NOTE: You won't find the DomainDataSource control in the toolbox. You need to type it into the XAML manually.

2) xmlns:domain is the namespace for your Web project that launches your Silverlight application.

Define a name for the DomainDataSource using the x:Name property. This is the name used in the binding. Also set the QueryName property to the name of the Domain Service class method that gets the data for this data source. In this case, it is the GetCustomerTypes method.

The DomainDataSource also requires a DomainContext. This is where you define the name of the data context used by the data source. If you don't see an appropriate context using intellisense, try rebuilding your application. Otherwise you can type in the name. It will be the same name as your Domain Service class but replacing "Service" with "Context". Since this example Domain Service class is CodeService, the context is CodeContext.

The next set of XAML builds a ComboBox inside a StackPanel. The ItemsSource property of the ComboBox defines the binding to the name of the DomainDataSource. The DisplayMemberPath property is set to the name of the class property to display in the ComboBox. In this case, the ComboBox displays the CodeText property.

The result is as follows:

image

Use this technique to access your code tables from your Silverlight application.

Enjoy!

Many Silverlight examples show how to build controls with XAML. But XAML does not support programming logic. So sometimes you need to build the controls with real code in C# or VB. This example demonstrates how to build a Grid and StackPanel with Buttons first in XAML and then in C# and VB.

It seems that there would be a relatively straightforward set of rules to "convert" XAML into C# or VB code. And there is, sort of. But these rules break down in some cases. Hopefully this is just because XAML is still relatively new and these inconsistencies will be corrected over time.

A common scenario in building Silverlight applications is to divide the layout into areas that contain a header, menu, contents, and so on. In this example, the layout simply contains a set of buttons.

Here the buttons are on the top:

image

In XAML:

<UserControl x:Class="SilverlightApplication1.MainPage"
    xmlns="
http://schemas.microsoft.com/winfx/2006/xaml/presentation"
    xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml">
  <Grid x:Name="LayoutRoot">
        <Grid.RowDefinitions>
            <RowDefinition Height="auto"/>
            <RowDefinition Height="*"/>
        </Grid.RowDefinitions>
        <StackPanel Orientation="Horizontal" 
                    Background="Bisque">
            <Button Content="Home" Margin="10,10,0,0"/>
            <Button Content="Customers" Margin="10,10,0,0"/>
            <Button Content="Sales" Margin="10,10,0,0"/>
        </StackPanel>
    </Grid>
</UserControl>

This layout includes a basic grid that is the layout for the entire UserControl. The grid has two row definitions: one for a menu bar that is automatically sized to the size of the buttons and the other for content area sized to the remainder of the page. A StackPanel within the grid contains the set of three buttons.

But lets say that this feature needs to be implemented in C# or VB code instead. So the XAML code is limited to the very basics:

<UserControl x:Class="SilverlightApplication1.MainPage"
    xmlns="
http://schemas.microsoft.com/winfx/2006/xaml/presentation"
    xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml">
  <Grid x:Name="LayoutRoot">
     <!—Generated in code -->    
  </Grid>
</UserControl>

In the code, the first thing to do is create the row definitions.

In C#:

RowDefinition rd;
rd = new RowDefinition {
    Height = GridLength.Auto};
LayoutRoot.RowDefinitions.Add(rd);
rd = new RowDefinition {
    Height = new GridLength(1, GridUnitType.Star)};
LayoutRoot.RowDefinitions.Add(rd);

In VB:

Dim rd As New RowDefinition
rd = New RowDefinition With { _
        .Height = GridLength.Auto}
LayoutRoot.RowDefinitions.Add(rd)
rd = New RowDefinition With { _
        .Height = New GridLength(1, GridUnitType.Star)}
LayoutRoot.RowDefinitions.Add(rd)

This code creates a new RowDefinition, adds it to the list of RowDefinitions for the Grid named LayoutRoot.

For the first row definition the code uses the GridLength.Auto property. This ensures that the row is sized automatically based on the controls within the grid.

The second row definition uses the GridLength constructor to define the "*" value. The "*" value is a weighted proportion of available space. The "*" requires the constructor to define both a value (the proportion) and the type (GridUnitType.Star). Since this example only has one row to fill, the value is set to 1.

If there were two rows to fill whereby one row was to take 80% of the remaining area and the other row 20%, the constructor parameters would be GridLength(80, GridUnitType.Star) and GridLength(20, GridUnitType.Star) respectively.

The next step is to create the StackPanel. This example uses a horizontal orientation and a Bisque background color.

In C#:

StackPanel sp = new StackPanel();
sp.Orientation = Orientation.Horizontal;
sp.Background = new SolidColorBrush(Color.FromArgb(0xFF, 0xFF,
                                                   0xE4, 0xC4));
LayoutRoot.Children.Add(sp);

In VB:

Dim sp As New StackPanel
sp.Orientation = Orientation.Horizontal
sp.Background = New SolidColorBrush(Color.FromArgb(&HFF, &HFF, _
                                                   &HE4, &HC4))
LayoutRoot.Children.Add(sp)

This code creates the StackPanel. It then sets its Orientation and Background. Finally, it adds it to the children collection for the LayoutRoot grid. This places the StackPanel within the UserControl grid.

This example points out one of the inconsistencies in converting the code from XAML to C# or VB: Color values.

XAML has a set of about 141 colors where C# and VB only have 16.

There are color charts (including this one here) that provide the Hexadecimal color values of the XAML predefined colors. For this example, Bisque converts to #FFFFE4C4.

Finally, the buttons are added to the StackPanel with this code.

In C#:

Button btn;
btn = new Button  { 
        Content = "Home", 
        Margin = new Thickness(10, 10, 0, 0)};
sp.Children.Add(btn);
btn = new Button  { 
        Content = "Customers", 
        Margin = new Thickness(10, 10, 0, 0)};
sp.Children.Add(btn);
btn = new Button  { 
        Content = "Sales", 
        Margin = new Thickness(10, 10, 0, 0)};
sp.Children.Add(btn);

In VB:

Dim btn As Button
btn = New Button With { _
        .Content = "Home", _
        .Margin = New Thickness(10, 10, 0, 0)}
sp.Children.Add(btn)
btn = New Button With { _
        .Content = "Customers", _
        .Margin = New Thickness(10, 10, 0, 0)}
sp.Children.Add(btn)
btn = New Button With { _
        .Content = "Sales", _
        .Margin = New Thickness(10, 10, 0, 0)}
sp.Children.Add(btn)

This code adds three buttons to the StackPanel. For each Button, the Content string and Margin are set. The Button is then added to the children collection for the StackPanel.

Here the buttons are along the left:

image

In XAML:

<UserControl x:Class="SilverlightApplication1.MainPage"
    xmlns="
http://schemas.microsoft.com/winfx/2006/xaml/presentation"
    xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml">
  <Grid x:Name="LayoutRoot">
            <Grid.ColumnDefinitions>
            <ColumnDefinition Width="auto"/>
            <ColumnDefinition Width="*"/>
        </Grid.ColumnDefinitions>
        <StackPanel Orientation="Vertical" Background="Bisque" >
            <Button Content="Home" Margin="10,10,0,0"/>
            <Button Content="Customers" Margin="10,10,0,0"/>
            <Button Content="Sales" Margin="10,10,0,0" />
        </StackPanel>
    </Grid>
</UserControl>

In C#:

public MainPage()
{
    InitializeComponent();
    Loaded += new RoutedEventHandler(MainPage_Loaded);
}

private void MainPage_Loaded(object sender, RoutedEventArgs e)
{
    ColumnDefinition cd ;
    cd = new ColumnDefinition {
            Width = GridLength.Auto};
    LayoutRoot.ColumnDefinitions.Add(cd);
    cd = new ColumnDefinition {
            Width = new GridLength(1, GridUnitType.Star)};
    LayoutRoot.ColumnDefinitions.Add(cd);

    StackPanel sp = new StackPanel(); 
    sp.Orientation = Orientation.Vertical;
    sp.Background = new SolidColorBrush(Color.FromArgb(0xFF, 0xFF,
                                                       0xE4, 0xC4));
    LayoutRoot.Children.Add(sp);

    Button btn;
    btn = new Button  {
            Content = "Home",
            Margin = new Thickness(10, 10, 0, 0)};
    sp.Children.Add(btn);
    btn = new Button  {
            Content = "Customers",
            Margin = new Thickness(10, 10, 0, 0)};
    sp.Children.Add(btn);
    btn = new Button  {
            Content = "Sales",
            Margin = new Thickness(10, 10, 0, 0)};
    sp.Children.Add(btn);
}

In VB:

Public Sub New()
    InitializeComponent()
End Sub

Private Sub MainPage_Loaded(ByVal sender As Object,  _
   ByVal e As System.Windows.RoutedEventArgs) Handles Me.Loaded

    Dim cd As ColumnDefinition
    cd = New ColumnDefinition With { _
            .Width = GridLength.Auto}
    LayoutRoot.ColumnDefinitions.Add(cd)
    cd = New ColumnDefinition With { _
            .Width = New GridLength(1, GridUnitType.Star)}
    LayoutRoot.ColumnDefinitions.Add(cd)

    Dim sp As New StackPanel
    sp.Orientation = Orientation.Vertical
    sp.Background = New SolidColorBrush(Color.FromArgb(&HFF, &HFF, 
                                                       &HE4, &HC4))
    LayoutRoot.Children.Add(sp)

    Dim btn As Button
    btn = New Button With { _
            .Content = "Home", _
            .Margin = New Thickness(10, 10, 0, 0)}
    sp.Children.Add(btn)
    btn = New Button With { _
            .Content = "Customers", _
            .Margin = New Thickness(10, 10, 0, 0)}
    sp.Children.Add(btn)
    btn = New Button With { _
            .Content = "Sales", _
            .Margin = New Thickness(10, 10, 0, 0)}
    sp.Children.Add(btn)
End Sub

This code is nearly identical to the prior code. It demonstrates ColumnDefinitions instead of RowDefinitions and orients the StackPanel vertically.

Use these techniques any time you need to create Column or Row Definitions in code. Or if you need to create a StackPanel or Buttons in code.

Enjoy!

with 1 comment(s)
Filed under: , , , ,

As WinForms and WebForms developers, we are used to building user interfaces (UIs) that look like this:

image

Notice the rectangular header area and the rectangular link area and the rectangular data entry area.

With Silverlight it is (relatively) easy to give the UI less of a boxy look:

image

So the above UI may not be amazing, but it should give you some ideas about the kinds of things you can do to make your Silverlight applications look a little less square.

To make it easier to try this yourself, all of the source code for this User Control is included at the bottom of this post.

NOTE: This example uses a medium gray and white for the two background colors and a shade of red for the labels. You can change this to any color scheme you would like for your application by adjusting the color resources defined at the top of the code.

Designing the Layout

Even though the goal is to have a non-boxy style user interface, this effect is achieved using a set of grids. The grids are laid out as follows:

image

The user control is comprised of one primary grid, called LayoutRoot in the sample code. It has two rows as shown with two red rectangles above. The top row contains the header information and the bottom row contains the content information.

The top row of the LayoutRoot grid contains another grid, called HeaderGrid in the sample code. It contains two columns as shown with two blue rectangles above. The first column contains the logo and the second column contains the application title.

The background of the HeaderGrid is set to the background color defined for the application. In this example, the color is a medium gray.

NOTE: You cannot easily use a StackPanel here instead of a Grid because the background color needs to stretch across the entire width of the control. With a StackPanel, the background only covers the controls within the StackPanel. If you use a StackPanel, ensure your controls are resized to the full width of the user control.

The bottom row of the LayoutRoot grid contains another grid, called ContentGrid in the sample code. It contains three columns as shown with the two green rectangles above along with the red vertical line between them.

The background of the ContentGrid is where the magic takes place. It contains the radial gradient which displays the arc.

[For an overview of Silverlight gradients, see this prior post.]

NOTE: If you define a background for any of the controls within the ContentGrid, the ContentGrid background will not appear properly.

Defining the Gradient

The gradient used to achieve the arc depicted in the design is as follows:

  <Grid.Background>
      <RadialGradientBrush>
          <RadialGradientBrush.RelativeTransform >
              <TransformGroup>
                  <ScaleTransform 
                      CenterX="0.5" 
                      ScaleY="2" ScaleX="2.5"/>
                  <SkewTransform/>
                  <RotateTransform/>
                  <TranslateTransform/>
              </TransformGroup>
          </RadialGradientBrush.RelativeTransform>
          <GradientStop Color="White" Offset="0.95"/>
          <GradientStop 
                 Color="{StaticResource BackgroundColor}" 
                 Offset=".98"/>
      </RadialGradientBrush>
  </Grid.Background>

To better understand this XAML, let's take it step by step. The following sets of screen shots were taken using Expression Blend 3.

Add a simple radial gradient to the ContentGrid background with code like this:

<RadialGradientBrush>
    <GradientStop Color="White" Offset="0"/>
    <GradientStop Color="{StaticResource BackgroundColor}"
                  Offset="1"/>
</RadialGradientBrush>

The Silverlight control appears like this:

image

Notice that the center point of the bottom grid is white and radiates out to a medium gray. While this may also be an interesting effect, it is not what is needed for this example.

For this example, the majority of the background should be white. So the first change is to the gradient Offset values:

<RadialGradientBrush>
    <GradientStop Color="White" Offset=".95"/>
    <GradientStop Color="{StaticResource BackgroundColor}"
                  Offset=".98"/>
</RadialGradientBrush>

The result is as follows:

image

By setting the white Offset to .95, the white center point does not begin the gradient effect until 95% of the way through the gradient radius. This leaves the majority of the background white.

Setting the Offset values closer to each other, the actual gradient effect is very small as shown in the red circled area above.

For a wider gradient with more space between the white and the gray, adjust the Offset values until you get the desired result. For example, with Offsets of .70 and .98, the gradient between the white and the gray is much wider:

image

Once you get the desired gradient effect, you can resize the ellipse. In this example, only the top of the gradient ellipse is desired. To resize the gradient to get the desired effect, use the gradient RelativeTransform property:

<RadialGradientBrush.RelativeTransform >
    <TransformGroup>
        <ScaleTransform CenterX="0.5" ScaleY="2" ScaleX="2.5"/>
        <SkewTransform/>
        <RotateTransform/>
        <TranslateTransform/>
    </TransformGroup>
</RadialGradientBrush.RelativeTransform>

The ScaleY defines the height of the ellipse. By default, the ellipse is one unit tall. If you want only the top half of the ellipse, you can set the ScaleY=2. This doubles the size so you only get the top half of the ellipse. The result is as follows:

image

Adjust the ScaleX property to change the width of the ellipse and flatten the arc. Setting ScaleX to 2.5 results in the following:

image

That flattened the arc, but now the ellipse is no longer centered. To fix that, adjust the CenterX property. Setting CenterX to .5 results in desired layout:

image

Now that it is centered, adjust the ScaleX and ScaleY values until you get the desired arc. Try out the other transformation features and see what looks you can achieve.

Enjoy!

Full Source Code

<UserControl x:Class="SLVB.MainPage"
    xmlns="
http://schemas.microsoft.com/winfx/2006/xaml/presentation"
    xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
    xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
    xmlns:dataInput="clr-namespace:System.Windows.Controls;assembly=System.Windows.Controls.Data.Input"
    xmlns:x="
http://schemas.microsoft.com/winfx/2006/xaml"
    mc:Ignorable="d" d:DesignWidth="640" d:DesignHeight="480">

    <UserControl.Resources>
        <Color x:Key="BackgroundColor">#959595</Color>
        <Color x:Key="ForegroundColor">#E5E5E5</Color>
        <Color x:Key="LabelColor">#95002C</Color>
        <SolidColorBrush x:Key="BackgroundBrush"
           Color="{StaticResource BackgroundColor}"/>
        <SolidColorBrush x:Key="ForegroundBrush" 
           Color="{StaticResource ForegroundColor}"/>
        <SolidColorBrush x:Key="LabelBrush"
           Color="{StaticResource LabelColor}"/>

        <Style x:Key="HeaderStyle" TargetType="dataInput:Label">
            <Setter Property="FontFamily" Value="Arial" />
            <Setter Property="FontSize" Value="20" />
            <Setter Property="FontWeight" Value="Bold" />
            <Setter Property="Foreground"
                    Value="{StaticResource LabelBrush}" />
        </Style>
        <Style x:Key="LabelStyle" TargetType="dataInput:Label">
            <Setter Property="Foreground"
                    Value="{StaticResource LabelBrush}" />
        </Style>
        <Style x:Key="LinkStyle" TargetType="HyperlinkButton">
            <Setter Property="Foreground"
                    Value="{StaticResource LabelBrush}"/>
            <Setter Property="FontSize" Value="12"/>
            <Setter Property="Padding" Value="8,4,8,4"/>
        </Style>
    </UserControl.Resources>

    <Grid x:Name="LayoutRoot">
        <Grid.RowDefinitions>
            <!-- Header -->
            <RowDefinition Height="auto"/>
            <!-- Content -->
            <RowDefinition Height="*"/>
        </Grid.RowDefinitions>

        <!-- Header -->
        <Grid x:Name="HeaderGrid" Grid.Row="0"
              Background="{StaticResource BackgroundBrush}">
            <Grid.ColumnDefinitions>
                <!-- Icon -->
                <ColumnDefinition Width="auto"></ColumnDefinition>
                <!-- Text -->
                <ColumnDefinition Width="*"></ColumnDefinition>
            </Grid.ColumnDefinitions>

            <Image Source="Images/Logo.png"
                   Grid.Row="0" Grid.Column="0"
                   Margin="5"
                   HorizontalAlignment="Left"
                   Width="100"/>
            <TextBlock x:Name="ApplicationTextTextBlock"
                       Grid.Row="0" Grid.Column="1"
                       Foreground="{StaticResource ForegroundBrush}"
                       FontSize="32"
                       TextAlignment="Center"
                       Margin="0,0,10,5"
                       VerticalAlignment="Center"
                       Text="Customer Management">
            </TextBlock>
        </Grid>

        <!-- Content -->
        <Grid x:Name="ContentGrid" Grid.Row="1">
            <Grid.Background>
                <RadialGradientBrush>
                    <RadialGradientBrush.RelativeTransform >
                        <TransformGroup>
                            <ScaleTransform CenterX="0.5"
                                     ScaleY="2" ScaleX="2.5"/>
                            <SkewTransform/>
                            <RotateTransform/>
                            <TranslateTransform/>
                        </TransformGroup>
                    </RadialGradientBrush.RelativeTransform>
                    <GradientStop Color="White" Offset="0.95"/>
                    <GradientStop 
                           Color="{StaticResource BackgroundColor}"
                                      Offset=".98"/>
                </RadialGradientBrush>
            </Grid.Background>

            <Grid.ColumnDefinitions>
                <!-- Links -->
                <ColumnDefinition Width="auto"></ColumnDefinition>
                <!-- Divider -->
                <ColumnDefinition Width="auto"></ColumnDefinition>
                <!-- Content -->
                <ColumnDefinition Width="*"></ColumnDefinition>
            </Grid.ColumnDefinitions>

            <StackPanel x:Name="LinksStackPanel" Grid.Column="0">
                <Rectangle x:Name="Divider0"
                   Height="40"/>
                <HyperlinkButton x:Name="CustomerLink"
                   Content="Customer Info"
                   Style="{StaticResource LinkStyle}"/>
                <HyperlinkButton x:Name="AddressLink"
                   Content="Addresses" 
                   Style="{StaticResource LinkStyle}"/>
                <HyperlinkButton x:Name="EmailAddressLink"
                   Content="Email Addresses"
                   Style="{StaticResource LinkStyle}"/>
            </StackPanel>

            <Line Grid.Column="1"  Fill="White" Stretch="Fill"
                 Stroke="{StaticResource LabelBrush}"
                 StrokeThickness="2.5"
                 X1="0" Y1="0" X2="0" Y2="1"/>
            <Canvas Grid.Column="2">
                <dataInput:Label Canvas.Left="17" Canvas.Top="19"
                     Content="Address"
                     Style="{StaticResource HeaderStyle}" />
                <Path Fill="White" Stretch="Fill" 
                     Stroke="{StaticResource LabelBrush}"
                     Height="2.5" Width="370"
                     Canvas.Left="17" Canvas.Top="46"
                     Data="M17,46 L611.5,46"/>

                <dataInput:Label Canvas.Left="33" Canvas.Top="57"
                     Content="Street:"
                     Style="{StaticResource LabelStyle}"/>
                <TextBox x:Name="Street1TextBox"
                     Canvas.Left="102" Canvas.Top="53" Text="" 
                     TextWrapping="NoWrap" Width="250"/>
                <TextBox x:Name="Street2TextBox"
                     Canvas.Left="102" Canvas.Top="81" Text="" 
                     TextWrapping="NoWrap" Width="250"/>

                <dataInput:Label Canvas.Left="34" Canvas.Top="125"
                     Content="City, State:" 
                     Style="{StaticResource LabelStyle}"/>
                <TextBox x:Name="CityTextBox"
                     Canvas.Left="102" Canvas.Top="121"
                     Text="" TextWrapping="NoWrap" Width="200"/>
                <TextBox x:Name="StateTextBox"
                     Canvas.Left="306" Canvas.Top="121"
                     Text="" TextWrapping="NoWrap" Width="45"/>

                <dataInput:Label Canvas.Left="34" Canvas.Top="158"
                     Content="Zip code:" 
                     Style="{StaticResource LabelStyle}"/>
                <dataInput:Label Canvas.Left="34" Canvas.Top="192"
                     Content="Country:" 
                     Style="{StaticResource LabelStyle}"/>
                <TextBox x:Name="CountryTextBox" 
                     Canvas.Left="102" Canvas.Top="186"
                     Text="" TextWrapping="NoWrap" Width="250"/>
                <TextBox x:Name="ZipTextBox"
                     Canvas.Left="102" Canvas.Top="153"
                     Text="" TextWrapping="NoWrap" Width="250"/>
                <Button x:Name="OKButton" Width="75"
                     Canvas.Left="238" Canvas.Top="230"
                     Content="OK" />
                <Button x:Name="CancelButton" Width="75" 
                     Canvas.Left="317" Canvas.Top="230"
                     Content="Cancel" />
            </Canvas>
        </Grid> 
    </Grid>
</UserControl>

with 5 comment(s)
Filed under: , , , ,

I just needed to add a simple image to my Silverlight application to include a company logo. Three hours later …

This post provides information on the image formats supported by Silverlight so that you don't run into the same issues and waste as much time as I did today.

[NOTE: To anyone on the Silverlight team that may read this: Why not have Visual Studio (or even Expression Blend) tell me I was making this mistake instead of letting me waste so much time!!]

First, I followed the *very* simple instructions I found everywhere:

1) Add the image to the Silverlight project.

I put mine in an Images folder in the project.

2) Set the Build Action for the image to "Resource".

3) Add the image tag to the XAML.

Easy peasy. Then why didn't it work? No image appeared. I read/tried everything I could think of but no luck. Three hours later my partner suggested I try another file… AND IT WORKED.

The problem was the image format!

  • bmp: NOT SUPPORTED IN SILVERLIGHT
    No warning, no error, it just does not display the image
  • jpg: It works, but does not support a transparent color
  • gif: It supports a transparent color, but NOT SUPPORTED IN SILVERLIGHT
  • png: It works AND it supports a transparent color

As soon as I replaced my logo.bmp with a logo.png, it worked!

[For help setting up a transparent color, see this Silverlight Tip of the Day.]

So here again are the steps for adding an image to a Silverlight application:

1) Add the image to the Silverlight project.

BE SURE that the file is either a .jpg or .png format image.

image

NOTE: The above screen shot shows the image under my Silverlight VB project, but the same is true for a C# Silverlight project.

2) Set the Build Action for the image to "Resource".

image

3) Add the image tag to the XAML.

<StackPanel Background="BlanchedAlmond">
    <Image Source="Images/Logo.png" Width="100"
           HorizontalAlignment="Left"
           Margin="2"/>
    <data:DataGrid x:Name="CustomerList"
      ItemsSource="{Binding Data, ElementName=CustomerSource}">
</data:DataGrid>           
</StackPanel>

NOTE: Since I set up the file as a resource, I set the source property of the Image tag directly to the location of the image in the Silverlight project.

And the result:

image

Enjoy!

with 8 comment(s)
Filed under: , , , ,

I mentioned in this prior post that RIA Services did not know how to handle properties that have an Enum data type. The problem is not really that RIA Services does not know how to handle the properties; rather it does not have any knowledge of the Enum itself.

Take this example.

In C#:

public enum CustomerTypeOption
{
    Consumer,
    Corporation,
    Education,
    Government
}

private CustomerTypeOption _CustomerType;
public CustomerTypeOption CustomerType {
    get { return _CustomerType; }
    set { _CustomerType = value; }
}

In VB:

Public Enum CustomerTypeOption
    Consumer
    Corporation
    Education
    Government
End Enum

Private _CustomerType As CustomerTypeOption
Public Property CustomerType() As CustomerTypeOption
    Get
        Return _CustomerType
    End Get
    Set(ByVal value As CustomerTypeOption)
        _CustomerType = value
    End Set
End Property

This code resides in a Customer class like the one shown in this prior post. If you create a CustomerService domain services class, Silverlight will generate a proxy class containing all of the Customer class properties. However, the generated code contains a syntax error because it does not understand the CustomerTypeOption type.

This problem can be solved by sharing the Enum with Silverlight following the technique detailed in this prior post and shown below.

image

Let's go through the steps in detail:

1. Add a new file to your business object component and call it Customer.shared.vb (if your component is VB.NET) or Customer.shared.cs (if you are using C#).

2. Add the partial keyword to the class name in the file as shown in the code below.

3. Cut the Enum definition from the Customer class and paste it into this partial class.

In C#:

public partial class Customer
{
    public enum CustomerTypeOption
    {
        Consumer,
        Corporation,
        Education,
        Government
    }
}

In VB:

Partial Public Class Customer

    Public Enum CustomerTypeOption
        Consumer
        Corporation
        Education
        Government
    End Enum

End Class

4. Right click on the Web project linked to your Silverlight project and select Add | Existing Item.

5. In the Add Existing Item dialog, select the file and select Add As Link from the Add button drop down menu.

image

This links the file between your business object class library project and the Web project linked to your Silverlight project.

6. Build the solution.

Silverlight should now have a copy of the Customer.shared.vb or Customer.shared.cs file under the Generated_Code node. Since the Enum is now defined in the Silverlight project, your CustomerType property in the generated code should compile without errors.

Use this technique any time you want to share an Enum between your business object class library project and your Silverlight project.

Enjoy!

So I have read in dozens of places that you can easily share a source code file between your business objects and your Silverlight project. So I thought I would try it. No go.

[To begin with an overview of Silverlight, RIA, and your business objects, start here.]

Supposedly, if you created a file in your business object of the form myName.shared.vb or myName.shared.cs the compiler will automatically generate the file in your Silverlight project as well. This allows you to share the same code between the business object and Silverlight.

Many hours of trying, Bing'ing, and reading later … still no go.

Then I re-read the RIA Services Overview document that is provided with the RIA download. Reading it carefully, it starts to become clear that there are TWO things that a file needs before it will be shared with your Silverlight project:

  1. It must be named appropriately: myName.shared.vb or myName.shared.cs
  2. It must exist in the project that is linked to the Silverlight project.

This second item is the more challenging item because in most cases, your Silverlight project is linked to a Web project.

As far as I know, it is not possible to re-point the link from a Web project to a business object class library component because Silverlight cannot reference a standard class library. It can only reference a Silverlight class library or a Web project.

So I did the following:

  1. Named the file using the .shared.vb/.shared.cs syntax.
  2. Used the Add Link feature of Visual Studio projects to manually define a link between the shared file and the Web project.

The second step required the following:

1. Right click on the Web project and select Add | Existing Item.

2. In the Add Existing Item dialog, select the file and select Add As Link from the Add button drop down menu.

image

This adds the file as a link to the Web project.

Now that the shared file is in the Web project, when you compile Silverlight will generate the copy. You can confirm this by following these steps:

1. Select the Silverlight project.

2. Click the Show All Files button in the Solution Explorer toolbar.

3. Open the Generated_Code node.

The shared file should exist under this node.

Use this technique any time you have code in your business objects that you want to share "as is" with the Silverlight project.

[For a more detailed example of using this technique to share a file, see this post.]

Enjoy!

There may be times when you want RIA Services to ignore a property in your business object. This may be the case if you know you won't ever need to access the property from your Silverlight UI or if you can't make RIA services understand the property properly.

[To begin with an overview of Silverlight, RIA, and your business objects, start here.]

My case is the second scenario. I have yet to figure out how to make RIA Services understand my Enum property. Until I do, I want my code to compile, so I need RIA to ignore any property in my business object that is of an Enum type.

[EDIT 11/10/09: Found a solution to this. Blogged it here.]

I assumed that the DataAnnotations feature would have an attribute to mark a property to be ignored. But no. There is no attribute to do this in the DataAnnotations namespace.

Rather, the property is in the System.Web.DomainServices names. Here is the code example.

NOTE: Be sure to set a reference to System.Web.DomainServices.

In C#:

public enum CustomerTypeOption
{
    Consumer,
    Corporation,
    Education,
    Government
}

private CustomerTypeOption _CustomerType;
[Exclude()]
public CustomerTypeOption CustomerType {
    get { return _CustomerType; }
    set { _CustomerType = value; }
}

In VB:

Public Enum CustomerTypeOption
    Consumer
    Corporation
    Education
    Government
End Enum

Private _CustomerType As CustomerTypeOption
<Exclude()> _
Public Property CustomerType() As CustomerTypeOption
    Get
        Return _CustomerType
    End Get
    Set(ByVal value As CustomerTypeOption)
        _CustomerType = value
    End Set
End Property

This code resides in a Customer class like the one show here. The Exclude attribute causes RIA Services to exclude this property from its generated code.

Use this attribute any time you want a property excluded from the code generated in the Silverlight project. This property is then inaccessible from your Silverlight code.

I would have never figured this out without the help of Colin Blair from the Microsoft Silverlight forums, and from additional information I found on Hatim's Blog post.  Thanks Colin and Hatim!

Enjoy!

Edited 12/7/09: Corrected the credits in the last paragraph.

This post details first how to build a list containing the data to display in a WinForms TreeView control. Then it demonstrates how to use recursion to populate the TreeView control from the list.

[For information on populating a TreeView control from XML, see this link.]

First, create a class that will store the data for the TreeView.

In C#:

public class TreeViewItem
{
    public int ID { get; set; }
    public int ParentID { get; set; }
    public string Text { get; set; }
}

In VB:

Public Class TreeViewItem
    Public Id As Integer
    Public ParentId As Integer
    Public Text As String
End Class

The C# code uses auto-implemented properties to short-cut the code. The VB code is just me being lazy tonight. It is using Public fields instead of Public Properties as it should. (In VS 2010, VB will have auto—implemented properties as well.)

The class defines an Id associated with the item and a ParentId defining the Id of the parent item (that is the item under which this item will appear in the TreeView). It also has a Text property that contains the text of the TreeView node.

In the WinForm containing the TreeView control, add the code to build the list as shown below.

In C#:

List<TreeViewItem> treeViewList = new List<TreeViewItem>();

treeViewList.Add(new TreeViewItem() { 
          ParentID = 0, ID = 1, Text = "Parent node" });
treeViewList.Add(new TreeViewItem() { 
          ParentID = 1, ID = 2, Text = "First child node" });
treeViewList.Add(new TreeViewItem() { 
         ParentID = 1, ID = 3, Text = "Second child node" });
treeViewList.Add(new TreeViewItem() { 
         ParentID = 3, ID = 4, Text = "Child of second child node" });
treeViewList.Add(new TreeViewItem() { 
         ParentID = 3, ID = 5, Text = "Child of second child node" });

PopulateTreeView(0, null);

In VB:

Private treeViewList As New List(Of TreeViewItem)

treeViewList.Add(New TreeViewItem() With { _
        .ParentId = 0, .Id = 1, .Text = "Parent node"})
treeViewList.Add(New TreeViewItem() With { _
        .ParentId = 1, .Id = 2, .Text = "First child node"})
treeViewList.Add(New TreeViewItem() With { _
        .ParentId = 1, .Id = 3, .Text = "Second child node"})
treeViewList.Add(New TreeViewItem() With { _
        .ParentId = 3, .Id = 4, .Text = "Child of second child node"})
treeViewList.Add(New TreeViewItem() With { _
        .ParentId = 3, .Id = 5, .Text = "Child of second child node"})

PopulateTreeView(0, Nothing)

This code defines a generic List that contains the set of TreeViewItem instances. The Add method of the list sets the data into the list. It then calls the PopulateTreeView method (shown below).

The PopulateTreeView method uses recursion to populate the TreeView from the list.

In C#:

private void PopulateTreeView(int parentId, TreeNode parentNode)
{
    var filteredItems = treeViewList.Where(item => 
                                item.ParentID == parentId);

    TreeNode childNode;
    foreach (var i in filteredItems.ToList())
    {
        if (parentNode == null)
            childNode = treeView1.Nodes.Add(i.Text);
        else
            childNode = parentNode.Nodes.Add(i.Text);

        PopulateTreeView(i.ID, childNode);
    }
}

In VB:

Private Sub PopulateTreeView(ByVal parentId As Integer, _
                             ByVal parentNode As TreeNode)
    Dim filteredItems = treeViewList.Where(Function(item) _
                                     item.ParentId = parentId)

    Dim childNode As TreeNode
    For Each i In filteredItems.ToList()
        If parentNode Is Nothing Then
            childNode = TreeView1.Nodes.Add(i.Text)
        Else
            childNode = parentNode.Nodes.Add(i.Text)
        End If
        PopulateTreeView(i.Id, childNode)
    Next
End Sub

The PopulateTreeView method has two parameters: parentId and parentNode. The parentId is the Id value associated with the parent node. The code will find all items in the list with the defined parent Id. The parentNode is the TreeView node under  which the items are added.

The filteredItems variable contains the results of a lambda expression finding all of the items in the list with the passed in parentId.

The code then loops through those items and adds the nodes to the parent node.

It then calls itself, making the method recursive. The method call passes in the node's Id and the node itself. This will cause the method to load all of its child nodes.

When you run the code, the TreeView should appear as follows:

image

Enjoy!

with 1 comment(s)
Filed under: , , , ,

One of my friends sends an email message to me telling me about their great new job and letting me know that they have a new email address. I dutifully update my Outlook Address book and fix the address.

Five unanswered emails later, I realize that I have been sending email to the WRONG email address. Even though I fixed my Outlook Address book, the Outlook Most Recently Used (MRU) list still has the OLD email address. What's to be done?

Then Beth Massi shared with me an Outlook tip that I want to pass along: You can delete items from the MRU in Outlook!

Say I am typing in John's name:

image

If I am not watching closely, I will send it to the wrong address again (nowhere.com).

To prevent this mistake:

  1. Highlight the incorrect address.
  2. Press the Del key.

The unwanted MRU entry is then deleted.

Enjoy!

with 1 comment(s)
Filed under:

With RIA Services, it is easy to use your own business objects in your Silverlight application… once you have the basic plumbing in place. However, there are quite a few steps required to set up that plumbing. This post details the process of hooking up your business objects, RIA Services, and Silverlight.

So … you follow best practices and build business objects for all of the entities involved in your application. Or maybe you generate those business objects with something like Entity Framework (EF), Linq to SQL or other similar tool. In any case, you now want to use those business objects from a Silverlight application.

Seems like it should be easy, right? Just define a reference from your Silverlight application to your business object component and proceed just like with your WinForms or ASP.NET application. But no.

Silverlight does not allow you to set a reference to a non-Silverlight component. There are a number of ways you can deal with this:

  1. Use a Silverlight class library project type and build your business objects in there. Not a good solution if you are using those same business objects with other user interfaces.
  2. Build a WCF service that provides your business objects to your Silverlight application. You can find out more about this option here.
  3. Use Rich Internet Application (RIA) Services. This is a new Microsoft technology that is currently out in preview.

This post demonstrates option #3: Using RIA Services. The example presented in this post uses business objects you build yourself. These "home made" business objects are often referred to as POCO, or plain old CLR objects. Use the techniques presented in this post any time you start a new Silverlight application and want to access your POCOs from that application.

[To use RIA with Entity Framework, there is video walkthrough here.]

The basic idea behind RIA Services is to use an ASP.NET application between your business object component and your Silverlight application to provide the communication between the two as shown below.

image

Starting right to left, you build your business objects in a Class Library component. This example uses a Customer class to define a customer and a Customers class to provide the list of customers.

The ASP.NET application has a standard project reference to the POCO class library. The ASP.NET application includes a Domain Service class, called CustomerService in this example, that calls the desired methods in the POCO class library.

The Silverlight application has an RIA Services link to the ASP.NET Web application. When the Silverlight application is compiled, it generates a client-side copy of the entity referenced by the ASP.NET application. In this case, it generates a Customer class. It also generates a entity context (CustomerContext in this case) that can be used for Silverlight data binding.

The remainder of this post walks through this process one step at a time.

Prerequisites

Before you can begin, there are some prerequisite steps:

  1. If you don't have it, download and install Silverlight 3 using the information provided here.
  2. Download and install RIA Services using the information provided here. (You may have already done this if you followed all of the instructions in step #1)
  3. Create a new Visual Studio Solution.
  4. Create a new Class Library project in that solution and build your business objects. This example uses the Customer and Customers classes defined below.

In C#:

public class Customer
{
    public int CustomerId { get; set; }
    public string LastName { get; set; }
    public string FirstName { get; set; }
    public string EmailAddress { get; set; }
}


public class Customers
{
   public static List<Customer> Retrieve()
   {
      List<Customer> custList = new List<Customer>
                    {new Customer()
                          { CustomerId = 1,
                            FirstName="Bilbo",
                            LastName = "Baggins",
                            EmailAddress = "bb@hob.me"},
                    new Customer()
                          { CustomerId = 2,
                            FirstName="Frodo",
                            LastName = "Baggins",
                            EmailAddress = "fb@hob.me"},
                    new Customer()
                          { CustomerId = 3,
                            FirstName="Samwise",
                            LastName = "Gamgee",
                            EmailAddress = "sg@hob.me"},
                    new Customer()
                          { CustomerId = 4,
                            FirstName="Rosie",
                            LastName = "Cotton",
                            EmailAddress = "
rc@hob.me"}};
      return custList;
   }
}

In VB:

Public Class Customer

    Private _CustomerId As Integer
    Public Property CustomerId() As Integer
        Get
            Return _CustomerId
        End Get
        Set(ByVal value As Integer)
            _CustomerId = value
        End Set
    End Property

    Private _FirstName As String
    Public Property FirstName() As String
        Get
            Return _FirstName
        End Get
        Set(ByVal value As String)
            _FirstName = value
        End Set
    End Property

    Private _LastName As String   
    Public Property LastName() As String
        Get
            Return _LastName
        End Get
        Set(ByVal value As String)
            _LastName = value
        End Set
    End Property

    Private _EmailAddress As String
    Public Property EmailAddress () As String
        Get
            Return _EmailAddress
        End Get
        Set(ByVal value As String)
            _EmailAddress = value
        End Set
    End Property
End Class

Public Class Customers

    Public Shared Function Retrieve() As List(Of Customer)
        Dim custList As New List(Of Customer)
        custList.Add(New Customer With {.CustomerId = 1, _
                                        .LastName = "Baggins", _
                                        .FirstName = "Bilbo", _
                                        .EmailAddress = "bb@hob.me"})
        custList.Add(New Customer With {.CustomerId = 2, _
                                        .LastName = "Baggins", _
                                        .FirstName = "Frodo", _
                                        .EmailAddress = "fb@hob.me"})
        custList.Add(New Customer With {.CustomerId = 3, _
                                        .LastName = "Gamgee", _
                                        .FirstName = "Samwise", _
                                        .EmailAddress = "sg@hob.me"})
        custList.Add(New Customer With {.CustomerId = 4, _
                                        .LastName = "Cotton", _
                                        .FirstName = "Rosie", _
                                        .EmailAddress = "rc@hob.me"})
        Return custList
    End Function

End Class

The C# code here uses auto-implemented properties to shorten the property syntax. The VB code uses the full property syntax.

In a real application, the Retrieve method would collect the data from the database. This example uses hard-coded values to make it easier for you to try this code without having to set up data access.

Decorating your Business Objects

Once you have the prerequisites complete, you need to add some attributes to your business objects so they are recognized by RIA Services when it generates the Silverlight entity.

1. Open the solution containing your business objects.

2. Open the Class Library project properties window for the project containing your business objects.

3. Add a reference to System.ComponentModel.DataAnnotations.

image

I had two of these. Be sure to pick the one from the Microsoft SDKs\RIA Services directory.

This namespace contains the attributes you need to annotate your business objects for RIA Services.

4. Add the Key attribute on the property that represents the unique key of the business object.

In C#:

[System.ComponentModel.DataAnnotations.Key()]
public int CustomerId { get; set; }

In VB:

Private _CustomerId As Integer
<System.ComponentModel.DataAnnotations.Key()> _
Public Property CustomerId() As Integer
    Get
        Return _CustomerId
    End Get
    Set(ByVal value As Integer)
        _CustomerId = value
    End Set
End Property

In this example, CustomerId is the unique key, so it is marked with the Key attribute.

5. Optionally, add RIA attributes for validation as desired. (More on this in a future post.)

Adding the Silverlight Project

Now that the business objects are ready, you can add the Silverlight project to your solution. This will automatically generate the associated ASP.NET project as well.

1. Add a new Silverlight Application to your solution.

image

You can create your Silverlight project in VB or in C#.

2. Be sure to enable RIA Services.

image

3. Click OK.

Visual Studio creates both the Silverlight project and the associated ASP.NET project. At runtime, the ASP.NET project launches the Silverlight project. The ASP.NET project also provides access to your business objects through RIA services.

NOTE: If you already have a Silverlight project that you wish to use with RIA Services, you can enable .NET RIA Services in the Silverlight project properties window:

image

Adding the Domain Service Classes

The ASP.NET application provides the communication between your business objects and the Silverlight application. This is accomplished with RIA Services by creating a set of Domain Service classes in the ASP.NET application.

1. Add a Domain Service class to the ASP.NET application.

image

2. Be sure to enable client access.

image

Notice that the bottom of this dialog is empty. If you used Entity Framework to generate your entities, they would appear in this dialog. But since this example uses POCOs, there won't be any entities displayed.

3. Click OK.

Visual Studio creates the basic structure of your Domain Service class as shown below.

In C#:

namespace SLCSharp.Web
{
    using System;
    using System.Collections.Generic;
    using System.ComponentModel;
    using System.ComponentModel.DataAnnotations;
    using System.Linq;
    using System.Web.Ria;
    using System.Web.Ria.Data;
    using System.Web.DomainServices;

    // TODO: Create methods containing your application logic.
    [EnableClientAccess()]
    public class CustomerService : DomainService
    {
    }
}

In VB:

Option Strict Off
Option Explicit On

Imports System
Imports System.Collections.Generic
Imports System.ComponentModel
Imports System.ComponentModel.DataAnnotations
Imports System.Linq
Imports System.Web.DomainServices
Imports System.Web.Ria
Imports System.Web.Ria.Data

'TODO: Create methods containing your application logic.
<EnableClientAccess()>  _
Public Class CustomerService
    Inherits DomainService
End Class

NOTE: IMMEDIATELY change the Option Strict Off to Option Strict ON OR remove the lines entirely if you have them both on by default in your Compile options.

You can also remove any import statements for namespaces you already import through the References tab of the Properties window.

Accessing Your Business Objects From the Domain Service Class

The code to access your business objects goes into the Domain Service class created above. You can think of the Domain Service class as a wrapper around your business object members that provides a way for Silverlight to access the object properties and methods.

Any time you need to access any properties or call any methods on your business objects, you need to write a wrapper for that access in the Domain Service class.

Normally, you create one Domain Service class for each entity. So if your business objects included Customer/Customers, Purchase/Purchases, Invoice/Invoices, for example, you would create a CustomerService, PurchaseService, and InvoiceService class.

1. In your ASP.NET project containing your Domain Service class, set a reference to your business object component.

image

My sample project has two sets of business objects, one in VB and one in C#. You can access business objects in either language.

2. In the Domain Service class, import the namespace for the business object component.

In C#:

using BoCSharp;

In VB:

Imports BoVB

Or if you are using VB, import this namespace using the References tab of the Properties window:

image

3. Add the code that wraps the functionality of your business object.

For the purposes of this post, the code retrieves the set of customers.

In C#:

public IEnumerable<Customer> GetCustomers()
{
    return Customers.Retrieve();
}

In VB:

Public Function GetCustomers() As IEnumerable(Of Customer)
    Return Customers.Retrieve()
End Function

Note that this must be a method and not a property. If you attempt to use a property here, the compiler won't generate the required code.

The GetCustomers method simply calls the static/shared Retrieve method of the Customers class (defined at the beginning of this post).

Accessing the Domain Service Class from Silverlight

Finally! You are ready to use your business object from Silverlight by way of the Domain Service class.

1. Open the Silverlight Application project.

2. This example uses a grid to display the customer data, so set a reference to System.windows.Controls.Data.

3. To use the RIA Service controls, set a reference to System.Windows.Ria.Controls.

3. Open the MainPage.xaml file that was created for you in the Silverlight Application project.

4. Set up the XML namespaces.

<UserControl x:Class="SLCSharp.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:data="clr-namespace:System.Windows.Controls;assembly=System.Windows.Controls.Data"
 xmlns:riaControls="clr-namespace:System.Windows.Controls;assembly=System.Windows.Ria.Controls"
 xmlns:domain="clr-namespace:SLCSharp.Web"
mc:Ignorable="d" d:DesignWidth="640" d:DesignHeight="480">

NOTE: If you copy and paste these namespaces into your application, be sure that you change the values shown in red. The x:Class is the name of the associated code behind class file. the xmlns:domain is the name of the associated ASP.Net application namespace.

These namespaces are as follows:

  • xmlns - Default for Silverlight and WPF.
  • xmlns:x – XAML-defined language elements for Silverlight and WPF, required for even basic features such as mapping a XAML file to its code behind file using x:Class.
  • xmlns:data – XAML data controls, such as the DataGrid.
  • xmlns:riaControls – RIA Service controls, such as the DomainDataSource
  • xmlns:domain – Defines the associated ASP.NET namespace, required when using RIA services.

5. Add code to display a bound grid.

<Grid x:Name="LayoutRoot">
    <riaControls:DomainDataSource x:Name="CustomerSource"
                        QueryName="GetCustomers" AutoLoad="True">
        <riaControls:DomainDataSource.DomainContext>
            <domain:CustomerContext/>
        </riaControls:DomainDataSource.DomainContext>
    </riaControls:DomainDataSource>

    <data:DataGrid x:Name="CustomerList"
          ItemsSource="{Binding Data, ElementName=CustomerSource}">
    </data:DataGrid>
</Grid>

This code sets up an RIA Services DomainDataSource defining the source of the data for this UserControl. It defines a name (CustomerSource) and a QueryName (GetCustomers). The query name must match the name of the method to call in the Domain Service class.

It also sets a DomainContext, which associates the DomainDataSource with the Domain Service. This must match the name of the DomainContext in the generated code. Intellisense should help you define the correct name.

The DataGrid element uses the ItemsSource attribute to define the binding source. In this case, it is CustomerSource. This name must match the name given to the DomainDataSource.

6. Run it.

If everything works, it should appear as follows:

image

Now you can use your mad skills with styles and other Silverlight features to make this look nice.

Wow! That seemed like a lot of work.

The good news is that once you set this up, adding more features is easy:

  • Add appropriate wrappers to the ASP.Net project using the Domain Service classes.
  • Use the wrappers as needed from your Silverlight application.

Enjoy!

Gradients are a good way to make your application more visually interesting. They turn a flat solid color into something more natural and appealing to the eye by creating an illusion of light and shadow.

For example, compare this image which uses flat colors:

image

to this image that uses gradient colors:

image

For some great uses of gradient effects in user interface designs, see this link.

If you are not familiar with the terminology, a gradient is basically a blending of colors with an even graduation from one set of color values to another set of color values. Notice in the second image above, the white gradually blends into blue from the upper left corner to the lower right corner.

In Silverlight and WPF, you can set colors for just about anything, and wherever you can set a color you can often set a gradient. These examples color the background of the Canvas, but you could also paint Buttons or Rectangles or whatever.

In XAML, there are two primary types of gradients: Linear and Radial.

Linear Gradient

A linear gradient defines the graduation of colors along a line. The example below shows a linear gradient following a line from white at the top to blue at the bottom:

image

The XAML code required to set the linear gradient shown above is as follows:

<Canvas>
    <Canvas.Background>
        <LinearGradientBrush StartPoint=".5,0" EndPoint=".5,1">
            <GradientStop Color="White" Offset="0"/>
            <GradientStop Color="LightSteelBlue" Offset="1"/> 
        </LinearGradientBrush>
    </Canvas.Background>

</Canvas>

This specific code sets the gradient for a Canvas background, but you could use it anywhere a gradient is allowed.

The first properties you need to consider when defining your gradient are the StartPoint and EndPoint properties. These properties define where the gradient line begins and where it ends.

Think of the canvas as a rectangle with the (0,0) point in the upper left corner and the (1,1) point in the lower right corner as shown below:

image

If you set the StartPoint to 0,0 and the EndPoint to 1,1, the gradient will start in the upper left and end in the lower right as shown in the Address dialog example shown at the top of this post. (This is the default value, so if you don't set the StartPoint and EndPoint properties, this is the gradient you will get.)

By setting the StartPoint to .5,0 and EndPoint to .5,1, the gradient line begins in the middle top of the rectangle and ends in the middle bottom, creating the affect shown above.

If you want the gradient to appear from left to right, set the StartPoint to 0,.5 and the EndPoint to 1,.5. For right to left, set the StartPoint to 1,.5 and the EndPoint to 0,.5.

You can use any point in the rectangle as the StartPoint and EndPoint to create different effects. For example, you could set the StartPoint to .2,.2 and the EndPoint to .8,.8 to create this effect:

image

Notice the upper left corner is completely white and the lower right corner is completely blue. The gradient does not begin until .2,.2 and then ends at .8,.8.

The other important property to use when working with a linear gradient is the GradientStop. It defines the colors and where they start. The first GradientStop in this example is set to white and starts at an Offset of 0. The second GradientStop is set to LightSteelBlue at an Offset of 1.

Think of the Offset as the point on the gradient line where 0 is the origin of the line and 1 is the termination of the line. Note that if your StartPoint and EndPoint properties are not at a point of origin (such as .2,.2 and .8,.8), the Offset will still begin at the origin and end at the termination of the gradient line.

You can use any number of GradientStops (with a minimum of two) to include multiple colors in your gradient. For example, this gradient changes from White, to LightSteelBlue, to SteelBlue, back to LightSteelBlue and then White:

<Canvas>
    <Canvas.Background>
        <LinearGradientBrush StartPoint="0,0" EndPoint="1,1" >
            <GradientStop Color="White" Offset="0"/>
            <GradientStop Color="LightSteelBlue" Offset=".2"/>
            <GradientStop Color="SteelBlue" Offset=".4"/>
            <GradientStop Color="LightSteelBlue" Offset=".8"/>
            <GradientStop Color="White" Offset="1"/>
        </LinearGradientBrush>
    </Canvas.Background>
</Canvas>

And the effect is as follows:

image

Radial Gradient

A radial gradient defines the graduation of colors that radiate outward from an origin. The example below shows a radial gradient radiating out from the center:

image

The XAML code required to set a radial gradient is as follows:

<Canvas>
    <Canvas.Background>
        <RadialGradientBrush GradientOrigin=".5,.5">
            <GradientStop Color="White" Offset="0"/>         
            <GradientStop Color="LightSteelBlue" Offset="1"/> 
        </RadialGradientBrush>
    </Canvas.Background>

This specific code sets the gradient for a Canvas background, but you could use it anywhere a gradient is allowed.

The first property you need to consider when defining your radial gradient is the GradientOrigin. It defines where the gradient begins.

Think of the canvas as a rectangle with the (0,0) point in the upper left corner and the (1,1) point in the lower right corner as shown below:

image
In this example, the gradient radiates out from the center point, which is .5,.5. (This is the default value, so if you don't set the GradientOrigin property, this is the gradient origin you will get.)

The other important property to use when working with a radial gradient is the GradientStop. It defines the colors and where they start. The first GradientStop in this example is set to white and starts at an Offset of 0. The second GradientStop is set to LightSteelBlue at an Offset of 1.

This GradientStop property in the radial gradient is similar to the linear gradient. The primary difference is that the gradient line begins at the GradientOrigin and radiates outward.

You can use any number of GradientStops (with a minimum of two) to include multiple colors in your gradient. For example, this gradient has an origin of .2,.2. It then changes from White, to LightSteelBlue, to SteelBlue, back to LightSteelBlue and then White:

<Canvas>
    <Canvas.Background>
        <RadialGradientBrush GradientOrigin=".2,.2">
            <GradientStop Color="White" Offset="0"/>
            <GradientStop Color="LightSteelBlue" Offset=".2"/>
            <GradientStop Color="SteelBlue" Offset=".4"/>
            <GradientStop Color="LightSteelBlue" Offset=".8"/>
            <GradientStop Color="White" Offset="1"/>
        </RadialGradientBrush>
    </Canvas.Background>
</Canvas>

And the effect is as follows:

image

Feel free to experiment with both types of gradients to produce interesting visual effects.

Enjoy!

with 6 comment(s)
Filed under: , , , , ,
More Posts Next page »