如何使用 MVC 模式填充 TreeView






2.08/5 (8投票s)
使用 MVC(模型-视图-控制器)模式填充 TreeView。
引言
大多数人不需要遵循 MVC(模型-视图-控制器)模式的复杂 TreeView 填充器,他们只会即时创建所有需要的树节点。如果您已经创建了一个派生自 TreeNode
类的类,您就知道它能给您带来的强大功能,以及它如何使您的代码更加简洁。是的,很多时候,我们的 Form 代码可能包含 10000 多行代码,这非常难以维护。所以,重点是将 TreeView
的所有节点所需代码移到另一个类中。
背景
我之前已经阅读了 Mike Appleby 的文章 "Generic TreeView"[^],该文章描述了如何从对象模型填充 TreeView
。但是,它的方法非常复杂,难以理解和使用。
数据
这是一个完整的示例,展示了我的想法。首先,让我们简单看一下 TreeView
内容的表示。
+ Directory
+ Directory
+ File
+ File
+ File
+ File
目录和文件(或多个文件)可以取任何名称。我们可以看到,一个目录下可以有两个类型的对象:目录或文件。
模型
为了处理我们 TreeView
的内容,我们需要创建两个类:CDirectory
、CFile
,以及它们的抽象。
public abstract class CContent
{
public string Name;
public CContent(string Name)
{
this.Name = Name;
}
};
public class CDirectory : CContent
{
public List<CContent> List = new List<CContent>();
public CDirectory(string Name)
: base(Name)
{ }
};
public class CFile : CContent
{
public CFile(string Name)
: base(Name)
{ }
};
视图(非 MVC,糟糕的解决方案)
假设我们希望我们的目录在 TreeView
中显示为绿色,而我们的文件显示为红色。为了方便处理,我们将创建另外两个派生自 TreeNode
的类:TN_Directory
和 TN_File
,它们看起来像这样:
public class TN_Directory : TreeNode
{
public TN_Directory(CDirectory dir)
{
Tag = dir;
Text = dir.Name;
ForeColor = Color.Green;
foreach (CContent content in dir.List)
Nodes.Add(content.CreateNode());
// See lower for the code of CreateNode.
}
};
public class TN_File : TreeNode
{
public TN_File(CFile file)
{
Tag = file;
Text = file.Name;
ForeColor = Color.Red;
}
};
这样,我们可以更容易地使用这些自定义的 TreeNode
来填充我们的 TreeView
。在某些程序中,由于复杂性,这样做是必要的。
没有好的方法来填充我们的 TreeView
。我找到的唯一方法是将 CreateNode
函数添加到 CContent
、CDirectory
和 CFile
中,如下所示:
public class CContent
{
...
public abstract TreeNode CreateNode();
};
public class CDirectory
{
...
public override TreeNode CreateNode()
{ return new TN_Directory(this); }
};
public class CFile
{
...
public override TreeNode CreateNode()
{ return new TN_File(this); }
};
现在,我们想填充我们的 TreeView
。我们只需要:treeView1.Nodes.Add(m_root.CreateNode());
。
Form1
的完整代码。
public partial class Form1 : Form
{
private TreeView treeView1;
private CDirectory m_root;
public Form1()
{
InitializeComponent();
SuspendLayout();
treeView1 = new TreeView();
treeView1.Dock = System.Windows.Forms.DockStyle.Fill;
Controls.Add(this.treeView1);
ResumeLayout(false);
// Add data to our Model
CDirectory dir = new CDirectory("Windows");
dir.List.Add(new CFile("notepad.exe"));
dir.List.Add(new CFile("regedit.exe"));
dir.List.Add(new CFile("win.ini"));
m_root = new CDirectory("C:\\");
m_root.List.Add(dir);
m_root.List.Add(new CFile("autoexec.bat"));
// Add our Model to our View
treeView1.Nodes.Add(m_root.CreateNode());
}
}
这里有一个大问题,那就是我们的模型中包含了视图的东西(CreateNode
函数)。为了避免这种情况,我们将采用不同的方法。我们将实现访问者模式。
视图(MVC,使用访问者模式的良好解决方案)
您可以在 Wikipedia [^] 上找到该模式的详细描述,我建议您在继续之前阅读它。好的,访问者模式包含四个部分:可访问接口、访问者类、一个访问者以及一个实现可访问接口的类。首先,我们来看看可访问接口(我们在这里将其声明为抽象类,但也可以将其声明为接口)。
public abstract class CVisitable
{
public abstract void accept(CVisitor visitor);
}
接下来,我们需要访问者类。
public abstract class CVisitor
{
public abstract void visit(CFile file);
public abstract void visit(CDirectory dir);
}
同样,我们可以创建一个接口而不是一个抽象类。
现在,我们在模型中实现 CVisitable
。
public class CDirectory : CContent
{
...
public override void accept(CVisitor visitor)
{
visitor.visit(this);
}
};
public class CFile : CContent
{
...
public override void accept(CVisitor visitor)
{
visitor.visit(this);
}
};
这非常简单,对于您可能创建的任何其他类,代码都将是相同的。现在,有趣的部分开始了。真正能产生区别的部分。我们将创建一个访问者对象。我们的访问者将负责创建相应的 TreeNode
,并将其命名为 CVisitor_TreeNode
。
public class CVisitor_TreeNode : CVisitor
{
// Result
public TreeNode TreeNode;
// Visitor
public override void visit(CFile file)
{
TreeNode = new TN_File(file);
}
public override void visit(CDirectory dir)
{
TreeNode = new TN_Directory(dir);
}
}
我们快完成了。现在我们要做的就是更改 Form1
构造函数中的 TreeView
填充代码,并将 TN_Directory
修改为使用我们的新技术。
public class TN_Directory : TreeNode
{
public TN_Directory(CDirectory dir)
{
Tag = dir;
Text = dir.Name;
ForeColor = Color.Green;
// This is where all the magic happen!
foreach (CContent content in dir.List)
{
CVisitor_TreeNode castTreeNode = new CVisitor_TreeNode();
content.accept(castTreeNode);
Nodes.Add(castTreeNode.TreeNode);
}
}
};
public partial class Form1 : Form
{
private TreeView treeView1;
private CDirectory m_root;
public Form1()
{
...
// Add our Model to our View
CVisitor_TreeNode castTreeNode = new CVisitor_TreeNode();
content.accept(castTreeNode);
treeView1.Nodes.Add(castTreeNode.TreeNode);
}
}
这是一个简单且非常干净的解决方案。但它并不遵循 MVC 模式。
关注点
在本文中,我们学习了访问者模式是什么,以及如何在复杂的 TreeView 架构中将视图与模型分离。
历史
- 修订 0:初次修订(2008 年 7 月 2 日)。