Properly Remove a dockable

Hi,

Could you please advice, how to properly remove a dockable?

I tried to create a dockable, removing it and then create another dockable. I noticed that I have 2 instead of 1.

Scenario : Click on “Add Green Dockable” --> “Remove Green Dockable” --> “Add Green Dockable” --> 2 green dockable stacked


 
 
[SIZE=2][SIZE=2][COLOR=#7f0055][SIZE=2][COLOR=#7f0055]**package **[/SIZE]tutorial.common.basics;
 
 
 
[LEFT][SIZE=2]**import **[/SIZE]bibliothek.gui.dock.common.CControl;[/LEFT]
 
 
 
 
[LEFT][SIZE=2]**import **[/SIZE]bibliothek.gui.dock.common.CGrid;[/LEFT]
 
 
 
 
[LEFT][SIZE=2]**import **[/SIZE]bibliothek.gui.dock.common.CLocation;[/LEFT]
 
 
 
 
[LEFT][SIZE=2]**import **[/SIZE]bibliothek.gui.dock.common.DefaultSingleCDockable;[/LEFT]
 
 
 
 
[LEFT][SIZE=2]**import **[/SIZE]bibliothek.gui.dock.common.mode.ExtendedMode;[/LEFT]
 
 
 
 
 
[LEFT][SIZE=2]**import **[/SIZE]net.miginfocom.swing.MigLayout;[/LEFT]
 
 
 
 
 
[LEFT][SIZE=2]**import **[/SIZE]tutorial.support.ColorSingleCDockable;[/LEFT]
 
 
 
 
[LEFT][SIZE=2]**import **[/SIZE]tutorial.support.JTutorialFrame;[/LEFT]
 
 
 
 
 
 
[LEFT][SIZE=2]**public **[/SIZE][SIZE=2]**class**[/SIZE] AddRemoveDockableExample {[/LEFT]
 
 
 
 
[LEFT]**[SIZE=2]private[/SIZE] CGrid [SIZE=2]grid[/SIZE] = [SIZE=2]null[/SIZE]****;**

**[SIZE=2]private[/SIZE] CControl [SIZE=2]control[/SIZE]****;**
[LEFT]**[SIZE=2]private[/SIZE] DefaultSingleCDockable [SIZE=2]green[/SIZE]****;**
**[SIZE=2]private[/SIZE] JTutorialFrame [SIZE=2]frame[/SIZE]****;**[/LEFT]
[/LEFT]

 
 
 
 
 
[LEFT][SIZE=2]**public**[/SIZE]** AddRemoveDockableExample() {**

**[SIZE=2]frame[/SIZE] = [SIZE=2]new[/SIZE] JTutorialFrame(AddRemoveDockableExample.[SIZE=2]class[/SIZE]****);**
[LEFT]**[SIZE=2]control[/SIZE] = [SIZE=2]new[/SIZE] CControl([SIZE=2]frame[/SIZE]****);**
**[SIZE=2]frame[/SIZE].setLayout([SIZE=2]new[/SIZE] MigLayout([SIZE=2]"fill"[/SIZE]****));**[/LEFT]
[/LEFT]

 
 
 
 
[LEFT]**JButton addBtn = ****[SIZE=2]new[/SIZE] JButton([SIZE=2]"Add Green Dockable"[/SIZE]****);**

**addBtn.addActionListener(**[SIZE=2]**new**[/SIZE]** ActionListener() {**
[LEFT][SIZE=2]**@Override**[/SIZE]
**[SIZE=2]public[/SIZE][SIZE=2]void[/SIZE]**** actionPerformed(ActionEvent actionEvent) {**
**[SIZE=2]green[/SIZE] = [SIZE=2]new[/SIZE] ColorSingleCDockable([SIZE=2]"Green"[/SIZE], Color.**[SIZE=2]GREEN[/SIZE]******);**
**[SIZE=2]grid[/SIZE].add(10, 0, 5, 5, [SIZE=2]green[/SIZE]****);**
**[SIZE=2]control[/SIZE].getContentArea().deploy([SIZE=2]grid[/SIZE]****);**[/LEFT]
[/LEFT]

 
 
 
 
[LEFT]**[SIZE=2]control[/SIZE].getContentArea().getCenter().getContentPane().add([SIZE=2]new[/SIZE] JButton([SIZE=2]"CouCou"[/SIZE]****));**[/LEFT]
 
 
 
[LEFT][SIZE=2]**green**[/SIZE]**.setLocation(CLocation.**base**().minimalWest());**

**[SIZE=2]green[/SIZE].setExtendedMode(ExtendedMode.**[SIZE=2]NORMALIZED[/SIZE]******);**
[LEFT]**[SIZE=2]green[/SIZE].setCloseable([SIZE=2]true[/SIZE]****);**
**[SIZE=2]green[/SIZE].setExternalizable([SIZE=2]true[/SIZE]****);**
**[SIZE=2]green[/SIZE].setMaximizable([SIZE=2]true[/SIZE]****);**
**}**
**});**[/LEFT]
[/LEFT]

 
 
 
 
 
[LEFT]**JButton removeBtn = ****[SIZE=2]new[/SIZE] JButton([SIZE=2]"Remove Green Dockable"[/SIZE]****);**

**removeBtn.addActionListener(**[SIZE=2]**new**[/SIZE]** ActionListener() {**
[LEFT][SIZE=2]**@Override**[/SIZE]
**[SIZE=2]public[/SIZE][SIZE=2]void[/SIZE]**** actionPerformed(ActionEvent actionEvent) {**
**[SIZE=2]control[/SIZE].remove([SIZE=2]green[/SIZE]****);**
**}**
**});**[/LEFT]
[/LEFT]

 
 
 
 
[LEFT]**[SIZE=2]frame[/SIZE].getContentPane().add(addBtn, [SIZE=2]"cell 0 0"[/SIZE]****);**

**[SIZE=2]frame[/SIZE].getContentPane().add(removeBtn, [SIZE=2]"cell 1 0"[/SIZE]****);**
[LEFT]**[SIZE=2]frame[/SIZE].getContentPane().add([SIZE=2]control[/SIZE].getContentArea(), [SIZE=2]"cell 0 1, span, grow, push"[/SIZE]****);**[/LEFT]
[/LEFT]

 
 
 
 
[LEFT]**DefaultSingleCDockable red = ****[SIZE=2]new[/SIZE] ColorSingleCDockable([SIZE=2]"Red"[/SIZE], Color.**[SIZE=2]RED[/SIZE]******);**[/LEFT]
 
 
 
[LEFT]**[SIZE=2]grid[/SIZE] = [SIZE=2]new[/SIZE] CGrid([SIZE=2]control[/SIZE]****);**

[SIZE=2]**grid**[/SIZE]**.add(0, 0, 10, 10, red);**[/LEFT]

 
 
 
 
[LEFT]**[SIZE=2]control[/SIZE].getContentArea().deploy([SIZE=2]grid[/SIZE]****);**[/LEFT]
 
 
 
[LEFT]**red.setLocation(CLocation.**base**().minimalEast());**

**red.setExtendedMode(ExtendedMode.****[SIZE=2]**NORMALIZED**[/SIZE]****);**[/LEFT]

 
 
 
 
[LEFT]**[SIZE=2]frame[/SIZE].setVisible([SIZE=2]true[/SIZE]****);**

**}**[/LEFT]

 
 
 
 
 
[LEFT]**[SIZE=2]public[/SIZE][SIZE=2]static[/SIZE][SIZE=2]void[/SIZE]**** main(String[] args) {**

[SIZE=2]**new**[/SIZE]** AddRemoveDockableExample();**
[LEFT]**}**[/LEFT]
[/LEFT]

 
 
[LEFT]**}**[/LEFT]
[/COLOR][/SIZE][/COLOR][/SIZE]

You need to use “CDockable.setVisisible”:

“control.remove(green);” unlinks the Dockable with the CControl. The CControl is no longer interested in “green” (all meta information is deleted), it remains however in the tree of Dockables and DockStations.

“green.setVisible(false)” the Dockable is removed from its parent, thus removed from the tree of Dockables and DockStations. The CControl stores meta information about the Dockable. This meta information can later be used to make “green” again visible.

Hi Beni,

Thx for your reply

[ol]
[li]I tried [/li]```
[SIZE=2]green[/SIZE].setVisible([SIZE=2]false[/SIZE]);


I click on "Add Green Dockable" --> click on "Remove Green Dockable" (It works fine till now). If I click on "Add Green Dockable" again, I'll got the following exception:
 
 


[SIZE=2][LEFT]Exception in thread “AWT-EventQueue-0” [/LEFT]

[LEFT][/SIZE][SIZE=2]java.lang.IllegalArgumentException[/SIZE][SIZE=2]: unique id ‘Green’ already in use for another SingleCDockable[/SIZE][/LEFT]

[SIZE=2]at bibliothek.gui.dock.common.CControl.add(
[LEFT][/SIZE][SIZE=2]CControl.java:1554[/SIZE][SIZE=2])[/SIZE][/LEFT]

[LEFT][SIZE=2]at bibliothek.gui.dock.common.CControl.addDockable([/SIZE][/LEFT]

[LEFT][SIZE=2]CControl.java:1524[/SIZE][SIZE=2])[/SIZE][/LEFT]

[LEFT][SIZE=2]at bibliothek.gui.dock.common.CGrid.add([/SIZE][/LEFT]

[LEFT][SIZE=2]CGrid.java:115[/SIZE][SIZE=2])[/SIZE]
[SIZE=2]at tutorial.common.basics.AddRemoveDockableExample$1.actionPerformed [/SIZE][SIZE=2]AddRemoveDockableExample.java:47[/SIZE][SIZE=2])[/SIZE][/LEFT]



[li]I tried [/li]

green.setVisible([SIZE=2]false[/SIZE]);[SIZE=2]control[/SIZE].remove([SIZE=2]green[/SIZE]);


Duplicate green dockables are noticed
[/ol]In my case, I want to add a dockable, than replace this dockable by another instances of dockable (several times)
 
Could you please advice?
 
Thank you

“CGrid.add” registers the Dockable again at the CControl, and since the id is already in use you get the exception. Don’t use the CGrid for more than building the initial layout, it is not built for anything else.

As for replacing: I would just replace the contents of the Dockable, not the Dockable itself. Otherwise using persistent layouts can get quite tricky because the same identifier will be used for different Dockables.

But if your application absolutely demands it: Make sure that CControl does not delete meta information by registering a “SingleCDockableFactory” with the same identifier as the Dockable. Use the method “CControl.addSingleDockableFactory” for that (or implement a “MissingCDockableStrategy” which leads to the same effects, see “CControl.setMissingStrategy”).
Then call both “setVisible(false)” and “CControl.remove” to remove the first Dockable completely. Afterwards call “CControl.addSingleDockable” and “setVisible(true)” for the replacement. The meta information (like the location) associated with the first Dockable will be applied to the replacement.

Hi Beni,

I have the exact same issue as described above. I want to add a dockable, remove it, then replace it by another instance of dockable, at the same location.

I followed what you said as for how to:

  • Remove the dockable using “setVisible(false)” and “CControl.remove”
  • Add the new dockable using “CControl.addSingleDockable” and “setVisible(true)”, without calling “CGrid.add”, nor “deploy(grid)” since you’re saying the CGrid should not be used for more than building the initial layout

The replacement dockable is displayed properly, except that it’s not at the same location.

You mentioned using: „CControl.addSingleDockableFactory“ or „Control.setMissingStrategy“.
Can you please provide more details on how to use those? A sample program would be great! :smiley:

Thanks a lot Beni,
Shant

It’s quite a simple example. Just click the “replace” button and see how the title of the “green” dockable changes (because the entire dockable was replaced). You can comment out the factory, to see how its very existence changes the behavior of the application.

This is the solution with SingleCDockableFactory, which I personally like much more than the solution with the “MissingCDockableStrategy”. This solution also allows lazy loading of the “green” dockable, usefull for bigger projects.


import java.awt.BorderLayout;
import java.awt.Color;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;

import javax.swing.JButton;
import javax.swing.JFrame;
import javax.swing.JPanel;

import bibliothek.gui.dock.common.CControl;
import bibliothek.gui.dock.common.CGrid;
import bibliothek.gui.dock.common.DefaultSingleCDockable;
import bibliothek.gui.dock.common.SingleCDockable;
import bibliothek.gui.dock.common.SingleCDockableFactory;

public class Dock20 {
	private static int count = 0;
	
	public static void main( String[] args ){
		JFrame frame = new JFrame();
		final CControl control = new CControl( frame );
		frame.setDefaultCloseOperation( JFrame.EXIT_ON_CLOSE );

		// register a factory for "green". This tells the framework, that there can be a dockable
		// with id "green", and thus all meta-information needs to be/remain stored.
		control.addSingleDockableFactory( "green", new SingleCDockableFactory(){
			@Override
			public SingleCDockable createBackup( String id ){
				return createGreen();
			}
		} );
		
		// This is how you could have the same results with a MissingCDockableStrategy
//		control.setMissingStrategy( new MissingCDockableStrategy(){
//			@Override
//			public boolean shouldStoreSingle( String id ){
//				return id.equals( "green" );
//			}
//			
//			@Override
//			public boolean shouldStoreMulti( String id ){
//				return false;
//			}
//			
//			@Override
//			public <L extends MultipleCDockableLayout> boolean shouldCreate( String id, MultipleCDockableFactory<?, L> factory, String uniqueId, L data ){
//				return false;
//			}
//			
//			@Override
//			public boolean shouldCreate( String id, MultipleCDockableFactory<?, ?> factory ){
//				return false;
//			}
//		} );
		
		frame.add( control.getContentArea() );

		CGrid grid = new CGrid( control );
		grid.add( 0, 0, 1, 1, create( "a", "Aaaa" ) );
		grid.add( 0, 1, 1, 1, create( "b", "Bbbb" ) );
		grid.add( 1, 0, 1, 1, create( "c", "Cccc" ) );
		grid.add( 1, 1, 1, 1, create( "d", "Dddd" ) );
		grid.add( 3, 0, 1, 1, create( "e", "Eeee" ) );
		grid.add( 3, 1, 1, 1, create( "f", "Ffff" ) );
		grid.add( 2, 0, 1, 2, createGreen() );
		
		control.getContentArea().deploy( grid );

		JButton replace = new JButton( "Replace" );
		frame.add( replace, BorderLayout.SOUTH );
		replace.addActionListener( new ActionListener(){
			@Override
			public void actionPerformed( ActionEvent e ){
				// remove the old dockable...
				DefaultSingleCDockable green = (DefaultSingleCDockable)control.getSingleDockable( "green" );
				green.setVisible( false );
				control.removeDockable( green );
				
				// ... create and add the new one
				green = createGreen();
				control.addDockable( green );
				green.setVisible( true );
			}
		} );
		
		frame.setBounds( 20, 20, 400, 400 );
		frame.setVisible( true );
	}
	
	private static DefaultSingleCDockable createGreen(){
		DefaultSingleCDockable green = new DefaultSingleCDockable( "green", "Green " + count );
		count++;
		JPanel panel = new JPanel();
		panel.setOpaque( true );
		panel.setBackground( Color.GREEN );
		green.add( panel );
		return green;
	}
	
	private static SingleCDockable create(String key, String title){
		DefaultSingleCDockable dockable = new DefaultSingleCDockable( key, title );
		return dockable;
	}
}

Thank you so much Beni for the sample application! :smiley: This is exactly what I was looking for. I still have a couple of questions if you don’t mind.

Question 1:
I tried commenting out the factory, as you pointed, and noticed how it is the factory that is telling the framework to store the meta-information for the dockable with the given id. What I didn’t understand though is the implementation of createBackup. The fact that createGreen() is explicitly called inside grid.add (line 67) and then again inside actionPerformed of the ‚replace‘ button (line 82) made me wonder when would it be called inside createBackup? So I debugged and couldn’t detect any call of the createBackup method. So can you please clarify this point?

Question 2:
I have a mixture of standalone dockables but also dockStations. As you can see, I use CStack and CStackDockStation to create dockStations. I modified the sample application such that one of the dockables becomes a dockStation. I need to implement the same REPLACE behavior on the dockStation. I prepared its own ‚replace‘ button to remove it and add it back. There’s only the factory part for the dockStation that is missing. Can you please tell me what should I do to achieve the same effect as ‚addSingleDockableFactory‘ on my dockStation?

Thanks a lot,
Shant


import java.awt.BorderLayout;
import java.awt.Color;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;

import javax.swing.JButton;
import javax.swing.JFrame;
import javax.swing.JPanel;

import bibliothek.gui.DockController;
import bibliothek.gui.DockStation;
import bibliothek.gui.Dockable;
import bibliothek.gui.dock.StackDockStation;
import bibliothek.gui.dock.action.DockActionSource;
import bibliothek.gui.dock.common.CControl;
import bibliothek.gui.dock.common.CGrid;
import bibliothek.gui.dock.common.CLocation;
import bibliothek.gui.dock.common.CStation;
import bibliothek.gui.dock.common.DefaultSingleCDockable;
import bibliothek.gui.dock.common.SingleCDockable;
import bibliothek.gui.dock.common.SingleCDockableFactory;
import bibliothek.gui.dock.common.intern.AbstractDockableCStation;
import bibliothek.gui.dock.common.intern.CControlAccess;
import bibliothek.gui.dock.common.intern.CDockable;
import bibliothek.gui.dock.common.intern.CommonDockable;
import bibliothek.gui.dock.common.intern.station.CommonDockStation;
import bibliothek.gui.dock.common.intern.station.CommonDockStationFactory;
import bibliothek.gui.dock.common.mode.CNormalModeArea;
import bibliothek.gui.dock.common.mode.ExtendedMode;
import bibliothek.gui.dock.common.perspective.CStationPerspective;
import bibliothek.gui.dock.event.DockStationAdapter;
import bibliothek.gui.dock.facile.mode.Location;
import bibliothek.gui.dock.facile.mode.LocationMode;
import bibliothek.gui.dock.facile.mode.ModeAreaListener;
import bibliothek.gui.dock.layout.DockableProperty;
import bibliothek.gui.dock.support.mode.AffectedSet;
import bibliothek.gui.dock.util.DockUtilities;
import bibliothek.util.Path;
 
public class Dock20 {
    private static int count = 0;
    
    public static void main( String[] args ){
        JFrame frame = new JFrame();
        final CControl control = new CControl( frame );
        frame.setDefaultCloseOperation( JFrame.EXIT_ON_CLOSE );
 
        // register a factory for "green". This tells the framework, that there can be a dockable
        // with id "green", and thus all meta-information needs to be/remain stored.
        control.addSingleDockableFactory( "green", new SingleCDockableFactory(){
            @Override
            public SingleCDockable createBackup( String id ){
            	//When is this needed?
                return createGreen();
            }
        } );
        
        // This is how you could have the same results with a MissingCDockableStrategy
//      control.setMissingStrategy( new MissingCDockableStrategy(){
//          @Override
//          public boolean shouldStoreSingle( String id ){
//              return id.equals( "green" );
//          }
//          
//          @Override
//          public boolean shouldStoreMulti( String id ){
//              return false;
//          }
//          
//          @Override
//          public <L extends MultipleCDockableLayout> boolean shouldCreate( String id, MultipleCDockableFactory<?, L> factory, String uniqueId, L data ){
//              return false;
//          }
//          
//          @Override
//          public boolean shouldCreate( String id, MultipleCDockableFactory<?, ?> factory ){
//              return false;
//          }
//      } );
        
        frame.add( control.getContentArea() );
        
        CStack stack = createStack(control);
 
        CGrid grid = new CGrid( control );
        grid.add( 0, 0, 1, 1, create( "a", "Aaaa" ) );
        grid.add( 0, 1, 1, 1, create( "b", "Bbbb" ) );
        grid.add( 1, 0, 1, 1, create( "c", "Cccc" ) );
        grid.add( 1, 1, 1, 1, create( "d", "Dddd" ) );
        grid.add( 3, 0, 1, 1, create( "e", "Eeee" ) );
        grid.add( 3, 1, 1, 1, stack );
        grid.add( 2, 0, 1, 2, createGreen() );
        
        control.getContentArea().deploy( grid );
 
        JButton replaceGreen = new JButton( "Replace Green Dockable" );
        frame.add( replaceGreen, BorderLayout.SOUTH );
        replaceGreen.addActionListener( new ActionListener(){
            @Override
            public void actionPerformed( ActionEvent e ){
                // remove the old dockable...
                DefaultSingleCDockable green = (DefaultSingleCDockable)control.getSingleDockable( "green" );
                green.setVisible( false );
                control.removeDockable( green );
                
                // ... create and add the new one
                green = createGreen();
                control.addDockable( green );
                green.setVisible( true );
            }
        } );
        
        JButton replaceStack = new JButton( "Replace Stack" );
        frame.add( replaceStack, BorderLayout.NORTH );
        replaceStack.addActionListener( new ActionListener(){
            @Override
            public void actionPerformed( ActionEvent e ){
            	CStack stack = (CStack)control.getStation("dockStation");
            	stack.setVisible(false);
                control.removeDockable(stack);
                control.remove(stack.asStation());
                
                stack = createStack(control);
                control.addDockable(stack);
                stack.setVisible(true);
            }
        } );
        
        frame.setBounds( 20, 20, 400, 400 );
        frame.setVisible( true );
    }
    
    private static DefaultSingleCDockable createGreen(){
        DefaultSingleCDockable green = new DefaultSingleCDockable( "green", "Green " + count );
        count++;
        JPanel panel = new JPanel();
        panel.setOpaque( true );
        panel.setBackground( Color.GREEN );
        green.add( panel );
        return green;
    }
    
    private static CStack createStack(CControl control){
    	CStack stack = new CStack("dockStation");
        control.addStation(stack, true);
        
        SingleCDockable tab1 = create( "tab1", "tab1" );
        stack.getStation().add(tab1.intern(), 0);
        
        SingleCDockable tab2 = create( "tab2", "tab2" );
        stack.getStation().add(tab2.intern(), 1);
        return stack;
    }
    
    private static SingleCDockable create(String key, String title){
        DefaultSingleCDockable dockable = new DefaultSingleCDockable( key, title );
        return dockable;
    }
    
    static class CStack extends AbstractDockableCStation<CStackDockStation> implements CNormalModeArea, SingleCDockable {
        private boolean hideIfEmpty;
 
 
        public CStack(String id) {
            CStackDockStation delegate = new CStackDockStation(this);
            CLocation stationLocation = new CLocation() {
 
                @Override
                public CLocation getParent() {
                    return null;
                }
 
                @Override
                public String findRoot() {
                    return getUniqueId();
                }
 
                @Override
                public DockableProperty findProperty(DockableProperty successor) {
                    return successor;
                }
 
                @Override
                public ExtendedMode findMode() {
                    return ExtendedMode.NORMALIZED;
                }
 
                @Override
                public CLocation aside() {
                    return this;
                }
            };
            init(delegate, id, stationLocation, delegate);
        }
 
 
        public void setHideIfEmpty(boolean hideIfEmpty) {
            this.hideIfEmpty = hideIfEmpty;
        }
 
        @Override
        public CStationPerspective createPerspective() {
            throw new IllegalStateException("not implemented");
        }
 
        @Override
        public boolean isNormalModeChild(Dockable dockable) {
            return isChild(dockable);
        }
 
        @Override
        public DockableProperty getLocation(Dockable child) {
            return DockUtilities.getPropertyChain(getStation(), child);
        }
 
        @Override
        public void setLocation(Dockable dockable, DockableProperty location, AffectedSet set) {
            set.add(dockable);
            if (isChild(dockable)) {
                getStation().move(dockable, location);
            } else {
                if (!getStation().drop(dockable, location)) {
                    getStation().drop(dockable);
                }
            }
        }
 
        @Override
        public void addModeAreaListener(ModeAreaListener listener) {
        }
 
        @Override
        public Path getTypeId() {
            return null;
        }
 
        @Override
        public boolean autoDefaultArea() {
            return true;
        }
 
        @Override
        public boolean isChild(Dockable dockable) {
            return dockable.getDockParent() == getStation();
        }
 
        @Override
        public void removeModeAreaListener(ModeAreaListener listener) {
        }
 
        @Override
        public void setController(DockController controller) {
        }
 
        @Override
        public void setMode(LocationMode mode) {
        }
 
        @Override
        public CLocation getCLocation(Dockable dockable) {
            DockableProperty property = DockUtilities.getPropertyChain(getStation(), dockable);
            return getStationLocation().expandProperty(property);
        }
 
        @Override
        public CLocation getCLocation(Dockable dockable, Location location) {
            DockableProperty property = location.getLocation();
            if (property == null) {
                return getStationLocation();
            }
            return getStationLocation().expandProperty(property);
        }
 
        @Override
        public boolean respectWorkingAreas() {
            return false;
        }
 
        @Override
        public boolean isCloseable() {
            return false;
        }
 
        @Override
        public boolean isExternalizable() {
            return false;
        }
 
        @Override
        public boolean isMaximizable() {
            return false;
        }
 
        @Override
        public boolean isMinimizable() {
            return false;
        }
 
        @Override
        public boolean isStackable() {
            return false;
        }
 
        @Override
        public boolean isWorkingArea() {
            return false;
        }
 
        public boolean hideIfEmpty() {
            return hideIfEmpty;
        }
 
        public DockActionSource[] getSources() {
            return new DockActionSource[] { getClose() };
        }
 
        @Override
        protected void install(CControlAccess access) {
            access.getLocationManager().getNormalMode().add(this);
        }
 
        @Override
        protected void uninstall(CControlAccess access) {
            access.getLocationManager().getNormalMode().remove(getUniqueId());
        }
    }
 
    static class CStackDockStation extends StackDockStation implements CommonDockStation<StackDockStation, CStackDockStation>, CommonDockable {
 
        private CStack delegate;
 
 
        public CStackDockStation(CStack stack) {
            this.delegate = stack;
            final Runnable makeVisible = new Runnable() {
                @Override
                public void run() {
                    delegate.setVisible(true);
                }
            };
            DockUtilities.disableCheckLayoutLocked();
            addDockStationListener(new DockStationAdapter() {
                    @Override
                    public void dockableAdded(DockStation station, Dockable dockable) {
                        DockController controller = delegate.getControl().getController();
                        controller.getHierarchyLock().onRelease(makeVisible);
                    }
                });
        }
 
 
        @Override
        public CDockable getDockable() {
            return delegate;
        }
 
        @Override
        public DockActionSource[] getSources() {
            return delegate.getSources();
        }
 
        @Override
        public CStation<CStackDockStation> getStation() {
            return delegate;
        }
 
        @Override
        public StackDockStation getDockStation() {
            return this;
        }
 
        @Override
        public CStackDockStation asDockStation() {
            return this;
        }
 
        @Override
        public CommonDockable asDockable() {
            return this;
        }
 
        @Override
        public String getConverterID() {
            return super.getFactoryID();
        }
 
        @Override
        public String getFactoryID() {
            return CommonDockStationFactory.FACTORY_ID;
        }
    }
}```

About 1, the “createBackup” method is called if:

  • the Dockable is not registered (e.g. you never called “addDockable”) AND…
  • … a layout is loaded (e.g. from an xml file).

This allows you to load a layout without creating all necessary Dockables first. It’s an optimization that can be used for big clients.

About 2, there are two things you need.

  1. since CStack is a SingleCDockable itself, you can add another factory. Like this:
			@Override
			public SingleCDockable createBackup( String id ){
				return createStack( control );
			}
		} );```
(and if you are going to use more than one CStack, you can consider using the second "addSingleDockableFactory" method, which does require a "Filter" instead of a "String" as first argument).
 2. The CStackDockStation automatically calls "setVisible", you must not call this code if you replace the station. Because if you do, the station makes itself visible too early (Some more debugging would be required to tell what exactly happens, but the explanation is close to the truth).

Hi Beni,

Thank you for clarifying point 1.

About 2, yeah I had already tried adding a singleDockableFactory for the CStack itself but it hadn’t work. Now, I tried again, as you suggested, and this time I removed the line “stack.setVisible(true);” from the actionPerformed method of the ‘replaceStack’ button, but it still didn’t work, i.e. the stack was not added back to its original location.

You said I need two things:
1- addSingleDockableFactory for “dockStation”
2- remove “stack.setVisible(true);”

Did I get you right? Is there something else I’m missing?

Thanks a lot,
Shant

I think we are not speaking of the same call to “setVisible”. Try this application, it works on my system.


import java.awt.BorderLayout;
import java.awt.Color;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;

import javax.swing.JButton;
import javax.swing.JFrame;
import javax.swing.JPanel;

import bibliothek.gui.DockController;
import bibliothek.gui.DockStation;
import bibliothek.gui.Dockable;
import bibliothek.gui.dock.StackDockStation;
import bibliothek.gui.dock.action.DockActionSource;
import bibliothek.gui.dock.common.CControl;
import bibliothek.gui.dock.common.CGrid;
import bibliothek.gui.dock.common.CLocation;
import bibliothek.gui.dock.common.CStation;
import bibliothek.gui.dock.common.DefaultSingleCDockable;
import bibliothek.gui.dock.common.SingleCDockable;
import bibliothek.gui.dock.common.SingleCDockableFactory;
import bibliothek.gui.dock.common.intern.AbstractDockableCStation;
import bibliothek.gui.dock.common.intern.CControlAccess;
import bibliothek.gui.dock.common.intern.CDockable;
import bibliothek.gui.dock.common.intern.CommonDockable;
import bibliothek.gui.dock.common.intern.station.CommonDockStation;
import bibliothek.gui.dock.common.intern.station.CommonDockStationFactory;
import bibliothek.gui.dock.common.mode.CNormalModeArea;
import bibliothek.gui.dock.common.mode.ExtendedMode;
import bibliothek.gui.dock.common.perspective.CStationPerspective;
import bibliothek.gui.dock.event.DockStationAdapter;
import bibliothek.gui.dock.facile.mode.Location;
import bibliothek.gui.dock.facile.mode.LocationMode;
import bibliothek.gui.dock.facile.mode.ModeAreaListener;
import bibliothek.gui.dock.layout.DockableProperty;
import bibliothek.gui.dock.support.mode.AffectedSet;
import bibliothek.gui.dock.util.DockUtilities;
import bibliothek.util.Path;
 
public class Dock20 {
    private static int count = 0;
    
    public static void main( String[] args ){
        JFrame frame = new JFrame();
        final CControl control = new CControl( frame );
        frame.setDefaultCloseOperation( JFrame.EXIT_ON_CLOSE );
 
        // register a factory for "green". This tells the framework, that there can be a dockable
        // with id "green", and thus all meta-information needs to be/remain stored.
        control.addSingleDockableFactory( "green", new SingleCDockableFactory(){
            @Override
            public SingleCDockable createBackup( String id ){
            	//When is this needed?
                return createGreen();
            }
        } );
        control.addSingleDockableFactory( "dockStation", new SingleCDockableFactory(){
			@Override
			public SingleCDockable createBackup( String id ){
				return createStack( control, false );
			}
		} );
        
        // This is how you could have the same results with a MissingCDockableStrategy
//      control.setMissingStrategy( new MissingCDockableStrategy(){
//          @Override
//          public boolean shouldStoreSingle( String id ){
//              return id.equals( "green" );
//          }
//          
//          @Override
//          public boolean shouldStoreMulti( String id ){
//              return false;
//          }
//          
//          @Override
//          public <L extends MultipleCDockableLayout> boolean shouldCreate( String id, MultipleCDockableFactory<?, L> factory, String uniqueId, L data ){
//              return false;
//          }
//          
//          @Override
//          public boolean shouldCreate( String id, MultipleCDockableFactory<?, ?> factory ){
//              return false;
//          }
//      } );
        
        frame.add( control.getContentArea() );
        
        CStack stack = createStack(control, true);
 
        CGrid grid = new CGrid( control );
        grid.add( 0, 0, 1, 1, create( "a", "Aaaa" ) );
        grid.add( 0, 1, 1, 1, create( "b", "Bbbb" ) );
        grid.add( 1, 0, 1, 1, create( "c", "Cccc" ) );
        grid.add( 1, 1, 1, 1, create( "d", "Dddd" ) );
        grid.add( 3, 0, 1, 1, create( "e", "Eeee" ) );
        grid.add( 3, 1, 1, 1, stack );
        grid.add( 2, 0, 1, 2, createGreen() );
        
        control.getContentArea().deploy( grid );
 
        JButton replaceGreen = new JButton( "Replace Green Dockable" );
        frame.add( replaceGreen, BorderLayout.SOUTH );
        replaceGreen.addActionListener( new ActionListener(){
            @Override
            public void actionPerformed( ActionEvent e ){
                // remove the old dockable...
                DefaultSingleCDockable green = (DefaultSingleCDockable)control.getSingleDockable( "green" );
                green.setVisible( false );
                control.removeDockable( green );
                
                // ... create and add the new one
                green = createGreen();
                control.addDockable( green );
                green.setVisible( true );
            }
        } );
        
        JButton replaceStack = new JButton( "Replace Stack" );
        frame.add( replaceStack, BorderLayout.NORTH );
        replaceStack.addActionListener( new ActionListener(){
            @Override
            public void actionPerformed( ActionEvent e ){
            	CStack stack = (CStack)control.getStation("dockStation");
            	stack.setVisible(false);
                control.removeDockable(stack);
                control.remove(stack.asStation());
                
                stack = createStack(control, false);
                control.addDockable(stack);
                stack.setVisible(true);
            }
        } );
        
        frame.setBounds( 20, 20, 400, 400 );
        frame.setVisible( true );
    }
    
    private static DefaultSingleCDockable createGreen(){
        DefaultSingleCDockable green = new DefaultSingleCDockable( "green", "Green " + count );
        count++;
        JPanel panel = new JPanel();
        panel.setOpaque( true );
        panel.setBackground( Color.GREEN );
        green.add( panel );
        return green;
    }
    
    private static CStack createStack(CControl control, boolean autoShow){
    	CStack stack = new CStack("dockStation", autoShow);
        control.addStation(stack, true);
        
        SingleCDockable tab1 = create( "tab1", "tab1" );
        stack.getStation().add(tab1.intern(), 0);
        
        SingleCDockable tab2 = create( "tab2", "tab2" );
        stack.getStation().add(tab2.intern(), 1);
        return stack;
    }
    
    private static SingleCDockable create(String key, String title){
        DefaultSingleCDockable dockable = new DefaultSingleCDockable( key, title );
        return dockable;
    }
    
    static class CStack extends AbstractDockableCStation<CStackDockStation> implements CNormalModeArea, SingleCDockable {
        private boolean hideIfEmpty;
 
 
        public CStack(String id, boolean autoShow) {
            CStackDockStation delegate = new CStackDockStation(this, autoShow);
            CLocation stationLocation = new CLocation() {
 
                @Override
                public CLocation getParent() {
                    return null;
                }
 
                @Override
                public String findRoot() {
                    return getUniqueId();
                }
 
                @Override
                public DockableProperty findProperty(DockableProperty successor) {
                    return successor;
                }
 
                @Override
                public ExtendedMode findMode() {
                    return ExtendedMode.NORMALIZED;
                }
 
                @Override
                public CLocation aside() {
                    return this;
                }
            };
            init(delegate, id, stationLocation, delegate);
        }
 
 
        public void setHideIfEmpty(boolean hideIfEmpty) {
            this.hideIfEmpty = hideIfEmpty;
        }
 
        @Override
        public CStationPerspective createPerspective() {
            throw new IllegalStateException("not implemented");
        }
 
        @Override
        public boolean isNormalModeChild(Dockable dockable) {
            return isChild(dockable);
        }
 
        @Override
        public DockableProperty getLocation(Dockable child) {
            return DockUtilities.getPropertyChain(getStation(), child);
        }
 
        @Override
        public void setLocation(Dockable dockable, DockableProperty location, AffectedSet set) {
            set.add(dockable);
            if (isChild(dockable)) {
                getStation().move(dockable, location);
            } else {
                if (!getStation().drop(dockable, location)) {
                    getStation().drop(dockable);
                }
            }
        }
 
        @Override
        public void addModeAreaListener(ModeAreaListener listener) {
        }
 
        @Override
        public Path getTypeId() {
            return null;
        }
 
        @Override
        public boolean autoDefaultArea() {
            return true;
        }
 
        @Override
        public boolean isChild(Dockable dockable) {
            return dockable.getDockParent() == getStation();
        }
 
        @Override
        public void removeModeAreaListener(ModeAreaListener listener) {
        }
 
        @Override
        public void setController(DockController controller) {
        }
 
        @Override
        public void setMode(LocationMode mode) {
        }
 
        @Override
        public CLocation getCLocation(Dockable dockable) {
            DockableProperty property = DockUtilities.getPropertyChain(getStation(), dockable);
            return getStationLocation().expandProperty(property);
        }
 
        @Override
        public CLocation getCLocation(Dockable dockable, Location location) {
            DockableProperty property = location.getLocation();
            if (property == null) {
                return getStationLocation();
            }
            return getStationLocation().expandProperty(property);
        }
 
        @Override
        public boolean respectWorkingAreas() {
            return false;
        }
 
        @Override
        public boolean isCloseable() {
            return false;
        }
 
        @Override
        public boolean isExternalizable() {
            return false;
        }
 
        @Override
        public boolean isMaximizable() {
            return false;
        }
 
        @Override
        public boolean isMinimizable() {
            return false;
        }
 
        @Override
        public boolean isStackable() {
            return false;
        }
 
        @Override
        public boolean isWorkingArea() {
            return false;
        }
 
        public boolean hideIfEmpty() {
            return hideIfEmpty;
        }
 
        public DockActionSource[] getSources() {
            return new DockActionSource[] { getClose() };
        }
 
        @Override
        protected void install(CControlAccess access) {
            access.getLocationManager().getNormalMode().add(this);
        }
 
        @Override
        protected void uninstall(CControlAccess access) {
            access.getLocationManager().getNormalMode().remove(getUniqueId());
        }
    }
 
    static class CStackDockStation extends StackDockStation implements CommonDockStation<StackDockStation, CStackDockStation>, CommonDockable {
 
        private CStack delegate;
 
 
        public CStackDockStation(CStack stack, boolean autoShow) {
            this.delegate = stack;
            if( autoShow ){
                    // you could of course just activate this feature later, but commenting it out was an easy solution for getting the app to run.
	            final Runnable makeVisible = new Runnable() {
	                @Override
	                public void run() {
	                    delegate.setVisible(true);
	                }
	            };
	            DockUtilities.disableCheckLayoutLocked();
	            addDockStationListener(new DockStationAdapter() {
	                    @Override
	                    public void dockableAdded(DockStation station, Dockable dockable) {
	                        DockController controller = delegate.getControl().getController();
	                        controller.getHierarchyLock().onRelease(makeVisible);
	                    }
	            });
            }
        }
 
        @Override
        public void setDockParent( DockStation station ){
        	super.setDockParent( station );
        }
 
        @Override
        public CDockable getDockable() {
            return delegate;
        }
 
        @Override
        public DockActionSource[] getSources() {
            return delegate.getSources();
        }
 
        @Override
        public CStation<CStackDockStation> getStation() {
            return delegate;
        }
 
        @Override
        public StackDockStation getDockStation() {
            return this;
        }
 
        @Override
        public CStackDockStation asDockStation() {
            return this;
        }
 
        @Override
        public CommonDockable asDockable() {
            return this;
        }
 
        @Override
        public String getConverterID() {
            return super.getFactoryID();
        }
 
        @Override
        public String getFactoryID() {
            return CommonDockStationFactory.FACTORY_ID;
        }
    }
}```

Indeed, sorry Beni! :confused: Now, I see! Thanks a lot!

I noticed that in your call: „CStack stack = createStack(control, true);“ (on line 91), you passed autoShow TRUE. I assume the logic behind it is: initially, since we are not yet ‚replacing‘, there won’t be any explicit „setVisible(true)“ call, and hence we enable ‚autoShow‘ to make the „setVisible(true)“ call from within CStackDockStation. Is that right?

However, I noticed that, passing autoShow FALSE is not causing a problem even initially (i.e. line 91).
Is it then safe to completely remove the „setVisible(true)“ logic from CStackDockStation?

            this.delegate = stack;
//            if( autoShow ){
//                    // you could of course just activate this feature later, but commenting it out was an easy solution for getting the app to run.
//                final Runnable makeVisible = new Runnable() {
//                    @Override
//                    public void run() {
//                        delegate.setVisible(true);
//                    }
//                };
//                DockUtilities.disableCheckLayoutLocked();
//                addDockStationListener(new DockStationAdapter() {
//                        @Override
//                        public void dockableAdded(DockStation station, Dockable dockable) {
//                            DockController controller = delegate.getControl().getController();
//                            controller.getHierarchyLock().onRelease(makeVisible);
//                        }
//                });
//            }
        }```

Thanks,
Shant

That was the idea yes. But if you don’t need the code at all, then by all means remove it :slight_smile:

Thank you Beni! :slight_smile:

Hi Beni,

Earlier in this forum, we solved the problem of replacing a dockable with another instance at the same location, by registering a factory for the dockable id, so that the framework stores all meta-information needed to restore the location.

I just have the following concern: if, while running my application, I keep registering a factory for every new dockable id, can this eventually cause me a memory problem?

Thanks a lot,
Shant

In the long run: yes. That is the reason why the framework is so happy when it can delete information :wink:

Two things you might want to consider:

  • What is the maximum number of ids? While I have not done any testing, I would be really surprised if a modern computer has issues with less than 1000 ids.
  • How fast is the number of ids growing? If there is no upper limit, but the growing rate is very slow, then you might be able to just ignore the issue. Of course that is a solution that could fire back…

Also if you restart the application, you may just ignore some factories (never create them) and thus delete old information.

Thank you Beni! :slight_smile: