False firing of Canvas Control's MouseDown and Up events

I’ve started an app in which all the command buttons are made up from canvas controls. The first window (Window 1) contains six buttons forming the main menu. When I click on the first button in the main menu it causes a modal window (Window 2) to open containing some options and at the base of that window is a Back and Next button. If you click Next on that window it causes the modal window to hide, control returns to Window 1 which causes another modal window (Window 3) to be opened containing more options. The base of that window also contains a Back and Next button. I’ve designed the code so that clicking on the Back button in either window causes the previous window to redisplay and clicking the Next button causes the next window to display. Windows 2 and 3 are the same size, they appear in exactly the same place on the screen and the Back and Next buttons are in exactly the same place on both windows. So if I’m at Window 3 and I click the Back button it takes me to Window 2. Then I click Window 2s Back button and it takes me to Window 1, the main menu window. The very odd thing is that if I’m at Window 3 and I click on the Back button repeatedly and rapidly, I get the following cycle happening endlessly: Window 2, Window 3, Window 2, Window 3, Window 2, Window 3, Window 2, Window 3…!!! What I’ve discovered is that in this rapid button pressing situation, when Window 2 is displaying, though I’m clicking over the Back button, the Next button fires. I’ve checked my code thoroughly and can’t see what would cause that to happen. I’ve recorded a movie and then slowed it to 5% of the original speed to watch what happens, but it reveals no clue as to why this is happening. Does anyone know why a button that the pointer is not over would be fired when the mouse is over another button which is not firing?

I’ve recorded the described events in a movie and slowed it down so you can see the false firing of the Next button in Window 2: https://youtu.be/inllmPOsso4

The video is private :confused:

And - pretty please - make your description so that we can read it and is easy to understand. Your paragraph is way too dense to read.

Apologies re the private video. I thought it would let you see it but just not list it. I’ve made it public.

I’m with Beatrix… topics that are too much trouble to read have a tendency to be skipped over and not responded to.

I find it very hard to say what the issue could be based on what I understand from the text and see in the video.
Did you code any logic in the Paint events of the buttons? Does it work normally with regular buttons?

Maybe you can put some system.debuglog statements in the navigating logic so you can see what’s going on.

Show us the codez, please!

I created a subclass of Canvas called CanvasButton. The code in the Paint event of that subclass is:

  Dim lcolCaptionColor as Color
  Dim lintYPos as integer
  Dim lintFontSize as Integer
  
  // Works out colour button's caption to be displayed in.
  If Me.Enabled then //When button is enabled.
    If pintButtonNumMouseIsOver = mintButtonNum Then //When mouse is over this button.
      lcolCaptionColor = mconCaptionFontColor_MouseOver
    Else //Else when mouse is not over this button.
      lcolCaptionColor = mconCaptionFontColor_MouseNotOver
    end if
  else //When button is not enabled.
    lcolCaptionColor = mconCaptionFontColor_Disabled
  end if
  
  // Works out Y position of caption and font size based on size of button.
  If mintButtonSize = mconButtonSize_Lrg Then
    lintYPos = mconLrgButton_CaptionYPos
    lintFontSize = mconLrgButton_CaptionFontSize
  Else
    lintYPos = mconSmlButton_CaptionYPos
    lintFontSize = mconSmlButton_CaptionFontSize
  End if
  
  // Displays button's caption.
  DisplayButtonCaption(g, mstrButtonCaption, lintYPos, lcolCaptionColor, lintFontSize)

The code in the MouseUp event of that subclass is:

  // Returns picture in Canvas control to MouseUp picture.
  Me.Backdrop = If(mintButtonSize = mconButtonSize_Lrg, Button_Lrg_Up, Button_Sml_Up)
  
  // Records that mouse is no longer down.
  pbooMouseIsDownOnButton = False
  
  // Forces Action event in superclass to fire.
  RaiseEvent Action

The code in the method DisplayButtonCaption of that subclass is:

  Dim lintCaptionWidth As Integer
  
  // Sets properties that affect appearance of caption.
  g.TextFont=mconCaptionFont
  g.TextUnit=FontUnits.Point
  g.TextSize=lintTextSize
  g.ForeColor = lcolTextColor
  
  // Calculates width of caption so it can be horizontally positioned in the centre. Height calculation not done due to fact that Graphics
  //  object's Y argument does not position caption's baseline correctly when given statement g.Width+pintCaptionWidth)\\2.
  lintCaptionWidth =g.StringWidth(lstrCaption)
  
  // Displays caption.
  g.DrawString(lstrCaption, (g.Width-lintCaptionWidth)\\2, lintYPos)

The CanvasButton control that is the Back button for Windows 2 and 3 contains this code in the Action event:

  // Records button user pressed.
  penuButtonPressed = Buttons.BackButton //penuButtonPressed is a property that is private to this window.
  
  // Hides this window.
  Self.Hide

And the logic for showing the windows?

The first window asks the user to select which tables they want to learn. The second, whether the tables are to go up to 10 x the selected table or to 12 x the selected table.

The code that controls the opening of the windows is:

  Dim lwdwSelectTablesToLearn as SelectTablesToLearn
  Dim lwdwSelectMaxMultiplier as SelectMaxMultiplier
  Dim lwdwTypingSpeedAssessment as TypingSpeedAssessment
  Dim lwdwCurWindow as Window
   // Controls display of windows for Start Learning Tables menu option.
  // Loops until no Start Learning Tables window exists.
  Do
    // Based on window currently existing, selects next action.
    Select Case lwdwCurWindow
    Case Nil //No Start Learning Tables window exists yet so shows Select Tables to Learn window in modal form.
      lwdwSelectTablesToLearn = New SelectTablesToLearn
      lwdwCurWindow = lwdwSelectTablesToLearn
      lwdwSelectTablesToLearn.ShowModal
    Case IsA SelectTablesToLearn //Select Tables to Learn window is current window.
      If lwdwSelectTablesToLearn.penuButtonPressed = Buttons.NextButton Then //When user pressed Next button then shows Select Maximum Multiplier window.
        If lwdwSelectMaxMultiplier = Nil Then lwdwSelectMaxMultiplier = New SelectMaxMultiplier
        lwdwCurWindow = lwdwSelectMaxMultiplier
        lwdwSelectMaxMultiplier.ShowModal
      Else // Else when user pressed Back button then causes loop to be terminated.
        lwdwCurWindow = Nil
      End if
    Case IsA SelectMaxMultiplier //Select Maximum Multiplier window is current window.
      If lwdwSelectMaxMultiplier.penuButtonPressed = Buttons.NextButton Then //When user pressed Next button then shows Typing Speed Assessent window.
        If lwdwTypingSpeedAssessment = Nil then lwdwTypingSpeedAssessment = New TypingSpeedAssessment
        lwdwCurWindow = lwdwTypingSpeedAssessment
        lwdwTypingSpeedAssessment.ShowModal
      Else // User pressed Back button so Select Tables to Learn window is shown again.
        lwdwCurWindow = lwdwSelectTablesToLearn
        lwdwSelectTablesToLearn.ShowModal
      End if
    Case IsA TypingSpeedAssessment //Typing Speed Assessment is current window.
      If lwdwTypingSpeedAssessment.penuButtonPressed = Buttons.NextButton Then //When user pressed Next button then 
        //Code still to be added.
      Else // User pressed Back button so Select Maximum Multiplier window is shown again.
        lwdwCurWindow = lwdwSelectMaxMultiplier
        lwdwSelectMaxMultiplier.ShowModal
      End if
    End Select
  Loop Until lwdwCurWindow = Nil
  
  // Ensures all Start Learning Tables windows are destroyed.
  lwdwSelectTablesToLearn = Nil
  lwdwSelectMaxMultiplier = Nil
  lwdwTypingSpeedAssessment = Nil

Interesting design. What does the debugger show you when you click on one of your modal windows on the back button. I’d think that you have a logic flaw in how ShowModal works. But without seeing the complete project this is hard to say. But the problem can only be in the function that you posted last.

I just did an interesting experiment. I created a new project that had 3 windows. On the first window I put a Canvas control with the following code in the MouseUp event:

Window2.ShowModal

On Window2 I put two Canvas controls - one to act as a Back button and the other to act as a Next button. In the MouseUp event of the Back button I put:

Self.Hide

In the MouseUp event of the Next button I put:

Self.Hide
Window3.ShowModal

On Window3 I put one Canvas control - to act as the Back button. In the MouseUp event of that control I put:

Self.Hide
Window2.Show

I found that the same error occurred if I, in Window3, clicked on the Back button rapidly and repeatedly - Window2 would show then Window3 again then Window2 again and so on. I then changed the ShowModal statements to Modal and still the same error occurred.

That’s very good. Try to control your window hiding AND showing from the main loop.

Do the Next button on window2 and the back button on window3 happen to be in the same place on the screen? I’m having trouble understanding how you could “rapidly and repeatedly” click on the back button where the first thing you’re doing is hiding the window.

That said, I’m not sure that hiding an implicit window that has been shown with ShowModal is a good idea. I’d think you’d be much better off to either try:

  1. Close the windows instead of Hiding them
  2. Turn off the implicit flag and create the instances in code.

I’d say you’re misusing modals. Do what Beatrix suggested and show/hide the windows from the main window. Each modal should know nothing of the others. Make them as self contained / encapsulated as possible.

the hiding and showing of the windows is a jarring experience for the user.
The ‘next/previous’ look is commonly called a ‘wizard’ dialog.

Instead of having 5 or 6 windows, put their contents onto the pages of page panels on a single window
Use the next and previous buttons to change the active panel which will show the correct controls without opening and closing a window.

this is a similar window, if you ignore the list on the left;
https://forum.xojo.com/26550-duplicating-libreoffice-start-up-interface

Thanks, Beatrix and Tim. Yes, I agree with you. In my actual application I’m controlling it all from the main window.

Greg the Back button on Window 2 is in the exact same place as the Back button in Window 3. That’s the odd thing. The Next button in Window 2 is well away from the Back button and the pointer is nowhere near it at any time during the clicking. Also,thanks for the advice on implicit creation of a window. In my actual application I do create an instance of the window rather than creating it implicitly.

Jeff, I like you suggestion of using page panels. That will get around the whole issue. I think I’ll head that way.

Thanks all for your help. I really appreciate it!!!

Beside using panels, you may want to look at how you “Action” the Next button in Windows 2. I notice that when you are at Windows 3 (in the YouTube video) press the Back button to show Windows 2, immediately when Windows 2 is shown, the Next button in Windows 2 is fired without you clicking on it. This is why Windows 3 is showing repeatedly.

This will be due to using canvases as buttons.
You need to return true from the mouse clicks to prevent the click ‘passing through’ to other controls.
If they were just buttons, then clicking next would fire once.

so if they were just buttons:
In the ‘Next’ button action event you add 1 to the ‘thing to show’ counter, and show it.
The one and only ‘Previous’ button subtracts 1 from the ‘thing to show’, and shows it.
(Keeping in mind the edge conditions such as trying to go ‘next’ when already at the end)

Thanks, Cho. That’s exactly the problem. Seems to be a bug. The odd thing is that if I click slowly it doesn’t happen, but if I click quickly it does.

Jeff, thanks for your comments. I return True in the code of the MouseDown event. I’m wondering if my rapid clicking is being treated as a double click. I haven’t included the DoubleClick event in my code, but tried it and it didn’t make any difference.