Skip to content

How to use SettingsManager

Mikle edited this page Jun 27, 2014 · 5 revisions

Available since: WebLaF v1.17 release
Required Java version: Java 6 update 30 or any later
Module: core


What is it for?

I am working with Java and Swing for a long time and keeping UI settings between application runs was always a big pain - you have to invent a way to save user UI settings for each different situation.

Actually, the same applies to any local data that application have to keep saved.

Usually minimum settings save/load process looks like this:

1st application run

  1. Wait till user does some changes
  2. Retrieve data from component
  3. Convert data into "savable" form
  4. Save it somewhere

2nd application run

  1. Read saved data
  2. Convert read data into object form
  3. Restore read data to the component

Implementation of this process usually requires a lot of coding. For example for a simple checkbox selected true/false value saving you will have to add additional checkbox listener to track its state and save value when it is changed. It gets even more complicated when it comes to some complex components and data. And keeping such settings/data grouped and structured both in file system and in code is even a harder task.

This is where WebLaF SettingsManager comes to save the day!


How to use it?

Configure SettingsManager

First of all you might want to setup your settings files location.
By default all settings will be saved into "{user.home}/.weblaf" directory.
They also will be read from this directory when queued.

To change ".weblaf" to something else but keep the parent directory the same:

SettingsManager.setDefaultSettingsDirName ( ".myapp" );

If you want to specify full path to default settings files location:

SettingsManager.setDefaultSettingsDir ( System.getProperty ( "user.dir" ) + File.separator + "settings" );

This is just an example, you can specify any path you like.

By default all settings are saved into a single SettingsGroup object which is equal to a single file in your file system. Use this method to modify default group name:

SettingsManager.setDefaultSettingsGroup ( "MyApplication" );

This group name is also used to set/get settings from the settings group and to name the settings file.

Default group is useful if you don't have too many settings and you don't want to write pointless code.You can skip specifying group name when working with various SettingsManager methods - default group will be used in that case.

If you have some settings you want to save somewhere else - put them in a separate group and specify a custom file location for that group:

SettingsManager.setGroupFileLocation ( "MyApplication", System.getProperty ( "user.home" ) );

You don't need to specify full path to file here, just the directory it will be saved into. File name will always be used according to settings group name.

One more useful option is settings auto-save on changes:

SettingsManager.setSaveOnChange ( true );

It is enabled by default and i really recommend you to use it for two reasons:

  1. To avoid unsaved data to be lost in case application is unexpectedly closed
  2. To avoid long save delays at the application exit

You can also specify auto-save delay to let the SettingsManager accumulate modified settings and save them all at once:

SettingsManager.setSaveOnChangeDelay ( 1000 );

This one is set to one second by default to filter out most of settings save calls which, as an example, might be massively performed by JSlider or JScrollBar.

One more option that might be useful in some cases - global settings save disabling/enabling:

SettingsManager.setAllowSave ( false );
// Do something here
SettingsManager.setAllowSave ( true );

If you need to disable settings saving at some point - this is exactly what you need.

You can also enable errors output:

SettingsManager.setDisplayErrors ( false );

This will print any error that occurs while loading/saving settings into System.err.

And the last one, settings and backup file extensions:

SettingsManager.setSettingsFilesExtension ( ".myapp" );
SettingsManager.setBackupFilesExtension ( ".dmp" );

By default ".xml" and ".backup" extensions are used.

And yes, each time settings are saved a backup file is created and removed only if operation succeed. That allows application to recover previously saved settings in case something goes totally wrong.

So, now you know how to setup SettingsManager, lets go to some examples...


Simple settings

First of all, lets use some custom settings for SettingsManager:

SettingsManager.setDefaultSettingsDirName ( ".myapp" );
SettingsManager.setDefaultSettingsGroup ( "MyApplication" );

With these settings our application settings will be saved into "{home.dir}/.myapp/MyApplication.xml" file.

Now lets see how we get/set some settings:

public class SettingsExample
{
    public static void main ( String[] args )
    {
        WebLookAndFeel.install ();
        SettingsManager.setDefaultSettingsDirName ( ".myapp" );
        SettingsManager.setDefaultSettingsGroup ( "MyApplication" );

        // Output old value
        System.out.println ( "Old: " + SettingsManager.get ( "option.one" ) );

        // Change value
        SettingsManager.set ( "option.one", MathUtils.random ( 0, 255 ) );

        // Output new value
        System.out.println ( "New: " + SettingsManager.get ( "option.one" ) );
    }
}

Note that if you are using your default settings group you don't have to specify its name into each separate SettingsManager method call, it is already used inside by default.

So if you run this code a few times you will see something like in the output:

Old: null
New: 74

Old: 74
New: 93

Old: 93
New: 47

So after configuring SettingsManager all you need to store/save and get/load settings is to call appropriate SettingsManager methods whenever you need to load/save your settings or data. Just check some methods available in SettingsManager - there are a lot of them for any possible case!


Component settings

"Methods described above are useful but they won't help a lot in saving component settings" - you will say. And this is true, so let's move to the next step - simplifying component settings management!

Minimum you need to register any component in SettingsManager is just one line of code:

SettingsManager.registerComponent ( component, "some.component" );

WebLaF components which support settings save also have shortcut methods:

WebCheckBox checkBox = new WebCheckBox ( "Example check box" );
checkBox.registerSettings ( "my.checkbox" );

Those methods are similar to ones in the SettingsManager but you don't need to pass component. You also stick to component methods in that case instead of using some separate static method.

In addition to settings key you can also specify settings group, default value or default value class. Default value may vary depending on the type of registered component. For example for checkbox with default SettingsProcessor provided by WebLaF you obviously need a simple boolean value - whether checkbox is selected or not. That is by default, you can of course replace the way SettingsManager handles component settings but let's talk about it a bit later.

Here is another live example:

WebCheckBox checkBox = new WebCheckBox ( "Example check box" );
SettingsManager.registerComponent ( checkBox, "my.checkbox", true );

This is enough to force checkbox "remember" its selection state between application runs and to provide "selected=true" state by default. So in just one line of code you solve like 3 or 4 different issues you might have encountered there.

As soon as you register component in SettingsManager - default, loaded or specified value will be applied to it and that component will also be listened for changes which affect the saved value.

"Now what if i want to alter SettingsManager behavior for existing component or add a new one for my custom component?" - you will ask, - that is also a standard situation you might get into and SettingsManager offers a fair solution for it.


SettingsProcessor

First of all, let me explain how SettingsManager understand which settings it should save and how does it determine component types, it is actually a pretty simple thing. For each supported component there is a custom SettingsProcessor inheritor class that knows how to save/load component settings and how to listen to its value changes. For example you can see AbstractButtonSettingsProcessor class to understand how SettingsManager handles toggle buttons, checkboxes and radiobuttons.

So in case you want to add some component settings support you have to write your own SettingsProcessor. Let's write one for JTabbedPane component - we will save selected tab index. First of all, let's create some custom small app:

public class SettingsExample
{
    public static void main ( String[] args )
    {
        WebLookAndFeel.install ();
        SettingsManager.setDefaultSettingsDirName ( ".myapp" );
        SettingsManager.setDefaultSettingsGroup ( "MyApplication" );
        initializeUI ();
    }

    private static void initializeUI ()
    {
        final WebTabbedPane tabbedPane = new WebTabbedPane ();
        tabbedPane.addTab ( "Tab 1", new WebLabel () );
        tabbedPane.addTab ( "Tab 2", new WebLabel () );
        tabbedPane.addTab ( "Tab 3", new WebLabel () );
        tabbedPane.addTab ( "Tab 4", new WebLabel () );
        tabbedPane.registerSettings ( "selected.tab.index", 3 );

        TestFrame.show ( tabbedPane, 5 );
    }
}

Note that settings are registered after we create tabs. This is done because otherwise we will get an exception because we are trying to select (remember? settings are loaded right inside this call) tab that does not exist yet.

Note: If you try registering a component that does not have linked SettingsProcessor for it you will see this exception in the output: [quote]java.lang.RuntimeException: Component type <a.b.c.MyComponent> is not supported.[/quote] But WebTabbedPane already has one default so it won't cause an exception unless you specify a wrong default value for it.

Anyway, we will now create and register our own SettingsProcessor to override default one:

public class JTabbedPaneSettingsProcessor extends SettingsProcessor<JTabbedPane, Integer>
{
    private ChangeListener listener;

    public JTabbedPaneSettingsProcessor ( SettingsProcessorData data )
    {
        super ( data );
    }

    protected void doInit ( JTabbedPane component )
    {
        listener = new ChangeListener ()
        {
            public void stateChanged ( ChangeEvent e )
            {
                save ();
            }
        };
        component.addChangeListener ( listener );
    }

    protected void doDestroy ( JTabbedPane component )
    {
        component.removeChangeListener ( listener );
        listener = null;
    }

    protected void doLoad ( JTabbedPane component )
    {
        component.setSelectedIndex ( loadValue () );
    }

    protected void doSave ( JTabbedPane component )
    {
        saveValue ( component.getSelectedIndex () );
    }
}

I did not add any checks to make code simple. Usually value might be null if we didn't specify default value so we should check that. Also selected tab might not exist in tabbed pane in case it has changed so we should check that as well.

Anyway, we still have to register our new SettingsProcessor at the application start before we register our tabbed pane for understandable reasons:

SettingsManager.registerSettingsProcessor ( JTabbedPane.class, JTabbedPaneSettingsProcessor.class );

Now we can run the example and see that selected tab is saved properly! :)

Note: This way our SettingsProcessor will be applied to all registered JTabbedPane components or any of its inheritors. To set a custom SettingsProcessor for a single component simply specify it when you register that component in SettingsManager, that will do the trick.


Handling exceptions

In some cases objects that represent your settings saved under some key might change and that might cause an exception when value for key is read. There are a few ways to avoid that happening.

1. Avoid changing value type under the same key I think this is the best way to "migrate" from old settings to newer ones - simply modify the key you use. Old value can always be retrieved using the old key so if you want you might convert it and save into new value and remove the old entry after that to avoid restoring the old value more than once.

2. Catch exceptions in SettingsProcessor If you are working only with component settings you might modify default settings processors provided for various components to make them exceptions-aware. By default there is no exceptions handling so they will be thrown directly from component register method if value load failed due to incompatibility.

2. Catch exceptions from "get" methods If you are using simple "get" methods from SettingsProcessor you will receive an exception when read object is not the type you expect - this one could also be easily handled.

Anyway, in my opinion avoiding exceptions and thinking through your settings structure ahead is always the best way. But in some cases you still might not be able to predict such changes.


Summary

So with this simple but yet powerful tool you can easily manage any amount of various application settings and data without creating tons of "supportive" code.

You will also be able to edit settings outside the application since they are saved into simple, readable and structured XML-files. Here is an example of settings file for example with tabbed pane from above:

<SettingsGroup id="SG-lTJ06-HIN63-02b3o-p5fIf-ID" name="MyApplication">
  <selected.tab.index type="int">0</selected.tab.index>
</SettingsGroup>

As you can see only meaningful things are saved here, anything else left aside.