问题
I'm trying to use a TreeNode (System.Windows.Forms.TreeNode) as a user setting for one of my applications.
if(treeView.SelectedNode != null)
{
Properties.Settings.Default.SelectedTreeNode = treeView.SelectedNode;
Properties.Settings.Default.Save();
}
Then on Application Load I'm trying to use that Setting
if (Properties.Settings.Default.SelectedTreeNode != null)
treeView.SelectedNode= Properties.Settings.Default.SelectedTreeNode;
but no matter what I do, Properties.Settings.Default.SelectedTreeNode
is always null when I reload the application.
I've also tried just using an Object and casting to a TreeNode, but that doesn't work either.
I really don't want to use string Settings for this, and want to stick with TreeNode if possible, but a serialized TreeNode would work if there is no way to use a TreeNode. I'm just not too familiar with Serialization.
回答1:
Even if you could store TreeNode
in settings, you cannot assign deserialized node to SelectedNode
property of TreeView
. TreeNode
is reference type and since the instance which you load from setting is not the same instance which exists in the tree, the assignment doesn't make sense and will not work. It's already mentioned in point b in the comment by Taw.
To preserve selected node in settings, it's better to rely on a string property. You have at least two options:
- Store
Name
property of the node in settings - Store
FullPath
property of the node in settings
Option 1 - Name Property
Each TreeNode
has a Name
property which can be used to find the node.
Assign a unique key to nodes when creating them:
treeView1.Nodes.Add("key", "text");
When saving data, store
treeView1.SelectedNode.Name
in settings.To select the node based on the settings:
treeView1.SelectedNode = treeView1.Nodes.Find("some key", true).FirstOrDefault();
Option 2 - FullPath Property
Each TreeNode
has a FullPath
which gets the path from the root tree node to the current tree node.
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.
When creating node, you don't need to do special settings. Every node has
FullPath
.When saving data, store
treeView1.SelectedNode.FullPath
in settings.To select the node based on the settings:
treeView1.SelectedNode = treeView1.Nodes.FindByPath(@"path\to\the\node");
In above code, FindByPath
is an extension method which you can create to find the ndoe by path:
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;
}
}
回答2:
What's happening here is that the call to the Save
method attempts to serialize the node in order to store it in the User.config
file. If you inspect this file, you will find that the node is empty:
<?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>
The reason is probably that the Save
method is attempting the serialization using the XmlSerializer
class, which does not respect the ISerializable
interface, which is how serialization is implemented for the TreeNode
class. It blows up internally at some point and it swallows the error, leaving an empty value instead.
What you can do to get around this is serialize the TreeNode
object using a proper serialization method, which is a formatter. Formatters respect the ISerializable
interface. You can then store the resulting string
in the setting and later read it and materialize it into the node:
if (string.IsNullOrWhiteSpace(Properties.Settings.Default.SelectedTreeNode))
{
Properties.Settings.Default.SelectedTreeNode = SerializeNode(treeView.SelectedNode);
Properties.Settings.Default.Save();
}
On application load:
if (Properties.Settings.Default.SelectedTreeNode != null)
treeView.SelectedNode= DeserializeNode(Properties.Settings.Default.SelectedTreeNode);
The serialization functions:
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;
}
}
}
For this answer, I am using the SoapFormatter
class. You will need to add a reference to System.Runtime.Serialization.Formatters.Soap
.
来源:https://stackoverflow.com/questions/53308604/using-a-treenode-as-a-user-setting