65.9K
CodeProject is changing. Read more.
Home

ComplexConverter - make configuration still more flexible

starIconstarIconstarIcon
emptyStarIcon
starIcon
emptyStarIcon

3.80/5 (3 votes)

Apr 17, 2008

CPOL

2 min read

viewsIcon

14489

downloadIcon

54

Converting complex Object-structures to string and reverse. Storing the result in an application-setting improves configurations flexibility substantially

Download ComplexConverter_src - 18.74 KB

Introduction

What I'd like to share is a technique, how multiple data items can be collected and converted into one String. The String can be stored as an application-setting, and all the data can be restored from it by one call.
This is useful to persist arbitrary user-settings, Form-Bounds, Form-WindowState, Widths of Splitterpanels, Slider-Positions, Widths of Columnheaders and stuff like this.
Actually a complete Treeview can be made persistent, with each Treenodes .Text, .IsExpanded-Property or what else may be important to save.

Using the code

Storing or Restoring starts with the call

(Dim S As String)  
S = aComplexConverter.ConvertToString() 
or 
aComplexConverter.RestoreFromString(S)
Both calls will raise the same aComplexConverter.Convert() - event, where the user has to implement a pass-through through his data and "point out" each Item he wants to get stored/restored.

Here a sample for a complex storing:

It stores/restores two Form-Properties (WindowState, Bounds), a Treeview (each TreeNode.Text, TreeNode.IsExpanded), the current Column-Widths of a ListView and all its Items (each SubItems Text)
 
    Private WithEvents _Memory As New ComplexConverter

    Private Sub Form1_Load(ByVal sender As Object, ByVal e As EventArgs) _
            Handles MyBase.Load
        _Memory.RestoreFromString(My.Settings.Memory)
    End Sub

    Private Sub Form1_FormClosing( _
            ByVal sender As Object, ByVal e As FormClosingEventArgs) _
            Handles Me.FormClosing
        My.Settings.Memory = _Memory.ConvertToString()
    End Sub

    '''<summary>complex conversion for this form</summary>
    Private Sub _Memory_Convert( _
            ByVal sender As Object, ByVal e As ComplexConverter.EventArg) _
            Handles _Memory.Convert
        'convert Form-Properties 
        With Me
            e.ConvertValue(.WindowState)
            e.ConvertValue(.Bounds)
        End With
        'convert Treeview 
        EnumerateNodes(TreeView1.Nodes, e)
        'convert ListViews Column-Withs
        For Each Col As ColumnHeader In Me.ListView1.Columns
            e.ConvertValue(Col.Width)
        Next
        'convert ListView-Items
        e.ConvertList(Of ListViewItem)(ListView1.Items)
        For Each LVI As ListViewItem In ListView1.Items
            'convert ListView-SubItems 
            e.ConvertList(Of ListViewItem.ListViewSubItem)(LVI.SubItems)
            For Each SITM As ListViewItem.ListViewSubItem In LVI.SubItems
                e.ConvertValue(SITM.Text)
            Next
        Next
    End Sub

    Private Sub EnumerateNodes( _
            ByVal Nodes As TreeNodeCollection, ByVal e As ComplexConverter.EventArg)
        e.ConvertList(Of TreeNode)(Nodes)
        For Each Nd As TreeNode In Nodes
            e.ConvertValue(Nd.Text)
            EnumerateNodes(Nd.Nodes, e)
            'Workaround: Treenode.IsExpanded is readonly, so I can't restore it directly
            Dim B As Boolean = Nd.IsExpanded
            e.ConvertValue(B)
            If B Then Nd.Expand()
        Next
    End Sub

Two types of data have to be distinguished: Values and Lists. Values are stored/restored as they are, their datatype is inferred by the generic ConvertValue(Of T)(ByRef Item As T)-method.

        e.ConvertValue(Me.WindowState) 

Lists are stored by only saving their .Count. They are restored by generating and adding Count unititialized items.
For that the "pointing out" of any type of List requires to pass the datatype of its Items by Type-Parameter to the
ConvertList(Of TItem As New)(ByVal List As IList)-method.

        'convert ListView-Items
        e.ConvertList(Of ListViewItem)(ListView1.Items)

Points of Interest

The hack in this is: the same code is used for storing and also for restoring. This has two positive effects:

  • The user defines the conversion for both directions in only one data-pass-through-code.
  • Only pure data needs to be stored. No additional information, about what type of data, in which order it occurs, or how to interpret (like e.g. Xml-Serialisation would do).
These additionals need not to be stored, but stay immanently in the handler-code of the Convert-event. So the stored string is very compact - e.g. a result of the above shown code-sample:
Normal|0; 0; 765; 321|2|Knoten0|2|Knoten1|0|False|Knoten2|3|Knoten4|0|False|Knoten5|0|False|Knoten6|0|
False|False|True|Knoten7|2|Knoten8|0|False|Knoten9|0|False|False|90|90|
90|90|2|4|01|02|03|04|4|11|12|13|14

Inside ComplexConverter

ComplexConverter has a problem, when set up, or conversion-code is changed. After a change it will read the old data, which doesn't fit no more.
For that RestoreFromString() raises the Convert-Event two times in two different modes: TryRestore-mode and Restore-mode
TryRestore imitates a restoring, but writes the values into a buffer. If an error occurs, the exception is catched and the restoring-process will be aborted without writing data to any target-property. So the targets will stay in their default-configuration.
Finally in the Restore-mode the data will be copied to the target-properties.