Silverlight Performance Tips

November 19, 2008 09:30 by wjchristenson2

1)  Do set IsWindowless=False.  There is a high performance price in rendering windowless controls.  However I've found that if you do this, you might run into some problems.  First, if you want your background of your SL application to be transparent, then you must set IsWindowless to True.  Also, if you have HTML overlays (modals) over your SL application, you'll also need to set IsWindowless = True.

2)  Do NOT set the Silverlight's HTML control background property to transparent or any variation thereof (make it opaque).   If the background property is set to such, each render call will go through a blending sequence which adds to a higher CPU cost.

3)  Do change the Silverlight's application default MaxFrameRate.  The default value is 60.  Most SL applications will look/run fine anywhere between 15 to 30.  You can change the MaxFrameRate programmatically or simply markup the Silverlight HTML control.

4)  Do NOT do text size animations.  When you animate the size of text in SL, it uses hinting to smooth each text glyph.  When animating text size, SL may drop frames due to this.  If you can, use a vector graphic to represent large text animations.

5)  Do use Visibility instead of Opacity whenever possible.  Even if an object's opacity is set to 0, SL will still account for it and its still technically rendered.  Setting the object's visibility to Collapsed will cause SL to ignore rendering the object.

6)  When using the MediaElement object, do not specify its Width and Height.  Let SL render the object at its natural size.

7)  Do not set the Width and Height on path objects.  Rely on the points defined for the path.

8)  When displaying a double's value, do use Double.ToString(CultureInfo.InvariantCulture) rather than Double.ToString().  This will alleviate the need for SL to acquire the culture setting before displaying the double and CurltureInfo.InvariantCulture is optimized for perormance.

9)  If your application is very large, consider loading pieces of it "on demand".


Be the first to rate this post

  • Currently 0/5 Stars.
  • 1
  • 2
  • 3
  • 4
  • 5

Save Scroll Position GridView Control

October 29, 2008 16:09 by wjchristenson2

I've seen a lot of articles on the web describing how to scroll a GridView.  Some of those articles may even go a little deeper and show you how to persist the scroll position across PostBacks.  What I haven't been able to find is how to encapsulate the scrolling and saving/persisting of the scroll position into a custom GridView control.  That is what I am going to show you how to do today.

There are many ways a developer may want to scroll their GridView (horizontal, vertical, freezing the header/footer, etc).  I'm not going to go into creating a bullet proof GridView control to accomplish those tasks.  My objective is to show you a technique to save/set the scroll position across PostBacks within a custom GridView control.

Here is what our end GridView control will look like.  First you can see a scrollable GridView.  When we scroll the GridView and then click the PostBack button, the page will perform a PostBack and the <div> will scroll itself back to where we left off.



So let's get to it.  First thing we do is create a new control and inherit from the GridView.  We then implement the IPostBackDataHandler to get and set our scroll position across PostBacks.  You'll see the LoadPostData function accomplishes this.  Take note that in line 3 I am referencing the name of my hidden field that I'll go over later.  We also tell the page that our new custom GridView (Me) has data to PostBack and we can do this in the GridView's Init.


   1:      Public Function LoadPostData(ByVal postDataKey As String, ByVal postCollection As System.Collections.Specialized.NameValueCollection) As Boolean Implements System.Web.UI.IPostBackDataHandler.LoadPostData
   2:          'get and set the new scroll position
   3:          Dim postedValue As String = postCollection(Me.ClientID & "_scroll")
   4:          If Not postedValue Is Nothing Then
   5:              Dim presentValue As Integer = Me.ScrollPosition
   6:              Me.ScrollPosition = CType(postedValue, Integer)
   7:              Return Not presentValue.Equals(Me.ScrollPosition)
   8:          End If
   9:      End Function
  10:   
  11:      Public Sub RaisePostDataChangedEvent() Implements System.Web.UI.IPostBackDataHandler.RaisePostDataChangedEvent
  12:          'do nothing
  13:      End Sub
  14:   
  15:      Private Sub ScrollingGridView_Init(ByVal sender As Object, ByVal e As System.EventArgs) Handles Me.Init
  16:          If Not Me.Page Is Nothing Then
  17:              'indicates that the control has data to postback
  18:              Me.Page.RegisterRequiresPostBack(Me)
  19:          End If
  20:      End Sub

In order to scroll my new custom GridView, I need to wrap my GridView with a <div> tag.  To do this, I can override the Render method like so:


   1:      Protected Overrides Sub Render(ByVal writer As System.Web.UI.HtmlTextWriter)
   2:          If Not Me.Page Is Nothing Then
   3:              Me.Page.VerifyRenderingInServerForm(Me)
   4:          End If
   5:   
   6:          Me.PrepareControlHierarchy()
   7:   
   8:          If Not Me.DesignMode Then
   9:              If String.IsNullOrEmpty(Me.ClientID) Then
  10:                  Throw New HttpException("ScrollingGridView must be parented!")
  11:              End If
  12:   
  13:              writer.AddAttribute(HtmlTextWriterAttribute.Id, String.Format("{0}_div", Me.ClientID), True)
  14:              writer.AddAttribute("onScroll", "saveScrollPos('" & String.Format("{0}_scroll", ClientID) & "', '" & String.Format("{0}_div", Me.ClientID) & "');")
  15:              writer.AddStyleAttribute(HtmlTextWriterStyle.OverflowY, "auto")
  16:              writer.AddStyleAttribute(HtmlTextWriterStyle.Height, "300px")
  17:              writer.RenderBeginTag(HtmlTextWriterTag.Div)
  18:          End If
  19:   
  20:          Me.RenderContents(writer)
  21:   
  22:          If Not Me.DesignMode Then
  23:              writer.RenderEndTag()
  24:          End If
  25:      End Sub

Notice that when I render my <div> that I also wire in a call to my JavaScript function to save the scroll position "onScroll".  At this point I have completed the PostBack data handling and I have shown you how to wrap a <div> around our GridView so we can scroll and it will call a JavaScript function "onScroll" that will save the position to a hidden field.

The next phase is to emit our JavaScripts.  I always emit JavaScript in the PreRender phase of a control's lifecycle as the ClientID should be set at that point.  We need a function to set the scroll position and a function to save the scroll position.  Here they are:


   1:          'generate & register javascript blocks
   2:          Dim key As String = "ScrollingGridView"
   3:          Dim script As StringBuilder = New StringBuilder()
   4:          With script
   5:              .AppendLine("function saveScrollPos(whereID, whatID) {")
   6:              .AppendLine("  document.getElementById(whereID).value = document.getElementById(whatID).scrollTop;")
   7:              .AppendLine("}")
   8:              .AppendLine("function setScrollPos(whereID, whatID) {")
   9:              .AppendLine("  document.getElementById(whatID).scrollTop = (document.getElementById(whereID).value.length > 0) ? document.getElementById(whereID).value : 0;")
  10:              .AppendLine("}")
  11:          End With
  12:          If Not sm Is Nothing Then
  13:              ScriptManager.RegisterStartupScript(Me.Page, Me.GetType(), key, script.ToString(), True)
  14:          Else
  15:              Me.Page.ClientScript.RegisterStartupScript(Me.GetType(), key, script.ToString(), True)
  16:          End If

Notice we either acquire or save the scroll position to our hidden field.  Now how do we emit a hidden field inside our GridView?  My first attempts were to override the Render of the GridView control and emit hidden field there.  It didn't work.  I ended up registering the hidden field via .NET's scripting objects instead.  Check it out:


   1:          'register our hidden field to save our scroll position
   2:          If Not sm Is Nothing Then
   3:              ScriptManager.RegisterHiddenField(Me.Page, String.Format("{0}_scroll", Me.ClientID), Me.ScrollPosition.ToString())
   4:          Else
   5:              Me.Page.ClientScript.RegisterHiddenField(String.Format("{0}_scroll", Me.ClientID), Me.ScrollPosition.ToString())
   6:          End If

Notice that I assign the scroll position to the hidden field's value.  Last thing we need to do is register a startup script to get the persisted scroll position from our hidden field and scroll our <div>.  If you remember we created a JavaScript function for this (setScrollPosition).


   1:          'generate & register startup javascripts
   2:          key = String.Format("{0}_setScrollPos", Me.ClientID)
   3:          script = New StringBuilder()
   4:          script.AppendLine("setScrollPos('" & String.Format("{0}_scroll", Me.ClientID) & "','" & String.Format("{0}_div", Me.ClientID) & "');")
   5:          If Not sm Is Nothing Then
   6:              ScriptManager.RegisterStartupScript(Me.Page, Me.GetType(), key, script.ToString(), True)
   7:          Else
   8:              Me.Page.ClientScript.RegisterStartupScript(Me.GetType(), key, script.ToString(), True)
   9:          End If

Putting it all Together:

In summary, we persist the scroll position of the div that wraps our GridView via a hidden field which is registered via the Page.ClientScript or ScriptManager object.  We wrap the GridView with a <div> by overriding the GridView's Render method.  The <div> saves the scroll position to the hidden field as the <div> is scrolled.  We acquire the value stored in the hidden field via the LoadPostData function.  Once a PostBack occurs, we set the scroll position of the <div> with a startup script.


ScrollingGridView_Soln.zip (97.19 kb)

Be the first to rate this post

  • Currently 0/5 Stars.
  • 1
  • 2
  • 3
  • 4
  • 5

Get ActualWidth and ActualHeight in Silverlight

October 17, 2008 12:27 by wjchristenson2

You may often encounter situations where you need to acquire the size of a Silverlight object and the Width, Height, ActualWidth, ActualHeight property values are not set.  This can occur if the Width & Height are set to Auto, if the object being referenced is in the process of being added dynamically, or it can occur in a myriad of other circumstances.

Let's assume that we have a Canvas object as our root element for our Silverlight application.  It's Width and Height are set to Auto.  As its parent is resized, the Canvas object will grow & shrink accordingly.  Now if we want to acquire the Canvas size to do some placement logic of Canvas child elements, we run into our problem.

My first approach was to investigate why ActualWidth and ActualHeight were not telling me (the developer) the ... uh... actual width and actual height.  You'd think the property names would say it all, but they don't.  They actually "get or set the rendered width/height of a FrameworkElement."  Ok, that's easy enough.  We'll just wait until the objects are rendered before we position them.  So, I figured I'd hook into my Silverlight's Loaded event.  At this point all objects should be "loaded" and I can acquire the actual size of my Canvas.  Wrong!  ActualWidth and ActualHeight are calculated based on the Width/Height property values and the layout system.  There is no guarantee as to when these values will be "calculated".  So I've found 2 ways to workaround this issue.

Dispatcher.BeginInvoke()
This approach executes a specified delegate asynchronously on the thread the Dispatcher is associated with.  Measurement and layout passes run in Silverlight asynchronously.  Therefore our ActualWidth and ActualHeight object properties can be set AFTER we actually need them.  Using BeginInvoke() ensures that our ActualWidth and ActualHeight calculations are done as we programmatically access them.  Here is a sample use of .BeginInvoke() and the delegate.


   1:      'ActualWidth and ActualHeight are calculated values and may not be set yet
   2:      'therefore, execute GetLayoutRootActualSize() asynchronously on the thread the Dispatcher is associated with
   3:      Me.Dispatcher.BeginInvoke(AddressOf GetLayoutRootActualSize)
   4:   
   5:      Private Sub GetLayoutRootActualSize()
   6:          Me.tbxInvoke.Text = Me.LayoutRoot.ActualWidth.ToString() & ", " & Me.LayoutRoot.ActualHeight.ToString()
   7:      End Sub

_SizeChanged Event
My preferred method of accessing ActualWidth and ActualHeight in these situations is by hooking into the FrameworkElement's SizeChanged event.  This event is fired when the ActualWidth and ActualHeight values change for a FrameworkElement.  So let's say we have an element which we need to position based on the size of it's parent.  We can hook into the parent's SizeChanged event and position the child element on the fly.  The ActualWidth and ActualHeight properties of the parent would be present at this time.  Here is a snipped of SizeChanged event handling.


   1:      Private Sub LayoutRoot_SizeChanged(ByVal sender As Object, ByVal e As System.Windows.SizeChangedEventArgs) Handles LayoutRoot.SizeChanged
   2:          Me.tbxSizeChanged.Text = Me.LayoutRoot.ActualWidth.ToString() & ", " & Me.LayoutRoot.ActualHeight.ToString()
   3:      End Sub

I've uploaded an example using my 3 tests (page Loaded, BeginInvoke(), and _SizeChanged).  Hope it helps!


ActualWidthHeight_Soln.zip (586.38 kb)


Currently rated 4.0 by 1 people

  • Currently 4/5 Stars.
  • 1
  • 2
  • 3
  • 4
  • 5

Microsoft Releases Silverlight 2

October 14, 2008 06:25 by wjchristenson2

Microsoft has released Silverlight 2 today.  Here are the downloads that I found useful:

Silverlight Tools for Visual Studio 2008 SP1
This allows you to develop Silverlight 2 applications within Visual Studio 2008 SP1.  Make sure you have SP1 installed first on Visual Studio before you attempt to install the tools.

Expression Blend 2 Service Pack 1
The service pack will allow you to develop Silverlight 2 with Expression Blend 2.

Microsoft® Silverlight™ 2 SDK
The Silverlight Tools for VS 2008 SP1 already includes the SL2 SDK.  However, if you are not installing that, you can install this SDK seperately.  The software development kit provides documentation, libraries and tools for developing Silverlight 2 applications.

Microsoft Releases Silverlight 2, Already Reaching One in Four Consumers Worldwide
This is the official press announcement.

I have not found any differences yet between SL2 and SL2RC0.  I'll post if I find any.


Be the first to rate this post

  • Currently 0/5 Stars.
  • 1
  • 2
  • 3
  • 4
  • 5

Silverlight 2 RC0 is now Available

September 30, 2008 08:52 by wjchristenson2

About the Release
Silverlight 2 Release Candidate 0 is intended for developers so that they can update their existing SL2 Beta applications to work with SL2.  It is important to upgrade your development environment to SL2RC0 so that your SL application will work the day SL2 is officially released.  Some highlights that I found interesting in this new version is 3 new controls: ComboBox, ProgressBar, and the PasswordBox.  There's also some new control skins available.  Scott Guthrie has a nice overview of the new features and gives a great overview of SL2RC0 here.  It also looks like we can now inherit directly from DependencyObject again!  We can also set default values for custom dependency properties using PropertyMetadata.

What should I do?
As a developer, you'll want to download and install the updates. 

Here are the important downloads:

  • Microsoft Silverlight Tools for Visual Studio 2008 SP1
    This will install the necessary Visual Studio updates, Silverlight project templates, RC0 developer runtime, and SDK.
  • Microsoft Expression Blend 2 Service Pack 1 Preview
    This version of Expression Blend is targeted to work with the RC0 developer runtime for Silverlight.
  • Before you begin, ensure you have Visual Studio 2008 SP1 installed.  If you already had the Expression Blend 2 June Preview installed, you'll need to uninstall it first.  Second, install the new Tools for VS 2008 SP1.  Finally, install the MS Expression Blend 2 SP1 Preview.

    After you have installed the updates, you'll want to open your SL application and test/debug/update it to work with the new SL2RC0 version.  You will NOT want to deploy it.  This version is intended for developers only.

    Issues I Ran Into
    The strangest issue I had was a weird bug when I tried to compile my SL application.  "The 'ValidateXaml' task failed unexpectedly."  I found several articles on this error, however simply cleaning the solution and rebuilding it resolved the issue.  The main issue I ran into was the fact that I didn't initially see the Expression Blend update.  Therefore I couldn't use Expression Blend from within VS.  That was a I-D-1-0-T error on my part.


    Be the first to rate this post

    • Currently 0/5 Stars.
    • 1
    • 2
    • 3
    • 4
    • 5

    Set Dependency Property's Default Value in Silverlight

    September 29, 2008 07:03 by wjchristenson2

    If you are familiar with WPF, you may be used to setting a dependency property's default value through its property metadata.  Unfortunately SL2B2 (Silverlight 2 Beta 2) does not support specifying the default value via the metadata.  This was shocking to me as coders often need to check to see if a property has been set or not.  So what value is returned if the property is not set?  According to this article, it depends on the data type.  Reference-type dependency properties will be null, value-types are the default constructed value (double = 0.0), strings will be an empty string.

    So I figured I could use the Nullable(Of T) generic structure as the data type of my dependency property to get around this limitation.  Doing this would allow me to test to see if the dependency property has been set or not by simply coding something like this: PropertyName.HasValue.  This works great programmatically, but I soon found that setting the dependency properties via XAML caused parser exceptions.  I also lost my intellisense when coding in XAML.  I've posted on the MS Silverlight forums to get an answer why Nullable(Of T) generic structures' XAML are unable to be parsed.  "Unfortunately XAML doesn't support Nullable types because they're generic types".

    So here's my solution.  We found that we cannot use property metadata to set default values and we found that Nullable(Of T) generic data type structures for dependency properties do not play well with XAML.  Instead I just set my dependency property values when the object is constructed.


       1:  Partial Public Class SilverlightControl1
    
       2:      Inherits UserControl
    
       3:   
    
       4:      Public Shared ReadOnly ColorProperty As DependencyProperty = DependencyProperty.Register("Color", GetType(Color), GetType(SilverlightControl1), Nothing)
    
       5:   
    
       6:      Public Property Color() As Color
    
       7:          Get
    
       8:              Return CType(GetValue(SilverlightControl1.ColorProperty), Color)
    
       9:          End Get
    
      10:          Set(ByVal value As Color)
    
      11:              SetValue(SilverlightControl1.ColorProperty, value)
    
      12:          End Set
    
      13:      End Property
    
      14:   
    
      15:      Public Sub New()
    
      16:          InitializeComponent()
    
      17:   
    
      18:          'set dependency property default value
    
      19:          Me.Color = Colors.Transparent
    
      20:      End Sub
    
      21:   
    
      22:  End Class
    

    ** Edit 9/30/2008 **
    SL2RC0 (Silverlight 2 Release Candidate 2) now allows programmers to set a custom depencency property's default value! Simply construct a new PropertyMetadata object and pass in the default value.


    Be the first to rate this post

    • Currently 0/5 Stars.
    • 1
    • 2
    • 3
    • 4
    • 5

    MultiSelect GridView Control

    September 17, 2008 14:18 by wjchristenson2

    In this post, I am going to create an ASP .NET GridView that supports multiple selection.  This is a common feature that is desired by many web developers.  The new GridView control I will create will support the following: selection of 1 row at a time, toggling of all rows to be selected or deselected at once, coloring of what row(s) are selected, what column index we want our selection checkboxes to be put in, and a way to access what DataKeys are selected on PostBack.  Here is what our final control will look like.

    The first step to creating our control is setting up our solution.  You can download the solution in its entirety below.  I have a project for my new control and a web project to test it in.  I am using the Northwind SQL database for my DataSource.  Now we are ready to start programming our MultiSelectGridView control.  We first setup our class-wide variables and properties.


       1:  <ToolboxData("<{0}:MultiSelectGridView runat=server></{0}:MultiSelectGridView>")> _
    
       2:  Public Class MultiSelectGridView
    
       3:      Inherits GridView
    
       4:   
    
       5:      Private _tfMultiSelect As TemplateField = Nothing
    
       6:   
    
       7:  #Region "Public Properties"
    
       8:      <DefaultValue(False), Category("MultiSelect"), Description("Indicates whether or not multiselection is enabled.")> _
    
       9:     Public Property EnableMultiSelect() As Boolean
    
      10:          Get
    
      11:              If Not ViewState("EnableMultiSelect") Is Nothing Then
    
      12:                  Return DirectCast(ViewState("EnableMultiSelect"), Boolean)
    
      13:              Else
    
      14:                  Return False
    
      15:              End If
    
      16:          End Get
    
      17:          Set(ByVal value As Boolean)
    
      18:              ViewState("EnableMultiSelect") = value
    
      19:          End Set
    
      20:      End Property
    
      21:   
    
      22:      <Bindable(True), Category("MultiSelect"), TypeConverter(GetType(WebColorConverter)), Description("Specifies the background color of a selected row.")> _
    
      23:      Public Property MultiSelectColor() As Color
    
      24:          Get
    
      25:              If ViewState("MultiSelectColor") Is Nothing Then
    
      26:                  ViewState("MultiSelectColor") = System.Drawing.ColorTranslator.FromHtml("#FFCC99")
    
      27:              End If
    
      28:              Return DirectCast(ViewState("MultiSelectColor"), Color)
    
      29:          End Get
    
      30:   
    
      31:          Set(ByVal value As Color)
    
      32:              ViewState("MultiSelectColor") = value
    
      33:          End Set
    
      34:      End Property
    
      35:   
    
      36:      <Bindable(True), Category("MultiSelect"), Description("Specifies the where the multiselection column should be placed.")> _
    
      37:      Public Property MultiSelectColumnIndex() As Integer
    
      38:          Get
    
      39:              If Not ViewState("MultiSelectColumnIndex") Is Nothing Then
    
      40:                  Return DirectCast(ViewState("MultiSelectColumnIndex"), Integer)
    
      41:              Else
    
      42:                  Return -1
    
      43:              End If
    
      44:          End Get
    
      45:          Set(ByVal value As Integer)
    
      46:              ViewState("MultiSelectColumnIndex") = value
    
      47:          End Set
    
      48:      End Property
    
      49:   
    
      50:      Public ReadOnly Property SelectedDataKeys() As DataKeyArray
    
      51:          Get
    
      52:              Return GetSelectedDataKeys()
    
      53:          End Get
    
      54:      End Property
    
      55:  #End Region
    

    We want to extend the GridView control so we first inherit from it.  I've defined 4 properties.  First is to toggle whether or not we want to enable multiselection or not.  The second is used to define what color we want to use to delineate the selected state of a row.  The third property is used to allow the developer to specify where they wish to put the multiple selection column.  The final property (SelectedDataKeys) is an array of selected DataKeys that can be accessed programmatically when a PostBack occurs.

    A problem I had at first, was how to add columns in a GridView control.  At first, I tried to add them in the Init and the CreateChildControls and then work with them later on in the control lifecycle.  This *can* work, however you'll soon find out that if you try and access the Columns property, you may find that there are no other columns outside the one you added if you set AutoGenerateColumns = True.  For instance, if you are trying to do a GridView.Columns.Count, you may have more columns than what the property returns.  The Columns property only returns what columns are defined.  So if you are trying to programmatically insert columns or move them around, you may run into issues if you are trying to do that in the Init or PreRender or Load events of your control lifecycle.  The key to adding our column is to Override the CreateColumns function.  We want to let the GridView create its normal columns and then we want to insert our multiselect column wherever the column index tell us to.


       1:      Protected Overrides Function CreateColumns(ByVal dataSource As System.Web.UI.WebControls.PagedDataSource, ByVal useDataSource As Boolean) As System.Collections.ICollection
    
       2:          'let the GridView create the default set of columns
    
       3:          Dim columnList As ICollection = MyBase.CreateColumns(dataSource, useDataSource)
    
       4:          Dim extendedColumnList As ArrayList = New ArrayList(columnList)
    
       5:   
    
       6:          If Me.EnableMultiSelect Then
    
       7:              'add our multi-select checkbox column
    
       8:              _tfMultiSelect = New TemplateField()
    
       9:              With _tfMultiSelect
    
      10:                  .HeaderTemplate = New MultiSelectColumnTemplate(DataControlRowType.Header)
    
      11:                  .ItemTemplate = New MultiSelectColumnTemplate(DataControlRowType.DataRow)
    
      12:              End With
    
      13:   
    
      14:              If Me.MultiSelectColumnIndex < 0 Or Me.MultiSelectColumnIndex > extendedColumnList.Count Then
    
      15:                  extendedColumnList.Add(_tfMultiSelect)
    
      16:              Else
    
      17:                  extendedColumnList.Insert(Me.MultiSelectColumnIndex, _tfMultiSelect)
    
      18:              End If
    
      19:          End If
    
      20:   
    
      21:          Return extendedColumnList
    
      22:      End Function
    

    Above is the overridden CreateColumns function.  We first see if multiselection is enabled.  If it is, we insert our multiselect column (template field) into the collection of GridView columns.  We do some logic here to ensure the column is inserted in the appropriate position.  This gets our multiselect column into the GridView, however now we need to wire in some Javascript.


       1:      Private Sub MultiSelectGridView_PreRender(ByVal sender As Object, ByVal e As System.EventArgs) Handles Me.PreRender
    
       2:          If Me.EnableMultiSelect Then
    
       3:              'declare variables used to initialize onclick client scripts
    
       4:              Dim cbxMultiSelect As CheckBox = Nothing
    
       5:              Dim cbxSingleSelect As CheckBox = Nothing
    
       6:              Dim hidCheckBoxIDs As HiddenField = Nothing
    
       7:              Dim checkboxIDs As ArrayList = New ArrayList()
    
       8:              Dim selectColor As String = System.Drawing.ColorTranslator.ToHtml(Me.MultiSelectColor)
    
       9:   
    
      10:              'set header checkbox onclick
    
      11:              If Me.ShowHeader And Not Me.HeaderRow Is Nothing Then
    
      12:                  cbxMultiSelect = CType(Me.HeaderRow.FindControl("cbxMultiSelect"), CheckBox)
    
      13:                  hidCheckBoxIDs = CType(Me.HeaderRow.FindControl("hidCheckBoxIDs"), HiddenField)
    
      14:                  cbxMultiSelect.Attributes.Add("onClick", "toggleCheckBox(this, '" & cbxMultiSelect.ClientID & "', '" & hidCheckBoxIDs.ClientID & "', '" & selectColor & "');")
    
      15:              End If
    
      16:   
    
      17:              'set data row checkbox onclick
    
      18:              For Each gvr As GridViewRow In Me.Rows
    
      19:                  cbxSingleSelect = CType(gvr.FindControl("cbxMultiSelect"), CheckBox)
    
      20:   
    
      21:                  If cbxMultiSelect Is Nothing Or hidCheckBoxIDs Is Nothing Then
    
      22:                      cbxSingleSelect.Attributes.Add("onClick", "toggleCheckBox(this, '', '', '" & selectColor & "');")
    
      23:                  Else
    
      24:                      cbxSingleSelect.Attributes.Add("onClick", "toggleCheckBox(this, '" & cbxMultiSelect.ClientID & "', '" & hidCheckBoxIDs.ClientID & "', '" & selectColor & "');")
    
      25:                  End If
    
      26:   
    
      27:                  If cbxSingleSelect.Checked Then
    
      28:                      gvr.Style.Add("background-color", selectColor)
    
      29:                  Else
    
      30:                      gvr.Style.Add("background-color", "")
    
      31:                  End If
    
      32:                  checkboxIDs.Add(cbxSingleSelect.ClientID)
    
      33:              Next
    
      34:   
    
      35:              'set hidden field value w/ checkbox client ids
    
      36:              If Not hidCheckBoxIDs Is Nothing Then
    
      37:                  hidCheckBoxIDs.Value = Join(checkboxIDs.ToArray(), ",")
    
      38:              End If
    
      39:   
    
      40:              RegisterClientScriptBlock()
    
      41:          End If
    
      42:      End Sub
    

    I wire in client-side script in the control's PreRender phase of its lifecycle.  Basically when a checkbox is clicked, I call a Javascipt function to do perform background color changes and toggle the check or uncheck of other checkboxes.  So if I check the toggle all checkbox, all rows will be selected.  If I uncheck the toggle all checkbox, all rows will be deselected.  If I select each row manually 1 at a time and all are checked, I check the "toggle all" checkbox with Javascript and vice versa.  Here's the Javascript code that performs this logic.


       1:  var lastColorUsed;
    
       2:  function toggleCheckBox(thisCheckBox, multiSelectID, checkBoxIDs, selectedColor) {
    
       3:    var arrayIDs;
    
       4:    if (checkBoxIDs.length > 0) 
    
       5:      arrayIDs = document.getElementById(checkBoxIDs).value.split(',');
    
       6:    var cbxMultiSelect = document.getElementById(multiSelectID);
    
       7:    if (thisCheckBox == cbxMultiSelect) {
    
       8:      var cbx;
    
       9:      for (var i = 0; i < arrayIDs.length; i++) {
    
      10:        cbx = document.getElementById(arrayIDs[i]);
    
      11:        if (cbx) {
    
      12:          if (!cbx.disabled) {
    
      13:              cbx.checked = thisCheckBox.checked;
    
      14:              if (cbx.checked) {
    
      15:                cbx.parentNode.parentNode.parentNode.style.backgroundColor = selectedColor;
    
      16:                lastColorUsed = selectedColor;
    
      17:              } else {
    
      18:                cbx.parentNode.parentNode.parentNode.style.backgroundColor = '';
    
      19:                lastColorUsed = '';
    
      20:              }
    
      21:          }  
    
      22:        }
    
      23:      }
    
      24:    } else {
    
      25:      if (thisCheckBox.checked) {
    
      26:        thisCheckBox.parentNode.parentNode.parentNode.style.backgroundColor = selectedColor;
    
      27:        lastColorUsed = selectedColor;
    
      28:        
    
      29:        if (cbxMultiSelect) {
    
      30:          var allChecked = true;
    
      31:          for (var i = 0; i < arrayIDs.length; i++) {
    
      32:            allChecked = document.getElementById(arrayIDs[i]).checked;
    
      33:            if (!(allChecked))
    
      34:              break;
    
      35:          }
    
      36:          if (allChecked)
    
      37:            cbxMultiSelect.checked = true;
    
      38:        }
    
      39:      } else {
    
      40:        thisCheckBox.parentNode.parentNode.parentNode.style.backgroundColor = '';
    
      41:        lastColorUsed = '';
    
      42:        if (cbxMultiSelect) 
    
      43:          cbxMultiSelect.checked = false;
    
      44:      }
    
      45:    }
    
      46:  }
    

    Now we need to acquire what rows are selected when the page performs a PostBack.  I've created a public property that returns an array of DataKeys that were selected.  The DataKeys can be 1 or a combination of values to uniquely identify the row.  Make sure you define what column(s) or object(s) of each row make up the DataKey by setting the DataKeyNames property of the GridView.  The SelectedDataKeys property calls our function below.


       1:      Private Function GetSelectedDataKeys() As DataKeyArray
    
       2:          Dim keys As ArrayList = New ArrayList()
    
       3:   
    
       4:          If Me.EnableMultiSelect Then
    
       5:              Dim cbxMultiSelect As CheckBox = Nothing
    
       6:   
    
       7:              If Me.DataKeys.Count > 0 Then
    
       8:                  For Each gvr As GridViewRow In Me.Rows
    
       9:                      cbxMultiSelect = CType(gvr.FindControl("cbxMultiSelect"), CheckBox)
    
      10:   
    
      11:                      If Not cbxMultiSelect Is Nothing AndAlso cbxMultiSelect.Checked Then
    
      12:                          keys.Add(Me.DataKeys(gvr.RowIndex))
    
      13:                      End If
    
      14:                  Next
    
      15:              End If
    
      16:          End If
    
      17:   
    
      18:          Return New DataKeyArray(keys)
    
      19:      End Function
    

    What we do here is loop through each row of the GridView and get a handle on the multiselect checkbox.  If it is checked, we get the associated DataKeys for the row and add it to our ArrayList.  We return all selected DataKeys when finished looping through all rows of the GridView.  Now we are ready to use our control.


       1:      <cc1:MultiSelectGridView 
    
       2:        ID="MultiSelectGridView1" 
    
       3:        runat="server" 
    
       4:        DataSourceID="SqlDataSource1" 
    
       5:        DataKeyNames="RegionID" 
    
       6:        EnableMultiSelect="True" 
    
       7:        MultiSelectColor="#9EC630" 
    
       8:        MultiSelectColumnIndex="0" />
    

    Here is a simple use of our MultiSelectGridView control.  Remember I am using the Northwind database via my SqlDataSource1.  What's important here is the MultiSelect properties.  I enable multiselection, set the selected color, and then place the multiselect column on the far left of my GridView.  After I've selected what rows I want, I click a button and display the SelectedDataKeys on my page.  Here is how I use the SelectedDataKeys property programmatically.


       1:      Protected Sub Button1_Click(ByVal sender As Object, ByVal e As EventArgs) Handles Button1.Click
    
       2:          Dim selectedKeys As StringBuilder = New StringBuilder()
    
       3:          For Each key As DataKey In Me.MultiSelectGridView1.SelectedDataKeys
    
       4:              selectedKeys.Append(", " & key(0).ToString())
    
       5:          Next
    
       6:   
    
       7:          If selectedKeys.Length > 0 Then selectedKeys.Remove(0, 2)
    
       8:          lblSelectedItems.Text = selectedKeys.ToString()
    
       9:      End Sub
    

    That's all there is to it.  You now have a GridView that allows you to multiselect rows!


    GridViewMultiSelect_Soln.zip (111.25 kb)

    Be the first to rate this post

    • Currently 0/5 Stars.
    • 1
    • 2
    • 3
    • 4
    • 5

    How to Make a Dashed Line in Silverlight

    September 8, 2008 14:22 by wjchristenson2

    I attempted to create a dashed line today in Silverlight and quickly found myself searching for examples on how to do so.  In ASP.NET I'm used to setting a BorderStyle property to do so.  With Silverlight, we have to do a little more work.  I will show how you can create a dashed line in Silverlight via XAML and I will also show you how to do this programmatically.  This is what our final Silverlight application will look like once we are finished.

    "StrokeDashArray" is the property which we use to define our dashes.  This property indicates the pattern of dashes and spaces between the dashes.  The first value is the width of the dash and the second value is the space between the dashes.


       1:  <Line x:Name="Line1" X1="20" Y1="10" X2="300" Y2="10" Stroke="#4284B0" Canvas.Top="10" StrokeDashArray="4 2"/>
    

    This is how you would define a dashed line in XAML.  Here we have a line where each dash is 4 pixels wide and has a 2 pixel spacer between each dash.  Next, I will show you how you can create a dashed line programmatically.


       1:      Private Sub Page_Loaded(ByVal sender As Object, ByVal e As System.Windows.RoutedEventArgs) Handles Me.Loaded
    
       2:          Dim dashArray As DoubleCollection = New DoubleCollection()
    
       3:          With dashArray
    
       4:              .Add(10.0)   'width
    
       5:              .Add(5.0)   'space
    
       6:          End With
    
       7:   
    
       8:          Me.Line2.StrokeDashArray = dashArray
    
       9:      End Sub
    

    Here we define our dashed line programmatically.  You see that I create a dashArray object first of a DoubleCollection type.  I fill it with a 10 and a 5 (10 pixel wide dash with 5 pixel spacers in between each dash).  I then assign my line's StrokeDashArray property to my dashArray DoubleCollection.

    SilverlightDashedLine_Soln.zip (537.26 kb)


    Be the first to rate this post

    • Currently 0/5 Stars.
    • 1
    • 2
    • 3
    • 4
    • 5

    Define Silverlight Animations Programmatically

    September 2, 2008 13:34 by wjchristenson2

    With Silverlight, you animate object's by changing one or more properties over time.  In Silverlight, storyboards are used to house animations.  Silverlight animations are structured to take place in response to an event (a trigger).  There is only one trigger that is supported by Silverlight and that is the EventTrigger.  There are 3 types of animations: double, point, and color.  In this post, I am going to show you how to create a double animation programmatically.  We are going to make a duck fly!

    First, I am going to create my canvas with my duck hunt background.  Within my canvas, I have a button and my duck.  My plan is to animate my duck across the screen when I click my "Fly!" button.


       1:  <UserControl x:Class="Animation_Programmatic.Page"
       2:      xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation" 
       3:      xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" 
       4:      xmlns:local="clr-namespace:Animation_Programmatic"
       5:      Width="500" Height="438">
       6:      <Canvas x:Name="LayoutRoot">
       7:          <Canvas.Background>
       8:              <ImageBrush ImageSource="Images/canvas.png" />
       9:          </Canvas.Background>
      10:          
      11:          <local:Duck x:Name="Duck1" Canvas.Left="26" Canvas.Top="57" />
      12:          <Button x:Name="btnFly" Height="30" Width="100" Content="Fly!" Background="#FF000000"/>
      13:      </Canvas>
      14:  </UserControl>

     

    As I mentioned in the intro, I described how a Storyboard houses 1-to-many animations.  Each animation may vary in type and may also target different objects and associated object properties. In our example, we want to animate the Canvas.Left position of our duck from the left area of the canvas to the right when the "Fly!" button is clicked.  To accomplish this, we are going to use a DoubleAnimation.  We can accomplish this in XAML or programmatically.  I see lots of examples on the web for using XAML so I'll do this programmatically.


       1:      Private Sub btnFly_Click(ByVal sender As Object, ByVal e As System.Windows.RoutedEventArgs) Handles btnFly.Click
    
       2:          Dim dur As Duration = New Duration(TimeSpan.FromSeconds(3))
    
       3:   
    
       4:          Dim da As DoubleAnimation = New DoubleAnimation()
    
       5:          With da
    
       6:              .From = 26
    
       7:              .To = 425
    
       8:              .Duration = dur
    
       9:          End With
    
      10:   
    
      11:          Dim sb As Storyboard = New Storyboard()
    
      12:          sb.Duration = dur
    
      13:          sb.Children.Add(da)
    
      14:   
    
      15:          Storyboard.SetTarget(da, Me.Duck1)
    
      16:          Storyboard.SetTargetProperty(da, New PropertyPath("(Canvas.Left)"))
    
      17:   
    
      18:          sb.Begin()
    
      19:      End Sub
    

    I first create the duration object and set it to span 3 seconds.  I define the double animation to begin at 26 and go to 425 and assign it my 3 second duration.  I then create my storyboard and add my animation to it.  I associate my animation to my duck object and tell the animation to update the duck's Canvas.Left property (26 to 425 over 3 seconds).  When all is wired up, I then call the storyboard's Begin() method to start the animation.  That's it!  You could also add an event to fire when the animations are complete for the storyboard via the Complete event.

    Animation_Programmatic_Soln.zip (2.12 mb)


    Be the first to rate this post

    • Currently 0/5 Stars.
    • 1
    • 2
    • 3
    • 4
    • 5

    Viewing TFS Shelveset Contents

    August 27, 2008 11:47 by ccrews

    Earlier today, a developer came by my desk wishing to know who had a number of files checked out which were part of a rather large project he had just completed.

    A real quick glance from the Team Foundation Services Source Control explorer told me that, in fact, no one had those files checked out... yet Visual Studio was showing that they were.

    Apparently when a file is a part of a shelveset, Visual Studio displays the icon identifying the file is checked out to another user.  The developer was concerned that all his changes might be accidentally overwritten when the shelveset was unshelved and wanted to know, for curiosity sake, who had shelved changes for those files.

    Long story short... I was actually the person who had the files in a shelveset (it's amazing how sleep can make you forget what you did just the day before), but finding that information out was not a straight-forward task... and I wasn't about to attempt an "Unshelve" for every shelveset, for every developer in our company.

    I ended up writing the stored procedure below (for the TfsVersionControl database) which produces a list of files for a given shelveset.  It allows me to query any shelveset by shelvesetName, ownerID, or filePath; these parameters are mutually exclusive and the procedure is written to handle any combination.  The shelvesetName is a direct comparison (not case sensitive) while the filePath is a wildcard search on both ends of the path.


       1:  CREATE PROCEDURE sp_QueryShelvesetContents (
    
       2:      @ownerID as int = NULL,
    
       3:      @shelvesetName as nvarchar(64) = NULL,
    
       4:      @filePath as nvarchar(520) = NULL
    
       5:  )
    
       6:  AS
    
       7:      SET NOCOUNT ON
    
       8:   
    
       9:      BEGIN
    
      10:          --Prep the filePath for a LIKE statement
    
      11:          IF @filePath IS NULL            SET @filePath = '%$%'
    
      12:          SET @filePath = LTrim(RTrim(@filePath))
    
      13:          IF LEFT(@filePath, 1) <> '%'    SET @filePath = '%' + @filePath
    
      14:          IF RIGHT(@filePath, 1) <> '%'    SET @filePath = @filePath + '%'
    
      15:   
    
      16:          --Create a temp table to hold workspace IDs
    
      17:          CREATE TABLE #tmpWorkspaces (WorkspaceID int, OwnerID int)
    
      18:   
    
      19:          IF @shelvesetName IS NOT NULL
    
      20:              BEGIN
    
      21:                  --Add all shelvesets which match the shelveset name
    
      22:                  INSERT INTO #tmpWorkspaces
    
      23:                  SELECT WorkspaceID, OwnerID FROM dbo.tbl_Workspace WHERE WorkspaceName = @shelvesetName AND Type = 1
    
      24:              END
    
      25:          
    
      26:          IF @ownerID IS NOT NULL
    
      27:              BEGIN
    
      28:                  --Remove any shelvesets which don't match the owner passed in (even if it matched the workspace name)
    
      29:                  DELETE FROM #tmpWorkspaces WHERE OwnerID <> @ownerID
    
      30:                  
    
      31:                  IF @shelvesetName IS NULL
    
      32:                      BEGIN
    
      33:                          --Add all shelvesets which match the ownerid (and aren't already in the table)
    
      34:                          INSERT INTO #tmpWorkspaces
    
      35:                          SELECT W.WorkspaceID, W.OwnerID
    
      36:                          FROM dbo.tbl_Workspace AS W
    
      37:                          LEFT JOIN #tmpWorkspaces AS TW ON TW.WorkspaceID = W.WorkspaceID
    
      38:                          WHERE W.OwnerID = @ownerID AND TW.WorkspaceID IS NULL
    
      39:                      END
    
      40:          END
    
      41:   
    
      42:          IF (SELECT COUNT(WorkspaceID) FROM #tmpWorkspaces) > 0
    
      43:              BEGIN
    
      44:                  SELECT WS.WorkspaceID, WS.WorkspaceName AS ShelvesetName, I.IdentityID, I.DisplayName, PC.TargetServerItem
    
      45:                  FROM dbo.tbl_PendingChange AS PC
    
      46:                  INNER JOIN #tmpWorkspaces AS TW ON TW.WorkspaceID = PC.WorkspaceID
    
      47:                  INNER JOIN dbo.tbl_Workspace AS WS ON WS.WorkspaceID = TW.WorkspaceID
    
      48:                  INNER JOIN dbo.tbl_Identity AS I ON I.IdentityID = WS.OwnerID
    
      49:                  WHERE TargetServerItem LIKE @filePath
    
      50:              END
    
      51:          ELSE
    
      52:              BEGIN
    
      53:                  SELECT WS.WorkspaceID, WS.WorkspaceName AS ShelvesetName, I.IdentityID, I.DisplayName, PC.TargetServerItem
    
      54:                  FROM dbo.tbl_PendingChange AS PC
    
      55:                  INNER JOIN dbo.tbl_Workspace AS WS ON WS.WorkspaceID = PC.WorkspaceID AND WS.Type = 1
    
      56:                  INNER JOIN dbo.tbl_Identity AS I ON I.IdentityID = WS.OwnerID
    
      57:                  WHERE TargetServerItem LIKE @filePath
    
      58:              END        
    
      59:      END
    
      60:   
    
      61:      DROP TABLE #tmpWorkspaces
    

    Examples

    Example 1:  List of all files in your DEV branch which are part of any shelveset

    	exec sp_QueryShelvesetContents @filePath = 'dev'				
    	

    Example 2:  List of all files in your DEV branch which are part of a shelveset for ownerID 5

    	exec sp_QueryShelvesetContents @ownerID  = 5				
    	

    Example 3:  List all GridView.vb files which are part of any shelveset

    	exec sp_QueryShelvesetContents @filePath = 'GridView.vb'				
    	

    sp_QueryShelvesetContents.zip (903.00 bytes)


    Be the first to rate this post

    • Currently 0/5 Stars.
    • 1
    • 2
    • 3
    • 4
    • 5