[Gtk-sharp-list] A rudimentary proposal for supporting complex binding in TreeView

Chas chastamar@yahoo.com
Mon, 9 May 2005 04:39:16 -0700 (PDT)


--0-486935817-1115638756=:63179
Content-Type: text/plain; charset=us-ascii
Content-Id: 
Content-Disposition: inline

Attached is an html file describing a rudimentary
proposal for supporting complex binding in TreeView
(binding to a list).

Comments would be most welcome; especially as there
are open questions and issues.

Cheers,
Chas


		
Discover Yahoo! 
Use Yahoo! to plan a weekend, have fun online and more. Check it out! 
http://discover.yahoo.com/
--0-486935817-1115638756=:63179
Content-Type: text/html; name="gtk-sharp-binding-tv.html"
Content-Description: 2659168794-gtk-sharp-binding-tv.html
Content-Disposition: inline; filename="gtk-sharp-binding-tv.html"

<html><head></head><body>
<h1>A rudimentary proposal for binding lists to TreeView</h1>
<p>This proposal is meant to allow binding to typical data sources in the framework. To that end, the framework's interfaces ITypedList and IBindingList are used. This would allow to easily bind to e.g. a DataView. Of course, some additional sugar may be needed in order to make the API more obvious.</p>

<p>I've tried to consider most details, but I am no gtk# master. The general goals of data binding can be achieved in various ways so I felt there was a need for some kind of basis for discussion; I hope this serves the purpose well.</p>

<h2>Abstract:</h2>

<p>We have an IBindingList implementor which contains the original data. We need to display the formatted (e.g., converted to values the CellRenderers know to display) data at the TreeView. I see two main options:</p>
<p><b>[Option 1]</b> Keep a ListStore-variant object that is synchronized with the IBindingList, containing the formatted data, updating it as the list changes. The TreeView can then be mapped directly to the values at the ListStore.<br>
<h4>Pro for this approach:</h4>
	Since the data is kept synced in the ListStore, the user can use regular column mappings when the data is of the basic type of the CellRenderer (i.e., string for CellRendererText, bool for CellRendererToggle). This mapping is a faster way to display data than using a CellDataFunc, AFAIK.
<h4>Con for this approach:</h4>
	The data is replicated in the ListStore from the IBindingList. For large sets of data, this may not be acceptable.</p>

<p><b>[Option 2]</b> Keep a ListStore-variant that'd hold references to the data in the IBindingList implementor; no real data will be kept at this ListStore object. CellDataFunc would be used to extract the data from the IBindingList implementor and set CellRenderers' properties as needed when drawing.<br>
<h4>Pro for this approach:</h4>
	No replicated data is kept; more memory-friendly.
<h4>Con for this approach:</h4>
	Uses CellDataFunc for all of the columns; performance may suffer.</p>

<h2>Details:</h2>

<pre class="screen">
<p>1. The 'Synced' ListStore - a ListStore-variant which is kept synchronized to the IBindingList implementor - would require the following additions, compared to a ListStore:
	a.	Add a constructor that receives ITypedList and takes the types from the PropertyDescriptor's.
	b.	Add a method Bind(IBindingList) that:
			- Registers the BoundListChanged handler (see below) to the given IBindingList's ListChanged event.
			- Calls ReloadList() (see below) with the given list.
			- Keeps a private reference to the given list.
	c.	Add a private handler for ListChanged: BoundListChanged
		This handler receives IBindingList's ListChanged event.
		(This event's arguments include the ListChangedType enum, NewIndex and OldIndex)
		This handler handles:
			Possible events and responses:
				Reset :		calls Clear() and ReloadList() (see below)
				ItemAdded :	calls AppendValues() for the item
				ItemDeleted :	calls Remove() the item
				ItemMoved :	calls Swap() for the item as needed
				ItemChanged :	calls SetValues() for the item
				PropertyDescriptor[Added, Deleted, Changed] : Ignored, as ListStore does not support changing the columns after rows have been added according to gtk+ docs.
			An internal mapping between TreeIter's and list indexes needs to kept and updated as needed, i.e. in order to be able to parse IBindingList's events.
	c.	Add a private method ReloadList(IList) that:
			Performs AppendValues() for all values in the list (recreating the internal 2-way mapping between TreeIters and indexes in the IList). (Note about the IList argument - IBindingList inherits from IList and I see no reason for this method to depend on IBindingList specifically.)</p>

<p>2. The 'Referencing' ListStore - A ListStore-variant that just keeps references to the 'real' data at the bound list. The stored value is just this reference (the index in the list). So, compared to ListStore, the needed support would be something like:
	a.	Add a constructor that receives ITypedList and takes the types from the PropertyDescriptor's.
	b.	Add a method Bind(IBindingList) that:
			- Registers the BoundListChanged (see below) to the given IBindingList's ListChanged event.
			- Calls UpdateListRefs() (see below) with the given list.
			- Keeps a private reference to the given list.
	c.	Add a private handler method for ListChanged: BoundListChanged.
		Possible events and responses:
			Reset :		calls Clear() and UpdateListRefs() for all of the list.
			ItemAdded :	calls AppendValues() for the item (only the index is stored)
			ItemDeleted :	calls Remove() the item
			ItemMoved :	calls Swap() for the item as needed
			ItemChanged :	The internal value is not changed, but a RowChanged event needs to be emitted.
			PropertyDescriptor[Added, Deleted, Changed] ignored here also, for same reason as in option (1) above.
	c.	Add a private method UpdateListRefs(IList) that performs AppendValues() for all values in the list. (The value stored for each row is just the index at the given IList).

3. Notes for both of the above options:
	a.	These changes can be done to the ListStore class itself but the problem is that allowing the user to mess with the data in both ways (modifying it from both the IBindingList implementor and the ListStore) is problematic, as this requires support in the ListStore (would need to reflect changes to the bound list) and is nothing but headache IMHO. If it's possible to make a ListStore variant that internally (gtk+-wise) implements TreeModel but does not expose TreeModel's modifying methods to C# it'd be best, I believe.
	b.	Adding some public methds to convert a list index to a TreeIter and vice-versa is needed.</p>
<p>Now, the user can bind to data pretty easily - in option (1), use mappings or CellDataFunc as needed, and in option (2), use CellDataFunc in her columns to obtain the data from the list (by translating the TreeIter to the list index).
However, in order to make life easier for the common case, some sugar coating API may be useful:</p>
<p>3. TreeView
	a.	Add a public helper method AppendColumns(string[] titles, ITypedList, bool modifiable)
		That method'd iterate on the PropertyDescriptors, creates the columns accordingly and adds them to the TreeView with the given names and CellRendererText/CellRendererToggle, doing the necessary arrangements to support display and modification of the data.
		E.g., for each PropertyDescriptor from ITypedList's PropertyDescriptorCollection:
			If its type is boolean:
				Use CellRendererToggle
				Set the cell-renderer's 'activatable' property to the given 'modifiable'
				In option (1): Set up a mapping between 'active' and the data
				In option (2):
					Set a proper CellDataFunc to do the job of setting the 'active' property based on the value from the list
					Set a proper handler for the 'Toggled' event to modify the data in the list
			Else, if its type is string:
				Use CellRendererText
				Set the cell-renderer's 'activatable' property to the given 'modifiable'
				In option (1): Set up a mapping between 'text' and the data
				In option (2):
					Set a proper CellDataFunc to do the job of setting the 'text' property based on the value from the list
					Set a proper handler for the 'Edited' event to modify the data in the list
			Else:
				Use CellRendererText
				Set the cell-renderer's 'activatable' property to the given 'modifiable'
				In both options, a CellDataFunc should be set; the function would perform ToString() on the data and set the text of the CellRenderer to it.
				Also, if 'modifiable' is true, a handler for the 'Edited' event should be set. This handler would use the TypeConverter (obtained from the PropertyDescriptor) to convert the string to the correct data type and update the bound list correctly.<br>

		This scheme may look primitive, but it should be useful in many common cases. AFAIK, WinForms behaves in a principally similar way.
		Questions:
			Maybe the data should somehow be kept already formatted in the ListStore in this case, allowing the usage of property-to-column mappings at all times?
			Should the 'modifiable' argument be replaced for an IBindingList, from which the 'AllowEdit' property would be used for the same purpose?</p>
</pre>

<h3>Additional issues:</h3>
<p><b>1</b>. Sorting - handled both by IBindingList implementors and by TreeView. Which one should be used? My guess is IBindingList's. Here, sorting the IBindingList would affect the data displayed in the TreeView (a Reset event is produced for the list). What are the implications? Should the sort be indicated to the TreeView somehow (so the sort column and direction are displayed correctly) ? Should TreeView's sort be disabled in some way?<br>
<b>2.</b> How do we support addition and removal of rows (if at all) ?<br>
<b>3.</b> This is just for lists. Trees aren't considered here. Should they be?</p>
</body></html>

--0-486935817-1115638756=:63179--