views:

346

answers:

1

I need an extension method to traverse all Textboxes on my Silverlight page. Assume I always use a grid Layout, then I have:

public static IEnumerable<UIElement> Traverse(this UIElementCollection source, Func<Grid, UIElementCollection> fnRecurse)
    {
        foreach (UIElement item  in source)
        {
            yield return item;
            var g = source.OfType<Grid>();
            foreach (Grid itemsub in g)
                {
                    var t = fnRecurse(itemsub);
                      t.Traverse(fnRecurse);
                      yield return itemsub;

                };
        }

    }

Now, I can call this as such:

baseLayout.Children.Traverse(x =>  x.Children ).OfType<TextBox>().ForEach(
                w =>
                {
                    //Text Box Extension Method, clears the text
                    w.reset();
                });

This never fires. I believe it is the OfType not being able distinguish the UI Elements.

How would I do this? I want to flatten the visual tree then cycle through. Not just textboxes but just getting All whatever I want. Am I yielding in the wrong place or too often?

Edit:

Current Code

public static IEnumerable<UIElement> Traverse(this UIElementCollection source, Func<Grid, UIElementCollection> fnRecurse)
    {

        foreach (UIElement item  in source)
        {
           source.OfType<Grid>().Select(x => x.Children).ForEach(v => Traverse(v, fnRecurse));
           yield return item;
        }

    }

and the grid is

<Grid x:Name="LayoutRoot">
    <Grid.RowDefinitions>
        <RowDefinition Height="42"/>
        <RowDefinition Height="42"/>
        <RowDefinition Height="45"/>
        <RowDefinition Height="43"/>
        <RowDefinition Height="47"/>
        <RowDefinition Height="46"/>
        <RowDefinition/>
        <RowDefinition Height="67"/>
    </Grid.RowDefinitions>
    <Button x:Name="saveAddressButton" Height="24" HorizontalAlignment="Left" Margin="8,0,0,8" VerticalAlignment="Bottom" Width="135" Content="Save and Add More" Click="saveClick" Style="{StaticResource SaveButton}" Foreground="White" Grid.Row="7"/>
    <Button x:Name="saveAddressButton_Copy" Height="24" HorizontalAlignment="Right" Margin="0,0,8,8" VerticalAlignment="Bottom" Width="81" Content="Clear" Click="clearClick" Style="{StaticResource SaveButton}" Foreground="White" Grid.Row="7"/>
    <Grid Height="30" VerticalAlignment="Top" Margin="8,9,8,0">
        <TextBlock Margin="8,0,0,0" Text="AddressLine1" FontSize="16" Foreground="White" HorizontalAlignment="Left" Width="121"/>
        <TextBox x:Name="inputAddressLine1" TextWrapping="Wrap" Margin="218,0,0,0"/>
    </Grid>
    <Grid Height="30" Margin="8,8,8,0" VerticalAlignment="Top" Grid.Row="2">
        <TextBlock Margin="8,0,0,0" Text="AddressLine2" FontSize="16" Foreground="White" HorizontalAlignment="Left" Width="146"/>
        <TextBox x:Name="inputAddressLine2" TextWrapping="Wrap" Margin="219,0,0,0"/>
    </Grid>
    <Grid Height="30" Margin="8,0,8,7" VerticalAlignment="Bottom" Grid.Row="1">
        <TextBlock Margin="8,0,0,0" Text="Post Code" FontSize="16" Foreground="White" HorizontalAlignment="Left" Width="107"/>
        <TextBox x:Name="inputPostCode" TextWrapping="Wrap" Margin="219,0,0,0"/>
    </Grid>
    <Grid Height="30" VerticalAlignment="Bottom" Margin="8,0,8,9" Grid.Row="4">
        <TextBox x:Name="inputCounty" TextWrapping="Wrap" Margin="220,0,0,0"/>
        <TextBlock Margin="8,0,0,0" Text="County" FontSize="16" Foreground="White" HorizontalAlignment="Left" Width="155"/>
    </Grid>
    <Grid Margin="8,8,8,5" Grid.Row="3">
        <TextBox x:Name="inputTown" TextWrapping="Wrap" Margin="219,0,0,0"/>
        <TextBlock Margin="8,0,0,0" Text="AddressLine2" FontSize="16" Foreground="White" HorizontalAlignment="Left" Width="165"/>
    </Grid>
    <Grid Margin="8" Grid.Row="5">
        <TextBlock Text="Number" FontSize="16" Foreground="White" Margin="8,0,0,0" HorizontalAlignment="Left" Width="178"/>
        <TextBox x:Name="inputNumber" TextWrapping="Wrap" Margin="220,0,0,0"/>
    </Grid>
</Grid>

Still only getting this to go one level deep, returning 6 grids and one button!

My current Traverse function is:

public static IEnumerable<UIElement> Traverse(this UIElementCollection source)
    {
        source.OfType<Grid>().SelectMany(v => Traverse(v.Children));
        //This is the top level.
        foreach (UIElement item in source)
        {
            yield return item;
        }
    }

This just knows we are dealing with grids so no need for the second argument. I am only yielding from the iterator box, not from the first line which should call back into the Traverse function with child grids.

+3  A: 

Here is the function I use to deliver this sort of thing:-

    public static IEnumerable<DependencyObject> Descendents(this DependencyObject root)
    {
        int count = VisualTreeHelper.GetChildrenCount(root);
        for (int i = 0; i < count; i++)
        {
            var child = VisualTreeHelper.GetChild(root, i);
            yield return child;
            foreach (var descendent in Descendents(child))
                yield return descendent;
        }
    }

With that extension method available your code becomes:-

 foreach(var txt in baseLayout.Descendents().OfType<TextBox>())
 {
     txt.reset();
 }

Note the avoidance of a "foreach" extension method is one of choice. I don't like the idea of a LINQEsq extension method actually mutating or doing anything for to he application. I prefer to use a proper foreach to actually then operate on the results of the query.

Edit for the "extension method junkies" (if your sensible you won't do this):-

 public static IEnumerable<T> ForEach(this IEnumerable<T> items, Action<T> fn)
 {
     foreach (T item in items)
        fn(item);
 }

Edit what's wrong with your code.

Well primarily this line deep in your Traverse method is the main cause of your problem:-

 t.Traverse(fnRecurse);

It returnns a IEnumerable<UIElement> but you don't do anything with it, not even store the result in a variable.

Also this line:-

var g = source.OfType<Grid>();

would cause each grid found to be enumerated, for each UIElement found. So for example if source contains a TextBox and 2 Grids the above line gets called 3 times. Both Grids would be run through the inner foreach twice.

Also this line:-

yield return itemsub;

well itemsub is always a Grid and will be filtered out by the subsequent TypeOf<TextBox>.

Hence the only TextBox this code would ever return is any TextBox found in the initial UIElementCollection.

AnthonyWJones
Anthony, I am a extension method junkie, hence my ForEach! Ofcourse that doesn't let me use yield. Ta for the code, i'll up it but I'll avoid the tick for a while incase a pure extension method comes up!
DavidA
your method works, mine doesn't. It is the end of the day...I need to know why :)
DavidA
@DavidA: I've added a ForEach for you, despite how I feel about it (trust me it really is a bad idea). It would help me describe to you why your original code doesn't work if a) you show your ForEach extension method and b) a sample of XAML being iterated over.
AnthonyWJones
I have my ForEach! Chalk and Cheese. I am so used to jquery that it just seems natural.
DavidA
I use the same foreach as you. I have a Grid with 6 nested grids each containing a Textblock and a textbox.What I can gather, I am not recursing, the Traverse function returns just the first 6 Grids.
DavidA
@DavidA: Could please edit your question to include at least a little XAML so that its clear what "nested grids", just the outer grid plus one inner grid would be enough.
AnthonyWJones
@AnthonyWJones: +1 That dependency enumerator is just what I needed! Cheers.
Enough already