If you don't know the words to this classic folk song, here they are:
Oh, the old gray mare, she ain't what she used to be, Ain't what she used to be, ain't what she used to be. The old gray mare, she ain't what she used to be, Many long years ago.
I can't count how many times I heard this song's melody in the background of an animated cartoon. Usually it was played at a slow pace in an exaggerated manner to accompany a worn-out character trudging along, apparently bogged down by some trouble or other. (If you listened to different melodies as a kid, check it out here http://freekidsmusic.com/traditional/the-old-gray-mare.html) Because of this cartoon-ization I attributed negative connotations to the melody. Then I found the ‘real' story. According Wikipedia, "Some authors have said that the song originated based upon the extraordinary performance of Lady Suffolk, who was the first horse to do the mile in less than two and a half minutes. It occurred on July 4, 1843, at the Beacon Course racetrack in Hoboken, New Jersey, when she was over 10 years old." Wow, a ten year old horse breaking a global speed record. Not bad!What's this got to do with PowerBuilder, plenty! As you'll soon see!
I just finished writing a rich media eTutorial on WPF Layout using PowerBuilder 12 .NET. The tutorial guides Classic PowerBuilder developers in the nuances of building fluid WPF layouts using declarative features of XAML. WPF has awesome capabilities that you can leverage to transform your user interface. It's a real powerful racehorse. But (and it's a big but) WinForm and WPF are different technologies and have different presentation strategies. WinForm is coordinate based. The control location is specified by x and y coordinates. Controls are sized absolutely with a given height and width. On the performance side, the entire screen is redrawn every time a control moves. Taking a page from the publishing playbook, WPF is layout-based; the emphasis is on creating flexible, fluid, adaptable layouts that are adaptive as well as screen resolution independent. In WPF, flow-based layout is standard; classic coordinate-based layout only has rudimentary support in the form of a Canvas layout that approximates absolute positioning. Screen rendering is fast, because WPF sits on top of DirectX technology. Only the portion of the screen affected by the move or change is redrawn.
When you migrate your Classic application to .NET WPF, PowerBuilder renders your windows and CVUOs in a rigid Canvas layout. If your application has a home brewed resize service or it relies on the industry standard PFC service, your windows will continue to resize in a fluid manner, albeit in a code-based rather than a native declarative manner. But if your Classic windows are resize challenged, you will find yourself tempted to move to fluid, flexible display containers. You might say to yourself, "Hey all I have to do is change a couple of XAML tags and I'm good to go." The news is there are two inherent flaws in this approach. First, there are visual design considerations that must precede any radical GUI modification. You'll need to figure out how your GUI elements should respond to size and resolution changes and set up your elements so they respond logically. You can learn about this non-language / platform-specific design issue from my eCourse or from any good WPF book. Second there's a code problem that is PowerBuilder specific: the visual inheritance hierarchy and its XAML implementation, which are going to fight you tooth and nail not to be changed. I'm going to focus on this issue in the rest of this article.
Visual Inheritance and Layout
XAML was not designed to support inheritance. XAML is a declarative markup style language. It is predicated on the separation of data and business logic from presentation. It is not an object-oriented code-based approach (although XAML does compile into code object runtime calls). The way to reformulate XAML is through resource dictionaries, template bindings and the like. You can apply resource dictionaries both dynamically and statically. (I'll be building eTutorials to show you how to do these.)
Because Classic PowerBuilder fully supports visual inheritance and most applications rely heavily on it, PowerBuilder .NET added development and compile-time support for visual XAML ‘inheritance.' However, as you'll soon see, because it depends on Painter dynamic code generation, and some proprietary internal formatting, it's resistant to change. At compile time and when a descendent is opened in a painter, descendent XAML is dynamically composed by consolidating all the XAML up the hierarchy. Let's take a look at how PowerBuilder does it.
XAML Merging by Example
I created a small application in Classic and then migrated it .NET to illustrate the issues. Figure 1 shows the application architecture. Figure 2 shows the target in the Solution Explorer. It has a CVUO (u_bar), an ancestor window composed of a button bar, a DataWindow control, a picture and a couple of command buttons. Last, there is a descendent window that adds another DataWindow control and a couple more buttons.
Starting at the top, let's take a look at the XAML that is generated. Listing 1 shows the XAML generated for the CVUO u_bar.sru.xaml (namespace declarations removed for brevity's sake).
Figure 4 shows the same XAML inside the user object painter. It's plain to see that there are many more attributes in the painter than in the XAML file. How come?
Now let's go down a level to the ancestor window. Listing 2 shows the generated XAML file w_anc.srw.xaml. I bolded the XAML for the user object. Notice that it's just a placeholder. Most of the XAML for the user object is not here!
Figure 5 shows the same XAML in the WPF window painter. You can plainly see that the XAML for the user object has been merged into the window. Holy cow, painter magic!
Now let's go down to the next level, the descendent window. Listing 3 shows the XAML. Once again notice that the ancestor XAML (bolded) is merely a placeholder. This time the placeholders go up two ancestor levels: the base window and the user object.
Figure 6 shows the same XAML in the WPF window painter. Once again it's fully expanded.
Note several key issues. (1) You might think you can change any XAML in a window. But in reality, you can't. If you use the WPF editor on the descendent XAML and make changes to ancestor XAML, you might think you have accomplished something, but when you save, all your changes will be lost (with no warning!). (2) Worse yet (at least in build 5530) because of the inheritance issue, some of your well-intentioned descendant changes will confuse the XAML writing process and cause your XAML to render in a corrupt manner. Net result - next time you open your window, the layout editor will tell you it can't load the XAML. Then you'll be forced to figure out what it did on the save and fix it manually.
What to Do?
In a conversation with some of the product engineers, I raised the issue of changing XAML (specifically from a <Canvas> to a <Grid> but it can apply to any fundamental change). Here's what was suggested,
"If inheritance is involved, the best recommendation would be to change the root layout panel in the XAML files of both descendant and all its ancestors *outside the IDE* so they are consistent before reopening the descendant inside the IDE. The intent is to cause the least impact on the underlying Merge/Diff algorithms by maintaining consistency."
However, Canvas is very different from Grid since you are going from an absolute positioning system to a Margin-based positioning system. You would subsequently have to change the Canvas.Left and Canvas.Top properties to Margin properties for all constituent controls in the XAML, preferably starting with the ancestor and descending."
My reaction, "Ugh, work outside the painter, without syntax and tool support; without a clear understanding of the partial ancestor XAML that is written into descendents. What a time-consuming, error-prone approach! No way!"
After careful consideration, I came to the conclusion that it might be better (as in much less painful) to start with a clean slate. Create a new base window hierarchy. Build (or wire) into it all generic, core window functionality. This base window should have no controls. Give it a <Grid> layout container. Then for each implementation window inherit from this base window and style each user interface within a properly designed <Grid> in at the concrete level.
The old gray mare is really a powerful race horse. There's a powerful beast waiting to be released. But when working with a legacy system, the mare's carrying a heavy load. It's going to take careful consideration and a thoughtful approach to merge newer technology into an existing code line.