DockingFrames and Better Swing Application Framework (BSAF)

Hello,

Has anybody managed to get DockingFrames and BSAF saving/restoring application session together?

I use following methods

getContext().getSessionStorage().save(mainFrame, fileName);
getContext().getSessionStorage().restore(mainFrame, fileName);

saving and restoring session of my application.

Following recommendations, I implemented this class


    protected class DockingPropertySupport implements PropertySupport {

        @Override
        public Object getSessionState(Component component) {
            try {
                ByteArrayOutputStream stream = new ByteArrayOutputStream();
                dockFrontend.write(new DataOutputStream(stream));
                return stream.toByteArray();
            } catch (IOException e) {
                Logger logger = Logger.getLogger(DesktopApp.class.getName());
                logger.log(Level.WARNING, "saving docking settings failed", e);
                return null;
            }
        }

        @Override
        public void setSessionState(Component component, Object obj) {
            try {
                ByteArrayInputStream stream = new ByteArrayInputStream((byte[]) obj);
                dockFrontend.read(new DataInputStream(stream));
            } catch (IOException e) {
                Logger logger = Logger.getLogger(DesktopApp.class.getName());
                logger.log(Level.WARNING, "loading docking settings failed", e);
            }
        }
    }

added corresponding property

SessionStorage storage = DesktopApp.getInstance().getContext().getSessionStorage();
storage.putProperty(SplitDockStation.class, new DockingPropertySupport());

and set a name for the dockStation component

dockStation.setName("dockStation");

otherwise it can’t see the dockStation component

As a result it saved mainFrame and dock layout, but it didn’t see any child component of dockStation.

Probably SessionStorage unable to recognize SplitDockStation as a container.

I appreciate any comments and suggestions.

Thank you,

Alex

Can’t see where exactly the issue is, my example below works fine on my computer.

You might want to check:

  • SplitDockStation is registered as root station prior to restoring
  • Any Dockable is registered prior to restoring
  • … or: there are factories registered using “DockFrontend.registerBackupFactory”

If that does not help: write a small executable application that I can run without guessing how you set up the application.


import java.awt.BorderLayout;
import java.awt.Color;
import java.awt.Component;
import java.awt.event.WindowAdapter;
import java.awt.event.WindowEvent;
import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import java.io.DataInputStream;
import java.io.DataOutputStream;
import java.io.IOException;

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

import org.jdesktop.application.Application;
import org.jdesktop.application.SessionStorage;
import org.jdesktop.application.session.PropertySupport;

import bibliothek.gui.DockFrontend;
import bibliothek.gui.dock.DefaultDockable;
import bibliothek.gui.dock.SplitDockStation;
import bibliothek.gui.dock.station.split.SplitDockProperty;

public class Dock35 extends Application {
	private DockFrontend dockFrontend;
	private JFrame frame;

	public Dock35(){
		dockFrontend = new DockFrontend( frame );
	}

	@Override
	protected void startup(){
		SessionStorage storage = getContext().getSessionStorage();
		storage.putProperty( SplitDockStation.class, new DockingPropertySupport() );
		
		frame = new JFrame( "Hello World" );
		frame.setDefaultCloseOperation( JFrame.DO_NOTHING_ON_CLOSE );
		frame.addWindowListener( new WindowAdapter(){
			@Override
			public void windowClosing( WindowEvent e ){
				exit();
			}
		});
		
		SplitDockStation root = new SplitDockStation();
		root.setName( "root" );
		dockFrontend.addRoot( "root", root );
		
		DefaultDockable red = new ColorDockable( "red", Color.RED );
		DefaultDockable green = new ColorDockable( "green", Color.GREEN );
		DefaultDockable blue = new ColorDockable( "blue", Color.BLUE );
		
		dockFrontend.addDockable( "red", red );
		dockFrontend.addDockable( "green", green );
		dockFrontend.addDockable( "blue", blue );
		
		root.drop( red );
		root.drop( green );
		root.drop( blue, SplitDockProperty.EAST );
		
		frame.add( root, BorderLayout.CENTER );
		
		
		
		try {
			storage.restore( frame, "session.xml" );
		}
		catch( IOException e1 ) {
			e1.printStackTrace();
		}
		
		frame.setSize( 500, 300 );
		frame.setVisible( true );
	}

	@Override
	protected void shutdown(){
		frame.setVisible( false );
		try {
			getContext().getSessionStorage().save( frame, "session.xml" );
		}
		catch( IOException e ) {
			// TODO Auto-generated catch block
			e.printStackTrace();
		}
	}

	public static void main( String[] args ){
		Application.launch( Dock35.class, args );
	}

	protected class DockingPropertySupport implements PropertySupport {
		@Override
		public Object getSessionState( Component component ){
			try {
				ByteArrayOutputStream stream = new ByteArrayOutputStream();
				dockFrontend.write( new DataOutputStream( stream ) );
				return stream.toByteArray();
			}
			catch( IOException e ) {
				e.printStackTrace();
				return null;
			}
		}

		@Override
		public void setSessionState( Component component, Object obj ){
			try {
				ByteArrayInputStream stream = new ByteArrayInputStream( (byte[]) obj );
				dockFrontend.read( new DataInputStream( stream ) );
			}
			catch( IOException e ) {
				e.printStackTrace();
			}
		}
	}
	
	public class ColorDockable extends DefaultDockable{
		private JPanel panel = new JPanel();
		
		public ColorDockable( String title, Color color ){
			this( title, color, 1.0f );
		}
		
		public ColorDockable( String title, Color color, float brightness ){
			setTitleText( title );

			if( brightness != 1.0 ){
				float[] hsb = Color.RGBtoHSB( color.getRed(), color.getGreen(), color.getBlue(), null );
				
				hsb[1] = Math.min( 1.0f, hsb[1] / brightness );
				hsb[2] = Math.min( 1.0f, hsb[2] * brightness );
				
				color = Color.getHSBColor( hsb[0], hsb[1], hsb[2] );
			}

			setColor( color );
		}
		
		public void setColor( Color color ){
			panel = new JPanel();
			panel.setOpaque( true );
			panel.setBackground( color );
			add( panel );
		}
		
		public Color getColor(){
			return panel.getBackground();
		}
	}
}

Beni: Thank you very much! It works!

My code was missing of following lines

dockFrontend.addDockable( "red", red );
dockFrontend.addDockable( "green", green );
dockFrontend.addDockable( "blue", blue );

Thank you again! and

Happy New Year!

Now I have another one question. Is there any way to save docking information in XML as a part of XML session file?

I used following code in the getSessionState method

XElement element = new XElement("DockFrontend");
dockFrontend.writeXML(element);
return element.toString();

and received following lines instead of wellformed XML structure

...<void method="put"> 
<string>dockStation/null.contentPane/null.layeredPane/JRootPane0/Form</string> 
<string><?xml version='1.0'?>
<DockFrontend>
<current>...

what it is quite expectable because I converted XElement to String, but I don’t see any better way to put docking information as XML into the XML session file.

Maybe this forum is a wrong place to ask this question, and I need to dig into XMLEncoder that is responsible for that…

Thank you,

Alex

Well, XElement.toString should produce correct XML, but I guess the XMLEncoder then converts the string… so yeah, there is not much I can do from this end of the line.

I’ve got it to work. Now I have well formed XML in the session file

<void method="put"> 
<string>dockStation/null.contentPane/null.layeredPane/JRootPane0/Form</string> 
<object class="app.MainFrame$DockState"> 
<void property="children"> 
<void method="add"> 

First of all I introduced following classes which look similar to your XElement and XAttribute

    public static class DockState {

        private String name;
        private ArrayList<DockStateAttribute> attributes = new ArrayList<DockStateAttribute>();
        private ArrayList<DockState> children = new ArrayList<DockState>();

        public DockState() {
        }

        public String getName() {
            return name;
        }

        public void setName(String name) {
            this.name = name;
        }

        public ArrayList<DockStateAttribute> getAttributes() {
            return attributes;
        }

        public void setAttributes(ArrayList<DockStateAttribute> attributes) {
            this.attributes = attributes;
        }

        public ArrayList<DockState> getChildren() {
            return children;
        }

        public void setChildren(ArrayList<DockState> children) {
            this.children = children;
        }

        public static DockState createDockState(XElement element) {
            DockState dockState = new DockState();
            // set name
            dockState.name = element.getName();
            // copy attributes
            for (XAttribute attr : element.attributes()) {
                DockStateAttribute dsAttr = new DockStateAttribute();
                dsAttr.setName(attr.getName());
                dsAttr.setValue(attr.getValue());
                dockState.attributes.add(dsAttr);
            }
            // copy children
            for (XElement child : element.children()) {
                DockState dsChild = createDockState(child);
                dockState.children.add(dsChild);
            }
            return dockState;
        }

        public static XElement createXElement(DockState dockState) {
            XElement element = new XElement(dockState.name);
            // copy attributes
            for(DockStateAttribute dsAttr : dockState.attributes) {
                XAttribute attr = new XAttribute(dsAttr.name);
                attr.setValue(dsAttr.value);
                element.addAttribute(attr);
            }
            // copy children
            for(DockState dsChild : dockState.children) {
                XElement child = createXElement(dsChild);
                element.addElement(child);
            }
            return element;
        }

        ////////////////////////////////////////////////////////////////////////

        public static class DockStateAttribute {

            private String name;
            private String value;

            public DockStateAttribute() {
            }

            public String getName() {
                return name;
            }

            public void setName(String name) {
                this.name = name;
            }

            public String getValue() {
                return value;
            }

            public void setValue(String value) {
                this.value = value;
            }
        }
    }

then I modified the DockingPropertySupport class in following way

    public class DockingPropertySupport implements PropertySupport {

        @Override
        public Object getSessionState(Component component) {
            try {                
                XElement element = new XElement("DockFrontend");
                dockFrontend.writeXML(element);
                // convert XElement into DockState
                DockState dockState = DockState.createDockState(element);
                return dockState;
            } catch (Exception e) {
                Logger logger = Logger.getLogger(DesktopApp.class.getName());
                logger.log(Level.WARNING, "saving docking settings failed", e);
                return null;
            }
        }

        @Override
        public void setSessionState(Component component, Object obj) {
            try {
                DockState dockState = (DockState)obj;
                XElement element = DockState.createXElement(dockState);
                dockFrontend.readXML(element);
            } catch (Exception e) {
                Logger logger = Logger.getLogger(DesktopApp.class.getName());
                logger.log(Level.WARNING, "loading docking settings failed", e);
            }
        }
    }

I don’t know why XElement can’t be stored directly, maybe because of non-standard getter and setter methods there, see http://java.sun.com/products/jfc/tsc/articles/persistence4/
Probably it can be solved with persistence delegates also.

Thank you! and Happy New Year!

Alex

Hi Beni,

Playing with dockable menus and toolbars I moved from the 1.0.8 to 1.1.0_p5b version and found that above solution that serializes state of dockables doesn’t work anymore. It fails with the following exception:

bibliothek.util.xml.XException: not a boolean: 
        at bibliothek.util.xml.XContainer.getBoolean(Unknown Source)
        at bibliothek.gui.dock.station.split.SplitDockStationFactory.read(Unknown Source)
        at bibliothek.gui.dock.station.split.SplitDockStationFactory.read(Unknown Source)
        at bibliothek.gui.dock.layout.PredefinedDockSituation$PreloadFactory.read(Unknown Source)
        at bibliothek.gui.dock.layout.PredefinedDockSituation$PreloadFactory.read(Unknown Source)
        at bibliothek.gui.dock.layout.DockSituation.readEntry(Unknown Source)
        at bibliothek.gui.dock.layout.DockSituation.readCompositionXML(Unknown Source)
        at bibliothek.gui.dock.frontend.Setting.readXML(Unknown Source)
        at bibliothek.gui.DockFrontend.readXML(Unknown Source)
        at bibliothek.gui.DockFrontend.readXML(Unknown Source)
        at com.cass.ecass.app.MainFrame$DockingPropertySupport.setSessionState(MainFrame.java:137)
        at org.jdesktop.application.SessionStorage.restoreTree(SessionStorage.java:342)
        at org.jdesktop.application.SessionStorage.restoreTree(SessionStorage.java:357)
        at org.jdesktop.application.SessionStorage.restoreTree(SessionStorage.java:357)
        at org.jdesktop.application.SessionStorage.restoreTree(SessionStorage.java:357)
        at org.jdesktop.application.SessionStorage.restoreTree(SessionStorage.java:357)
        at org.jdesktop.application.SessionStorage.restore(SessionStorage.java:383)
        at com.cass.ecass.app.MainApp.startup(MainApp.java:46)
        at org.jdesktop.application.Application$1.run(Application.java:187)
        at java.awt.event.InvocationEvent.dispatch(InvocationEvent.java:209)
        at java.awt.EventQueue.dispatchEvent(EventQueue.java:597)
        at java.awt.EventDispatchThread.pumpOneEventForFilters(EventDispatchThread.java:269)
        at java.awt.EventDispatchThread.pumpEventsForFilter(EventDispatchThread.java:184)
        at java.awt.EventDispatchThread.pumpEventsForHierarchy(EventDispatchThread.java:174)
        at java.awt.EventDispatchThread.pumpEvents(EventDispatchThread.java:169)
        at java.awt.EventDispatchThread.pumpEvents(EventDispatchThread.java:161)
        at java.awt.EventDispatchThread.run(EventDispatchThread.java:122)

Thank you,

Alex

There is only one place where it can go wrong:

        boolean fullscreenAction = true;
        if( xfullscreenAction != null ){
        	fullscreenAction = xfullscreenAction.getBoolean();
        }```

This item is written at only one position, so in the current version it should not be stored wrong.

[Edit: your class DockState needs to store the value of the XElements as well. See "XElement.getValue". Most likely the missing content causes this error]

Beni, thank you very much!

Now it works again! Here are methods which have been modified
public static DockState createDockState(XElement element) {

DockState dockState = new DockState();
            // set name
            dockState.name = element.getName();
            dockState.value = element.getValue();
            // copy attributes
            for (XAttribute attr : element.attributes()) {
                DockStateAttribute dsAttr = new DockStateAttribute();
                dsAttr.setName(attr.getName());
                dsAttr.setValue(attr.getValue());
                dockState.attributes.add(dsAttr);
            }
            // copy children
            for (XElement child : element.children()) {
                DockState dsChild = createDockState(child);
                dockState.children.add(dsChild);
            }
            return dockState;
        }

        public static XElement createXElement(DockState dockState) {
            XElement element = new XElement(dockState.name);
            element.setValue(dockState.value);
            // copy attributes
            for (DockStateAttribute dsAttr : dockState.attributes) {
                XAttribute attr = new XAttribute(dsAttr.name);
                attr.setValue(dsAttr.value);
                element.addAttribute(attr);
            }
            // copy children
            for (DockState dsChild : dockState.children) {
                XElement child = createXElement(dsChild);
                element.addElement(child);
            }
            return element;
        }

All the best,

Alex

Hello Beni,

Recently I extended above example with two tool bars (just JToolBars with BorderLayouts), also I implemented ToolBarProperty and ToolBarState classes to serialize them using the BSAF serialization. The first toolbar that is located on the MainForm was serialized without any problems. But the second one that is located on the JPanel that is wrapped with Dockable and placed into SplitDockStation that is placed on MainForm wasn’t.

I have discovered that the second toolbar was saved to XML, but wasn’t loaded. It happened because its path was different from the saved one. For example here the saved path

northToolBar/Form/BackgroundPanel0/AnonymousBackgroundPanel0/BasicDockableDisplayer1/Content1/dockStation/null.contentPane/null.layeredPane/JRootPane0/Form

and here its path during the loading

northToolBar/Form/BackgroundPanel0/AnonymousBackgroundPanel0/BasicDockableDisplayer3/Content1/dockStation/null.contentPane/null.layeredPane/JRootPane0/Form

Because these paths are different the state of second tool bar wasn’t loaded.

Is there any way to make such paths unique for different application instances?

Thank you,

Alex

DF does not care in which order Components are added to their parents (the z-order is irrelevant for Components that do not overlap). BSAF on the other hand uses the z-order to build these paths.

Changing DF such that BSAF resolves these paths always the same way is too much work for too little benefit, but the other way could work:

These paths seem to be generated by the SessionStorage.getComponentPathname method, which is part of the BSAF. I suggest to take the entire Sourcecode of BSAF and rewrite this method such that it returns something that is more configurable, for example a unique identifier that you assign to your toolbar yourself.

Hello Beni,

Thank you!

Following you suggestions I overrode the SessionStorage.getComponentPathname method. In case if it will be useful for somebody here my steps

  • create subclass from SessionStorage
  • because getComponentPathname is a private method copy and paste following methods also
    — checkSaveRestoreArgs, getComponentName, saveTree, save, restoreTree, restore
  • in getComponentPathname comment concatenation of component’s ZOrder to its name
                
int n = c.getParent().getComponentZOrder(c);
if (n >= 0) {
    Class cls = c.getClass();
    name = cls.getSimpleName();
    if (name.length() == 0) {
           name = "Anonymous" + cls.getSuperclass().getSimpleName();
    }
    //name = name + n;
}

  • because ApplicationContext.setSessionStorage is a protected method invoke it using Reflection API

Class c = ApplicationContext.class;
Method m = c.getDeclaredMethod("setSessionStorage", new Class[]{SessionStorage.class});
m.setAccessible(true);
CustomSessionStorage s = new CustomSessionStorage(getContext());
m.invoke(getContext(), new Object[]{s});

All the best,

Alex