Blog Archives

Simplifying the Process of Calling a WCF Service from Silverlight (or any .NET Application)

I’m currently working on a large Silverlight 3 project for a client that will ultimately have hundreds of calls from Silverlight to WCF service operations.  Initially I did things the standard way which is to define a client-side class that’s used to call the WCF service using the generated service proxy (I’m basically following a service agent type of pattern).  The service agent class handles instantiating the WCF proxy object and calling the appropriate operations.  Here are a few examples….each of these methods accept the parameters passed to the WCF service as well as a callback method that’s called when the service returns data.

public void GetJobs(EventHandler<GetJobsCompletedEventArgs> callback)
{
    IJobPlanService proxy = GetProxy();
    proxy.GetJobsCompleted += callback;
    proxy.GetJobsAsync();
}

public void GetEmployees(EventHandler<GetEmployeesCompletedEventArgs> callback)
{
    IJobPlanService proxy = GetProxy();
    proxy.GetEmployeesCompleted += callback;
    proxy.GetEmployeesAsync();
}

public void GetEmployeesByJobID(int jobID, EventHandler<GetEmployeesByJobIDCompletedEventArgs> callback)
{
    IJobPlanService proxy = GetProxy();
    proxy.GetEmployeesByJobIDCompleted += callback;
    proxy.GetEmployeesByJobIDAsync(jobID);
}

public void GetTimeSheetViews(int? jobID, int? empID, string weekEnding, EventHandler<GetTimeSheetViewsCompletedEventArgs> callback)
{
    IJobPlanService proxy = GetProxy();
    proxy.GetTimeSheetViewsCompleted += callback;
    proxy.GetTimeSheetViewsAsync(jobID, empID, weekEnding);
}

After writing nearly the same code method after method (aside from changes in the event name and method name) I opted for a simplified approach that allows me to call any WCF service operation using a single method.  That way I don’t have to continually write wrapper methods just to call a service operation which really seems pointless to me.  Some people will like the following code since it can eliminate a ton of code from your Silverlight project (the code would work fine outside of Silverlight too by the way) while others won’t like it since reflection is involved (I personally don’t view reflection as a big deal here since it’s running on the client-side).

Here’s how it works:

  1. The CallService method shown next accepts an async callback delegate as well as the parameter data that should be passed to the service.  In other words, tell it what method to call once the service returns data as well as what data to pass up to the service operation.
  2. When calling the CallService method you must supply the type of EventArgs that will be returned from the service call.  This is done using generics. 
/// <summary>
/// Used to call a WCF service operation.
/// </summary>
/// <typeparam name="T">The type of EventArgs that will be returned by the service operation.</typeparam>
/// <param name="callback">The method to call once the WCF call returns (the callback).</param>
/// <param name="parameters">Any parameters that the service operation expects.</param>
public void CallService<T>(EventHandler<T> callback, params object[] parameters) where T : EventArgs
{
    string action = typeof(T).Name.Replace("CompletedEventArgs", String.Empty);
    IJobPlanService proxy = new JobPlanServiceClient();
    Type t = typeof(JobPlanServiceClient);
    t.GetEvent(action + "Completed").AddEventHandler(proxy, callback);
    t.InvokeMember(action + "Async", BindingFlags.InvokeMethod, null, proxy, parameters);
}

An example of using the CallService method is shown next:

public void GetAreasByJobID()
{
    _Proxy.CallService<GetAreasByJobIDCompletedEventArgs>((s,e) => this.CurrentTimeSheetView.Areas = e.Result,
        this.TimeSheetJob.JobID);
}

The GetAreasByJobID method passes a type of GetAreasByJobIDCompletedEventArgs as the generic type to CallService, defines the async callback using a lambda expression (although a separate method could certainly be defined) and passes the parameter that the service operation expects (multiple parameters can be passed when needed).  CallService knows which WCF service operation to call based upon the generic type that’s passed since WCF service proxy object’s always create EventArgs classes that end with “CompletedEventArgs”.  The method simply removes the string “CompletedEventArgs” to get the operation name, instantiates the service proxy object and then uses reflection to wire up the appropriate event and async method to call.  If you use several different service proxy objects you could certainly handle that as well with generics.  I played around with ChannelFactory as well but opted for this approach mainly because it was more straightforward in my opinion and because I wanted to use the generated WCF proxy anyway.

If scalability was an issue (which is not the case here since this runs on the client-side) then I’d go with the standard approach of using the WCF proxy methods that are generated as shown in the first section of code above.  For this application it’s eliminated hundreds of lines of redundant code though (which would’ve grown to 1000s since we’re only 25% of the way through the project) which means we have less to worry about plus this eliminates the time it would’ve taken to write the wrapper methods.  Ultimately I view this as a significant benefit to future maintenance since there’s less wrapper code that can get out of sync. 

 

Logo

For more information about onsite, online and video training, mentoring and consulting solutions for .NET, SharePoint or Silverlight please visit www.thewahlingroup.com/.

Silverlight 3 Released – Exciting Stuff Here!

Silverlight 3 is now officially released and available for download!  It’s definitely exciting for me personally because my company has been working on a Silverlight 3 client application for the past few months and really enjoyed the new features.  Here are some of the key features Silverlight 3 brings to the table:

Out of Browser Support  Users can run a Silverlight 3 application in the browser or run it directly from the desktop even when they’re not connected to the Internet.  This supports several sync scenarios that can be useful when a connection isn’t always available.
Enhanced Graphics Support New features include GPU acceleration, perspective 3D support, bitmap and pixel APIs for dynamically generating images, videos, etc.  Animations can also be eased in and out and perform many other cool effects.  Pixel shaders allow objects to have different effects applied to them such as shadows and blurs.
New Controls Silverlight 3 provides many new controls that can be used to build solid line of business (LOB) applications.  Controls can be bound to each other using element to element binding and validation can also be perform more easily.  Several new controls are also available in the Silverlight 3 toolkit released by Microsoft.
Better Navigation Navigating between Silverlight “pages” is now built in along with better search engine optimization (SEO) support and deep linking.
Enhanced Text Rendering One knock against Silverlight 2 was that text didn’t render as clearly as it should in some situations.  Silverlight 3 includes a major update to the text rendering engine.  Text renders very crisply now.
Enhanced Styles  Silverlight 3 provides merged dictionary support allowing multiple style files to be merged into an application (similar to how a standard web application can use multiple CSS files).  This allows themes and other styles to be switched much more easily.  Styles can also be based on other styles (similar to inheritance in OO languages).
Faster Transfer of Data  Windows Communication Foundation (WCF) support was available in Silverlight 2 but Silverlight 3 now adds support for binary XML serialization which allows data to be transferred between a Silverlight application and a WCF service much faster than before.
Assembly Caching  Silverlight 3 allows developers to store assemblies on a central company server and Silverlight 3 applications can then download them as needed rather than downloading everything up front in a single .xap file.  This can significantly speed-up application load times.
Enhanced Networking Support  A new client networking (ClientHttp) stack is available that supports more verbs than simply GET/POST.  Applications that fully leverage REST APIs will benefit from this new feature.
HD Media Support  Silverlight 3 includes support for GPU acceleration (as mentioned earlier) as well as 1080p HD videos to be played over the web.  New codec support for H.264, AAC audio and MPEG-4 content is also included.  If you need to provide media solutions then Silverlight covers all of the major scenarios now.

Some other changes to Visual Studio and Silverlight applications in general:

  • The Visual Studio 2008 designer has been removed for Silverlight 3 applications.  A lot of developers turned it off anyway so all of the dev efforts have gone into the designer that’ll be available in Visual Studio 2010.  Expression Blend 3 can also be used of course.
  • The Silverlight ASP.NET server control isn’t used now.  It simply emitted the object and associated params tags anyway.
  • Expression Blend 3 has also been released (currently a release candidate) along with a new feature called SketchFlow.  SketchFlow allows application prototypes and mockups to be created more easily in order to get customer feedback…very cool if you haven’t seen it.  Although I rarely used Blend for coding previously, it now includes code intellisense which is a nice feature to have and adds support for behaviors, importing Adobe Photoshop and Illustrator files and integrating sample data into applications.

To get started with Silverlight 3 visit silverlight.net/getstarted/

Silverlight 3 Jumpstart

If you’re interested in a focused look at some of the new features in Silverlight 3 check out David Yack’s new Silverlight 3 JumpStart Book.

 

Logo

For more information about onsite, online and video training, mentoring and consulting solutions for .NET, SharePoint or Silverlight please visit www.thewahlingroup.com/.

Validating Properties in Silverlight Classes

Silverlight classes rely on the INotifyPropertyChanged interface and associated PropertyChanged event it contains to ensure that data binding stays up-to-date in an application.  It’s a great feature because you don’t have to worry about ensuring that changes to object properties are propagated back to controls….Silverlight handles refreshing control values for you automatically as long as the class that’s being data bound implements INotifyPropertyChanged and property set blocks raise the event.  I use a fairly standard pattern for defining my properties and raising property changed events (there are several options for doing this…see a nice list here).  Here’s an example of a property:

public Job CurrentJob
{
    get
    {
        return _CurrentJob;
    }
    set
    {
        if (value != _CurrentJob)
        {
            _CurrentJob = value;
            OnPropertyChanged("CurrentJob");
        }
    }
}

The problem is that I tend to change property names from time to time within an application and use the Visual Studio refactor feature to do it.  This works great for changing the property name or field name but doesn’t change the quoted value passed to OnPropertyChanged.  I’ve run into a few issues where I missed renaming the string value and decided that I needed to be more proactive with validating the value.  I ended up going with the following validation code after looking at a few options:

protected void OnPropertyChanged(string propertyName)
{
    CheckPropertyExists(propertyName);

    if (this.PropertyChanged != null)
    {
        this.PropertyChanged.Invoke(this, new PropertyChangedEventArgs(propertyName));
    }
}

[Conditional("DEBUG")]
private void CheckPropertyExists(string propertyName)
{
    Type type = this.GetType();
    if (type.GetProperty(propertyName) == null)
    {
        string errorMessage = string.Format("Invalid property found: {0} in {1}", propertyName, type.FullName);
        Debug.Assert(false, errorMessage);
    }
}


Originally I used #if..#endif statements to conditionally add or take out the CheckPropertyExists method until I came across a nice post by Josh Smith where he used the Conditional attribute in a WPF application.  If you’re new to it, the compiler will leave CheckPropertyExists in the compiled code if you’re in debug mode.  If you switch to release mode it’ll take it out which is good since at that point you wouldn’t want to slow down the application with the reflection statements. 

With the CheckPropertyExists method in place I now have an easy way to know if I missed renaming a property value that’s passed to OnPropertyChanged which saves a few headaches.  I put the code in my ViewModelBase class so I only have to write it once and have it available for just about everything I do in Silverlight.  Here’s the message that I get if there’s a problem:

image

Update:  The same day I posted this I ran into a mis-named property.  The assert message popped up and I fixed it within a matter of seconds instead of searching through the code for minutes (or longer) to locate the problem. 

 

Logo

For more information about onsite, online and video training, mentoring and consulting solutions for .NET, SharePoint or Silverlight please visit www.thewahlingroup.com/.

 

Creating a ComboBox Style AutoCompleteBox Control in Silverlight

Silverlight’s AutoCompleteBox control provides a nice way to filter a list of items as the user types into a TextBox.  If you haven’t used it before you can see an example here.  In a previous post I showed how you could customize AutoCompleteBox to make it look and feel more like an editable ComboBox so that users can click a down arrow to see the list of items and also filter the items by typing into the TextBox. 

image

The solution I wrote about in the previous post relied upon a custom control template which works fine but you may want to encapsulate that functionality into a re-useable control so that you don’t have to worry about adding the template into each application project.  Fortunately, it’s not too hard to derive from AutoCompleteBox and create a custom control.  Here are the steps I went through to build a derived version of AutoCompleteBox that I call AutoCompleteComboBox.

Step 1: Create a Silverlight Class Library

Create a new Silverlight class library and add a class named AutoCompleteComboBox into it.  Derive the class from AutoCompleteBox:

public partial class AutoCompleteComboBox : AutoCompleteBox

Once the class is created add a folder into the project named Themes that contains a file named Generic.xaml in it.  Right-click on Generic.xaml, select Properties and ensure that the Build Action is set to Resource.

Step 2: Define the Control’s Template

Since AutoCompleteComboBox needs to look like a ComboBox I took a template available in one of the AutoCompleteBox toolkit samples that added the drop down arrow (normally seen on the ComboBox control) and tweaked it a little bit.  The template code needs to go in the Generic.xaml file that was created in the previous step.  Here’s what my Generic.xaml file looks like.  You’ll see that there’s a ComboToggleButton style which is referenced by the AutoCompleteComboBox style.

<ResourceDictionary
  xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
  xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
  xmlns:system="clr-namespace:System;assembly=mscorlib"
  xmlns:custom="clr-namespace:MyCustomControls">

    <!-- AutoCompleteBox Styles -->

    <!-- Custom toggle button template -->
    <Style x:Name="ComboToggleButton" TargetType="ToggleButton">
        <Setter Property="Foreground" Value="#FF333333"/>
        <Setter Property="IsTabStop" Value="False" />
        <Setter Property="Background" Value="#FF1F3B53"/>
        <Setter Property="Padding" Value="0"/>
        <Setter Property="Template">
            <Setter.Value>
                <ControlTemplate TargetType="ToggleButton">
                    <Grid>
                        <Rectangle Fill="Transparent" />
                        <ContentPresenter
                            x:Name="contentPresenter"
                            Content="{TemplateBinding Content}"
                            ContentTemplate="{TemplateBinding ContentTemplate}"
                            HorizontalAlignment="{TemplateBinding HorizontalContentAlignment}"
                            VerticalAlignment="{TemplateBinding VerticalContentAlignment}"
                            Margin="{TemplateBinding Padding}"/>
                    </Grid>
                </ControlTemplate>
            </Setter.Value>
        </Setter>
    </Style>

    <!-- Custom control template used for the IntelliSense sample -->
    <Style TargetType="custom:AutoCompleteComboBox">

        <!-- ComboBox should not perform text completion by default -->
        <Setter Property="IsTextCompletionEnabled" Value="False" />

        <!-- The minimum prefix length should be 0 for combo box scenarios -->
        <Setter Property="MinimumPrefixLength" Value="1" />

        <!-- Regular template values -->
        <Setter Property="Background" Value="#FF1F3B53"/>
        <Setter Property="IsTabStop" Value="False" />
        <Setter Property="HorizontalContentAlignment" Value="Left"/>
        <Setter Property="BorderBrush">
            <Setter.Value>
                <LinearGradientBrush EndPoint="0.5,1" StartPoint="0.5,0">
                    <GradientStop Color="#FFA3AEB9" Offset="0"/>
                    <GradientStop Color="#FF8399A9" Offset="0.375"/>
                    <GradientStop Color="#FF718597" Offset="0.375"/>
                    <GradientStop Color="#FF617584" Offset="1"/>
                </LinearGradientBrush>
            </Setter.Value>
        </Setter>
        <Setter Property="Template">
            <Setter.Value>
                <ControlTemplate TargetType="custom:AutoCompleteComboBox">
                    <Grid Margin="{TemplateBinding Padding}">
                        <TextBox IsTabStop="True" x:Name="Text" Style="{TemplateBinding TextBoxStyle}" Margin="0,0,0,2" />
                        <ToggleButton x:Name="ToggleButton"
                                HorizontalAlignment="Right"
                                VerticalAlignment="Center"
                                Style="{StaticResource ComboToggleButton}"
                                Margin="0" BorderBrush="Black" BorderThickness="1,0,0,0"
                                HorizontalContentAlignment="Center"
                                Background="LightGray"
                                Height="16" Width="16">
                            <ToggleButton.Content>
                                <Path x:Name="BtnArrow" Height="4" Width="8" Stretch="Uniform"
                                      Data="F1 M 301.14,-189.041L 311.57,-189.041L 306.355,-182.942L 301.14,-189.041 Z "
                                          Margin="0,0,6,0" HorizontalAlignment="Right">
                                    <Path.Fill>
                                        <SolidColorBrush x:Name="BtnArrowColor" Color="#FF333333"/>
                                    </Path.Fill>
                                    <Path.RenderTransform>
                                        <ScaleTransform x:Name="ToggleButtonScale" />
                                    </Path.RenderTransform>
                                </Path>
                            </ToggleButton.Content>
                        </ToggleButton>
                        <Popup x:Name="Popup">
                            <Border x:Name="PopupBorder" HorizontalAlignment="Stretch" Opacity="1.0" BorderThickness="0">
                                <Border.RenderTransform>
                                    <TranslateTransform X="2" Y="-2" />
                                </Border.RenderTransform>
                                <Border.Background>
                                    <SolidColorBrush Color="#11000000" />
                                </Border.Background>
                                <ListBox x:Name="Selector" ScrollViewer.HorizontalScrollBarVisibility="Auto"
                                                 ScrollViewer.VerticalScrollBarVisibility="Auto"
                                                 ItemTemplate="{TemplateBinding ItemTemplate}" />
                            </Border>
                        </Popup>
                        <VisualStateManager.VisualStateGroups>
                            <VisualStateGroup x:Name="FocusStates">
                                <VisualStateGroup.Transitions>
                                    <VisualTransition GeneratedDuration="0:0:0.2" To="ToggleButtonOver" />
                                    <VisualTransition GeneratedDuration="0:0:0.2" To="ToggleButtonOut" />
                                </VisualStateGroup.Transitions>
                                <VisualState x:Name="ToggleButtonOver">
                                    <Storyboard>
                                        <DoubleAnimation Storyboard.TargetName="ToggleButtonScale" Storyboard.TargetProperty="ScaleX" To="1.3" />
                                        <DoubleAnimation Storyboard.TargetName="ToggleButtonScale" Storyboard.TargetProperty="ScaleY" To="1.3" />
                                    </Storyboard>
                                </VisualState>
                                <VisualState x:Name="ToggleButtonOut">
                                    <Storyboard>
                                        <DoubleAnimation Storyboard.TargetName="ToggleButtonScale" Storyboard.TargetProperty="ScaleX" To="1" />
                                        <DoubleAnimation Storyboard.TargetName="ToggleButtonScale" Storyboard.TargetProperty="ScaleY" To="1" />
                                    </Storyboard>
                                </VisualState>
                            </VisualStateGroup>
                            <VisualStateGroup x:Name="PopupStates">
                                <VisualStateGroup.Transitions>
                                    <VisualTransition GeneratedDuration="0:0:0.4" To="PopupOpened" />
                                    <VisualTransition GeneratedDuration="0:0:0.2" To="PopupClosed" />
                                </VisualStateGroup.Transitions>
                                <VisualState x:Name="PopupOpened">
                                    <Storyboard>
                                        <DoubleAnimation Storyboard.TargetName="PopupBorder" Storyboard.TargetProperty="Opacity" To="1.0" />
                                    </Storyboard>
                                </VisualState>
                                <VisualState x:Name="PopupClosed">
                                    <Storyboard>
                                        <DoubleAnimation Storyboard.TargetName="PopupBorder" Storyboard.TargetProperty="Opacity" To="0.0" />
                                    </Storyboard>
                                </VisualState>
                            </VisualStateGroup>
                            <VisualStateGroup x:Name="ValidationStates">
                                <VisualState x:Name="Valid" />
                                <VisualState x:Name="InvalidUnfocused">
                                    <Storyboard>
                                        <ObjectAnimationUsingKeyFrames Storyboard.TargetName="ValidationErrorElement" Storyboard.TargetProperty="Visibility">
                                            <DiscreteObjectKeyFrame KeyTime="0">
                                                <DiscreteObjectKeyFrame.Value>
                                                    <Visibility>Visible</Visibility>
                                                </DiscreteObjectKeyFrame.Value>
                                            </DiscreteObjectKeyFrame>
                                        </ObjectAnimationUsingKeyFrames>
                                    </Storyboard>
                                </VisualState>
                                <VisualState x:Name="InvalidFocused">
                                    <Storyboard>
                                        <ObjectAnimationUsingKeyFrames Storyboard.TargetName="ValidationErrorElement" Storyboard.TargetProperty="Visibility">
                                            <DiscreteObjectKeyFrame KeyTime="0">
                                                <DiscreteObjectKeyFrame.Value>
                                                    <Visibility>Visible</Visibility>
                                                </DiscreteObjectKeyFrame.Value>
                                            </DiscreteObjectKeyFrame>
                                        </ObjectAnimationUsingKeyFrames>
                                        <ObjectAnimationUsingKeyFrames Storyboard.TargetName="validationTooltip" Storyboard.TargetProperty="IsOpen">
                                            <DiscreteObjectKeyFrame KeyTime="0">
                                                <DiscreteObjectKeyFrame.Value>
                                                    <system:Boolean>True</system:Boolean>
                                                </DiscreteObjectKeyFrame.Value>
                                            </DiscreteObjectKeyFrame>
                                        </ObjectAnimationUsingKeyFrames>
                                    </Storyboard>
                                </VisualState>
                            </VisualStateGroup>
                        </VisualStateManager.VisualStateGroups>
                    </Grid>
                </ControlTemplate>
            </Setter.Value>
        </Setter>
    </Style>
</ResourceDictionary>

Step 3: Add a TemplatePart Attribute

Go back to the AutoCompleteComboBox class created in Step 1 and add a TemplatePart attribute on it that references the ToggleButton control name (which will represent the drop down arrow) defined in the control template.  Template parts are used to be able to reference the different controls defined in the template.  Although TemplatePart’s Name property can be given a direct string value, I followed the convention seen in Silverlight controls and used a constant to define the name value.

[TemplatePart(Name = AutoCompleteComboBox.ElementToggleButton, Type = typeof(ToggleButton))]
public partial class AutoCompleteComboBox : AutoCompleteBox
{
    private const string ElementToggleButton = "ToggleButton";
}

Step 4: Add the ToggleButton Property

Add a ToggleButton property into the AutoCompleteComboBox class as shown next.  You can call it whatever you want but I like to keep things simple and call it what it really is….a ToggleButton.  The event handlers referenced in the code below are shown in Step 6.

#region Template Parts

ToggleButton _ToggleButton;

ToggleButton ToggleButton
{
    get { return _ToggleButton; }
    set
    {
        if (_ToggleButton != null)
        {
            _ToggleButton.Click -= new RoutedEventHandler(ToggleButton_Click);
            _ToggleButton.MouseEnter -= new MouseEventHandler(ToggleButton_MouseEnter);
            _ToggleButton.MouseLeave -= new MouseEventHandler(ToggleButton_MouseLeave);
        }

        _ToggleButton = value;

        if (_ToggleButton != null)
        {
            _ToggleButton.Click += new RoutedEventHandler(ToggleButton_Click);
            _ToggleButton.MouseEnter += new MouseEventHandler(ToggleButton_MouseEnter);
            _ToggleButton.MouseLeave += new MouseEventHandler(ToggleButton_MouseLeave);
        }
    }
}

#endregion

Step 5: Set the DefaultStyleKey for the AutoCompleteComboBox Control

Next you need to tell the AutoCompleteComboBox to use the Style you defined in Generic.xaml instead of the default style used by AutoCompleteBox.  Do this by adding the following constructor into the class (which also adds code to call the ApplyTemplate class shown in the next step):

public AutoCompleteComboBox()
{
    this.DefaultStyleKey = typeof(AutoCompleteComboBox);
    Loaded += (sender, e) => ApplyTemplate();
}

Step 6: Override ApplyTemplate and Handle Events

We’re almost there! The next thing that needs to be done is to override the control’s OnApplyTemplate method.  We need to do this so that the ToggleButton can be injected into the process so that we can handle the user clicking on it to show all of the items like a normal ComboBox would do.  Here’s the remaining code that needs to be added into the class. 

public override void OnApplyTemplate()
{
    base.OnApplyTemplate();
    ToggleButton = GetTemplateChild(ElementToggleButton) as ToggleButton;
}

void ToggleButton_MouseEnter(object sender, MouseEventArgs e)
{
    VisualStateManager.GoToState(this, "ToggleButtonOver", true);
}

void ToggleButton_MouseLeave(object sender, MouseEventArgs e)
{
    VisualStateManager.GoToState(this, "ToggleButtonOut", true);
}

void ToggleButton_Click(object sender, RoutedEventArgs e)
{
    if (String.IsNullOrEmpty(this.SearchText))
    {
        this.Text = String.Empty;
    }
    this.IsDropDownOpen = !this.IsDropDownOpen;
}

This code accesses the ToggleButton control defined in the Generic.xaml template using the base class’s GetTemplateChild method.  Once the ToggleButton control is found it’s assigned to the ToggleButton property of our control which handles hooking up the Click, MouseEnter and MouseLeave events.  The ToggleButton_Click event handler does the magic of making the AutoCompleteComboBox control look and feel like a standard ComboBox.

Step 7: Use the AutoCompleteComboBox Control

Once the custom control is finished and you successfully build it you can use the control in a Silverlight application.  To do that, create a Silverlight application project and reference the control project created in Step 1 (or reference the dll that’s generated by the project).  Within the XAML for your Silverlight application add a reference to the control’s namespace and assembly:

<Usercontrol x:Class="SilverlightApplication1"
    xmlns:controls="clr-namespace:YourCustomControlNamespace;assembly=YourCustomControlAssemblyName">
</UserControl>

Once the assembly and namespace are referenced you can define the control and bind items to it using it’s ItemSource property:

<controls:AutoCompleteComboBox ItemsSource="{Binding Jobs}"
    Margin="5,0,5,10" Width="150"  HorizontalAlignment="Left"
    MinimumPopulateDelay="0" MinimumPrefixLength="0" />

If I had to pick out the key parts from these steps they’d be creating the Generic.xaml file, adding the TemplatePart attribute, setting the control’s DefaultStyleKey, overriding ApplyTemplate and calling GetTemplateChild to locate the ToggleButton control.  These changes allow the AutoCompleteComboBox to look like a regular ComboBox while still allowing the user to type into it to filter the items. 

Download Code

 

 

Logo

For more information about onsite, online and video training, mentoring and consulting solutions for .NET, SharePoint or Silverlight please visit www.thewahlingroup.com/.

Handling Events within Silverlight Control Templates – AutoCompleteBox Example

One of the great features Silverlight offers is the ability to customize controls by using control templates.  If you don’t like how a particular control looks you can modify the template and in many cases be ready to use the new control without writing a single line of C# or VB code.  I’m working on a client application that uses the AutoCompleteBox found in the Silverlight Toolkit and needed a way to change it from a regular TextBox to more of an editable ComboBox.  Fortunately the Silverlight Toolkit samples (for Silverlight 2 and 3) already do something like this as you can see here (once on the page click on AutoCompleteBox to the left and then on the Styling tab at the top of the page to see the sample).

image

The sample modifies the standard AutoCompleteBox to look more like an editable ComboBox by defining a custom control template with a ToggleButton in it (Tim Heuer provides a nice walk through of this type of customization here if you’re interested).  Here’s a simplified version of the Silverlight Toolkit’s sample template that I’m using:

<ControlTemplate TargetType="input:AutoCompleteBox">
    <Grid Margin="{TemplateBinding Padding}">
        <TextBox IsTabStop="True" x:Name="Text" Style="{TemplateBinding TextBoxStyle}" Margin="0" />
        <ToggleButton x:Name="ToggleButton"
            HorizontalAlignment="Right"
            VerticalAlignment="Center"
            Style="{StaticResource ComboToggleButton}"
            Margin="0"
            HorizontalContentAlignment="Center"
            Background="{TemplateBinding Background}"
            BorderThickness="0"
            Height="16" Width="16"
            Click="DropDownToggle_Click">
            <ToggleButton.Content>
                <Path x:Name="BtnArrow" Height="4" Width="8" Stretch="Uniform" Data="F1 M 301.14,-189.041L 311.57,-189.041L 306.355,-182.942L 301.14,-189.041 Z "
                      Margin="0,0,6,0" HorizontalAlignment="Right">
                    <Path.Fill>
                        <SolidColorBrush x:Name="BtnArrowColor" Color="#FF333333"/>
                    </Path.Fill>
                </Path>
            </ToggleButton.Content>
        </ToggleButton>
        <Popup x:Name="Popup">
            <Border x:Name="PopupBorder" HorizontalAlignment="Stretch" Opacity="1.0" BorderThickness="0">
                <Border.RenderTransform>
                    <TranslateTransform X="2" Y="2" />
                </Border.RenderTransform>
                <Border.Background>
                    <SolidColorBrush Color="#11000000" />
                </Border.Background>
                        <ListBox x:Name="Selector" ScrollViewer.HorizontalScrollBarVisibility="Auto"
                             ScrollViewer.VerticalScrollBarVisibility="Auto"
                             ItemTemplate="{TemplateBinding ItemTemplate}" />
             </Border>
        </Popup>
    </Grid>
</ControlTemplate>


Looking at the template you’ll see that it defines a ToggleButton with a Click event and associated event handler named DropDownToggle_Click.  Here’s what the ToggleButton’s Click event handler looks like:

private void DropDownToggle_Click(object sender, RoutedEventArgs e)
{
    FrameworkElement fe = sender as FrameworkElement;
    AutoCompleteBox acb = null;
    while (fe != null && acb == null)
    {
        fe = VisualTreeHelper.GetParent(fe) as FrameworkElement;
        acb = fe as AutoCompleteBox;
    }
    if (acb != null)
    {
        if (String.IsNullOrEmpty(acb.SearchText))
        {
            acb.Text = String.Empty;
        }
        acb.IsDropDownOpen = !acb.IsDropDownOpen;
    }
}


You can see that the code uses the VisualTreeHelper class to access the parent of the ToggleButton which is the AutoCompleteBox.  Once the AutoCompleteBox parent is found the code handles showing or hiding the Popup control that’s part of the control template by setting the IsDropDownOpen property to true or false.

This works fine if the control template is placed in the same scope as the event handler code such as the page or user control resources section.  However, if you try to move the template code to another resources section that doesn’t have access to the event handler code (DropDownToggle_Click) you’ll run into problems .  What if you want to put the control template in a merged resource dictionary (similar to an external CSS stylesheet to give a web analogy) and can’t hard-code the click event into the control template since you don’t know where the event handler will be defined at that point?  Although you can certainly write a custom control that derives from AutoCompleteBox in this case (which would be recommended if you’ll re-use the control across multiple pages or user controls), another solution is to hook-up the ToggleButton’s Click event when the AutoCompleteBox control first loads as shown next:

 

void SilverlightApplication_Loaded(object sender, RoutedEventArgs e)
{
    HookAutoCompleteBoxEvents();
}

void HookAutoCompleteBoxEvents()
{
    AutoCompleteBox[] boxes = { this.JobIDAutoCompleteBox, this.EmployeeAutoCompleteBox };
    foreach (var box in boxes)
    {
        Grid grid = VisualTreeHelper.GetChild(box, 0) as Grid;
        ToggleButton tb = grid.Children[1] as ToggleButton;
        if (tb != null) tb.Click += DropDownToggle_Click;
    }
}


When the Silverlight application Loaded event is called it calls the HookAutoCompleteBoxEvents() method.  Within HookAutoCompleteBoxEvents() an array of AutoCompleteBox controls is iterated through to locate the ToggleButton for each control and attach a Click event handler to it.  Doing this avoids hard-coding the event handler in the control template so that it can be defined just about anywhere you’d like without running into code scoping issues. 

Note: Keep in mind that if you plan on using the customized AutoCompleteBox control in several places it may be worth the time to create a custom control that derives from AutoCompleteBox to avoid having to put the ToggleButton event handler code and the HookAutoCompleteBoxEvents code into each page or user control. 

 

Logo

For more information about onsite, online and video training, mentoring and consulting solutions for .NET, SharePoint or Silverlight please visit www.thewahlingroup.com/.

Control-Oriented Vs. Data-Oriented Programming in Silverlight

If you build ASP.NET applications then you’re used to working with controls a lot.  Nearly everything you do requires accessing control IDs.  If you need to retrieve data entered by an end user you need to reference the controls that contain the data.  That’s just the way we do it in ASP.NET and if you’ve been writing ASP.NET applications very long its how you’re conditioned to think.

Silverlight changes the game quite a bit due to the way you can bind data to controls.  With Silverlight applications I don’t always name my controls since ultimately I care about accessing the data rather than the control that contains the data.  Sure, if I need to perform an animation or change a control’s style I’ll need to access the control directly by its name, but when it comes to accessing data there’s an easier way.  Silverlight provides two-way bindings that allow a data object to be bound to a control in a unique way.  If the user changes the data the source object is automatically updated without additional code on your part.  An example of a two-way binding defined in XAML is shown next:

<TextBox Text="{Binding MondayQuantity, Mode=TwoWay}"  Style="{StaticResource TimeSheetTextBoxStyle}" />


This example binds the DataContext (the main object bound to the overall form) object’s MondayQuantity property to a TextBox control.  The Mode is set to TwoWay rather than the default OneWay binding which means that any changes to the TextBox are automatically moved back to the MondayQuantity property in the source object.  As a result, you don’t need to access the TextBox to get the value entered since the source object that was originally bound to the control contains up-to-date data.  Having done a lot of ASP.NET programming this took some time to get used to, but it’s nice to leverage once you know about it. 

I’m working on an application that uses a Silverlight DataGrid control with nested ComboBox and TextBox controls in each row.  As a user changes  quantity or hour values the totals need to be updated.

image

Let’s take a look at different solutions that can be used to update the totals values.

A Control-Oriented Approach

One solution to updating the totals is to iterate through the target row and locate each TextBox to get the values.  That’s the control-oriented approach we’d normally use in ASP.NET applications.  To update TextBlock controls that track totals at the end of each row (red area in the image shown above) as a user changes values, you could use the following code which iterates through each column in a given row and locates TextBox controls:

//Get the DataGrid's selected item
TimeSheetViewRow dataContext = (TimeSheetViewRow)TimeSheetDataGrid.SelectedItem;
decimal totalQty = 0;
decimal totalHours = 0;

//Loop through all the columns for the selected row
for (int i = 2; i < TimeSheetDataGrid.Columns.Last().DisplayIndex; i++)
{
    StackPanel sp = (StackPanel)TimeSheetDataGrid.Columns[i].GetCellContent(dataContext);
    foreach (UIElement elem in sp.Children)
    {
        //Find the TextBox controls
        if (elem is TextBox)
        {
            TextBox tb = (TextBox)elem;
            if (!String.IsNullOrEmpty(tb.Text))
            {
                decimal val = decimal.Parse(tb.Text);
                if (tb.Tag.ToString() == "Quantity") totalQty += val;
                if (tb.Tag.ToString() == "Hours") totalHours += val;
            }
        }
    }
}
//Find totals TextBlocks and update them
StackPanel totalsSP = (StackPanel)TimeSheetDataGrid.Columns[TimeSheetDataGrid.Columns.Last()
    .DisplayIndex].GetCellContent(dataContext);
((TextBlock)totalsSP.FindName("TotalQuantityTextBlock")).Text = totalQty.ToString();
((TextBlock)totalsSP.FindName("TotalHoursTextBlock")).Text = totalHours.ToString();


While this technique works fine, it’s definitely not the easiest way to total each day’s hours and quantity and would have to be re-written if the TextBox controls’ container changes from a StackPanel to something else.  Let’s look at a more flexible data-oriented approach.

A Data-Oriented Approach

Since the TextBoxes in each row all have TwoWay bindings defined, the source object that was originally bound (of type TimeSheetViewRow) is automatically updated as TextBox values change.  As a result, I can simply grab the selected item (which represents the bound object) from the DataGrid and then total up the property values.  Once the totals are calculated the appropriate quantity and hours total properties can be updated on the source object which automatically updates the grid TextBlock controls bound to those properties.  It’s important to note that the TimeSheetViewRow class implements INotifyPropertyChanged so that it can notify Silverlight as property values change so that the data can be re-bound to the controls.  The control-oriented approach shown earlier can be simplified to the following:

TimeSheetViewModel vm = (TimeSheetViewModel)LayoutRoot.DataContext;
vm.UpdateRowTotals((TimeSheetViewRow)TimeSheetDataGrid.SelectedItem);


The code that performs the actual calculations is shown next.  It’s located in a ViewModel class named TimeSheetViewModel that exposes an UpdateRowTotals method.  The ViewModel class contains all of the properties that are bound to the form (called the View) that the end user sees.  I used reflection to simplify the calculation process since each property has a set naming convention (MondayHours, MondayQuantity, TuesdayHours, TuesdayQuantity, etc.) but I certainly could’ve written code to add all of the Monday – Sunday quantity and hours properties together more explicitly.

 

public void UpdateRowTotals(TimeSheetViewRow row)
{
    decimal qty = 0M;
    decimal hours = 0M;
    Type t = typeof(TimeSheetViewRow);
    foreach (PropertyInfo prop in t.GetProperties())
    {
        object val = prop.GetValue(row, null);
        decimal decimalVal = (val==null)
            ?0.00M:decimal.Parse(val.ToString());
        if (prop.Name.EndsWith("Hours")) hours += decimalVal;
        if (prop.Name.EndsWith("Quantity")) qty += decimalVal;
    }
    row.HoursTotal = hours;
    row.QuantityTotal = qty;
}

As the HoursTotal and QuantityTotal properties change the corresponding TextBlock controls that they’re bound to will be updated automatically.

You can see that with Silverlight you can focus on working with data as opposed to working with controls by using the built-in support for TwoWay binding.  Controls are there to present the data to the end user and allow them to change values.  By focusing less on controls we can reduce the amount of code that has to be written in many cases.  It takes a little getting used to especially if you’re used to the ASP.NET control-centric approach, but once the concept clicks it really changes how you think about writing data-oriented code.

Customizing Silverlight 3 DataGrid Headers

We’re currently working on a client application that captures time sheet information and needed the ability to completely customize the look and feel of DataGrid headers.  Fortunately, that’s fairly straightforward to do in Silverlight 3 (or Silverlight 2 for that matter).  Nearly all of the columns being used are DataGridTemplateColumn types (although that’s not required to customize headers) and changing the header is accomplished by using the HeaderStyle property as shown next:

<data:DataGridTemplateColumn Header="Tue" HeaderStyle="{StaticResource TimeSheetDayHeaderStyle}">
    <data:DataGridTemplateColumn.CellTemplate>
        <DataTemplate>
            <StackPanel Orientation="Horizontal">
                <TextBox Text="{Binding TuesdayQuantity}" Style="{StaticResource TimeSheetTextBoxStyle}"/>
                <Rectangle Fill="#FFC9CACA" VerticalAlignment="Stretch" Width="1" />
                <TextBox Text="{Binding TuesdayHours}" Margin="2,0,0,0" Style="{StaticResource TimeSheetTextBoxStyle}"/>
            </StackPanel>
        </DataTemplate>
    </data:DataGridTemplateColumn.CellTemplate>
</data:DataGridTemplateColumn>


The tricky part of creating a custom header for the first time is knowing what the style’s TargetType should be.  You’ll need to reference the System.Windows.Controls.Primitives namespace as shown next as it contains the DataGridColumnHeader type:

xmlns:dataprimitives="clr-namespace:System.Windows.Controls.Primitives;assembly=System.Windows.Controls.Data"

Once that’s available you can define the styles.  We needed stacked headers that displayed each day of the week as well as hours and quantity under the day and ended up going with the following styles (note that Silverlight 3’s new BasedOn feature is being used).  The TimeSheetDayHeaderStyle style defines the stacked headers using a Grid control.

<Style x:Key="DataGridBaseHeaderStyle" TargetType="dataprimitives:DataGridColumnHeader">
    <Setter Property="FontWeight" Value="Bold" />
</Style>

<Style x:Key="TimeSheetTotalsHeaderStyle" TargetType="dataprimitives:DataGridColumnHeader"
       BasedOn="{StaticResource TimeSheetDayHeaderStyle}">
    <Setter Property="Foreground" Value="#FFFF0000"/>
</Style>

<Style x:Key="TimeSheetDayHeaderStyle" TargetType="dataprimitives:DataGridColumnHeader"
       BasedOn="{StaticResource DataGridBaseHeaderStyle}">
    <Setter Property="Foreground" Value="#FF000000"/>
    <Setter Property="HorizontalContentAlignment" Value="Left"/>
    <Setter Property="VerticalContentAlignment" Value="Center"/>
    <Setter Property="IsTabStop" Value="False"/>
    <Setter Property="SeparatorBrush" Value="#FFC9CACA"/>
    <Setter Property="Padding" Value="8"/>
    <Setter Property="Template">
        <Setter.Value>
            <ControlTemplate>
                <Grid x:Name="Root">
                    <Grid.ColumnDefinitions>
                        <ColumnDefinition/>
                        <ColumnDefinition Width="Auto"/>
                    </Grid.ColumnDefinitions>
                    <VisualStateManager.VisualStateGroups>
                        <VisualStateGroup x:Name="CommonStates">
                            <VisualState x:Name="Normal"/>
                            <VisualState x:Name="MouseOver">
                                <Storyboard>
                                    <ColorAnimation Duration="0"
                                                    Storyboard.TargetName="BackgroundRectangle"
                                                    Storyboard.TargetProperty="(Fill).Color" To="#FF448DCA"/>
                                    <ColorAnimation Duration="0"
                                                    Storyboard.TargetName="BackgroundGradient"
                                                    Storyboard.TargetProperty="(Fill).(GradientStops)[3].Color" To="#7FFFFFFF"/>
                                    <ColorAnimation Duration="0"
                                                    Storyboard.TargetName="BackgroundGradient"
                                                    Storyboard.TargetProperty="(Fill).(GradientStops)[2].Color" To="#CCFFFFFF"/>
                                    <ColorAnimation Duration="0"
                                                    Storyboard.TargetName="BackgroundGradient"
                                                    Storyboard.TargetProperty="(Fill).(GradientStops)[1].Color" To="#F2FFFFFF"/>
                                </Storyboard>
                            </VisualState>
                            <VisualState x:Name="Pressed">
                                <Storyboard>
                                    <ColorAnimation Duration="0"
                                                    Storyboard.TargetName="BackgroundRectangle"
                                                    Storyboard.TargetProperty="(Fill).Color" To="#FF448DCA"/>
                                    <ColorAnimation Duration="0"
                                                    Storyboard.TargetName="BackgroundGradient"
                                                    Storyboard.TargetProperty="(Fill).(GradientStops)[0].Color" To="#D8FFFFFF"/>
                                    <ColorAnimation Duration="0"
                                                    Storyboard.TargetName="BackgroundGradient"
                                                    Storyboard.TargetProperty="(Fill).(GradientStops)[1].Color" To="#C6FFFFFF"/>
                                    <ColorAnimation Duration="0"
                                                    Storyboard.TargetName="BackgroundGradient"
                                                    Storyboard.TargetProperty="(Fill).(GradientStops)[2].Color" To="#8CFFFFFF"/>
                                    <ColorAnimation Duration="0"
                                                    Storyboard.TargetName="BackgroundGradient"
                                                    Storyboard.TargetProperty="(Fill).(GradientStops)[3].Color" To="#3FFFFFFF"/>
                                </Storyboard>
                            </VisualState>
                        </VisualStateGroup>
                        <VisualStateGroup x:Name="SortStates">
                            <VisualState x:Name="Unsorted"/>
                            <VisualState x:Name="SortAscending" />
                            <VisualState x:Name="SortDescending" />
                        </VisualStateGroup>
                    </VisualStateManager.VisualStateGroups>
                    <Rectangle x:Name="BackgroundRectangle" Fill="#FF1F3B53" Stretch="Fill" Grid.ColumnSpan="2"/>
                    <Rectangle x:Name="BackgroundGradient" Stretch="Fill" Grid.ColumnSpan="2">
                        <Rectangle.Fill>
                            <LinearGradientBrush EndPoint=".7,1" StartPoint=".7,0">
                                <GradientStop Color="#FCFFFFFF" Offset="0.015"/>
                                <GradientStop Color="#F7FFFFFF" Offset="0.375"/>
                                <GradientStop Color="#E5FFFFFF" Offset="0.6"/>
                                <GradientStop Color="#D1FFFFFF" Offset="1"/>
                            </LinearGradientBrush>
                        </Rectangle.Fill>
                    </Rectangle>
                    <Grid HorizontalAlignment="{TemplateBinding HorizontalContentAlignment}"
                          VerticalAlignment="{TemplateBinding VerticalContentAlignment}">
                        <Grid.RowDefinitions>
                            <RowDefinition Height="20" />
                            <RowDefinition Height="1" />
                            <RowDefinition Height="20" />
                        </Grid.RowDefinitions>
                        <Grid.ColumnDefinitions>
                            <ColumnDefinition Width="50"/>
                            <ColumnDefinition Width="1" />
                            <ColumnDefinition Width="50"/>
                        </Grid.ColumnDefinitions>
                        <!-- Row 0 -->
                        <ContentPresenter Content="{TemplateBinding Content}"
                                          VerticalAlignment="Center" HorizontalAlignment="Center"
                                          Grid.ColumnSpan="3" />

                        <!-- Row 1 -->
                        <Rectangle Fill="#FFC9CACA" VerticalAlignment="Stretch" Height="1"
                                   Visibility="Visible" Grid.Row="1" Grid.ColumnSpan="3" />
                        <!-- Row 2 -->
                        <ContentPresenter Content="Qty" Grid.Row="2" VerticalAlignment="Center"
                                          HorizontalAlignment="Center" />
                        <Rectangle Fill="#FFC9CACA" VerticalAlignment="Stretch" Width="1"
                                   Visibility="Visible" Grid.Row="2" Grid.Column="1" />
                        <ContentPresenter Content="Hours" Grid.Row="2" Grid.Column="2"
                                          VerticalAlignment="Center" HorizontalAlignment="Center" />
                    </Grid>
                    <Rectangle x:Name="VerticalSeparator" Fill="#FFC9CACA"
                               VerticalAlignment="Stretch" Width="1" Visibility="Visible"
                               Grid.Row="1" Grid.Column="1"/>
                </Grid>
            </ControlTemplate>
        </Setter.Value>
    </Setter>
</Style>


The end result is a grid with headers that are just like the client wanted:

image

 

 

Logo

For more information about onsite, online and video training, mentoring and consulting solutions for .NET, SharePoint or Silverlight please visit www.thewahlingroup.com/.

Pivots to the Rescue – Providing Flexible SQL Server Queries

image One of the projects my company is currently working on has a requirement to be able to store timesheet data in a more flexible manner.  The existing Access forms application is being converted to Silverlight 3 and has a fairly rigid (albeit standard) database structure for timesheets.  Hours and quantities are associated directly with days using columns such as MonHours, MonQuant, etc.  (see the table image to the right).  The catch is that the company needs to be able to store several other pieces of data in the future.  Of course, if they need a new column that tracks overtime, they’d have to modify the database table and add in 7 new columns if the existing table structure is used which definitely isn’t optimal.

After thinking through a few options we decided to go with a more complex yet flexible solution.  It’s only useful (in my opinion anyway) when the data that will be collected can change in the future.  I’m not a big fan of architecting for “what if” type scenarios since in my experience most of those scenarios are never realized, but in this situation it was a requirement to be able to handle different types of timesheet data without having to re-work the table structure and add new columns.  We ended up going with the following table structure to store timesheet values:

image

Here’s a quick overview of what the different tables store:

  • TimeSheet – Associates a job, employee and week ending date together
  • TimeSheet_WorkCode – Associates work codes with a timesheet
  • TimeSheetDayValue – This is where the values are actually stored.  It can store hours, overtime, quantity or anything else really.  The DayValue column stores the actual value and the type of value being stored (hours, quantity, etc.)  is tracked by the TimeSheetDayValueTypeID which links to the TimeSheetDayValueType table.
  • TimeSheetDayValueType – This table tracks the type of data being stored in TimeSheetDayValue (currently just hours and quantity of materials installed).  The type of data is tracked in the ValueType column which is a simple varchar data type.
  • Day – This is a simple lookup table that contains all of the day names (Monday, Tuesday, Wednesday, etc.)

While the table structure provides a flexible way to store different types of timesheet data, it doesn’t store the data in a format that’s compatible with timesheets.  Normally, an employee will select a work code and then enter data for the hours worked on each day.  With the initial table structure that we inherited from the original database (image to the right) that was easy since the existing columns match up perfectly with grid header columns.  With the new table structure we gain flexibility but add complexity since we have to reshape the data to fit into a timesheet grid.

Fortunately, SQL Server 2005 or higher supports pivots which allow data to be “pivoted”.  In other words, row data can be turned into columns.  If I run the following query against the tables shown earlier I’ll get back rows but they won’t match up with Monday, Tuesday, Wednesday timesheet header columns as you can see next:

SELECT TS.TimeSheetID, TS.WeekEnding, TS.EmployeeID, TS.JobID, TSWC.WorkCodeID,
TSDV.DayValue, D.Day + TSDVT.ValueType as RotateColumn
FROM TimeSheet TS
INNER JOIN Timesheet_WorkCode TSWC on TS.TimeSheetID = TSWC.TimeSheetID
INNER JOIN TimeSheetDayValue TSDV on TSWC.TimeSheetWorkCodeID = TSDV.TimeSheetWorkCodeID
INNER JOIN TimeSheetDayValueType TSDVT on TSDV.TimeSheetDayValueTypeID = TSDVT.TimeSheetDayValueTypeID
INNER JOIN Day D on TSDV.DayID = D.DayID
WHERE TS.JobID = 1 AND EmployeeID = 1 AND WeekEnding = '5/10/2009'

image

However, by using a pivot I can make it so that the values shown above for RotateColumn are “pivoted” to be columns in the resultset so that everything fits into a single row on a timesheet grid.  There are a few tricks that have to be leveraged to make it all work such as leveraging some of the SQL Server 2005+ XML functionality but we felt it was a good tradeoff to get the data storage flexibility required by the client.  Here’s the pivot query we’re currently using and the resultset that it returns.  We’ll likely change things as the application is optimized more, but you’ll get the idea.  The query is in a stored procedure which accepts jobID, employeeID and week ending date and returns timesheet data for different work codes.

DECLARE @Cols varchar(500)
SELECT @Cols = STUFF((SELECT ',' + QUOTENAME(D.Day + TSDVT.ValueType) AS [text()]
FROM Day D
CROSS JOIN TimeSheetDayValueType TSDVT
FOR XML PATH('')), 1, 1, '')

DECLARE @Query nvarchar(MAX)
SELECT @Query = '
SELECT * FROM
(
    SELECT TS.TimeSheetID, TS.WeekEnding, TS.EmployeeID, TS.JobID, TSWC.WorkCodeID,
    TSDV.DayValue, D.Day + TSDVT.ValueType as RotateColumn
    FROM TimeSheet TS
    INNER JOIN Timesheet_WorkCode TSWC on TS.TimeSheetID = TSWC.TimeSheetID
    INNER JOIN TimeSheetDayValue TSDV on TSWC.TimeSheetWorkCodeID = TSDV.TimeSheetWorkCodeID
    INNER JOIN TimeSheetDayValueType TSDVT on TSDV.TimeSheetDayValueTypeID = TSDVT.TimeSheetDayValueTypeID
    INNER JOIN Day D on TSDV.DayID = D.DayID
    WHERE TS.JobID = ' + CONVERT(NVARCHAR,@JobID) +
    ' AND EmployeeID = ' + CONVERT(NVARCHAR,@EmployeeID) + ' AND WeekEnding = ''' + @WeekEnding + '''
) AS SourceTable
PIVOT (SUM(DayValue) FOR RotateColumn IN (' + @Cols + ')) AS TimeSheetRow'
-- FOR XML AUTO, ELEMENTS XSINIL
EXEC sp_executesql @Query

 

image


You can see that the RotateColumn values shown earlier have been changed to be columns using SQL Server’s pivot functionality which is exactly what we needed for the Silverlight 3 application that displays the data.  We had to resort to some dynamic SQL to make it work, but the end result is that we’re able to shape the data as needed yet still retain the flexibility provided by the table structure.  As new types of timesheet values are required the table structure and query can be left intact.  Pivots to the rescue…pretty nice when you need them.

Run Silverlight Plug-In on Google Chrome [Google Chrome]

For those Google Chrome users who need access to one or two Silverlight sites, a Microsoft developer offers a file-tweaking work-around to get access to certain sites.

The Chrome/Silverlight hack requires grabbing a few .js files normally intended for Silverlight developers and tweaking them to have Silverlight sites accept and provide content to Chrome. By downloading the .js files and modifying them a bit, you should be able to access, as the developer rates it, “most” Silverlight apps, but your mileage will certainly vary.





Moonlight 1.0 Gives Linux Users Silverlight 1.0 Compatability [Downloads]

Linux users running into the web’s brick walls of “This site requires Microsoft Silverlight,” rejoice. The Moonlight team has brought Silverlight compatibility to Firefox browsers on Linux systems.

Moonlight installs like any other Firefox extension. Once it’s installed, you should be ready to use any site or webapp that requires Silverlight 1.0; 2.0 access is, hopefully, coming up soon. You should also be able to work inside Silverlight apps with traditionally Windows-based media codecs, another compatibility bonus. Tell us how Moonlight works for your Linux systems in the world of Windows in the comments.






WP Like Button Plugin by Free WordPress Templates