Friday, December 28, 2007

SnTT: Hiding an ActiveX control when you hide its layer

My friend Adam (aka Wombat) is working on an app for doing employee badges. The badges will have the employee photo on them, so the plan is for the HR people to take pictures of employees, crop them down, and print out the badge on a special printer. Adam already has an employee management system built in Notes, but he had never worked with image editing before. I pointed him in the direction of an imaging component. He settled on one, figured out how to make it work in Notes, then started adding it to his employee management system.

The users wanted to just click a button, have something pop up that would let them select the picture and crop it, then they just save it. That was easy enough if he launched a separate document, but that was a little intrusive. His first approach was to put the control on a form and call it using NotesUIWorkspace.DialogBox. However, ActiveX controls won't display at all if you use DialogBox.

The next approach was to use a layer, which worked... mostly. The problem was the control would remain visible after the layer was hidden:


Adam figured out that by adding a call to MessageBox after RefreshHideFormulas the control (the big box with DEMO in the center above) would disappear. He was okay with that solution, but it was another box for the users to click and I knew there had to be something less intrusive.

He sent the app to me and after extensive testing I learned that any change in focus would cause it to render properly. Eventually I stumbled across using Tab, which would consistently make the artifact disappear. Then the great hunt for how to get Tab to work.

Adding SendKeys "{TAB}" after the RefreshHideFormulas call always generated an "Invalid Procedure call" error. I went through everything I could think of short of closing and reopening the document -- which would have cleared the artifact, but was a little too harsh for such a simple thing. No matter what I still got an "Invalid Procedure Call". I finally decided to dig into my API toolkit. And after numerous failed attempts I decided to get medieval and dragged out keybd_event:

Declare Sub keybd_event Lib "user32" (Byval bVk As Byte, Byval bScan As Byte, Byval dwFlags As Long, Byval dwExtraInfo As Long)
Const VK_TAB = &H9

Then to call it:

keybd_event VK_TAB, 1, 0, 0

What it does is it stuffs the keyboard buffer with a Tab, and executes it immediately. That's what SendKeys supposedly does, too, but this works! No more weird artifacts, no errors, it just silently does its job. It isn't cross-platform, but Adam is in a Windows-only environment so this was an acceptable solution for him. He's using an ActiveX control so it's definitely not cross-platform anyway.

You can easily extend Notes with ActiveX controls, but presenting them in a way that makes them easy for users to interact with can be a little tricky. Maybe one day IBM will make controls on DialogBoxes display properly, and perhaps they'll get them to hide when you hide their containing layer. Until then hopefully this tip will help anyone else who has struggled with a similar problem. :-)

3 comments:

  1. Why not NotesUIDocument.gotoField("someField") after the RefreshHideFormulas?

    ReplyDelete
  2. For whatever reason uidoc.GotoField("somefield") alone does not work. Likewise using the keyboard event alone doesn't seem to work either.

    So you end up with the end code being more like

    Call uidoc.FieldSetText( "HideLayer3" , "1")
    Call uidoc.RefreshHideFormulas
    Call uidoc.GotoField("someField")
    keybd_event VK_TAB, 1, 0, 0

    ReplyDelete
  3. Good catch, Adam. Sorry I forgot to put it all together. :-)

    @Nate - There has to be an actual keypress or click involved. I don't understand why, but you could reset the field focus to everything on the form and it still won't work. Even using the API call SetActiveWindow doesn't force the type of refresh that causes Notes to hide the ActiveX control. Another thing we discovered is you have to set the focus to a field that is not the last in the tab order before you call keybd_event. I'll fully admit this is a weird hack, but at least it's transparent to the user.

    ReplyDelete