Migrating PowerBuilder DataWindows to web (part 3)

by John Browne, on Mar 11, 2019 11:37:36 AM

Welcome back. If you've been reading along, in Part 1 and Part 2 we talked about what PowerBuilder DataWindows do, how they are used, and the challenges of modernizing PowerBuilder DataWindows to native web apps.

Having migrated our PowerBuilder DataWindows from our sample app using WebMAP, in this final chapter we'll take a deeper dive into the generated code. Let's begin with an overview of what the generated application looks like:

 

PB architecture

 

Notice the option to migrate from PowerBuilder to .NET or Java. WebMAP can convert your PowerBuilder app--including DataWindows--to either C# running on ASP.NET Core or Java using the Spring MVC pattern. In either case, the client-side code is identical, using Kendo UI for Angular and Angular 7.

In Part 2 we reviewed the front end files, including the component, template, and styling files. In this post we'll look at the back end in more detail.

In our original PowerBuilder app, we had two data windows: a grid presentation (dw_grid) and a form-style presentation (dw_info). Each DataWindow contained some data-bound controls and a command button. Each command button had an event handler for a click event.

That structure has been preserved in our C#/ASP.NET version as well. Our app consists of four C# files: main.cs, demo.cs, dw_grid.cs, and dw_info.cs.

The simplest of the four is main.cs, which is just the application entry point. It has just one method:

public
static void Main()
	{new demo().Show();
	}
    

The demo.cs file is a little more interesting: we handle the click events for each of the two command buttons and define our data table for the DataWindows:

private void cb_2_Click(object sender, System.EventArgs e)
        {
            this.Close();
        }

        private void cb_1_Click(object sender, System.EventArgs e)
        {
            if ( Mobilize.Web.MessageBox.Show("Do you want reset customer info?", "Reset", Mobilize.Web.MessageBoxButtons.YesNo) == Mobilize.Web.DialogResult.Yes)
            {
                this.dw1.reset();
            }
        }

        public static void bind(dw_info dw_info, dw_grid dw_grid, bool first = true)
        {
            var dc = new Mobilize.Web.VBUC.ADODataControlHelper();
            var dt = new DataTable();
            dc.Recordset.Tables.Add(dt);
            dt.Columns.Add("Name");
            dt.Columns.Add("Id");
            dt.Columns.Add("Info1");
            dt.Columns.Add("Info2");
            dt.Columns.Add("Info3");
            dt.Rows.Add(new object[] { "Boe Peep", 123, "Contact: Boe Peep", "Boe Peep's Part Store", "123 Somestreet NE" });
            dt.Rows.Add(new object[] { "XYZ Supply", 124, "Contact: John Doe", "XYZ Supply Company", "123 Abcstreet SE" });
            if (first) dc.Recordset.MoveFirst(); else dc.Recordset.MoveLast();
            dw_info.bind(dc);
            dw_grid.bind(dt, dw_info);
        }

Note that just as in the original PowerBuilder app this demo inserts actual data (two rows) instead of connecting to an actual database. Sheer laziness but it's just as instructive for this purpose.

Modal dialogs and other witchery

Notice again the MessageBox.Show method: it looks surprisingly like you would expect to see a "normal" C# Winforms dialog invocation. Except for the "Mobilize.Web" namespace decoration, it would be exactly like a standard .NET Framework MessageBox() invocation. 

How is this possible?

Super short answer: it's complicated. Slightly longer answer: we use code weaving, courtesy of the Roslyn compiler. For a more detailed discussion, check out this three-part series on the complete WebMAP architecture. An easy way to think about this is that we're just letting the compiler do the heavy lifting. A Mobilize.Web.MessageBox() method handles all the issues that make a modal dialog difficult on a web app: suspending the session (in effect), caching the user state, maintaining all the other sessions with no penalty, and timing out the invoking session if necessary. Normally you would have to write a bunch of code to handle this kind of an event on a web app, which is why you don't see them very often. But virtually every desktop app has this pattern--saving a file, for example. And so moving those desktop apps to web apps presents this modality problem at least once and more commonly multiple times.

WebMAP, however, makes it simple.

The converted DataWindow files

We have a new C# file for each DataWindow object that was in the original PowerBuilder project. Let's look at dw_info.cs first:

public partial class dw_info : Mobilize.Web.UserControl
    {
        public dw_info()
        {
            InitializeComponent();
        }

        public void reset()
        {
            this.theName.Text = "";
            this.theId.Text = "";
            this.info1.Text = "";
            this.info2.Text = "";
            this.info3.Text = "";
        }

        internal void bind(Mobilize.Web.VBUC.ADODataControlHelper dc)
        {
            this.theName.Text = dc.Recordset.CurrentRow["Name"].ToString();
            this.theId.Text = dc.Recordset.CurrentRow["Id"].ToString();
            this.info1.Text = dc.Recordset.CurrentRow["Info1"].ToString();
            this.info2.Text = dc.Recordset.CurrentRow["Info2"].ToString();
            this.info3.Text = dc.Recordset.CurrentRow["Info3"].ToString();
        }

    }
    

Points of interest:

  • We inherit from Mobilize.Web.UserControl, which is the parent class for all our UI objects. Notice that dw_info() is a partial class, which means that some of the implementation is somewhere else; in this case it's in a designer file (dw_info.Designer.cs) that we will explore in a moment.
  • We have three methods in this class: a constructor, reset() which clears the data, and a bind() method that binds the UI elements to the database model.
  • The reset() method is called from the click event handler (see the demo.cs listing above). It clears the upper DataWindow here: 
Chrome_run
  • Finally, the database model (the bind() method) maps the model to the actual client-side UI. Remember our HTML template file (see Part 2)? Here's what the HTML for this DataWindow's Angular component looks like:

dw_info_html

You can see that the user-interface control names match the model in the C# file. And if we look in the associated CSS file we'll see the styling for those UI elements as well:

info_css

The designer file

I noted (above) that the dw_info() class defined in dw_info.cs is a partial class; partial classes in C# have their implementations spread over more than one file. In our case the dw_info() class is also implemented in the dw_info.Designer.cs file. 

Confused yet? Try this:

PB_to_ASPNET

So turning our attention to the last file we haven't yet looked at (dw_info.Designer.cs), we see the following:

 partial class dw_info
    {

        [Mobilize.WebMap.Common.Attributes.Intercepted]
        ///  
        /// Required designer variable.
        /// 
        private
        System.ComponentModel.IContainer components { get; set; }

        protected override void Dispose(bool disposing)
        {
            if (disposing && (components != null))
            {
                components.Dispose();
            }
        }

        [Mobilize.WebMap.Common.Attributes.Designer]


        [Mobilize.WebMap.Common.Attributes.Intercepted]

        private Mobilize.Web.Label name_t { get; set; }

        [Mobilize.WebMap.Common.Attributes.Intercepted]
        private Mobilize.Web.Label id_t { get; set; }

        [Mobilize.WebMap.Common.Attributes.Intercepted]
        private Mobilize.Web.Label info2_t { get; set; }

        [Mobilize.WebMap.Common.Attributes.Intercepted]
        private Mobilize.Web.Label info1_t { get; set; }

        [Mobilize.WebMap.Common.Attributes.Intercepted]
        private Mobilize.Web.Label info3_t { get; set; }

        [Mobilize.WebMap.Common.Attributes.Intercepted]
        private Mobilize.Web.TextBox theName { get; set; }

        [Mobilize.WebMap.Common.Attributes.Intercepted]
        private Mobilize.Web.TextBox theId { get; set; }

        [Mobilize.WebMap.Common.Attributes.Intercepted]
        private Mobilize.Web.TextBox info2 { get; set; }

        [Mobilize.WebMap.Common.Attributes.Intercepted]
        private Mobilize.Web.TextBox info1 { get; set; }

 Notice the Intercepted attribute used extensively here. Interception in this instance is a way to handle "cross-cutting concerns" via Aspect Oriented Programming (AOP). Notice that all the visual objects in the C# version of our DataWindow have only get() and set() methods.The implementation is handled by the interceptors--all this witchery is made possible by the Roslyn compiler (AKA Visual Studio Compiler Platform) which in turn is why we rely on Visual Studio 2017 or 2019 for the ASP.NET Core code (as opposed to Visual Studio Code). Roslyn makes it possible to implement AOP in such a way that the code that we actually look at and work on is de-cluttered. 

The good news is you don't have to worry about it.But if you want to learn more, check out this short video by our own Rick LaPlant explaining how to use Roslyn to de-clutter your own code. You can learn more about how WebMAP implements AOP here (PDF) and here (blog post).

The view is not the model

Let's go back to our running Angular app again:

running_angular

I don't like the labels of "Info1", "Info2", and "Info3." While they are consistent with the original PowerBuilder app labels, they don't really tell me what they represent. I'd like the user to see more representative field names like "Contact", "Company", and "Address." And I'd like all the labels to be in a bold font, to make the upper DataWindow a little easier to read.

Every experienced web developer who is reading along now just snickered and thought something like, "Well, that's trivial in a web application. Just edit the HTML and CSS." 

Bingo.

So bear with me, because I know that many of our Dear Readers aren't experienced web developers. So they might enjoy seeing how simple this is. 

Unlike the original PowerBuilder app, or many legacy desktop apps written in various languages like Visual Basic or even C/Win32, the view/presentation/user interface is not part and parcel of the actual business logic. Instead, the view in this app is handled exclusively by the HTML and CSS, with Kendo UI and Angular behind the scenes doing the heavy lifting. The model--ie the internal business logic representation of all the visual elements--is in the ASP.NET Core C# code running on the web server. The model doesn't need to know anything about the view and the view doesn't need to know anything about the model: we have separation of concerns via the MVC (model view controller) pattern.

Ok, enough theory. Let's fix the UI. Fortunately this is not only easy, it's something someone untrained in C# or advanced programming can easily do. Here's how I like to do this. 

Modifying the UI

With the app running in Chrome, right click the "Info1:" label and select Inspect. This opens Chrome developer tools with the focus on that HTML element:

inspect

On the right we see all the HTML and the blue highlighted code tells us that the label is a member of the class "k-form-field info1_t" (k-form-field being from the Kendo UI for Angular component library we are using), that it is from the wmcontrols Angular library from WebMAP, and that the value is "Info1:". 

Let's change the label to "Contact":

Change name

Zooming in. Changing this:

html_mag1

Changes this:

form_mag1

So that's pretty easy--changing the HTML in the Chrome developer tools lets us see exactly how those changes will change the app running in the browser. Note this doesn't change our source code--if we refresh the browser our changes will be lost. This is a way to get things how we want them, or play around with the presentation. Once we know what the changes are, we have to make them in the original HTML and CSS files and rebuild the client-side code. 

Let's keep going. Notice that our label now bumps into the text box. Can we change the position? Remember the styling (including positioning) comes from our CSS file:

Info1_css

The controlling factor for our problem is the "left: 277px;" line. Remember that in order to have the migrated Angular app look as much as possible like the original PowerBuilder app we have to position all the elements precisely on the browser window--in this case these values (left and top) are offsets from the upper left corner of the browser's area for rendering HTML. If we didn't use the "absolute" property for the position element, these labels and text boxes could be scattered all over the browser space. 

Since our label .info1_t runs into the text box (info1), we need to shift its position left. Let's try a value of 250 pixels. How do we do that? It's not in the HTML, but Chrome developer tools also shows us all the styles, in their cascading order, like this:

left_position

Notice how the CSS matches what's in the view above. We can change it here to 250 pixels and see if that fixes our problem:

changing css

We can change anything we want if it's CSS: let's make the label bold. Notice how Chrome suggests things like Visual Studio's "Intellisense":

adding style for bold font

Clearly, we'd want to adjust everything so it aligns correctly, maybe make the label font a little larger and prominent, and so on. And we don't have to make notes about what we did: it's easy in Chrome to associate the original CSS and HTML source files--in our Angular folder--with the running app so all our changes are saved back into our project. All we have to do then is rebuild the wwwroot folder (using "ng build") and the app is automatically updated. We don't even have to recompile the ASP.NET Core solution in Visual Studio. 

Wrapping up

When I talk to developers about PowerBuilder, the first thing I usually hear is, "Wow, I can't believe how much PowerBuilder is still out there." If they have PowerBuilder, the second thing I usually hear is, "It's hard/difficult/impossible to find developers anymore." 

The world has changed. PowerBuilder belongs to a different, older world. Today it's all about cloud-native apps. If you've still got PowerBuilder, I hope this series of posts gives you some useful information about options to get rid of it. 

And have something far, far better.

Topics:application modernizationWeb Application DevelopmentPowerBuilder

Comments

Subscribe to Mobilize.Net Blog

More...

More...
FREE CODE ASSESSMENT TOOL