[Erledigt] Persistence

Hi,

I have created a simple test app using DF. I am trying to add persistence capabilities using Frontend’s writeXML() and readXML() method.

I have also created my own Factory which I register with Frontend when the app starts.

I am able to save the layout into an xml file. However when I read the same layout, the views do not show up properly. The xml is read properly and I even get a valid XElement.

When I tried debugging the app, I realized that my factories layout(…) method is not called at all. I think this is the reason why the views do not get created.

I am not sure if I have provided you with enough information to help me, but even if you can give me some pointers to debug, it will be very helpful.

I am posting my xml file at the bottom of the mail.

Thanks for your time.


Regards
Parag

<?xml version='1.0'?> Grid FormulaEditor BindingConfigPanel

As far as I see: the xml file is correct, the issue really is loading.

The xml-file tells also, that you do not add your Dockables to the DockFrontend before loading (you don’t use DockFrontend.add( Dockable, String )). That’s perfectly ok, unless you add them after the xml-file was loaded…

So my guess would be, that you missed a method:
There are two “layout” and two “setLayout”-methods.

  • the “layout” methods are used to create new Dockables
  • the “setLayout” methods are used to change the layout of existing Dockables
    (there is always a version with, and without children).

The DockFrontend uses all four methods, which one depends on differend states.
So you need to implement all four methods. The result might look like this:

        [...]
         
        public MyElement layout( MyLayout layout ) {
            return layout( layout, null );
        }
        
        public MyElement layout( MyLayout layout, Map<Integer, Dockable> children ) {
            MyElement element = new MyElement();
            setLayout( element, layout, children );
            return element;
        }
        
        public void setLayout( MyElement element, MyLayout layout ) {
            setLayout( element, layout, null );
        }
        
        public void setLayout( MyElement element, MyLayout layout, Map<Integer, Dockable> children ) {
            element.setXYZ( layout.getXYZ() );
        }
    }```

If I'm wrong, then I need more information... your DockFactory and DockLayout would be interesting ;)

Hi,

Thanks for your reply. I solved the problem. It was a minor oversight in the code.

When reading the XML I gave the XElement to Frontend

_frontend.readXML(element);

which was incorrect. Everything worked when I changed the above line to

_frontend.readXML(element.getElement(“frontend”));

Regardingadding the views to the frontend, if I add the views then the resulting XML that gets generated when I persist the layout uses ‘predefined’ factory instead of my factory for my views. I am embedding the code of my app and the xml for your reference in the following post.


Thanks
Parag

Thanks for the code you sent me. I’ll ask the administrator whether he can remove the limit…

I have two comments.

Regardingadding the views to the frontend, if I add the views then the resulting XML that gets generated when I persist the layout uses ‚predefined‘ factory instead of my factory for my views.

Yes. The first version of DF had a much less sophisticated mechanism for persistent storage. The core of that mechanism worked well, so I never changed it. All new functionality was later added wrapping up the old core.

If you don’t add your Dockable, then the output is something like this:
[XML]
blopp

[/XML]

When you add the Dockable, then the output changes to:
[XML]


blopp


[/XML]
This short snippet tells: that some Dockable was added to the Frontend with the name „MyElement“, and that the factory will delegate its work (to restore the layout of „MyElement“) to „my_factory“. So your factory is not thrown away, it just gets wrapped up in another factory.

In your code you store the DockLayout „LbViewLayout“ directly in the „LbView“, and you use always the same LbViewLayout-object.
A DockLayout is meant to be a snapshot of the layout (=content) of a Dockable. The idea is, that the DockFactory creates a new, independent DockLayout every time when „getLayout“ is called (hm, seems I have to enhance the documentation at that point).

Right now your solution works, but it will fail as soon as:
[ul][li]You use two DockFactories with different names (=ids).[/li][li]You put more than static data in the layout, and the layout gets stored in memory. That happens as soon as you use the „save“ and „load“ methods of DockFrontend.[/li][/ul]

So please remove „LbView#LbViewLayout _layout;“ and use „new LbViewLayout“ in the „getLayout“-method :wink:

Hi,

Thanks for your reply.

I will make the changes you have suggested.

I was also going through the NotesViewFactory class. It has a method called setLayout() which seems to be setting the model (Note) of NoteView in NoteView.

 public void setLayout( NoteView element, NoteViewLayout layout ) {
    element.setNote( model.getNote( layout.getId() ) );
}

I am not sure I understand this properly.

Also the interface DockViewLayout has methods to get and set the factoryID. This does not seem to represent a snapshot of the dockable’s layout. Can you please explain the purpose of the DockLayout.


Thanks
Parag

Ok, I try :wink:

DockLayout
In the very beginning, DockFactory had one method „read“ and one method „write“. These methods could read or write a Dockable from or to a DataInput/OutputStream.

Suddenly the DockFrontend came, and the properties were no longer written in a file, but stored directly in memory using byte-arrays. So all properties could be exchanged very quickly.

Then XML came, and it was no longer a good idea to store information in byte-arrays (because byte-arrays are not xml conform…).

So there was the need for an intermediate format between xml/byte-streams and the „real world“ (the things that are visible on the screen). DockLayout was that intermediate format.

The intent of DockLayout was to store informations about a Dockable, the same way as information was stored directly in a file in earlier versions. But DockLayout should make things easier, because the algorithm to write/read into/from a Output/InputStream was no longer called every time some properties were stored or loaded.

get/setFactoryId
A layout and a factory meet more than once: at some point the factory creates the layout (using getLayout), and at another point the factory writes the layout (write/writeXML). And the other way around read/readXML/setLayout is also possible.

So there needs to be a connection between layout and factory in order to do the meeting more than once.

The factory-id was a way to make that connection. The algorithm that used the factory called „setFactoryId“ so it could later ask the layout for its origin. Since an algorithm could use its own names for the factories, the id could not be hardwired in the layout.

Your question raises a valid concern. This is neither intuitive nor a good design. So I’ve decided to change that part.
This is how it will look in the next version: factories can create any object, and that object gets wrapped into a DockLayout - which will no longer be an interface but a class. Clients will not longer have any contact with the class DockLayout.
The idea behind the mechanism remains unchanged: the object created by the DockLayout is a snapshot of the contents of some Dockable.
(Transition from the current to the next version will be quit simple, every occurrence of „implements DockLayout“ has to be deleted, but that’s all the client has to do.)

NoteView/Layout
If the DockLayout is a snapshot of the contents of a Dockable, then storing the text of a note directly in a DockLayout would not work. Every time the user loads an old set of properties, the new notes would be deleted and the old ones restored.

So I made a distinction between the notes (model) and the Dockables which show the notes (view). If the user now loads an old set of properties, the Dockable can ask the model for the newest version of its note (assuming the note was not deleted, in which case the Dockable will not be made visible anyway). The view changes, the model remains.

Hm, I’m not sure if I explained anything at all. Ask again if this post did not help…

Thanks for the explanation. I think my confusion came from my incorrect understanding of what a Layout represented.

I thought a Layout represented how the Dockable was laid-out in the main window, in relation to other Dockables.

Now if I understand correctly, a Layout represents the contents of the Dockable (what a Dockable will contain as opposed to where it will be placed). I had probably mixed up the concepts of Situation and Layout.


Regards
Parag Shah

[quote=Beni]
When you add the Dockable, then the output changes to:
[LEFT] XML [LEFT] [FONT=‚Courier New‘,Courier,monospace][COLOR=black]<child[COLOR=black]>[/COLOR][/COLOR]
[COLOR=black]<layout factory=„predefined“>[/COLOR]
[COLOR=black]<replacement id=„dockableMyElement“/>[/COLOR]
[COLOR=black]<delegate id=„delegate_my_factory“>[/COLOR]blopp[COLOR=black]</delegate[COLOR=black]>
[/COLOR][/COLOR]
[COLOR=black]</layout[COLOR=black]>[/COLOR][/COLOR]
[COLOR=black]<children ignore=„false“/>[/COLOR]
[COLOR=black]</child[COLOR=black]>
[/COLOR][/COLOR]

[/font]
[/LEFT]
[/LEFT]
This short snippet tells: that some Dockable was added to the Frontend with the name „MyElement“, and that the factory will delegate its work (to restore the layout of „MyElement“) to „my_factory“. So your factory is not thrown away, it just gets wrapped up in another factory.

;)[/quote]

I tried adding dockables to the frontend and the resulting xml is embedded below.

<properties>
    <frontend>
        <current>
            <roots>
                <root name="rootDockingStation">
                    <layout factory="predefined">
                        <replacement id="rootrootDockingStation"/>
                        <delegate id="delegate_SplitDockStationFactory">
                            <node orientation="HORIZONTAL" divider="0.30000001192092896">
                                <leaf id="1"/>
                                <leaf id="0"/>
                            </node>
                        </delegate>
                    </layout>
                    <children ignore="false">
                        <child>
                            <layout factory="predefined">
                                <replacement id="dockableGrid Table"/>
                                <delegate id="delegate_lbviews">
                                    <lbviews>Grid Table</lbviews>
                                </delegate>
                            </layout>
                            <children ignore="false"/>
                        </child>
                        <child>
                            <layout factory="predefined">
                                <replacement id="dockableBindable Measures"/>
                                <delegate id="delegate_lbviews">
                                    <lbviews>Bindable Measures</lbviews>
                                </delegate>
                            </layout>
                            <children ignore="false"/>
                        </child>
                    </children>
                </root>
            </roots>
            <children/>
        </current>
    </frontend>
</properties>


Even though the XML defines a delegate that mentions my factory, it is still not being used to get the dockable components. (My factory’s layout method does not get called. I believe that the layout method of PreloadFactory is being called) I also think that the rootDockingStation is the only component being returned from PreloadFactory. It actually returns null the remaining 3 times, which I am assuming is for the dockable components.

Can you please help me by answering some questions:

  1. Exactly which part of DockingFrames is responsible for invoking the layout method of a Factory.
  2. Does the dockable component get created somewhere in frontend.readXML(…)?
  3. Which factory encloses my factory as a delegate?


Regards
Parag

(My factory’s layout method does not get called. I believe that the layout method of PreloadFactory is being called)

That is correct. And PreloadFactory calls „setLayout“ on your factory.

  1. Exactly which part of DockingFrames is responsible for invoking the layout method of a Factory.
  2. Does the dockable component get created somewhere in frontend.readXML(…)?
  3. Which factory encloses my factory as a delegate?

The invisible „PreloadFactory“, a private class of „PredefinedDockSituation“.

The two „layout“-methods are called by „DockSituation“, to be exact: by the method „DockElement convert( DockLayoutComposition )“. DockSituation does not know about delegates, DockFrontends and such stuff, it always asks for a new Dockable when it encounters an element that represents a Dockable.
The two „setLayout“-methods are called by the PreloadFactory. PreloadFactory forwards every invocation of its „layout“-method to the „setLayout“-method of its delegate.

If and only if one of the two „layout“-methods of your factory is called. (There are no magic tricks with reflection built in). That happens if and only if your factory is directly accessed (= not wrapped into the PreloadFactory)

By the way, you can get into serious trouble if you use an old xml-file. The framework gets confused if the xml file tells that some Dockable is added/not added while in your new programm, the Dockable is not added/added to DockFrontend.


In order to give you this explanation, I’ve written a small programm. You might be interested in how the output differs for Dockables which were added to the DockFrontend - and for those which were not.

My programm has only two Dockables „a“ and „b“, „a“ is added to a DockFrontend, „b“ is not. Both Dockables contain a textfield so we have something interesting to store… There is a single factory used for both Dockables. I’ve put a „System.out.println…“ in each method of the factory so the factory tells us what is going on.

On write:

getLayout: A
getLayout: B
write: I am the text from A
write: I am the text from B

On read:

read: I am the text from A
read: I am the text from B
setLayout: A I am the text from A
layout: I am the text from B
import java.awt.event.WindowEvent;
import java.io.*;
import java.util.Map;

import javax.swing.JFrame;
import javax.swing.JScrollPane;
import javax.swing.JTextArea;

import bibliothek.gui.DockFrontend;
import bibliothek.gui.Dockable;
import bibliothek.gui.dock.DefaultDockable;
import bibliothek.gui.dock.DockFactory;
import bibliothek.gui.dock.SplitDockStation;
import bibliothek.gui.dock.station.split.SplitDockGrid;
import bibliothek.util.xml.XElement;
import bibliothek.util.xml.XIO;

public class Dock2 {
    public static void main( String[] args ){
        final JFrame frame = new JFrame( "Demo" );
        final DockFrontend frontend = new DockFrontend( frame );
        
        SplitDockStation station = new SplitDockStation();
        frame.setDefaultCloseOperation( JFrame.DO_NOTHING_ON_CLOSE );
        frame.setBounds( 20, 20, 400, 400 );
        frame.add( station.getComponent() );
        
        frontend.addRoot( station, "root" );

        SplitDockGrid grid = new SplitDockGrid();
        TextDockable a = new TextDockable( "A" );
        TextDockable b = new TextDockable( "B" );
        
        frontend.add( a, "a" );
        // frontend.add( b, "b" );   DO NOT ADD b!
        
        frontend.registerFactory( new TextFactory() );
        
        grid.addDockable( 0, 0, 1, 1, a );
        grid.addDockable( 0, 1, 1, 1, b );
        station.dropTree( grid.toTree() );
        
        
        // do the IO operations...
        try{
            XElement element = XIO.readUTF( new FileInputStream( "test.xml" ));
            frontend.readXML( element );
        }
        catch( IOException ex ){
            // Ooops
            ex.printStackTrace();
        }
        
        frame.addWindowListener( new WindowAdapter(){
            @Override
            public void windowClosing( WindowEvent e ) {
                frame.setVisible( false );
                
                XElement element = new XElement( "root" );
                frontend.writeXML( element );
                try {
                    XIO.writeUTF( element, new FileOutputStream( "test.xml" ) );
                }
                catch( FileNotFoundException e1 ) {
                    e1.printStackTrace();
                }
                catch( IOException e1 ) {
                    e1.printStackTrace();
                }
                
                System.exit( 0 );
            }
        });
        
        frame.setVisible( true );
    }

    private static class TextDockable extends DefaultDockable{
        private JTextArea area = new JTextArea();
        public TextDockable( String title ){
            super( title );
            getContentPane().add( new JScrollPane( area ));
            setFactoryID( "text" );
        }
        public String getText(){
            return area.getText();
        }
        public void setText( String text ){
            area.setText( text );
        }
    }
    
    private static class TextFactory implements DockFactory<TextDockable, String>{
        public String getID() {
            return "text";
        }

        public String getLayout( TextDockable element, Map<Dockable, Integer> children ) {
            System.out.println( "getLayout: " + element.getTitleText() );
            return element.getText();
        }

        public TextDockable layout( String layout, Map<Integer, Dockable> children ) {
            System.out.println( "layout: " + layout );
            TextDockable dock = new TextDockable( "?" );
            dock.setText( layout );
            return dock;
        }

        public TextDockable layout( String layout ) {
            System.out.println( "layout: " + layout );
            TextDockable dock = new TextDockable( "?" );
            dock.setText( layout );
            return dock;
        }

        public String read( DataInputStream in ) throws IOException {
            // never happens
            System.out.println( "I should not be here" );
            return in.readUTF();
        }

        public String read( XElement element ) {
            System.out.println( "read: " + element.getString() );
            return element.getString();
        }

        public void setLayout( TextDockable element, String layout, Map<Integer, Dockable> children ) {
            System.out.println( "setLayout: " + element.getTitleText() + " " + layout );
            element.setText( layout );
        }

        public void setLayout( TextDockable element, String layout ) {
            System.out.println( "setLayout: " + element.getTitleText() + " " + layout );
            element.setText( layout );
        }

        public void write( String layout, DataOutputStream out ) throws IOException {
            // never happens
            System.out.println( "I should not be here" );
            out.writeUTF( layout );
        }

        public void write( String layout, XElement element ) {
            System.out.println( "write: " + layout );
            element.setString( layout );
        }
    }
}```

[XML]<root>
	<current>
		<roots>
			<root name="root">
				<layout factory="predefined">
					<replacement id="rootroot"/>
					<delegate id="delegate_SplitDockStationFactory">
						<node orientation="VERTICAL" divider="0.5">
							<leaf id="0"/>
							<leaf id="1"/>
						</node>
					</delegate>
				</layout>
				<children ignore="false">
					<child>
						<layout factory="predefined">
							<replacement id="dockablea"/>
							<delegate id="delegate_text">I am the text from A</delegate>
						</layout>
						<children ignore="false"/>
					</child>
					<child>
						<layout factory="delegate_text">I am the text from B</layout>
						<children ignore="false"/>
					</child>
				</children>
			</root>
		</roots>
		<children/>
	</current>
</root>[/XML]

Hi,

Thanks for the example.

I ran the example. It displayed 2 text areas. Then I shutdown the app, so that test.xml is saved.

Then I ran the application again. This time the application read test.xml, but the TextArea for B had a ‘?’ instead of a ‘B’.

I am running version 1.03 of DockingFrames.

Can you reproduce the error at your end, or am I doing something incorrect?


Regards
parag

That was deliberately :wink:

On the first run, both „A“ and „B“ are created in lines 31/32. Since there is no xml-file yet, nothing happens after they were put on the JFrame - they just stay there.

On the second run, „A“ und „B“ are created in lines 31/32 as well. But then the xml-file is loaded.
The framework removes all children from „station“ in order to apply the new layout (which is in the xml). Since „A“ was added to „frontend“, it goes into a cache. „B“ however was not added to „frontend“, and so nobody knows of the existence of „B“. It just becomes fodder for the garbage collector.
Then a „PredefinedDockSituation“ is ordered to create the new layout, it has access to the cache of „frontend“. When it encounters the element that represents „A“, it reads „A“ from the cache. When it encounters the element that represents „B“… then it can’t read „B“ from the cache*. It calls the „layout“-method of „TextFactory“ (our DockFactory). That method however produces a new Dockable, and just assigns the title „?“ (line 102 or 109, if think it is 102 but that is not important).

(* actually the xml-file tells whether a Dockable is expected in the cache or not. That is possible because the content of the cache was already known then the file was written. If the expectation is not met unpleasant things happen. Like Dockables missing or replaced without need)

To fix that bug:

  • one could store the title as well
  • or „B“ could be added to „frontend“ like „A“
    Both solutions would invalidate the current xml-file.

It actually returns null the remaining 3 times, which I am assuming is for the dockable components.

I though a long time about that one, but either I misunderstood your problem, or maybe the bug is on your side. Can you tell me more exactly what you did?

I am attaching some sample code. Instructions for running the code are given after the attachment.

import java.awt.Dimension;
import java.awt.Menu;
import java.awt.MenuBar;
import java.awt.MenuItem;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.io.BufferedInputStream;
import java.io.FileInputStream;
import java.io.FileWriter;
import java.io.IOException;
import java.io.InputStreamReader;
import java.util.List;

import javax.swing.JFrame;
import javax.swing.SwingUtilities;

import bibliothek.extension.gui.dock.theme.FlatTheme;
import bibliothek.gui.DockFrontend;
import bibliothek.gui.DockTheme;
import bibliothek.gui.Dockable;
import bibliothek.gui.dock.SplitDockStation;
import bibliothek.gui.dock.event.DockFrontendAdapter;
import bibliothek.gui.dock.event.DockFrontendListener;
import bibliothek.gui.dock.station.split.SplitDockPathProperty;
import bibliothek.gui.dock.station.split.SplitDockProperty;
import bibliothek.gui.dock.themes.NoStackTheme;
import bibliothek.gui.dock.title.DockTitle;
import bibliothek.util.xml.XElement;
import bibliothek.util.xml.XIO;


public class TestDockingFrames
{    
    private DockFrontend _frontend;
    JFrame frame;
    private SplitDockStation _defaultStation;
    private LbViewFactory _factory;
    private String _layoutFile;
    
    public static void main(String args[])
    {
        TestDockingFrames tdf = new TestDockingFrames();
    }
    
    TestDockingFrames()
    {        
        String homeDir = System.getProperty("user.home");
        _layoutFile = homeDir+"/"+"df_layout.xml";
    
        frame = new JFrame();
        
        _frontend = new DockFrontend();
        
        DockTheme theme = new FlatTheme();
        //set the Theme
        _frontend.getController().setTheme(new NoStackTheme(theme));
        
        _defaultStation = new SplitDockStation();
        
        _frontend.addRoot(_defaultStation, "DefaultStation");
        _factory = new LbViewFactory();
        _frontend.registerFactory(_factory);
        
        XElement xElement = loadPersistedLayout();
        if(xElement == null)
        {
            System.out.println("Persisted Layout was not loaded... creating default layout");
            createDefaultLayout();
        }
        display();
    }

    private XElement loadPersistedLayout()
    {
        XElement element = null;
        InputStreamReader in = null;
        try
        {
            in = 
                new InputStreamReader(
                        new BufferedInputStream(new FileInputStream(_layoutFile)), 
                                                        "UTF-8");
            element = XIO.read(in);
            in.close();
            System.out.println(element);
            _frontend.readXML(element.getElement("frontend"));
        }
        catch(IOException ioe)
        {
            System.out.println("Could not read layout " + ioe.getMessage());
        }
        return element;
    }

    private void createDefaultLayout()
    {    
        LbViewFactory.LbView gridView = 
            _factory.layout(LbViewFactory.DockableEnum.GRID);
        _frontend. add(gridView, LbViewFactory.DockableEnum.GRID.getTitle());
        _defaultStation.drop(gridView);
        
        LbViewFactory.LbView formulaEditorView = 
            _factory.layout(LbViewFactory.DockableEnum.FORMULA_EDITOR);
        SplitDockPathProperty feProp = new SplitDockPathProperty();
        feProp.add(SplitDockPathProperty.Location.BOTTOM, 0.3);
        _frontend.add(formulaEditorView, 
                          LbViewFactory.DockableEnum.FORMULA_EDITOR.getTitle());
        _defaultStation.drop(formulaEditorView, feProp);
        
        LbViewFactory.LbView bcpView = 
            _factory.layout(LbViewFactory.DockableEnum.BINDING_CONFIG_PANEL);
        _frontend.add(bcpView, 
                          LbViewFactory.DockableEnum.BINDING_CONFIG_PANEL.getTitle());
        SplitDockPathProperty bcpProp = new SplitDockPathProperty();
        bcpProp.add(SplitDockPathProperty.Location.LEFT, 0.3);
        _defaultStation.drop(bcpView,    bcpProp);
    }

    private void display()
    {
        SwingUtilities.invokeLater(new Runnable()
        {
            @Override
            public void run()
            {
                frame.setSize(new Dimension(800, 600));
                frame.setMenuBar(getMenuBar());
                frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
                frame.getContentPane().add(_defaultStation.getComponent());
                frame.setVisible(true);
            }
        });
    }

    private MenuBar getMenuBar()
    {
        MenuBar menuBar = new MenuBar();  
        menuBar.add(createTitlebarMenu());
        menuBar.add(createViewsMenu());
        menuBar.add(createPersistenceMenu());
        return menuBar;
    }

    private Menu createPersistenceMenu()
    {
        Menu persistenceMenu = new Menu("Persistence");
        MenuItem save = new MenuItem("save");
        save.addActionListener(new ActionListener()
        {
            @Override
            public void actionPerformed(ActionEvent e)
            {
                XElement element = new XElement("properties");
                _frontend.writeXML(element.addElement("frontend"));
                
                FileWriter writer = null;
                try
                {
                    writer = new FileWriter(_layoutFile);
                    XIO.write(element, writer);
                    //writer.append(element.toString());
                } catch (IOException e1)
                {
                    // TODO Auto-generated catch block
                    e1.printStackTrace();
                }
                finally
                {
                    try
                    {
                        writer.close();
                    }
                    catch(IOException ioe){}
                }
            }
        });
        persistenceMenu.add(save);
        return persistenceMenu;
    }

    private Menu createViewsMenu()
    {
        //add hide/show views menu
        Menu viewsMenu = new Menu("Hide/Show Views");
        
        viewsMenu.add(createViewsMenuItem(LbViewFactory.DockableEnum.GRID));
        viewsMenu.
            add(createViewsMenuItem(LbViewFactory.DockableEnum.FORMULA_EDITOR));
        viewsMenu.
            add(createViewsMenuItem(LbViewFactory.DockableEnum.BINDING_CONFIG_PANEL));

        return viewsMenu;
    }

    private MenuItem createViewsMenuItem(final LbViewFactory.DockableEnum dockableEnum)
    {
        MenuItem menuItem = new MenuItem(dockableEnum.getTitle());
        VisibilityManager vm = new VisibilityManager(dockableEnum);
        menuItem.addActionListener(vm);
        _frontend.addFrontendListener(vm);
        return menuItem;
    }

    private Menu createTitlebarMenu()
    {
        Menu titlebarMenu = new Menu("Titlebar");
        MenuItem hideTitlebar = new MenuItem("toggle titlebar");
        hideTitlebar.addActionListener(new ActionListener()
        {
            private boolean _titlebarsShowing = true;
            
            @Override
            public void actionPerformed(ActionEvent e)
            {
                _titlebarsShowing = !_titlebarsShowing;
                List<Dockable> dockables = _frontend.listDockables();
                for(Dockable dockable : dockables)
                {
                    DockTitle dockTitles[] = dockable.listBoundTitles();
                    for(DockTitle dockTitle : dockTitles)
                    {
                        dockTitle.getComponent().setVisible(_titlebarsShowing);
                    }
                }
            }    
        });
        titlebarMenu.add(hideTitlebar);
        return titlebarMenu;
    }
    
    class VisibilityManager extends DockFrontendAdapter 
                                                                        implements ActionListener
    {
        private boolean _showing;
        LbViewFactory.DockableEnum _dockableEnum;
        
        VisibilityManager(LbViewFactory.DockableEnum dockableEnum)
        {
            _dockableEnum = dockableEnum;
            _showing = true;
        }
        
        @Override
        public void actionPerformed(ActionEvent e)
        {
            LbViewFactory.LbView dockable = _factory.layout(_dockableEnum);
            if(_showing)
            {
                _frontend.hide(dockable);
            }
            else
            {
                _frontend.show(dockable);
            }
        }
        
        @Override
        public void shown(DockFrontend frontend, Dockable dockable)
        {
            if(dockable.equals(_factory.layout(_dockableEnum)))
            {
                if(!_showing)
                {
                    _showing = true;
                }
            }
        }
        
        @Override
        public void hidden(DockFrontend frontend, Dockable dockable)
        {
            if(dockable.equals(_factory.layout(_dockableEnum)))
            {
                if(_showing)
                {
                    _showing = false;
                }
            }
        }
    }
}

Run the class. You wil see 3 dockables. Click on the ‚Persistence‘ menu and click on ‚Save‘. This will save the xml in your home directory in a file called ‚df-layout.xml‘

Run the application again. This time it will first try to locate the xml file and create the dockables based on it. You will notice that the JFrame does not contain any dockables. I think this is because the layout(…) method of my factory is not called by the enclosing factory.

Change the source so that the line

createDefaultLayout();

appears before

XElement xElement = loadPersistedLayout();

This ensures that all the needed dockables are in the Frontend’s cache. Now the application works file.

I am guessing, but I could be wrong. I think that when a PredefinedDockFactory wraps my factory, it is able to show a dockable only if the dockable exists in the Frontend’s cache. If the dockable does not exist in the cache, then it does not call the layout() method of my factory to get the dockable.

Is my understanding correct? If so, is there a way to get around this?


Thanks & Regards
Parag

I think that when a PredefinedDockFactory wraps my factory, it is able to show a dockable only if the dockable exists in the Frontend’s cache. If the dockable does not exist in the cache, then it does not call the layout() method of my factory to get the dockable.

Is my understanding correct? If so, is there a way to get around this?

That’s correct. You use the frontend in a way that I did not anticipate.

Well, you could write your own caching mechanism and completely forget DockFrontend…
Or you could use the snapshot of the next version - which miraculously has a new feature that allows to add a „backup factory“ to DockFrontend.
The new library can be found here.

This „backup factory“ is used whenever an element is read that is missing in the cache. The frontend even inserts the missing element into its cache - with the name the element had when it was stored (there can be no conflicts in the cache, if the element would have the same name as an already existing element, then it would be overridden by the existing element).

The only thing you have to do, is to replace every occurrence of…
frontend.registerFactory( someFactory );
… by …

frontend.registerBackupFactory( someFactory );```
.. or the short version ...
```frontend.registerFactory( someFactory, true );```

----
I've rewritten the small example we had earlier in order to support the new backup factory. The important change is around lines 50-60 (don't worry about the two factories, I just use two of them to simulate a more complex application).

On the first startup, we do not have a xml-file:

shown:
added: A name=a
added: B name=b
shown: A
shown: B



It is written on shutdown:

getLayout: A > Text from A
getLayout: B > Text from B
write: A > Text from A
write: B > Text from B



Using the old version: If we start the application now, without putting "A" and "B" in the cache, then we would get a blank frame:

shown:
read: A > Text from A
read: B > Text from B



In the new version however, the backup factory jumps in, creates and registers two new Dockables:

shown:
read: A > Text from A
read: B > Text from B
layout: A > Text from A
added: A name=a
layout: B > Text from B
added: B name=b
shown: A
shown: B



And that is the code:
```import java.awt.event.WindowAdapter;
import java.awt.event.WindowEvent;
import java.io.*;
import java.util.Map;

import javax.swing.JFrame;
import javax.swing.JScrollPane;
import javax.swing.JTextArea;

import bibliothek.gui.DockFrontend;
import bibliothek.gui.Dockable;
import bibliothek.gui.dock.DefaultDockable;
import bibliothek.gui.dock.DockFactory;
import bibliothek.gui.dock.SplitDockStation;
import bibliothek.gui.dock.event.DockFrontendAdapter;
import bibliothek.gui.dock.station.split.SplitDockGrid;
import bibliothek.util.xml.XElement;
import bibliothek.util.xml.XIO;

public class Dock2 {
    public static void main( String[] args ){
        final JFrame frame = new JFrame( "Demo" );
        final DockFrontend frontend = new DockFrontend( frame );
        
        frontend.addFrontendListener( new DockFrontendAdapter(){
            @Override
            public void added( DockFrontend frontend, Dockable dockable ) {
                System.out.println( "added: " + dockable.getTitleText() + " name=" + frontend.getNameOf( dockable ) );
            }
            @Override
            public void removed( DockFrontend frontend, Dockable dockable ) {
                System.out.println( "removed: " + dockable.getTitleText() );
            }
            @Override
            public void shown( DockFrontend frontend, Dockable dockable ) {
                System.out.println( "shown: " + dockable.getTitleText() );
            }
            @Override
            public void hidden( DockFrontend fronend, Dockable dockable ) {
                System.out.println( "hidden: " + dockable.getTitleText() );
            }
        });
        
        SplitDockStation station = new SplitDockStation();
        frame.setDefaultCloseOperation( JFrame.DO_NOTHING_ON_CLOSE );
        frame.setBounds( 20, 20, 400, 400 );
        frame.add( station.getComponent() );
        
        frontend.addRoot( station, "root" );

        TextFactory factoryA = new TextFactory( "A" );
        TextFactory factoryB = new TextFactory( "B" );
        
        frontend.registerFactory( factoryA );
        frontend.registerFactory( factoryB );
        
        /* use new feature */
        //*
        frontend.registerBackupFactory( factoryA );
        frontend.registerBackupFactory( factoryB );
        // */
        /* end use new feature */
        
        
        if( !putXML( frontend ) )
            putDefault( frontend, station );
        
        frame.addWindowListener( new WindowAdapter(){
            @Override
            public void windowClosing( WindowEvent e ) {
                frame.setVisible( false );
                
                XElement element = new XElement( "root" );
                frontend.writeXML( element );
                try {
                    XIO.writeUTF( element, new FileOutputStream( "test.xml" ) );
                }
                catch( FileNotFoundException e1 ) {
                    e1.printStackTrace();
                }
                catch( IOException e1 ) {
                    e1.printStackTrace();
                }
                
                System.exit( 0 );
            }
        });
        
        frame.setVisible( true );
    }
    
    private static boolean putXML( DockFrontend frontend ){
        try{
            XElement element = XIO.readUTF( new FileInputStream( "test.xml" ));
            frontend.readXML( element );
            return true;
        }
        catch( IOException ex ){
            // Ooops
            ex.printStackTrace();
            return false;
        }
    }
    
    private static void putDefault( DockFrontend frontend, SplitDockStation station ){
        SplitDockGrid grid = new SplitDockGrid();
        TextDockable a = new TextDockable( "A" );
        TextDockable b = new TextDockable( "B" );
        
        frontend.add( a, "a" );
        frontend.add( b, "b" );

        grid.addDockable( 0, 0, 1, 1, a );
        grid.addDockable( 0, 1, 1, 1, b );
        station.dropTree( grid.toTree() );        
    }

    private static class TextDockable extends DefaultDockable{
        private JTextArea area = new JTextArea();
        public TextDockable( String title ){
            super( title );
            getContentPane().add( new JScrollPane( area ));
            setFactoryID( "text_" + title );
        }
        public String getText(){
            return area.getText();
        }
        public void setText( String text ){
            area.setText( text );
        }
    }
    
    private static class TextFactory implements DockFactory<TextDockable, String>{
        private String title;
        
        public TextFactory( String title ){
            this.title = title;
        }
        
        public String getID() {
            return "text_" + title;
        }

        public String getLayout( TextDockable element, Map<Dockable, Integer> children ) {
            System.out.println( "getLayout: " + title + " > " + element.getText() );
            return element.getText();
        }

        public TextDockable layout( String layout, Map<Integer, Dockable> children ) {
            System.out.println( "layout: " + title + " > " + layout );
            TextDockable dock = new TextDockable( title );
            dock.setText( layout );
            return dock;
        }

        public TextDockable layout( String layout ) {
            System.out.println( "layout: " + title + " > " + layout );
            TextDockable dock = new TextDockable( title );
            dock.setText( layout );
            return dock;
        }

        public String read( DataInputStream in ) throws IOException {
            // never happens
            System.out.println( "I should not be here" );
            return in.readUTF();
        }

        public String read( XElement element ) {
            System.out.println( "read: " + title + " > " + element.getString() );
            return element.getString();
        }

        public void setLayout( TextDockable element, String layout, Map<Integer, Dockable> children ) {
            System.out.println( "setLayout: " + element.getTitleText() + " > " + layout );
            element.setText( layout );
        }

        public void setLayout( TextDockable element, String layout ) {
            System.out.println( "setLayout: " + element.getTitleText() + " > " + layout );
            element.setText( layout );
        }

        public void write( String layout, DataOutputStream out ) throws IOException {
            // never happens
            System.out.println( "I should not be here" );
            out.writeUTF( layout );
        }

        public void write( String layout, XElement element ) {
            System.out.println( "write: " + title + " > " + layout );
            element.setString( layout );
        }
    }
}```