1. Create the new control
The task of creating a new instance of a control in the code behind is very similar to what you are used to in Windows Forms. Of course, just to keep you on your toes, several of the basic properties have different names in WPF. Also there are new properties that you may need to use. Finally, you have to think in WPF terms for layout and positioning of the control - for example, you can't simply set Left and Top property values to assign the control's location on screen.
I have created a procedure named CreateTextBlock to house the code. The first part of this procedure looks like this:
Let's just briefly look at how the TextBlock instance is built.
The instantiation and setting of Width and Height are standard and familiar. The control's Location however needs a bit more thought.
Recall that controls in WPF have their layout decided in relation to their containers, whether this is the Window, Grid, other panel type or even another control. In the above example, by setting the Alignment properties to Left and Top we effectively shift the TextBlock up into the left hand top corner of its container. As it happens, the container I am using in this example will be a Grid, but the principle is the same no matter which container you use.
Having moved it into the general area where we want it to appear, we can now fine tune it's location by setting values on the TextBlock's Margin property. Note that you can't just use code such as:
dynamicWPFTextBlock.Margin = (12, 10, 12, 15)
You have to assign values to the Thickness structure as shown above.
The Text content is added next and finally this text is centered within the bounds of the TextBlock.
You can assign values to any other properties that you need to at this stage, but for our example this will suffice.
2. Adding it to the Application
The process here is very similar to the WinForms way. One key difference though is that the containing control (whether it is the Window, Grid, or other kind of panel or container) keeps its child controls in a Children collection, not a Controls collection. Apart from that the procedure is the same.
When I first looked at this task I thought it might be necessary to serialize the control to a string and use a XamlReader and XamlWriter to save and load the control's data into the application. Most of the examples I could find gave me this impression. It turns out though that in this basic type of situation that this step isn't necessary (although the XamlReader does come in very handy for some more advanced scenarios).
In this example I have placed the TextBlock inside a Grid named MainGrid. So the code to load the control into that grid is simply:
So, if adding this control to the grid is the limit of your ambitions and you don't need to handle any events on the TextBlock, your task is complete. The above process will let you add the control to the Grid. Once (More on this later).
3. Let's Be Realistic - You're Usually Going To Need To Handle Events
With a TextBlock it's conceivable that you won't need to hook up any event handling. In many cases though, for instance with buttons that are built for user interaction, you are going to want to hook up event handlers to your control.
Once again, when I searched through the samples and forum posts it looked as though this was going to be more complex than previously. Most examples tend to show code in the format of:
ControlInstanceName.AddHandler(RoutedEvent, Delegate)
where the delegate points to the appropriate method.
As you can see, there are some significant changes from Windows Forms, such as the AddHandler method being implemented using the Dot notation as a method of the Control Instance.
The event is a RoutedEvent which in most cases isn't a significant factor as you will want to use a RoutedEvent in most situations like this.
The Delegate parameter though is a shift from previous approaches. In this version of the syntax the requirement is specifically for a delegate and not (as you can get away with in WinForms) an event handling method name. As you may know, in Win32 Visual Basic automagically translates the method name into a delegate pointer for you under the hood, but shields you from this implementation detail.
So for technical completeness and to conform to what seems to be the "official" syntax, here is the kind of code needed to add an event handler to your control in WPF:
Note the way that the address of the mouse down handler is wrapped in the MouseButtonEventHandler delegate.
But it turns out that you can continue with old habits after all. Although most sample variations of the above kind of syntax require the delegate to be included as shown above, if you use the old style of utilizing the AddHandler statement:
AddHandler ControlInstanceName.EventName, AddressOf EventHandler Method
then it seems to work just fine. Whether there is some not yet obvious problem that will rise to punish you at some later stage, I really don't know. Personally I'm going to stick with the more verbose "official" version, now that I've mastered the requirements for using it, but it's a personal choice.
So for completeness, here is a sample handling method for a TextBlock's MouseDown event which is no different from what you have been doing in Windows Forms and earlier frameworks.
Try both ways of adding handlers and see which works best for you. If you come across any problems in either approach I would be very interested in your feedback.
4. Beyond the Basics
The question that first started me off with this quest was a case where someone wanted even finer regulation of exactly where the newly minted controls were located in relation to their containers. In other words, not simply slapping the control in the top left corner as we have done so far, but also taking into account requirements such as docking in DockPanels or placing a control in a specific cell of a grid via the Columns and Grids attached properties.
So that's what I'll look at in the next page.