Share via


Moving from Winforms to WPF

This article is for the WinForms developer learning WPF.

It introduces important new concepts, and lists differences, pitfalls & work-arounds.

Contributed to by those that have [or are still] taking that same journey.

If you are on that journey and know of anything that is missing, please add it to the article.

Or if your still searching, please return and add what you didn't find here! For the next weary traveler ;)

Leave "tutorial style specifics" out, but link to the best resources you found helped you.

Introduction

Moving from Windows Forms to Windows Presentation Foundation can be a daunting and often confusing journey for even the most skilled developer. In fact the more hard core you are at Windows Forms, the more set in your ways you are in how to achieve the desired result. At first, many controls and events seem so similar that a WinForm developer can get started straight away, and produce results very quickly. However, it is then common to hit a brick wall, or unknown exception that, once researched, the answer is unfortunately to tear it all down and code it another way, the WPF way.

The Differences in Controls

The first resources you should read and bookmark are these essential resources from MSDN:

Filling the Control Gaps

You will see that some controls do not have a direct equivalent in WPF. However, there are many great solutions provided.

Here are the main, non-commercial sources:

Other Shared Feature Mapping

This is a list of other aspects of development that differ between WinForms and WPF.

New Concepts To Understand

Layout

Windows Forms was all about rigidly positioned Location.X and Location.Y.

WPF is all about flexible, adaptable Grids and Panels.

In WinForms you can use containers like SplitContainer and TableLayoutPanel to group and dock controls into areas of the window. Also, a few controls use the AutoSize property. But when you run the application and stretch the window out, the limitations are obvious. Which is why some WinForm developers just set Minimum and Maximum form size to the same.

When Windows Forms was conceived, there was not such a demand for different screen sizes and resolutions. With today's vast array of form factors and platforms, an application needs to adapt to many different screens and orientations. That is why WPF is much more powerful than WinForms. Controls are laid out in flexible containers, and layout changes can be responsive to change, like orientation from Landscape to Portrait may mean moving some controls from an outer column to a bottom row. This is something you must consider very early in design, or it will come back to bite you in production.

When picking up WPF, a WinForm developer will often adopt the Canvas as their first container, as controls within it have Canvas.Left and Canvas.Top to act as X and Y. Many early and basic tutorials use Canvas frequently. However, the WPF Canvas is rarely needed in most applications unless you have good reason to need absolute positioning, like a graph, chessboard or drawing application (see InkCanvas). The Grid control is almost always the first choice for most experienced WPF'ers. With the Grid you can chop up the page like a TableLayoutPanel, and it stretches in the same way. What is very different is how the Children (the controls contained within each cell) react to change.

Layout Basics

FPF child controls use HorizontalAlignment, VerticalAlignment and Margin. Parent containers usually have a Padding property. These combined make the application adaptable and attractive through a much better range of window sizes. A child control, can still be 'absolutely' positioned by setting HorizontalAlignment=Left and VerticalAlignment=Top, then setting the Margin.Left and Margin.Top (properties of Thickness). However, the default Alignment=Stretch, which when designing works best most often. The exception being Buttons and TextBoxes, which usually need at least a MinWidth.

The most important first step is to cut out your layout in terms of a Grid. For example, a Menu or RibbonControl may be in the first row or column, as discussed here. Then each 'cell' of that parent (LayoutRoot) Grid may have another Grid or StackPanel to group the next controls, then more within them. It is not uncommon to have StackPanels within StackPanels, within Grids, within Grids, etc, etc. It means when you stretch your window, everything just flows, it's a beautiful sight and a big delight to many a Winform mover.

Grid rows and columns are measured by GridLength and can be Star, Pixel or Auto sized. Autosizing is a powerful tool to help the UI adapt to changes. Auto will take as much space as it needs, but no more than it requires. Star is a percentage (or weighted number) which defines how much of the total available space it takes up, from the total of the other combined starred sections. This is a powerful concept new to WPF and needs a clear and early understanding.

Data Manipulation

In WinForms, code was used to generate DataGridView.Rows and you could add extra rows and delete them and traverse them to extract the controls and their data. All very long winded and prone to bugs.

In WPF the emphasis is on separating the User Interface from the Data. Rows in WPF DataGrids are usually generated out of bound collections of data, automatically from DataTemplates and ColumnDefinitions, using the DataGrid's ItemsSource property.

WPF DataGrids have two states, read and edit. Data is automatically passed back through the bindings to the source data that is bound to it. This means you should work with the data, instead of traversing the control to extract data. This is particularly relevant to list controls like DataGrid and ListBox, because they use Virtualization to destroy rows that are not scrolled into view. This is a much better and simpler way to manage data and it much more testable. If correctly wired up, a simple "Create, Update, Delete" (CRUD) data browse add and edit application can be made with practically no lines of code at all.

Once fully understood, DataTemplates are one of the most powerful aspects of WPF that save heaps of development code, hundreds of "control creation" and "data shuffling" lines of code. This all means less mistakes, fewer tests, easier to understand code, faster deployment time and less maintenance.

MVVM - Developers and Designers in Parallel

The emphasis on "separation of the data from the UI" means an application can much more easily be re-skinned. It also means application developers and UI designers can work independently of each other, in parallel, which can mean twice as fast development. One of the popular design patterns for WPF is Model View ViewModel (MVVM).

The View (Page/Window/Control) 'consumes' the ViewModel, to produce controls shown on the user interface. The ViewModel is the interface between View and Model (data). The ViewModel is responsible for collecting the data and presenting it [through public properties] for the View to consume. Events are replaced with Commands. Styling, DataTemplates and Triggers give greater flexability to the designer, and keep all UI related 'business logic' in the UI.

For example, a [C#/VB.net] developer may expose a boolean IsReady property. It is up to the [XAML] designer how he consumes it. In one Control like the header bar, it may flash a green indicator. In the Window itself, it may trigger a new section to open. The developer can then free themselves from shuffling data in and out of controls, and concentrate on data and application/enterprise level business logic, and wraping tests around the ViewModel much better than messy methods in a code-behind file. As long as the developer and designer agree on which properties will be exposed, they can both work independently, with the designer using dummy data to produce his UI, while he waits for the real data to test his UI against.

Note that in teams which have no separate designers there is still a substantial development advantage from the clear separation of concerns mark up allows.  One substantial benefit afforded by loosely coupled View and Code is that automated testing of the code can be carried out without faking or mocking any UI.  Obviating testing via the front end makes writing BDD and or TDD tests far easier.  Such tests will readily run as "quick tests" so a developer can repeatedly write a test, change, test, fix and iterate without leaving  the development environment.

Naturally enough separation of concerns also offers it's usual benefits in terms of quicker development and easier maintenance of smaller pieces of code. 

Be sure to read Josh Smith's superb article about MVVM and WPF.

Styles, Templates and Triggers

A very important concept to embrace in WPF is HOW controls are made.

This article is not to teach them, but to emphasize it's importance and provide some good starting references.

A Brief Overview

The visual element of a control is defined by it's ControlTemplate.

Elements in the ControlTemplate bind their properties to TemplateBindings, to properties of the control like VerticalContentAlignment or Background.

ControlTemplates have Triggers which can react to changes in control properties like MouseEnter, and affect other properties with setters, or Enter and Exit actions, like running a StoryBoard to animate a menu opening.

Property changes can be grouped into a Style. A Style uses Setters to change control properties. So you may have a "Red Button" style that sets Button.Background to Red.

As ControlTemplate is also a property of the control, Styles can define that too, and change it based on a DataTrigger.

Themes define the base controls. If Styling and Templating is [rarely] not enough, you can change the core Window control theme like Aero, or even define your own.

Commanding

Commands are an alternative to events like the Click event, but are also more specific, like "Copy, "Cut" & "Paste". Commands don't have to be handled by the code-behind, they can come from a static resource, or a binding from your ViewModel or business object. In WPF, you can define and reuse DataTemplates and ControlTemplates from Dictionaries. A button defined in a Template from another file cannot therefore have a Click event, if you don't know where it may be used, so the alternative is to use a Command, which simply travels up the normal binding (VisualTree) path, only when it needs to.

User Drawn Controls

The biggest thing to wrap your head around switching from Windows.Forms OnPaint() is recognizing that WPF drawing is retained and *updatable*. OnRender() might be better named AccumulateDrawingObjects(), because that's really what it's doing. For example, you can "draw" a DrawingGroup or RenderTargetBitmap into a DrawingContext during OnRender() and update it after OnRender() is complete, and WPF will efficiently repaint it in your UI.

Stepping back, WPF also offers many more options for how to paint a user-drawn control. Here are some of the choices:

  • Shapes
  • DrawingGroup, hosted in a FrameworkElement or UIElement
  • RenderTargetBitmap, set as a Source on an Image control, drawn to with DrawingVisual and DrawingContext
  • WrtieableBitmap, set as a Source on an Image control, drawn to with GDI System.Drawing.Graphics
  • CompositionTarget.Rendering event, for updating information per WPF rendering frame

A Shape is a retained geometric object. For example, instead of writing an OnPaint method which draws a rectangle every time a control is invalidated, Shapes allow you to create a Rectangle once and WPF manages redrawing as necessary. Shapes may be moved around or changed, and they may be animated with WPF Animations. Shapes also provide layout, input, focus, and event handling. However, Shapes are heavier weight than GDI drawing. You don't want to be creating and destroying thousands of them every frame.

A DrawingGroup is a set of retained drawing commands that can be updated *after* they are put into a DrawingContext during OnRender(). Where in Windows.Forms you would Form.Invalidate() and it would call your OnPaint() to repaint the data, in WPF your OnRender() is only called at the end of the WPF layout process. If you want to update your control's UI without an expensive re-layout, then create a DrawingGroup "backing store" for drawing commands, add it to the DrawingContext during OnRender() and update it whenever you like using DrawingGroup.Open(). WPF will take care of repainting your UI. If you only need to update when your control's layout changes, you can just draw directly to the DrawingContext.

RenderTargetBitmap can be added to a DrawingContext and updated *after* it's added. If for some reason you want to control the pixel size your drawing commands are rasterized to, this is a good option. However, in most cases you are better off using DrawingGroup as explained above. 

WriteableBitmap is similar to RenderTargetBitmap, except you can create a GDI System.Drawing.Graphics context ontop of it. This is helpful if you have a complex bit of Windows.Forms drawing code you'd like to move over to a WPF control. You can also write on WriteableBitmap from multiple threads at once, if that's important. After writing the bitmap, one calls WritableBitmap.AddDirtyRect() to notify WPF of changes and cause it to re-composite the resulting UI.

CompositionTarget.Rendering is an event which fires once every WPF rendering frame, and can be used to update any of the above rendering visuals on a per-frame basis.

Animation

Animation allows developers to enhance user experience by making the UI more attractive and focussing attention on the most important thing which is changing.

Expression Blend is a very powerful and useful tool for WPF UI developers.

For many developers Blend is very different from the Visual Studio environment they are used to.  Blend can be somewhat daunting since it looks a "designer" orientated tool best left to graphic artists.  At some point, the developer will wish to learn how to extract templates from controls so they can understand how they work.

If you adopt Blend early, it will help you understand Templates, Styles, Triggers and Layout much faster. You can copy a template and see how a control is made, you can make animations with point and click and test your creations in real time and watch the storyboard get scripted for you.

The visual studio designer in more recent versions of visual studio is the same as Blend and will run the Window/Page/Control's constructor.  This can have implications to someone wishing to design even in visual studio because a clean constructor is necessary for the designer to work properly.  This is best explained by Laurent Bugnion in some of his videos where he describes writing for "blendability" and embodied in the MVVM Light  toolkit templates.

Say you are working on a control which uses an RSS feed.  Your RSS feed can load directly into your control within the designer, and you can work as shows live feeds - so long as it works using only objects supplied from that constructor.

  1. Animation Overview - MSDN

Other significant articles on moving from WinForms to WPF

http://c.statcounter.com/8185303/0/e1ff8bcd/1/