Creating a very dynamic LightSwitch shell extension in 15 minutes or less…

I just finished cooking, and eating a hearty prawns wok…  The recipe, which I’m proud to say I have discovered myself through trial-and-error (mostly error), is a one-liner:  heat up a stir-fry pan, throw in a packet of marinated prawns (remove the wrapping first), add some deep-frozen vegetables and half a box of rice vermicelli, and pour over a quarter of a can of bisque du homard (“lobster soup”).  Hey, I enjoy cooking and never buy TV meals, but also refuse to believe that a good meal should take hours to prepare.  Sometimes, by just throwing the content of a couple of boxes together, you can get the simplest but most delicious dinners…

The same is true for software development… (You didn’t really think I was going to do a blog post on cooking, did you?) Less then a week ago, I created a small LightSwitch application for someone.  He was a dream customer, from a LightSwitch point-of-view.  The application only included a dozen screens on even less entities, and if it wasn’t for some specific reports, could have been built without writing a single line of code… Needless to say I considered the project to be “easy money“, and the customer considered me “way too cheap compared to others“, a win-win situation.  The day after our first meeting, where we agreed on a price and what functionality the application should include,  I headed back to his office to go over the implementation together.   Again, all of his requests were surprisingly easy to implement.  Make a field read-only, or hide the label, using the runtime editor.  Check.  Make it a bit more colorful, using the metro theme.  Check.  Move the save & refresh buttons to the left, under the navigation tree… Check.

No wait… What?  It’s amazing how a “minor change” in the eye of the customer, can have a huge influence on the technical implementation.  To move the commands view (the ribbon that holds the save & refresh buttons), I would have to write a custom shell extension.  Out of all the extensions one can write for LightSwitch, the shell extension surely isn’t the easiest one, the result you get with the walkthrough doesn’t look very professional, and since we already agreed on a price I had no intend to spend a lot of time on this hidden requirement.

I asked the customer if I could take a small break, and much to my own surprise, came back fifteen minutes later with a LightSwitch shell extension where not only the commands view was located under the navigation view, but the end-user could drag and dock both views to whatever location he/she could want…  Sometimes, by just throwing the content of a couple of boxes together, you can get the simplest but most effective LOB applications…

Minute 1->4: “marinated prawns”: creating a shell extension

This is the obvious box, if we need a custom shell extension, we’re going to use the extensibility toolkit that came with our Visual Studio LightSwitch installation.

  • Create a new LightSwitch Extension Project, name it DockableShell.
  • Right click on the DockableShell.lspkg project, select Add>New Item…>Shell and name it DockableShell.  This will add all required classes and metadata for our extension.
  • Press F5 to debug the extension and see what we have so far, a new instance of visual studio will open in experimental mode… Really? Waiting for the visual studio instance to load, then open a new LightSwitch project just to test our extension, will kill productivity, so let’s fix that first.
  • Close the LightSwitch Extension Project

Minute 5 ->8: “deep-frozen vegetables”: using Extensions Made Easy to increase our shelling productivity

In case you’re new to my blog, and I have a feeling this post might attract a lot of first-timers,  let me show you how to use Extensions Made Easy, a light-weight extension that I have been working on, to boost your LightSwitch hacking experience…

  • Create a new LightSwitch project, name it DockableShellTestArea, and create a couple of dummy entities and screens.  Or open an existing LightSwitch project, obviously.
  • Download and activate Extensions Made Easy.
  • While you’re messing with your LightSwitch project’s properties, select the “EasyShell” as the selected shell.
  • Right click on the solution in the solution explorer, and select Add>Existing Item…  Add the client project from the extension that we made in the previous step.
  • Select your LightSwitch project, and change the view from “Logical View” to “File View”. (Highlighted with the red “circle” in the image below).
  • Add a project reference from your LightSwitch client project (DockableShellTestArea.Client) to your Extension’s client project (DockableShell.Client).
  • Change the visibility of the DockableShell class (found in the DockableShell.Client project under Presentation.Shells.Components), from internal to public.
  • And, lastly, export the shell to ExtensionsMadeEasy, by adding a new class to the DockableShellTestArea.Client project with the code below… (This is the only code we’ll write by the way… )
namespace LightSwitchApplication
{
    public class DockableShellExporter :
        ExtensionsMadeEasy.ClientAPI.Shell.EasyShellExporter
        <DockableShell.Presentation.Shells.Components.DockableShell>
    { }
}

All done, your solution should look a similar to this…

If you made it through these 4 minutes, you can now press F5 to see your LightSwitch application with your custom shell applied.

It doesn’t look so fancy yet, and that’s an overstatement, because we haven’t actually implemented our shell extension yet.  Move along fast, you’re over halfway there…

Minute 9 ->12: “rice vermicelli”: pandora’s box

Right, time to open pandora’s box.

Before you read on, realize that the kind of LightSwitch hacking we’re about to do, might damage your application, software, or cause your hardware to spontaniously self-ignite, and I won’t take responsability for it.  Seriously though, once you leave the path of published APIs, you should realize that no one can/will provide support for the errors you might encounter, and that your extensions can / will break in later versions of LightSwitch…

In the solution explorer, select your LightSwitch application’s client project and select “Open folder in windows explorer”.  Go up one level, and then down the ClientGenerated>Bin>Debug path.  You’ll find an assembly called Microsoft.LightSwitch.Client.Internal.DLL.   This assembly contains the files that are used by, for example, the LightSwitch default shell.  Instead of rolling our own shell, we’re going to tear apart and reuse the built-in LightSwitch default shell.  This scenario is in no way officially supported, and quite frankly I don’t believe you’re even allowed to do that, so do it at your own risk and don’t tell anyone about it, in a blog post or whatever… Crap…

  • Add a reference to Microsoft.LightSwitch.Client.Internal from your DockableShell.Client project.
  • Also add a reference to System.Windows.Controls
  • And a reference to System.Windows.Controls.Navigation
  • Open the file DockableShell.xaml and replace the contents with the following
<UserControl x:Class="DockableShell.Presentation.Shells.DockableShell"
     xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
     xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
     xmlns:nav="clr-namespace:System.Windows.Controls;assembly=System.Windows.Controls.Navigation"
     xmlns:stringsLocal="clr-namespace:Microsoft.LightSwitch.Runtime.Shell.Implementation.Resources;assembly=Microsoft.LightSwitch.Client.Internal"
     xmlns:DefaultShell="clr-namespace:Microsoft.LightSwitch.Runtime.Shell.Implementation.Standard;assembly=Microsoft.LightSwitch.Client.Internal"
     xmlns:ShellHelpers="clr-namespace:Microsoft.LightSwitch.Runtime.Shell.Helpers;assembly=Microsoft.LightSwitch.Client"
     xmlns:controls="clr-namespace:System.Windows.Controls;assembly=System.Windows.Controls"
     xmlns:internalControls="clr-namespace:Microsoft.LightSwitch.SilverlightUtilities.Controls.Internal;assembly=Microsoft.LightSwitch.Client"
     xmlns:internalToolkit="clr-namespace:Microsoft.LightSwitch.Presentation.Framework.Toolkit.Internal;assembly=Microsoft.LightSwitch.Client"
     xmlns:framework="clr-namespace:Microsoft.LightSwitch.Presentation.Framework;assembly=Microsoft.LightSwitch.Client">

     <Grid x:Name="shellGrid" Background="{StaticResource NavShellBackgroundBrush}">
          <Grid.RowDefinitions>
               <RowDefinition Height="Auto"/>
               <RowDefinition Height="*"/>
               <RowDefinition Height="Auto"/>
          </Grid.RowDefinitions>

          <DefaultShell:CommandsView x:Name="_commandsView" ShellHelpers:ComponentViewModelService.ViewModelName="Default.CommandsViewModel"
               VerticalAlignment="Stretch" VerticalContentAlignment="Stretch" HorizontalAlignment="Stretch" BorderThickness="0"
               HorizontalContentAlignment="Stretch" Margin="0"/>
          <Grid Grid.Row="1" >
               <Grid.ColumnDefinitions>
                    <ColumnDefinition Width="Auto"/>
                    <ColumnDefinition Width="Auto"/>
                    <ColumnDefinition Width="*"/>
               </Grid.ColumnDefinitions>
               <DefaultShell:NavigationView x:Name="NavigationView"
                    ShellHelpers:ComponentViewModelService.ViewModelName="Default.NavigationViewModel"
                    HorizontalAlignment="Stretch" HorizontalContentAlignment="Stretch"
                    Grid.Column="0"
                    VerticalAlignment="Stretch" VerticalContentAlignment="Top"/>

               <controls:GridSplitter Grid.Column="1" Width="6" Style="{StaticResource GridSplitterStyle}" IsTabStop="False"
                    Background="Transparent"
                    IsEnabled="{Binding ElementName=NavigationView,Path=IsExpanded, Mode=TwoWay}"
                    VerticalAlignment="Stretch" HorizontalAlignment="Left"/>

               <ContentControl Grid.Column="2" HorizontalAlignment="Stretch" HorizontalContentAlignment="Stretch" Margin="6,3,6,6"
               VerticalAlignment="Stretch" VerticalContentAlignment="Stretch" IsTabStop="False"
               ShellHelpers:ComponentViewService.ComponentContent="Default.ActiveScreensView"
               ShellHelpers:ComponentViewModelService.ViewModelName="Default.ActiveScreensViewModel"/>
          </Grid>
     </Grid>
</UserControl>

As you can see, we got three grids here, that hold together a bunch of controls in the DefaultShell namespace.  They are the actual views that are used in the default LightSwitch shell extension.  We’re also using ShellHelpers to do MVVM, the LightSwitch way.

You might get some red squigly lines in your xaml (errors), caused by the fact that the LightSwitch controls are internal and are not supposed to be used in your custom shell.  However, compiling and running, works just fine.  Press F5 to enjoy the result…

Basically, and given the implementation, it should come to no surprise, it looks exactly like we are using the Default shell.  We just spent 12 of our 15 minutes and from a functional point of view, end up with exactly the same result.  Not good.  However, from a technical point of view, we went from a simple LightSwitch application that uses the Default shell, to a simple LightSwitch application that uses a custom shell extension that looks, works and behaves exactly like the default LightSwitch shell.  Major difference!

Now, we have three minutes left on the clock before we should go back in the office and show the customer our results.  We could spend one minute to update the XAML posted above, swap the grids around until we reach the effect the customer wanted (save & close button under the navigation menu), and arrive two minutes early…

Or… Underpromise, overdeliver, and spend our 180 seconds opening box number four…

Minute 13 ->15: “bisque du homard”: Telerik RADControls for SilverLight

If you don’t have the Telerik RADControls for Silverlight installed, you can hop over to their website and download a trial, which should work just fine for this example.  I’m also giving away a licence at the end of this blog post!

  • Drag a RADDocking control on your DockableShell.xaml’s XAML.  I always forget the correct references to add, and this little trick does that for us.
  • Replace the contents of the DockableShell.xaml with the following…
<UserControl x:Class="DockableShell.Presentation.Shells.DockableShell"
     xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
     xmlns:nav="clr-namespace:System.Windows.Controls;assembly=System.Windows.Controls.Navigation"
     xmlns:controls="clr-namespace:System.Windows.Controls;assembly=System.Windows.Controls"
     xmlns:internalControls="clr-namespace:Microsoft.LightSwitch.SilverlightUtilities.Controls.Internal;assembly=Microsoft.LightSwitch.Client"
     xmlns:internalToolkit="clr-namespace:Microsoft.LightSwitch.Presentation.Framework.Toolkit.Internal;assembly=Microsoft.LightSwitch.Client"
     xmlns:framework="clr-namespace:Microsoft.LightSwitch.Presentation.Framework;assembly=Microsoft.LightSwitch.Client"
     xmlns:stringsLocal="clr-namespace:Microsoft.LightSwitch.Runtime.Shell.Implementation.Resources;assembly=Microsoft.LightSwitch.Client.Internal"
     xmlns:DefaultShell="clr-namespace:Microsoft.LightSwitch.Runtime.Shell.Implementation.Standard;assembly=Microsoft.LightSwitch.Client.Internal"
     xmlns:ShellHelpers="clr-namespace:Microsoft.LightSwitch.Runtime.Shell.Helpers;assembly=Microsoft.LightSwitch.Client"
     xmlns:telerik="http://schemas.telerik.com/2008/xaml/presentation"
     xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml">

     <Grid x:Name="shellGrid" Background="{StaticResource NavShellBackgroundBrush}">
          <telerik:RadDocking x:Name="Docking" BorderThickness="0" Padding="0" telerik:StyleManager.Theme="Metro" >
               <telerik:RadDocking.DocumentHost>
                    <ContentControl HorizontalAlignment="Stretch" HorizontalContentAlignment="Stretch" Margin="6,3,6,6"
                    VerticalAlignment="Stretch" VerticalContentAlignment="Stretch" IsTabStop="False"
                    ShellHelpers:ComponentViewService.ComponentContent="Default.ActiveScreensView"
                    ShellHelpers:ComponentViewModelService.ViewModelName="Default.ActiveScreensViewModel"/>
               </telerik:RadDocking.DocumentHost>

               <telerik:RadSplitContainer InitialPosition="DockedTop">
                    <telerik:RadPaneGroup>
                    <telerik:RadPane Header="Actions">
                              <DefaultShell:CommandsView x:Name="_commandsView"
                                   ShellHelpers:ComponentViewModelService.ViewModelName="Default.CommandsViewModel"
                                   VerticalAlignment="Stretch" VerticalContentAlignment="Stretch" HorizontalAlignment="Stretch" BorderThickness="0"
                                   HorizontalContentAlignment="Stretch" Margin="0"/>
                         </telerik:RadPane>
                    </telerik:RadPaneGroup>
               </telerik:RadSplitContainer>

               <telerik:RadSplitContainer InitialPosition="DockedLeft">
                    <telerik:RadPaneGroup>
                         <telerik:RadPane Header="Navigation" >
                              <DefaultShell:NavigationView x:Name="NavigationView"
                                   ShellHelpers:ComponentViewModelService.ViewModelName="Default.NavigationViewModel"
                                   HorizontalAlignment="Stretch" HorizontalContentAlignment="Stretch"
                                   VerticalAlignment="Stretch" VerticalContentAlignment="Top"/>
                         </telerik:RadPane>
                    </telerik:RadPaneGroup>
               </telerik:RadSplitContainer>
          </telerik:RadDocking>
     </Grid>
</UserControl>

(You might want to hit F5, and walk back inside the customer’s office, while you’re waiting for the build process to complete, or read the rest of the blog post…)  

Compared to the result we had 2 minutes and 45 seconds ago, we’re using the same views that we stole from the LightSwitch default shell implementation, but positioned them in Telerik RadPanes instead of in a simple grid.

Functionally, the end-user can now drag the “Actions” (business name for what we call the Commands View), and drag them anywhere he wants…

Or…

Mission accomplished, project delivered on time, customer is happy, and we made a nice amount of money… LightSwitch business as usual. :-)

Win a Telerik RADControls for SilverLight license worth $799!

The Telerik licence has been won by @MatthieuFichefet, by being selected by Mojo, our judge (Video on the way, it’s hilarious), after tweeting:

Essential reading : Creating a very dynamic #LightSwitch shell extension in 15 minutes or less… http://wp.me/p1J0PO-9T@janvanderhaegen

In case you joined the contest but didn’t win, keep an eye out on the blog for new opportunities… Whenever I have anything to give away, I will!

Enjoy the hacking!

About these ads

15 thoughts on “Creating a very dynamic LightSwitch shell extension in 15 minutes or less…

  1. Hey.. its not fare! I’ve been tweeting ur blog posts since the day one I came to know you! Will I be considered special as a “long time reader” in the contest? :) :)

    When you said Contest, I was worried that you might ask us to create a extension control or something to win!! :) LOL

    • :-) I thought about that for a while, but figured I won’t have the time to go through all the undoubtedly fantastic entries!
      I do consider you very special, but for the contest, everyone should have an equal chance of winning, that’s why Mojo is the judge :-)

  2. Jan, there was just one thing missed (Shell reference) in the exporter code.

    public class DockableShellExporter :
    ExtensionsMadeEasy.ClientAPI.Shell.EasyShellExporter
    { }

  3. What in world…? :) Now I understand why it didn’t show up on your code too. (I think it doesn’t like tags. interprets as HTML I guess).

    Let me try again

    public class DockableShellExporter :
    ExtensionsMadeEasy.ClientAPI.Shell.EasyShellExporter
    { }

  4. This isn’t happening.

    public class DockableShellExporter :
    ExtensionsMadeEasy.ClientAPI.Shell.EasyShellExporter : LESSTHAN DockableShell.Presentation.Shells.Components.DockableShell GREATERTHAN
    { }

  5. great! I’ve been thinking to do something similar with devexpress docklayout control, so i could have screens docked around, grouped together or even detached from controls, start was great, but i got stuck when things get complicated, and also the trial run out.. ;) damn! and this recipe is so cleaner and simpler.. keep good work! cheers!
    Kivito

  6. This is amazing! You’ve made creating shells a doddle!

    Just a couple of comments though. I think there is a mistake in the XAML for the shell when you add the raddocking control. Near the bottom of the XAML, you have a tag like this…

    telerik:RadPane Header=”Navigation”

    …and there is a closing angle bracket missing at the end of the line.

    Also, when I ran it, I got a run time exception…

    System.Windows.Markup.XamlParseException occurred
    Message=The type ‘RadDocking’ was not found because ‘http://schemas.telerik.com/2008/xaml/presentation’ is an unknown namespace. [Line: 19 Position: 52]
    LineNumber=19
    LinePosition=52
    StackTrace:
    at System.Windows.Application.LoadComponent(Object component, Uri resourceLocator)
    at DockableShell.Presentation.Shells.DockableShell.InitializeComponent()
    at DockableShell.Presentation.Shells.DockableShell..ctor()
    InnerException:

    …when the execution hit the InitializeComponent() method in the shell code behind. I added a using for Telerik.Windows and Telerik.Windows.Controls to the file, and it ran fine. Oddly enough, I then removed those usings, and it still ran fine!

    Anyway, thanks again for a brilliant blog post.

    • Hey, thanks for a brilliant comment!

      I updated the typing error in the XAML, thanks for pointing it out.

      I have a suspicion that the run time exception might have something to do with the assembly references to the Telerik components from the shell project, were not being included at runtime by the LightSwitch client project that references your shell project (and uses it via MEF > Extensions Made Easy > …).

      Either way, the trick with Extensions Made Easy is only to help you develop / debug your shell/theme extension. If my suspicion is correct then you’ll never get that run time weirdness if you actually build your shell extension, install it and use it in a LightSwitch application.

      Keep rocking LS!

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out / Change )

Twitter picture

You are commenting using your Twitter account. Log Out / Change )

Facebook photo

You are commenting using your Facebook account. Log Out / Change )

Google+ photo

You are commenting using your Google+ account. Log Out / Change )

Connecting to %s