Click me
Click me

Click me

Windows to web: adding an event handler to a WebMAP app

by John Browne, on Oct 4, 2016 11:15:28 AM

You may be troubled by the seeming complexity of web applications compared to a relatively-simple Windows desktop (ie .NET or VB) app. I hear this from time to time when our customers tell me they are uncertain about using WebMAP to modernize their VB6 or Winforms apps to the web.

Certainly there are more files, more dependent pieces, and more languages involved, but it's also a more complex problem to solve.

In this post I want to take a simple .NET app, migrate it to the web, then demonstrate adding an event handler--the sort of thing you would do after deploying a web version as you maintain it or enhance it.  I get that this is a trivial example, but it's useful to begin the conversation. We'll add a control (a more typical scenario) in a subsequent article.

Let's get started

Here's a simple app created with Visual Studio 2013 (C#/Winforms template). There's a label, textbox, and command button. No code, just a few properties set. Looks like this:

form1_cs.jpg 

After migrating it with WebMAP (using the Angular/Bootstrap option), it looks like this running in Chrome:

Angular1.jpg

Since this app has no event handlers, it doesn't actually do anything. Let's look at the network traffic (using Fiddler from Telerik) when the application loads:

fiddler1.jpg

You can see that on app startup all the supporting pieces have to be fetched from the server (bootstrap, jquery, angular, etc. plus some of our client-side helper classes. Next is request to start the session: GET http://localhost:54486/Home/AppState HTTP/1.1; the response is JSON to create the form in the browser. Since nothing is listening to any of the controls, clicking on the command button "Click me" doesn't fire any events. 

Form1 code

This is really an empty app skeleton, since it has some objects but does nothing. Let's look at the original C# code for Form1:

Form1.cs:

using System.Windows.Forms;
namespace SampleApp
{
    public partial class Form1 : Form
    {
        public Form1()
        {
            InitializeComponent();
        }
    }
}

which you can see does nothing beyond initialization. Once we migrate the app to the web with WebMAP, we get the following code in our Form1.cs file:

Form1.cs (migrated):

namespace SimpleApp
{
    public partial class Form1
    : UpgradeHelpers.Interfaces.ILogicView<SimpleApp.ViewModels.Form1ViewModel>, UpgradeHelpers.Interfaces.IInitializable
    {

        public SimpleApp.ViewModels.Form1ViewModel ViewModel { get; set; }

There's actually a lot more code, but that's the relevant bit. Notice the big difference is we are using a ViewModel of the object, not the object (Form1) itself. Again, the app actually does nothing. Yet.

Handling the event

Ok, let's create a simple event handler for the button--we'll display "Clicked" in the textbox.

Remember this is an MVC app (model view controller). Mostly. The difference is that WebMAP MIGRATES existing Winforms/C# apps which have all their business logic in C# files--frequently a file for each form in the app. This "code behind" style of programming is quite common in older client server apps. One of the benefits of WebMAP is that it preserves your business logic so it doesn't introduce new logic errors. What WebMAP does do is to abstract those UI elements that will be in the client with ViewModels, so we see the pattern in code snippet above.

To add our event handler, we have to do two things. First of all, we need to capture the event and handle it. In this case, we will set the ViewModel of the text box to have it's text property set to "Clicked:" 

ViewModel.textBox1.Text = "Clicked";

Here's the complete code to add (Form1.cs):

        
        internal void button1_Click(object sender, System.EventArgs e)
        {
            ViewModel.textBox1.Text = "Clicked";
        }

    }

Now we have to modify the controller to create an action result for the button1_Click event. Here's the applicable code from Form1Controller.cs, after adding our event handler:

public class Form1Controller
        : System.Web.Mvc.Controller
    {

        public System.Web.Mvc.ActionResult button1_Click(SimpleApp.ViewModels.Form1ViewModel viewFromClient, object eventSender)
        {
            logic.ViewModel = viewFromClient;
            logic.button1_Click(null, null);
            return new UpgradeHelpers.WebMap.Server.AppChanges();
        }

Notice it returns a object that contains the changes to the application state for processing. WebMAP keeps track of all changes to state on both the client and server sides and synchronizes them. 

Do we need to modify the model (remember, this is MVC)? Let's look at the file.

We already have a viewmodel for the button, along with all the other controls on the form.

Form1ViewModel.cs:

public virtual void Build(UpgradeHelpers.Interfaces.IIocContainer ctx)
        {
            this.textBox1 = ctx.Resolve<UpgradeHelpers.BasicViewModels.TextBoxViewModel>();
            this.button1 = ctx.Resolve<UpgradeHelpers.BasicViewModels.ButtonViewModel>();
            this.label1 = ctx.Resolve<UpgradeHelpers.BasicViewModels.LabelViewModel>();
            Name = "SimpleApp.Form1";
        }

Do we need to make a change? No, because we're not changing the model or the view. No new objects are being added to the user interface. The view doesn't change--we already have all the UI being displayed on the form. All the event handler is doing is setting a property on the textbox control:

ViewModel.textBox1.Text = "Clicked";

Almost there

So now the question is, how does the client know that it has to do something? The key is in this file: SimpleApp.Form1.ts, where we add some code to process the event: 

this.ControllerName = "Form1";
            (<any>this.$scope).button1_Click = () => this.button1_Click();
        }
        public button1_Click() {
          
            return this.webmapapp.sendAction({vmid: this.uniqueId,controller:this.ControllerName,action:"button1_Click"});            
        }
    }
    return Form1Controller;
});

Note that when the button is clicked we have a sendAction for the JavaScript on the client ("action:"button1_Click"). 

And finally, we have to modify the generated HTML (SimpleApp.Form1.cshtml):

<button
        id="button1"
        class=" button1 btn"
        ng-disabled="!model.button1.Enabled"
        type="button"
        ng-click="button1_Click()"
        ng-show="model.button1.Visible">Click me</button>

 

And we're done

That's all there is to it. By adding a few lines of code, and rebuilding our application, we get the following result in Firefox on clicking the button:

clicked.jpg

If we go back to Fiddler, we can now see when we click the button the client sends this HTML:

POST /Form1/button1_Click/ HTTP/1.1

And the server returns this JSON:

{"VD":{},"MD":[{"Text":"Clicked","UniqueID":"textBox1#2"}],"RM":[],"MDT":[158]} 

Which basically tells the client to set the Text property of the object textBox1 to "Clicked".

Last words

To recap, our areas of change included the following:

  • Updated our logic file (Form1.cs) to handle the click event
  • Updated the controller (Form1Controller.cs) to create an action method
  • Updated the client side typescript file (SimpleApp.Form1.ts)
  • Added the Angular ng-click directive to the HTML (SimpleApp.Form1.cshtml).

Admittedly this was a simple problem (adding an event handler). Normally you wouldn't even have a button with no event handler in your Windows app. But this shows you the basics of how to modify the code in the WebMAP version. In a future blog post I'll walk through the steps of adding a new control and handling events on it. Got questions? Need to see it with your code? Drop us a line so we can help.

 

Comments

Subscribe to Mobilize.Net Blog

More...
FREE CODE ANALYSIS TOOL