Silverlight Sortables

August 19, 2008 04:02 by wjchristenson2

If any of you have ever developed with the jQuery UI framework, you have probably seen or used their "Sortables".  A sortable is a list of items that you can drag around & adjust their sort order.  In this post, I am going to show you how we can sort Border objects within a StackPanel.  Here is a screen shot of what we are after:

When the user drags an item over another, they will swap places.  A transparent placeholder will represent the current item being dragged.  First, let's setup our XAML page before we get into actual code.

<UserControl x:Class="Sortables.Page"
    xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation" 
    xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" 
    Width="400" Height="300">
    <UserControl.Resources>
        <Style x:Key="borderStyle" TargetType="Border">
            <Setter Property="Width" Value="220" />
            <Setter Property="Height" Value="50" />
            <Setter Property="Margin" Value="5,5,5,0" />
            <Setter Property="BorderBrush" Value="Black" />
            <Setter Property="Background" Value="SteelBlue" />
            <Setter Property="BorderThickness" Value="1" />
            <Setter Property="CornerRadius" Value="10" />
            <Setter Property="HorizontalAlignment" Value="Left" />
        </Style>
    </UserControl.Resources>
      
    <Canvas x:Name="LayoutRoot" Background="White">
        <StackPanel x:Name="StackPanel1" Orientation="Vertical" Width="250">
            <Border x:Name="border1" Style="{StaticResource borderStyle}">
                <TextBlock Text="Item 1" Foreground="White" Margin="0,10,0,0" HorizontalAlignment="Center" />
            </Border>
            <Border x:Name="border2" Style="{StaticResource borderStyle}">
                <TextBlock Text="Item 2" Foreground="White" Margin="0,10,0,0" HorizontalAlignment="Center" />
            </Border>
            <Border x:Name="border3" Style="{StaticResource borderStyle}">
                <TextBlock Text="Item 3" Foreground="White" Margin="0,10,0,0" HorizontalAlignment="Center" />
            </Border>
            <Border x:Name="border4" Style="{StaticResource borderStyle}">
                <TextBlock Text="Item 4" Foreground="White" Margin="0,10,0,0" HorizontalAlignment="Center" />
            </Border>
        </StackPanel>
    </Canvas>
</UserControl>

You can see we have a Canvas that has a StackPanel with 4 border objects.  We will be dragging (sorting) the items within the StackPanel.  This example builds off my previous post Drag and Drop in Silverlight.  I'm not going to review the code for dragging and dropping, so if you are having problems, revisit the previous tutorial.  The Border items in the StackPanel will utilize all of my basic drag & drop code from before, but it will also perform extra logic for the "sorting" abilities.  Let's first define our variables.

Private isMouseDown As Boolean = False
Private mousePosition As Point = Nothing
Private itemIndex As Integer = -1   'used to keep track of the index of the item we are dragging

The itemIndex variable is used to keep track of what index the dragged item should be tied to within the StackPanel.  The other 2 variables are for dragging the item.  When the page is loaded, we'll wire in the necessary events for each Border object within our StackPanel.

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

    For Each element As UIElement In StackPanel1.Children
        If TypeOf (element) Is Border Then
            Dim item As Border = DirectCast(element, Border)
            AddHandler item.MouseLeftButtonDown, AddressOf item_MouseLeftButtonDown
            AddHandler item.MouseLeftButtonUp, AddressOf item_MouseLeftButtonUp
            AddHandler item.MouseMove, AddressOf item_MouseMove
        End If
    Next
End Sub

When the user clicks a Border object within our StackPanel, well do our basic drag & drop routines and then we'll also add to that.  First we'll remove the Border object from the StackPanel and add it to our LayoutRoot (parent).  What this does is allow us to drag the Border object around the screen.  We also want to add a placeholder where the Border object used to be in our StackPanel.  I'll be using a transparent Rectangle object to accomplish this.  Here's the logic for the MouseLeftButtonDown event.

Sub item_MouseLeftButtonDown(ByVal sender As Object, ByVal e As MouseEventArgs)
    Dim item As Border = DirectCast(sender, Border)
    mousePosition = e.GetPosition(Nothing)
    Me.isMouseDown = True
    item.CaptureMouse()

    'get the item's position relative to the LayoutRoot canvas object
    Dim newTopLeft As Point = GetRelativePosition(item, LayoutRoot)
    item.SetValue(Canvas.TopProperty, newTopLeft.Y - item.Margin.Top)
    item.SetValue(Canvas.LeftProperty, newTopLeft.X - item.Margin.Left)
    item.SetValue(Canvas.ZIndexProperty, 2000)

    'define a rectangle placeholder for the item we are dragging
    Dim recPlaceHolder As Rectangle = New Rectangle()
    With recPlaceHolder
        .Fill = New SolidColorBrush(Colors.Transparent)
        .Margin = New Thickness(5, 5, 5, 0)
        .Width = 220
        .Height = 50
        .HorizontalAlignment = Windows.HorizontalAlignment.Left
    End With

    'remove the item we are dragging and insert our placeholder
    itemIndex = StackPanel1.Children.IndexOf(item)
    StackPanel1.Children.Remove(item)
    StackPanel1.Children.Insert(itemIndex, recPlaceHolder)
    LayoutRoot.Children.Add(item)
End Sub

At this point, we have a placeholder inserted into our StackPanel where our dragged Border object used to be.  As we drag our Border object around the screen, we want to perform a "HitTest" to see if we are hovering over another Border object within the StackPanel.  If we are, we'll want to switch the placeholder with the hovered over Border object.  We also want to keep track of the new index for the dragged item.

Sub item_MouseMove(ByVal sender As Object, ByVal e As MouseEventArgs)
    Dim item As Border = DirectCast(sender, Border)

    If (Me.isMouseDown) Then
        Dim deltaV As Double = e.GetPosition(Nothing).Y - mousePosition.Y
        Dim deltaH As Double = e.GetPosition(Nothing).X - mousePosition.X
        Dim newTop As Double = deltaV + DirectCast(item.GetValue(Canvas.TopProperty), Double)
        Dim newLeft As Double = deltaH + DirectCast(item.GetValue(Canvas.LeftProperty), Double)
        item.SetValue(Canvas.TopProperty, newTop)
        item.SetValue(Canvas.LeftProperty, newLeft)
        mousePosition = e.GetPosition(Nothing)

        'run a hit test to see if the dragged item is hovering over another item in the StackPanel
        For Each element As UIElement In HitTest(New Point(e.GetPosition(Nothing).X, e.GetPosition(Nothing).Y))
            If TypeOf (element) Is Border AndAlso TypeOf (DirectCast(element, Border).Parent) Is StackPanel AndAlso DirectCast(DirectCast(element, Border).Parent, StackPanel).Name = "StackPanel1" Then
                'item order changed - switch placeholder with hovered over item
                Dim newItemIndex = StackPanel1.Children.IndexOf(DirectCast(element, Border))
                Dim recPlaceHolder As Rectangle = DirectCast(StackPanel1.Children(itemIndex), Rectangle)
                StackPanel1.Children.RemoveAt(itemIndex)
                StackPanel1.Children.Insert(newItemIndex, recPlaceHolder)
                itemIndex = newItemIndex
                Exit For
            End If
        Next
    End If
End Sub

When the user stops dragging their item, we want to snap the dragged item back into place within the StackPanel.  To accomplish this, we will remove the placeholder Rectangle object and insert the dragged Border object where the placeholder was within the StackPanel.  Here is the final event we handle and we are finished.

Sub item_MouseLeftButtonUp(ByVal sender As Object, ByVal e As MouseEventArgs)
    Dim item As Border = DirectCast(sender, Border)
    isMouseDown = False
    mousePosition = New Point(0, 0)
    item.ReleaseMouseCapture()

    'replace the placeholder with the dragged item
    LayoutRoot.Children.Remove(item)
    StackPanel1.Children.RemoveAt(itemIndex)
    StackPanel1.Children.Insert(itemIndex, item)
End Sub

Sortables.zip (551.97 kb)

** Edit 9/30/2008 **
Silverlight 2 RC0 no longer supports the HitTest function within UIElement.  You have to use the VisualTree.FindElementInHostCoordinates method instead.  Below you'll find the SL2B2 version of this example.

Sortables_SL2RC0.zip (556.49 kb)

Bookmark and Share

Drag and Drop in Silverlight

August 16, 2008 09:19 by wjchristenson2

In this example, I am going to show you how to drag and drop objects in a Silverlight Application.  To make it a little more fun, we are going to be dragging ducks around a Duck Hunt for NES canvas.  Bring back the memories?  The following code snippet is our XAML document that gives us 2 ducks on our page.

<UserControl x:Class="Drag_N_Drop.Page"
    xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation" 
    xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" 
    xmlns:local="clr-namespace:Drag_N_Drop"
    Width="500" Height="438">
    <Canvas x:Name="LayoutRoot">
        <Canvas.Background>
            <ImageBrush ImageSource="Images/canvas.png" />
        </Canvas.Background>
          
        <local:Duck Canvas.Left="140" Canvas.Top="20" />
        <local:Duck Canvas.Left="240" Canvas.Top="20" />
    </Canvas>
</UserControl>

So we have 2 ducks on a canvas.  The next step is to wire each duck up so that when the user drags a duck across the screen, the duck moves with the cursor until they let go.  First we define our variables.  We need a variable to hold the X and Y coordinates of the mouse when we begin dragging and another variable to track if the user has their left mouse button down or not.

Private isMouseDown As Boolean = False
Private mousePosition As Point = Nothing

We want to handle 3 mouse events for each duck: MouseLeftButtonDown, MouseMove, and MouseLeftButtonUp.  When the page is loaded, I loop through through each duck on the canvas and add these 3 event handlers for them.

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

    For Each element As UIElement In LayoutRoot.Children
        If TypeOf (element) Is Duck Then
            Dim item As Duck = DirectCast(element, Duck)
            AddHandler item.MouseLeftButtonDown, AddressOf duck_MouseLeftButtonDown
            AddHandler item.MouseLeftButtonUp, AddressOf duck_MouseLeftButtonUp
            AddHandler item.MouseMove, AddressOf duck_MouseMove
        End If
    Next
End Sub

When the user presses the left mouse button on a duck, we need to capture what the current X and Y coordinates are, set that the mouse left button is down, and capture the mouse events for the duck.

Sub duck_MouseLeftButtonDown(ByVal sender As Object, ByVal e As MouseEventArgs)
    Dim item As Duck = DirectCast(sender, Duck)
    mousePosition = e.GetPosition(Nothing)
    Me.isMouseDown = True
    item.CaptureMouse()
End Sub

Take note of line 5.  The item.CaptureMouse() method basically tells Silverlight to only handle mouse events for the duck we are dragging.  As the user moves the mouse, the MouseMove event is fired.  This event will fire regardless if the user is dragging the duck or not.  Therefore we check to see if the left mouse button is down first.  If it is, it's being dragged so we want to refresh the duck's new position.  We use the current position of the mouse and calculate what the new position the duck should be set to relative to the canvas.

Sub duck_MouseMove(ByVal sender As Object, ByVal e As MouseEventArgs)
    Dim item As Duck = DirectCast(sender, Duck)

    If (Me.isMouseDown) Then
        Dim deltaV As Double = e.GetPosition(Nothing).Y - mousePosition.Y
        Dim deltaH As Double = e.GetPosition(Nothing).X - mousePosition.X
        Dim newTop As Double = deltaV + DirectCast(item.GetValue(Canvas.TopProperty), Double)
        Dim newLeft As Double = deltaH + DirectCast(item.GetValue(Canvas.LeftProperty), Double)
        item.SetValue(Canvas.TopProperty, newTop)
        item.SetValue(Canvas.LeftProperty, newLeft)
        mousePosition = e.GetPosition(Nothing)
    End If
End Sub

When the user releases the left mouse button, we release the mouse capture and set our isMouseDown variable back to false.

Sub duck_MouseLeftButtonUp(ByVal sender As Object, ByVal e As MouseEventArgs)
    Dim item As Duck = DirectCast(sender, Duck)
    isMouseDown = False
    mousePosition = New Point(0, 0)
    item.ReleaseMouseCapture()
End Sub


The final application looks like below.  You can drag the ducks around the canvas.

Drag-N-Drop_Soln.zip (2.11 mb)

Bookmark and Share

Call Javascript Method from Silverlight and Vice Versa

August 11, 2008 09:49 by wjchristenson2

Silverlight integrates seamlessly with Javascript and ASP.NET AJAX.  In this post, I am going to show you how to communicate back and forth between Silverlight and Javascript.  Silverlight managed code can interact with JavaScript by utilizing the classes within the System.Windows.Browser namespace.  Below is the markup for both the Silverlight application as well as the ASP.NET markup for our example.

Silverlight XAML:

<Grid x:Name="LayoutRoot" Background="Silver">
    <Grid.RowDefinitions>
        <RowDefinition Height="25" />
        <RowDefinition Height="25" />
    </Grid.RowDefinitions>
    <Grid.ColumnDefinitions>
        <ColumnDefinition Width="150" />
        <ColumnDefinition Width="150" />
    </Grid.ColumnDefinitions>
   
    <TextBlock x:Name="tbkTest" Grid.Column="0" Grid.Row="0" Grid.ColumnSpan="2" Foreground="Black">HELLO WORLD!!</TextBlock>
    <TextBox x:Name="tbxText" Grid.Column="0" Grid.Row="1"></TextBox>
    <Button x:Name="btnFire" Grid.Column="1" Grid.Row="1" Content="Fire JavaScript Alert"></Button>
</Grid>

ASP.NET HTML:

<div  style="height:100%;">
  <div>
    <asp:TextBox ID="tbxText" runat="server" />
    <input type="button" value="Set Silverlight Text" onclick="setText();" />
  </div>
          
  <asp:Silverlight ID="Xaml1" runat="server" Source="~/ClientBin/SilverlightJScriptInterop_App.xap" MinimumVersion="2.0.30523" Width="100%" Height="100%" />
</div>

My plan is to:
1)  Change the Silverlight TextBlock's text to the text I type into the ASP.NET TextBox when the user clicks the HTML button.
2)  Fire a JavaScript alert consisting of the text I type into my Silverlight TextBox when the user clicks the Silverlight "btnFire" button.

 

Call Silverlight Method from Javascript

Using Javascript to interact with Silverlight is easy to do.  The first thing you need to do is expose objects of your Silverlight application so that JavaScript can interact with your managed code without requiring a round trip (PostBack) to the server.

Imports System.Windows.Browser

<ScriptableType()> _
  Partial Public Class Page
    Inherits UserControl

    Public Sub New()
        InitializeComponent()
    End Sub

    Private Sub Page_Loaded(ByVal sender As Object, ByVal e As System.Windows.RoutedEventArgs) Handles Me.Loaded
        'register silverlight object on the page for javascript to use
        HtmlPage.RegisterScriptableObject("myObject", Me)
    End Sub

    <ScriptableMember()> _
    Public Sub SetText(ByVal text As String)
        Me.tbkTest.Text = text
    End Sub

    Private Sub btnFire_Click(ByVal sender As Object, ByVal e As System.Windows.RoutedEventArgs) Handles btnFire.Click
        HtmlPage.Window.Invoke("alertText", Me.tbxText.Text)
    End Sub
End Class

Remember that the System.Windows.Browser namespace needs to be utilized so that Silverlight can interact with JavaScript.  You'll notice on line 3 that we give our class a ScriptableType() attribute so that we can interact with it via JavaScript.  Anything we wish to expose to JavaScript is also given the ScriptableMember attribute.  On line 13 you'll notice that we register an object on the page for JavaScript to use to access our exposed Silverlight objects.  So let's have some JavaScript pass in a string to our "SetText()" Silverlight method.

  <script type="text/javascript" language="javascript">
  function setText() {
    var pluginObject = $find("<%=Xaml1.ClientID%>");
    var plugin = pluginObject.get_element();
    plugin.Content.myObject.SetText(getText());
  }
  function getText() {
    var obj = document.getElementById("<%=tbxText.ClientID%>");
    return obj.value;
  }
  function alertText(text) {
    alert(text);
  }
  </script>

When the user clicks on my HTML button, I call the setText() JavaScript function.  In line 3 we first get a handle to the Silverlight plugin.  We then get the reference to the actual Silverlight plugin element within the page.  Now we can access the exposed objects of our Silverlight application by using the object name we setup in the  Page_Loaded event.  Remember that we had the "SetText()" method that set the TextBlock's text.  We call that method on line 5 and pass in the text entered via the TextBox on the page.

 

Call JavaScript Method from Silverlight

Calling a JavaScript method from Silverlight is easy.  The HtmlPage.Window.Invoke method invokes a method on the current scriptable object and you can pass in one or more parameters if you wish.  In our example we wanted to pass in a string from our Silverlight application to our JavaScript function and alert the string.  First is the Silverlight managed code to invoke the JavaScript function and the second code snippet is the JavaScript function we invoked.

Private Sub btnFire_Click(ByVal sender As Object, ByVal e As System.Windows.RoutedEventArgs) Handles btnFire.Click
    HtmlPage.Window.Invoke("alertText", Me.tbxText.Text)
End Sub

 

  function alertText(text) {
    alert(text);
  }

I've posted the solution so you can see the code in its entirety and see it in action.

SilverlightJScriptInterop_Soln.zip (541.66 kb)

Bookmark and Share

Silverlight Introduction

August 9, 2008 04:22 by wjchristenson2

Silverlight is often referred to as Microsoft's Flash.  Microsoft needed a cross-platform cross-browser implementation of the .NET Framework for building and delivering next generation media experiences and Rich Interactive Applications (RIA) for the web.  Playing WMV video, MP3 and WMA audio, progressively downloading & streaming media , vector drawing and animations, etc are all supported by Silverlight.

Developers can write Silverlight applications in any .NET language and/or they can write them in entirely in XAML.  Silverlight integrates seamlessly with Javascript and ASP.NET AJAX.  For instance, a Silverlight function can call a Javascript function and vice versa.  Silverlight is also delivered to the browser in XAML.  This is interesting to me because search engines can then crawl the Silverlight object's XAML.  Therefore Silverlight application content can be easily indexed by search engines and thus are more findable than Adobe Flash movie content.

I plan to start developing with Silverlight in the near future.  I'll be blogging on my experiences as time permits.

Bookmark and Share