Table of Contents
In my previous article, I discussed few basics of WPF applications, its architecture and internal structure to start with WPF. In this article of the series, I am going to discuss about the very basics of writing your first WPF application and how you can place controls in your window. This is very basic to anybody who wants to start with WPF. I will discuss most of them which are commonly used.
While building your application, the first thing you notice is a Window. Window
is the main class that interacts with the user and produces the lifetime of windows and dialog boxes. Like in normal windows application, it produces the object windows using the normal API. A window has two sections:
- Non-Client Area: which displays the outer boundary of the window, that we normally see with any windows. The main parts of them are Icon, System Menu, a title Bar and Border.
- Client part: This is the main part where the WPF controls will appear. You can customize this area using WPF.
WPF window is of 3 types:
Window
: This is basically a normal windowed application, where every control is placed within the same window. The window appears normally as I told you earlier. The Client area is fully customizable using XAML.
NavigationWindow
: This is a special type of window which is inherited from Windows, but with a Navigation panel top of it. So if you want to create an application that makes sense when used as Wizards, you might better go with NavigationWindow
. You can also customize the navigation panel yourself so that it goes with your own look and feel.
Page
: Almost similar to NavigationWindow
, the main difference is that, Page
can be opened in Web Browser as XBAP applications.
In the above image, you can see how Normal Window differs from NavigationWindow
. NavigationWindow
is very uncommon in general case, but might come in handy when you need special treatment for your application.
Let me discuss a bit on how you can use Pages in your application.
Pages are created to be used as a Page for the Same Window
. Navigating from one page to another is very simple. Page
class exposes an object of NavigationService
which you can use to navigate between pages. NavigationService
has few events like Navigating
, NavigationFailed
, NavigationProgress
, NavigationStopped
, etc. which you can use to show progressbar while the page is navigating. Methods like GoBack
, GoForward
and Navigate
are the best way to navigate from one page to another.
private void Button_Click(object sender, RoutedEventArgs e)
{
this.NavigationService.Navigate(new Uri("Page2.xaml", UriKind.Relative));
}
Thus you can see, rather than calling a new window for Page2.xaml, I just used NavigationService
to navigate from one page to another.
For further reference, you can use MSDN article:
WPF Window is derived from ContentControl
. Basically while working with Controls, you might come across a few types of controls which form the basis for any WPF control. A ContentControl
holds any arbitrary content. It may be a string
, an object
of any type or even a UIElement
like Button
, TextBox
, etc. In other words, A Content
is an arbitrary element that might be placed inside a container. Let's take a look at them one by one:
- ContentControl: A
ContentControl
holds a single child Content. As Window is derived from ContentControl
, every window can have only a single Child. For Example: Window, Button
, etc.
- HeaderedContentControl: It is basically the same as
ContentControl
, but additionally there is a header part which shows the Header for the Content
. For instance, a GroupBox, Expander
are HeaderedContentControl
.
- ItemsControl: The content of
ItemsControl
is multiple. Therefore, you can place many arbitrary elements inside an ItemsControl
. For instance : ListBox, ListView
are examples of ItemsControl
.
- HeaderedItemsControl: Here, each
Collection
has a specific header content of it. A HeaderedItemsControl
is a complex element which holds each content with a specific header. TreeView
is an example of HeaderedItemsControl
.
The above picture shows the distinction between different ContentControls
. Each contentControl
contains a Content
property which stores the inner content. In your XAML, you can specify using Content
attribute, or you can directly write the Content
inside the Tag
. Thus,
<Button Content="This is a Button" />
is the same as:
<Button>This is a Button</Button>
XAML parser parses the element written inside the XAML ContentControl
Tag as Content
.
Alignment
, Margin
and padding
are the 3 most important properties that you should always consider for every UIElement
. Before going further with the containers, you need to know about them.
Alignment: Alignment
determines how the child elements will be placed within the allocated space of the Parent Element. In other words, it determines the position on the space it was provided. There are two types of Alignment
:
- HorizontalAlignment: It has 4 possible values -
Left, Right, Center and Stretch. Stretch
is the default value of any HorizontalAlignment
.
- VerticalAlignment: It has 4 possible Values -
Top, Center, Bottom and Stretch. Stretch
is the default value of any VerticalAlignment
.
Margin: It determines the distance between one control to the boundary of the cell where it is placed. It can be uniform when specified as a integer value, or you can use TypeConverter
to specify the value of all its sides. For instance:
Margin = "20"
means Left=20
, Top=20
, Right=20
and Bottom=20
.
You can also specify as
Margin="20,10,0,10"
which means Left =20
, Top=10
, Right=0
, and Bottom=10
.
<Button Margin="0,10,0,10">Button 1</Button>
<Button Margin="0,10,0,10">Button 2</Button>
<Button Margin="0,10,0,10">Button 3</Button>
Padding: Padding is present in few of the controls that help in enlarging the size of the control by its value. So it is almost similar, but Margin places space outside the boundary of the control whereas padding places it inside the boundary of the control.
<Button Padding="0,10,0,10">Button 1</Button>
<Button Padding="0,10,0,10">Button 2</Button>
<Button Padding="0,10,0,10">Button 3</Button>
Each of the Margin
and padding
takes an object of Thickness
.
Button bb = new Button();
bb.Margin = new Thickness(20);
bb.Padding = new Thickness(10, 20, 30, 10);
this.MyGrid.Children.Add(bb);
Another important part of any WPF application is to define the Layout of the screen. WPF introduces a number of Panels each are derived from abstract
class Panel
. You can also derive Panel
to define your custom Panel
if you wish. We will look into how you can define your own CustomPanel
later. Now let's discuss about all the basic Panel
s supported by WPF.
Panel
is the abstract
class from which each and every panel
is derived from. So each layout element that we will talk about is derived from the Panel
class and has few properties which I should discuss before talking about Concrete
objects.
Z-Index
: It determines the index of the UIElement
which overlapped with another element. ZIndex
is an Attached property which determines the index in layered elements. One with higher Zindex
will show above the other.
InternalChildren
: This is the basic UIElementCollection
element which is exposed using Children
property. When defining your custom Panel
, you can use it to get the elements.
Background
: This is also present for any panel
element, which specifies the Background
Color for the Panel
.
To create a custom Panel
yourself, you need to override two methods:
MeasureOverride: This method is called whenever an element is added on the Panel
. It takes the availableSize
as input and returns the DesiredSize
for the element passed. You need to calculate the Size
so that it could be placed accordingly in desired size.
ArrangeOverride: This method is called to determine the arrangement of the Element
. It will be called once for the whole panel, when Layout
is created and the final desired size for the panel is returned from it. It will again be called when Layout
is updated.
You can try the MSDN sample for more details of creating Custom Panel
:
Grid
is the most basic layout panel which forms a graph in the whole frame. Grid
forms a Table
which you can address using Row and Column. You can specify the RowDefination
and ColumnDefination
for Rows and columns of the Grid
. You can specify the height of a Row and Width of a Column easily using RowDefinations
and ColumnDefinations
.
As I already said, Height and Width of each Cell in a Grid can be specified using RowDefinations
and ColumnDefinations
, the sizing can be specified in more than one way. The Sizing can be:
- Auto: This is the default Sizing which is determined by the element you place within the Control.
- * (Star): When you use star, it means the measurement will be done using ratio. 2* means double of 1*. So if you want to make two columns in 2:1 ratio, you mention the width as 2* and 1*.
- Absolute: You can also specify the absolute value of the height and width. Means if you specify 100 as height, it will take it accordingly.
From my own experience, it is better practice to use MinHeight
and MaxWidth
instead of Width and Height when you want your layout to be strict and doesn't depend on the child elements.
In the sample application, I have created a Grid
with 3X3 matrix. You can use TextBoxes
specified on the top to position the Box in Row
and Columns
Dynamically.
<Grid Grid.Row="1">
<Grid.RowDefinitions>
<RowDefinition />
<RowDefinition />
<RowDefinition />
</Grid.RowDefinitions>
<Grid.ColumnDefinitions>
<ColumnDefinition/>
<ColumnDefinition/>
<ColumnDefinition/>
</Grid.ColumnDefinitions>
<Border Background="BurlyWood" x:Name="brdElement">
<TextBlock x:Name="tbElement" Text="Row 0, Column 0"
HorizontalAlignment="Center" VerticalAlignment="Center"/>
</Border>
</Grid>
This will place the Border in 0,0 column of the 3X3 Table.
The very next control that I must start with is a StackPanel
. StackPanel
is a container where all the child elements are placed in stacks, that means one after another, so that no one overlaps on one another.
Stackpanel
places controls based on PositiveInfinity
, which means the size that it can take in positive direction. The main property of StackPanel
is its Orientation
. There are two Orientation
s supported.
Vertical: This is the default orientation for StackPanel
where the child items are placed vertically one after another from top to bottom.
Horizontal: Here the items are placed from left to Right one after another.
<StackPanel x:Name="spMain" Orientation="Horizontal">
<Border Background="Brown" Padding="50"></Border>
<Border Background="Green" Padding="50" />
</StackPanel>
WrapPanel
is almost similar to StackPanel
, but it produces a newLine
when it reaches the edge of the panel. Thus WrapPanel
has additional flexibility to wrap elements when space matters. Another difference is, WrapPanel
always determines the size based on the size of the content rather than PositiveInfinity
as of StackPanel
.
So if you resized the window, the content will be automatically wrapped to the new line. WrapPanel
also exposes Orientation
Property as StackPanel
.
<WrapPanel x:Name="wpMain" Grid.Row="1">
<Border Background="Brown" Padding="30"/>
<Border Background="Green" Padding="30" />
<Border Background="Brown" Padding="30" />
<Border Background="Green" Padding="30" />
</WrapPanel>
DockPanel
is the most widely used control to determine the layout of an application. It uses DockPanel.Dock
attached property to determine the position of the element. The Dock
element when Top or Bottom, will make the element appear Top or Bottom of each other, and when its Left and right, it is left and right of each other.
In case of DockPanel
, if the height and width of the element placed within it is not specified, it takes the size of the area it is provided with.
<DockPanel>
<Border Background="Aqua" DockPanel.Dock="Top">
<TextBlock Text="Dock:Top" />
</Border>
<Border Background="Red" DockPanel.Dock="Bottom">
<TextBlock Text="Dock:Bottom" />
</Border>
<Border Background="Orange" DockPanel.Dock="Left">
<TextBlock HorizontalAlignment="Center" VerticalAlignment="Center"
Text="Dock:Left" />
</Border>
<Border Background="Blue" DockPanel.Dock="Left">
<TextBlock HorizontalAlignment="Center" VerticalAlignment="Center"
Text="Dock:Left" />
</Border>
<Border Background="Aqua" DockPanel.Dock="Bottom">
<TextBlock Text="Dock:Top" />
</Border>
<Border Background="Aquamarine" DockPanel.Dock="Top">
<TextBlock Text="Dock:Top" />
</Border>
<Border Background="BurlyWood" DockPanel.Dock="Right">
<TextBlock HorizontalAlignment="Center" VerticalAlignment="Center"
Text="Dock:Right" />
</Border>
<Border Background="Coral" DockPanel.Dock="Right">
<TextBlock HorizontalAlignment="Center" VerticalAlignment="Center"
Text="Dock:Right" />
</Border>
<Border Background="Cornsilk" >
<TextBlock HorizontalAlignment="Center" VerticalAlignment="Center"
Text="Remaining Fill" />
</Border>
</DockPanel>
So, you can see that you need to explicitly mention Dock
property of each individual element to make it appear accordingly. The sequence of declaration also plays a vital role in case of DockPanel
s. If you mention two elements in a row with DockPanel.Dock=Top
, that means the two individual elements will appear as Vertically Oriented StackPanel
.
A property called LastChildFill
makes the remaining space to be filled with undocked element. You can see in the figure the last element is filled with the entire space left. You can make it false
if you don't need it.
If you want to do this with code, you need to use DockPanel.SetDock
.
WPF introduces a special type of panel which Virtualize
s its content when the content is bound to Data elements. The word virtualize means the content will only be produced when the element is visible to the screen. Thus the performance will be improved a lot.
<ListBox x:Name="lstElements" VirtualizingStackPanel.IsVirtualizing="True"
VirtualizingStackPanel.VirtualizationMode="Recycling" ItemsSource="{Binding}"/>
Now from code if you write:
private void Window_Loaded(object sender, RoutedEventArgs e)
{
ObservableCollection<int> obs = new ObservableCollection<int>();
Random rnd = new Random(1000);
for (int i = 0; i < 100000; i++)
obs.Add(rnd.Next());
this.lstElements.DataContext = obs;
}</int></int>
This will produce 100000 elements to be added over the ListBox
. If you use VirtualizingStackPanel.IsVirtualizing=True
, the content will appear instantly, as it doesn't require to produce all the ListBoxItem
elements from the first time. The application will hang if you make IsVirtualizing
=false
as creating 100000 ListboxItem
elements takes a lot of time.
VirtualizationMode
can be of two types:
- Standard: It means the Item will be created when the
ScrollViewer
is scrolled.
- Recycling: It means the item will be replaced with Data when
ScrollViewer
is scrolled.
Canvas
is a special Layout
panel which positions elements with absolute position means using x and y co-ordinates. When used within a Canvas
, elements are not restricted to anything. It can be overlapped when the position intersects with other controls. Each element is drawn based on the sequence of its declaration. You can easily use Panel.ZIndex
to make this declaration unspecific.
Canvas
doesn't employ any restriction to its elements. So the width and height of individual elements is very necessary to be specified. You can use Canvas.Left, Canvas.Right, Canvas.Top
and Canvas.Bottom
to specify the co-ordinates. The only thing you need to remember is that Canvas.Left
and Canvas.Right
are the same, but it determines the start point of co-ordinate system from the extreme left or extreme right.
<Canvas>
<Border Canvas.Top="20" Canvas.Left="25" Background="Bisque" Width="30"
Height="25" />
<Border Canvas.Top="20" Canvas.Right="25" Background="Green" Width="30"
Height="25" />
<Border Canvas.Bottom="20" Canvas.Right="25"
Background="Black" Width="30" Height="25" />
<Border Canvas.Bottom="20" Canvas.Left="25" Background="IndianRed"
Width="30" Height="25" />
<Ellipse Fill="DarkGray" Canvas.Left="100" Canvas.Top="130" Width="100"
Height="80"></Ellipse>
<Ellipse Fill="DarkCyan" Canvas.Left="100" Canvas.Top="80" Width="100"
Height="80"></Ellipse>
<Ellipse Fill="DarkSalmon" Canvas.Left="140" Canvas.Top="100" Width="100"
Height="80" />
</Canvas>
Here you can see the Border
elements even though placed in the same area, the Canvas
properties change the Co-ordinate system and thus place on the four sides of the window.
The ellipses are Overlapped between one another in the same sequence as it is specified.
UniformGrid
is a special control which adjusts its elements uniformly. If you want your Rows and Columns to be uniform in your grid, you can use UniformGrid
instead of Normal Grid declaration.
In case of UniformGrid
, the child elements will always be of the same size.
<UniformGrid Columns="2" Rows="3">
<Border Background="Red" />
<Border Background="Green" />
<Border Background="Blue" />
<Border Background="Yellow" />
<Border Background="DarkGoldenrod" />
<Border Background="DarkKhaki" />
</UniformGrid>
So its 3X2 Grid and all elements are placed according to the sequence it is specified.
It is often happens that the elements go outside of the Display area. In that case, ScrollViewer
places an Automatic Scrollbars
which can be used to view the area outside the bounds. ScrollViewer
encapsulates the ScrollBar
s within it and displays it whenever it is required. As the ScrollViewer
implements IScrollInfo
in the main scrolling area inside the scrollviewer
. ScrollViewer
also responds to mouse and keyboard commands.
<ScrollViewer HorizontalScrollBarVisibility="Auto">
<StackPanel VerticalAlignment="Top" HorizontalAlignment="Left">
<TextBlock TextWrapping="Wrap" Margin="0,0,0,20">Scrolling is
enabled when it is necessary. Resize the window, making it larger
and smaller.</TextBlock>
<Rectangle Fill="Honeydew" Width="500" Height="500"></Rectangle>
</StackPanel>
</ScrollViewer>
The CanContentScroll
property of a ScrollViewer
determines whether the elements would be scrollable or not. HorizontallScrollBarVisibility
and VerticalScrollBarVisibility
make the scrollbar appear accordingly. The default behaviour is Auto
which means the scrollbar
will appear only when it is required.
GroupBox
allows to group content with a custom header in it. This is the same as GroupBox
we know in windows. The Header
property takes the text
element which is placed as header
of the GroupBox
. As GroupBox
is a ContentControl
, it can have only a single element in the body. So you need to use Panel
s to add children within it.
<GroupBox Header="This goes to Header" Margin="50">
<StackPanel>
<Button Content="First Element"/>
<Button Content="Second Element" />
</StackPanel>
</GroupBox>
Expander
is same as groupbox
but has extra facility to expand content. It is also derived form HeaderedContentControl
and thus has one single content within it. The IsExpanded
property determines if the panel is expanded or not.
ExpandDirection
is used to make the content expanded behaviour. It has four Directions, Down, Up, Right and Left
. You can use them to change the Expanded direction of the content.
<Expander Header="This goes to Header" Margin="50" IsExpanded="True"
ExpandDirection="Down">
<StackPanel>
<Button Content="First Element"/>
<Button Content="Second Element" />
</StackPanel>
</Expander>
ViewBox
is a special WPF control which stretches or scales the contents of the elements. This comes in very handy to allow anchoring of the elements, as in case of ViewBox
the controls will never change its position, rather the whole content will be stretched or shrunk.
The Stretch
property of ViewBox
can have four properties:
Fill
: Fills the content and also makes the Aspect Ratio intact.
None
: Stretch behaviour will not be set.
UniformToFill
: Fills the element uniformly, and changes the Aspect Ratio.
Uniform
: Uniformly enlarges the content.
The stretchDirection
can be specified as Both, DownOnly and UpOnly
.
<Viewbox Stretch="None" StretchDirection="Both" >
<Grid>
<TextBox Text="This is a content" FontWeight="Bold" FontSize="30" />
</Grid>
</Viewbox>
Popup
is a special control that is used to create floating window over the actual window. Popup
is a control that is rendered always on Top of the window. Popup
is used to display quick elements whenever it is needed without altering the whole window.
A Popup
control can be positioned using properties called PlacementTarget, PlacementRectangle, Placement, HorizontalOffset, VerticalOffset
, etc. A popup
is a window outside the bounds of the existing WPF window, and thus it can be moved outside the whole content area of the XAML. WPF popup
control supports few animation like Fade, Scroll, Slide
, etc. which you can apply to it using PopupAnimation
property. A WPF Popup
supports transparency when AllowsTransparency
is set to true
.
<ToggleButton IsChecked="{Binding ElementName=pup, Path=IsOpen}"
Content="Open Popup" Margin="100" />
<Popup Placement="Bottom" AllowsTransparency="True"
PopupAnimation="Fade" x:Name="pup" VerticalOffset="-100">
<StackPanel>
<TextBlock Name="McTextBlock" Background="Black" Foreground="White" >
This is popup text
</TextBlock>
<Button Content="This is button on a Popup" />
</StackPanel>
</Popup>
Here, the Popup
will be displayed when the ToggleButton
is clicked as IsChecked
is bound to IsOpen
of Popup
. When IsOpen
is true
, the popup
will be displayed.
Another most powerful control which is introduced with WPF is InkCanvas
. This control allows you to draw over the Canvas
and ultimately get the image bytes saved. It is very powerful as you can easily get the strokes drawn over the canvas as Objects
.
Just place an InkCanvas
on the WPF Window and you will find that you can draw over the screen. The EditingMode
gives you few editing mode for the InkCanvas
. We will discuss with this control later in another article.
<StackPanel>
<InkCanvas Height="200" x:Name="icBox">
</InkCanvas>
<RadioButton GroupName="mode" Checked="Pen_Checked" Content="Pen"/>
<RadioButton GroupName="mode" Checked="Erase_Checked"
Content="Eraser By Point" />
<RadioButton GroupName="mode" Checked="EraseByStroke_Checked"
Content="Eraser By Stroke" />
</StackPanel>
Transformation is one of the important features that is introduced with WPF. Transformation allows to map element from one co-ordinate space to another co-ordinate space. The transformation is mapped using Transformation Matrix in 2D space. By manipulating the matrix values, you can Transform elements to Rotate, Scale, Skew and Translate
.
Transformation is of 4 basic types:
- RotateTranform: Rotates an element by a specified
Angle
. You can specify the Angle of Rotation and the element will be rotated in 2D space.
- ScaleTransform:
ScaleTransform
allows you to scale the element means increase/decrease the size of the element in the 2D space.
- SkewTransform: It skews the element by specified angle. Skew stretches elements in a
NonUniform
manner and thus the element will be transformed so as in 3D space.
- TranslateTransform: This transformation will make the element move by a specified X and Y co-ordinates.
There is also a provision to apply more than one Transformation using TransformGroup
or MatrixTransform
. TransformGroup
allows you to specify more than one Transformation
to be applied on the single element and thus gives you a hybrid Transformation
for your control.
<TextBlock FontWeight="Bold" FontSize="20" Text="This is Text" Margin="20">
<TextBlock.RenderTransform>
<TransformGroup>
<RotateTransform Angle="20" />
<SkewTransform AngleX="10" AngleY="10" />
<TranslateTransform X="15" Y="19"/>
<ScaleTransform ScaleX="2" ScaleY="1" />
</TransformGroup>
</TextBlock.RenderTransform>
</TextBlock>
This is the second part of the series. I hope you like it. Please don't forget to write your feedback. I have skipped few things intentionally (like details of InkCanvas
) as it would make the article very long. I will discuss that in a separate article.
Thanks for reading.
- 28th December, 2010: Initial post