Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Deserializing into a UI

I have an assembly which contains several UserControl objects that I want to be able to save/load via the application UI. To do this, each control implements the ISerializable interface to customize the fields they need to save.

Here's a simplified version of that library:

namespace LibraryProject
{
    using System;
    using System.Runtime.Serialization;
    using System.Windows.Forms;

    [Serializable]
    public partial class UserControl1 : UserControl, ISerializable
    {
        public UserControl1()
        {
            InitializeComponent();
        }

        public UserControl1(SerializationInfo info, StreamingContext ctxt)
            : this()
        {
            this.checkBox1.Checked = info.GetBoolean("Checked");
        }

        public void GetObjectData(SerializationInfo info, StreamingContext context)
        {
            info.AddValue("Checked", this.checkBox1.Checked);
        }
    }
}

The client application instantiates several of this controls, and allows the user saving/loading the various UserControl configurations. Here's a simplified version of the application:

namespace ApplicationProject
{
    using System;
    using System.IO;
    using System.Runtime.Serialization.Formatters.Soap;
    using System.Windows.Forms;
    using LibraryProject;

    public partial class Form1 : Form
    {
        private const string filename = @"test.xml";

        //int hash1;
        //int hash2;

        public Form1()
        {
            InitializeComponent();

            //hash1 = this.ctrl1.GetHashCode();
        }

        private void SaveClick(object sender, EventArgs e)
        {
            using (var stream = File.Open(filename, FileMode.Create))
            {
                var formatter = new SoapFormatter();

                formatter.Serialize(stream, this.ctrl1);
            }
        }

        private void LoadClick(object sender, EventArgs e)
        {
            using (var stream = File.Open(filename, FileMode.Open))
            {
                var formatter = new SoapFormatter();

                this.ctrl1= (UserControl1)formatter.Deserialize(stream);
            }

            //hash2 = this.ctrl1.GetHashCode();
        }
    }
}

On SaveClick, the values are properly saved to file. On LoadClick, the CheckBox.Checked is properly updated in the Debugger Watch list, but the UI doesn't reflect the new value.

I have tried adding calls to Refresh(), Invalidate(), Update(), but nothing seems to work.

As expected, hash1 and hash2 are different, but Form1 uses the correct instance.

What am I doing wrong, and how can I fix the UI to display the correct (updated) value?

EDIT: Also, notice that I need to handle multiple config files, that the user must have the ability to save/load to/from a path of her choice

like image 634
Gustavo Mori Avatar asked Jul 01 '11 22:07

Gustavo Mori


People also ask

Can you deserialize Interface C#?

You can't deserialize something directly to an interface, You need a concrete class to instantiate.

What does serializing and Deserializing mean?

Serialization is a mechanism of converting the state of an object into a byte stream. Deserialization is the reverse process where the byte stream is used to recreate the actual Java object in memory. This mechanism is used to persist the object.

What is Deserializing a JSON?

JSON is a format that encodes objects in a string. Serialization means to convert an object into that string, and deserialization is its inverse operation (convert string -> object).

What is Deserializing an object?

Deserialization is the process of reconstructing a data structure or object from a series of bytes or a string in order to instantiate the object for consumption. This is the reverse process of serialization, i.e., converting a data structure or object into a series of bytes for storage or transmission across devices.


2 Answers

I have a sneaky suspicion that the UI isn't update because it isn't yet visible. I have found controls with a visible property set to false sometimes act up.

You are really better off serializing the data in some structured format and then using that to instantiate a control and populate the controls. Something like this (untested):

public class UserControlData
{
    public string Type { get; set; } // or assembly qualified type
    public List<ControlValue> ControlValues { get; set; }
}

public class ControlValue
{
    public string Name { get; set; }
    public object Value { get; set; }
}

public interface IControlPersistence
{
    List<ControlValue> GetControlValues();
    void SetControlValues(List<ControlValue> controlValues);
}

Then serialize / deserialize the user control data and instantiate and set values from the definitions. After you have instantiated and added the user control it can update its control values independently from the instantiation --- or even only once it is visible (should that endup being the issue).

I would also suggest wrapping the XmlSerializer and using that for serialization (maybe something like IObjectSerializer / XmlObjectSerializer : IObjectSerializer).

Hope that makes sense.

like image 61
Eben Roux Avatar answered Oct 01 '22 14:10

Eben Roux


I'm not sure but I'm going to guess it's because InitializeComponent doesn't get called.

But to solve your problem, it is better to serialize a surrogate. Just make little surrogate classes marked [Serializable] and copy properties from the UserControl to the surrogate before serialization. Then you don't have to mess around with GetObjectData - the serialization process just assumes every property of the surrogate should be serialized.

The deserialization process will give you a surrogate back. The surrogate just has to know how to instantiate a UserControl and map the properties back to it.

And if you define a common interface, you don't have to know which specific type of UserControl you are deserializing:

var surrogate = formatter.Deserialize(stream) as ISerializationSurrogate;
UserControl uc = surrogate.Create();
this.Controls.Add(uc);

Here's an example of how a surrogate might look:

[Serializable]
public class MySurrogate: ISerializationSurrogate
{
    public MySurrogate() {}

    public MySurrogate(MyControl control)
    {
        CB1Checked = control.checkBox1.Checked;
    }

    public bool CB1Checked { get; set; }

    public Control Create()
    {
        var control = new MyControl();
        control.checkBox1.Checked = CB1Checked;
        return control;
    }
}

Update

Actually, I bet the problem is that you're simply reassigning this.ctrl, which doesn't change which controls are on the form. What you actually need to do is something like this:

this.Controls.Remove(existingControl); // if it exists
this.Controls.Add(newControl);

But you should still use serialization surrogates. They make this kind of stuff much easier.

like image 37
default.kramer Avatar answered Oct 01 '22 15:10

default.kramer