Skip Ribbon Commands
Skip to main content

Quick Launch

Home
while(!(succeed=try()));
October 24
Walk the Visual Tree

How do you obtain a reference to an ancestor (within the visual tree) of a Silverlight FrameworkElement, given its name?  The question seems pretty straight forward.  FrameworkElement exposes a method, aptly named FindName, which appears to do just that.  Easy solution, right?  Well....maybe.

Using the FindName method works great, as long as the object you're looking for resides within the same XAML namescope as the FrameworkElement.  How do you know if your target object is in the same XAML namescope as your FrameworkElement?  Does the XAML for the FrameworkElement contain the XAML for the target?  If so, then it's in the same namescope.  Should the XAML for the target be nested somewhere along the way (such as within a few levels of UserControls), then you're out of luck.

So what happens if your target isn't in the same XAML namescope as your FrameworkElement?  It's still part of the visual tree, so there should be some way to walk the tree down.  FrameworkElement doesn't appear to expose any members to facilitate this type of action.  Still, there must be a way to do it.

I eventually discovered how to recurse through a visual tree, regardless of the XAML namescope.  The key to doing this lies within the VisualTreeHelper class.  I put together a simple class that would wrap the calls to VisualTreeHelper, and handle the object recursion for me.

This was my first implementation:

public static class VisualTreeWalker

{

    public static FrameworkElement FindName(string name, DependencyObject reference)

    {

        return FindNameInternal(name, reference);

    }

 

    private static FrameworkElement FindNameInternal(string name, DependencyObject reference)

    {

        foreach (DependencyObject obj in GetChildren(reference))

        {

            FrameworkElement elem = obj as FrameworkElement;

            if (elem != null && elem.Name == name)

            {

                return elem;

            }

 

            elem = FindNameInternal(name, obj);

            if (elem != null)

            {

                return elem;

            }

        }

 

        return null;

    }

 

    private static IEnumerable<DependencyObject> GetChildren(DependencyObject reference)

    {

        int childCount = VisualTreeHelper.GetChildrenCount(reference);

        for (int i = 0; i < childCount; i++)

        {

            yield return VisualTreeHelper.GetChild(reference, i);

        }

    }

}

Here is an example where we are attempting to find a nested TextBlock named ChildControl:

TextBlock test = (TextBlock)VisualTreeWalker.FindName("ChildControl", this);

The code performs a top-down search through the visual tree, exhausting each new branch before moving onto the next.  Even though I was pleased with the results, I starting thinking about ways to improve it.  What if I want to look for a specific type, rather than the base FrameworkElement?  It shouldn't be very difficult to add this functionality, and this would be a good excuse to work with Generics on the method level.  I couldn't resist.

This was my next implementation:

public static class VisualTreeWalker

{

    public static FrameworkElement FindName(string name, DependencyObject reference)

    {

        return FindName<FrameworkElement>(name, reference);

    }

 

    public static T FindName<T>(string name, DependencyObject reference) where T : FrameworkElement

    {

        if (string.IsNullOrEmpty(name))

        {

            throw new ArgumentNullException("name");

        }

 

        if (reference == null)

        {

            throw new ArgumentNullException("reference");

        }

 

        return FindNameInternal<T>(name, reference);

    }

 

    private static T FindNameInternal<T>(string name, DependencyObject reference) where T : FrameworkElement

    {

        foreach (DependencyObject obj in GetChildren(reference))

        {

            T elem = obj as T;

            if (elem != null && elem.Name == name)

            {

                return elem;

            }

 

            elem = FindNameInternal<T>(name, obj);

            if (elem != null)

            {

                return elem;

            }

        }

 

        return null;

    }

 

    private static IEnumerable<DependencyObject> GetChildren(DependencyObject reference)

    {

        int childCount = VisualTreeHelper.GetChildrenCount(reference);

        for (int i = 0; i < childCount; i++)

        {

            yield return VisualTreeHelper.GetChild(reference, i);

        }

    }

}

Here is a new example where we are attempting to find a nested TextBlock named ChildControl:

TextBlock test = VisualTreeWalker.FindName<TextBlock>("ChildControl", this);

Once I was satisfied with this new implementation, I couldn't help but think of ways to improve it even more.  Wouldn't it be cool to have some functionality that would return all instances of a specific type?  Ooh.  How cool would it be to impliment searching off of a name pattern in addition to an exact match?

Well, I'll leave those up to you.  Go ahead and give it a shot.  You're already thinking about how you would write the code, aren't you?

July 17
Visual Studio Macros (C# Developers)
How many times have you defined a block of code, such as an if statement?  You begin the statement, followed by the conditions "if ([condition(s)])".  You then follow a pattern similar to the following: 
  • Press Enter to advance a line
  • Start the block with a left brace ({)
  • Press Enter to advance a line
  • End the block with a right brace (})
  • Press Up so you're behind the left brace
  • Press Enter to add a line between the braces

I quickly grew tired of repeating that bulleted list every time I wanted to define a block of code (classes, properties, methods, if statements, switch statements, using statements, etc.).  I found a fast, and relatively easy way to automate this task for me: Visual Studio Macros.  (Note: the reason for the "relatively" part is due to the VB.NET language requirement for macros)

This is the code I wrote to define a block for me:

Sub AutoBrace()

    Dim sel As EnvDTE.TextSelection

 

    sel = DTE.ActiveWindow.Selection

 

    DTE.UndoContext.Open("Auto Brace")

 

    Try

        sel.EndOfLine()

        sel.NewLine()

        sel.Unindent()

        sel.Text = "{"

        sel.EndOfLine()

        sel.NewLine()

        sel.Unindent()

        sel.Text = "}"

        sel.LineUp()

        sel.EndOfLine()

        sel.NewLine()

    Finally

        DTE.UndoContext.Close()

    End Try

End Sub

Since my attempt to automate block definition turned out so well, I decided to take a stab at automating another repetative task I routinely performed: region directives.

In general, I like to use region directives around methods, properties, etc. for outlining.  Again, I figured that there had to be a way to automate this.  This macro proved to be quite a bit tougher than the last one, but I was able to automate the task.

#region SomeMethod

public void SomeMethod()

{

    ...

}

#endregion

This is the macro that I wrote to handle generation of region directives around the code element that the cursor was within.  Basically, it finds the inner-most code element that the cursor is in, locates the start and end points, and wraps them in a #region directive.

Sub AutoRegion()

    Dim sel As EnvDTE.TextSelection

    Dim selPoint As EnvDTE.EditPoint

    Dim editPoint As EnvDTE.EditPoint

    Dim elem As EnvDTE.CodeElement

 

    sel = DTE.ActiveWindow.Selection

    selPoint = sel.ActivePoint.CreateEditPoint

    editPoint = sel.ActivePoint.CreateEditPoint

    elem = GetCurrentCodeElement(editPoint)

 

    DTE.UndoContext.Open("Auto Region")

 

    Try

        editPoint.MoveToPoint(elem.GetStartPoint(vsCMPart.vsCMPartWholeWithAttributes))

        editPoint.LineUp()

 

        Do While IsCommentLine(editPoint, "///")

            editPoint.LineUp()

        Loop

 

        editPoint.EndOfLine()

        sel.MoveToPoint(editPoint, False)

        sel.NewLine()

        sel.Text = "#region " + elem.Name

        editPoint.MoveToPoint(selPoint)

        editPoint.MoveToPoint(elem.GetEndPoint(vsCMPart.vsCMPartWholeWithAttributes))

        sel.MoveToPoint(editPoint, False)

        sel.NewLine()

        sel.Text = "#endregion"

        sel.MoveToPoint(selPoint)

    Finally

        DTE.UndoContext.Close()

    End Try

End Sub

The first issue I ran into was how to find the inner-most element at the cursor location.  The best method of doing this, at the time, was to step through a list of possible elements, returning the first that didn't throw an exception (dirty, I know, but I already felt a little dirty working in VB.NET anyway).

Private Function GetCurrentCodeElement(ByVal editPoint As EnvDTE.EditPoint) As EnvDTE.CodeElement

    Dim elem As EnvDTE.CodeElement

    Dim types As vsCMElement() = _

        {vsCMElement.vsCMElementProperty, _

        vsCMElement.vsCMElementFunction, _

        vsCMElement.vsCMElementEnum, _

        vsCMElement.vsCMElementInterface, _

        vsCMElement.vsCMElementStruct, _

        vsCMElement.vsCMElementClass}

    Dim type As vsCMElement

 

    For Each type In types

        elem = GetCodeElement(editPoint, type)

        If Not elem Is Nothing Then

            Exit For

        End If

    Next

 

    GetCurrentCodeElement = elem

End Function

 

Private Function GetCodeElement(ByVal editPoint As EnvDTE.EditPoint, ByVal type As vsCMElement) As EnvDTE.CodeElement

    Dim elem As EnvDTE.CodeElement

 

    Try

        elem = editPoint.CodeElement(type)

    Catch ex As Exception

        elem = Nothing

    End Try

 

    GetCodeElement = elem

End Function

I also wanted the macro to be able to handle XML comment tags for the target code element as well.

Private Function IsCommentLine(ByVal editPoint As EnvDTE.EditPoint, ByVal linePrefix As String) As Boolean

    Dim lineText As String

    Dim prefixText As String

 

    EditPoint.StartOfLine()

    lineText = EditPoint.GetText(EditPoint.LineLength).Trim

    prefixText = linePrefix.Trim

 

    If lineText.Equals(prefixText) Or lineText.StartsWith(prefixText) Then

        IsCommentLine = True

    Else

        IsCommentLine = False

    End If

End Function

I have attached a zip containing the source file here.  Happy automating!

* edited to correct spelling mistake (consicions -> conditions)