Click here to Skip to main content
15,886,258 members
Articles / Desktop Programming / WPF

ObjectPresenter - How to Generate an Object's Testing GUI from a Given Object

Rate me:
Please Sign up or sign in to vote.
4.94/5 (27 votes)
5 Jan 2012CPOL11 min read 37.3K   924   49  
In this article, I explain step by step, how we can create a WPF custom control that gets an object and, generates a GUI that enables editing that object's properties and invoking that object's methods.
<ResourceDictionary xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
                    xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
                    xmlns:local="clr-namespace:ObjectPresentation">
    
    <ResourceDictionary.MergedDictionaries>
        <ResourceDictionary Source="/ObjectPresentation;component/Styles/ButtonsStyles.xaml" />
    </ResourceDictionary.MergedDictionaries>

    <DataTemplate x:Key="regularFieldDataTemplate"
                  DataType="{x:Type local:ValueViewModel}">
        <Grid>
            <TextBox Text="{Binding Value}" x:Name="editableFieldValue" />
            <TextBlock Text="{Binding Value}" x:Name="constFieldValue" 
                               Visibility="Collapsed"/>
        </Grid>
        <DataTemplate.Triggers>
            <DataTrigger Binding="{Binding IsEditable}"
                             Value="False">
                <Setter TargetName="editableFieldValue"
                            Property="Visibility"
                            Value="Collapsed" />
                <Setter TargetName="constFieldValue"
                            Property="Visibility"
                            Value="Visible" />
            </DataTrigger>
        </DataTemplate.Triggers>
    </DataTemplate>

    <DataTemplate x:Key="booleanFieldDataTemplate"
                  DataType="{x:Type local:ValueViewModel}">
        <Grid>
            <CheckBox IsChecked="{Binding Value, Mode=TwoWay}" 
                              x:Name="editableBooleanFieldValue" />
            <TextBlock Text="{Binding Value}" x:Name="constBooleanFieldValue" 
                               Visibility="Collapsed"/>
        </Grid>
        <DataTemplate.Triggers>
            <DataTrigger Binding="{Binding IsEditable}"
                             Value="False">
                <Setter TargetName="editableBooleanFieldValue"
                            Property="Visibility"
                            Value="Collapsed" />
                <Setter TargetName="constBooleanFieldValue"
                            Property="Visibility"
                            Value="Visible" />
            </DataTrigger>
        </DataTemplate.Triggers>
    </DataTemplate>

    <DataTemplate x:Key="enumFieldDataTemplate"
                  DataType="{x:Type local:ValueViewModel}">
        <Grid>
            <ComboBox x:Name="editableEnumFieldValue" 
                              ItemsSource="{Binding EnumValues}"
                              SelectedItem="{Binding Path=Value, Mode=TwoWay}" />
            <TextBlock  Text="{Binding Value}" x:Name="constEnumFieldValue" 
                               Visibility="Collapsed" />
        </Grid>
        <DataTemplate.Triggers>
            <DataTrigger Binding="{Binding IsEditable}"
                             Value="False">
                <Setter TargetName="editableEnumFieldValue"
                            Property="Visibility"
                            Value="Collapsed" />
                <Setter TargetName="constEnumFieldValue"
                            Property="Visibility"
                            Value="Visible" />
            </DataTrigger>
        </DataTemplate.Triggers>
    </DataTemplate>

    <DataTemplate x:Key="complexFieldDataTemplate"
                  DataType="{x:Type local:ValueViewModel}">
        <DataTemplate.Resources>
            <Storyboard x:Key="showSubFieldsStoryboard">
                <DoubleAnimation Storyboard.TargetName="subFields"
                                 Storyboard.TargetProperty="(FrameworkElement.LayoutTransform).(ScaleTransform.ScaleX)"
                                 To="1"
                                 Duration="0:0:0.2" />
                <DoubleAnimation Storyboard.TargetName="subFields"
                                 Storyboard.TargetProperty="(FrameworkElement.LayoutTransform).(ScaleTransform.ScaleY)"
                                 To="1"
                                 Duration="0:0:0.2" />
            </Storyboard>
            <Storyboard x:Key="hideSubFieldsStoryboard">
                <DoubleAnimation Storyboard.TargetName="subFields"
                                 Storyboard.TargetProperty="(FrameworkElement.LayoutTransform).(ScaleTransform.ScaleX)"
                                 To="0"
                                 Duration="0:0:0.2" />
                <DoubleAnimation Storyboard.TargetName="subFields"
                                 Storyboard.TargetProperty="(FrameworkElement.LayoutTransform).(ScaleTransform.ScaleY)"
                                 To="0"
                                 Duration="0:0:0.2" />
            </Storyboard>
        </DataTemplate.Resources>
        <StackPanel HorizontalAlignment="Stretch">
            <ToggleButton x:Name="toggleSubFields"
                                Style="{StaticResource expandButtonStyle}"
                                  HorizontalAlignment="Left"
                                ToolTip="Expand sub-fields."
                                    IsChecked="{Binding IsExpandedByDefault, Mode=OneTime}" />
            <ItemsControl ItemsSource="{Binding SubFields}" x:Name="subFields"
                              Margin="15,0,0,0" >
                <ItemsControl.LayoutTransform>
                    <ScaleTransform ScaleX="0" ScaleY="0" />
                </ItemsControl.LayoutTransform>
            </ItemsControl>
        </StackPanel>
        <DataTemplate.Triggers>
            <Trigger SourceName="toggleSubFields"
                         Property="IsChecked"
                         Value="True">
                <Setter TargetName="toggleSubFields"
                        Property="ToolTip"
                        Value="Collapse sub-fields." />
                <Trigger.EnterActions>
                    <BeginStoryboard Storyboard="{StaticResource showSubFieldsStoryboard}"/>
                </Trigger.EnterActions>
                <Trigger.ExitActions>
                    <BeginStoryboard Storyboard="{StaticResource hideSubFieldsStoryboard}" />
                </Trigger.ExitActions>
            </Trigger>
        </DataTemplate.Triggers>
    </DataTemplate>

    <DataTemplate x:Key="collectionFieldDataTemplate"
                  DataType="{x:Type local:ValueViewModel}">
        <DataTemplate.Resources>
            <Storyboard x:Key="showCollectionElementsStoryboard">
                <DoubleAnimation Storyboard.TargetName="collectionElements"
                                 Storyboard.TargetProperty="(FrameworkElement.LayoutTransform).(ScaleTransform.ScaleX)"
                                 To="1"
                                 Duration="0:0:0.2" />
                <DoubleAnimation Storyboard.TargetName="collectionElements"
                                 Storyboard.TargetProperty="(FrameworkElement.LayoutTransform).(ScaleTransform.ScaleY)"
                                 To="1"
                                 Duration="0:0:0.2" />
            </Storyboard>
            <Storyboard x:Key="hideCollectionElementsStoryboard">
                <DoubleAnimation Storyboard.TargetName="collectionElements"
                                 Storyboard.TargetProperty="(FrameworkElement.LayoutTransform).(ScaleTransform.ScaleX)"
                                 To="0"
                                 Duration="0:0:0.2" />
                <DoubleAnimation Storyboard.TargetName="collectionElements"
                                 Storyboard.TargetProperty="(FrameworkElement.LayoutTransform).(ScaleTransform.ScaleY)"
                                 To="0"
                                 Duration="0:0:0.2" />
            </Storyboard>
        </DataTemplate.Resources>
        <StackPanel HorizontalAlignment="Stretch">
            <ToggleButton x:Name="toggleCollectionElements"
                                Style="{StaticResource expandButtonStyle}"
                                  HorizontalAlignment="Left"
                                ToolTip="Expand collection's elements."
                                    IsChecked="{Binding IsExpandedByDefault, Mode=OneTime}" />
            <StackPanel x:Name="collectionElements">
                <StackPanel.LayoutTransform>
                    <ScaleTransform ScaleX="0" ScaleY="0" />
                </StackPanel.LayoutTransform>
                <ItemsControl ItemsSource="{Binding CollectionElements}"                                  
                                  Margin="15,0,0,0" />
                <Button x:Name="btnAddElement" Content="Add element" Margin="5"
                                HorizontalAlignment="Center"
                                Command="{Binding AddNewCollectionElementCommand}" />
            </StackPanel>
        </StackPanel>
        <DataTemplate.Triggers>
            <DataTrigger Binding="{Binding IsEditable}"
                             Value="False">
                <Setter TargetName="btnAddElement"
                            Property="Visibility"
                            Value="Collapsed" />
            </DataTrigger>
            <Trigger SourceName="toggleCollectionElements"
                         Property="IsChecked"
                         Value="True">
                <Setter TargetName="toggleCollectionElements"
                        Property="ToolTip"
                        Value="Collapse collection's elements." />
                <Trigger.EnterActions>
                    <BeginStoryboard Storyboard="{StaticResource showCollectionElementsStoryboard}"/>
                </Trigger.EnterActions>
                <Trigger.ExitActions>
                    <BeginStoryboard Storyboard="{StaticResource hideCollectionElementsStoryboard}" />
                </Trigger.ExitActions>
            </Trigger>
        </DataTemplate.Triggers>
    </DataTemplate>

    <DataTemplate x:Key="nullFieldDataTemplate"
                  DataType="{x:Type local:ValueViewModel}">
        <TextBlock Text="[null]"
                   Foreground="Blue" />
    </DataTemplate>

    <DataTemplate DataType="{x:Type local:ValueViewModel}">
        <DataTemplate.Resources>
            <BooleanToVisibilityConverter x:Key="BooleanToVisibilityConverter" />
            <Storyboard x:Key="showRootElementStoryboard">
                <DoubleAnimation Storyboard.TargetName="rootElement"
                                 Storyboard.TargetProperty="(FrameworkElement.LayoutTransform).(ScaleTransform.ScaleX)"
                                 To="1"
                                 Duration="0:0:0.2" />
                <DoubleAnimation Storyboard.TargetName="rootElement"
                                 Storyboard.TargetProperty="(FrameworkElement.LayoutTransform).(ScaleTransform.ScaleY)"
                                 To="1"
                                 Duration="0:0:0.2" />
            </Storyboard>
            <Storyboard x:Key="hideRootElementStoryboard">
                <DoubleAnimation Storyboard.TargetName="rootElement"
                                 Storyboard.TargetProperty="(FrameworkElement.LayoutTransform).(ScaleTransform.ScaleX)"
                                 To="0"
                                 Duration="0:0:0.2" />
                <DoubleAnimation Storyboard.TargetName="rootElement"
                                 Storyboard.TargetProperty="(FrameworkElement.LayoutTransform).(ScaleTransform.ScaleY)"
                                 To="0"
                                 Duration="0:0:0.2" />
            </Storyboard>
            <Storyboard x:Key="showTypesStoryboard">
                <DoubleAnimation Storyboard.TargetName="typesBorder"
                                 Storyboard.TargetProperty="(FrameworkElement.LayoutTransform).(ScaleTransform.ScaleX)"
                                 To="1"
                                 Duration="0:0:0.2" />
                <DoubleAnimation Storyboard.TargetName="typesBorder"
                                 Storyboard.TargetProperty="(FrameworkElement.LayoutTransform).(ScaleTransform.ScaleY)"
                                 To="1"
                                 Duration="0:0:0.2" />
            </Storyboard>
            <Storyboard x:Key="hideTypesStoryboard">
                <DoubleAnimation Storyboard.TargetName="typesBorder"
                                 Storyboard.TargetProperty="(FrameworkElement.LayoutTransform).(ScaleTransform.ScaleX)"
                                 To="0"
                                 Duration="0:0:0.2" />
                <DoubleAnimation Storyboard.TargetName="typesBorder"
                                 Storyboard.TargetProperty="(FrameworkElement.LayoutTransform).(ScaleTransform.ScaleY)"
                                 To="0"
                                 Duration="0:0:0.2" />
            </Storyboard>
        </DataTemplate.Resources>
        <Grid x:Name="rootElement"
              HorizontalAlignment="Stretch"
              Margin="1">
            <Grid.LayoutTransform>
                <ScaleTransform ScaleX="0" ScaleY="0" />
            </Grid.LayoutTransform>
            <Grid.ColumnDefinitions>
                <ColumnDefinition Width="Auto" />
                <ColumnDefinition />
                <ColumnDefinition Width="Auto" />
            </Grid.ColumnDefinitions>
            
            <local:SuspendedButton x:Name="btnRemove" 
                                   Style="{StaticResource removeButtonStyle}"
                                   ToolTip="Remove element."
                                   SuspendTime="0:0:0.2"
                                   Margin="1"
                                   Command="{Binding RemoveCommand}"
                                   Visibility="Collapsed"
                                   VerticalAlignment="Top"
                                   HorizontalAlignment="Left" />
            
            <Grid x:Name="fieldRegion"
                  Grid.Column="1">
                <Grid.ColumnDefinitions>
                    <ColumnDefinition Width="Auto"/>
                    <ColumnDefinition Width="Auto"/>
                    <ColumnDefinition />
                </Grid.ColumnDefinitions>
                <StackPanel Orientation="Horizontal"
                            Visibility="{Binding HasName, Converter={StaticResource BooleanToVisibilityConverter}}">
                    <TextBlock Text="{Binding Name}" />
                    <TextBlock Text=": " />
                </StackPanel>
                <Grid x:Name="isNullRegion"
                      Grid.Column="1"
                      Margin="2">
                    <ToggleButton Visibility="{Binding IsNullable, Converter={StaticResource BooleanToVisibilityConverter}}"
                                  Style="{StaticResource nullButtonStyle}"
                                  VerticalAlignment="Top"
                                  IsChecked="{Binding IsNull, Mode=TwoWay}" />
                </Grid>

                <ContentControl x:Name="fieldValue"
                                Content="{Binding}"
                                Grid.Column="2" />
            </Grid>
            
            <Grid Visibility="{Binding HasAdditionalCompatibleTypes, Converter={StaticResource BooleanToVisibilityConverter}}"
                  Grid.Column="2"
                  VerticalAlignment="Top"
                  Margin="5,0,0,0">
                <Grid.RowDefinitions>
                    <RowDefinition Height="Auto" />
                    <RowDefinition />
                </Grid.RowDefinitions>
                <ToggleButton x:Name="typesToggle" 
                              Style="{StaticResource expandButtonStyle}"
                              HorizontalAlignment="Right"
                              ToolTip="Expand the compatible types region.">
                    <ToggleButton.LayoutTransform>
                        <ScaleTransform ScaleX="-1" />
                    </ToggleButton.LayoutTransform>
                </ToggleButton>
                <Border x:Name="typesBorder"
                        Grid.Row="1"
                        Margin="0,3"
                        BorderThickness="1"
                        CornerRadius="3"
                        BorderBrush="#CC000000"
                        Background="#44000000">
                    <Border.LayoutTransform>
                        <ScaleTransform ScaleX="0" ScaleY="0" />
                    </Border.LayoutTransform>
                    <StackPanel Margin="5">
                        <TextBlock Text="Types" 
                                   HorizontalAlignment="Center"
                                   FontSize="14"
                                   Margin="0,0,0,5"
                                   Foreground="#EEFFFFFF"/>
                        <ListBox ItemsSource="{Binding CompatibleTypes}"
                                 SelectedItem="{Binding SelectedCompatibleType, Mode=TwoWay}"
                                 Background="Transparent"
                                 BorderBrush="Transparent"
                                 MaxHeight="250">
                            <ListBox.ItemContainerStyle>
                                <Style TargetType="{x:Type ListBoxItem}">
                                    <Setter Property="Template">
                                        <Setter.Value>
                                            <ControlTemplate TargetType="{x:Type ListBoxItem}">
                                                <Grid>
                                                    <Border x:Name="selectedTypeBackground"
                                                            Background="#99FFFFFF"
                                                            BorderThickness="1"
                                                            BorderBrush="#DDFFFFFF"
                                                            CornerRadius="3"
                                                            Visibility="Hidden"/>
                                                    <ContentPresenter Margin="3" />
                                                </Grid>
                                                <ControlTemplate.Triggers>
                                                    <Trigger Property="IsSelected"
                                                             Value="True">
                                                        <Setter TargetName="selectedTypeBackground"
                                                                Property="Visibility"
                                                                Value="Visible" />                                                                
                                                    </Trigger>
                                                </ControlTemplate.Triggers>
                                            </ControlTemplate>
                                        </Setter.Value>
                                    </Setter>
                                </Style>
                            </ListBox.ItemContainerStyle>
                        </ListBox>
                    </StackPanel>
                </Border>                              
            </Grid>

        </Grid>
        <DataTemplate.Triggers>
            <EventTrigger SourceName="rootElement"
                          RoutedEvent="FrameworkElement.Loaded">
                <BeginStoryboard Storyboard="{StaticResource showRootElementStoryboard}" />
            </EventTrigger>
            <EventTrigger RoutedEvent="local:SuspendedButton.BeforeClick"
                          SourceName="btnRemove">
                <BeginStoryboard Storyboard="{StaticResource hideRootElementStoryboard}" />
            </EventTrigger>
            <Trigger SourceName="fieldValue"
                     Property="ContentTemplate"
                     Value="{x:Null}">
                <Setter TargetName="fieldRegion"
                        Property="Visibility"
                        Value="Collapsed" />
            </Trigger>
            <DataTrigger Binding="{Binding IsString}"
                             Value="True">
                <Setter TargetName="fieldValue"
                            Property="ContentTemplate"
                            Value="{StaticResource regularFieldDataTemplate}" /> 
            </DataTrigger>
            <DataTrigger Binding="{Binding IsParsable}"
                             Value="True">
                <Setter TargetName="fieldValue"
                            Property="ContentTemplate"
                            Value="{StaticResource regularFieldDataTemplate}" />
            </DataTrigger>
            <DataTrigger Binding="{Binding HasSubFields}"
                             Value="True">
                <Setter TargetName="fieldValue"
                            Property="ContentTemplate"
                            Value="{StaticResource complexFieldDataTemplate}" />
            </DataTrigger>
            <DataTrigger Binding="{Binding IsCollection}"
                             Value="True">
                <Setter TargetName="fieldValue"
                            Property="ContentTemplate"
                            Value="{StaticResource collectionFieldDataTemplate}" />
            </DataTrigger>
            <DataTrigger Binding="{Binding IsEnum}"
                             Value="True">
                <Setter TargetName="fieldValue"
                            Property="ContentTemplate"
                            Value="{StaticResource enumFieldDataTemplate}" />
            </DataTrigger>
            <DataTrigger Binding="{Binding IsBoolean}"
                             Value="True">
                <Setter TargetName="fieldValue"
                            Property="ContentTemplate"
                            Value="{StaticResource booleanFieldDataTemplate}" />
            </DataTrigger>
            <DataTrigger Binding="{Binding HasValueDataTemplate}"
                         Value="True">
                <Setter TargetName="fieldValue"
                            Property="ContentTemplate"
                            Value="{Binding ValueDataTemplate}" />                
            </DataTrigger>
            <DataTrigger Binding="{Binding IsNull}"
                             Value="True">
                <Setter TargetName="fieldValue"
                            Property="ContentTemplate"
                            Value="{StaticResource nullFieldDataTemplate}" />
            </DataTrigger>           
            <DataTrigger Binding="{Binding IsRemovable}"
                             Value="True">
                <Setter TargetName="btnRemove"
                            Property="Visibility"
                            Value="Visible" />
            </DataTrigger>
            <DataTrigger Binding="{Binding IsEditable}"
                             Value="False">
                <Setter TargetName="isNullRegion"
                            Property="Visibility"
                            Value="Collapsed" />
            </DataTrigger>
            <Trigger SourceName="typesToggle"
                     Property="IsChecked"
                     Value="True">
                <Setter TargetName="typesToggle"
                        Property="ToolTip"
                        Value="Collapse the compatible types region." />
                <Trigger.EnterActions>
                    <BeginStoryboard Storyboard="{StaticResource showTypesStoryboard}"/>
                </Trigger.EnterActions>
                <Trigger.ExitActions>
                    <BeginStoryboard Storyboard="{StaticResource hideTypesStoryboard}" />
                </Trigger.ExitActions>
            </Trigger>
        </DataTemplate.Triggers>
    </DataTemplate>

</ResourceDictionary>

By viewing downloads associated with this article you agree to the Terms of Service and the article's licence.

If a file you wish to view isn't highlighted, and is a text file (not binary), please let us know and we'll add colourisation support for it.

License

This article, along with any associated source code and files, is licensed under The Code Project Open License (CPOL)


Written By
Software Developer
Israel Israel
This member has not yet provided a Biography. Assume it's interesting and varied, and probably something to do with programming.

Comments and Discussions