65.9K
CodeProject is changing. Read more.
Home

A slightly less simple object browser for Windows phone 7

emptyStarIconemptyStarIconemptyStarIconemptyStarIconemptyStarIcon

0/5 (0 vote)

Jan 29, 2012

CPOL
viewsIcon

9680

A slightly less simple object browser for Windows phone 7

Building slightly on the previous post, we can drill into properties and navigate back up the parent tree.The XAML adds some navigation hyperlinks and styling to differentiate betwwen a property that can drilled into vs one that can't (ValueTypes)

<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 a little bit to the control's 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;
        }
    }
    
    /// <summary>
    /// The <see cref="CanBack"> dependency property's name.
    /// </summary>
    public const string CanBackPropertyName = "CanBack";
    
    /// <summary>
    /// Gets or sets the value of the <see cref="CanBack">
    /// property. This is a dependency property.
    /// </summary>
    public bool CanBack
    {
        get
        {
            return (bool)GetValue(CanBackProperty);
        }
        set
        {
            SetValue(CanBackProperty, value);
        }
    }
    
    /// <summary>
    /// Identifies the <see cref="CanBack"> dependency property.
    /// </summary>
    public static readonly DependencyProperty CanBackProperty = 
                    DependencyProperty.Register(
        CanBackPropertyName,
        typeof(bool),
        typeof(ObjectBrowser),
        new PropertyMetadata(false));
}

And we still need this converter class to take an object and break out its properties into something we can enumerate over to get the property name and instance's value:

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 rough UI structure and then deal with UI styling later.

** Warning - I'm posting as I code this so test coverage is well, um... limited **