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

0 thoughts on “Using VisualTreeHelper to Perform LightSwitch Wizardry

  1. 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

    • 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!

  2. This is the missing line that was causing the error:

    using System.Linq;

    It works perfect now!

    Thank you Jewel.

  3. 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.

    • 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.” ?

  4. Hi Jewel,

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

    Thanks,

    John

    • 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.

  5. 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?

    • 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()

      • 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?

          • 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.

        • 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.

          • 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.

  6. 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);

    }

    • 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);
      }
      
  7. 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

  8. 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

  9. 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

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

      thanks for the helper.

      • 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.

Leave a Reply

Your email address will not be published. Required fields are marked *

You may use these HTML tags and attributes: <a href="" title=""> <abbr title=""> <acronym title=""> <b> <blockquote cite=""> <cite> <code> <del datetime=""> <em> <i> <q cite=""> <strike> <strong>