Using VisualTreeHelper to Perform LightSwitch Wizardry

Normally when you want to teach LightSwitch a fancy new trick, the best approach is to use a Silverlight UserControl. Or if you need easy reuse of the new feature, a LightSwitch extension.  In some cases you may even need to write a custom shell.  But that can be a daunting prospect for a LightSwitch newbie.  Furthermore if the developer doesn’t have access to the full Visual Studio, it may not be possible to create a UserControl or LightSwitch extension (see Extensions Made Easy for a solution to this problem in many cases!).

In situations where you’d like to perform a little LightSwitch wizardry without resorting to writing LightSwitch Extensions, a very useful class is VisualTreeHelper.

VisualTreeHelper allows you to navigate the underlying visual tree of your screens and get references to any control, even those you normally can’t get access to easily from LightSwitch.

To make the class easier to use, I often take advantage of the following helper class:

using System;
using System.Collections.Generic;
using System.Windows;
using System.Windows.Media;

namespace DotNetLore.Xpf.Core.Extensions
{
    public static class VisualTreeHelpers
    {
        public static IEnumerable<T> GetChildrenByType<T>(this DependencyObject element)
          where T : DependencyObject
        {
            for (int i = 0; i < VisualTreeHelper.GetChildrenCount(element); i++)
            {
                var child = VisualTreeHelper.GetChild(element, i);
                if (child != null)
                {
                    T t = child as T;
                    if (t != null)
                        yield return t;
                    foreach (T item in GetChildrenByType<T>(child))
                    {
                        yield return item;
                    }
                }
            }
        }

        public static IEnumerable<T> GetParentsByType<T>(this DependencyObject element) 
          where T : DependencyObject
        {
            var parent = VisualTreeHelper.GetParent(element);
            while (parent != null)
            {
                T t = parent as T;
                if (t != null)
                    yield return t;
                parent = VisualTreeHelper.GetParent(parent);
            }
        }
    }
}

By using a tool like Silverlight Spy, you can analyze the underlying control tree and XAML of your LightSwitch screens and find the controls that you need to access.

As a simple example, suppose that you want to set a larger font size or a different color for the label that is attached to one of your Text Box controls. If you wanted access to the TextBox control, this would be easy…

this.FindControl("MyTextBox").ControlAvailable += (s,e) =>
{
  (e.Control as TextBox).FontSize = 24;
};

But how do we get access to the attached label instead of the TextBox itself? Using the VisualTreeHelper and VisualTreeHelpers classes, we can write something like this in the screen’s Created method:

this.FindControl("CustomerCode").ControlAvailable += (s,e)=>
{
    var textBox = (e.Control as Control);
    var root = System.Windows.Media.VisualTreeHelper.GetRoot(textBox);
    var label = root.GetChildrenByType<TextBlock>()
        .Where(textBlock => textBlock.DataContext == textBox.DataContext)
        .First();
    label.FontSize = 16;
    label.Foreground = new SolidColorBrush(Colors.Red);
    label.Effect = new System.Windows.Media.Effects.DropShadowEffect();
};

You’ll need these usings:

using DotNetLore.Xpf.Core.Extensions;
using System.Windows.Controls;
using System.Windows.Media;

Here I’m getting the DataContext for the TextBox we’re interested in, then enumerating all the screen’s TextBlock controls and finding the one which has the same DataContext, since I know in this particular case that this will be the attached label that I want. There’s no particular need to take this approach here… one could just do root.GetChildrenByType().Where(textBlock => textBlock.Text == “Customer Code:”).First();

For VB, the VisualTreeHelpers module would look like this:

Module VisualTreeHelpers

    <System.Runtime.CompilerServices.Extension> _
    Public Iterator Function GetChildrenByType(Of T As DependencyObject)(element As DependencyObject) As IEnumerable(Of T)
        For i As Integer = 0 To VisualTreeHelper.GetChildrenCount(element) - 1
            Dim child = VisualTreeHelper.GetChild(element, i)
            If child IsNot Nothing Then
                Dim cast As T = TryCast(child, T)
                If cast IsNot Nothing Then
                    Yield cast
                End If
                For Each item As T In GetChildrenByType(Of T)(child)
                    Yield item
                Next
            End If
        Next
    End Function

    <System.Runtime.CompilerServices.Extension> _
    Public Iterator Function GetParentsByType(Of T As DependencyObject)(element As DependencyObject) As IEnumerable(Of T)
        Dim parent = VisualTreeHelper.GetParent(element)
        While parent IsNot Nothing
            Dim cast As T = TryCast(parent, T)
            If cast IsNot Nothing Then
                Yield cast
            End If
            parent = VisualTreeHelper.GetParent(parent)
        End While
    End Function


End Module

And the code for my screen looks like this:

Imports VisualTreeHelpers

Namespace LightSwitchApplication

    Public Class CreateNewCustomer

        Private Sub CustomerCode_ControlAvailable(ByVal sender As Object, ByVal e As ControlAvailableEventArgs)
            Dim textBox = TryCast(e.Control, Control)
            Dim root = VisualTreeHelper.GetRoot(textBox)
            Dim label = root.GetChildrenByType(Of TextBlock)().Where(Function(textBlock) Equals(textBlock.DataContext, textBox.DataContext)).First()
            label.FontSize = 16
            label.Foreground = New SolidColorBrush(Colors.Red)
            label.Effect = New System.Windows.Media.Effects.DropShadowEffect()
        End Sub

        Private Sub CreateNewCustomer_InitializeDataWorkspace(ByVal saveChangesTo As Global.System.Collections.Generic.List(Of Global.Microsoft.LightSwitch.IDataService))
            ' Write your code here.
            Me.CustomerProperty = New Customer()
            AddHandler Me.FindControl("CustomerCode").ControlAvailable, AddressOf CustomerCode_ControlAvailable
        End Sub

        Private Sub CreateNewCustomer_Saved()
            ' Write your code here.
            Me.Close(False)
            Application.Current.ShowDefaultScreen(Me.CustomerProperty)
        End Sub

    End Class

End Namespace
  • Daniel

    Hi Jewel,

    From this line of code:

    var label = root.GetChildrenByType().Where(textBlock => textBlock.DataContext == textBox.DataContext).First();

    I’m getting this error:

    System.Collections.Generic.IEnumerable’ does not contain a definition for ‘Where’ and no extension method ‘Where’…

    Can you tell me what I am missing

    (working with Lightswitch 2012)
    Thanks,
    Daniel

    • http://dotnetlore.com Jewel Lambert

      I suspect you may be missing some using clauses. Make sure that when you added the usings I mentioned, you didn’t overwrite the ones which were already there…. i.e. you should have something like this:

      using System;
      using System.Linq;
      using System.IO;
      using System.IO.IsolatedStorage;
      using System.Collections.Generic;
      using Microsoft.LightSwitch;
      using Microsoft.LightSwitch.Framework.Client;
      using Microsoft.LightSwitch.Presentation;
      using Microsoft.LightSwitch.Presentation.Extensions;
      using DotNetLore.Xpf.Core.Extensions;
      using System.Windows.Controls;
      using System.Windows.Media;

      Hope that helps!

  • Daniel

    This is the missing line that was causing the error:

    using System.Linq;

    It works perfect now!

    Thank you Jewel.

  • Ricardo

    Hello Jewel,

    Great Blog!

    I am getting the following error:

    System.Windows.Media.VisualTreeHelper does not contain a definition for ‘GetRoot’

    on the line:

    var root = System.Windows.Media.VisualHelper.GetRoot(textbox);

    Do you have any idea?

    Thank you.

    • http://dotnetlore.com Jewel Lambert

      Hi Ricardo,

      Sorry, I have no idea why you would get that error. Does “GetRoot” not come up with intellisense when you type “System.Windows.Media.VisualTreeHelper.” ?

  • Ricardo

    Yes that is correct. Does not come up.

  • John

    Hi Jewel,

    can you pls provide VB code? I am struggling to get ‘label’ defined in the Created screen method.

    Thanks,

    John

    • http://dotnetlore.com Jewel Lambert

      Hi John,

      I managed to get an example working in VB (mostly using the online C# to VB converters, since I’m not a VB expert), and I’ve added the code to the article.

      Thanks for the feedback.

  • John

    Thanks a lot Jewel for this great code. It does work fine with all LS basic controls but somehow I can’t get it working with the Phone Number control. My understanding is that the PN control is a specific version of TextBox and therefore I tried to cast from that control but an error message “Sequence contains no elements” is displayed. Any suggestions?

    • http://dotnetlore.com Jewel Lambert

      Comparing DataContexts seems to not work well when dealing with the Phone Number control and some of the other special textbox controls.

      An alternate approach is just to search for the textblock by its Text value. By default LS uses the Display Name followed by a colon. So for instance if you’ve got a Phone Number field whose name is “Home Phone”, then you’d use something like this:

      Dim label = root.GetChildrenByType(Of TextBlock)().Where(Function(textBlock) Equals(textBlock.Text, “Home Phone:”)).First()

      • John

        Thanks a lot, Jewel, that works brilliant. To pick your brains once again: how do you get the code working with a layout control like Columns Layout and Rows Layout?

        • http://dotnetlore.com Jewel Lambert

          Hi John,

          Could you be a bit more specific… what are you trying to do with the layout controls?

          • John

            On a new data screen I have a number of group controls (in this case a Pixata Labelled Group) with Rows and Columns Layout and therein individual controls (mostly auto complete boxes and text boxes).

            I am trying to get the display names of all controls including those attached to the group controls in a customised format (specific font and colour) that varies per control and differs from the LS standard format.

            Thanks to your help all individual controls have now re-formatted display names but I am struggling to get the group controls working in the same way to give the screen a consistent look and feel.

            I don’t want those formats applied to all LS screens so a customised Theme would not work in my case?

            If not clear I can send you some code snippets if you wish.

            Thanks a lot for your appreciated feedback.

        • http://dotnetlore.com Jewel Lambert

          The technique of searching by the exact text of the label should allow you to find pretty much any TextBlocks on the screen. So if you’ve got a Pixata Labeled Group with a label that reads “My Group Label” then you could find it using something like

          Dim label = root.GetChildrenByType(Of TextBlock)().Where(Function(textBlock) Equals(textBlock.Text, “My Group Label”)).First()

          Hope that helps.

          • John

            Got it sorted: in FindControl(“My Control”), My Control needs to be the parent of the layout control in which the label is changed. If the lay-out control itself is taken, the description My Group Label” is simply not recognised (no error message).

            Thanks a lot for your help, Jewel.

  • http://na omar

    thanks Jewel for the grate work ,
    my question is how to apply the same formatting for multiple label controls , how to code the event handler to share it for multiple controls ;instead of repeating the code , i need some thing like this :-

    this.FindControl(“first_name”).ControlAvailable += RedFormattingHandeler(s , e);
    this.FindControl(“second_name”).ControlAvailable += RedFormattingHandeler(s , e);
    this.FindControl(“third_name”).ControlAvailable += RedFormattingHandeler(s , e);
    this.FindControl(“forth_name”).ControlAvailable += RedFormattingHandeler(s , e);

    RedFormattingHandeler(s , e);
    {
    var textBox = (e.Control as TextBox);
    var root = System.Windows.Media.VisualTreeHelper.GetRoot(textBox);
    var label = root.GetChildrenByType()
    .Where(textBlock => textBlock.DataContext == textBox.DataContext)
    .First();
    label.FontSize = 14;
    label.Foreground = new SolidColorBrush(Colors.Red);

    }

    • http://dotnetlore.com Jewel Lambert

      Glad you’ve found it helpful.

      Your approach should work fine for applying the formatting to multiple textBlocks… the code might look something like this:

      partial void CreateNewTest_Created()
      {
          this.FindControl(&quot;FirstName&quot;).ControlAvailable += RedFormattingHandler;
          this.FindControl(&quot;SecondName&quot;).ControlAvailable += RedFormattingHandler;
      }
      
      void RedFormattingHandler(object sender, Microsoft.LightSwitch.Presentation.ControlAvailableEventArgs e)
      {
              var textBox = (e.Control as TextBox);
              var root = System.Windows.Media.VisualTreeHelper.GetRoot(textBox);
                      
              var label = root.GetChildrenByType&lt;TextBlock&gt;()
                  .Where(textBlock =&gt; textBlock.DataContext == textBox.DataContext)
                  .First();
              label.FontSize = 14;
              label.Foreground = new SolidColorBrush(Colors.Red);
      }
      
  • omar

    Thanks very much for your help , it works fine for me ,

    i have a question , i want to navigate between controls when click Enter Button
    so it will be more easy for data entry operator to enter a value then click enter to go to next controls

    any idea ?

    omar awwad

    • http://dotnetlore.com Jewel Lambert

      No ideas for how to do that easily…. I may play around with it later. If I come up with something I’ll make a new blog post about it.

  • omar

    Hi Jewel

    i fond that when call this code for dropdown list for Gender , it fire an error

    this.FindControl(“gender1″).ControlAvailable += RedFormattingHandler;

    how to solve this ,

    omar

    • http://dotnetlore.com Jewel Lambert

      Likely this is due to it being an AutocompleteBox instead of a TextBox.

      Instead of var textBox = (e.Control as TextBox) try this:

      var textBox = (e.Control as Control);

      • omar

        Thanks Very much for your reply

        I Works fine.

  • Michiel

    Hi Jewel,

    I’m trying to use your VisualTreeHelper to find the ModalWindowDialogContent in a Modal Window.
    I observe the ControlAvailable event of the modal window, and use this code:
    VisualTreeHelpers.GetChildrenByType((DependencyObject) e1.Control);

    This returns null, though the Modal Window (control type ScreenChildWindow) should have a child with type ModalWindowDialogContent according to SilverlightSpy?

    Do you have any idea?
    Michiel

    • Michiel

      Correction:
      VisualTreeHelpers.GetChildrenByType((DependencyObject) e1.Control);

      thanks for the helper.

      • Jewel Lambert

        Hi Michiel, sorry for the delay in response – for some reason WordPress didn’t send me an email to alert me about your comment.

        I suspect that the problem you’re seeing is due to LightSwitch not having added the children of the ScreenChildWindow into the visual tree when the ControlAvailable event fires.

        What you’ll likely have to do is attach to an additional event which fires once the controls have been added to the Visual Tree.

        Something like this: http://pastebin.com/TffEBwHi

        Argh… the comment system is mangling the code… I edited it to a link on PasteBin.