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" Wid开发者_运维技巧th="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.
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
.
精彩评论