I have found various solutions on how to add Buttons inside a Tab or overlay on the TabPane, which however did not satisfy me.
What I want is to have a button beside the most right tab, like e.g. my browser offers it:
What I have tried now is instead of adding a Button using the setGraph(Node) on this Tab, I have added a Tab that is treated different if selected.
addTab = new Tab("+");
addTab.setClosable(false);
TabPane.getSelectionModel().selectedItemProperty().addListener(new ChangeListener<Tab>() {
#Override
public void changed(ObservableValue<? extends Tab> ov, Tab oldValue, Tab newValue) {
if (newValue == addTab) {
groupTabPane.getSelectionModel().select(oldValue);
DO_ADD_TAB();
}
}
});
To keep it the right most Tab I have a listener on the Tablist and always remove / add it again if the list is modified (maybe this can be done more elegantly)
However the problem I face is that the TabPane always needs a selection, if a Tab is present. Therefore on startup DO_ADD_TAB() is always executed. I can work around that using a boolean variable, but then the Tab is selected, and can not be selected again to add a new Tab. TabPane.getSelectionModel().clearSelection() does not work.
Can somebody advise how to add such a button more elegantly or how to make the TabPane accept no Selection if only this one Tab is present?
Related
This is a weird question so I'll make my best to explain myself properly.
What I'd like is to trigger an event when a Tab in a TabPane get clicked, and by "clicked" I mean just clicked, not necessarily selected.
I already tried using the selectedProperty of the Tab, but that does call the event only if the Tab is clicked when it's not selected, not even if it's clicked when it's already selected.
The reason why I'm doing this is that I'm trying to make a collapsible tab pane that hides the content of the TabPane if you click again on the opened tab, I've already wrote the code for collapsing the TabPane and that works but... I have no idea on how to get a click event from the tab header.
I've even looked into TabPane source code too hoping that I could find the tab header container but I didn't find it there.
No need for a completely new skin - we can access the header nodes by lookup. Beware: implies relying on implementation details, which might change across versions.
The (undocumented!) style id to look for is ".tab-container" - that's the only child of the TabHeaderSkin (== region for a single tab header). It contains the label, the close button (if any) and the focus marker. This "skin" keeps a reference to its tab in its properties (undocumented, of course ;)
So the basic approach is to
lookup all tab-containers after the skin is installed
for each, register an appropriate mouse handler on its parent
on the respective mouseEvent, do whatever is needed
Note that the listeners have to be removed/added when the list of tabs is modified (not included in the snippet below).
In example code:
/**
* looks up the styled part of tab header and installs a mouseHandler
* which calls the work load method.
*
* #param tabPane
*/
private void installTabHandlers(TabPane tabPane) {
Set<Node> headers = tabPane.lookupAll(".tab-container");
headers.forEach(node -> {
// implementation detail: header of tabContainer is the TabHeaderSkin
Parent parent = node.getParent();
parent.setOnMouseClicked(ev -> handleHeader(parent));
});
}
/**
* Workload for tab.
* #param tabHeaderSkin
*/
private void handleHeader(Node tabHeaderSkin) {
// implementation detail: skin keeps reference to associated Tab
Tab tab = (Tab) tabHeaderSkin.getProperties().get(Tab.class);
System.out.println("do stuff for tab: " + tab.getText());
}
In a CTabFolder, I'd like to check the content for unsaved data before the user can switch from one tab to another. SWT does not provide a PreSelection event, as stated here.
I found a workaround, suggesting to switch back to the old tab when a selection is triggered, validate the data and then perform the desired switch again, if data is valid.
I do understand the general idea of this workaround, however, it is not working for me. oldPageIndex and newPageIndex do always have the same value, though I did not click on the same tab.
this.tabContainer.addSelectionListener(new SelectionAdapter() {
#Override
public void widgetSelected(SelectionEvent event) {
int oldPageIndex = tabContainer.getSelectionIndex();
int newPageIndex = tabContainer.indexOf((CTabItem)event.item);
// Here: oldPageIndex == newPageIndex
...
}
});
Is this workaround still working or is there anything I could possibly be doing wrong? Or maybe, has there been any fix for a real PreSelection event in the meantime? I tried using event.doit, but the SelectionEvent is fired, when the tabs have been switched already.
You can use the selection listener but as you have found the getSelectionIndex() does not give you the old tab. So you will have to maintain the old tab index yourself.
This is the technique used by the Eclipse FormEditor.
When I'm setting the button to be disabled using this:
jButton.setEnabled(false);
then there is this visual effect visible on the second element ->
How can I disable the button, but keep the the look of the first element?
how to get rid of visual effect when disabling buttons
Companies spend millions of dollars to develop a UI can is common and can be used by all users.
How is the user suppose to know that the button is disabled if there is no visual indication?
Anyway, (rant finished) you can manually set the disabled icon:
button.setDisabledIcon( button.getIcon());
If you also happen to have text on the button the text will still be disabled so instead of a disabled icon you can use a custom ButtonModel:
button.setModel( new DefaultButtonModel()
{
#Override
public boolean isArmed()
{
return false;
}
#Override
public boolean isPressed()
{
return false;
}
});
I have implemented a JFace Wizard with 2 WizardPages.
By default, the first WizardPage has these 4 Buttons:
Back (disabled)
Next (focused)
Cancel
Finish (disabled)
Now I want to set the default focus on the Cancel Button. How do I do that?
Removing the focus and setting it to some Control of the page's content would also be ok.
I tried setting the focus to a Button in the content layout of the WizardPage, but this only sets me a second focus on the immediateButton. The focus on the Next button is still there, and the Next button reacts to pressing enter, which is what I want to avoid.
#Override
public void setVisible(boolean visible) {
super.setVisible(visible);
if (visible) {
immediateButton.setFocus();
}
}
How can I access the Dialog buttons and change their focus?
The Next button does not actually have focus, rather it is the Shell Default button.
The logic in WizardDialog makes either Next or Finish the default button and there does not seem to be a way to change this.
You may be able to override this by calling getShell().setDefaultButton(button) in your wizard page.
Update: Testing this you can do it in setVisible but you need to use Display.asyncExec to make the code run at the right time:
final Shell shell = getShell();
shell.getDisplay().asyncExec(() -> shell.setDefaultButton(immediateButton));
above is for Java 8, for Java 7 or earlier:
shell.getDisplay().asyncExec(new Runnable()
{
#Override
public void run()
{
shell.setDefaultButton(immediateButton);
}
});
On an app that I'm working on, I need a context menu to show up if a user performs a longClick on a tab, which would allow them to close the tab. I can't seem to find a way to add a listener to a tab though. I either need each tab to have its own listener or the listener needs to be able to tell which tab had the longClick performed on it, as it won't always be the active tab.
Any ideas?
I appreciate that an answer has been accepted but if you want to utilise the built-in ContextMenu capabilities rather than set onLongClickListeners on the TabWidget itself, you can do this as follows...
Example, my current TabActivity adds tabs in a for loop and to register each for context menu I do the following.
for (int tabNumber = 1; tabNumber < 8; tabNumber++) {
...
spec = tabHost.newTabSpec(tag).setIndicator(indicator).setContent(intent);
tabHost.addTab(spec);
View v = tabWidget.getChildAt(tabNumber - 1);
registerForContextMenu(v);
...
}
Then in my Activity I simply override onCreateContextMenu(...) and onContextItemSelected (MenuItem item)
#Override
public void onCreateContextMenu (ContextMenu menu, View v, ContextMenuInfo menuInfo) {
...
// The parameter v is the actual tab view and not the TabWidget
// this makes it easy to get the indicator text or its tag in order
// to easily identify which tab was long-clicked and build the menu
...
}
#Override
public boolean onContextItemSelected (MenuItem item) {
...
// Process selected item here
...
}
There's no need to set an OnLongClickListener on any views explicitly as that is done by the call to registerForContextMenu(...). Also, the ContextMenu creation and selection handling is all handled for you by the ContextMenu methods exposed by Activity.
Unless you need to handle all of this stuff yourself (for a custom context menu layout for example) it seems easier to just use what's buit-in to Activity.
A TabWidget is a View like any other; you should be able to register an OnLongClickListener with it via myTabWidget.setOnClickListener and use the View argument of OnLongClickListener.onLongClick(View v) to determine which tab was clicked.
When you use a TabSpec to register the indicator for each tab with your TabHost, if the resource you pass in has an associated ID, you should be able to use that ID to look up the tab itself. This may mean you could have to start using Views or layouts as your TabSpec.setContent or TabSpec.setIndicator arguments (if you aren't already) so you can look them up by ID later.