Skip to content

Instantly share code, notes, and snippets.

@janvanderhaegen
Last active January 19, 2020 05:42
Show Gist options
  • Star 1 You must be signed in to star a gist
  • Fork 2 You must be signed in to fork a gist
  • Save janvanderhaegen/068fb41c50dad319f664 to your computer and use it in GitHub Desktop.
Save janvanderhaegen/068fb41c50dad319f664 to your computer and use it in GitHub Desktop.
Loading of collections in the desktop client on a 'need to see' basis.
using Microsoft.LightSwitch.Client;
using Microsoft.LightSwitch.Details.Client;
using Microsoft.LightSwitch.Framework.Client;
using Microsoft.LightSwitch.Presentation;
using Microsoft.LightSwitch.Presentation.Extensions;
using Microsoft.LightSwitch.Presentation.Internal;
using Microsoft.LightSwitch.Sdk.Proxy;
using Microsoft.VisualStudio.ExtensibilityHosting;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Reflection;
using System.Windows;
namespace LightSwitchApplication
{
public static class LoadOnDemandExtensions
{
public static void LoadCollectionsOnDemand(this IScreenObject screen)
{
foreach (var collection in screen.Details.Properties.All().OfType<IScreenCollectionProperty>())
{
collection.LoadOnDemand();
}
}
public static void LoadOnDemand(this IScreenCollectionProperty screenProperty)
{
SetAutoLoad(screenProperty, false);
var contentItems = FindContentItems(screenProperty.Screen, screenProperty);
foreach (var contentItem in contentItems)
{
var proxy = screenProperty.Screen.FindControl(contentItem.Name);
proxy.ControlUnavailable -= proxy_ControlUnavailable;
proxy.ControlUnavailable += proxy_ControlUnavailable;
proxy.ControlAvailable -= proxy_ControlAvailable;
proxy.ControlAvailable += proxy_ControlAvailable;
}
}
public static void LoadOnDemand<T>(this VisualCollection<T> collection) where T : class
{
var screen = collection.Screen;
var screenCollectionProperty = screen.Details.Properties.All().Single(p => p.Value == collection) as IScreenCollectionProperty;
screenCollectionProperty.LoadOnDemand();
}
static void SetAutoLoad(IScreenProperty screenProperty, bool value)
{
var boundProp = screenProperty as Microsoft.LightSwitch.Details.Client.IScreenBoundProperty;
var loader = boundProp.Loader;
var type = loader.GetType();
while (type != null && !type.Name.StartsWith("ScreenPropertyLoader"))
type = type.BaseType;
var f = type.GetFields(System.Reflection.BindingFlags.NonPublic | System.Reflection.BindingFlags.Instance);
var pmProp = f.Single(field => field.Name == "_propertyModel");
var _propertyModel = pmProp.GetValueByExpression<Microsoft.LightSwitch.Model.IScreenPropertyDefinitionBase>(loader);
var manualLoadProp = typeof(Microsoft.LightSwitch.Model.Storage.ScreenPropertyBase).GetProperty("ManualLoad");
manualLoadProp.SetValueByExpression(_propertyModel, !value);
}
public static void LoadIfNeeded<T>(this VisualCollection<T> visualCollection) where T : class
{
var screenCollectionProperty = visualCollection.Screen.Details.Properties.All().Single(p => p.Value == visualCollection) as IScreenBoundProperty;
var boundProp = screenCollectionProperty as Microsoft.LightSwitch.Details.Client.IScreenBoundProperty;
var loader = boundProp.Loader;
if (!loader.IsLoaded)
loader.Load();
}
static void proxy_ControlUnavailable(object sender, ControlUnavailableEventArgs e)
{
GetVisualCollectionFromControlEventArgs(e,
(IVisualCollection visualCollection) =>
{
//Set auto-load off
var screenCollectionProperty = visualCollection.Screen.Details.Properties.All().Single(p => p.Value == visualCollection);
SetAutoLoad(screenCollectionProperty, false);
});
}
static void proxy_ControlAvailable(object sender, ControlAvailableEventArgs e)
{
GetVisualCollectionFromControlEventArgs(e,
(IVisualCollection visualCollection) =>
{
//Set auto-load on
var screenCollectionProperty = visualCollection.Screen.Details.Properties.All().Single(p => p.Value == visualCollection);
SetAutoLoad(screenCollectionProperty, true);
//'Load' is on VisualCollection<T>, but we don't know T
var loadMethod = visualCollection.GetType().GetMethod("Load");
loadMethod.Invoke(visualCollection, emptyArgs);
});
}
private static void GetVisualCollectionFromControlEventArgs(ControlAvailableBaseEventArgs e, Action<IVisualCollection> whenAvailable)
{
IVisualCollection visualCollection = null;
var control = e.Control as FrameworkElement;
var contentItem = (control.DataContext as IContentItem);
if(contentItem != null)
visualCollection = contentItem.Value as IVisualCollection;
if (visualCollection == null)
{
control.Tag = whenAvailable;
control.DataContextChanged -= control_DataContextChanged;
control.DataContextChanged += control_DataContextChanged;
}
else {
whenAvailable(visualCollection);
}
}
static void control_DataContextChanged(object sender, DependencyPropertyChangedEventArgs e)
{
Action<IVisualCollection> whenAvailable = (sender as FrameworkElement).Tag as Action<IVisualCollection>;
var visualCollection = ((sender as FrameworkElement ).DataContext as IContentItem).Value as IVisualCollection;
if (whenAvailable != null && visualCollection != null)
whenAvailable(visualCollection);
}
private static readonly object[] emptyArgs = new object[] { };
public static List<IContentItem> FindContentItems(this IScreenObject screen, object viewModel)
{
IPresentationScreenView screenView = VsExportProviderService
.GetServiceFromCache<IServiceProxy>().ScreenViewService.GetScreenView(screen) as IPresentationScreenView;
if (screenView == null)
{
throw new InvalidOperationException();
}
IContentItem currentView = screenView.ContentItemTree;
var alreadyFoundViews = new List<IContentItem>();
FindContentItems(currentView, viewModel, alreadyFoundViews);
return alreadyFoundViews;
}
private static void FindContentItems(IContentItem currentView, object viewModel, List<IContentItem> alreadyFoundViews)
{
if (currentView != null)
{
if (currentView.Details != null && currentView.Details.Equals(viewModel))
{
alreadyFoundViews.Add(currentView);
}
foreach (var child in currentView.ChildItems)
{
FindContentItems(child, viewModel, alreadyFoundViews);
}
}
}
public static T GetValueByExpression<T>(this PropertyInfo field, object obj)
{
if (!typeof(T).IsAssignableFrom(field.PropertyType))
throw new InvalidCastException("Type '" + typeof(T).Name + "' cannot be assigned from field type '" + field.PropertyType.Name + "'.");
var constObj = obj == null ? null : System.Linq.Expressions.Expression.Constant(obj);
var fieldAccess = System.Linq.Expressions.Expression.Property(constObj, field);
return System.Linq.Expressions.Expression.Lambda<Func<T>>(fieldAccess).Compile().Invoke();
}
public static T GetValueByExpression<T>(this FieldInfo field, object obj)
{
if (!typeof(T).IsAssignableFrom(field.FieldType))
throw new InvalidCastException("Type '" + typeof(T).Name + "' cannot be assigned from field type '" + field.FieldType.Name + "'.");
var constObj = obj == null ? null : System.Linq.Expressions.Expression.Constant(obj);
var fieldAccess = System.Linq.Expressions.Expression.Field(constObj, field);
return System.Linq.Expressions.Expression.Lambda<Func<T>>(fieldAccess).Compile().Invoke();
}
public static void SetValueByExpression<T>(this FieldInfo field, object owner, T value)
{
if (!typeof(T).IsAssignableFrom(field.FieldType))
throw new InvalidCastException("Type '" + typeof(T).Name + "' cannot be assigned from field type '" + field.FieldType.Name + "'.");
var constObj = owner == null ? null : System.Linq.Expressions.Expression.Constant(owner);
var fieldAccess = System.Linq.Expressions.Expression.Field(constObj, field);
var input = System.Linq.Expressions.Expression.Parameter(typeof(T), "input");
var assign = System.Linq.Expressions.Expression.Assign(fieldAccess, input);
System.Linq.Expressions.Expression.Lambda<Action<T>>(assign, new System.Linq.Expressions.ParameterExpression[] { input })
.Compile().Invoke(value);
}
public static void SetValueByExpression<T>(this PropertyInfo field, object owner, T value)
{
if (!typeof(T).IsAssignableFrom(field.PropertyType))
throw new InvalidCastException("Type '" + typeof(T).Name + "' cannot be assigned from field type '" + field.PropertyType.Name + "'.");
var constObj = owner == null ? null : System.Linq.Expressions.Expression.Constant(owner);
var fieldAccess = System.Linq.Expressions.Expression.Property(constObj, field);
var input = System.Linq.Expressions.Expression.Parameter(typeof(T), "input");
var assign = System.Linq.Expressions.Expression.Assign(fieldAccess, input);
System.Linq.Expressions.Expression.Lambda<Action<T>>(assign, new System.Linq.Expressions.ParameterExpression[] { input })
.Compile().Invoke(value);
}
public static void LoadVisualCollectionsOnDemand(this IClientApplication application)
{
var proxy = Microsoft.VisualStudio.ExtensibilityHosting.VsExportProviderService
.GetExportedValue<Microsoft.LightSwitch.Sdk.Proxy.IServiceProxy>();
proxy.NotificationService.Subscribe(
typeof(Microsoft.LightSwitch.Runtime.Shell.ScreenOpenedNotification),
ScreenOpenedNotificationHolder.instance.OnScreenOpened);
proxy.NotificationService.Subscribe(
typeof(Microsoft.LightSwitch.Runtime.Shell.ScreenReloadedNotification),
ScreenOpenedNotificationHolder.reloadedInstance.OnScreenReloaded);
}
public class ScreenOpenedNotificationHolder : Microsoft.LightSwitch.BaseServices.Notifications.IWeakNotificationSupport
{
public static ScreenOpenedNotificationHolder instance = new ScreenOpenedNotificationHolder();
public static ScreenOpenedNotificationHolder reloadedInstance = new ScreenOpenedNotificationHolder();
public void OnScreenReloaded(Microsoft.LightSwitch.BaseServices.Notifications.Notification n)
{
var screenReloadedNotification = (Microsoft.LightSwitch.Runtime.Shell.ScreenReloadedNotification)n;
var screenObject = screenReloadedNotification.NewScreen;
screenObject.LoadCollectionsOnDemand();
}
public void OnScreenOpened(Microsoft.LightSwitch.BaseServices.Notifications.Notification n)
{
var screenOpenedNotification = (Microsoft.LightSwitch.Runtime.Shell.ScreenOpenedNotification)n;
var screenObject = screenOpenedNotification.Screen;
screenObject.LoadCollectionsOnDemand();
}
private object obj;
public void HoldObject(object value)
{
obj = value;
}
}
}
}
Imports Microsoft.LightSwitch.Client
Imports Microsoft.LightSwitch.Details.Client
Imports Microsoft.LightSwitch.Framework.Client
Imports Microsoft.LightSwitch.Presentation
Imports Microsoft.LightSwitch.Presentation.Extensions
Imports Microsoft.LightSwitch.Presentation.Internal
Imports Microsoft.LightSwitch.Sdk.Proxy
Imports Microsoft.VisualStudio.ExtensibilityHosting
Imports System.Collections.Generic
Imports System.Linq
Imports System.Reflection
Imports System.Windows
Namespace LightSwitchApplication
Module LoadOnDemandExtensions
<System.Runtime.CompilerServices.Extension> _
Public Sub LoadCollectionsOnDemand(screen As IScreenObject)
For Each collection As IScreenCollectionProperty In screen.Details.Properties.All().OfType(Of IScreenCollectionProperty)()
collection.LoadOnDemand()
Next
End Sub
<System.Runtime.CompilerServices.Extension> _
Public Sub LoadOnDemand(screenProperty As IScreenCollectionProperty)
SetAutoLoad(screenProperty, False)
Dim contentItems = FindContentItems(screenProperty.Screen, screenProperty)
For Each contentItem As IContentItem In contentItems
Dim proxy = screenProperty.Screen.FindControl(contentItem.Name)
RemoveHandler proxy.ControlUnavailable, AddressOf proxy_ControlUnavailable
AddHandler proxy.ControlUnavailable, AddressOf proxy_ControlUnavailable
RemoveHandler proxy.ControlAvailable, AddressOf proxy_ControlAvailable
AddHandler proxy.ControlAvailable, AddressOf proxy_ControlAvailable
Next
End Sub
<System.Runtime.CompilerServices.Extension> _
Public Sub LoadOnDemand(Of T As Class)(collection As VisualCollection(Of T))
Dim screen = collection.Screen
Dim screenCollectionProperty = TryCast(screen.Details.Properties.All().[Single](Function(p) p.Value.Equals(collection)), IScreenCollectionProperty)
screenCollectionProperty.LoadOnDemand()
End Sub
Private Sub SetAutoLoad(screenProperty As IScreenProperty, value As Boolean)
Dim boundProp = TryCast(screenProperty, Microsoft.LightSwitch.Details.Client.IScreenBoundProperty)
Dim loader = boundProp.Loader
Dim type = loader.[GetType]()
While type IsNot Nothing AndAlso Not type.Name.StartsWith("ScreenPropertyLoader")
type = type.BaseType
End While
Dim f = type.GetFields(System.Reflection.BindingFlags.NonPublic Or System.Reflection.BindingFlags.Instance)
Dim pmProp = f.[Single](Function(field) field.Name = "_propertyModel")
Dim _propertyModel = pmProp.GetValueByExpression(Of Microsoft.LightSwitch.Model.IScreenPropertyDefinitionBase)(loader)
Dim manualLoadProp = GetType(Microsoft.LightSwitch.Model.Storage.ScreenPropertyBase).GetProperty("ManualLoad")
manualLoadProp.SetValueByExpression(_propertyModel, Not value)
End Sub
<System.Runtime.CompilerServices.Extension> _
Public Sub LoadIfNeeded(Of T As Class)(visualCollection As VisualCollection(Of T))
Dim screenCollectionProperty = TryCast(visualCollection.Screen.Details.Properties.All().[Single](Function(p) p.Value.Equals(visualCollection)), IScreenBoundProperty)
Dim boundProp = TryCast(screenCollectionProperty, Microsoft.LightSwitch.Details.Client.IScreenBoundProperty)
Dim loader = boundProp.Loader
If Not loader.IsLoaded Then
loader.Load()
End If
End Sub
Private Sub proxy_ControlUnavailable(sender As Object, e As ControlUnavailableEventArgs)
GetVisualCollectionFromControlEventArgs(e, Function(visualCollection As IVisualCollection)
'Set auto-load off
Dim screenCollectionProperty = visualCollection.Screen.Details.Properties.All().[Single](Function(p) p.Value.Equals(visualCollection))
SetAutoLoad(screenCollectionProperty, False)
End Function)
End Sub
Private Sub proxy_ControlAvailable(sender As Object, e As ControlAvailableEventArgs)
GetVisualCollectionFromControlEventArgs(e, Function(visualCollection As IVisualCollection)
'Set auto-load on
Dim screenCollectionProperty = visualCollection.Screen.Details.Properties.All().[Single](Function(p) p.Value.Equals(visualCollection))
SetAutoLoad(screenCollectionProperty, True)
''Load' is on VisualCollection<T>, but we don't know T
Dim loadMethod = visualCollection.[GetType]().GetMethod("Load")
loadMethod.Invoke(visualCollection, emptyArgs)
End Function)
End Sub
Private Sub GetVisualCollectionFromControlEventArgs(e As ControlAvailableBaseEventArgs, whenAvailable As Action(Of IVisualCollection))
Dim visualCollection As IVisualCollection = Nothing
Dim control = TryCast(e.Control, FrameworkElement)
Dim contentItem = TryCast(control.DataContext, IContentItem)
If contentItem IsNot Nothing Then
visualCollection = TryCast(contentItem.Value, IVisualCollection)
End If
If visualCollection Is Nothing Then
control.Tag = whenAvailable
RemoveHandler control.DataContextChanged, AddressOf control_DataContextChanged
AddHandler control.DataContextChanged, AddressOf control_DataContextChanged
Else
whenAvailable(visualCollection)
End If
End Sub
Private Sub control_DataContextChanged(sender As Object, e As DependencyPropertyChangedEventArgs)
Dim whenAvailable As Action(Of IVisualCollection) = TryCast(TryCast(sender, FrameworkElement).Tag, Action(Of IVisualCollection))
Dim visualCollection = TryCast(TryCast(TryCast(sender, FrameworkElement).DataContext, IContentItem).Value, IVisualCollection)
If whenAvailable IsNot Nothing AndAlso visualCollection IsNot Nothing Then
whenAvailable(visualCollection)
End If
End Sub
Private ReadOnly emptyArgs As Object() = New Object() {}
<System.Runtime.CompilerServices.Extension> _
Public Function FindContentItems(screen As IScreenObject, viewModel As Object) As List(Of IContentItem)
Dim screenView As IPresentationScreenView = TryCast(VsExportProviderService.GetServiceFromCache(Of IServiceProxy)().ScreenViewService.GetScreenView(screen), IPresentationScreenView)
If screenView Is Nothing Then
Throw New InvalidOperationException()
End If
Dim currentView As IContentItem = screenView.ContentItemTree
Dim alreadyFoundViews = New List(Of IContentItem)()
FindContentItems(currentView, viewModel, alreadyFoundViews)
Return alreadyFoundViews
End Function
Private Sub FindContentItems(currentView As IContentItem, viewModel As Object, alreadyFoundViews As List(Of IContentItem))
If currentView IsNot Nothing Then
If currentView.Details IsNot Nothing AndAlso currentView.Details.Equals(viewModel) Then
alreadyFoundViews.Add(currentView)
End If
For Each child As IContentItem In currentView.ChildItems
FindContentItems(child, viewModel, alreadyFoundViews)
Next
End If
End Sub
<System.Runtime.CompilerServices.Extension> _
Public Function GetValueByExpression(Of T)(field As PropertyInfo, obj As Object) As T
If Not GetType(T).IsAssignableFrom(field.PropertyType) Then
Throw New InvalidCastException("Type '" & GetType(T).Name & "' cannot be assigned from field type '" & field.PropertyType.Name & "'.")
End If
Dim constObj = If(obj Is Nothing, Nothing, System.Linq.Expressions.Expression.Constant(obj))
Dim fieldAccess = System.Linq.Expressions.Expression.[Property](constObj, field)
Return System.Linq.Expressions.Expression.Lambda(Of Func(Of T))(fieldAccess).Compile().Invoke()
End Function
<System.Runtime.CompilerServices.Extension> _
Public Function GetValueByExpression(Of T)(field As FieldInfo, obj As Object) As T
If Not GetType(T).IsAssignableFrom(field.FieldType) Then
Throw New InvalidCastException("Type '" & GetType(T).Name & "' cannot be assigned from field type '" & field.FieldType.Name & "'.")
End If
Dim constObj = If(obj Is Nothing, Nothing, System.Linq.Expressions.Expression.Constant(obj))
Dim fieldAccess = System.Linq.Expressions.Expression.Field(constObj, field)
Return System.Linq.Expressions.Expression.Lambda(Of Func(Of T))(fieldAccess).Compile().Invoke()
End Function
<System.Runtime.CompilerServices.Extension> _
Public Sub SetValueByExpression(Of T)(field As FieldInfo, owner As Object, value As T)
If Not GetType(T).IsAssignableFrom(field.FieldType) Then
Throw New InvalidCastException("Type '" & GetType(T).Name & "' cannot be assigned from field type '" & field.FieldType.Name & "'.")
End If
Dim constObj = If(owner Is Nothing, Nothing, System.Linq.Expressions.Expression.Constant(owner))
Dim fieldAccess = System.Linq.Expressions.Expression.Field(constObj, field)
Dim input = System.Linq.Expressions.Expression.Parameter(GetType(T), "input")
Dim assign = System.Linq.Expressions.Expression.Assign(fieldAccess, input)
System.Linq.Expressions.Expression.Lambda(Of Action(Of T))(assign, New System.Linq.Expressions.ParameterExpression() {input}).Compile().Invoke(value)
End Sub
<System.Runtime.CompilerServices.Extension> _
Public Sub SetValueByExpression(Of T)(field As PropertyInfo, owner As Object, value As T)
If Not GetType(T).IsAssignableFrom(field.PropertyType) Then
Throw New InvalidCastException("Type '" & GetType(T).Name & "' cannot be assigned from field type '" & field.PropertyType.Name & "'.")
End If
Dim constObj = If(owner Is Nothing, Nothing, System.Linq.Expressions.Expression.Constant(owner))
Dim fieldAccess = System.Linq.Expressions.Expression.[Property](constObj, field)
Dim input = System.Linq.Expressions.Expression.Parameter(GetType(T), "input")
Dim assign = System.Linq.Expressions.Expression.Assign(fieldAccess, input)
System.Linq.Expressions.Expression.Lambda(Of Action(Of T))(assign, New System.Linq.Expressions.ParameterExpression() {input}).Compile().Invoke(value)
End Sub
<System.Runtime.CompilerServices.Extension> _
Public Sub LoadVisualCollectionsOnDemand(application As IClientApplication)
Dim proxy = Microsoft.VisualStudio.ExtensibilityHosting.VsExportProviderService.GetExportedValue(Of Microsoft.LightSwitch.Sdk.Proxy.IServiceProxy)()
proxy.NotificationService.Subscribe(GetType(Microsoft.LightSwitch.Runtime.Shell.ScreenOpenedNotification), AddressOf ScreenOpenedNotificationHolder.instance.OnScreenOpened)
proxy.NotificationService.Subscribe(GetType(Microsoft.LightSwitch.Runtime.Shell.ScreenReloadedNotification), AddressOf ScreenOpenedNotificationHolder.reloadInstance.OnScreenReloaded)
End Sub
Public Class ScreenOpenedNotificationHolder
Implements Microsoft.LightSwitch.BaseServices.Notifications.IWeakNotificationSupport
Public Shared instance As ScreenOpenedNotificationHolder = New ScreenOpenedNotificationHolder()
Public Shared reloadedInstance As ScreenOpenedNotificationHolder = New ScreenOpenedNotificationHolder()
Public Sub OnScreenReloaded(n As Microsoft.LightSwitch.BaseServices.Notifications.Notification)
Dim screenReloadedNotification = DirectCast(n, Microsoft.LightSwitch.Runtime.Shell.ScreenReloadedNotification)
Dim screenObject = screenOpenedNotification.NewScreen
screenObject.LoadCollectionsOnDemand()
End Sub
Public Sub OnScreenOpened(n As Microsoft.LightSwitch.BaseServices.Notifications.Notification)
Dim screenOpenedNotification = DirectCast(n, Microsoft.LightSwitch.Runtime.Shell.ScreenOpenedNotification)
Dim screenObject = screenOpenedNotification.Screen
screenObject.LoadCollectionsOnDemand()
End Sub
Private obj As Object
Public Sub HoldObject(value As Object) Implements Microsoft.LightSwitch.BaseServices.Notifications.IWeakNotificationSupport.HoldObject
obj = value
End Sub
End Class
End Module
End Namespace
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment