grabKeyboardFocus not working in paint()


#1

I've just created a fresh new GUI application using the introjucer, and only adjusted the MainComponent to this:

MainContentComponent::MainContentComponent()
{
    addAndMakeVisible(editor);
    editor.setWantsKeyboardFocus(true);
    
    setSize (600, 400);
}

void MainContentComponent::paint (Graphics& g)
{
    if (!editor.hasKeyboardFocus(true) && isVisible())
        editor.grabKeyboardFocus();
}
void MainContentComponent::resized()
{
    editor.setBounds(100, 100,128, 24);
}

According to this topic, putting grabKeyboardFocus() inside paint() should be enough to make this work, but it doesn't.

I'm running Mac OSX 10.10.3. Any thoughts?


#2

Hi, stijnfrishert.

I'm not familiar with JUCE yet, but I don't think judge and put a component's grabKeyboardFocus() in paint() or resized() methods is a good idea. Why not try setExplicitFocusOrder (0) in constructor or write a method, put the code in this method, then call it after the component really displaying and ready for work?


#3

Yeah, the paint method is a very bad place to put code that makes any kind of change to the component hierarchy. You should pretty much treat that mathod as if it's 'const', and do nothing but draw things.


#4

I thought so too, but keeping it in the constructor or putting it in e.g. `visibilityChanged()` doesn't work either. There are some people in the other topic (albeit from 2010) for which paint() works, but I gotta admit it feels fishy.

The following doesn't work either, nor when I put grabKeyboardFocus in a visibilityChanged():

MainContentComponent::MainContentComponent()
{
    addAndMakeVisible(editor);
    editor.setWantsKeyboardFocus(true);
    editor.setExplicitFocusOrder(0);
    editor.grabKeyboardFocus();
    
    setSize (600, 400);
}

void MainContentComponent::resized()
{
    editor.setBounds(100, 100,128, 24);
}

If the component is not ready and visible in visibilityChanged() yet, then when is it?


#5

Also, I know little about C++ and compile theories. From what I know, C++ constructs its objects from the lowest bottom, then one by one to the top, in other words, if a class HAS a object as its data member, it'll construct itself after the object has constructed completely (include pointers. they're just 'ints', hasn't pointed to any heap data). So, in your code above, the parent hasn't showed yet, of course, neither all of its child components, and the top level component also. I think in this case, there is no way to decide how the keyboard events happening on which component. Perhaps I'm wrong, again :)

I've also encountered the same problem a few days before. There are some ways to solve it, depend on the actual situation and what you want. However, I think let some unnecessary things done by a constructor is unfair and not flexible enough. You may call some method which one of them I mentioned above after all the components showed up, then load your data model or something else, finnally, there is no other things need to be done, call it and other component-relative methods breezily. For me, I feel this way make me more comfortable. If there is no other easy way to do these, I'll use the Timer class... For example: I need the viewport scroll to the bottom immediately after the whole app begin to run...


#6

I couldn't reproduce this on Windows 10 with Visual Studio 2015.

Timur has managed to on OSX, however.


#7

Does that mean this is an OSX specific bug, or am I doing something wrong? :)

It seems the window doesn't have focus at all when opened, because when I click on the window's background, the editor does get selected.


#8

I don't think you're doing anything wrong, we're looking into it :) thanks for the extra info!


#9

"...It seems the window doesn't have focus at all when opened, because when I click on the window's background, the editor does get selected."

I also encountered this problem (on Windows) the other day, but I don't think it's a bug of JUCE, maybe the OS or something else I'm not sure. I did this to solve it (notice I didn't use grabKeyboardFocus() for the child component, instead of its parent's parent, and the sentence places in its parent's parent's parent's constructor :) ):

In the top level component: DocumentWindow's constructor:

setWantsKeyboardFocus (false);            
mainContentComponent->grabKeyboardFocus();

mainContentComponent has a child component (login ui), the child has a textEditor, in the child's constructor, I added:

// class PasswordInut inherits from TextEditor
addAndMakeVisible (passwordTextEdit = new PasswordInput()); 
   
passwordTextEdit->setExplicitFocusOrder (0);
allOtherChilds->setWantsKeyboardFocus (false);

// the focus order should start at 1
// edited: 2015-07-22 19:37 Thank for stijnfrishert
passwordTextEdit->setExplicitFocusOrder (1);

When the whole app begin to run, the first interface is the child component (of course, the DocumentWindow also showed up), the keyboard focus locates exactly on the passwordTextEdit. All the platforms are the same (OSX, Linux, Windows, iOS, Android).

In other words, if you want a component has focus and insist doing this in the 'nearest upper class's' constructor, I think it's better in its parent's parent's constrcutor, even in the top level comp or the JUCEApplication::initialise(). Make sure all components have showed up, then doing this. Perhaps I'm wrong...again...

Back to your question, appoint the keyboard focus on a very child compoennt when its parent showing up, and even more complex (for example: after x seconds or minutes, if the user hasn't input anything, make the focus auto transfer to another component, and so on...) Such requirements I needed at least for 3 situations in my recent project. Each of them, I adopted different strategies. The most important thing is: make sure the comp and all its upper parents have showed up, then appoint the focus.


#10

@loopfine: Ok, so that solution actually works! Thanks!

A small sidenote: explicitFocusOrder starts at 1 instead of 0. If you set it to 1, you don't have to setWantsKeyboardFocus(false) for all the other components.

@jgerrard: So I think loopfine's right and it has something to do with Juce <-> OS interaction. I'm note sure where the problem lies, but as a JUCE user the way this currently works is too much of a hassle. If you need any more information I'd be glad to help :)


#11

Glad to hear you solve the problem :)

Yes, indeed, you are right. Just found it should start at 1.

I've edited the post above.

Thanks!