Reputation: 284
I am currently writing a game, and have come to the point where I need the user to be able to save preferences for things such as the JFrame size, key bindings etc. The users will be running the game through a jar downloaded from my website.
I've decided to go with the Preferences API under "java.util.prefs.Preferences". I am still quite new when it comes to reading things like documentation, and would like someone to help explain a bit to me.
So far, I have something like this:
Preferences prefs = Preferences.userNodeForPackage(com.custardgames.horde2d.Game.class);
prefs.put(name, value);
Now then, how do I actually get to saving the preference file on the users computer? Right now, if I run the program again and try just:
Preferences prefs = Preferences.userNodeForPackage(com.custardgames.horde2d.Game.class);
System.out.println(prefs.get(name, null));
It just returns null which is the default value if there is nothing saved. I know I am missing the vital part of actually saving the file, and then opening it the correct way, but haven't gotten much out of googling it.
Thanks so much in advance!
Edit: FYI, for reading images, I am using something like this:
BufferedImage currentImage=ImageIO.read(this.getClass().getClassLoader().getResource("res/images/image.jpg"));
I would like to be able to do something similar with preferences.
Edit:
name
and value
are both String variables declared beforehand.
Upvotes: 3
Views: 1906
Reputation: 48824
As an alternative, I personally prefer to save a configuration file (and avoid the Registry), which the Properties
class enables nicely. Like you're imagining, you generally create a file on the user's machine in a logical location, often either the directory the application was started from, or the user's home directory.
jGrep, A Swing application I wrote a while ago does exactly this, and you're welcome to use the associated code as a starting point. The source code is a public Mercurial repository, jGrep source code, and the relevant lines start in JGrep.java:140.
Essentially, you do three steps (the code snippets below are loosely based on the jGrep codebase):
Decide where to save your file:
jGrep can run either as a JNLP, a standalone Jar, or an installed Windows application. In the first and second case, we want the application to be as self contained as possible, and so we save the properties file in the directory the application was started from. In the latter case (or if you "install" it on Linux or Mac) it instead writes to the user's home directory, enabling multiple users to share the application without sharing state.
public File getConfigFile(boolean isInstalled) {
String filename = ".jGrep.conf"; // The . prefix means "hidden file" in Unix
return isInstalled ? // indicates the application is "installed"
new File(getUserDir(), filename) : // identifies the user's home directory
new File(filename); // default to a file in the current directory
}
Load your properties file (if it exists) at startup / whenever you need:
This method potentially throws several exceptions if the file doesn't exist, can't be read, or contains unexpected content. You'll serve your users best by explicitly handling these cases and giving them a useful error message, however you could just return an empty properties file; jGrep does this, providing an unhelpful generic "could not be opened" message - I should probably go fix this :)
public Properties loadProperties(File f)
throws FileNotFoundException, IOException, IllegalArgumentException {
Properties prop = new Properties();
try(FileInputStream in = new FileInputStream(f)) {
prop.load(in); // Reads from the input stream
}
// catch (Exception e) { } // uncomment to suppress errors
return prop;
Save your properties file on exit / whenever the user asks:
Now we save our config file in the same way. Here similarly you could suppress errors, but I particularly don't suggest doing that for this method, as the user expects to have their data saved, and should be told clearly what the error was if you can't.
public void saveProperties(Properties prop, File f)
throws FileNotFoundException, IOException {
try(FileOutputStream out = new FileOutputStream(f)) {
prop.store(out, "AwesomeProgram configuration file");
// return true; // uncomment if suppressing errors
}
// catch (Exception e) { return false; } // uncomment to suppress errors
}
Note that in neither method do we express an encoding - the properties file writes out in Latin 1 with Java escape codes (i.e. \uXXXX
) for non-Latin characters. If you want your file to be human-readable you can pass Reader
/Writer
objects to the properties file instead, which can be any encoding you want (UTF-8).
Obviously, if you need to save multiple files, you can do so simply by saving/loading separate files to different Properties objects.
Upvotes: 1
Reputation: 5989
Are you sure that name
variable has the same value? Because it should return correct stored value - maybe value is null?
Try printing them out just before storing & getting.
You can also try to flush preferences after storing:
prefs.flush();
If you're on Windows you can check if the preferences are stored in regedit
under
\HKEY_CURRENT_USER\Software\JavaSoft\Prefs\<your package path>
in Linux I'm not sure but there should be a hidden folder in your home directory. Something like
~/.java/.prefs/<package path>
I've developed a JFrame using preferences for storing last position & size of a frame. You can look at it here:
The library is at: java-utils
The piece of code that might interest you:
public void saveSize() {
Preferences preferences = Preferences.userNodeForPackage(this.getClass());
preferences.put(getId() + X_KEY, String.valueOf(getLocation().x));
preferences.put(getId() + Y_KEY, String.valueOf(getLocation().y));
preferences.put(getId() + W_KEY, String.valueOf(getSize().width));
preferences.put(getId() + H_KEY, String.valueOf(getSize().height));
preferences.put(getId() + MAX_KEY,
String.valueOf((getExtendedState() & JFrame.MAXIMIZED_BOTH) == JFrame.MAXIMIZED_BOTH));
try {
preferences.flush();
}
catch(BackingStoreException e) {
e.printStackTrace();
}
}
public void setSavedSize() {
Preferences preferences = Preferences.userNodeForPackage(this.getClass());
String xs = preferences.get(getId() + X_KEY, "");
String ys = preferences.get(getId() + Y_KEY, "");
String ws = preferences.get(getId() + W_KEY, "");
String hs = preferences.get(getId() + H_KEY, "");
String max = preferences.get(getId() + MAX_KEY, "");
if(max != null && !max.trim().isEmpty() && Boolean.valueOf(max) == true) {
setDefaultSize();
setExtendedState(JFrame.MAXIMIZED_BOTH);
return;
}
if(xs.length() == 0 || ys.length() == 0 || ws.length() == 0 || hs.length() == 0) {
setDefaultSize();
}
else {
sizeFromPreferences = true;
int x = Integer.parseInt(xs);
int y = Integer.parseInt(ys);
int w = Integer.parseInt(ws);
int h = Integer.parseInt(hs);
setLocation(x, y);
setSize(w, h);
}
}
EDIT
If you want to create some kind of settings storing system you can use the same convention as in RememberableFrame
- use prefixes.
In RememberableFrame
I use:
private String getId() {
return this.getClass().getSimpleName() + id;
}
where id
is custom String provided by the developer or an empty string. But remember that the key of the property you want to set has length limit.
From the API:
static int MAX_KEY_LENGTH
Maximum length of string allowed as a key (80 characters).
static int MAX_NAME_LENGTH
Maximum length of a node name (80 characters).
static int MAX_VALUE_LENGTH
Maximum length of string allowed as a value (8192 characters).
You might also consider using classes specifically for "storing settings" purposes. For example class KeyboardSettings
and even in different packages.
Upvotes: 2