50 Apps by Christmas: SilverKeys - Porting a Silverlight App to a Windows Store App

This article is part of the 50 Apps by Christmas series.

Graph of 50 app progress

I developed a Silverlight app a few years ago and I wanted to see how easy it would be to port to a Windows Store app. You can download free trial of SilverKeys for Windows 8.

The Original Silverlight App

Here’s a screenshot of the original Silverlight app:

Original Silverlight app screenshot

The app helps amateur composers try out different scales and also helps them understand what common chord progressions are often used.

More...

SHARE:

MVVM Light Telling the View to play Storyboards

Sometimes you want to tell the view to play an animation (Storyboard). One simple way to do this is to define a StartStoryboardMessage class, populate this with the name of a Storyboard to play, then send it to the Messenger.

public class StartStoryboardMessage
{
    public string StoryboardName { getset; }
    public bool LoopForever { getset; }
}

In the viewmodel when you want to tell the view to play an animation:

Messenger.Default.Send(new StartStoryboardMessage { StoryboardName = "TimerFinAnimation",LoopForever=true });

The view (i.e. in the code-behind) registers for these messages:

Messenger.Default.Register<StartStoryboardMessage>(this, x => StartStoryboard(x.StoryboardName, x.LoopForever));

...

private void StartStoryboard(string storyboardName, bool loopForever)
{
    var storyboard = FindName(storyboardName) as Storyboard;
    if (storyboard != null)
    {
        if (loopForever) 
            storyboard.RepeatBehavior = RepeatBehavior.Forever;
        else
            storyboard.RepeatBehavior = new RepeatBehavior(1);
        storyboard.Begin();
    }
}

SHARE:

Notification Window "toast" in Silverlight 4

To create "toast" notifications (small popup messages that appear at the bottom right on Windows) the NotificationWindow class can be used.

NotificationWindow will only work in out-of-browser mode, if you try to show a notification when running in-browser a System.InvalidOperationException is thrown.

The default size of the toast is 400 x 100, the size can be altered by setting NotificationWindow's Width & Height, but the max size is 400 x 100;

Example 1 - Creating Some Simple Content

The code below shows a notification that contains a  TextBlock with the text "hello" and show it for 2 seconds:

var toast = new NotificationWindow();

// Create some content to show
var tb = new TextBlock();
tb.Text = "hello";

// Set content of the toast before we show it
toast.Content = tb;

// Show notification for 2 seconds
toast.Show(2000);

Example 2 - Using a UserControl as Content

This example shows how a UserControl defined elsewhere in XAML can be used as the content of the notification:

var toast = new NotificationWindow();

// Create an instance of a UserControl called SampleToast
// that is defined in XAML
var toastContent = new SampleToast();

// Set the content of the notification window to that of the UserControl
toast.Content = toastContent;

// Optionally set size to be different from the
// default of 400 x 100
toast.Width = 200;
toast.Height = 50;

// Show notification for 2 seconds
toast.Show(5000);

MSDN Docs

 

SHARE:

Hosting HTML Content in Silverlight 4

In Silverlight 4 we can present HTML content to the user.

The WebBrowser Control

For example to display the content of DontCodeTired.com we can set the Source property in XAML:

<WebBrowser Name="webBrowser" Source="http://www.dontcodetired.com" />

Or we can use the Navigate method of WebBrowser:

webBrowser.Navigate(new Uri(@"http://www.dontcodetired.com"));

The user can navigate to links, right-click and open in new tab (which opens up tab in a full web browser), view source, etc.

To use the WebBrowser the Silverlight app must be running out of browser (OOB), if you view when embedded in a web page (i.e. not OOB) the control displays a message saying that it can only be used in Out-of-Browser mode.

WebBrowser control running in-browser

WebBrowserBrush

Using the new WebBrowserBrush (previously HtmlBrush in beta 4) we can use the contents of a WebBrowser to fill another element.

For example, to fill an Ellipse with the content of a WebBrowser named "webBrowser" we define a WebBrowserBrush and sets its SourceName:

<Ellipse>
    <Ellipse.Fill>
        <WebBrowserBrush SourceName="webBrowser"  />
    </Ellipse.Fill>
</Ellipse>

The following example creates an ellipse which is filled with a blurred version of the content of a WebBrowser. One thing to note is that we hook into the WebBrowser's LoadCompleted event to ensure the ellipse is updated whenever a page has been loaded into the WebBrowser. When run it looks like the image below: 



XAML:

<UserControl x:Class="SL4HTMLHosting.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"
    mc:Ignorable="d"
    d:DesignHeight="300" d:DesignWidth="400">

    <Grid x:Name="LayoutRoot" Background="White">
        <Grid.RowDefinitions>
            <RowDefinition Height="1*" />
            <RowDefinition Height="1*" />
        </Grid.RowDefinitions>
       
        <WebBrowser Name="webBrowser"
                    Source="http://www.dontcodetired.com"
                    Grid.Row="0"
                    LoadCompleted="webBrowser_LoadCompleted">
        </WebBrowser>
       
        <Ellipse HorizontalAlignment="Stretch"
                    VerticalAlignment="Stretch"
                    Grid.Row="1">
            <Ellipse.Effect>
                <BlurEffect Radius="5" />
            </Ellipse.Effect>
            <Ellipse.Fill>
                <WebBrowserBrush x:Name="browserBrush"
                                    SourceName="webBrowser"  />
            </Ellipse.Fill>
        </Ellipse>
          
    </Grid>
</UserControl>

Code Behind:

using System.Windows.Controls;

namespace SL4HTMLHosting
{
    public partial class MainPage : UserControl
    {
        public MainPage()
        {
            InitializeComponent();
        }

        private void webBrowser_LoadCompleted(object sender, System.Windows.Navigation.NavigationEventArgs e)
        {
            browserBrush.Redraw();
        }
    }
}

Interacting With WebBrowser Content

The following example uses WebBrowser.NavigateToString which allows a string of HTML to be rendered rather than a page at some Uri. The example creates a simple html button which uses window.external.notify to pass a message to the WebBroswer control via it's ScriptNotify event.

XAML:

<Grid x:Name="LayoutRoot" Background="White">            
    <WebBrowser Name="webBrowser"
                ScriptNotify="webBrowser_ScriptNotify">                  
    </WebBrowser>          
</Grid>

Code Behind:

using System.Windows.Controls;
using System.Text;
using System.Windows;

namespace SL4HTMLHosting
{
    public partial class MainPage : UserControl
    {
        public MainPage()
        {
            InitializeComponent();
           
            // Create some basic HTML on the fly containing a simple button,
            // that when clicked passes a message to the containing WebBrowser
            var html = new StringBuilder();
            html.Append("<html>");
            html.Append("   <body>");
            html.Append(@"       <input type=""button"" value=""Show Message Box"" onclick=""window.external.notify('button click');"" /> ");
            html.Append("   </body>");
            html.Append("</html>");

            webBrowser.NavigateToString(html.ToString());
        }



        private void webBrowser_ScriptNotify(object sender, NotifyEventArgs e)
        {
            MessageBox.Show(string.Format("Message from web page: '{0}'.",e.Value));
        }
    }
}
 

SHARE:

Right Click and Context Menu Support in Silverlight 4

Silverlight 4 allows us access to right-click functionality, in earlier versions of Silverlight right-clicking would bring up the standard SL popup menu.

Simple Right Click

To capture a right-click the MouseRightButtonDown event can be subscribed to:

MouseRightButtonDown="btnPopup_MouseRightButtonDown"

To prevent the standard SL popup from appearing, in the event handler we set Handled to true, this stops the right-click event from bubbling up to Silverlight:

private void btnPopup_MouseRightButtonDown(object sender, MouseButtonEventArgs e)
{   
    // Prevent the standard Silverlight popup          
    e.Handled = true;

    MessageBox.Show("Right button clicked");
}

Context Menu

The ContextMenu is available in the Silverlight Toolkit.

A popup context menu can easily be added to a control (e.g. a button), when the control is right-clicked the context menu is automatically shown

The XAML below declares a simple popup menu with 2 menu items: Open & Save that when clicked both fire the same event handler.

<Button Name="btnContextMenu"
        Content="ContextMenu Right Click"
        MouseRightButtonDown="btnContextMenu_MouseRightButtonDown">
    <toolkit:ContextMenuService.ContextMenu>
        <toolkit:ContextMenu>
            <toolkit:MenuItem Header="Open" Click="ContextMenuItem_Click"/>
            <toolkit:MenuItem Header="Save" Click="ContextMenuItem_Click"/>
        </toolkit:ContextMenu>                   
    </toolkit:ContextMenuService.ContextMenu>     
</Button>

The namespace (xmlns:toolkit="http://schemas.microsoft.com/winfx/2006/xaml/presentation/toolkit") must be declared - if you drag and drop something from the Toolkit the namespace will be automatically added.

Full listing (simple popup & context menu)

XAML:

<UserControl x:Class="SL4RClickPopup.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:toolkit="http://schemas.microsoft.com/winfx/2006/xaml/presentation/toolkit"
    mc:Ignorable="d"
    d:DesignHeight="300" d:DesignWidth="400" >  
   
    <StackPanel>
       
        <Button Name="btnPopup"
                Content="Simple Right Click"
                MouseRightButtonDown="btnPopup_MouseRightButtonDown" />
       
        <Button Name="btnContextMenu"
                Content="ContextMenu Right Click"
                MouseRightButtonDown="btnContextMenu_MouseRightButtonDown">
           
            <toolkit:ContextMenuService.ContextMenu>
                <toolkit:ContextMenu>
                    <toolkit:MenuItem Header="Open" Click="ContextMenuItem_Click"/>
                    <toolkit:MenuItem Header="Save" Click="ContextMenuItem_Click"/>
                </toolkit:ContextMenu>
            </toolkit:ContextMenuService.ContextMenu>
           
        </Button>
         
    </StackPanel>
   
</UserControl>

Code Behind:

using System.Windows;
using System.Windows.Controls;
using System.Windows.Input;

namespace SL4RClickPopup
{
    public partial class MainPage : UserControl
    {
        public MainPage()
        {
            InitializeComponent();
        }



        private void btnPopup_MouseRightButtonDown(object sender, MouseButtonEventArgs e)
        {
            // Prevent the standard Silverlight popup          
            e.Handled = true;

            MessageBox.Show("Right button clicked");
        }



        private void btnContextMenu_MouseRightButtonDown(object sender, MouseButtonEventArgs e)
        {
            // Because we have declare a ContextMenu in Xaml we don't
            // need to manually set e.Handled = true;
        }



        private void ContextMenuItem_Click(object sender, RoutedEventArgs e)
        {
            MessageBox.Show("ContextMenu item clicked");
        }
    }
}
 

SHARE:

Drag and Drop in Silverlight 4

To enable drag and drop support on a Silverlight UIElement the AllowDrop property needs to be set to True (it is false by default). The Drop event can now be subscribed to, in which the dropped item(s) can be processed.

The example below allows a .jpg file to be dragged (from the desktop, Windows Explorer, etc.) and dropped onto the application. When the user drops a .jpg, the image is loaded and displayed.

<Grid x:Name="LayoutRoot"
        Background="Black"
        AllowDrop="True"
        Drop="LayoutRoot_Drop">
       
    <Image Name="imgPresenter"
            Stretch="UniformToFill"
            IsHitTestVisible="False" />
       
    <TextBlock HorizontalAlignment="Center"
                VerticalAlignment="Center"
                IsHitTestVisible="False"
                Foreground="#FFCBC3C3"
                FontSize="32"
                Opacity="0.33"
                Text="drag photo here"/>       
       
</Grid>

 

Code behind:

private void LayoutRoot_Drop(object sender, DragEventArgs e)
{
    if (e.Data != null)
    {
        // Get a list of the file(s) that the user has dragged & dropped
        FileInfo[] droppedFiles = e.Data.GetData(DataFormats.FileDrop) as FileInfo[];

        // If there is at least 1 file that has been dropped
        if (droppedFiles != null && droppedFiles.Length > 0)
        {
            LoadImage(droppedFiles.First());
        }               
    }
}



private void LoadImage(FileInfo imageFileInfo)
{
    if (imageFileInfo.Extension.ToUpper() != ".JPG")
    {
        MessageBox.Show("Only .jpg files are supported...");
    }
    else
    {
        // Load the image from disk
        BitmapImage source = new BitmapImage();
        source.SetSource(imageFileInfo.OpenRead());

        // Set the image being displayed to the new dropped one
        imgPresenter.Source = source;
    }
}

SHARE:

Copy and Paste in Silverlight 4

Silverlight 4 added the static Clipboard class with the clearly-named SetText, GetText and ContainsText methods.

If running in-browser or out-of-browser without elevated trust the Clipboard methods can only be called as a result of user actions (such as Click, KeyDown). The first time a method is called, a security prompt is presented to the user asking them to grant the app access to the clipboard.



If the user selects 'No' a SecurityException is thrown.

 

If the app is running out-of-browser with elevated trust, the above dialog is not shown and the Clipboard methods can be called even if not in response to user initiated events such as in your page constructor, in a DispatcherTimer.Tick event, etc.

Example Code

<Button x:Name="btnCopy" Click="btnCopy_Click">Copy</Button>
<Button x:Name="btnPaste" Click="btnPaste_Click">Paste</Button>
<TextBox x:Name="txtBox"></TextBox>

 

private void btnCopy_Click(object sender, RoutedEventArgs e)
{
    // Assumes we want to prevent the user from copy 'nothing' to the clipboard.
    // Without this check, whatever text is currently in the clipboard will be replaced
    // with nothing.
    if (!string.IsNullOrEmpty(txtBox.Text))
    {
        try
        {
            Clipboard.SetText(txtBox.Text);
        }
        catch (SecurityException ex)
        {
            // If the user does not grant access for the app to access the clipboard
            // a SecurityException is thrown
            MessageBox.Show("Clipboard access is required to use this feature.");
        }
    }
}


private void btnPaste_Click(object sender, RoutedEventArgs e)
{
    try
    {
        txtBox.Text = Clipboard.GetText();
    }
    catch (SecurityException ex)
    {
        // If the user does not grant access for the app to access the clipboard
        // a SecurityException is thrown
        MessageBox.Show("Clipboard access is required to use this feature.");
    }
}

SHARE:

FallbackValue, TargetNullValue & StringFormat in Silverlight 4

Silverlight 4 introduces some further databinding functionality including:

FallbackValue

If there is a problem with performing the binding, for example an incorrect path to a property value, the fallback value will be used in it's place. This allows some meaningful display, rather that the default behaviour of showing nothing.

TargetNullValue

Allows a value to be used if the bound property is null.

StringFormat

Allows formatting of the bound value using standard formatting expressions.

Example

The following is the output of the XAML below:

 

 

<Grid x:Name="LayoutRoot" Background="WhiteSmoke" >
    <Grid.ColumnDefinitions>
        <ColumnDefinition></ColumnDefinition>
        <ColumnDefinition></ColumnDefinition>
    </Grid.ColumnDefinitions>
    <Grid.RowDefinitions>
        <RowDefinition Height="auto"></RowDefinition>
        <RowDefinition Height="auto"></RowDefinition>
        <RowDefinition Height="auto"></RowDefinition>
        <RowDefinition Height="auto"></RowDefinition>
        <RowDefinition Height="auto"></RowDefinition>
        <RowDefinition Height="auto"></RowDefinition>
    </Grid.RowDefinitions>

    <TextBlock TextWrapping="Wrap">Invalid binding path with FallbackValue set</TextBlock>
    <TextBlock Text="{Binding Path=badPath, FallbackValue='this is a fallback value'}" Grid.Column="1"></TextBlock>

    <TextBlock TextWrapping="Wrap" Grid.Row="1">Invalid binding path withOUT FallbackValue set</TextBlock>
    <TextBlock Text="{Binding Path=badPath}" Grid.Row="1" Grid.Column="1"></TextBlock>

    <TextBlock TextWrapping="Wrap" Grid.Row="2">Bound property = null with TargetNullValue set</TextBlock>
    <TextBlock Text="{Binding Path=AnIntProperty, TargetNullValue='The int is null'}" Grid.Column="1" Grid.Row="2"></TextBlock>

    <TextBlock TextWrapping="Wrap" Grid.Row="3">Bound property = null withOUT TargetNullValue set</TextBlock>
    <TextBlock Text="{Binding Path=AnIntProperty}" Grid.Column="1" Grid.Row="3"></TextBlock>

    <TextBlock TextWrapping="Wrap" Grid.Row="4">Bound property with StringFormat</TextBlock>
    <TextBlock Text="{Binding Path=AnotherIntProperty, StringFormat='Age is {0} years old.'}" Grid.Column="1" Grid.Row="4"></TextBlock>

    <TextBlock TextWrapping="Wrap" Grid.Row="5">Bound property withOUT StringFormat</TextBlock>
    <TextBlock Text="{Binding Path=AnotherIntProperty}" Grid.Column="1" Grid.Row="5"></TextBlock>

</Grid>

The DataContext is set to an instance of the following class:

public class TestData : DependencyObject
{
    public int? AnIntProperty
    {
        get { return (int?)GetValue(AnIntPropertyProperty); }
        set { SetValue(AnIntPropertyProperty, value); }
    }

    // Using a DependencyProperty as the backing store for AnIntProperty.  This enables animation, styling, binding, etc...
    public static readonly DependencyProperty AnIntPropertyProperty =
        DependencyProperty.Register("AnIntProperty", typeof(int?), typeof(TestData),
            new PropertyMetadata(0));


    public int AnotherIntProperty
    {
        get { return (int)GetValue(AnotherIntPropertyProperty); }
        set { SetValue(AnotherIntPropertyProperty, value); }
    }

    // Using a DependencyProperty as the backing store for AnotherIntProperty.  This enables animation, styling, binding, etc...
    public static readonly DependencyProperty AnotherIntPropertyProperty =
        DependencyProperty.Register("AnotherIntProperty", typeof(int), typeof(TestData), new PropertyMetadata(0));


    public TestData()
    {
        AnIntProperty = null;
        AnotherIntProperty = 44;
    }

}

SHARE:

Printing Support in Silverlight 4

Basic printing can be accomplished through the PrintDocument class. For example, to print the entire contents of the LayoutRoot grid:

private void btnSimplePrint_Click(object sender, RoutedEventArgs e)
{
    string docName = "Test SL4 Print";

    PrintDocument doc = new PrintDocument();

    doc.PrintPage += (s, args) =>
    {
        // print the entire contents of our page (i.e. the
        // outer grid called LayoutRoot)
        args.PageVisual = LayoutRoot;
    };

    doc.Print(docName); // automatically shows a print dialog box
}

Various properties are avaible on the PrintPageEventArgs that are passed to the PrintPage event handle ('args' in the above example); args.HasMorePages allows you to control multi-page printing and args.PrintableArea allows you to get the printable page area you have available to you (which will depend on the settings chosen in the print dialog box that is shown automatically for you.

It is up to the developer to handle more complex scenarios such as overflowing contents, knowing if there are more pages required, etc. In an enterprise situation other PDF-scentric options may be a better choice, especially if there are complex report layouts required, etc. For example, using iText.NET, FOP, or an enterprise solution such as Thunderhead, xPression, etc. 

SHARE:

Scholar Open Source Project

Scholar is now up on Codeplex. Scholar is a new open source project that will provide a starting point for developers to create distributed data processing such as the SETI@home project. It will use Azure to enable scalability and allow independent developers to get started without needed there own data centres. It's hoped that eventually it might be used to enable large medical and scientific data to be processed.

http://scholar.codeplex.com/

SHARE: