ApplicationSettingsBase 为自定义集合写入空标签

发布于 2024-11-15 00:46:36 字数 8903 浏览 5 评论 0原文

我已经断断续续地与这个斗争好几天了。我需要存储自定义对象的集合作为用户设置的一部分。根据谷歌的大量工作,似乎从 ApplicationSettingsBase 构建首选项类是一种合适的方法。我遇到的问题是,一旦我尝试存储自定义类型的集合,就不会为该属性保存数据。如果我坚持使用基本类型的集合(例如字符串),那么事情就可以了。我有一个单独的概念验证项目,过去一天我一直在处理这个问题。

该项目由一个带有列表框和两个按钮“+”和“-”的 WPF 窗口组成。我有一个 Prefs 类,其中包含三个定义不同类型集合的属性。在我的窗口代码中,我将列表框绑定到这些列表之一,并且按钮将项目添加到列表或从列表中删除项目。关闭窗口应将列表内容保存到当前用户的 users.config 文件中。重新打开它应该显示列表中保存的内容。

如果我使用 List1 (ObservableCollection) 并单击 + 按钮几次,然后关闭窗口,数据将正确保存到 user.config。但是,如果我更改并使用 List2(ObservableCollection) 或 List3(FooCollection) 我最终会得到一个空值标签。我尝试实现 ISerializedIXmlSerialized ,试图在不改变行为的情况下使其正常工作。事实上,在带断点的调试模式下运行表明,调用 save 方法时集合包含数据,但从未调用序列化接口方法。

我缺少什么?

更新:我改变了我的方法,暂时将数据与 user.config 一起写入另一个文件,以便我可以在应用程序的其他部分取得一些进展。但我仍然想知道为什么应用程序设置库无法记录我的收集数据。

这是所有相关代码,如果有人认为省略的部分很重要,我会将其添加回帖子中。

将几个元素添加到每个列表属性后的 user.config

<setting name="List1" serializeAs="Xml">
    <value>
        <ArrayOfString xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
            xmlns:xsd="http://www.w3.org/2001/XMLSchema">
            <string>0</string>
            <string>1</string>
            <string>2</string>
        </ArrayOfString>
    </value>
</setting>
<setting name="List2" serializeAs="Xml">
    <value />
</setting>
<setting name="List3" serializeAs="Xml">
    <value />
</setting>

的Window XAML代码隐藏

<Grid>
    <Grid.ColumnDefinitions>
        <ColumnDefinition></ColumnDefinition>
        <ColumnDefinition Width="Auto"></ColumnDefinition>
    </Grid.ColumnDefinitions>
    <ListBox Name="l1" Grid.Column="0" Grid.Row="0" ItemsSource="{Binding}"></ListBox>
    <StackPanel Grid.Column="1" Grid.Row="0">
        <Button Name="bP" Margin="5" Padding="5" Click="bP_Click">+</Button>
        <Button Name="bM" Margin="5" Padding="5" Click="bM_Click">-</Button>
    </StackPanel>

</Grid>

Window

public partial class Window1 : Window
{
    Prefs Prefs = new Prefs();

    public Window1()
    {
        InitializeComponent();

        //l1.DataContext = Prefs.List1;
        //l1.DataContext = Prefs.List2;
        l1.DataContext = Prefs.List3;
    }

    private void Window_Closed(object sender, EventArgs e)
    {
        Prefs.Save();
    }

    private void bP_Click(object sender, RoutedEventArgs e)
    {
        //Prefs.List1.Add(Prefs.List1.Count.ToString());
        //Prefs.List2.Add(new Foo(Prefs.List2.Count.ToString()));
        Prefs.List3.Add(new Foo(Prefs.List3.Count.ToString()));
    }

    private void bM_Click(object sender, RoutedEventArgs e)
    {
        //Prefs.List1.RemoveAt(Prefs.List1.Count - 1);
        //Prefs.List2.RemoveAt(Prefs.List2.Count - 1);
        Prefs.List3.RemoveAt(Prefs.List3.Count - 1);
    }
}

Prefs 类

class Prefs : ApplicationSettingsBase
{
    [UserScopedSettingAttribute()]
    [DefaultSettingValueAttribute(null)]
    public System.Collections.ObjectModel.ObservableCollection<string> List1
    {
        get
        {
            System.Collections.ObjectModel.ObservableCollection<string> Value = this["List1"] as System.Collections.ObjectModel.ObservableCollection<string>;
            if (Value == null)
            {
                Value = new System.Collections.ObjectModel.ObservableCollection<string>();
                this["List1"] = Value;
            }
            return Value;
        }
    }

    [UserScopedSettingAttribute()]
    [DefaultSettingValueAttribute(null)]
    public System.Collections.ObjectModel.ObservableCollection<Foo> List2
    {
        get
        {
            System.Collections.ObjectModel.ObservableCollection<Foo> Value = this["List2"] as System.Collections.ObjectModel.ObservableCollection<Foo>;
            if (Value == null)
            {
                Value = new System.Collections.ObjectModel.ObservableCollection<Foo>();
                this["List2"] = Value;
            }
            return Value;
        }
    }

    [UserScopedSettingAttribute()]
    [DefaultSettingValueAttribute(null)]
    public FooCollection List3
    {
        get
        {
            FooCollection Value = this["List3"] as FooCollection;
            if (Value == null)
            {
                Value = new FooCollection();
                this["List3"] = Value;
            }
            return Value;
        }
    }
}

Foo Class

[Serializable()]
class Foo : System.ComponentModel.INotifyPropertyChanged, ISerializable, IXmlSerializable
{
    private string _Name;
    private const string PropName_Name = "Name";
    public string Name
    {
        get { return this._Name; }
        set
        {
            if (value != this._Name)
            {
                this._Name = value;
                RaisePropertyChanged(Foo.PropName_Name);
            }
        }
    }
    public override string ToString()
    {
        return Name;
    }

    public Foo() { }
    public Foo(string name)
    {
        this._Name = name;
    }

    #region INotifyPropertyChanged Members
/***Omitted for space***/
    #endregion

    #region ISerializable Members
    public Foo(SerializationInfo info, StreamingContext context)
    {
        this._Name = (string)info.GetValue(Foo.PropName_Name, typeof(string));
    }
    public void GetObjectData(SerializationInfo info, StreamingContext context)
    {
        info.AddValue(Foo.PropName_Name, this._Name);
    }
    #endregion

    #region IXmlSerializable Members
    public System.Xml.Schema.XmlSchema GetSchema()
    {
        return null;
    }

    public void ReadXml(System.Xml.XmlReader reader)
    {
        reader.MoveToContent();
        _Name = reader.GetAttribute(Foo.PropName_Name);
        bool Empty = reader.IsEmptyElement;
        reader.ReadStartElement();
        if (!Empty)
        {
            reader.ReadEndElement();
        }
    }

    public void WriteXml(System.Xml.XmlWriter writer)
    {
        writer.WriteAttributeString(Foo.PropName_Name, _Name);
    }
    #endregion
}

和 FooCollection 类

[Serializable()]
class FooCollection : ICollection<Foo>, System.ComponentModel.INotifyPropertyChanged, INotifyCollectionChanged, ISerializable, IXmlSerializable
{
    List<Foo> Items;
    private const string PropName_Items = "Items";

    public FooCollection()
    {
        Items = new List<Foo>();
    }

    public Foo this[int index]
    {
/***Omitted for space***/
    }

    #region ICollection<Foo> Members
/***Omitted for space***/
    #endregion

    public void RemoveAt(int index)
    {
/***Omitted for space***/
    }

    #region IEnumerable Members
/***Omitted for space***/
    #endregion

    #region INotifyCollectionChanged Members
/***Omitted for space***/
    #endregion

    #region INotifyPropertyChanged Members
/***Omitted for space***/
    #endregion

    #region ISerializable Members
    public FooCollection(SerializationInfo info, StreamingContext context)
    {
        this.Items = (List<Foo>)info.GetValue(FooCollection.PropName_Items, typeof(List<Foo>));
    }
    public void GetObjectData(SerializationInfo info, StreamingContext context)
    {
        info.AddValue(FooCollection.PropName_Items, this.Items);
    }
    #endregion

    #region IXmlSerializable Members
    public System.Xml.Schema.XmlSchema GetSchema()
    {
        return null;
    }

    public void ReadXml(System.Xml.XmlReader reader)
    {
        XmlSerializer FooSerializer = new XmlSerializer(typeof(Foo));

        reader.MoveToContent();
        bool Empty = reader.IsEmptyElement;
        reader.ReadStartElement();
        if (!Empty)
        {
            if (reader.IsStartElement(FooCollection.PropName_Items))
            {
                reader.ReadStartElement();

                while (reader.IsStartElement("Foo"))
                {
                    this.Items.Add((Foo)FooSerializer.Deserialize(reader));
                }

                reader.ReadEndElement();
            }

            reader.ReadEndElement();
        }
    }

    public void WriteXml(System.Xml.XmlWriter writer)
    {
        XmlSerializer FooSerializer = new XmlSerializer(typeof(Foo));

        writer.WriteStartElement(FooCollection.PropName_Items);

        foreach (Foo Item in Items)
        {
            writer.WriteStartElement("Foo");
            FooSerializer.Serialize(writer, Item);
            writer.WriteEndElement();//"Foo"
        }

        writer.WriteEndElement(); //FooCollection.PropName_Items
    }
    #endregion
}

I have been fighting with this off and on for several days now. I need to store a collection of custom objects as part of the user's settings. Based on lots of google work it seems that building a preferences class off of ApplicationSettingsBase is an appropriate way to do so. The problem I run into is as soon as I try to store a collection of a custom type no data gets saved for that property. If I keep to collections of base types, such as string, things work. I have a separate proof of concept project I have been working with for the past day to isolate down to just this issue.

This project consists of a WPF window with a listbox and two buttons, “+” and “-“. I have a Prefs class with three properties defining different types of collections. In my window code I bind the listbox to one of these lists and the buttons either add or remove an item to/from the list. Closing the window should save the contents of the list to the users.config file for the current user. Reopening it should display the saved contents of the list.

If I use List1 (ObservableCollection<string>) and click the + button a few times then close the window the data is correctly saved to user.config. However if I change and use List2(ObservableCollection<Foo>) or List3(FooCollection) I just end up with an empty value tag. I have tried to implement ISerializable and IXmlSerializable in attempts to get this working with no change in behavior. In fact running in debug mode with break points shows that the collection contains data when the save method is called but the serialization interface methods are never being called.

What am I missing?

UPDATE: I have changed my approach and written the data off to another file alongside of user.config for the time being so that I can make some progress on other portions of the app. But I would still like to know why application settings base was failing to record my collection data.

Here is all of the relevent code, if someone thinks an ommitted section is important I will add it back into the post.

user.config after adding a couple of elements to each list property

<setting name="List1" serializeAs="Xml">
    <value>
        <ArrayOfString xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
            xmlns:xsd="http://www.w3.org/2001/XMLSchema">
            <string>0</string>
            <string>1</string>
            <string>2</string>
        </ArrayOfString>
    </value>
</setting>
<setting name="List2" serializeAs="Xml">
    <value />
</setting>
<setting name="List3" serializeAs="Xml">
    <value />
</setting>

Window XAML

<Grid>
    <Grid.ColumnDefinitions>
        <ColumnDefinition></ColumnDefinition>
        <ColumnDefinition Width="Auto"></ColumnDefinition>
    </Grid.ColumnDefinitions>
    <ListBox Name="l1" Grid.Column="0" Grid.Row="0" ItemsSource="{Binding}"></ListBox>
    <StackPanel Grid.Column="1" Grid.Row="0">
        <Button Name="bP" Margin="5" Padding="5" Click="bP_Click">+</Button>
        <Button Name="bM" Margin="5" Padding="5" Click="bM_Click">-</Button>
    </StackPanel>

</Grid>

Code Behind for Window

public partial class Window1 : Window
{
    Prefs Prefs = new Prefs();

    public Window1()
    {
        InitializeComponent();

        //l1.DataContext = Prefs.List1;
        //l1.DataContext = Prefs.List2;
        l1.DataContext = Prefs.List3;
    }

    private void Window_Closed(object sender, EventArgs e)
    {
        Prefs.Save();
    }

    private void bP_Click(object sender, RoutedEventArgs e)
    {
        //Prefs.List1.Add(Prefs.List1.Count.ToString());
        //Prefs.List2.Add(new Foo(Prefs.List2.Count.ToString()));
        Prefs.List3.Add(new Foo(Prefs.List3.Count.ToString()));
    }

    private void bM_Click(object sender, RoutedEventArgs e)
    {
        //Prefs.List1.RemoveAt(Prefs.List1.Count - 1);
        //Prefs.List2.RemoveAt(Prefs.List2.Count - 1);
        Prefs.List3.RemoveAt(Prefs.List3.Count - 1);
    }
}

Prefs class

class Prefs : ApplicationSettingsBase
{
    [UserScopedSettingAttribute()]
    [DefaultSettingValueAttribute(null)]
    public System.Collections.ObjectModel.ObservableCollection<string> List1
    {
        get
        {
            System.Collections.ObjectModel.ObservableCollection<string> Value = this["List1"] as System.Collections.ObjectModel.ObservableCollection<string>;
            if (Value == null)
            {
                Value = new System.Collections.ObjectModel.ObservableCollection<string>();
                this["List1"] = Value;
            }
            return Value;
        }
    }

    [UserScopedSettingAttribute()]
    [DefaultSettingValueAttribute(null)]
    public System.Collections.ObjectModel.ObservableCollection<Foo> List2
    {
        get
        {
            System.Collections.ObjectModel.ObservableCollection<Foo> Value = this["List2"] as System.Collections.ObjectModel.ObservableCollection<Foo>;
            if (Value == null)
            {
                Value = new System.Collections.ObjectModel.ObservableCollection<Foo>();
                this["List2"] = Value;
            }
            return Value;
        }
    }

    [UserScopedSettingAttribute()]
    [DefaultSettingValueAttribute(null)]
    public FooCollection List3
    {
        get
        {
            FooCollection Value = this["List3"] as FooCollection;
            if (Value == null)
            {
                Value = new FooCollection();
                this["List3"] = Value;
            }
            return Value;
        }
    }
}

Foo Class

[Serializable()]
class Foo : System.ComponentModel.INotifyPropertyChanged, ISerializable, IXmlSerializable
{
    private string _Name;
    private const string PropName_Name = "Name";
    public string Name
    {
        get { return this._Name; }
        set
        {
            if (value != this._Name)
            {
                this._Name = value;
                RaisePropertyChanged(Foo.PropName_Name);
            }
        }
    }
    public override string ToString()
    {
        return Name;
    }

    public Foo() { }
    public Foo(string name)
    {
        this._Name = name;
    }

    #region INotifyPropertyChanged Members
/***Omitted for space***/
    #endregion

    #region ISerializable Members
    public Foo(SerializationInfo info, StreamingContext context)
    {
        this._Name = (string)info.GetValue(Foo.PropName_Name, typeof(string));
    }
    public void GetObjectData(SerializationInfo info, StreamingContext context)
    {
        info.AddValue(Foo.PropName_Name, this._Name);
    }
    #endregion

    #region IXmlSerializable Members
    public System.Xml.Schema.XmlSchema GetSchema()
    {
        return null;
    }

    public void ReadXml(System.Xml.XmlReader reader)
    {
        reader.MoveToContent();
        _Name = reader.GetAttribute(Foo.PropName_Name);
        bool Empty = reader.IsEmptyElement;
        reader.ReadStartElement();
        if (!Empty)
        {
            reader.ReadEndElement();
        }
    }

    public void WriteXml(System.Xml.XmlWriter writer)
    {
        writer.WriteAttributeString(Foo.PropName_Name, _Name);
    }
    #endregion
}

and FooCollection class

[Serializable()]
class FooCollection : ICollection<Foo>, System.ComponentModel.INotifyPropertyChanged, INotifyCollectionChanged, ISerializable, IXmlSerializable
{
    List<Foo> Items;
    private const string PropName_Items = "Items";

    public FooCollection()
    {
        Items = new List<Foo>();
    }

    public Foo this[int index]
    {
/***Omitted for space***/
    }

    #region ICollection<Foo> Members
/***Omitted for space***/
    #endregion

    public void RemoveAt(int index)
    {
/***Omitted for space***/
    }

    #region IEnumerable Members
/***Omitted for space***/
    #endregion

    #region INotifyCollectionChanged Members
/***Omitted for space***/
    #endregion

    #region INotifyPropertyChanged Members
/***Omitted for space***/
    #endregion

    #region ISerializable Members
    public FooCollection(SerializationInfo info, StreamingContext context)
    {
        this.Items = (List<Foo>)info.GetValue(FooCollection.PropName_Items, typeof(List<Foo>));
    }
    public void GetObjectData(SerializationInfo info, StreamingContext context)
    {
        info.AddValue(FooCollection.PropName_Items, this.Items);
    }
    #endregion

    #region IXmlSerializable Members
    public System.Xml.Schema.XmlSchema GetSchema()
    {
        return null;
    }

    public void ReadXml(System.Xml.XmlReader reader)
    {
        XmlSerializer FooSerializer = new XmlSerializer(typeof(Foo));

        reader.MoveToContent();
        bool Empty = reader.IsEmptyElement;
        reader.ReadStartElement();
        if (!Empty)
        {
            if (reader.IsStartElement(FooCollection.PropName_Items))
            {
                reader.ReadStartElement();

                while (reader.IsStartElement("Foo"))
                {
                    this.Items.Add((Foo)FooSerializer.Deserialize(reader));
                }

                reader.ReadEndElement();
            }

            reader.ReadEndElement();
        }
    }

    public void WriteXml(System.Xml.XmlWriter writer)
    {
        XmlSerializer FooSerializer = new XmlSerializer(typeof(Foo));

        writer.WriteStartElement(FooCollection.PropName_Items);

        foreach (Foo Item in Items)
        {
            writer.WriteStartElement("Foo");
            FooSerializer.Serialize(writer, Item);
            writer.WriteEndElement();//"Foo"
        }

        writer.WriteEndElement(); //FooCollection.PropName_Items
    }
    #endregion
}

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(2

那小子欠揍 2024-11-22 00:46:36

我也遇到了类似的问题,我设法解决了这个问题,但我没有使用 ObservableCollection,而是使用普通的 ListColumn > 作为我的一个类,它包含公共成员:string、int 和 bool,它们都是 xmlserialized。 List<>,因为它实现了IEnumerable,所以也是XMLSerialized

在自定义 Foo 类中您需要注意的唯一事情是:您必须将成员设为公共、无参数构造函数,并且类本身必须是公共的。您不需要为 xml 序列化添加 [Serializing] 标签。

我不需要实现 FooCollection 类,因为我使用的 List 没有 xml 序列化问题。

另一件事:

  • ApplicationSettingsBase 派生的类可以是内部密封的 - 不需要它是公共的。
  • 在 list 属性上方添加它是 xml 可序列化的事实:

    [全局::System.Configuration.UserScopedSettingAttribute()]
    [SettingsSerializeAs(SettingsSerializeAs.Xml)]
    [全局::System.Configuration.DefaultSettingValueAttribute("")]
    公共列表栏
    {
    得到
    {
    return ((List)this["列"]);
    }

    {
    this["列"] = (列表)值;
    }
    在 list 属性上方

如果这不起作用,您还可以尝试实现 TypeConverter

I also had a similar problem, which I managed to fix, but I was not using an ObservableCollection, but a normal List<Column>, Column being a class of mine, that contained as public members: string, int and bool, which are all xmlserializable. The List<>, since it implement the IEnumerable is also XMLSerializable.

The only things you have to take care of in the custom Foo Class are: you must have the members as public, a parameterless constructor and the class itself has to be public. You do not need to add the [Serializable] tag for xml serialization.

I did not need to implement a FooCollection class, since I am using List which has no issue with xml serialization.

Another thing:

  • the class that derives from ApplicationSettingsBase can be internal sealed - no need for it to be public.
  • above the list prop add the fact that it is xml serializable:

    [global::System.Configuration.UserScopedSettingAttribute()]
    [SettingsSerializeAs(SettingsSerializeAs.Xml)]
    [global::System.Configuration.DefaultSettingValueAttribute("")]
    public List Columns
    {
    get
    {
    return ((List)this["Columns"]);
    }
    set
    {
    this["Columns"] = (List)value;
    }
    }

If this does not work you can also try to implement a TypeConverter.

喜爱皱眉﹌ 2024-11-22 00:46:36

我也为一个非常类似的问题苦苦挣扎了两天,但现在我发现了一个缺失的链接,这可能会有所帮助。
如果你的情况和我的情况确实相似,那么
类“Foo”和“FooCollection”需要显式公开!

我假设,在 List2(ObservableCollection) 和 List3(FooCollection) 的情况下,.NET 会遇到 IXmlSerialized ,这需要以某种方式显式公共访问(跨越自己程序集的边界)。

相反的选项 List1 (ObservableCollection) 似乎在平面字符串类型转换上运行,它对(内部)类“Foo”感到满意...

(来自 ApplicationsettingsBase Docu:
ApplicationSettingsBase 使用两种主要机制
序列化设置:
1) 如果存在可以与字符串相互转换的 TypeConverter,我们就使用它。
2) 如果没有,我们回退到 XmlSerializer )

亲切的问候,
鲁迪

I struggled with a very similar issue for two days as well, but now I found a missing link which might help here.
If your and my case are actually similar, then
the classes 'Foo' and 'FooCollection' need to be explicitly public!

I assume, that in case of List2(ObservableCollection<Foo>) and List3(FooCollection) the .NET runs into IXmlSerializable which requires somehow the explicit public access (crosses the border of the own assembly).

Option List1 (ObservableCollection<string>) in the opposite seems to run on a flat string-Typeconversion which is happy with the (internal) class 'Foo' ...

(From ApplicationsettingsBase Docu:
There are two primary mechanisms that ApplicationSettingsBase uses to
serialize settings:
1) If a TypeConverter exists that can convert to and from string, we use it.
2) If not, we fallback to the XmlSerializer )

Kind regards,
Rudy

~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文