65.9K
CodeProject 正在变化。 阅读更多。
Home

如何使用 MVC 模式填充 TreeView

starIconstarIcon
emptyStarIcon
starIcon
emptyStarIconemptyStarIcon

2.08/5 (8投票s)

2008年7月2日

CPOL

3分钟阅读

viewsIcon

59460

downloadIcon

780

使用 MVC(模型-视图-控制器)模式填充 TreeView。

FormScreenShot.png

引言

大多数人不需要遵循 MVC(模型-视图-控制器)模式的复杂 TreeView 填充器,他们只会即时创建所有需要的树节点。如果您已经创建了一个派生自 TreeNode 类的类,您就知道它能给您带来的强大功能,以及它如何使您的代码更加简洁。是的,很多时候,我们的 Form 代码可能包含 10000 多行代码,这非常难以维护。所以,重点是将 TreeView 的所有节点所需代码移到另一个类中。

背景

我之前已经阅读了 Mike Appleby 的文章 "Generic TreeView"[^],该文章描述了如何从对象模型填充 TreeView。但是,它的方法非常复杂,难以理解和使用。

数据

这是一个完整的示例,展示了我的想法。首先,让我们简单看一下 TreeView 内容的表示。

+ Directory
  + Directory
    + File
    + File
    + File
  + File

目录和文件(或多个文件)可以取任何名称。我们可以看到,一个目录下可以有两个类型的对象:目录或文件。

模型

为了处理我们 TreeView 的内容,我们需要创建两个类:CDirectoryCFile,以及它们的抽象。

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_DirectoryTN_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 函数添加到 CContentCDirectoryCFile 中,如下所示:

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 日)。
© . All rights reserved.