Wednesday, June 20, 2007
« Resolution to the NetDataContract/Serial... | Main | Codeless WPF data edit forms with CslaDa... »

I’ve been spending some quality time with WPF and Visual Studio 2008 Beta 1 (Orcas) over the past few months. A couple observations I’ve made:

Observation ichi:

Putting code in a Page/Window constructor is bad. Yes, I know it is the “C# way”, but it is bad. The “VB way” of putting code in the Loaded event handler is better.

Why?

Because any exceptions thrown in the constructor prevent the page/window from loading at all, and you have to catch those exceptions in the code that is creating the page/window. In many navigation scenarios you can’t catch them, so the user gets an ugly WPF exception dialog.

However, if you do all your init work in the Loaded event handler, the page/window instance already exists. Navigation has already happened, so the “calling code” is no longer responsible for your page/window going haywire. Instead, you can actually handle the exception and show a nice dialog, explaining to the user what happened, and you can (if desired) navigate somewhere else or whatever.

Observation ni:

Handing control events with += or AddHandler is superior to using WithEvents/Handles. Yes, I am a strong advocate of WithEvents/Handles, and it is one of the major missing features in C#, but for WPF it turns out to be a negative.

Why?

Because the Handles clause links the name of the control to the code behind. The AddHandler or += approach, while less attractive in many ways, only couples the event name to the code behind.

In other words, when using Handles you get code like:

Private Sub Button1_Click(ByVal sender As Object, ByVal e As EventArgs) _
  Handles Button1.Click

While the AddHandler approach results in this code:

Private Sub SaveData(ByVal sender As Object, ByVal e As EventArgs)

The value of the second approach is that any control can route an event to SaveData(). It doesn’t have to be a button, much less Button1. It actually doesn’t even have to be a Click event. It could be almost any event.

The result is that the Presentation (XAML) is less coupled to the UI (VB/C# code) this way than when using Handles.

Unfortunately the Handles approach is more explicit and thus more readable (which is why C# should really get the Handles feature), so we’re left trading readability in order to gain loose coupling…

WPF

Wednesday, June 20, 2007 2:36:41 PM (Central Standard Time, UTC-06:00)
About your 2nd observation I think that it can be solved using the following notation:

Private Sub Button1_Click(ByVal sender As Object, ByVal e As EventArgs) _
Handles Button1.Click, Button2.Click, MyControl.MyEvent

Just my two cents, regards!
Jacobo
Wednesday, June 20, 2007 3:03:26 PM (Central Standard Time, UTC-06:00)
Thanks Jacobo, unfortunately I don't think so.

I'm buying into Microsoft's stated goal of having developers write C#/VB code and designers write the XAML. This means there must be a defined interface between the XAML and code behind. The less there is to this interface, the less there is to break.

Handles _always_ requires the VB dev to know the name of the UI control and the name of the event. AddHandler only requires the XAML designer to know the name of the event handler.

So using Handles forces the XAML designer to use a specific control name, and only a control that raises that specific event. When in reality they might use NO control name, and they could use many other controls to do the job - but those controls might raise different events.

Using AddHandler allows the XAML designer the most flexibility, and requires the VB/C# dev to do the least amount of work.
Thursday, June 21, 2007 1:29:18 AM (Central Standard Time, UTC-06:00)
Hey Rocky,

I don't really get you're claim that you can wire events without knowing the control name. I assume you mean a "reference" to the control, not it's actual "Name" property. There is a similarity between those due to the way designers work, but they aren't the same. If you don't have an object reference you can't hook into it's events.

If you are talking about looping through child controls or something similar, it's probably a lot cleaner to use a conatiner and let the events buble up to the container and decleratively handle the container's events.

Using AddHandler certainly is not less work. Handles is actually less as the designer will emit the correct code for you if you use the drop down combos. (note a build is needed in Orcas at present)
And the nice thing about Handles is if you set the WithEvents variable to Nothing all the events get **unwired**. Tryign to manage unwiring events when the code is added via AddHandler is a maintenance nightmare. It's even worse when that code is hidden in designer generated code al la C#.

Declerative code is beautiful : be it XAML or declerative event handlers :D



Thursday, June 21, 2007 12:29:07 PM (Central Standard Time, UTC-06:00)
Hi Bill,

The reason is because the += or AddHandler is actually done by the XAML generation process, not by you. So when a designer does this:

<Button Click="SaveData">Save</Button>

That links to your SaveData() method

Private Sub SaveData()

But notice that you (the developer) don't know or care that the event source was a button, or that a click event triggered the call.

Compare that with

<Button Name="SaveButton">Save</Button>

Where the VB code would be

Private Sub SaveButton_Click(...) Handles SaveButton.Click

See the coupling? It is right there in the code: both the control name and event name, plain as day.

In the FIRST example, the designer could switch the XAML to use some other control, with some other event, and they could still trigger the SaveData() operation. No change to the VB/C# code is required to accomodate the XAML change.

But in the SECOND example, if the designer made such a big change then the VB dev would have to make a change too.
Thursday, June 21, 2007 3:33:38 PM (Central Standard Time, UTC-06:00)
Rocky

I noticed a problem with Observation ichi.

The loaded events is like the Activate event in VB6. It fires every time the object is put in focus. I discovered this when clicking between tabs. I have same usercontrol that exists on different tabs in a tabcontrol. Each tab contains a different instance of the usercontrol. Everytime I switch between tabs the loaded event fires for the usercontrol instance, not only when the usercontrol is initially loaded. Not sure if its a bug or a feature. LOL.
RockoWPB
Thursday, June 21, 2007 9:51:32 PM (Central Standard Time, UTC-06:00)
Hey Rocky,

Ah, I can now see what you are talking about.. still don't agree, but at least I understand what you're saying <g>
There's 3 ways to wire up event handlers to the code behind (excluding data binding). All of them eventually use AddHandler. Those ways are :
(1) Wire up the event to a method yourself using AddHandler in one of the Window's Load or Init kind of events.
(2) Use Handles to declaratively mark a method as handling an event.
(3) Use implicit binding by typing the name of the method in the XAML for the Event you wish to wire up.

So it appears you are talking about (3) even though you never actually type AddHandler, and as said (1), (2) and (3) all use AddHandler at some part. ;)

As to coupling, the code behind file is meant to be coupled, and it is in all cases (1), (2) and (3).
(2) has advantages over (1) of being clearer as to what the code is handling and provides for simpler swapping of controls at runtime and unwiring. It makes code maintenance easier.
So form (1) and (2), I think (2) wins hands down. And it was (1) I thought you were referring to when you said AddHandler because it is the only one when you actually type AddHandler.

So, the discussion is now about (2) versus (3). I'd call (3) implicit wiring but it doesn't really decouple the UI from the code behind, in fact it intertwines the two. Let's say we have two people working on this team, one doing purely the XAML design using Blend, the other doing purely the code behind. Here we haven't separated the XAML from the code behind, we have separated design and coder roles ;) So using (3) the XAML designer crew member adds some buttons and says button1's click event will fire the Button1_Clicked method. Can that crew member compile the code ? No, they are reliant on the code behind developer to add the method stub. They have to check out the code behind file before they can proceed. The coder has to check it in before they can proceed. They are ground to a single person working at a time ONLY.
Later in the process consider what happens when the coder wants to refactor the method names, be it because of a conflict with another interface, or just to make it clearer as to the code intent. To do so, the coder has to check out the designer's XAML file, so the designer has to check that in and stop working on it. Again, ground to a single person working ONLY.

Now let's revisit this using (2) instead of (3). The designer can add controls at will and compile. They don't have to worry about what will trigger events or how. The coder can add the events based on the last checked in version... doesn't need the current one. If the designer changes/removes a control, they get a compile error and they can fix that by simply applying the same name to the new control. And the coder can refactor their work as much as they like. And on top of this the coder has clearer code because it declaratively marks what it handles.
Thursday, August 09, 2007 6:20:38 AM (Central Standard Time, UTC-06:00)
Hi Rocky,

a few comments. Catching unhandled exceptions in WPF can be done at the dispatcher level for async calls (Dispatcher.UnhandledException event) but also at the AppDomain like every other .net application (AppDomain.UnhandledException). Have you encountered a case where these events don't get fired?

And about your previous WPF navigation post I just read (replying here as the commetns are closed on the other one), WPF always keeps the Page alive when you navigate using the Page object reference. It won't keep a Page alive when navigation is done by uri (either relative or pack syntax).

As for the values still being in the controls, all the journalled properties will get restored when navigating by object reference, and should have been restored by navigating by URL. Note that in the latter case, the value will get restored but you will loose the binding as wpf doesn't restore it properly. This will be fixed in SP1 and is fixed in 3.5 beta 2.

Hope it helps,

SerialSeb
Comments are closed.