<UserControl x:Class="GoogleAuthDemo.ObjectBrowser" xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation" xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" xmlns:d="http://schemas.microsoft.com/expression/blend/2008" xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006" xmlns:local="clr-namespace:GoogleAuthDemo" mc:Ignorable="d" FontFamily="{StaticResource PhoneFontFamilyNormal}" FontSize="{StaticResource PhoneFontSizeNormal}" Foreground="{StaticResource PhoneForegroundBrush}" d:DesignHeight="480" d:DesignWidth="480" x:Name="root"> <UserControl.Resources> <local:ObjectPropertiesConverter x:Key="ObjectPropertiesConvert"/> <Style x:Key="PropertyStyle" TargetType="HyperlinkButton"> <Setter Property="Foreground" Value="{StaticResource PhoneForegroundBrush}"/> <Setter Property="Background" Value="Transparent"/> <Setter Property="FontSize" Value="{StaticResource PhoneFontSizeMedium}"/> <Setter Property="Padding" Value="0"/> <Setter Property="Template"> <Setter.Value> <ControlTemplate TargetType="HyperlinkButton"> <Border Background="Transparent"> <VisualStateManager.VisualStateGroups> <VisualStateGroup x:Name="CommonStates"> <VisualState x:Name="Normal"> <Storyboard> <ObjectAnimationUsingKeyFrames Storyboard.TargetProperty="Foreground" Storyboard.TargetName="TextElement"> <DiscreteObjectKeyFrame KeyTime="0" Value="{StaticResource PhoneAccentBrush}"/> </ObjectAnimationUsingKeyFrames> </Storyboard> </VisualState> <VisualState x:Name="MouseOver"/> <VisualState x:Name="Pressed"> <Storyboard> <DoubleAnimation Duration="0" To="0.5" Storyboard.TargetProperty="Opacity" Storyboard.TargetName="TextElement"/> </Storyboard> </VisualState> <VisualState x:Name="Disabled"> <Storyboard> <ObjectAnimationUsingKeyFrames Storyboard.TargetProperty="Foreground" Storyboard.TargetName="TextElement"> <DiscreteObjectKeyFrame KeyTime="0" Value="{StaticResource PhoneForegroundBrush}"/> </ObjectAnimationUsingKeyFrames> </Storyboard> </VisualState> </VisualStateGroup> </VisualStateManager.VisualStateGroups> <Border Background="{TemplateBinding Background}" Margin="{StaticResource PhoneHorizontalMargin}" Padding="{TemplateBinding Padding}"> <TextBlock x:Name="TextElement" HorizontalAlignment="{TemplateBinding HorizontalContentAlignment}" Text="{TemplateBinding Content}" VerticalAlignment="{TemplateBinding VerticalContentAlignment}"/> </Border> </Border> </ControlTemplate> </Setter.Value> </Setter> </Style> <DataTemplate x:Key="PropertyTemplate"> <StackPanel Orientation="Horizontal" HorizontalAlignment="Stretch"> <TextBlock Text="{Binding Name}" Margin="0,0,10,1"/> <HyperlinkButton HorizontalAlignment="Right" IsEnabled="{Binding HasChildren}" Content="{Binding Value}" Click="HyperlinkButton_Click" Style="{StaticResource PropertyStyle}"/> </StackPanel> </DataTemplate> </UserControl.Resources> <StackPanel Orientation="Vertical"> <HyperlinkButton Content="< Back" Foreground="{StaticResource PhoneAccentBrush}" HorizontalAlignment="Left" IsEnabled="{Binding ElementName=root, Path=CanBack}" Click="BackButton_Click" FontWeight="Bold" FontStyle="Normal"/> <ScrollViewer> <ItemsControl ItemTemplate="{StaticResource PropertyTemplate}" ItemsSource="{Binding Path=., Converter={StaticResource ObjectPropertiesConvert}}"> </ItemsControl> </ScrollViewer> </StackPanel> </UserControl>Then we need to add little bit to the control code behind to handle forward and backward navigation:
public partial class ObjectBrowser : UserControl { public ObjectBrowser() { InitializeComponent(); } private Stack<object> _backStack = new Stack<object>(); private void HyperlinkButton_Click(object sender, RoutedEventArgs e) { if (this.DataContext != null) { _backStack.Push(this.DataContext); CanBack = true; } ObjectProperty p = ((HyperlinkButton)sender).DataContext as ObjectProperty; if (p != null) this.DataContext = p.TheObject; } private void BackButton_Click(object sender, RoutedEventArgs e) { if (_backStack.Count > 0) { DataContext = _backStack.Pop(); CanBack = _backStack.Count > 0; } } ///And we still need this converter class to take an object and break out its properties into something we can enumerate over to get theproperty name and instance's value:/// The public const string CanBackPropertyName = "CanBack"; ///dependency property's name. /// /// Gets or sets the value of the public bool CanBack { get { return (bool)GetValue(CanBackProperty); } set { SetValue(CanBackProperty, value); } } ////// property. This is a dependency property. /// /// Identifies the public static readonly DependencyProperty CanBackProperty = DependencyProperty.Register( CanBackPropertyName, typeof(bool), typeof(ObjectBrowser), new PropertyMetadata(false)); }dependency property. ///
public class ObjectPropertiesConverter : IValueConverter { public object Convert(object value, Type targetType, object parameter, CultureInfo culture) { if (value == null) return null; return from p in value.GetType().GetProperties() where p.CanRead && p.GetIndexParameters().Count() == 0 // skip indexer properties select new ObjectProperty { Name = p.Name, TheObject = p.GetValue(value, null) }; } public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture) { throw new NotImplementedException(); } }But we're going to replace the KeyValuePair with a small helper class. This is what each row in the browser will bind to and it keeps the actual object around to support navigation through the tree:
public class ObjectProperty { public object TheObject { get; set; } public string Name { get; set; } public string Value { get { return TheObject != null ? TheObject.ToString() : "(null)"; } } public bool HasChildren { get { if (TheObject != null) return !TheObject.GetType().IsValueType;// && !(TheObject is string); return false; } } }So with that you should be able to navigate object hierarchies from within the phone app at runtime. I wouldn't use it in an app but I'm hoping it will be good debugging and rapid prototyping tool so I can build the data layer and rought UI structure and then deal with UI styling later.
** warning - I'm posting as I code this so test coverage is well, um... limited **
CodeProject
No comments:
Post a Comment