Skip to content

Working with TabView's Loader sandbox effect

Daniel O'Neill edited this page Aug 22, 2015 · 2 revisions

QML Gotchas? Yep, here's a good one:

TabView. What's wrong with TabView? It works great! Well, until you try to reference components within it from parent code. What do I mean? Well, consider the following:

TabView {
 Tab {
  title: 'My Tab'
  TextField { id: myTextField; }
 }
}

Button { text: 'Click me!'; onClicked: { console.log(myTextField.text); } }

Alright, now click that button. You'll get a reference error claiming that myTextField cannot be found! Why? Well, that's because Tab items are instantiated in Loader components, which are sort-of sandboxed from the rest of the application. Basically the app isn't aware of QML instantiated in a Loader (at least with respect to implicit references).

Fear not, there are a few ways to work around the issue. One quick, dirty, and not-recommended way is to reference the Tab's item:

TabView {
 id: tabView
 Tab {
  title: 'My Tab'
  property alias myTextField: myTextField
  TextField { id: myTextField; }
 }
}

Button { text: 'Click me!'; onClicked: { console.log(tabView.getTab(0).item.myTextField.text); } }

... but, ew, gross. The cleaner method is to expose the relevant item contents:

TabView {
 id: tabView
 Tab {
  title: 'My Tab'
  property alias text: myTextField.text
  TextField { id: myTextField; }
 }
}

Button { text: 'Click me!'; onClicked: { console.log(tabView.getTab(0).item.text); } }

Getting there, but you might find that in both of the above examples the code only works as long as that Tab has been made visible at least once. So if, for example, it's the third tab and the user hasn't clicked it yet, you'll get an error when you click your Click Me! Button. If you dig, you will discover that "tabView.getTab(x).item" will be 'undefined'.

Alright, so what to do, then?

The best method in my experience is to treat the Tab contents as stand-alone components. That is, the contents of a Tab are lone wolves. Desperadoes. Well, at least, they can't reference their parents.

Nevermind that. Create a new file called, say, TabbedInput.qml containing your code:

TextField {
 id: myTextField;
}

This can absolutely be left inline for something so trivial, in fact, that would make more sense. But for the purposes of this example, and MOST practical uses of TabView, this is the preferred way to go.

In our case, now, we can simply hook the onAccepted or, if you prefer, onTextChanged signals to update our data. For more complicated Tab contents you will probably need to define additional signals and properties.

Now our main UI will look something like this:

TabView {
 id: tabView
 Tab {
  title: 'My Tab'
  TabbedInput { id: myTabbedInput; text: myText; onAccepted: myText = myTabbedInput.text; }
 }
}

property string myText: ''

Button { text: 'Click me!'; onClicked: { console.log(myText); } }

Now the contents of the Tab do the work. We're using the TabbedInput's signals and properties to access the text property from the inside rather than from the outside. This ensures that the data will be valid whether or not the user has clicked that Tab and, in doing so, instantiated the contents therein.

The same rule holds true with ListView which recycles delegate components. That is, if you have a ListView displaying 65k rows, the user will probably only see less than 100 at a time. Rather than rendering and hiding 65k delegates, Qt will create just enough to fill the view and scroll it around. Then, when the user does scroll down, will take the delegate objects which have scrolled out of view and place them into slots which are now in view with relevant data from those new positions.

This is important because it means any changes the user did on those rows which weren't then committed to the data in the ListView's model will now be lost! Try it for yourself. Scroll away, then back, and see if your change survives.

If you're a seasoned Qt developer who has used used Qt with C++ you'll be familiar with this separation, but it's easy to overlook it when you land in the Javascript and QML world.

Also, don't assume I'm offering the best method for dealing with the Loader sandbox effect either in general or with TabView and don't be afraid to try out your own methods!

Clone this wiki locally