On the EditorPreferences Class.

Started by Calin Leafshade, Sun 30/01/2011 20:10:52

Previous topic - Next topic

Calin Leafshade

Currently the editor prefs are stored in the registry as discrete fields in the class.

I propose changing this in 2 ways.

Firstly by making the preferences dictionary based.

Code: ags

Dictionary<String, object> preferences;


This allows new additions to the editor (and plugins) to store their preferences in a unified way simply by adding to the dictionary.

The 'object' would be an anonymous object (i forget if thats the right term, alternatively a separate 'Preference' struct could be used.) containing the value of the preference, the real-world name of the preference and the name of the category that the preference should occupy. (the String would obviously be the preference key).

Each component would then access the preference with a function call, passing the preference key. This function would return an object which would then be cast however appropriate.

Secondly i propose that we ditch the registry keys and use an xml file in the appdata folder. This is easier to maintain, more portable and make sense since everything else in the editor is stored in an xml file.

Thoughts from the other developers would be welcome.

Shane 'ProgZmax' Stevens


RickJ

I think saving preferences in a file is a good idea.   

I have often wished that some preferences could be made on a per project basis.   Perhaps this could be implemented by first reading the xml preferences file in the appdata folder and then reading an xml preferences xml file in the game folder.   Entries in the game folder file would then overwrite/override entries in the appdata folder.

For example, it would be very nice if one could specify a path to a fonts folder/library for every AGS project and be able to specify a different sprite import path for each project.   

Calin Leafshade

That seems reasonable. One can set their global preferences which apply to each new project and the per-project preferences which only hold for any particular project.

tzachs

This sounds like a good idea.
My two cents here, would be to not use an 'object' here, but to create a base class (PreferenceBase, for example), and put all common functionality (like Name) in there.
Even if you can't think of anything common that the preferences have now, this will probably change in the future.

Pumaman

Using an xml file in AppData folder instead of registry -- fine. It probably is easier to maintain and extend that way. Just make sure that all the existing preference settings are preserved and copied across from the registry the first time someone uses the new version.

I think rather than using "object" I'd prefer some sort of Preference<T> class that could be strongly typed to the type of data the preference was storing. Something like:

class Preference<T>
{
 public string Name { get; set; }
 public string DisplayName { get; set; }
 public string Category { get; set; }
 public T Value { get; set; }
}

each component could then register its Preferences on startup, and the core editor code could read/write these from the xml file.

Any thoughts?

Calin Leafshade

strongly typing the value was something that was bothering me. A template class would fix that problem so sounds good to me.

Calin Leafshade

#7
Sorry for bringing this back up. Necropost!

So I've been trying to implement a Preference generic class but i have come across two issues.

Firstly you cant have a dictionary or list of *different* generic types. So in order to do that it has to be abstracted to an interface so it can be kept in a Dictionary.

But, secondly interfaces cannot be serialized. So I wrote a custom serializer for it but how can i re-instantiate the Pref<T> if T is unknown at design time?

I can save the Type easily enough but I cant then return a value of that type unless the type is known at design time.

So as far as I can tell strong typing a preference class with generics is impossible in .net 2.0

Anyone more experienced willing to shine some light on it?

RickJ

CJ gave you clear guidance when he said "I'd prefer some sort of Preference<T> class that could be strongly typed to the type of data the preference was storing.  Something like:...".   His advice seems sound and straight forward to me.   A base preference class that knows how to read and write itself to an XML file,  info about how to display itself on a dialog.   Extend value to be a list of values and corresponding value types.  Store values as strings and use type define how values are used, accessed, and dialog display/interaction. 

Calin Leafshade

You are somewhat missing the point of my post. A generic type (called a template in c) cannot be onstantiated without know the type parameter at design time. Sine cpmponents and plugins pass an arbitrary number of prefs you cant know the type

RickJ

Quote
You are somewhat missing the point of my post. A generic type (called a template in c) cannot be onstantiated without know the type parameter at design time.
Templates are not necessary or even useful for what you are trying to do.   The type is "Preference"  and is known at design time.   It contains a list of preference values (strings) and a list of preference types (also strings).  The preference type defines what the preference value is used for, it's data type (int, string, bool, etc), how it's displayed on the dialog, and other characteristics.   

A preference type class could be used to define the above characteristics.   It would then be a simple matter to create an array of preference types populated with whatever properties are desired or appropriate for each specific preference type.   

CJ's suggestion is a little different, more OO, and simpler.  It assumes each preference has a single value and a single data type.  If I understand correctly, there would be a base "Preference"  class and each preference type would be a subclass.  (i.e. Preference, PreferenceInt, PreferenceMytype, ...).  So if the XML looked something like "<Preference name="", type="Mytype">42</Preference>" then an instance of PreferenceMytype would be created and the value "42" stored in it.  Presumably there would be a list of preferences containing all instances.






monkey0506

If you're defining derived classes such as "PreferenceInt, PreferenceMytype, [...etc.]", wouldn't that kind of be the entire reason for using a generic type, which you say is "not...even useful" in this case...?

smiley

You can do something like this:
Code: ags

[Serializable]
public abstract class Preference
{
}

[Serializable]
public class Preference<T> : Preference
{
	public string Name { get; set; }
	public string DisplayName { get; set; }
	public string Category { get; set; }
	public T Value { get; set; }
}       

public static IList<Preference> Deserialize(string filename)
{
	using (XmlReader reader = new XmlTextReader(filename))
	{
		reader.ReadToDescendant("ArrayOfString");
		XmlSerializer typeSer = new XmlSerializer(typeof(List<string>));
		List<string> typeNames = (List<string>)typeSer.Deserialize(reader);
		List<Type> types = new List<Type>();
		foreach (var type in typeNames)
		{
			types.Add(Type.GetType(type));
		}

		reader.ReadToDescendant("preferences");

		XmlSerializer deSerializer = new XmlSerializer(typeof(Preference), types.ToArray());
		List<Preference> retvalue = new List<Preference>();
		while (reader.Read())
		{
			switch (reader.NodeType)
			{
				case XmlNodeType.Element:
					if (reader.Name == "Preference")
					{
						retvalue.Add((Preference)deSerializer.Deserialize(reader));
					}
					break;
			}
		}

		return retvalue;
	}
}

public static void Serialize(IList<Preference> preferences, string filename)
{
	List<Type> types = new List<Type>();
	List<string> typeNames = new List<string>();
	foreach (var pref in preferences)
	{
		var type = pref.GetType();
		if (!types.Contains(type))
		{
			types.Add(type);
			typeNames.Add(type.AssemblyQualifiedName);
		}
	}

	XmlSerializer serializer = new XmlSerializer(typeof(Preference), types.ToArray());
	XmlWriterSettings settings = new XmlWriterSettings()
	{
		Indent = true
	};

	using (XmlWriter writer = XmlWriter.Create(filename, settings))
	{
		writer.WriteStartElement("doc");

		writer.WriteStartElement("types");
		XmlSerializer typeSer = new XmlSerializer(typeNames.GetType());
		typeSer.Serialize(writer, typeNames);
		writer.WriteEndElement();

		writer.WriteStartElement("preferences");
		foreach (var pref in preferences)
		{
			serializer.Serialize(writer, pref);
		}
		writer.WriteEndElement();

		writer.WriteEndElement();
		writer.Flush();
	}
}

This will save the concrete 'Preference<T>' types to xml, and then use them to initialize the XmlSerializer on deserialization.

Calin Leafshade

@RickJ

So you have changed your mind about CJ's guidance now? CJ's guidance was pretty much entirely 'use generics'

Then in your next post you said that generics were 'not useful'

So which is it?

@smiley

Thanks! Thats a pretty good solution. I'll give it a go.

SMF spam blocked by CleanTalk