by Stephan Grieger - GUI Computing
... and Happy Users make Happy Programmers!
Sometimes it's easy to lose a clear perspective on what you're really doing as a programmer. For instance, I've been involved in a long-term workflow project that makes use of a VB4 Explorer lookalike, dozens of OLE servers, replicated databases and so on. Some of the work is pretty damn clever, if I do say so myself - especially in the dreaded VB4/16!
So I don't know whether I was pleased, annoyed or just surprised when a pretty technical user (Ed's Note : I admit, it was me!) commented the other day that he really liked the way it was going... and promptly complemented an Outlook bar lookalike that took me all of 10 minutes to do! It got me to thinking anew about the importance of those small finishing touches in an app to the way users accept the app - especially when you just know you're going to be competing with the likes of Office 97.
It's common for many programmers (including me at times) to forget that and to just concentrate on functionality. After all, if the users are getting what they want, surely all those peripheral things don't really make any difference? I suspect that's wrong, in the main. I suspect that, given a base level of functionality, users in general are after a feeling of comfort, and they get that from an app which comes with "all the trimmings". They probably even complain less.
That said, here are some of the simple things, that probably make my life easier...
Start by creating a form called frmToolTip. This is the form that will be displayed when a tool tip is to be shown. The reason we create a separate form is twofold. First, we can use the form for the entire application instead of having to place, say, a label control on each form - nice and generic. Second, it means that if the tooltip is to be displayed under the mouse pointer or control, and that just happens to be off the calling form, then we don't have any extra coding to do..
The form should have the following properties :
BackColor = &H00C0FFFF& BorderStyle = 1 - Fixed Single Caption = ControlBox = False Height = 600 MaxButton = False MinButton = False ScaleMode = 1 - Twip
Now place a label control on the form at position 0,0. Its properties are :
AutoSize = True BackColor = &H00C0FFFF& BackStyle = 1 = Opaque Borderstyle = 0 - None Caption = "Tooltips" ' This is so we can see and select the label box when in AutoSize mode. Name = lblToolTip
That's it for the actual tooltip. There is no need to place any code inside the form, as it will be controlled from elsewhere. The actual APIs' and Structures you will be needing are:
Type PointAPI X As Integer Y As Integer End Type 'PointAPI Declare Sub GetCursorPos Lib "user" (lpPoint As PointAPI) Declare Function WindowFromPoint Lib "user" (ByVal PointStructX%, _ ByVal PointStructY%) As Integer Declare Function ShowWindow Lib "user" (ByVal hWnd As Integer, ByVal nCmdShow _ As Integer) As Integer
On the form that is going to have ToolTips enabled, we want to place a timer control. The control should have its Interval property set to something like 1000 for 1 second.
Inside its Timer event, place the following code:
Dim PointStruct As PointAPI Static PreviousHwnd%, CurrenthWnd% Dim TipText$ 'Show the tool text. Call GetCursorPos(PointStruct) CurrenthWnd% = WindowFromPoint(PointStruct.Y, PointStruct.X) If CurrenthWnd% <> PreviousHwnd% Then PreviousHwnd% = CurrenthWnd% timToolTip.Interval = 1 Select Case CurrenthWnd% Case cmdReferenceDropdown.hWnd TipText$ = cmdReferenceDropdown.Tag Case cmdBackOneLevel.hWnd TipText$ = cmdBackOneLevel.Tag Case stsTime.hWnd TipText$ = "* " & Format$(Now, "dddd, dd mmmm yyyy") End Select If TipText <> "" Then ShowHelpTip TipText$ Else Unload frmToolTip End If If Len(TipText$) = 0 Then timToolTip.Interval = 1000 End If End If
The case statement above determines which control the mouse is currently over by its hWnd. Each control that is going to have a tooltip needs to have a reference here. There is probably a better way of doing this but, hey, it works.
The last case select I have here is a small box in the bottom right hand corner of the screen which displays the current time. When the mouse is over that, the current date is displayed. Exactly like in the Windows '95 Task Bar.
The control's 'Tip Text' is stored with the controls Tag property in my case. This seemed the best place as I wasn't using this property for anything else anyway.
The next function to insert is the one that will display the actual tooltip in the correct location.
Public Sub ShowHelpTip(TipText$) 'Dim a few variables. Dim PointStruct As PointAPI Dim TopOffset, LeftOffset Dim er% Dim Above As Integer 'Show the tool tip. If Len(TipText$) <> 0 Then frmToolTip.Hide If Left$(TipText$, 1) <> "*" Then frmToolTip.lblToolTip.Caption = TipText$ Above = False TopOffset = 18 LeftOffset = -2 Else frmToolTip.lblToolTip.Caption = Mid$(TipText$, 2, Len(TipText$)) Above = True TopOffset = -18 LeftOffset = (((frmToolTip.Width * -1) / Screen.TwipsPerPixelX) / 1.5) - 2 End If Call GetCursorPos(PointStruct) If (PointStruct.Y + TopOffset) * Screen.TwipsPerPixelY < Screen.Height Then frmToolTip.Top = (PointStruct.Y + TopOffset) * Screen.TwipsPerPixelY Else frmToolTip.Top = (PointStruct.Y - TopOffset) * Screen.TwipsPerPixelY End If frmToolTip.Left = (PointStruct.X + LeftOffset) * Screen.TwipsPerPixelY frmToolTip.Width = frmToolTip.lblToolTip.Width + 4 * Screen.TwipsPerPixelX frmToolTip.Height = frmToolTip.lblToolTip.Height + 2 * Screen.TwipsPerPixelY frmToolTip.ZOrder er% = ShowWindow(frmToolTip.hWnd, 4) Else frmToolTip.Hide End If End Sub
|Basically we get the current cursor position and we move the Tool Tip form to that location so that the Tool Tip appears just under the cursor. We also size the form to the size of the Tip Text. It's a good idea to place a space before the actual Tip Text as this puts a nice space between the left of the form and the text.|
A recent file list, recent recordset list or a recent anything list can add a great deal to the usability of your application with actually very little effort.
The basic idea is that everytime a user accesses a major part of the application like say a document or some such, that the reference to that is added to a recent list in the file menu. For examples, look at any Microsoft application. Users are getting used to the idea that if they open a document or record that they will be able to open it again from the file menu without first having to locate it again.
Let's take the example where a user accesses a particular clients record on a daily bases because they are the users biggest client and they are constantly changing the information for that client. Lets say also, that to bring up that clients record, they have to go through two or more steps - open a find window, find the record, open the record. Pretty time consuming and a silly waste of time every time.
Obviously, that record would have a unique Id that you get to locate the record. If you were to get the Client's name and the ID, you would have a direct link to that record and you could bypass the find window altogether.
The method I use in my applications is to create a string with the name and ID and place that into an array. I then place the new item at the top of the array, move the rest down and drop off the last one. Secondly, I loop through the array, placing the names into the file menu. When the user selects the first menu, I can easily assume that it will be element 1 of the array and can therefore get the client's ID and display the record.
When my application closes down, I write the array to an INI file which also gets loaded at startup ensuring that the list is persistent.
Now, I could give you the code which I use in my application but it would be less than useful to yourself. So, here are some of the things you will need to do to make it happen in general terms.
From your main form, you will need to create a menu item under the file menu called something like mnuRecent and give it an index of zero. This creates a menu array identical to a control array. Loading menus is then done via the Load command. ie: Load frmMain.mnuRecent(Index).
Saving the list to an INI file is obviously done via the WritePrivateProfileString API. You could, if you were adventurous, replace the existing entries in the INI file with the new. Personally, as this method is very fast, I prefer to scratch the entire section and start again.
er% = WritePrivateProfileString("RecentList", 0&, 0&, App.Path & "\MyApp.ini") For er% = 1 To UBound(RecentList) If RecentList(er%).Heading <> "" Then tmp$ = RecentList(er%) Call WriteToIni("RecentList", "Item" &er%, tmp$, App.Path & "\MyApp.ini") End If Next er%
The first line in this code will take the section RecentList in the ini file and remove any Keys from it. I then loop through my recent list putting them back in the new order that was generated during the time the user used my application. A lot neater and more importantly easier.
WriteToIni is a simple function I wrote which writes an entry to an INI file :
Sub WriteToIni(AppName, keyName As String, Value As String, FileName$) Dim er% er% = WritePrivateProfileString(AppName, keyName, Value, FileName$) End Sub
AppName is the entry in square brackets and KeyName is the key to the left of the equals sign.
The code behind the menu(s) simply calls the routine you use to bring up the client's record. You already have the client's ID so you need only pass it to the function.
Now, if you want to get really flash, you could have the option there for the user to place the recent item, or Shortcut, onto the desktop. Not a problem. Write a small VB app which has your string as a command line argument. When it launches it hooks up to your application via OLE, passes the string to the app, your app then loads the client screen and the shortcut app closes down.
When you create the desktop item, copy the VB app to the desktop directory, name it according to the client name and give it the command line argument. Nothing easier and very very cool. Of course, you could go the extra mile and create an actual Windows shortcut using the 32bit API but then that's just me.
(Ed's Note : The Editor disavows any knowledge of, and does not condone, the incredibly slipshod approach to documentation displayed herein. The fact that he, too, is guilty, is irrelevant.)
If you're anything like me, while you are coding your application you place little things into the app which make it easy for you to do something. For example, to delete a line in a grid you may have a delete button, but during the debugging process, you get so frustrated with having to press it to test that you write a small shortcut like Ctrl Del which calls the button's code.
Then you think to yourself (or you simply forget about it) that it's quite a nice little feature. Now you are faced with the extra task of documenting it in the User Manual. Well, forget the User Manual. Don't worry about it. The button is documented, the code was put there because you were getting lazy. But you still want to tell the user.
Tip-of-the-Day is a really great way of informing the user of all those little features you built in during coding.
The Window is as simple as they come. See below.
|Basically, an Image control, Picture
Box, Labels, Buttons etc. It chooses a randon tip text
from an array and displays it on screen. My actual form
is included with this article.
If you want to get a little more flash, then turn the tip form into an OLE Server which is called by your applications. You could then pass the tip to it making it truly generic.Of course, Next Tip and Previous Tip would need to pass a message back to the calling application to request a new tip, but that's fairly simple to do anyway.
The important things to remember are :
I think this is where we came in....
|Some of you may be using Microsoft
Outlook as your mail client rather than Exchange. I make
no comment on this choice. Well, actually, I do. Often.
But that's another story.
If you are, then you will have noticed that nice-looking bar to the left of the window which has icons in it. These icons, of course, do not have a border until you move over them with the mouse, as with all the Office 97 stuff. This is trendy, state-of-the-art stuff, guaranteed to to look sexy. The effect is also remarkably easy to attain. My kind of effect!
First, place a picture box on your form and size it to look like the icon bar. Make the background a color to dull boring CUA grey. Make sure that the AutoRedraw property is set to False. I'll explain why in a second.
Next, place as many Image controls inside the picture box as there are icons to show. The pictures you place inside these image controls should be Icons and not bitmaps, as icons can contain a transparent pixel, which shows the background through. Bitmaps do not have this functionality and it means that if you decide to change the color of the icon bar you do not need to change all the icons as well.
Next comes the "code". Inside the icons place the following code (or something very like it) into the MouseMove event.
icoPic1.BorderStyle = 1 icoPic2.BorderStyle = 0 icoPic3.BorderStyle = 0 picBar.ForeColor = &HC0C0C0 picBar.Line (Pic1.Left, Pic1.Top)-(Pic1.Width + Pic1.Left, Pic1.Top) picBar.Line (Pic1.Left, Pic1.Top)-(Pic1.Left, Pic1.Top + Pic1.Height)
In my app, I only have three icons so I can pretty much set them all as I have instead of looping through the control array.
PicBar is the picture box which contains my icons. Pic1 is the first image control. All I am doing is setting the borderstyle of this image to single and the rest to none. Beacuse PicBar has its AutoRedraw property set to False, any lines I drew previously are now going to be erased. Had I set it to True, I would need to draw over the lines with the back color of the picture box to remove them. Too hard!
I then draw a line to the top and left of the icon. Because I drew the lines after I set the border style, the lines will appear over the image control's border. In this way, the lines act as the top and left sides and the image control's own border acts as the right and the bottom lines. There is no need to draw the bottom and right lines - the benefits of applied laziness!
There are problems with this when the picture box tries to refresh itself and the borderstyle of the image is still one, but there are ways around that too.
Now all you need to do is place code into the surrounding controls, including the icon bar, which sets all the borderstyles of all the icons to 0 again. This will not only get rid of the image controls' border when you move off the control, but also the lines that you drew. Again, this is because AutoRedraw is set to False.
As I said, 10 minutes work.
And if it stops the user whingeing and carrying on (Ed's comment : He means "prevents adverse user feedback"), then I'm all for it.