Designing Interfaces for Windows and the Web

Andrew MacNeill

AKSEL Solutions

www.aksel.com

andrew@aksel.com

Overview

Interfaces are a lot like people – no two are ever really alike. That can present a problem for developers and end-users. On one hand, you want to provide a clear , consistent and easy to use experience for the user. On the other, you want to showcase your graphical design skills. This session goes through some of the issues confronting interface designers today and how we can best deal with them.

Building Intuitive Web-Like Applications

How intuitive is your application’s interface?  How difficult is it for a new user to figure out how to perform a task without asking for help? If you followed the Microsoft Interface guidelines up until recently, the answer may not exactly be what you thought it would be.

 

Since the advent of the graphical user interface and its acceptance by developers, we have been asking our users to follow a document-based approach but with a similar interface. All Office applications, for example, have a similar menu structure. The argument is that this will help reduce training because once a user learns one application; they will be familiar with all of the applications in the suite. Likewise with icons, the icon set displayed in figure 1 is fairly common in most applications so all users will know what to choose to create, open and print a document.

 

 

Figure 1- Common Menu and Toolbar. They may be familiar to us now, but are really that intuitive?

 

How Far we’ve Come, how far we have to go

To be fair, the graphical user interface has introduced a number of improvements that do make it easier to use: the tooltip, the status bar, context-sensitive help. It has also forced developers into providing “easier” tools like Wizards, “Startup” screens and even assistants. Like the icons and the menu system, all of these improvements still have to be “learned” to be used. As Visual FoxPro has made it easier to support these features, our applications have improved with it, luring us into a false view of how “easy” our applications really are.

Fact is, Microsoft’s view of an “intuitive” interface seems to change with every new release of Windows, leaving the rest of us to play catch-up. However, one of the approaches Microsoft has started taking with user interaction is something that we can easily emulate without having to wait for FoxPro to support it.

 

This new approach is easily evident in consumer tools such as Works and Office but is also making its way into the corporate world with Windows XP. Consider Windows 2000 and Windows XP. The two styles can be referred to as Inductive (Windows XP) and Deductive (older versions of Windows). The Windows XP interface is geared around the tasks that users do whereas the older interface requires users know (or to deduce) where to find the various options.

 

 

Managing users in Windows 2000 Administrative Tools. How do I add another user?

 

Updating user accounts in Windows XP. Which one would you prefer to work with?

 

Big Deal

Does this mean you should throw out your existing interfaces? Absolutely not. The two styles can live in harmony in the same application and they don’t really have to be that different. The inductive interface (or IUI for short) can be used with regular Windows dialogs or with the web-style pages and still provide the same benefit. When used it with regular windows dialogs, the interface takes on a more “wizard-style” approach but it still works. Think about how the builders make complex tasks easier in FoxPro.

 

Words are subjective but effective. Those who have worked in DOS will remember the famous “Abort, Retry, Fail” errors which have now been replaced with “Cancel, Retry, Ignore”.  Older applications used the word Quit everywhere until Exit became more popular. Small changes in terminology can make all the difference.

Building an effective inductive interface depends on the application however there are some basic guidelines that developers would do well to follow:

  • Focus a screen on a single task. This doesn't mean one option per screen but it does mean that additional tasks should take place on separate areas (see below).
  • State the task. Instead of using blanket terms like "Customer Screen", tell the user what they should be doing such as "Change Information About Joe's Auto Shop".
  • Match the screen to the task. A grid showing all of the customers is likely not the best way to show a customer edit screen.
  • Offer links to additional tasks. A user likely shouldn't be able to edit a customer's data and create an invoice for the customer all on the same page. But offering options to create an invoice on the same page makes it easy for users to find the options they need.
  • Use a consistent screen design. This is kind of obvious but it's important to realize how effective good color design can be.
  • Use Start Task screens. Our Customer Manager screen starts by suggesting what users might want to do with customers. Think about the first thing users see when they start your application. Then think about how they KNOW where to go to start.
  • Make the Next Step obvious. This is very similar to how Wizards work. You always see the Next button in front of you.

These guidelines may seem obvious but they can make for very effective screen design whether using a web or windows interface.

 

But I Want a Web Interface!!

 

The web-style interface is becoming more popular in many commercial applications and it’s not difficult to create. Before building new forms with white backgrounds and rebuilding class libraries, why not consider building it as a “web” page?

 

Web pages use HTML (HyperText Markup Language) which provides a wide array of formatting tools. The following text creates a nice, if plain, page.

 

<HTML>

<TITLE>Customer Maintenance</TITLE>

<H1>Working with Customers</H1>

What would you like to do?<br>

<li>Add a Customer

<li>Change a customer’s information

<li>Remove a Customer

</HTML>

 

By adding a few HTML keywords, the page can spring to life with color (see below). Save this file as CUSTOMERS.HTM.

 

 

A New Way of Working with Customers. A web-like interface is a great way of adding some new life to your application.

 

There’s only one problem - how to integrate it with your application. Enter the Browser object.

The Browser object lets you incorporate an HTML document onto a form, just like an ActiveX component. Call the Navigate method to specify the URL to display.

THISFORM.oBrowser.Navigate("http://www.aksel.com")

The Browser component includes many methods that correspond to events that take place on a web page. However, it also works for HTML files that may be found on a hard disk or network connection, even without a web server. Pass the full path to the file instead of a web site to the Navigate statement.

 

THISFORM.oBrowser.Navigate(FULLPATH("CUSTOMERS.HTM"))

 

Creating Links

 

In HTML, the <a> is used to create hyperlinks between documents. The same can apply to your web-based interface. I’ll change the options above to hyperlinks pointing them to a different page.

 

<li><a href= "addcust.htm">Add a Customer</a>

<li><a href= "modicust.htm">Change a customer’s information</a>

<li><a href = "remcust.htm">Remove a Customer</a>

 

This might mean you need to create a lot of HTML pages but there’s an easier solution: build them on the fly. The Browser component has a BeforeNavigate2 event that fires before the browser goes to the specified page. Put navigation or VFP specific code in there and you can run your browser form with minimal changes in your code. The only difference will be your application now uses a web page interface instead of a Windows-based one. Another great part of the web interface is that it uses Internet Explorer components, so you can use ActiveX components in it just like on a regular VFP form.

 

** Method BeforeNavigate2

LPARAMETERS pdisp, url, flags, targetframename, postdata, headers, cancel

 

LOCAL lcUrl

lcUrl = STRTRAN(UPPER(url),SET("DEFAULT")+CURDIR())

IF NOT ".HTM"$UPPER(lcUrl)

EXECSCRIPT(lcURL)

CANCEL = .t.

 

ENDIF

 

This code accomplishes two things: if the URL passed in the <a> is HTM, it will navigate to that page. If it doesn’t HTM, it will evaluate the expression in the URL as a VFP command. Setting the Cancel parameter to .T. tells the browser not to continue the navigation.

This code uses a class that generates temporary HTM files and a form named HomePage that includes the web browser control and the BeforeNavigate2 code.

 

poNav = CREATEOBJECT("CustomerFunctions")

 

DO FORM HomePage WITH poNav.GetForm("DEFAULT")

 

DEFINE CLASS poNav AS Custom

  

   PROCEDURE GetForm

    LPARAMETERS tcType

 

   lcText = ""

   lcHeader = "<HTML><TITLE>Customer Manager</TITLE>"

   lcFooter = "</HTML>

    DO CASE

        CASE tcType= "DEFAULT"

          lcText = ""

 

        CASE tcType= "MODIFY"

          lcText = "<table>"

          USE CUSTOMERS

          SCAN

              lcText = lcText + ;

                 [<tr><td><a href="poNav.EditCust(']+;

                 custcode + ;

                 [')">Change</a>] + custname +;

                  [</td></tr>] + CHR(13)

          ENDSCAN

         lcText = lcText + "</table>"

    ENDCASE

 

    lcTmp = SYS(3)+".HTM"

    =STRTOFILE(lcText,lcTmp)

   RETURN FULLPATH(lcTmp)

   ENDPROC

ENDDEFINE

With this simple form and the use of some nicely designed web pages, you can make your application have a web-based interface even when it’s running on a stand-alone desktop. The web style of interface is very comfortable for new users whose only interaction with a computer may have been browsing the web. It works especially well for presenting drill-down style reporting interfaces.

Gotchas

The first time you display a form using this approach, an "Unspecified error" may appear. You can get around this sporadic error by trapping for it in the Error method of the browser object.

Dealing with User Input

Most applications require some form of data entry. Even with a web-based interface, you can still run your regular VFP form (after all, you are still running on a desktop). This lets you gradually introduce a web-like interface into your application while maintaining the existing work you've done. Next month, we'll take a look at how to use HTML forms.

Many of you may already be using some form of inductive interface without using the actual term. In many ways, the inductive interface completes the circle that graphical interfaces have led us down. In the late 80s', a menu driven interface was one where an onscreen menu described a task with several words. This was replaced by the File menu which is now being replaced by a task driven interface where tasks are described with both pictures and text. Now, inductive interfaces are attempting to make tasks clearer and easier for users to find and understand.

The Customer Manager screen from last month offered a clean looking interface and a few hyperlinks that hooked directly to other code or forms. The page shown in figure 1 used background graphics to create a more visually appealing interface. The fastest way to build this style is to use a tool such as FrontPage and apply themes. Then copy the necessary graphics into your working directory.

 

<HTML>

<TITLE>Customer Maintenance</TITLE>

<body background="blegtext.gif">

<font face="Verdana">

<table border="0" cellpadding="0" cellspacing="0" bordercolor="#111111" width="100%" id="AutoNumber1">

<tr>

<td width="12%" valign="top">&nbsp;<p>

<b><font size="2">Other Tasks</b></p>

<p><font size="1">Create An Invoice</p>

<p><font size="1">Print a list of customers</p>

<p><font size="1">View outstanding balances</td>

<td width="88%">

<h2>Working with Customers</h2>

<p>What would you like to do?</p>

<table border="0" cellpadding="0" cellspacing="0" bordercolor="#111111" width="100%" id="AutoNumber2">

<tr><td width="4%">

<img border="0" src=" blebul1a.gif" width="15" height="15"></td>

<td width="96%"> <a href= "addcust.htm">Add a Customer</a>

</td></tr>

<tr><td width="4%">

<img border="0" src="blebul1a.gif" width="15" height="15"></td>

<td width="96%"><a href= "modicust.htm">Change a customer's information</a>

</td></tr>

<tr><td width="4%">

<img border="0" src="blebul1a.gif" width="15" height="15"></td>

<td width="96%"><a href = "remcust.htm">Remove a Customer</a>

</td></tr>

</table>

<p>&nbsp;</td>

</tr>

</table>

</font>

</HTML>

 When the user clicks on Add a Customer, the addcust.htm page is loaded. This form is very similar to the main page only it uses an HTML form behind it. The result is a form based completely in HTML (see below).

 

<form action="custsave(**)">

<table border="0" cellpadding="0" cellspacing="0" bordercolor="#111111" width="100%">

<tr>

 <td width="50%"><font size="2">What is the customer's name?</font></td>

<td width="50%"><input type="text" name="name">

</td></tr>

<tr>

<td width="50%"><font size="2">What company do they work for?</font>

</td>

<td width="50%"><input type="text" name="company">

</td></tr>

<tr>

<td width="50%"><font size="2">What is their email address?</font></td>

<td width="50%"><input type="text" name="email">

</td></tr>

<tr>

<td width="50%"><font size="2">&nbsp;</font></td>

<td width="50%"><input type="submit" value="Next">

</td></tr></table>

</form>

 

 

Entering on the web. There is no visible difference between what this page looks like and one that would exist on a web site.

 

In the Form tag, I refer to custsave(**) as an action. This is a reference to a program but it could have just as easily referred to an object method. The ** will be used for string replacements later in the code. When the user fills in the form and clicks the Next button, the Web Browser control will attempt to redirect control to whatever the action is.

Why Not Post The Page?

 

Those of you who are familiar with HTML might be wondering why we aren't posting the form to another page. HTML forms also have a Method call where you can specify how the information is sent to another page. By specifying a method of POST, this tells the Web Browser to send information to the new page in a different manner. Posting a form bypasses the Navigate event, removing our ability to hook into the event.  By catching the call in the BeforeNavigate2 event, we can simulate the effect of the post and use it within our own code.

The Web Browser control does not understand FoxPro commands therefore it is up to our form to trap the call to CustSave and run it ourselves. This is done in the BeforeNavigate2 event. This event fires prior to the Web Browser loading or moving to a new form. It is passed several parameters, most of which contain either blank or NULL values.

 

LPARAMETERS pdisp, url, flags, targetframename, postdata, headers, cancel

 

IF NOT ".HTM"$UPPER(url)

   ** Code specific stuff here

   cancel = .T.

 

ENDIF

The pDisp parameter refers to the actual web browser itself while the url parameter specifies the page that the browser is being directed to. In this case, we don't actually want the browser to attempt to load CustSave( ) as a page but instead to call the function itself. Set the Cancel parameter to .T. and the control will not attempt to load the page.

 

The next step is to learn what the entered values were. This is done by referring to the contents of the form. The document object contains a Forms collection for the multiple forms that you might have on a page. Each form contains Elements that refer to the individual contents. The following code creates a text string that contains the values for the fields on the form.

 

LOCAL lni,lcData,lcValue,lcName,loForm,lcCall

lcData = ""

IF pDisp.Document.Forms.Length>0

   loForm = pDisp.Document.Forms(0)

   FOR lni = 1 TO loForm.Elements.length

      lcValue = loForm.elements(lni-1).Value

      lcName = loForm.elements(lni-1).Name

      IF NOT EMPTY(lcValue) AND NOT EMPTY(lcName)

         lcData = lcData + lcValue + ","

      ENDIF

   ENDFOR

   ** Remove the last comma

   lcData = LEFT(lcData,LEN(lcData)-1)

ENDIF

 

** Remove full path for URL

lcURL = SUBSTR(URL,AT("\",url,OCCURS("\",url))+1)

lcCall = STRTRAN(lcurl,"**",lcData)

lcRet = EVALUATE(lcCall)  

THIS.Navigate(FULLPATH(lcRet))

 

The final piece of this code deals with the URL and calling of the function. When using the Web Browser control locally, the URL will always contain the path of the current directory. Once that is stripped out, the ** are replaced with the content of the form and then the function is called. The CUSTSAVE program is fairly small but it returns the name of the HTML file where the user will be pointed to next.

 

Getting Rid of HTML Files

If you consider the above code in a larger application, you might find yourself building lots of HTML pages to replace a few simple screens. One solution is to use properties of the Web Browser's document object and specify the HTML directly in there.

In the BeforeNavigate2 event, change the EVALUATE call to point to the desired object. This approach assumes that you have created methods. Change the Navigate statement to modify the InnerHTML property of the document to point to the desired content.

 

lcRet = poNav.&lcCall

THIS.Document.Body.Innerhtml = lcRet

 

The new class that is built to handle all of this creates a single HTM file named DEFAULT.HTM that acts as the main "Home" page for the application. From there, all of the code is handled within the class.

 

poNav = CREATEOBJECT("poCust")

DO FORM HomePage WITH poNav.GetForm("DEFAULT")

DEFINE CLASS poCust AS Custom

   Header = ""

   Footer = ""

PROCEDURE Init

   THIS.Header = "<HTML><TITLE>Customer Manager</TITLE>" + CHR(13) + ;

[<body background="blegtext.gif">] + CHR(13) + ;

[<font face="Verdana" font-size="10">] + CHR(13)

THIS.Header = THIS.Header + ;

[<table border="0" cellpadding="0" ] + ;

[cellspacing="0" bordercolor="#111111" width="100%">]  + CHR(13) + ;

[<tr><td width="12%" valign="top">&nbsp;<p>]  + CHR(13) + ;

[<td width="88%" valign="top">]

 

   THIS.Footer = "</HTML>

   ENDPROC

   PROCEDURE GetForm

    LPARAMETERS tcType

 

    DO CASE

        CASE tcType= "DEFAULT"

          lcText = [<a href="GetForm('ADD')">Add a customer</a>]

 

        CASE tcType= "ADD"

      ** Just using FILETOSTR for previous code above

        lcText = FILETOSTR("ADDCUST.HTM")

 

        CASE tcType="EDIT"

        CASE tcType= "MODIFY"

          lcText = "<table>"

          USE CUSTOMERS

          SCAN

              lcText = lcText + ;

                 [<tr><td><a href="GetForm('EDIT]+;

                 custcode + ;

                 [')">Change</a>] + custname +;

                  [</td></tr>] + CHR(13)

          ENDSCAN

         lcText = lcText + "</table>"

    ENDCASE

    IF tcType= "DEFAULT"

      STRTOFILE(THIS.Header+lcText+THIS.Footer,"DEFAULT.HTM")

      RETURN "DEFAULT.HTM"

   ELSE

   RETURN THIS.Header + lcText  + THIS.Footer

   ENDIF

   ENDPROC

 

   PROCEDURE CustSave

   LPARAMETERS tcName, tcCompany, tcEmail

 

   ** Table specific code for adding customers

   ** and other related information

   lcText = "Thank you for adding " + +;

      tcName + " of " + tcCompany + " as a customer."

   RETURN THIS.Header + lcText + THIS.Footer

ENDDEFINE

 

Now when the user adds a customer, the CustSave method is called and the user is presented with a pleasant Thank you message, all within a simply web-like user interface.

Ideas for the future

When building and responding to HTML forms, you might find that you want to change around the order of these fields. The above code requires that Name is followed by Company and then by Email. Instead, create an XML string in the BeforeNavigate2 event that is then provided to the form. This makes it easier to get around dealing with missing variables and new fields in the report.

 

lcData = "<xml>"

FOR lni = 1 TO loForm.Elements.length

   lcValue = loForm.elements(lni-1).Value

   lcName = loForm.elements(lni-1).Name

   IF NOT EMPTY(lcValue) AND NOT EMPTY(lcName)

      lcData = lcData + ;

        "<"+lcName+">"+lcValue + "</"+lcName+">"

   ENDIF

ENDFOR

lcData = lcData +"</xml>"

 

Be Aware of Changing Standards

Microsoft publishes logo guidelines with every new operating system or interface it releases. While many of the compliancy requirements are met simply by using Visual FoxPro as a development tool, there are usually some notable interface requirements that are designed to provide consistent experiences for end-users.

For example, Windows XP requires that applications support the new visual styles (of themes). In Visual FoxPro 8, theme support is built-in, although you can turn it off with _SCREEN.Themes = .F.

As well, Windows XP requires that applications support fast-switching and remote logins. While these may not make a big change to your interface, it does impact how you build your application. Fast-switching and remote logins mean that your application cannot always assume that the data files are always open after being idle for a period of time.

Other Ways Of Input

Another important design decision is how much you want to support new interfaces such as handwriting or voice. The Tablet PC provides built-in support for handwriting so there aren't a lot of changes required to support it in your application. Voice however, must be added more explicitly. While some voice recognition software would allow you to call menus and buttons by name, a true voice-activated application requires a bit more.

Here's some samples of how to make speech recognition work for you: Here is the base class definition generated by the Object Browser, required to understand basic words. The actual object for SAPI is SAPI SpeechLib found in the DLL noted below.

 

DEFINE CLASS myclass AS session OLEPUBLIC

 

IMPLEMENTS _ISpeechRecoContextEvents IN ;

  "c:\program files\common files\microsoft shared\speech\sapi.dll"

 

PROCEDURE _ISpeechRecoContextEvents_Recognition(;

    StreamNumber AS Number, ;

    StreamPosition AS VARIANT, ;

    RecognitionType AS VARIANT, ;

    Result AS VARIANT) AS VOID;

    HELPSTRING "Recognition"

 

   ? Result.PhraseInfo.GetText

 

ENDPROC

 

ENDDEFINE

 

I haven't included all of the procedures for the sake of brevity. The key method here is Recognition event. When a word or phrase is recognized, the Recognition event fires, being passed where the information is coming from (the stream) and the Resulting recognized phrase.

 

This next code hooks into this class using VFP's EVENTHANDLER method.

 

PUBLIC oRecognize, oVFPObj,ogrammar

 

oVFPObj = CREATEOBJECT("myclass")

oRecognize = CREATEOBJECT("SAPI.spsharedrecocontext")

 

EVENTHANDLER(oRecognize,oVFPObj)

 

The class spSharedRecoContext is simply the interface by which an application hooks into the Speech Recognition engine. It also controls which words and phrases are available for the user to speak. This code turns VFP into a Dictation machine.

 

oGrammar = oRecognize.CreateGrammar

oGrammar.DictationSetState(1)

 

The oGrammar object is what is actively used to recognize the words. The call to DictationSetState activates the Grammar object. Call DictationSetState with a 0 to deactivate it.

When this program is running, every word I speak into my headset is recognized and then displayed on the screen. The first time you do this, the accuracy leaves a little to be desired but you can "train" SAPI for each voice so it gets smarter. The following code starts making decisions based on what the user says.

 

lcCommand =  UPPER(Result.PhraseInfo.GetText)

     

DO CASE

   CASE "PICKUP"$lcCommand

   MESSAGEBOX("Pick up this load")     

 

   CASE "DELIVER"$lcCommand

   MESSAGEBOX("Deliver up this load")     

ENDCASE

         

That's all there is to getting started. Using the SAPI Recognition engine, you can allow dictation into memo fields or let your users run your application from their voice commands, all with just a few lines of code added to your application.

Test Your Ideas

Perhaps, the best piece of advice that can ever be given is similar to what developers must do with their code: test! Test! Test!

When embarking on a new interface design, try it out with as many different people as possible. This doesn't have to be a hugely expensive ordeal. While Microsoft and other companies may have glass rooms with two-way mirrors, you can do the same with much less.

Who To Test With

There is usually some debate as to who should test your new interface. Many developers tend to look for their most talkative user because they will tell them everything. However, any time you deal with existing users, be aware that they come with "baggage" – that is, they have already learned behaviours and may not be as open to new ideas. For that reason, three is a good number to work with.

  1. Another developer. Another developer will at least be able to tell you if something is way off base.
  2. An experienced user. An experienced user will allow you to see what pitfalls lie ahead for customers who are upgrading.
  3. The New User. Grab someone off the street and simply see what works and what doesn't.

 

The Testing Process

If you have a video camera or web camera, it might be useful to simply leave the person in the room on their own with the software and simply watch them. Barring that, simply sit with them and observe how they go through the interface. The most difficult part of the interface testing process is being quiet. There is a natural tendency to want to help the user learn how to do something. When testing a new interface, you want to sit back and simply watch. This way, you learn what works and what does not. For some people, it might be best to get someone else to do the observing.

Conclusion

The inductive user interface experience isn't useful for every application but it is a great way of making complex processes easier to understand. Building these interfaces with a web-like style also makes it easier to eventually port these applications to a web site, whether it be using an ISAPI or WebConnection approach or an Active Server Pages based interface. Many times, developers tend to look at new or other technology as being far removed from their current project. It doesn't have to be that way and building interfaces with the web browser control is one way of introducing yourself to web interfaces, without having to jump into an entire web-based application.

I hope this session has given you some ideas on how to build new interfaces but perhaps more importantly, how to make sure your new interfaces will make your product more usable and the user's experience a happy one.