Десериализация в пользовательский интерфейс - PullRequest
6 голосов
/ 02 июля 2011

У меня есть сборка, которая содержит несколько UserControl объектов, которые я хочу сохранить / загрузить через пользовательский интерфейс приложения.Для этого каждый элемент управления реализует интерфейс ISerializable для настройки полей, которые необходимо сохранить.

Вот упрощенная версия этой библиотеки:

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);
        }
    }
}

Клиентское приложение создает несколькоэто контролирует и позволяет пользователю сохранять / загружать различные UserControl конфигурации.Вот упрощенная версия приложения:

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();
        }
    }
}

При SaveClick значения правильно сохраняются в файл.На LoadClick CheckBox.Checked правильно обновляется в списке отслеживания отладчика, но пользовательский интерфейс не отражает новое значение.

Я попытался добавить вызовы к Refresh(), Invalidate(), Update(), но, похоже, ничего не работает.

Как и ожидалось, hash1 и hash2 различны, но Form1использует правильный экземпляр.

Что я делаю неправильно, и как я могу исправить пользовательский интерфейс для отображения правильного (обновленного) значения?

РЕДАКТИРОВАТЬ: Кроме того, обратите внимание, что мне нужно обрабатывать несколько файлов конфигурации, чтопользователь должен иметь возможность сохранять / загружать в / из пути по своему выбору

Ответы [ 3 ]

3 голосов
/ 02 июля 2011

У меня есть подозрение, что пользовательский интерфейс не обновляется, потому что он еще не виден.Я обнаружил, что элементы управления с видимым свойством, установленным в false, иногда действуют.

Вам действительно лучше сериализовать данные в некотором структурированном формате, а затем использовать , что , чтобы создать экземпляр элемента управления и заполнитьуправления.Примерно так (не проверено):

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);
}

Затем сериализовать / десериализовать данные пользовательского элемента управления и создать и установить значения из определений.После того, как вы создали экземпляр и добавили пользовательский элемент управления, он может обновлять свои значения элемента управления независимо от создания экземпляра - или даже только после того, как он станет видимым (если этот вопрос является проблемой).

Я бы также предложил обернутьXmlSerializer и использование этого для сериализации (может быть что-то вроде IObjectSerializer / XmlObjectSerializer : IObjectSerializer).

Надеюсь, что имеет смысл.

1 голос
/ 02 июля 2011

Я не уверен, но я собираюсь догадаться, что это потому, что InitializeComponent не вызывают.

Но для решения вашей проблемы лучше сериализовать суррогат. Просто создайте маленькие суррогатные классы, помеченные как [Serializable], и ​​скопируйте свойства из UserControl в суррогат перед сериализацией. Тогда вам не нужно возиться с GetObjectData - процесс сериализации предполагает, что каждое свойство суррогата должно быть сериализовано.

Процесс десериализации вернет вам суррогат. Суррогат должен просто знать, как создать экземпляр UserControl и сопоставить ему свойства.

И если вы определяете общий интерфейс, вам не нужно знать, какой именно тип UserControl вы десериализуете:

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

Вот пример того, как суррогат может выглядеть:

[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;
    }
}

Обновление

На самом деле, держу пари, проблема в том, что вы просто переназначаете this.ctrl, что не меняет того, какие элементы управления находятся в форме. Что вам действительно нужно сделать, это что-то вроде этого:

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

Но вы все равно должны использовать суррогаты сериализации. Они делают такие вещи намного проще.

0 голосов
/ 02 июля 2011

В итоге я добавил интерфейсный метод для добавления сериализованных данных в существующие компоненты, что было очень небольшим изменением кода. Чтобы проиллюстрировать приведенный выше макет, вот что я сделал:

Добавить интерфейс для загрузки настроек. В моем реальном проекте это был просто новый метод интерфейса, поддерживаемый элементами управления в моей библиотеке, и, поскольку приложение все еще находится в разработке, это не является серьезным изменением. Для этого макета я добавил интерфейс ICopyFrom:

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

Измените все сериализуемые компоненты для реализации этого интерфейса. В макете, это только UserControl1. Реализация каждого класса приводит элемент управления вводом к своему собственному типу, а затем копирует необходимые свойства:

public void CopyFrom(UserControl control)
{
    using (var source = control as UserControl1)
    {
        if (source == null)
        {
            return;
        }

        // copy properties
        this.checkBox1.Checked= source.checkBox1.Checked;
    }
}

И, наконец, измените обработчик события LoadClick, чтобы использовать этот механизм для обновления существующих элементов управления:

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));
    }
}

Хотя это работает, это не очень элегантно. И есть предложение об отделении пользовательского интерфейса от данных. Однако внесение этого изменения требует больше времени, чем я могу посвятить этому прямо сейчас, поэтому я упустил его за следующую ревизию.

...