Saving MultipleCDockable layout Actionbutton

Hi,

i wanted to save a MultipleCDockable layout. With a litte modification from the tutorial example codes “MultipleCDockables” saving the MultipleCDockable layout to a xml file works quite well. But when adding buttons to the MultipleCDockables the button arent saved. What do i need to add to the save ActionButton to the xml file?

Regards

The framework does not save the JLabels or JTables you put on your Dockables, why should it save buttons? You’ll have to save the buttons yourself:

  • you could save them alongside the MultibleCDockableLayout that you implemented. Of course this assumes you can identify the buttons, e.g. with their Class name.
  • or if you always have the same buttons: subclass DefaultMultipleCDockable and always add them in the constructor to the Dockable.

Sorry i couldnt follow you. Was i did was creating the button alongside the dockable. This dockable is created when the clicking on a menuitem. I just want to achive that when the user load the design, the button on still is on the dynamic created Dockable. Can u write a simple example to what u mean.


    public TextActionDockable(TextFactory tfactory, TextLayout layout,
            String iconname, Boolean editable, Boolean closeable) {
        super(tfactory, layout, iconname, editable, closeable);  //is a class like TextDockable with icon, editable and closebutton
        control = cControl;
        factory = tfactory;
        //addSeparator();    
        action = createActionButton("doSthBtn",ImageComponents.makeImageIcon("ActionPng"));
        addAction(action);
        addSeparator();
    }
    
    private static CAction createActionButton( final String text,ImageIcon icon ){
    final CButton button =  new CButton( text, icon );
        button.addActionListener( new ActionListener(){
            @Override
            public void actionPerformed( ActionEvent e ){
                 // Do something
            }
       }
   }

regasrds,

Here an example. The example is just saving the text of the buttons, but you could for example use the text to find out what the button should do (or better: use some additional field, perhaps an “enum”, to save the functionality of the button). Or instead of just saving one button you could use an array or list for many buttons.

Does this help in any way?


import java.awt.BorderLayout;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.awt.event.WindowAdapter;
import java.awt.event.WindowEvent;
import java.io.DataInputStream;
import java.io.DataOutputStream;
import java.io.File;
import java.io.IOException;

import javax.swing.JButton;
import javax.swing.JFrame;
import javax.swing.JOptionPane;

import bibliothek.gui.dock.common.CControl;
import bibliothek.gui.dock.common.DefaultMultipleCDockable;
import bibliothek.gui.dock.common.MultipleCDockableFactory;
import bibliothek.gui.dock.common.MultipleCDockableLayout;
import bibliothek.gui.dock.common.action.CAction;
import bibliothek.gui.dock.common.action.CButton;
import bibliothek.util.xml.XElement;

public class ButtonExample {
	public static void main( String[] args ){
		final JFrame frame = new JFrame();
		frame.setDefaultCloseOperation( JFrame.DO_NOTHING_ON_CLOSE );
		final CControl control = new CControl( frame );
		frame.addWindowListener( new WindowAdapter(){
			@Override
			public void windowClosing( WindowEvent e ){
				try {
					control.writeXML( new File( "layout.xml" ) );
				}
				catch( IOException e1 ) {
					e1.printStackTrace();
				}
				System.exit( 0 );
			}
		});
		
		final TextFactory factory = new TextFactory();
		control.addMultipleDockableFactory( "factory", factory );
		frame.add( control.getContentArea() );
		
		try {
			control.readXML( new File( "layout.xml" ) );
		}
		catch( IOException e1 ) {
			e1.printStackTrace();
		}
		
		JButton add = new JButton( "Add" );
		add.addActionListener( new ActionListener(){
			@Override
			public void actionPerformed( ActionEvent e ){
				String text = JOptionPane.showInputDialog( frame, "Enter a button text:" );
				if( text != null ){
					TextLayout layout = new TextLayout( text );
					TextActionDockable dockable = new TextActionDockable( factory, layout );
					control.addDockable( dockable );
					dockable.setVisible( true );
				}
			}
		} );
		frame.add( add, BorderLayout.NORTH );
		frame.setBounds( 20, 20, 400, 400 );
		frame.setVisible( true );
	}
	
	public static class TextFactory implements MultipleCDockableFactory<TextActionDockable, TextLayout>{
		@Override
		public TextLayout write( TextActionDockable dockable ){
			return new TextLayout( dockable.getButtonText() );
		}

		@Override
		public TextActionDockable read( TextLayout layout ){
			return new TextActionDockable( this, layout );
		}

		@Override
		public boolean match( TextActionDockable dockable, TextLayout layout ){
			return false;
		}

		@Override
		public TextLayout create(){
			return new TextLayout( null );
		}
		
	}
	
	public static class TextLayout implements MultipleCDockableLayout{
		private String button;
		
		public TextLayout( String button ){
			this.button = button;
		}
		
		public String getButton(){
			return button;
		}
		
		@Override
		public void writeStream( DataOutputStream out ) throws IOException{
			// ignore
		}

		@Override
		public void readStream( DataInputStream in ) throws IOException{
			// ignore			
		}

		@Override
		public void writeXML( XElement element ){
			element.addElement( "button" ).setString( button );
		}

		@Override
		public void readXML( XElement element ){
			button = element.getElement( "button" ).getString();
		}
	}
	
	public static class TextActionDockable extends DefaultMultipleCDockable{
		private String buttonText;
		
	    public TextActionDockable(TextFactory factory, TextLayout layout) {
	        super( factory, "Title" );
	        buttonText = layout.getButton();
	        CAction action = createActionButton(buttonText);
	        addAction(action);
	        addSeparator();
	    }
	    
	    public String getButtonText(){
			return buttonText;
		}
	    
	    private static CAction createActionButton( final String text ){
	    	CButton button =  new CButton(text, null);
	    	button.setShowTextOnButtons( true );
	    	return button;
	    }
	}
}

Yes with the reference to the Buttontext it works. Thanks.
I got an other question. How to put an created dockable at the right to an existing Dockable?

regards,

I’m not certain how you mean that, but there are basically 3 ways to achieve what I think you want.

  1. The best one is using “CWorkingArea.show”, you can use this method to show a Dockable next to the last focused Dockable - but this only works for Dockables that belong to a working-area

  2. If you have a CDockable, call its “getBaseLocation” method to get a “CLocation”. “CLocation” offers a method “aside” which creates a location pointing next to the original location. You can use this new location for showing a new Dockable.

CLocation location = dockableOld.getBaseLocation();

DefaultSingleCDockable dockableNew = ...
dockableNew.setLocation( location.aside() );
dockableNew.setVisible( true );[/```

I should point out that this method of putting a Dockable next to an existing Dockable will be obsolete in the next stable release - but I will add some "deprecated" annotations to create a warning and tell how a new, better solution is used.

3. And then there is this piece of code I wrote some while ago. This truely sets a Dockable "right" to another Dockable:
```package docktest;

import java.awt.GridBagConstraints;
import java.awt.GridBagLayout;
import java.awt.GridLayout;
import java.awt.Insets;
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.CLocation;
import bibliothek.gui.dock.common.DefaultSingleCDockable;
import bibliothek.gui.dock.common.location.AbstractTreeLocation;
import bibliothek.gui.dock.common.location.CContentAreaCenterLocation;
import bibliothek.gui.dock.common.location.Side;

public class SplitDockableTest {
	public static void main( String[] args ){
		SplitDockableTest test = new SplitDockableTest();
		test.run();
	}
	
	private int currentId = 0;
	private CControl control;
	
	public void run(){
		JFrame frame = new JFrame();
		control = new CControl( frame );
		frame.add( control.getContentArea() );
		frame.setDefaultCloseOperation( JFrame.EXIT_ON_CLOSE );
		frame.setBounds( 20, 20, 400, 400 );
		showNewDockableAt( CLocation.base() );
		frame.setVisible( true );
	}
	
	public void showNewDockableAt( CLocation location ){
		SplitableDockable dockable = new SplitableDockable();
		dockable.setLocation( location );
		control.addDockable( dockable );
		dockable.setVisible( true );
	}
	
	private String nextId(){
		return "id = " + currentId++;
	}
	
	private class SplitableDockable extends DefaultSingleCDockable{
		public SplitableDockable(){
			super( nextId() );
			setTitleText( getUniqueId() );
			
			JPanel panel = new JPanel( new GridLayout( 4, 1 ) );
			panel.add( createButtonFor( Side.NORTH ));
			panel.add( createButtonFor( Side.EAST ));
			panel.add( createButtonFor( Side.WEST ));
			panel.add( createButtonFor( Side.SOUTH ));
			
			setLayout( new GridBagLayout() );
			add( panel, new GridBagConstraints( 0, 0, 1, 1, 1.0, 1.0, GridBagConstraints.CENTER, GridBagConstraints.NONE, new Insets( 0, 0, 0, 0 ), 0, 0 ));
		}
		
		private JButton createButtonFor( final Side side ){
			JButton button = new JButton( side.name() );
			button.addActionListener( new ActionListener(){
				@Override
				public void actionPerformed( ActionEvent e ){
					split( side );
				}
			});
			return button;
		}
		
		private void split( Side side ){
			CLocation location = locationFor( side );
			if( location != null ){
				showNewDockableAt( location );
			}
		}
		
		/* This is the important method. We are doing some black magic here... ;-)
		 * The location of a Dockable is encoded as path in a tree, each node represents
		 * a vertical or horizontal split. We travel that path upwards until we find an
		 * element that offers a method to be split, and create a new location by calling
		 * that method. 
		 *
		 * This may be a little hack, but the API responsible for locations will not change
		 * in the near future (perhaps it will never change again). So this solution
		 * should be pretty stable.
		 */
		private CLocation locationFor( Side side ){
			CLocation location = getBaseLocation();
			
			while( location != null ){
				if( location instanceof AbstractTreeLocation ){
					AbstractTreeLocation tree = (AbstractTreeLocation)location;
					switch( side ){
						case EAST:
							return tree.east( 0.5 );
						case WEST:
							return tree.west( 0.5 );
						case NORTH:
							return tree.north( 0.5 );
						case SOUTH:
							return tree.south( 0.5 );
					}
				}
				if( location instanceof CContentAreaCenterLocation ){
					CContentAreaCenterLocation content = (CContentAreaCenterLocation)location;
					switch( side ){
						case EAST:
							return content.east( 0.5 );
						case WEST:
							return content.west( 0.5 );
						case NORTH:
							return content.north( 0.5 );
						case SOUTH:
							return content.south( 0.5 );
					}
				}
				location = location.getParent();
			}
			
			// this Dockable might simply not be at a place where splitting is possible...
			return null;
		}
	}
}

Point 3. is exactly what iam looking for. :smiley:

Hi Benni,

so far i managed to save the MultiCDockle layout with it content like text,icon… My Problem now is, the save layout seems to be somewhat corrupt after loading the an layout(xml). Saving the change after loading doesnt seems to work.
What cause this in your opinion? I used the save and load function i found in this forum.

    
private static void saveLayout() throws FileNotFoundException, UnsupportedEncodingException, IOException {
        CPerspective perspective;
        String user =CSystem.getUsername();
        perspective = control.getPerspectives().getPerspective(true);
        File file = new File(user+"_layout.xml");
        DataOutputStream out = new DataOutputStream(new BufferedOutputStream(new FileOutputStream(file)));
        XElement element = new XElement("layout");
        control.getPerspectives().writeXML(element, perspective);
        OutputStreamWriter writer = new OutputStreamWriter(out, "UTF-8");
        XIO.write(element, writer);
        writer.close();
        out.flush();
        out.close();
    }
    
    private static void loadLayout() throws UnsupportedEncodingException, IOException {
        String user =CSystem.getUsername();
        File file = new File(user+"_layout.xml");
        DataInputStream in = new DataInputStream(new BufferedInputStream(new FileInputStream(file)));
        //(user+"_settings.txt");
        InputStreamReader reader = new InputStreamReader(in, "UTF-8");
        XElement element = XIO.read(reader);
        CPerspective perspective = control.getPerspectives().readXML(element);
        reader.close();
        in.close();
        control.getPerspectives().setPerspective(perspective,true);
    }

Just use

control.writeXML( new File( "layout.xml" ));

and
control.readXML( new File("layout.xml"));
to handle persistent storage.

Right now you are first converting the layout into perspectives, then you store the perspectives. There is an unnecessary step involved in this, and this unnecessary step even has some issues with keeping all the meta information together.

With crontrol.writeXML it works better. But when i do the following point some corruption in the layout are still there.

  1. Load saved layout
  2. adding, moving around some MultiCDockable, closing some…
  3. saving layout

Following missmatch appear:

  • After loading some MultiCdockable are missing.
  • Split function positionate on wrong position.
  • an empty MultiCDockable was load (should have textpane,icon…,textcontent)

modified split function from your example:


private static void setSideLocation( Side side,CControl control, MultipleCDockable newdockable ){
             CLocation location = locationFor( side, control );
             if( location != null ){
                 showNewDockableAt( location,control, newdockable );
             }
         }
         
         public static void showNewDockableAt( CLocation loc,CControl control,MultipleCDockable dockable ){
                //SplitableDockable dockable = new SplitableDockable();
                dockable.setLocation( loc );
                control.addDockable( dockable );
                dockable.setVisible( true );
            }
        

         private static CLocation locationFor( Side side, CControl control){
             CLocation location = control.getFocusedCDockable().getBaseLocation();
               ...
               ...
             }
            
             // this Dockable might simply not be at a place where splitting is possible...
             return null;
         }

Sounds to me like something is not configured the right way, perhaps a factory not creating a Dockable when it should… Would it be possible for you to give me your application, so I can have a better look at it? You could send it to me as a zip file sent at benjamin_sigg@gmx.ch .

Sorry I wasnt online the last few days. I will send you the application this evening. Thanks

Regards,