Lesson 5 - Code-Behind in C# .NET WPF and finishing the calculator
In the previous lesson, Designing a calculator form in C# .NET WPF, we made a form for a simple calculator in C# .NET. In today's lesson, we're going to work on the logical part of our application. Once we finish working on our app, we'll talk a little bit about how WPF works internally.
Code-Behind
The presentation layer of a WPF application is written in XAML. However, all XAML structures is the design. Each window has what is referred to as "code-behind" calling the application's logic layer. We are able to access this code from the Designer by right-clicking and selecting the "View Code" option. You could also access it using the CTRL + ALT + 0 shortcut, the zero on the alphanumeric keyboard, to move to the and Shift + F7 to move back to the Graphic designer.
Our form's code-behind looks like this (I've omitted namespaces):
public partial class MainWindow : Window { public MainWindow() { InitializeComponent(); } }
There you will see that the form is represented by a class inherited from the Window class. A Window is a class that represents the Window control. Each control has its own class in WPF, which won't come as a surprise to you, knowing that C# is an object-oriented language The mysterious InitializeComponents() method is called from inside the form's constructor. It internally parses the XAML and creates instances of each control. That is how forms are created.
Naming controls
If we wanted to work with a control from the code-behind, the first thing we would have to do is assign a name to it. Legacy versions of Visual Studio didn't incorporate control naming whatsoever. Later versions of VS added the feature and would assign names automatically based on the element type and an incremental value of the amount of controls of that type in use, e.g. TextBlock1.
Back to our calculator. In order to perform calculations, we will need to read the numbers from both TextBoxes, read the operation from the ComboBox, and write the result into the TextBlock. We will also need to handle the button's click event.
Let's move back to the XAML section and change each of the control's Name attributes to the following values: number1TextBox, number2TextBox, operationComboBox, resultTextBlock, calculateButton. Note that the names are suffixed with the type of control that they are. For larger, more complex forms, this helps clear things out quite a bit. Try to avoid names like button1, textBlock1, etc.
Events
WPF is built upon event models. In our calculator, the only event we have to react to is when the button is clicked. When we double-click the button in the Graphic designer, Visual Studio will send us to the code-behind, where the following method will be generated:
private void calculateButton_Click(object sender, RoutedEventArgs e) { }
The method above will be called when the user clicks the button. We'll go over how all of this is achieved later on. Events can be assigned and removed in the Properties window. All you have to do is select the control and click on the flash icon which moves from the properties section to the events section. The button next to it moves you back to properties:
Let's insert following code into the generated method:
// preparing variables string operation = operationComboBox.Text; double number1 = double.Parse(number1TextBox.Text); double number2 = double.Parse(number2TextBox.Text); double result = 0; // calculations if (operation == "+") result = number1 + number2; else if (operation == "-") result = number1 - number2; else if (operation == "*") result = number1 * number2; else if (operation == "/") { if (number2 != 0) result = number1 / number2; else MessageBox.Show("You cannot divide by zero"); } resultTextBlock.Text = result.ToString();
The first few lines prepare variables into which we will store values returned from the controls. We can access both the TextBox's text and the ComboBox's selected item text through the Text property. Here, we can see why it's a good idea to name controls properly. The application doesn't know how to deal with invalid user input. If that were to happen, the program would terminate with a value parsing exception. We will teach you user input validation in the next couple of lessons. Once you get there, you could come back and add user validations if you'd like.
Calculating the result should be clear to you all, seeing as how we made a calculator application in the first C# .NET course. The only added feature we need to include is checking whether or not we divide by zero. If that is the case, we will bring up what is known as a MessageBox, which I'm sure you are very familiar with from other applications (pop-up message windows in pretty much every app). To do so, we use the static MessageBox class, which ends up looking something like this:
Convert the result to a string because we will be assigning it to a Text property. At the end of the calculation method, assign the result to the TextBlock that displays the result. Done! Now, try the application out on some test data.
WPF under the hood
Before we move on to the more advanced material, I'll show you how WPF works internally in a few, short paragraphs. If you haven't finished the object-oriented course till the end, you probably won't fully understand the rest of this lesson. Nonetheless, you don't absolutely have to know every detail about how WPF works to develop practical WPF applications.
Partial class
Moving back to the MainWindow class (in the Code-Behind). The more perceptive amongst you may have noticed that the MainWindow class is partial (marked with the partial modifier). Meaning that it is defined in multiple files. The rest of the declaration is hidden and can be accessed by clicking on the InitializeComponent() method and pressing F12 which moves you to the method implementation.
Doing so takes us to a nasty looking class that was automatically generated by Visual Studio along with the new window. There, we see two methods: InitializeComponent() and Connect().
The InitializeComponent() method reads the XAML and calls the LoadComponent() method on it. Notice the CodeDOM namespace attributes above the class. These are classes for generating C# code at run-time. Which is exactly what this method does, it parses the XAML and creates control instances based on their definition in XAML. The Connect() method "magically" connects methods in the Code-Behind.
Here, we see the EventHandler in use:
this.calculateButton.Click += new System.Windows.RoutedEventHandler(this.calculateButton_Click);
The method also exposes the individual controls under their names, which is done by the ugly switch We will never interfere with this file in any way, but it is important for you to understand how WPF works.
Creating controls at run-time
By now, you might be thinking, controls are ordinary classes, right? Then, can they be added to the form by creating an instance in the Code-Behind instead of adding them to the XAML? The answer to that is, yes. Technically, we don't need XAML at all. However, designing forms would be extremely difficult and messy if we didn't.
Let's look back at our Grid in the XAML section:
<Grid Margin="0"> <Grid.ColumnDefinitions> <ColumnDefinition Width="*"/> <ColumnDefinition Width="50"/> <ColumnDefinition Width="*"/> <ColumnDefinition Width="50"/> <ColumnDefinition Width="*"/> </Grid.ColumnDefinitions> <Grid.RowDefinitions> <RowDefinition Height="*"/> <RowDefinition Height="30"/> </Grid.RowDefinitions> </Grid>
To achieve the same results in the Code-Behind, you would have to do something like this:
Grid grid = new Grid(); grid.Margin = ((Thickness)(TypeDescriptor.GetConverter(typeof(Thickness)).ConvertFromInvariantString("0"))); ColumnDefinition columnDefinition = new ColumnDefinition(); columnDefinition.Width = ((GridLength)(TypeDescriptor.GetConverter(typeof(GridLength)).ConvertFromInvariantString("*"))); grid.ColumnDefinitions.Add(columnDefinition); ColumnDefinition columnDefinition2 = new ColumnDefinition(); columnDefinition2.Width = ((GridLength)(TypeDescriptor.GetConverter(typeof(GridLength)).ConvertFromInvariantString("50"))); grid.ColumnDefinitions.Add(columnDefinition2); ColumnDefinition columnDefinition3 = new ColumnDefinition(); columnDefinition3.Width = ((GridLength)(TypeDescriptor.GetConverter(typeof(GridLength)).ConvertFromInvariantString("*"))); grid.ColumnDefinitions.Add(columnDefinition3); ColumnDefinition columnDefinition4 = new ColumnDefinition(); columnDefinition4.Width = ((GridLength)(TypeDescriptor.GetConverter(typeof(GridLength)).ConvertFromInvariantString("50"))); grid.ColumnDefinitions.Add(columnDefinition4); ColumnDefinition columnDefinition5 = new ColumnDefinition(); columnDefinition5.Width = ((GridLength)(TypeDescriptor.GetConverter(typeof(GridLength)).ConvertFromInvariantString("*"))); grid.ColumnDefinitions.Add(columnDefinition5); RowDefinition rowDefinition = new RowDefinition(); rowDefinition.Height = ((GridLength)(TypeDescriptor.GetConverter(typeof(GridLength)).ConvertFromInvariantString("*"))); grid.RowDefinitions.Add(rowDefinition); RowDefinition rowDefinition2 = new RowDefinition(); rowDefinition2.Height = ((GridLength)(TypeDescriptor.GetConverter(typeof(GridLength)).ConvertFromInvariantString("30"))); grid.RowDefinitions.Add(rowDefinition2);
I'm sure you agree that the code above is not very clear (keep in mind that the code above would only be a small part of the program). That is why XAML, which has a clear and simple tree structure, is used to design applications.
In some cases, it may be useful to set something or create a specific part of the application in the Code-Behind. Which is why I gave you this "Behind-the-Scenes" look, so you would know that it can be done.
In the next lesson, Solved tasks for C# .NET WPF lessons 1-5, we'll start programming a more robust application. A birthday reminder application. The source code calculator we made today is, as always, available for download below.
In the following exercise, Solved tasks for C# .NET WPF lessons 1-5, we're gonna practice our knowledge from previous lessons.
Did you have a problem with anything? Download the sample application below and compare it with your project, you will find the error easily.
Download
By downloading the following file, you agree to the license terms
Downloaded 83x (108.3 kB)
Application includes source codes in language C#