Click here to Skip to main content
65,938 articles
CodeProject is changing. Read more.
Articles
(untagged)

A slightly less simple object browser for Windows phone 7

0.00/5 (No votes)
29 Jan 2012 1  
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)

XML
<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:

C#
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:

C#
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:

C#
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 **

License

This article has no explicit license attached to it but may contain usage terms in the article text or the download files themselves. If in doubt please contact the author via the discussion board below.

A list of licenses authors might use can be found here