locked
Load on Demand RRS feed

  • General discussion

  • Users have been complaining that complicated screens are slow to load in the Silverlight client so I've been working on making the screens load data on demand. Turn off Automatic Loading of the queries and hook into the tab selection change event to load the data necessary for that tab.

    I've also got a helper routine to load lookup data for AutoCompleteBoxes only when the user drops the list for the first time.

    <System.Runtime.CompilerServices.Extension> _ Public Sub LoadOnDemand(screenObject As IScreenObject, controlName As String, lookupCollection As IVisualCollection, callbackMethod As Action) With New LoadOnDemandHelper(screenObject, controlName, lookupCollection, callbackMethod) End With End Sub Public Class LoadOnDemandHelper Private mScreenObject As IScreenObject Private WithEvents macbControl As AutoCompleteBox Private mLookupCollection As IVisualCollection Private mAction As Action Public Sub New(screenObject As IScreenObject, ControlName As String, lookupCollection As IVisualCollection, CallbackMethod As Action) mScreenObject = screenObject mLookupCollection = lookupCollection
    mAction = CallbackMethod AddHandler screenObject.FindControl(ControlName).ControlAvailable, Sub(sender As Object, e As ControlAvailableEventArgs) macbControl = CType(e.Control, AutoCompleteBox) End Sub End Sub Private Sub macbControl_DropDownOpening(sender As Object, e As RoutedPropertyChangingEventArgs(Of Boolean)) Handles macbControl.DropDownOpening If mLookupCollection.Count = 0 Then mScreenObject.Details.Dispatcher.BeginInvoke(Sub() mAction() End Sub) End If End Sub End Class

    You can then call this from each screen that needs it.

    Me.LoadOnDemand("Country", CountriesActive, AddressOf CountriesActive.Load)
    This works fine for AutoCompleteBoxes except where they are in a grid because that needs to use FindControlInCollection instead of FindControl. Anyone want to suggest improvements to cope with this?


    Simon Jones
    If you found this post helpful, please "Vote as Helpful". If it actually answered your question, please remember to "Mark as Answer". This will help other people find answers to their problems more quickly.



    • Edited by Simon Jones [MSDL] Friday, August 15, 2014 1:20 PM
    • Changed type Angie Xu Thursday, August 28, 2014 3:07 AM more suitable for discussion
    Thursday, August 14, 2014 3:45 PM

All replies

  • Simon:

    Thanks very much for this.  I am definitely going to use it.

    Mark

    Friday, August 15, 2014 12:20 PM
  • Simon:

    I am trying to implement the delayed tab loading and could use a little help.  Could you please give me some sample code that shows how to "hook into the tab selection change event"?  I can't seem to figure it out.

    Thanks very much.

    Mark

    Tuesday, September 16, 2014 2:02 AM
  • Declare a variable to hold/handle the tab control

    Private WithEvents mtabChildren As TabControl
    


    In Screen_Created event, add a handler for the TabControl's ControlAvailable event to remember the underlying control

    AddHandler Me.FindControl("Children").ControlAvailable, 
        Sub(sender As Object, e As ControlAvailableEventArgs)
            mtabChildren = CType(e.Control, TabControl)
        End Sub
    
    

    Handle the TabControl's SelectionChanged event, loading the appropriate tables for each tab, if they don't already contain any data.

            Private Sub mtabChildren_SelectionChanged(sender As Object, e As SelectionChangedEventArgs) Handles mtabChildren.SelectionChanged
    
                Select Case mtabChildren.SelectedIndex
                    Case 0 'Details
                        If Me.ProjectsExpenseTypes.Count = 0 Then Me.ProjectsExpenseTypes.Load()
                        If Me.PersonsProjectsRoles.Count = 0 Then Me.PersonsProjectsRoles.Load()
                        If Me.TeamMembers.Count = 0 Then Me.TeamMembers.Load()
    
                    Case 1 'Documents
                        If Me.ProjectDocuments.Count = 0 Then Me.ProjectDocuments.Load()
    
    ...
                End Select
    
            End Sub


    Simon Jones
    If you found this post helpful, please "Vote as Helpful". If it actually answered your question, please remember to "Mark as Answer". This will help other people find answers to their problems more quickly.

    Tuesday, September 16, 2014 8:31 AM
  • Hi Simon,

    That is a great idea, I try both the tabs and the autocomplete load on demand.

    One question, if you disable the auto-load for the queries, would that mean we need to handle the paging by our selves?



    Tuesday, September 16, 2014 3:53 PM
  • Yes, when you turn off AutoExecute on the queries you have to add a little code to handle paging and sorting, if these are required.

        <System.Runtime.CompilerServices.Extension> _
        Public Sub ISFixPagingProblem(screenObject As IScreenObject, controlName As String, CallbackMethod As Action)
            With New FixMicrosoftPagingProblem(screenObject, controlName, CallbackMethod)
    
            End With
        End Sub
    
        Public Class FixMicrosoftPagingProblem
            Private _CallbackMethod As Action
            Private _collectionName As [String]
            Private _screenObject As IScreenObject
    
            Public Sub New(screenObject As IScreenObject, collectionName As String, CallbackMethod As Action)
                _screenObject = screenObject
                _collectionName = collectionName
                Dim collection As INotifyPropertyChanged = DirectCast(screenObject.Details.Properties(_collectionName), INotifyPropertyChanged)
                _CallbackMethod = CallbackMethod
                Microsoft.LightSwitch.Threading.Dispatchers.Main.Invoke(Sub()
                                                                            AddHandler collection.PropertyChanged, AddressOf collection_PropertyChanged
                                                                        End Sub)
            End Sub
    
            Private Sub collection_PropertyChanged(sender As Object, e As PropertyChangedEventArgs)
                Select Case e.PropertyName
                    ' Changed when current page is changed
                    Case "PageNumber", "SortDescriptors"
                        ' Changed when sorting is changed
                        _screenObject.Details.Dispatcher.BeginInvoke(Sub()
                                                                         _CallbackMethod()
                                                                     End Sub)
                        Exit Select
                End Select
            End Sub
        End Class

    You then call this for each query in the Screen_Created event

    Me.ISFixPagingProblem("Invoices", AddressOf Invoices.Load)


    Simon Jones
    If you found this post helpful, please "Vote as Helpful". If it actually answered your question, please remember to "Mark as Answer". This will help other people find answers to their problems more quickly.

    Tuesday, September 16, 2014 4:37 PM
  • Simon:

    Thanks very much!

    Mark

    Tuesday, September 16, 2014 8:41 PM
  • Hi Simon,

    Thanks for the paging code..!  I am trying out the ISFixPagingProblem code. However, I am getting a Extension methods can be defined only in modules for the <System.Runtime.CompilerServices.Extension>.

    Do I need to declare a new Module, just like the way I declare a Class? If yes, where is the best place to do so?

    Best regards,

    CT

    Tuesday, September 16, 2014 11:09 PM
  • Thanks!  +1
    Wednesday, September 17, 2014 12:31 AM
  • Hi Simon,

    I never knew about using modules in VB is the same as static class in C#.  I have tried out your codes and it works like a charm... Thanks..!

    Best regards,
    CT
    Wednesday, September 17, 2014 7:16 AM
  • Glad you got it working.

    Hope you're finding a good speed improvement.

    Our testing has shown that on complicated forms we can halve the time taken to load the form from 8 seconds to a more acceptable four seconds.

    Where lookup lists are long, for example there are nearly 300 Countries in the ISO Countries list, not loading it until it is definitely required can shave a second of the screen load time of even simple screens.

    We also find that we have to train the users not to wait until all the "busy" animations have stopped spinning.


    Simon Jones
    If you found this post helpful, please "Vote as Helpful". If it actually answered your question, please remember to "Mark as Answer". This will help other people find answers to their problems more quickly.

    Wednesday, September 17, 2014 7:35 AM
  • We also find that we have to train the users not to wait until all the "busy" animations have stopped spinning.

    Hehe, I had to do that too... But with your help, I hope the screen load times will be dramatically reduced.

    For now, I am still trying to understand why the paging and sorting code works, mainly around the extension bit and the FixMicrosoftPagingProblem.New() sub

    Wednesday, September 17, 2014 7:48 AM
  • It adds a handler to the INotifyPropertyChanged event of the collection.

    That handler looks for the PageNumber and SortDescriptors properties changing and calls the CallBackFunction (the collection.Load method).

    That code comes from the discussion in this thread http://social.msdn.microsoft.com/Forums/vstudio/en-US/62ef7adf-3b34-403c-83b0-ad074023a076/getting-default-behavior-using-queries-with-autoexecute-false?forum=lightswitch

    I've merely converted that code to VB but I had to write the code to load lookup lists on demand to get the extra turn of speed the users needed.


    Simon Jones
    If you found this post helpful, please "Vote as Helpful". If it actually answered your question, please remember to "Mark as Answer". This will help other people find answers to their problems more quickly.

    Wednesday, September 17, 2014 8:19 AM
  • Somehow I previously missed Justin's posting in that super long discussion.

    I am still trying to fully understand the way you generalised the whole thing, concept of Extension, and the way the Callback action made.  I shall tinker around and see. Thanks so much for helping out and sharing your code..!

    By the way, is there a way to enable the build-in search within the datagrid when I disable the query's auto-execute?

     

    Thursday, September 18, 2014 1:24 AM
  • I've not found a way to enable the built-in Search functionality when AutoExecute is disabled on a Query.

    There is, apparently, a SearchTerms property on a collection which may change when the contents of the Search box on a grid is updated but I've not had any luck in detecting that.

    Disabling the built-in Search functionality and providing your own mechanism puts it all under your control so you can call the Load method when necessary.


    Simon Jones
    If you found this post helpful, please "Vote as Helpful". If it actually answered your question, please remember to "Mark as Answer". This will help other people find answers to their problems more quickly.

    Friday, September 26, 2014 8:59 AM
  • Has anyone converted this to C#.?  I am having trouble getting it working.

    Thanks,

    Mark

    Friday, September 26, 2014 4:50 PM
  • Which bit are you having problems with?

    This is the code from my original post autoconverted to C#.


    [System.Runtime.CompilerServices.Extension()]
    public void LoadOnDemand(IScreenObject screenObject, string controlName, IVisualCollection lookupCollection, Action callbackMethod)
    {
    	var _with1 = new LoadOnDemandHelper(screenObject, controlName, lookupCollection, callbackMethod);
    
    }
    
    public class LoadOnDemandHelper
    {
    	private IScreenObject mScreenObject;
    	private AutoCompleteBox withEventsField_macbControl;
    	private AutoCompleteBox macbControl {
    		get { return withEventsField_macbControl; }
    		set {
    			if (withEventsField_macbControl != null) {
    				withEventsField_macbControl.DropDownOpening -= macbControl_DropDownOpening;
    			}
    			withEventsField_macbControl = value;
    			if (withEventsField_macbControl != null) {
    				withEventsField_macbControl.DropDownOpening += macbControl_DropDownOpening;
    			}
    		}
    	}
    	private IVisualCollection mLookupCollection;
    
    	private Action mAction;
    	public LoadOnDemandHelper(IScreenObject screenObject, string ControlName, IVisualCollection lookupCollection, Action CallbackMethod)
    	{
    		mScreenObject = screenObject;
    		mLookupCollection = lookupCollection;
    		mAction = CallbackMethod;
    
    		screenObject.FindControl(ControlName).ControlAvailable += (object sender, ControlAvailableEventArgs e) => { macbControl = (AutoCompleteBox)e.Control; };
    	}
    
    
    	private void macbControl_DropDownOpening(object sender, RoutedPropertyChangingEventArgs<bool> e)
    	{
    		if (mLookupCollection.Count == 0) {
    			mScreenObject.Details.Dispatcher.BeginInvoke(() => { mAction(); });
    		}
    
    	}
    
    }


    Simon Jones
    If you found this post helpful, please "Vote as Helpful". If it actually answered your question, please remember to "Mark as Answer". This will help other people find answers to their problems more quickly.

    Friday, September 26, 2014 4:56 PM
  • Of course, it would be good if there was a property on the Query objects on a Screen which allowed you to specify if they should be loaded on creation of the screen or only when their data was required - EG a Grid or List is displayed or an AutoCompleteBox's drops down.

    This would make LightSwitch smarter and faster and save thousands of application programmers having to implement kludgy workarounds like this for every application they write.


    Simon Jones
    If you found this post helpful, please "Vote as Helpful". If it actually answered your question, please remember to "Mark as Answer". This will help other people find answers to their problems more quickly.

    Friday, September 26, 2014 5:46 PM
  • Simon:

    I took your converted code and changed it a little (making a static class).  I put in the Client project and it won't compile. I am getting:

     "'Microsoft.LightSwitch.Client.IScreenObject' does not contain a definition for 'FindControl' and no extension method 'FindControl' accepting a first argument of type
     'Microsoft.LightSwitch.Client.IScreenObject' could be found..."
     
     And
     
     "The type or namespace name 'RoutedPropertyChangingEventArgs' could not be found..."

    Here is the code:

    using Microsoft.LightSwitch.Client;
    using Microsoft.LightSwitch.Presentation;
    using System;
    using System.Windows.Controls;
    
    public static class ExtensionMethods
    {
        public static void LoadOnDemand(IScreenObject screenObject, string controlName, IVisualCollection lookupCollection, Action callbackMethod)
        {
            var _with1 = new LoadOnDemandHelper(screenObject, controlName, lookupCollection, callbackMethod);
        }
    
        public class LoadOnDemandHelper
        {
            private IScreenObject mScreenObject;
            private AutoCompleteBox withEventsField_macbControl;
            private AutoCompleteBox macbControl
            {
                get { return withEventsField_macbControl; }
                set
                {
                    if (withEventsField_macbControl != null)
                    {
                        withEventsField_macbControl.DropDownOpening -= macbControl_DropDownOpening;
                    }
                    withEventsField_macbControl = value;
                    if (withEventsField_macbControl != null)
                    {
                        withEventsField_macbControl.DropDownOpening += macbControl_DropDownOpening;
                    }
                }
            }
            private IVisualCollection mLookupCollection;
    
            private Action mAction;
            public LoadOnDemandHelper(IScreenObject screenObject, string ControlName, IVisualCollection lookupCollection, Action CallbackMethod)
            {
                mScreenObject = screenObject;
                mLookupCollection = lookupCollection;
                mAction = CallbackMethod;
    
                screenObject.FindControl(ControlName).ControlAvailable += (object sender, ControlAvailableEventArgs e) => { macbControl = (AutoCompleteBox)e.Control; };
            }
    
            private void macbControl_DropDownOpening(object sender, RoutedPropertyChangingEventArgs<bool> e)
            {
                if (mLookupCollection.Count == 0)
                {
                    mScreenObject.Details.Dispatcher.BeginInvoke(() => { mAction(); });
                }
            }
        }
    }

    Any ideas?

    Thanks,

    Mark

    Friday, September 26, 2014 6:12 PM
  • Try adding these to the top of your code

    Using Microsoft.LightSwitch.Details.Client;
    Using Microsoft.LightSwitch.Details;

    You may need to drop

    using Microsoft.LightSwitch.Presentation;
    if it isn't required by other code.


    Simon Jones
    If you found this post helpful, please "Vote as Helpful". If it actually answered your question, please remember to "Mark as Answer". This will help other people find answers to their problems more quickly.

    Saturday, September 27, 2014 8:09 AM
  • OK, I got it working and it definitely helps the loading time for the screen.  (I did not have a reference to System.Windows.Controls.) I added that and changed the C# code a little.  I am going to add it here in case anyone wants to use it.

    Instructions to implement load on demand AutoCompleteBox:

    1) Add a class file to your Client project with the following code:

    using Microsoft.LightSwitch.Client;
    using Microsoft.LightSwitch.Presentation;
    using Microsoft.LightSwitch.Presentation.Extensions;
    using System;
    using System.Windows.Controls;
    
    namespace LightSwitchApplication.UserCode
    {
        public static class ExtensionMethods
        {
            public static void LoadOnDemand(IScreenObject screenObject, string controlName, IVisualCollection lookupCollection, Action callbackMethod)
            {
                new LoadOnDemandHelper(screenObject, controlName, lookupCollection, callbackMethod);
            }
    
            public class LoadOnDemandHelper
            {
                private IScreenObject mScreenObject;
                private AutoCompleteBox withEventsField_macbControl;
                private AutoCompleteBox macbControl
                {
                    get { return withEventsField_macbControl; }
                    set
                    {
                        if (withEventsField_macbControl != null)
                        {
                            withEventsField_macbControl.DropDownOpening -= macbControl_DropDownOpening;
                        }
                        withEventsField_macbControl = value;
                        if (withEventsField_macbControl != null)
                        {
                            withEventsField_macbControl.DropDownOpening += macbControl_DropDownOpening;
                        }
                    }
                }
    
                private IVisualCollection mLookupCollection;
    
                private Action mAction;
                public LoadOnDemandHelper(IScreenObject screenObject, string ControlName, IVisualCollection lookupCollection, Action CallbackMethod)
                {
                    mScreenObject = screenObject;
                    mLookupCollection = lookupCollection;
                    mAction = CallbackMethod;
    
                    screenObject.FindControl(ControlName).ControlAvailable += (object sender, ControlAvailableEventArgs e) => { macbControl = (AutoCompleteBox)e.Control; };
                }
    
                private void macbControl_DropDownOpening(object sender, RoutedPropertyChangingEventArgs<bool> e)
                {
                    if (mLookupCollection.Count == 0)
                    {
                        mScreenObject.Details.Dispatcher.BeginInvoke(() => { mAction(); });
                    }
                }
            }
        }
    }

    2) Add this to the _Created event for the screen:

     UserCode.ExtensionMethods.LoadOnDemand(this, "ExpenseScenario", this.queryExpenseScenarios,
                  this.queryExpenseScenarios.Load);

    The parameters are:

         1) the current screen

         2) the name of the AutoCompleteBox control

         3) the table/query that contains the list for the AutoCompleteBox

         4) the load method for the table/query

    3)  Finally, uncheck the "Auto Execute Query" option for the relevant table/query.

    That's it.

    Mark


    • Edited by marks100 Tuesday, September 30, 2014 11:05 PM
    Tuesday, September 30, 2014 11:03 PM