使用 TreeNode 作为用户设置

Using a TreeNode as a User Setting

我正在尝试使用 TreeNode (System.Windows.Forms.TreeNode) 作为我的一个应用程序的用户设置。

if(treeView.SelectedNode != null)
{
    Properties.Settings.Default.SelectedTreeNode = treeView.SelectedNode;
    Properties.Settings.Default.Save();
}

然后在应用程序加载时我尝试使用该设置

if (Properties.Settings.Default.SelectedTreeNode != null)
    treeView.SelectedNode= Properties.Settings.Default.SelectedTreeNode;

但无论我做什么,当我重新加载应用程序时,Properties.Settings.Default.SelectedTreeNode 始终为 null。

我也尝试过只使用对象并转换为 TreeNode,但这也不起作用。

我真的不想为此使用字符串设置,如果可能的话我想坚持使用 TreeNode,但是如果没有办法使用 TreeNode,序列化的 TreeNode 也可以。我只是不太熟悉序列化。

这里发生的是对 Save 方法的调用试图序列化节点以便将其存储在 User.config 文件中。如果你检查这个文件,你会发现节点是空的:

<?xml version="1.0" encoding="utf-8"?>
<configuration>
    <configSections>
        <sectionGroup name="userSettings" type="..." >
            <section name="SomeProject.Properties.Settings" type="..." allowExeDefinition="MachineToLocalUser" requirePermission="false" />
        </sectionGroup>
    </configSections>
    <userSettings>
        <SomeProject.Properties.Settings>
            <setting name="SelectedTreeNode" serializeAs="Xml">
                <value /> <!-- The node was not serialized! -->
            </setting>
        </SomeProject.Properties.Settings>
    </userSettings>
</configuration>

原因可能是 Save 方法正在尝试使用 XmlSerializer class 进行序列化,这不符合 ISerializable 接口,这就是序列化的方式为 TreeNode class 实施。它在某个时候在内部爆炸并吞下错误,而是留下一个空值。

解决此问题的方法是使用适当的序列化方法(格式化程序)序列化 TreeNode 对象。格式化程序遵循 ISerializable 接口。然后,您可以将生成的 string 存储在设置中,稍后读取它并将其具体化到节点中:

if (string.IsNullOrWhiteSpace(Properties.Settings.Default.SelectedTreeNode))
{                
    Properties.Settings.Default.SelectedTreeNode = SerializeNode(treeView.SelectedNode);
    Properties.Settings.Default.Save();
}

在应用程序加载时:

if (Properties.Settings.Default.SelectedTreeNode != null)
    treeView.SelectedNode= DeserializeNode(Properties.Settings.Default.SelectedTreeNode);

序列化函数:

public string SerializeNode(TreeNode node)
{
    var formatter = new SoapFormatter();
    using (var stream = new MemoryStream())
    {
        formatter.Serialize(stream, node);
        stream.Position = 0;
        using (var reader = new StreamReader(stream))
        {
            var serialized = reader.ReadToEnd();
            return serialized;
        }
    }
}

public TreeNode DeserializeNode(string nodeString)
{
    var formatter = new SoapFormatter();
    using (var stream = new MemoryStream())
    {
        using (var writer = new StreamWriter(stream))
        {
            writer.Write(nodeString);
            writer.Flush();
            stream.Position = 0;
            var node = (TreeNode)formatter.Deserialize(stream);
            return node;
        }                
    }          
}

对于这个答案,我使用 SoapFormatter class。您需要添加对 System.Runtime.Serialization.Formatters.Soap.

的引用

即使您可以在设置中存储 TreeNode,也不能将反序列化的节点分配给 TreeViewSelectedNode 属性。 TreeNode 是引用类型,并且由于您从设置中加载的实例与树中存在的实例不同,因此分配没有意义,也不会起作用。它已经在 Taw 的 的 b 点中提到。

要在设置中保留 selected 节点,最好依赖字符串 属性。您至少有两个选择:

  1. 在设置
  2. 中存储节点的Name属性
  3. 在设置
  4. 中存储节点的FullPath属性

选项 1 - 姓名 属性

每个TreeNode都有一个Name属性可以用来查找节点

  • 在创建节点时为节点分配唯一键:

    treeView1.Nodes.Add("key", "text");
    
  • 保存数据时,在设置中存储treeView1.SelectedNode.Name

  • 根据设置到select节点:

    treeView1.SelectedNode = treeView1.Nodes.Find("some key", true).FirstOrDefault();
    

选项 2 - 全路径 属性

每个TreeNode都有一个FullPath获取从根树节点到当前树节点的路径。

The path consists of the labels of all the tree nodes that must be navigated to reach this tree node, starting at the root tree node. The node labels are separated by the delimiter character specified in the PathSeparator property of the TreeView control that contains this node.

  • 创建节点时,不需要做特殊设置。每个节点都有FullPath.

  • 保存数据时,在设置中存储treeView1.SelectedNode.FullPath

  • 根据设置到select节点:

    treeView1.SelectedNode = treeView1.Nodes.FindByPath(@"path\to\the\node");
    

在上面的代码中,FindByPath 是一个扩展方法,您可以创建它来通过路径查找 ndoe:

using System.Windows.Forms;
public static class TreeViewExtensiona
{
    public static TreeNode FindByPath(this TreeNodeCollection nodes, string path)
    {
        TreeNode found = null;
        foreach (TreeNode n in nodes)
        {
            if (n.FullPath == path)
                found = n;
            else
                found = FindByPath(n.Nodes, path);
            if (found != null)
                return found;
        }
        return null;
    }
}