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

Bind a Collection to a GridView

August 8, 2008 05:41 by wjchristenson2

Over the past few months I've had some requests to show how you can bind a custom object collection to a GridView.  When looking deeper into the collections, I noticed that the developers were working way to hard to create their collection class.   The collections were inheriting from System.Collections.Specialized.NameObjectCollectionBase and implementing IEnumerator, IEnumerable, & IUpdatable, etc to accomplish some simple tasks (ie: add, remove, for each loops, etc).  When implementing these interfaces, the developers were manually having to wire everything in.  On top of this, they were having problems binding to .NET controls.  Another quick point to make here is that the specialized collection does type casting at run time.

What I proposed was to inherit from System.Collections.Generic.List instead.  What this does is allow them to do their basic add, remove, sort, and manipulate their lists.  It also is strongly typed and can be accessed by index.  Generics provide better type safety and performance than non-generic collections (ie NameObjectCollectionBase mentioned above).

I'm going to create a simple vehicle collection and bind a GridView to it using the Generic.List.

Public Class Vehicle
    Private _Make As String = String.Empty
    Private _Model As String = String.Empty

    Public Property Make() As String
        Get
            Return _Make
        End Get
        Set(ByVal value As String)
            _Make = value
        End Set
    End Property

    Public Property Model() As String
        Get
            Return _Model
        End Get
        Set(ByVal value As String)
            _Model = value
        End Set
    End Property

    Public Sub New()

    End Sub

    Public Sub New(ByVal make As String, ByVal model As String)
        _Make = make
        _Model = model
    End Sub
End Class

Here is the vehicle class (object) that we'll reference in our collection.  Now for the collection.

Imports System.Collections.Generic

Public Class Vehicles
    Inherits List(Of Vehicle)

#Region "Constructors"
    Public Sub New()
        MyBase.New()
    End Sub

    Public Sub New(ByVal capacity As Integer)
        MyBase.New(capacity)
    End Sub

    Public Sub New(ByVal collection As IEnumerable(Of Vehicle))
        MyBase.New(collection)
    End Sub
#End Region

End Class

You'll notice how we inherit from System.Collections.Generic.List and cast the collection as a type of Vehicle.  It's as simple as that.  I added 3 constructors for your own reference.  Now all we have to do is add some data to the collection and bind a GridView to it.

Imports CollectionDataBinding.BLL

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

    Protected Sub Page_Load(ByVal sender As Object, ByVal e As System.EventArgs) Handles Me.Load
        If Not Page.IsPostBack() Then
            GridView1_DataBind()
        End If
    End Sub

    Private Sub GridView1_DataBind()
        Dim myVehicles As Vehicles = New Vehicles()
        myVehicles.Add(New Vehicle("Ford", "Mustang"))
        myVehicles.Add(New Vehicle("Pontiac", "Grand AM"))

        Me.GridView1.DataSource = myVehicles
        Me.GridView1.DataBind()
    End Sub
End Class

On page load, I bind the GridView if it is not a postback.  I create a new instance of the Vehicles collection and add 2 vehicles to it.  I then simply set the data source of the GridView to the Vehicle collection and call DataBind().  Now, each public property will be mapped and shown in your GridView if you have AutoGenerateColumns="true" set.  If you don't do this, you'd approach the custom column mappings the same way as with a DataTable, DataView, etc (TemplateField, Eval expressions).

CollectionDataBinding_Soln.zip (89.23 kb)

>>>>> Edit 8/21/2008 <<<<<

I found another article that relates to this post.  It's a great reference as to the benefits of using generic collections.  The article lists the following reasons why you should use generic collections.

  1. Readability and simplicity of your code. Let’s take the case of getting the first string in a list of strings. With generics you would declare List<T> myList = new List<T>(); and then say string firstString = myList[0] just like working with arrays. This syntax is much more simple and readable than what you used to have to write which was ArrayList myList = new ArrayList(); and then string firstString = myList[0] as string;
  2. Performance. Every time you add a value type to a non-generic collection you have to box it to make it an object and every time you retrieve a value type from a non-generic collection you have to unbox it back from object. Even with reference type you still need to cast back to the correct type when retrieving items.
  3. Working against new smaller Framework SKUs. In Silverlight we decided to remove all the concrete non-generic collections completely from the codebase. This is mostly because the Silverlight core managed libraries is set to be the smallest useful set of classes. It’s also possible that other future small frameworks will not have the non-generic collections in store. If you ever plan to write code for those frameworks — you should convert it to use the generic collections.
  4. Better type-safe libraries. If you are developing libraries to be consumed by 3rd parties you should most definitely use generic collections when possible. This would allow consumer of your libraries to quickly figure out what’s expected to be stored in the collections instead of having to guess or figure out from documentation.
Bookmark and Share

Custom GridView Paging

August 7, 2008 02:00 by wjchristenson2

One of the first topics I searched for when working with the ASP.NET GridView control was how to page data with it.  I quickly discovered that the GridView control has built-in paging capabilities.  However, I wanted to customize my GridView pager.  The built-in pager just didn't cut it for me.  Not only did I want to customize the pager, I didn't want to have to define my pager template and wire everything in each time I used my GridView control because it would be used 100's of times.

In order to encapsulate the paging features I desire, I am going to make my own custom GridView control.  In my pager I want the following:
1) A "Page X of Y" caption
2) Basic first, previous, next, and last navigation
3) A page selector drop down list so the user can jump to a page quickly

If you've used the GridView before, you may have utilized the built-in paging modes.  I don't necessarily want to remove that functionality.  I want to add to it.  So what I am going to do is create a public property called "PagerType" where we can specify if we want to use the GridView's built-in paging or if we want to use our custom paging.

Public Class PagingGridView
    Inherits GridView

    Public Enum PagerTypes
        Regular = 0
        Custom = 1
    End Enum

    <DefaultValue(PagerTypes.Custom), Category("Paging"), Description("Indicates whether to use the built-in custom pager or not.")> _
    Public Property PagerType() As PagerTypes
        Get
            If Not ViewState("PagerType") Is Nothing Then
                Return DirectCast(ViewState("PagerType"), PagerTypes)
            Else
                Return PagerTypes.Custom
            End If
        End Get
        Set(ByVal value As PagerTypes)
            ViewState("PagerType") = value
        End Set
    End Property

From this code snippet we are inheriting from the GridView control, defining our 2 paging types (regular & custom), and we define our pager type property.  In order for our pager to be initialized, we need to override the InitializePager method.  What this method does is initialize the pager row when paging is enabled.  We need to determine if we want to allow the GridView to use its built-in pager or if we need to use our custom pager.  A simple select case statement will do the trick here.

    Protected Overrides Sub InitializePager(ByVal row As System.Web.UI.WebControls.GridViewRow, ByVal columnSpan As Integer, ByVal pagedDataSource As System.Web.UI.WebControls.PagedDataSource)
        Select Case Me.PagerType
            Case PagerTypes.Custom
                InitCustomPager(row, columnSpan, pagedDataSource)
            Case Else
                MyBase.InitializePager(row, columnSpan, pagedDataSource)
        End Select

You'll notice that we have an InitCustomPager method we call to initialize our pager controls.  I'm not going to post the code snippet for this.  You can download the code and sift through it if you want.  The things to note is the wiring of events.  Our first, previous, next, and last image buttons throw a Command event and the drop down list throws a SelectedIndexChanged event.  Our PagingGridView control will handle these events and fire a the OnPageIndexChanging event which can be handled by the page.

    Protected Sub ddlPages_SelectedIndexChanged(ByVal sender As Object, ByVal e As System.EventArgs)
        Dim newPageIndex As Integer = CType(sender, DropDownList).SelectedIndex
        OnPageIndexChanging(New GridViewPageEventArgs(newPageIndex))
    End Sub

    Protected Sub PagerCommand(ByVal sender As Object, ByVal e As System.Web.UI.WebControls.CommandEventArgs)
        Dim curPageIndex As Integer = Me.PageIndex
        Dim newPageIndex As Integer = 0

        Select Case e.CommandName
            Case "First"
                newPageIndex = 0
            Case "Previous"
                If curPageIndex > 0 Then
                    newPageIndex = curPageIndex - 1
                End If
            Case "Next"
                If Not curPageIndex = Me.PageCount Then
                    newPageIndex = curPageIndex + 1
                End If
            Case "Last"
                newPageIndex = Me.PageCount
        End Select

        OnPageIndexChanging(New GridViewPageEventArgs(newPageIndex))
    End Sub

Here are the even handlers for my image buttons and the page drop down list.  You'll see that in each handler, I raise the OnPageIndexChanging event passing in the new page index to navigate to.

So let's use our new PagingGridView control.  I'm going to dynamically create a DataTable object on the fly to bind my GridView to for this example.  I also created some css and images to make the GridView look good.  You can find them in the download package if you need them.

<cc1:PagingGridView ID="PagingGridView1" runat="server" 
  CssClass="paging_gridview" PageSize="5" AllowPaging="true" Width="400">
  <RowStyle CssClass="paging_gridview_itm" />
  <PagerStyle CssClass="paging_gridview_pgr" />
  <HeaderStyle CssClass="paging_gridview_hdr" />
  <AlternatingRowStyle CssClass="paging_gridview_aitm" />
</cc1:PagingGridView>

Here is the HTML markup for our new PagingGridView.  Note that we enabled paging and set the page size to 5.  We did not specify the pager type to custom because the default value is set to our custom pager.  If we wanted to revert to the GridViews built-in paging, we would set PagerType="Regular".  The only thing left to do is handle the OnPageIndexChanging to rebind our GridView to the new page via our code behind and that's it.

    Private Sub PagingGridView1_PageIndexChanging(ByVal sender As Object, ByVal e As System.Web.UI.WebControls.GridViewPageEventArgs) Handles PagingGridView1.PageIndexChanging
        Me.PagingGridView1.PageIndex = e.NewPageIndex
        PagingGridView1_DataBind()
    End Sub

I hope you enjoyed this post.  It definitely has improved developer productivity without having to define a pager template and wire in the controls everytime I use the GridView.  The final screenshot of the PagingGridView is below.

GridViewPaging_Soln.zip (108.06 kb)

Bookmark and Share