Destruction in the user interface

I have an assembly that contains several UserControl objects that I want to save / load through the user interface of the application. To do this, each control implements the ISerializable interface to configure the fields that must be saved.

Here is a simplified version of this 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 creates several of these controls and allows the user to save / load various UserControl configurations. Here is 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(); } } } 

In SaveClick values โ€‹โ€‹are saved in a file. In LoadClick the CheckBox.Checked parameter is properly updated in the debug view list, but the user interface does not reflect the new value.

I tried adding calls to Refresh() , Invalidate() , Update() , but nothing works.

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

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

EDIT: Also note that I need to process multiple configuration files so that the user can save / load / delete the path of their choice.

+6
source share
3 answers

I'm not sure, but I'm going to guess, because InitializeComponent not called.

But to solve your problem, it is better to serialize the surrogate. Just make small surrogate classes marked with [Serializable] and copy the properties from UserControl to the surrogate before serialization. Then you don't have to bother with GetObjectData - the serialization process simply assumes that each surrogate property needs to be serialized.

The deserialization process will give you a surrogate back. A surrogate just needs to know how to create an instance of UserControl and map properties to it.

And if you define a common interface, you donโ€™t need to know which type of UserControl you are deserializing:

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

Here is an example of what a surrogate might look like:

 [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'm sure the problem is that you are simply reassigning this.ctrl , which does not change which controls are on the form. What you really 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 stuff a lot easier.

+1
source

I have a suspicious suspicion that the user interface is not updating because it is not yet visible. I found that controls with the visible property set to false sometimes fire.

You really better serialize the data in some structured format and then use this to instantiate the 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 create and define values โ€‹โ€‹from the definitions. After you create an instance and add a user control, it can update its control values โ€‹โ€‹regardless of the instance โ€” or even only after it is visible (if that is the problem).

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

Hope this makes sense.

+3
source

In the end, I ended up adding an interface method to output serialized data to existing components, which was a very small code change. To illustrate the above layout, here is what I did:

Add an interface to load settings. In my actual project, it was just a new interface method supported by the controls in my library, and since the application is still under development, this is not a violation. For this layout, I added the ICopyFrom interface:

 public interface ICopyFrom { void CopyFrom(UserControl control); } 

Modify all serializable components to implement this interface. In the layout, this is only UserControl1 . The implementation in each class introduces an input control in its own type, and then copies the required properties:

 public void CopyFrom(UserControl control) { using (var source = control as UserControl1) { if (source == null) { return; } // copy properties this.checkBox1.Checked= source.checkBox1.Checked; } } 

Finally, modify the LoadClick event handler to use this mechanism to update existing controls:

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

While this works, it is not very elegant. And there it is proposed to decouple the user interface from the data. However, to make such a change requires more time than I can devote to it right now, so I threw it on the next revision.

0
source

Source: https://habr.com/ru/post/891849/