Dialog panels do not hide properly on switch - is there a workaround? - dialog

I have a dialog that contains two panels as shown below, created with the given code. As one can see in the right image, the second panel still contains content from the first panel.
Is there any workaround to fix that?
I feel like bugs like these should be mentionend on Stackoverflow.
class TestDialog : UIFrame{
TagGroup panel_list;
/**
* Switch to the `index`-th panel.
*/
void switchToPanel(object self, number index){
panel_list.DLGValue(index);
}
void switchToPanel0(object self){self.switchToPanel(0);}
void switchToPanel1(object self){self.switchToPanel(1);}
/**
* Create the dialog content
*/
TagGroup createContent(object self){
panel_list = DLGCreatePanelList(0);
TagGroup box, switch_button, input, panel;
// panel 1
box = DLGCreateBox("Panel 1");
// switch panel button
switch_button = DLGCreatePushButton("Switch to panel 2", "switchToPanel1");
box.DLGAddElement(switch_button);
// input field
input = DLGCreateStringField("ABC");
box.DLGAddElement(input);
panel = DLGCreatePanel();
panel.DLGAddElement(box);
panel_list.DLGAddElement(panel);
// panel 2
box = DLGCreateBox("Panel 2");
// switch panel button
switch_button = DLGCreatePushButton("Switch to panel 1", "switchToPanel0");
box.DLGAddElement(switch_button);
// add a label so both boxes have different heights
box.DLGAddElement(DLGCreateLabel(""));
// input field
input = DLGCreateStringField("DEF");
box.DLGAddElement(input);
panel = DLGCreatePanel();
panel.DLGAddElement(box);
panel_list.DLGAddElement(panel);
TagGroup wrapper = DLGCreateGroup();
wrapper.DLGAddElement(panel_list);
return wrapper;
}
object init(object self){
return self.super.init(self.createContent())
}
}
alloc(TestDialog).init().pose()

Fix: Hide manually
After a lot of trying out I think triggering the UIFrame::SetElementIsShown() manually fixes this issue. In the given example one can add the identifiers input0 to the first and input1 to the second input and then change the TestDialog::switchToPanel() function to the following:
/**
* Switch to the `index`-th panel.
*/
void switchToPanel(object self, number index){
panel_list.DLGValue(index);
if(index == 0){
self.setElementIsShown("input0", 1);
self.setElementIsShown("input1", 0);
}
else{
self.setElementIsShown("input0", 0);
self.setElementIsShown("input1", 1);
}
}
The complete code is then:
class TestDialog : UIFrame{
TagGroup panel_list;
/**
* Switch to the `index`-th panel.
*/
void switchToPanel(object self, number index){
panel_list.DLGValue(index);
if(index == 0){
self.setElementIsShown("input0", 1);
self.setElementIsShown("input1", 0);
}
else{
self.setElementIsShown("input0", 0);
self.setElementIsShown("input1", 1);
}
}
void switchToPanel0(object self){self.switchToPanel(0);}
void switchToPanel1(object self){self.switchToPanel(1);}
/**
* Create the dialog content
*/
TagGroup createContent(object self){
panel_list = DLGCreatePanelList(0);
TagGroup box, switch_button, input, panel;
// panel 1
box = DLGCreateBox("Panel 1");
// switch panel button
switch_button = DLGCreatePushButton("Switch to panel 2", "switchToPanel1");
box.DLGAddElement(switch_button);
// input field
input = DLGCreateStringField("ABC");
input.DLGIdentifier("input0");
box.DLGAddElement(input);
panel = DLGCreatePanel();
panel.DLGAddElement(box);
panel_list.DLGAddElement(panel);
// panel 2
box = DLGCreateBox("Panel 2");
// switch panel button
switch_button = DLGCreatePushButton("Switch to panel 1", "switchToPanel0");
box.DLGAddElement(switch_button);
// add a label so both boxes have different heights
box.DLGAddElement(DLGCreateLabel(""));
// input field
input = DLGCreateStringField("DEF");
input.DLGIdentifier("input1");
box.DLGAddElement(input);
panel = DLGCreatePanel();
panel.DLGAddElement(box);
panel_list.DLGAddElement(panel);
TagGroup wrapper = DLGCreateGroup();
wrapper.DLGAddElement(panel_list);
return wrapper;
}
object init(object self){
return self.super.init(self.createContent())
}
}
alloc(TestDialog).init().pose()
Workaround: Use Tabs
I also found out that tabs work with the exact same code. So if possible one can just replace the panels with tabs.
class TestDialog : UIFrame{
TagGroup tab_list;
TagGroup inputs;
/**
* Create the dialog content
*/
TagGroup createContent(object self){
inputs = NewTagList();
tab_list = DLGCreateTabList(0);
TagGroup box, input, tab;
// panel 1
box = DLGCreateBox("Panel 1");
// input field
input = DLGCreateStringField("ABC");
box.DLGAddElement(input);
// save the input field in a TagList, this creates the problem
inputs.TagGroupInsertTagAsTagGroup(infinity(), input);
tab = DLGCreateTab("Tab 1");
tab.DLGAddElement(box);
tab_list.DLGAddElement(tab);
// panel 2
box = DLGCreateBox("Panel 2");
// add a label so both boxes have different heights
box.DLGAddElement(DLGCreateLabel(""));
// input field
input = DLGCreateStringField("DEF");
box.DLGAddElement(input);
inputs.TagGroupInsertTagAsTagGroup(infinity(), input);
tab = DLGCreateTab("Tab 2");
tab.DLGAddElement(box);
tab_list.DLGAddElement(tab);
TagGroup wrapper = DLGCreateGroup();
wrapper.DLGAddElement(tab_list);
return wrapper;
}
object init(object self){
return self.super.init(self.createContent())
}
}
object dialog = alloc(TestDialog).Init();
dialog.pose();

Related

How to define multiple sharedPreferences?

I have managed to get the sharedPreferences saving values. But i don't know how to make it reference the text i am clicking on. In the // Close Alert Window section when i click ok to change the text. Ok dismisses alert dialog, then suppose to add the new price to list in sharedPreferences.
In the putString() if i use putString("Price$it", input.text.toString()).applyit doesn't appear to do anything. However if i use "Price1" any text i change is saved and upon reopening the app Price1is changed to the new price. So i know the method is working. i just have no clue how to save the particular text i am editing. I hope this makes sense. Thanks for your time.
// Created Private Price List
val sharedPreferences = getSharedPreferences("priceList", Context.MODE_PRIVATE)
//Price
(1..912).forEach {
val id = resources.getIdentifier("Price$it", "id", packageName)
val tv = findViewById<TextView>(id)
tv.text = sharedPreferences.getString("Price$it","0.00")
}
(1..912).forEach {
val id = resources.getIdentifier("Price$it", "id", packageName)
val tv = findViewById<TextView>(id)
tv.setOnLongClickListener {
//Alert Window
val alertDialog = AlertDialog.Builder(this#MainActivity).create()
alertDialog.setTitle("NEW PRICE")
val input = EditText(this#MainActivity)
//Alert Submit on Enter
input.setOnKeyListener { v, keyCode, event ->
if (event.action == KeyEvent.ACTION_DOWN && keyCode == KeyEvent.KEYCODE_ENTER) {
// Input changes text
tv.text = input.text
when {
tv.text.startsWith("-") -> tv.setTextColor(Color.RED)
tv.text.startsWith("+") -> tv.setTextColor(Color.GREEN)
else -> {
tv.text = "_"
tv.setTextColor(Color.DKGRAY)
}
}
// Close Alert Window
alertDialog.dismiss()
// TODO Save Price Table //THIS PART vvv
sharedPreferences.edit().putString("Price1", input.text.toString()).apply()
}
false
}
val lp = LinearLayout.LayoutParams(
LinearLayout.LayoutParams.MATCH_PARENT,
LinearLayout.LayoutParams.MATCH_PARENT
)
input.layoutParams = lp
alertDialog.setView(input)
alertDialog.show()
return#setOnLongClickListener true
}
}
You are shadowing it. In your scope you are referencing the argument of tv.setOnLongClickListener. Specify the argument name so it's not shadowed by inner lambdas.
(1..912).forEach { index ->
...
sharedPreferences.edit().putString("Price$index", input.text.toString()).apply()
}

Using iOS system icons in a NavigationPage with a TabbedPage

I'm using a custom renderer to use iOS system icons in the navigation bar. It works fine, except that if the page is a TabbedPage, only the navigation icons for the default tab's page get their system icons. On other tabs, the system icons don't appear.
My current approach is to override PushViewController. The problem seems to be that when it's called, only the button items for that first tab are available. How can the custom renderer detect when the buttons on the navigation bar are changing? Or is there a better approach?
Current implementation:
/// <summary>
/// Sets system icons on the navigation bar that match the item text.
/// </summary>
class SystemIconNavigationRenderer : NavigationRenderer
{
public override void PushViewController(UIViewController viewController, bool animated)
{
base.PushViewController(viewController, animated);
// If any buttons are customized, replaces the list. Editing individual items doesn't work because UIBarButtonItem.Image is null for a new UIBarButtonItem created from a system item.
var items = viewController.NavigationItem.RightBarButtonItems;
bool changed = false;
var newItems = new UIBarButtonItem[items.Length];
for (int i = 0; i < items.Length; ++i) {
var item = items[i];
UIBarButtonSystemItem systemItem = (UIBarButtonSystemItem)(-1);
switch (item.Title) {
case nameof(UIBarButtonSystemItem.Add): systemItem = UIBarButtonSystemItem.Add; break;
// More icons...
}
if (systemItem >= 0) {
newItems[i] = new UIBarButtonItem(systemItem) { Action = item.Action, Target = item.Target };
changed = true;
} else
newItems[i] = item;
}
if (changed) viewController.NavigationItem.RightBarButtonItems = newItems;
}
}

SubclassDlgItem() debug assertion failed

I have created a dailog box with custom control. I am using ultimate grid in my application. (https://www.codeproject.com/Articles/20183/The-Ultimate-Grid-Home-Page).
I am having an error (Debug Assertion Failed) when I run my project.
BOOL CCustomControlDlg::OnInitDialog()
{
CDialog::OnInitDialog();
// IDM_ABOUTBOX must be in the system command range.
ASSERT((IDM_ABOUTBOX & 0xFFF0) == IDM_ABOUTBOX);
ASSERT(IDM_ABOUTBOX < 0xF000);
CMenu* pSysMenu = GetSystemMenu(FALSE);
if (pSysMenu != NULL)
{
BOOL bNameValid;
CString strAboutMenu;
bNameValid = strAboutMenu.LoadString(IDS_ABOUTBOX);
ASSERT(bNameValid);
if (!strAboutMenu.IsEmpty())
{
pSysMenu->AppendMenu(MF_SEPARATOR);
pSysMenu->AppendMenu(MF_STRING, IDM_ABOUTBOX, strAboutMenu);
}
}
// Set the icon for this dialog. The framework does this automatically
// when the application's main window is not a dialog
SetIcon(m_hIcon, TRUE); // Set big icon
SetIcon(m_hIcon, FALSE); // Set small icon
// TODO: Add extra initialization here
// Add "About..." menu item to system menu.
m_grid.AttachGrid(this, IDC_CUSTOM1);// ERROR LINE
return TRUE; // return TRUE unless you set the focus to a control
}
//Attach grid implementation
BOOL CUGCtrl::AttachGrid(CWnd * wnd,UINT ID){
if( SubclassDlgItem(IDC_CUSTOM1,wnd)) // ERROR LINE
{
long style = GetWindowLong(m_hWnd,GWL_STYLE);
style = style|WS_CLIPCHILDREN|WS_TABSTOP;
SetWindowLong(m_hWnd,GWL_STYLE,style);
// if the parent window is specified
if(wnd!= NULL)
{
LOGFONT logFont;
CFont *pTempFont = wnd->GetFont();
pTempFont->GetLogFont( &logFont );
// ceate a font object based on the font information retrieved from
// parent window. This font will be used as grid's default font.
int nIndex = AddFont( logFont.lfHeight, logFont.lfWidth, logFont.lfEscapement,
logFont.lfOrientation, logFont.lfWeight, logFont.lfItalic,
logFont.lfUnderline, logFont.lfStrikeOut, logFont.lfCharSet,
logFont.lfOutPrecision, logFont.lfClipPrecision,
logFont.lfQuality, logFont.lfPitchAndFamily, logFont.lfFaceName );
SetDefFont( nIndex );
// create a font that will be used for the heading cells. This object
// is almost identical to the grid's default font, except its weight
// was increased by 200.
nIndex = AddFont( logFont.lfHeight, logFont.lfWidth, logFont.lfEscapement,
logFont.lfOrientation, logFont.lfWeight + 200, logFont.lfItalic,
logFont.lfUnderline, logFont.lfStrikeOut, logFont.lfCharSet,
logFont.lfOutPrecision, logFont.lfClipPrecision,
logFont.lfQuality, logFont.lfPitchAndFamily, logFont.lfFaceName );
CUGCell cell;
GetHeadingDefault( &cell );
cell.SetFont( GetFont( nIndex ) );
SetHeadingDefault( &cell );
}
CreateChildWindows();
// When WS_EX_RTLREADING style was specified for the place holder
// window, then set the grid to be in RTL layout mode.
style = GetWindowLong( m_hWnd, GWL_EXSTYLE );
if ( style&WS_EX_RTLREADING )
SetGridLayout( 1 );
OnSetup();
OnSheetSetup(0);
// Allow drawing after the grid is initialized
m_GI->m_paintMode = TRUE;
// Adjust the grid's components to fit current setup
AdjustComponentSizes();
return TRUE;
}
return FALSE;
}
Anyone having any idea how to fix this??
// CAboutDlg dialog used for App About
class CAboutDlg : public CDialog
{
public:
CAboutDlg();
// Dialog Data
#ifdef AFX_DESIGN_TIME
enum { IDD = IDD_ABOUTBOX };
#endif
protected:
virtual void DoDataExchange(CDataExchange* pDX); // DDX/DDV support
// Implementation
protected:
DECLARE_MESSAGE_MAP()
};
CAboutDlg::CAboutDlg() : CDialog(IDD_ABOUTBOX)
{
}
void CAboutDlg::DoDataExchange(CDataExchange* pDX)
{
CDialog::DoDataExchange(pDX);
}
void CCustomControlDlg::DoDataExchange(CDataExchange* pDX)
{
CDialog::DoDataExchange(pDX);
//{{AFX_DATA_MAP(CCustomControlDlg)
// NOTE: the ClassWizard will add DDX and DDV calls here
DDX_Control(pDX, IDC_CUSTOM1, m_drawpad);
//}}AFX_DATA_MAP
}
The issue is, that you are attaching two different C++ objects to the same control (IDC_CUSTOM1). The entry in DoDataExchange() implicitly performs the subclassing by calling DDX_Control(pDX, IDC_CUSTOM1, m_drawpad);, while the code in OnInitDialog() is more explicit (m_grid.AttachGrid(this, IDC_CUSTOM1);), but essentially does the same thing.
To fix this, you have a number of options, depending on what you are after:
Remove the DDX_Control() call in DoDataExchange() if you don't need the m_drawpad object attached to IDC_CUSTOM1.
Remove the m_grid.AttachGrid() call inside OnInitDialog(), if you don't need the m_grid object attached to IDC_CUSTOM1.
Add an additional control placeholder to your dialog resource and use that for either of those objects, in case you need both.

OnInitMenuPopup does not init correctly when called from a toolbar button that was a POPUP menu in the App main menu bar

To make a long history short, imagine my main menu is a CMFCMenuBar which menu is defined by:
IDR_MAINFRAME MENU
BEGIN
POPUP "&File"
BEGIN
MENUITEM "New", ID_FILE_NEW
MENUITEM "Open", ID_FILE_OPEN
MENUITEM "Save", ID_FILE_SAVE
POPUP "Save As"
BEGIN
MENUITEM "Format XXX", ID_FILE_SAVEAS_XXX
MENUITEM SEPARATOR
MENUITEM "Format YYY", ID_FILE_SAVEAS_YYY
MENUITEM SEPARATOR
MENUITEM "Format ZZZ", ID_FILE_SAVEAS_ZZZ
MENUITEM "Format WWW", ID_FILE_SAVEAS_WWW
END
MENUITEM "Close", ID_FILE_CLOSE
END
POPUP "&Object"
BEGIN
POPUP "Create object"
BEGIN
MENUITEM "Create object...", ID_CREATE_OBJECT
MENUITEM "Create object as...", ID_CREATE_OBJECTAS
END
POPUP "Save object"
BEGIN
MENUITEM "Save object...", ID_SAVE_AS_XXX
MENUITEM "Save object copy", ID_SAVE_OBJECT_COPY
END
END
MENUITEM "Delete", ID_DELETE_OBJECT
END
END
As POPUP menus have no ID (all them have a value of -1), to know on what menu I am doing the initialization, I followed the approach on https://stackoverflow.com/a/3910405/383779 and implemented functions like
bool CMainFrame::IsFileMenu(CMenu* pPopupMenu) const
{
if(!pPopupMenu);
return false;
return (pPopupMenu->GetMenuItemCount() > 0) && (pPopupMenu->GetMenuItemID(0) == ID_FILE_NEW);
}
bool CMainFrame::IsObjectMenu(CMenu* pPopupMenu) const
{
if(!pPopupMenu);
return false;
return (pPopupMenu->GetMenuItemCount() > 0) && (pPopupMenu->GetMenuItemID(2) == ID_DELETE_OBJECT);
}
The Menu initialization is like:
void CMainFrame::OnInitMenuPopup(CMenu* pPopupMenu, UINT nIndex, BOOL bSysMenu)
{
if(!license.supports("SaveAsFile"))
{
if(IsFileMenu())
{
//code to find ID_FILE_SAVEAS_XXX parent menu ("Save As"), then recursively delete all its descendants and itself
}
if(IsObjectMenu())
{
for(int i = 0; i < pPopupMenu->GetMenuItemCount();i++)
{
MENUITEMINFO MenuItemInfo;
memset(&MenuItemInfo, 0, sizeof(MENUITEMINFO));
MenuItemInfo.cbSize = sizeof (MENUITEMINFO); // must fill up this field
MenuItemInfo.fMask = MIIM_SUBMENU;
if (!pPopupMenu->GetMenuItemInfo(i, &MenuItemInfo, TRUE))
continue;
CMenu* SubMenu = pPopupMenu->GetSubMenu(i);
if (SubMenu != NULL)
{
memset(&MenuItemInfo, 0, sizeof(MENUITEMINFO));
MenuItemInfo.cbSize = sizeof (MENUITEMINFO);
MenuItemInfo.fMask = MIIM_ID | MIIM_TYPE;
for(int j=0; j<SubMenu->GetMenuItemCount() ;j++ )
{
SubMenu->GetMenuItemInfo(j, &MenuItemInfo, TRUE);
if (MenuItemInfo.wID == ID_CREATE_OBJECTAS)
{
for (int i=0; i<m_custom_objects.GetSize(); i++)
pPopup->AppendMenu(MF_STRING | MF_ENABLED, WM_MENU_CUSTOM_OBJECTS_BEGIN + i, (LPCTSTR) m_custom_objects[i].GetName() );
found= true;
break;
}
}
if(found)
break;
}
}
}
}
__super::OnInitMenuPopup(pPopupMenu, nIndex, bSysMenu);
}
Notice the ID_FILE_SAVEAS_XXX MENUITEM identifer is repeated! As I don't'want to delete the "Save Object" POPUP when the the license supporting "SaveAsFile" is absent, so it is an absolute need to identify what menu is now being processed.
Now the user has created a toolbar by using the MFC Customize dialog, then he dragged the "Create Object" POPUP to the new toolbar. When he clicks this new button of the toolbar, he sees the "Create object" and "Create object as" option, but not the custom objects have not been appended because to the IsObjectMenu() condition was not satisfied. The behaviour was completely different when he came from the main menu; the appending has been done!
How can I make the code in a way that the Appending of custom objects is done when the user clicks the button of the toolbar?
I am not proud of my own code, but I solved it by separating the code of "Object" from "Create Object" menu.
bool CMainFrame::IsCreateObjectMenu(CMenu* pPopupMenu) const
{
if(!pPopupMenu);
return false;
return (pPopupMenu->GetMenuItemCount() > 0) && (pPopupMenu->GetMenuItemID(0) == ID_CREATE_OBJECT);
}
void CMainFrame::OnInitMenuPopup(CMenu* pPopupMenu, UINT nIndex, BOOL bSysMenu)
{
if(!license.supports("SaveAsFile"))
{
if(IsFileMenu())
{
//code to find ID_FILE_SAVEAS_XXX parent menu ("Save As"), then recursively delete all its descendants and itself
}
if(IsObjectMenu())
{
// Do things
}
if(IsCreateObjectMenu())
{
for (int i=0; i<m_custom_objects.GetSize(); i++)
pPopupMenu->AppendMenu(MF_STRING | MF_ENABLED, WM_MENU_CUSTOM_OBJECTS_BEGIN + i, (LPCTSTR) m_custom_objects[i].GetName());
}
}
__super::OnInitMenuPopup(pPopupMenu, nIndex, bSysMenu);
}

JavaFX Tab positioning on mouse drag/drop

I have a Tabpane with multiple tabs.
I want to re-position tabs by just dragging them at a particular position(just like the way we are able to arrange tabs in browser.)
Is there any way i can achieve it?
We achieved it in a slightly different way.Instead of drag/drop feature we provided the move left/move right functionality on tab context menu which in turns moves the tab.
We wanted to have this feature on priority so implemented it with this workaround for now.
Code snippet for MoveRight:
public void moveRight() {
protected TabPane workBook;
int cTabIndex = bem.workBook.getTabs().indexOf(bem.activeSheet);
int tabCount = workBook.getTabs().size();
if (tabCount > 1 && cTabIndex > 0) {
workBook.getTabs().remove(bem.activeSheet);
workBook.getTabs().add(cTabIndex - 1, bem.activeSheet);
}
}
I've implemented a class that handles both draggable and detachable tabs - more details here. The implementation is not the tidiest, nor the most resilient but works pretty well for me in the simple cases I've tried so far. I've deliberately kept everything in the one class to make it easier for others to copy / use / modify as they see fit.
The basic concept that I'm using (arguably mis-using) is that the graphic you can set on a tab can be any node, not just an ImageView (or similar.) So instead of using the setText() on Tab directly, I'm not adding any text at all, just setting the graphic to be a Label containing the desired text. Now that the label is present in the tab header (and is pretty much the tab header spacially), that makes it much easier (and skin-independant) to grab the global co-ordinates of each tab header in the pane. From then it's just a case of some relatively simple positioning logic to work out when to detach tabs into a new window, when to re-add them and when to reorder them.
Of course, this isn't an ideal solution but unfortunately I haven't seen much else on the subject!
import java.util.HashSet;
import java.util.Set;
import javafx.collections.ListChangeListener;
import javafx.event.EventHandler;
import javafx.geometry.Point2D;
import javafx.geometry.Pos;
import javafx.geometry.Rectangle2D;
import javafx.scene.Scene;
import javafx.scene.control.Control;
import javafx.scene.control.Label;
import javafx.scene.control.Tab;
import javafx.scene.control.TabPane;
import javafx.scene.input.MouseEvent;
import javafx.scene.layout.StackPane;
import javafx.scene.paint.Color;
import javafx.scene.shape.Rectangle;
import javafx.scene.text.Text;
import javafx.stage.Stage;
import javafx.stage.StageStyle;
import javafx.stage.WindowEvent;
/**
* A draggable tab that can optionally be detached from its tab pane and shown
* in a separate window. This can be added to any normal TabPane, however a
* TabPane with draggable tabs must *only* have DraggableTabs, normal tabs and
* DrragableTabs mixed will cause issues!
* <p>
* #author Michael Berry
*/
public class DraggableTab extends Tab {
private static final Set<TabPane> tabPanes = new HashSet<>();
private Label nameLabel;
private Text dragText;
private static final Stage markerStage;
private Stage dragStage;
private boolean detachable;
static {
markerStage = new Stage();
markerStage.initStyle(StageStyle.UNDECORATED);
Rectangle dummy = new Rectangle(3, 10, Color.web("#555555"));
StackPane markerStack = new StackPane();
markerStack.getChildren().add(dummy);
markerStage.setScene(new Scene(markerStack));
}
/**
* Create a new draggable tab. This can be added to any normal TabPane,
* however a TabPane with draggable tabs must *only* have DraggableTabs,
* normal tabs and DrragableTabs mixed will cause issues!
* <p>
* #param text the text to appear on the tag label.
*/
public DraggableTab(String text) {
nameLabel = new Label(text);
setGraphic(nameLabel);
detachable = true;
dragStage = new Stage();
dragStage.initStyle(StageStyle.UNDECORATED);
StackPane dragStagePane = new StackPane();
dragStagePane.setStyle("-fx-background-color:#DDDDDD;");
dragText = new Text(text);
StackPane.setAlignment(dragText, Pos.CENTER);
dragStagePane.getChildren().add(dragText);
dragStage.setScene(new Scene(dragStagePane));
nameLabel.setOnMouseDragged(new EventHandler<MouseEvent>() {
#Override
public void handle(MouseEvent t) {
dragStage.setWidth(nameLabel.getWidth() + 10);
dragStage.setHeight(nameLabel.getHeight() + 10);
dragStage.setX(t.getScreenX());
dragStage.setY(t.getScreenY());
dragStage.show();
Point2D screenPoint = new Point2D(t.getScreenX(), t.getScreenY());
tabPanes.add(getTabPane());
InsertData data = getInsertData(screenPoint);
if(data == null || data.getInsertPane().getTabs().isEmpty()) {
markerStage.hide();
}
else {
int index = data.getIndex();
boolean end = false;
if(index == data.getInsertPane().getTabs().size()) {
end = true;
index--;
}
Rectangle2D rect = getAbsoluteRect(data.getInsertPane().getTabs().get(index));
if(end) {
markerStage.setX(rect.getMaxX() + 13);
}
else {
markerStage.setX(rect.getMinX());
}
markerStage.setY(rect.getMaxY() + 10);
markerStage.show();
}
}
});
nameLabel.setOnMouseReleased(new EventHandler<MouseEvent>() {
#Override
public void handle(MouseEvent t) {
markerStage.hide();
dragStage.hide();
if(!t.isStillSincePress()) {
Point2D screenPoint = new Point2D(t.getScreenX(), t.getScreenY());
TabPane oldTabPane = getTabPane();
int oldIndex = oldTabPane.getTabs().indexOf(DraggableTab.this);
tabPanes.add(oldTabPane);
InsertData insertData = getInsertData(screenPoint);
if(insertData != null) {
int addIndex = insertData.getIndex();
if(oldTabPane == insertData.getInsertPane() && oldTabPane.getTabs().size() == 1) {
return;
}
oldTabPane.getTabs().remove(DraggableTab.this);
if(oldIndex < addIndex && oldTabPane == insertData.getInsertPane()) {
addIndex--;
}
if(addIndex > insertData.getInsertPane().getTabs().size()) {
addIndex = insertData.getInsertPane().getTabs().size();
}
insertData.getInsertPane().getTabs().add(addIndex, DraggableTab.this);
insertData.getInsertPane().selectionModelProperty().get().select(addIndex);
return;
}
if(!detachable) {
return;
}
final Stage newStage = new Stage();
final TabPane pane = new TabPane();
tabPanes.add(pane);
newStage.setOnHiding(new EventHandler<WindowEvent>() {
#Override
public void handle(WindowEvent t) {
tabPanes.remove(pane);
}
});
getTabPane().getTabs().remove(DraggableTab.this);
pane.getTabs().add(DraggableTab.this);
pane.getTabs().addListener(new ListChangeListener<Tab>() {
#Override
public void onChanged(ListChangeListener.Change<? extends Tab> change) {
if(pane.getTabs().isEmpty()) {
newStage.hide();
}
}
});
newStage.setScene(new Scene(pane));
newStage.initStyle(StageStyle.UTILITY);
newStage.setX(t.getScreenX());
newStage.setY(t.getScreenY());
newStage.show();
pane.requestLayout();
pane.requestFocus();
}
}
});
}
/**
* Set whether it's possible to detach the tab from its pane and move it to
* another pane or another window. Defaults to true.
* <p>
* #param detachable true if the tab should be detachable, false otherwise.
*/
public void setDetachable(boolean detachable) {
this.detachable = detachable;
}
/**
* Set the label text on this draggable tab. This must be used instead of
* setText() to set the label, otherwise weird side effects will result!
* <p>
* #param text the label text for this tab.
*/
public void setLabelText(String text) {
nameLabel.setText(text);
dragText.setText(text);
}
private InsertData getInsertData(Point2D screenPoint) {
for(TabPane tabPane : tabPanes) {
Rectangle2D tabAbsolute = getAbsoluteRect(tabPane);
if(tabAbsolute.contains(screenPoint)) {
int tabInsertIndex = 0;
if(!tabPane.getTabs().isEmpty()) {
Rectangle2D firstTabRect = getAbsoluteRect(tabPane.getTabs().get(0));
if(firstTabRect.getMaxY()+60 < screenPoint.getY() || firstTabRect.getMinY() > screenPoint.getY()) {
return null;
}
Rectangle2D lastTabRect = getAbsoluteRect(tabPane.getTabs().get(tabPane.getTabs().size() - 1));
if(screenPoint.getX() < (firstTabRect.getMinX() + firstTabRect.getWidth() / 2)) {
tabInsertIndex = 0;
}
else if(screenPoint.getX() > (lastTabRect.getMaxX() - lastTabRect.getWidth() / 2)) {
tabInsertIndex = tabPane.getTabs().size();
}
else {
for(int i = 0; i < tabPane.getTabs().size() - 1; i++) {
Tab leftTab = tabPane.getTabs().get(i);
Tab rightTab = tabPane.getTabs().get(i + 1);
if(leftTab instanceof DraggableTab && rightTab instanceof DraggableTab) {
Rectangle2D leftTabRect = getAbsoluteRect(leftTab);
Rectangle2D rightTabRect = getAbsoluteRect(rightTab);
if(betweenX(leftTabRect, rightTabRect, screenPoint.getX())) {
tabInsertIndex = i + 1;
break;
}
}
}
}
}
return new InsertData(tabInsertIndex, tabPane);
}
}
return null;
}
private Rectangle2D getAbsoluteRect(Control node) {
return new Rectangle2D(node.localToScene(node.getLayoutBounds().getMinX(), node.getLayoutBounds().getMinY()).getX() + node.getScene().getWindow().getX(),
node.localToScene(node.getLayoutBounds().getMinX(), node.getLayoutBounds().getMinY()).getY() + node.getScene().getWindow().getY(),
node.getWidth(),
node.getHeight());
}
private Rectangle2D getAbsoluteRect(Tab tab) {
Control node = ((DraggableTab) tab).getLabel();
return getAbsoluteRect(node);
}
private Label getLabel() {
return nameLabel;
}
private boolean betweenX(Rectangle2D r1, Rectangle2D r2, double xPoint) {
double lowerBound = r1.getMinX() + r1.getWidth() / 2;
double upperBound = r2.getMaxX() - r2.getWidth() / 2;
return xPoint >= lowerBound && xPoint <= upperBound;
}
private static class InsertData {
private final int index;
private final TabPane insertPane;
public InsertData(int index, TabPane insertPane) {
this.index = index;
this.insertPane = insertPane;
}
public int getIndex() {
return index;
}
public TabPane getInsertPane() {
return insertPane;
}
}
}
I just found out that this has been implemented in JavaFX 10.
tabPane.tabDragPolicy = TabPane.TabDragPolicy.REORDER
...does the trick.
Update Feb 2016
There is an open feature request you can use to track implementation:
JDK-8092098 [TabPane] Support for draggable tabs
The feature request is currently scheduled for implementation in Java 9. Patches for obtaining drag and drop functionality are attached to the feature request.
Drag and Drop for tab headers is not implemented in the base JavaFX 2.2 platform.
Until that is implemented in the standard JDK, you will need to implement the feature yourself using JavaFX's Drag and Drop functionality. A similar feature is implemented for dragging table column headers, so perhaps you could look to the TableColumnHeader.java code for inspiration in implementing your feature.
Should you implement it (if you wish) you can contribute the modifications back to OpenJFX via patches to the TabSkin.java source.
A very descriptive answer can be found where you can create custom tabs for the same:
http://0divides0.wordpress.com/2010/10/21/movable-tabbed-panes-in-javafx/
A JavaFX cooked solution is hard to find as dev blog for the same states that such functionality is not present for Tabs and they plan to incorporate later.
http://grokbase.com/p/openjdk/openjfx-dev/123fq9k310/draggable-tabs
The following code shows how to solve the problem in a very simple way without tricks.
.....
.....
Tab tab1 = new Tab("Tab1");
Tab tab2 = new Tab("Tab21");
TabPane tabPane = new TabPane(tab1, tab21);
root.getChildren().add(tabPane);
....
....
System.out.println("Tabs size()= " + tabPane.lookupAll(".tab").size());
tabPane.lookupAll(".tab").forEach(t -> {
System.err.println("tab.bounds = " + t.getLayoutBounds());
});
You can get an access to other areas of TabPane by using style classes such as tab-content-area, tab-header-area, tab-header-background, headers-region, control-buttons-tab. Just use lookup or lookupAll methods of TabPane

Resources