Building a Mashup UI – Margins and alignment explained

I’ve spent the last few months working with the UX 3.0, Infor’s new design guide lines for all Infor 10x applications, applying it for Smart Office. It has given me great insight in how I like to build a UI. I’m talking about what controls to use and why, how to think and how to align controls. In this post I’ll focus on Mashup development but this approach can be used in any WPF application. I highlight some issues and how to solve them.

I’ve done a previous post Design tips for Mashups where I did some example layouts and added a checklist. Some of the stuff is similar as I continue to nag about margins and alignment. I’ll review my other post and possibly do some changes now when I know what will happen in future versions with Infor UX3.0. The investment you have done in Mashups will still be intact but some differences can’t be avoided as Mashups developed for 10.0.5 and earlier versions get deployed in Smart Office 10.1.

As part of the implementation of UX3.0 I’ve been around most of Smart Office and I can clearly see different styles when it comes to building the UI.

1. The Dockpanel. For some reason people seem to love the DockPanel. I hate it. Especially since I have seen it used with DockPanel.Dock=”Bottom” which will reverse the order the controls were added. Only use it if you plan to set LastChildFill to true and you have this special requirement that you know only the DockPanel can solve.

2. The StackPanel. Arranges child elements into a single row or column that can be oriented horizontally or vertically. This is similar to the DockPanel you just add stuff to it, but, and this is an important but. It works best with elements that has a width and height set. So it does not work well for ListViews or TextBoxes or other kinds of controls that want to stretch or has the potential to grow depending on its content (data).

MSDN compares these two controls and uses the following as an example.


    <Grid.ColumnDefinitions>
      <ColumnDefinition />
    </Grid.ColumnDefinitions>
    <Grid.RowDefinitions>
      <RowDefinition />
      <RowDefinition />
    </Grid.RowDefinitions>

    <DockPanel Grid.Column="0" Grid.Row="0">
      <Image Source="C:\Users\kportillo\Pictures\pippi.jpg" />
      <Image Source="C:\Users\kportillo\Pictures\pippi.jpg" />
      <Image Source="C:\Users\kportillo\Pictures\pippi.jpg" Stretch="Fill"/>
    </DockPanel>

    <StackPanel Grid.Column="0" Grid.Row="1"  Orientation="Horizontal">
      <Image Source="C:\Users\kportillo\Pictures\pippi.jpg" />
      <Image Source="C:\Users\kportillo\Pictures\pippi.jpg" />
      <Image Source="C:\Users\kportillo\Pictures\pippi.jpg" Stretch="Fill"/>
    </StackPanel>
    </Grid>

It looks like this:

stackpanelvsdockpanel

The StackPanel is easy to understand and does not fill out the remaining space.

3. Strive for a liquid layout where lists are given more room as you resize the window. That type of resize behavior, plus the possibility to align separate forms can only be achieved with a Grid. There is no coincident that the default control when you create a new Mashup is a Grid. The Grid gives you the best control. It’s ok to use StackPanels when you want to stack stuff like those Ok and Cancel buttons or that row with multiple buttons. But when you create a UI like a form with a list, tabs, labels and input fields you should use a Grid.

4. Use extra rows and columns as margins to align controls and make sure margins are the same. There are two ways to do this. The pain of working with a Grid is when you realize you want to add something in the beginning and then you have to re-order all the following Grid.Row and/or Grid.Column values. A solution can be to not have one big grid with 20 rows but to split the page in 2 rows to start with and then have two separate grids in that parent grid.

If you want to use Auto size for the column with labels (which is a nice approach) you should also use the “SharedSizeGroup” and set that group name on a column. Then you set the Grid.IsSharedSizeScope=”True” property on the parent grid. It allows you to mark columns in different grids to have the same calculated width across a page.

If you haven’t heard of this concept you should read up on it – and start to use it. StackOverflow or this one.

5. An alternative to using the extra rows and columns is to always set Margin and in some case Padding as well. Margin is the distance to other controls from the border of the control whereas padding represents the control’s internal spacing characteristics. They go hand in hand and in the case of a Label there is no way to know if the distance you observe to other controls comes from a Margin or a Padding. When we did the ReStyling we noticed that the Label had a 5 margin padding simply because our template did not set OverridesDefaultStyle=true. This has changed in 10.1.

6. Set Margin on all elements. If you want to be sure to be able to align stuff you need to set your own Margin. If the control is a Label you need to set Padding as well.

Let me explain why. In Smart Office we have global styles. Global styles define our look of a TextBox or Button or whatever the control might be. Those styles might have margins. If you use Mashups then the Mashup will be injected with special styles that are only valid for Mashups. The one that need special attention are TextBox and TextBlock.

The styles look like this:

<Style TargetType="{x:Type TextBlock}" BasedOn="{StaticResource {x:Type TextBlock}}">
  <Setter Property="Margin" Value="5" />
</Style>

<Style TargetType="{x:Type TextBox}" BasedOn="{StaticResource {x:Type TextBox}}">
  <Setter Property="Margin" Value="5" />
  <Setter Property="MinWidth" Value="120" />
</Style>

It’s these styles that makes the following common construction of label – textbox – button look good.

SearchField

Ok maybe not good – but decent. It is still too close to the window title bar.

Search field entry. Notice that there are no margins specified but still there are margins in the image.

  <StackPanel Orientation="Horizontal" Margin="8" VerticalAlignment="Top" Grid.Column="0" Grid.Row="0">
    <TextBlock Text="Search query:" VerticalAlignment="Center" />

    <TextBox Name="TextBoxQuery" Width="200" VerticalAlignment="Center" HorizontalAlignment="Left" MaxLength="256" />

    <Button Content="Search" HorizontalAlignment="Left" VerticalAlignment="Center" Style="{DynamicResource styleButtonPrimary}" IsDefault="True">
      <Button.CommandParameter>
        <mashup:Event TargetName="CustomerList" SourceEventName="Search">
          <mashup:Parameter SourceKey="Query" Value="{Binding ElementName=TextBoxQuery, Path=Text}" />
        </mashup:Event>
      </Button.CommandParameter>
    </Button>
  </StackPanel>

The intentions with the default margins were that it should be easy to build a Mashup UI. So the intentions were good but on the other hand I have not seem a single Mashup control developed outside of our team that is 100% correct when it comes to margin, alignment (and resize behavior). Resize behavior is hard so we will not go into detail. Just check item 9.

The Margin Example

Here comes another example where I want you to look at the margins.

ControlTest

There are so many errors that I don’t know were to start. I’ve added a few controls but without any effort to align them.
Only the controls with implicit margins have any margin. The fact that the radio button is center aligned by default has to be a style bug in Smart Office. Crazy to have center as default.
Button is off. All labels appear to be aligned.

We can compare this look with the Microsoft default look using Kaxaml. I had to remove Smart Office controls – so these are pure Microsoft controls.

kaxaml

This UI looks slightly better but still not good. One textbox is higher because of the 5 px padding on the Label.
Not all labels are aligned because some of them are labels and other textboxes.

Conclusion

You have to think about alignment, margins and padding. You need to set them and understand them.

7. Set VerticalAlignment. In most cases you want vertical alignment to be set to center.

I’ll show you what alignment, margins and padding can do for this mess:

What I did:
– Added margin on Grid
– Set VerticalAlignment=”Center” Padding=”0″ Margin=”0″ on all Labels and VerticalAlignment=”Center” Margin=”0″ on all other controls
– Set HorizontalAlignment=”Left” on radio buttons

Now I have this compact format (but aligned!)

Step1

It may seem like the button have some margin but it is just the space for the green focus border.
There is no space between the labels and the textboxes.

We need to choose one approach:
a) Add margin row / columns
b) Add Margins. The controls in the first column should have a top margin and the controls in the next column should have a left,top margin. When using Margins I find it easy to always have this rule that the control should have a top margin for vertical space. The first control shouldn’t have any margin – that margin should be set on the parent – in this case the grid (which we already have). As for the different TextBoxes, CheckBoxes and RadioButtons they should have a top margin to be vertically aligned and a left margin so they have a nice margin to the label.

Since I’m changing an existing panel I’ll use margins.

Some more fixing and here is the result.

final

Some of you have been looking at the DatePicker and thinking that it looks disabled. Extra credit to you!
It’s just that this particular state isn’t used by M3 or S3. To allow it to be editable you have to set CanEdit=True and then the border gets Black. This allows you to be able to write a date in the control as well.

Here is the final XAML:

<Grid xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation" xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" xmlns:ui="clr-namespace:Mango.UI.Controls;assembly=Mango.UI" xmlns:mashup="clr-namespace:Mango.UI.Services.Mashup;assembly=Mango.UI">
  <Grid.Resources>
  </Grid.Resources>

  <Grid.ColumnDefinitions>
    <ColumnDefinition Width="*" />
  </Grid.ColumnDefinitions>
  <Grid.RowDefinitions>
    <RowDefinition Height="*" />
    <RowDefinition Height="Auto" />
  </Grid.RowDefinitions>

  <Grid Margin="8">
        <Grid.ColumnDefinitions>
          <ColumnDefinition Width="Auto" />
          <ColumnDefinition Width="200" />
          <ColumnDefinition Width="*" />
        </Grid.ColumnDefinitions>
        <Grid.RowDefinitions>
          <RowDefinition Height="Auto" />
          <RowDefinition Height="Auto" />
          <RowDefinition Height="Auto" />
          <RowDefinition Height="Auto" />
          <RowDefinition Height="Auto" />
          <RowDefinition Height="Auto" />
          <RowDefinition Height="Auto" />
          <RowDefinition Height="Auto" />
          <RowDefinition Height="Auto" />
          <RowDefinition Height="Auto" />
          <RowDefinition Height="Auto" />
          <RowDefinition Height="Auto" />
        </Grid.RowDefinitions>
        <Label VerticalAlignment="Center" Padding="0" Margin="0,5,0,0" Content="Grid with Auto Header - styleGroupBoxHeaderMashup" Grid.ColumnSpan="2" Style="{DynamicResource styleGroupBoxHeaderMashup}" />

        <Label Grid.Row="1" Grid.Column="0" VerticalAlignment="Center" Padding="0" Margin="0,5,0,0" Content="Label caption:" />
        <TextBox VerticalAlignment="Center" Margin="10,5,0,0" Grid.Row="1" Grid.Column="1" />
        
        <Button Grid.Row="2" Grid.Column="1" Margin="0,5,0,0" Content="Browse" HorizontalAlignment="Right" />

        <TextBlock VerticalAlignment="Center" Margin="0,5,0,0" Grid.Row="3" Text="Text block:" />
        <TextBox VerticalAlignment="Center" Margin="10,5,0,0" Grid.Row="3" Grid.Column="1" />

        <Label VerticalAlignment="Center" Padding="0" Margin="0,5,0,0" Grid.Row="4" Content="Label numeric text:" />
        <ui:NumericTextBox VerticalAlignment="Center" Margin="10,5,0,0" Grid.Row="4" Grid.Column="1" />

        <TextBlock VerticalAlignment="Center" Margin="0,5,0,0" Grid.Row="5" Text="Text block:" />
        <ui:DatePicker BorderBrush="Black" VerticalAlignment="Center" Margin="10,5,0,0" CanEdit="true" Grid.Row="5" Grid.Column="1" />

        <TextBlock VerticalAlignment="Center" Margin="0,5,0,0" Grid.Row="6" Text="Text block:" />
        <CheckBox VerticalAlignment="Center" Margin="10,5,0,0" Grid.Row="6" Grid.Column="1" />

        <Label VerticalAlignment="Center" Padding="0" Margin="0,5,0,0" Grid.Row="7" Content="Label header:" />
        <CheckBox VerticalAlignment="Center" Margin="10,5,0,0" Grid.Row="7" Grid.Column="1" />

        <CheckBox VerticalAlignment="Center" Margin="10,5,0,0" Grid.Row="8" Content="Use real header" IsChecked="True" Grid.Column="1" />

        <CheckBox VerticalAlignment="Center" Margin="10,5,0,0" Grid.Row="9" Content="Use ipsum dipsum" IsChecked="True" Grid.Column="1" />
        
        <RadioButton VerticalAlignment="Center" Margin="10,5,0,0" Grid.Row="10" Grid.Column="1" Content="Radiobutton" HorizontalAlignment="Left" />
        <RadioButton VerticalAlignment="Center" Margin="10,5,0,0" Grid.Row="11" Grid.Column="1" Content="Radiobutton" HorizontalAlignment="Left" />

      </Grid>
  <ui:StatusBar Name="StatusBar" Grid.Row="1" Grid.Column="0" />
</Grid>

8. Back to the Grid. Auto columns are great for fixed columns but there should always be at least one last column / row that is star sized. If you want to have list and details or split the screen use star sized columns for that and auto for the rest. A row should for example only have a height if it is used as a Margin. In other cases when you for example have a ListView – set the Height on the ListView and not the Row that the ListView is in. It makes the layout easier to change.

9. If you have ScrollViewer (and in some cases you should – like a very big mashup that will not fit on all screens) you need to make sure everything has a MaxWidth / MaxHeight and take special care or lists and controls that you would expect to strech, becuase when you put them in ScrollViewer it meens that they have unlimited space availble.

I also want to mention the WrapPanel, which positions child elements in sequential position from left to right, breaking content to the next line at the edge of the containing box. Subsequent ordering happens sequentially from top to bottom or from right to left, depending on the value of the orientation property. It’s a great panel when you want to break content to the next line like in a special card view. But not really a common scenario. The UniformGrid is another specialised panel that provides a way to arrange content in a grid where all the cells in the grid have the same size.

The final result has a lot more XAML than what we started out with. So if we set default margins or mostly vertical alignment to center why not use those values as defaults and save us all some time? Becuase it is impossible to know what should be default. We can’t set default margins becuase it will not work in all situations and we will not set it on all controls and then you would have to be able to compensate for an implicit value that you can’t see. The price for the possibility to be able to get it right is that you make an active decision on how to handle Alignment and Margin so that your Mashups looks great in existing and coming versions of Smart Office.

Other resources
Panel overview