Dynamically Aligned Controls In WPF

Introduction

 
Aligning controls on UI is a skill and a bit of a headache too.
 
One might go ahead and add a margin to every single control on-screen so that he/she can align them in a single line.
 
Here, they have to ensure that controls are aligned pixel by pixel. Being a developer, we might miss pixel or two & we won't even notice it.
 
IsSharedSizeScope is used to keep the control's alignment consistent between Panel's children.

Imagine a screen with hard-coded margins for every TextBox. It is always a bad practice, because as soon as the width of the main page or any content within page changes then all these hardcoded margins will stay unaffected of that change because of their hardcoded values.

Let me show you an example:
  1. <StackPanel x:Name="StackPanelOuter"    
  2.                Orientation="Vertical">    
  3.        <StackPanel x:Name="StackPanelInnerUserName"    
  4.                Orientation="Horizontal" >    
  5.            <Label x:Name="LabelUserName"        
  6.               Content="User Name:"     
  7.               Margin="0 10 0 0"/>    
  8.            <TextBox x:Name="TextBoxUserName"      
  9.                 Text="{Binding UserName}"    
  10.                 Height="20"        
  11.                 Width="150"       
  12.                 Margin="50 10 0 0"/>    
  13.        </StackPanel>    
  14.     
  15.        <StackPanel x:Name="StackPanelInnerPassword"    
  16.                Orientation="Horizontal">    
  17.            <Label x:Name="LabelPassword"         
  18.               Content="Password:" />    
  19.            <PasswordBox  x:Name="TextBoxPassword"        
  20.                 Height="20"        
  21.                 Width="150"    
  22.                 Margin="60 0 0 0" />    
  23.        </StackPanel>    
  24.     
  25.        <StackPanel x:Name="StackPanelInnerConfirmPassword"    
  26.                Orientation="Horizontal">    
  27.            <Label x:Name="LabelConfirmPassword"         
  28.               Content="Confirm Password:" />    
  29.            <PasswordBox x:Name="TextBoxConfirmPassword"        
  30.                 Height="20"        
  31.                 Width="150"     
  32.                 Margin="14 0 0 0" />    
  33.        </StackPanel>    
  34.           
  35.        <StackPanel x:Name="StackPanelInnerEmail"    
  36.                Orientation="Horizontal">    
  37.            <Label x:Name="LabelEmailId"         
  38.               Content="Email:" />    
  39.            <TextBox x:Name="TextBoxEmail"        
  40.                 Height="20"        
  41.                 Width="150"     
  42.                 Margin="80 0 0 0"/>    
  43.        </StackPanel>    
  44.            
  45.        <Button x:Name="ButtonLogin"        
  46.                Height="20"        
  47.                Width="100"        
  48.                Content="Register"        
  49.                HorizontalAlignment="Center"        
  50.                Margin="20 10 0 0"      
  51.                Command="{Binding RegisterButtonClicked}"    
  52.                />    
  53.     
  54.        <TextBlock x:Name="TextBlockMessage"    
  55.                   Visibility="{Binding IsButtonClicked, Converter={StaticResource BooleanToVisibilityConverter}}"    
  56.                   Text="{Binding UserName, StringFormat='User: {0} is successfully registered!'}"    
  57.                   HorizontalAlignment="Center"    
  58.                   Margin="20 8 0 0"      
  59.                   />    
  60.    </StackPanel>    
When you run the program, you will get an output as follows:
 
Dynamically Aligned Controls In WPF
 
Everything works smoothly, there are no required changes.
 
Now let's say that tomorrow our client wants to change 3 labels.
  1. Confirm Password to Password
  2. User Name to Name
  3. Email to Email Address
If we do that, our screen would look like this:
 
Dynamically Aligned Controls In WPF
 
Now the developer again has to change Margins of these TextBoxes, and if the client asks for more changes then again developer has to change margins as per new requirements.
 
We can't really blame client here, it is in their blood to change requirements constantly, So we have to be flexible enough to write code which is extensible with the new requirements.
 
So to overcome this problem of hard-coded margin we can use SharedSizeScope in WPF.
 
We should let XAML handle this situation, And we can simply use IsSharedSizeScope Attached property of Grid panel.

We have to change inner stack panels to grids then make Grid.IsSharedSizeScope="True" of outer a stackpanel.
 
Here I will show you:
  1. <Window x:Class="A.MainWindow"    
  2.         xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"    
  3.         xmlns:d="http://schemas.microsoft.com/expression/blend/2008"    
  4.         xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"    
  5.         xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"    
  6.         mc:Ignorable="d"    
  7.         Title="MainWindow" Height="220" Width="350"    
  8.         >    
  9.     <Window.Resources>    
  10.         <BooleanToVisibilityConverter x:Key="BooleanToVisibilityConverter"/>    
  11.     </Window.Resources>    
  12.     <StackPanel x:Name="StackPanelOuter"    
  13.                 Orientation="Vertical"    
  14.                 Grid.IsSharedSizeScope="True">    
  15.             
  16.         <Grid x:Name="GridPanelInnerUserName">    
  17.             <Grid.ColumnDefinitions>    
  18.                 <ColumnDefinition SharedSizeGroup="FirstColumn"/>    
  19.                 <ColumnDefinition SharedSizeGroup="SecondColumn"/>    
  20.             </Grid.ColumnDefinitions>    
  21.             <Label x:Name="LabelUserName"        
  22.                Content="User Name:"     
  23.                Margin="0 10 0 0"/>    
  24.             <TextBox x:Name="TextBoxUserName"      
  25.                  Text="{Binding UserName}"    
  26.                  Height="20"        
  27.                  Width="150"     
  28.                  Grid.Column="1"/>    
  29.         </Grid>    
  30.     
  31.         <Grid x:Name="GridPanelInnerPassword">    
  32.             <Grid.ColumnDefinitions>    
  33.                 <ColumnDefinition SharedSizeGroup="FirstColumn"/>    
  34.                 <ColumnDefinition SharedSizeGroup="SecondColumn"/>    
  35.             </Grid.ColumnDefinitions>    
  36.             <Label x:Name="LabelPassword"         
  37.                Content="Password:" />    
  38.             <PasswordBox  x:Name="TextBoxPassword"        
  39.                  Height="20"        
  40.                  Width="150"    
  41.                  Grid.Column="1"/>    
  42.         </Grid>    
  43.     
  44.         <Grid x:Name="GridPanelInnerConfirmPassword">    
  45.             <Grid.ColumnDefinitions>    
  46.                 <ColumnDefinition SharedSizeGroup="FirstColumn"/>    
  47.                 <ColumnDefinition SharedSizeGroup="SecondColumn"/>    
  48.             </Grid.ColumnDefinitions>    
  49.             <Label x:Name="LabelConfirmPassword"         
  50.                Content="Confirm Password:" />    
  51.             <PasswordBox x:Name="TextBoxConfirmPassword"        
  52.                  Height="20"        
  53.                  Width="150"    
  54.                  Grid.Column="1"/>    
  55.         </Grid>    
  56.     
  57.         <Grid x:Name="GridPanelInnerEmail">    
  58.             <Grid.ColumnDefinitions>    
  59.                 <ColumnDefinition SharedSizeGroup="FirstColumn"/>    
  60.                 <ColumnDefinition SharedSizeGroup="SecondColumn"/>    
  61.             </Grid.ColumnDefinitions>    
  62.             <Label x:Name="LabelEmailId"         
  63.                Content="Email:" />    
  64.             <TextBox x:Name="TextBoxEmail"        
  65.                  Height="20"        
  66.                  Width="150"     
  67.                  Grid.Column="1"/>    
  68.         </Grid>    
  69.             
  70.         <Button x:Name="ButtonLogin"        
  71.                 Height="20"        
  72.                 Width="100"        
  73.                 Content="Register"        
  74.                 HorizontalAlignment="Center"        
  75.                 Margin="20 10 0 0"      
  76.                 Command="{Binding RegisterButtonClicked}"    
  77.                 />    
  78.     
  79.         <TextBlock x:Name="TextBlockMessage"    
  80.                    Visibility="{Binding IsButtonClicked, Converter={StaticResource BooleanToVisibilityConverter}}"    
  81.                    Text="{Binding UserName, StringFormat='User: {0} is successfully registered!'}"    
  82.                    HorizontalAlignment="Center"    
  83.                    Margin="20 8 0 0"      
  84.                    />    
  85.     </StackPanel>    
  86. </Window>   
Output
 
Dynamically Aligned Controls In WPF
 
Wonderful. Aligned beautifully.
 

Conclusion

 
In this article, we learned how to align UI controls dynamically rather than hardcoding them.
 
We try to keep programming loosely coupled, so we should also focus on keeping UI loosely coupled to changes.
 
It is a good practice to use IsShareSizeScope while designing a page in WPF.
 
I hope you have learned something from the article.
 
Thank you for being a part of this, all the best!
 
And as always, Happy Coding!