Listbox GotFocus problem

In a window, I have a listbox and several text areas. When I tab into the listbox from one of the other controls, I’d like to go directly to row 0 column 1 and set the cell to edit mode. I had been using this code:

Sub GotFocus() Handles GotFocus
    'Check that Listbox has at least one row
    if me.LastIndex>=0 then me.EditCell(0,1)
End Sub

This seemed to be working, except that I’ve now discovered that it’s interfering with the CellClick event handler:

Function CellClick(row as Integer, column as Integer, x as Integer, y as Integer) Handles CellClick as Boolean
    if column=1 then
      me.EditCell(row,column)
    end if
End Function

If the listbox contains more rows than can be displayed, there’s a problem when clicking on one of the lower rows. For example, suppose the listbox contains 8 rows but displays only 5 rows. If I scroll down to the bottom and then click on the bottom row (row index 7), with the intention of editing it, the GotFocus event triggers, and scrolls the listbox to the top, and then the CellClick event receives row index 4 as the argument instead of 7. Hence cell (4,1) goes into edit mode instead of cell(7,1).

How do I change the GotFocus handler to prevent it from scrolling up if a cell click occurs in a lower cell?

I meant to add, that I find this part puzzling:
If a cell in the listbox already has focus, then why would the GotFocus event trigger again by clicking another cell. I would expect only the CellGotFocus event to trigger.

Not sure about focus, but you should be using ListIndex instead of LastIndex.

I used LastIndex, because I want to make sure the listbox isn’t empty before attempting to do the EditCell(0,1), in order to avoid an exception.

Use ListCount for that

I can do that, but that’s not the problem. That part works just fine.

The part that doesn’t work is that there doesn’t seem to be any way in the GotFocus event to tell whether the listbox already had focus. That’s what I find very strange about the GotFocus event. It seems to me that it should only trigger when the listbox first gets focus, not when it already has focus, and you click on another cell in the same listbox.

BTW if you are editing a cell, what you are REALLY doing is editing an overlaid TextField Or TextArea, and that TextField gets the focus, which could be generating a ListBox.GotFocus every time you finish editing a “cell”.

  • Karen

Thanks. I was trying to find in the docs where and when and in what order the various events get fired but wasn’t able to locate anything.

I’m probably not explaining the problem very well, so I’ve uploaded an example project that demonstrates what I’m trying to do.
When you run the application, you get a window with two text areas and a list box. If you hit the tab key repeatedly, focus will go from the top listbox, then through each editable cell in the listbox, and then finally to the second text area. So far so good. This is how I want it to behave.

Also, if you click in various cells in the righthand column of the list box the cell will go into edit mode. Again, this is how I want it to behave.

Now, scroll the listbox down to the last row (“oranges”) and click into the right hand cell of that row. Notice that the listbox scrolls back up and instead of editing the “oranges” data, you are in the “grapefruit” row. I assume that this is because both the GotFocus event and the CellClick events are firing and the CellClick handler gets the wrong row number because of this. If I comment out the code in the GotFocus handler, then clicking the the cells works properly, but the tabbing doesn’t work the way I want it to.

The problem is, indeed, that ListBox.GotFocus is being called even when the listbox has focus. Your code calls Edit on row 0, which causes the listbox to scroll to the top. One workaround would be to change your GotFocus code to

  if me.LastIndex>=0 then
    me.EditCell(me.ScrollPosition,1)
  end if

I don’t know if that would be acceptable when tabbing through the controls, as it could skip rows of the listbox if the user had already scrolled down. A further workaround would be to capture the Tab key in txArea1 KeyDown and scroll the listbox back to the top. In that case, your GotFocus code could be

  if me.ListCount>0 then
    if me.ListIndex< 0 then me.ListIndex = me.ScrollPosition
    me.EditCell(me.ListIndex,1)
  end if

Note that this problem (GotFocus firing on click even when the listbox has focus) has been around for a long time. It manifests in RealStudio 2010r5.1, which is the oldest version I currently have loaded.

Thanks Tim. I’ll try your workarounds tomorrow.

Okay, this make more sense now. I couldn’t think of any legitimate reason for GotFocus continuing to fire when it already had focus, and was wondering what I might be missing. Is there a bug report on this? I couldn’t find one.

Make a Boolean property to track the listbox focus status. Implement LostFocus to track when the listbox looses focus. Add the Boolean state on the conditional within GotFocus for the listbox.

[code]Sub GotFocus()
If Me.LastIndex>=0 And NOT varLbHasFocus Then
Me.EditCell(0,1)
varLbHasFocus = True
End If
End Sub

Sub LostFocus()
'varLbHasFocus = False
End Sub
[/code]

Note that the CellFocus seem to “take away” the focus from the Listbox initially, so sometimes the code seems to fail still. If you reset the varLbHasFocus on the other controls GotFocus events (instead of the listbox LostFocus event), it seems to work better.

<listbox>
Sub GotFocus()
  If Me.LastIndex>=0 And NOT varLbHasFocus Then
    Me.EditCell(0,1)
    varLbHasFocus = True
  End If
End Sub

<textarea1>
Sub GotFocus()
  varLbHasFocus = False
End Sub

<textarea2>
Sub GotFocus()
  varLbHasFocus = False
End Sub

On closer look, it’s the interaction between your CellClick and GotFocus code. You’re forcing an edit in both, when that happens you get the listbox losing focus while the cell is being edited and then gaining focus again when the cell loses focus. That’s what is causing multiple GetFocus events. A normal listbox doesn’t display this problem.

I think I said that! :wink:

Right. It took a while for that to sink in. I eventually decided to add some code to log all of the event handlers so that I could see the order in which they are triggered, and what their parameters are. That’s when I finally understood what you were saying. From a focus point of view, the cell is an independent entity from the listbox. So, when one has focus, then the other one doesn’t. I had initially assumed that cell focus was nested inside listbox focus.

When tabbing from an external control to the listbox, with my GotFocus handler code that jumps to cell(0,1), the Sequence of events is:
GotFocus
LostFocus
CellGotFocus (0, 1)

Then, when tabbing from cell to cell the sequence of events is:
KeyDown (0, 1)
CellAction (0, 1)
CellLostFocus (0, 1)
CellGotFocus (1, 1)

So, when tabbing between cells, there is no GotFocus/LostFocus for the listbox, just for the cells themselves.

When clicking directly into a cell with the mouse, this is what happens:
GotFocus
LostFocus
CellGotFocus (0, 1) (goes to cell (0,1) due to GotFocus code, regardless of the cell clicked on)
CellAction (0, 1) (exits edit mode of cell (0,1) due to CellClick event.)
CellLostFocus (0, 1) (leaves cell (0,1) due to CellClick event.)
CellClick (1, 1) (arrives at the cell that was actually clicked on.)
CellGotFocus (3, 1) (these parameters may be incorrect if the listbox scrolled due to the click.)

Note that the first three events are identical to what happens when tabbing into the listbox from an external control.
Also complicating things, when the GotFocus handler calls the EditCell method, that triggers the CellGotFocus event, and so the remaining code in the GotFocus handler is suspended until the cellGotFocus handler finishes.

Eventually, I figured that if I added a bit of code to the GotFocus handler to check the scroll position before and after calling EditCell method, and saving the difference as a scrollOffset property, the CellClick handler could use this value to correct the row number. This is the code that I ended up with:

Sub GotFocus() Handles GotFocus
  dim sp As Integer = me.ScrollPosition
  if cbAutoEdit.Value then
    if me.LastIndex>=0 then
      me.EditCell(0,1)
      txArea2.AppendText " sp2="+str(me.ScrollPosition)+EndOfLine
      scrollOffset=sp-me.ScrollPosition
    end if
  end if
End Sub

This is the revised CellClick handler code:

Function CellClick(row as Integer, column as Integer, x as Integer, y as Integer) Handles CellClick as Boolean
  'Column 0 is not editable. Column 1 is editable
  me.ScrollPosition=me.ScrollPosition+scrollOffset
  if column=1 then
    me.EditCell(row+scrollOffset,column)
  else
    me.ListIndex=me.ScrollPosition+scrollOffset
  end if
End Function

This code is working now. When clicking on a cell, it still jumps to cell(0,1) first and then back to the clicked cell, but this happens too fast to see. It could possibly be a problem on a much longer list, but for my current project it shouldn’t be a problem. The only way I can see to prevent the unnecessary jumping to Cell(0,1) would be to add code to the preceding and succeeding controls’ event handlers, which I’d rather avoid.

Can’t you simply trap the Tab key in the previous control (txArea1 in your example project) and initiate the cell edit there? Then dump all your GotFocus code completely.

// txArea1.KeyDown
if Key = chr(9) then
   listbox1.celledit(0,1)
   return true
end

You may or may not have to setFocus to the listbox as well.

Yes, I could do that, but if possible, I’d rather keep everything related to the listbox in the listbox event handlers.
In the future, I’m likely to copy this listbox to some other project, not remembering the code in the preceding control, and then not being able to understand why the listbox doesn’t work anymore.

Your method of calling CellEdit directly from the preceding control is quite clean though. I had been thinking it would require extra global variables, etc.

Possibly another way… Create a flag in the listbox, set it in CellGotFocus (orLostFocus?) and Check it in Listbox.GotFocus to decide what to do and unset it if it was set

  • Karen

Please change

Sub GotFocus() Handles GotFocus 'Check that Listbox has at least one row if me.LastIndex>=0 then me.EditCell(0,1) End Sub

to

Sub KeyUp(Key As String) Handles KeyUp 'user releasd tab 'and landed on this control 'and has an entry to edit if asc(key) = 9 and self.Focus.Name = me.name and me.ListCount > 0 then me.EditCell(0,1) end if End Sub

Its much neater and wont cause you random Focus problems.

Let me know what other problems if any you have left and I’ll try to help, rather than me trying to pick the problems out of the thread :slight_smile:

Thanks. I tried it just now. It looked promising, but once I’m in the cell and hit the tab key a second time, I get a NilObjectException on this line:

if asc(key) = 9 and self.Focus.Name = me.name and me.ListCount > 0 then

[quote=309723:@Robert Weaver]Thanks. I tried it just now. It looked promising, but once I’m in the cell and hit the tab key a second time, I get a NilObjectException on this line:

if asc(key) = 9 and self.Focus.Name = me.name and me.ListCount > 0 then [/quote]

Hmm interesting, I tested it in Windows and it worked flawlessly, you must have some additional login there causing the problem.

See my test project here: https://www.dropbox.com/s/tz7yg0i72sfu2wr/TestListBoxTab.xojo_binary_project?dl=1

Does that cause you any errors?

If your problem persists try nesting the IF’s, you will be able to narrow down the problem then and see what is actually causing the problem.

[code]Sub KeyUp(Key As String) Handles KeyUp
'user releasd tab
'and landed on this control
'and has an entry to edit
if asc(key) = 9 then
if self.Focus.Name = me.name then
if me.ListCount > 0 then
me.EditCell(0,1)
end if
end
end if

End Sub[/code]

Currently the only time this doesnt work is when you shift+tab out of an edit. Focus moves from the cell to the listbox and it goes back into edit mode. A simple state check between the cell focus and list focus should sort this out. I dont have the time to dabble with this any more until tomorrow. Happy coding :slight_smile: