IHierarchyData и IHierarchicalEnumerable в Winforms - PullRequest
6 голосов
/ 13 марта 2010

В настоящее время я знаю, как выполнить ленивую реализацию процедуры загрузки узлов в элементе управления treeview, и прочитать связанные вопросы в stackoverflow, но я также читаю об интерфейсах IHierarchyData и IHierarchicalEnumerable в asp.net (я не знал код asp.net), которые позволяют привязывать коллекцию к древовидной структуре для автоматического отображения элементов.

Хотелось бы знать, могу ли я сделать то же самое в winforms и C #. Я думаю, что ранее упомянутые интерфейсы недоступны в winforms.

Спасибо.

Ответы [ 3 ]

3 голосов
/ 21 марта 2010

Windows Forms TreeView не знает, как связываться с экземпляром IHierarchyData, что неудивительно, учитывая, что IHierarchyData и связанные с ним интерфейсы предназначены для использования веб-элементами управления (особенно картами сайта).

Однако создать собственный класс привязки данных не так уж сложно. Это казалось интересной проблемой, поэтому я собрал одну из них ради забавы. Я проведу вас через внутреннюю работу.

Сначала создайте базовый класс Component. Visual Studio запустит вас с кодом, подобным этому:

public partial class TreeViewHierarchyBinding : Component
{
    public TreeViewHierarchyBinding()
    {
        InitializeComponent();
    }

    public TreeViewHierarchyBinding(IContainer container)
    {
        container.Add(this);
        InitializeComponent();
    }
}

Один очевидный фрагмент "состояния", который должен иметь этот компонент, - это сопоставление каждого TreeNode с IHierarchyData. Теперь мы можем обойти это, добавив его в свойство TreeNode Tag, но давайте постараемся сделать этот компонент как можно более неинвазивным и отслеживать его собственное состояние. Следовательно, мы будем использовать словарь. Добавьте это поле в класс:

private Dictionary<TreeNode, IHierarchyData> nodeDictionary = new 
    Dictionary<TreeNode, IHierarchyData>();

Теперь, как минимум, этот компонент должен знать, как заполнить определенный родительский элемент TreeNode класса TreeView из его соответственно связанной IHierarchyData, поэтому давайте напишем следующий код:

private void PopulateChildNodes(TreeNodeCollection parentCollection,
    IHierarchicalEnumerable children)
{
    parentCollection.Clear();
    foreach (object child in children)
    {
        IHierarchyData childData = children.GetHierarchyData(child);
        TreeNode childNode = new TreeNode(childData.ToString());
        if (childData.HasChildren)
        {
            childNode.Nodes.Add("Dummy");   // Make expandable
        }
        nodeDictionary.Add(childNode, childData);
        parentCollection.Add(childNode);
    }
}

private void UpdateRootNodes(TreeView tv, IHierarchyData hierarchyData)
{
    if (tv == null)
    {
        return;
    }
    tv.Nodes.Clear();
    if (hierarchyData != null)
    {
        IHierarchicalEnumerable roots = hierarchyData.GetChildren();
        PopulateChildNodes(tv.Nodes, roots);
    }
}

Эта часть должна быть довольно простой. Первый метод просто заполняет TreeNodeCollection (то есть свойство Nodes TreeNode) иерархией, полученной из экземпляра IHierarchyData, используя интерфейс IHierarchyEnumerable. Единственный действительно интересный способ этого метода:

  1. Добавление фиктивного узла, когда у экземпляра IHierarchyData есть дочерние элементы; это делает «+» видимым в древовидной структуре, иначе мы не смогли бы расширяться глубже; и

  2. Добавление вновь добавленного узла в словарь с экземпляром IHierarchyData, с которым он соответствует.

Второй метод еще проще, он выполняет начальную «работу связывания», заменяя все, что находится в корне дерева, нашим экземпляром IHierarchyData верхнего уровня.

Следующее, что должен сделать наш компонент, это перехватить события загрузки из TreeView для выполнения отложенной загрузки. Вот код для этого:

private void RegisterEvents(TreeView tv)
{
    tv.BeforeExpand += TreeViewBeforeExpand;
}

private void UnregisterEvents(TreeView tv)
{
    tv.BeforeExpand -= TreeViewBeforeExpand;
}

private void TreeViewBeforeExpand(object sender, TreeViewCancelEventArgs e)
{
    if (e.Node.Checked)
    {
        return;
    }
    IHierarchyData hierarchyData;
    if (nodeDictionary.TryGetValue(e.Node, out hierarchyData))
    {
        PopulateChildNodes(e.Node.Nodes, hierarchyData.GetChildren());
        e.Node.Checked = true;
    }
}

Первые два метода должны быть самоочевидными, а третий - это код с отложенной загрузкой. Здесь мы немного обманываем, используя свойство TreeNode.Checked, чтобы определить, были ли уже загружены дочерние узлы, поэтому мы не делаем ненужных перезагрузок. Я всегда делаю это, когда внедряю ленивые деревья, потому что, по моему опыту, я почти никогда не использую свойство TreeNode.Checked. Однако если вам нужно использовать это свойство для чего-то другого, вы можете использовать другое свойство (например, Tag), создать другой словарь для хранения расширенных состояний или изменить существующий словарь для хранения составного класса (содержащего IHierarchyData, а также Expanded свойство). Я пока держу все просто.

Остальное уже должно иметь смысл для вас, если вы ранее выполняли ленивую загрузку в дереве, поэтому давайте пропустим это. На самом деле, единственное, что осталось сделать на данный момент, это реализовать некоторые свойства конструктора / пользователя, которые фактически соединят дерево и данные:

private IHierarchyData dataSource;
private TreeView treeView;

[Browsable(false)]
[DesignerSerializationVisibility(DesignerSerializationVisibility.Hidden)]
public IHierarchyData DataSource
{
    get { return dataSource; }
    set
    {
        if (value != dataSource)
        {
            dataSource = value;
            nodeDictionary.Clear();
            UpdateRootNodes(treeView, value);
        }
    }
}

[Category("Behavior")]
[DefaultValue(null)]
[Description("Specifies the TreeView that the hierarchy should be bound to.")]
public TreeView TreeView
{
    get { return treeView; }
    set
    {
        if (value != treeView)
        {
            if (treeView != null)
            {
                UnregisterEvents(treeView);
            }
            treeView = value;
            nodeDictionary.Clear();
            RegisterEvents(value);
            UpdateRootNodes(treeView, dataSource);
        }
    }
}

Легко, peasy. У нас есть свойство DataSource, которое принимает корень IHierarchyData, и свойство TreeView, к которому вы сможете получить доступ от дизайнера. Опять же, простые вещи здесь, когда свойство DataSource обновляется, мы просто сбрасываем поиск и снова заполняем корень. Когда свойство TreeView обновлено, нам нужно проделать еще немного работы: зарегистрировать события, убедиться в том, чтобы отменить регистрацию событий в старом древовидном представлении, и делать все то же самое, что мы делаем при изменении источника данных.

Это действительно все, что нужно! Откройте конструктор Windows Forms, добавьте TreeView, затем TreeViewHierarchyBinding и установите для его свойства TreeView древовидное представление, которое вы только что отбросили. Наконец, где-нибудь в вашем коде (то есть в событии Form_Load) укажите источник данных:

private void Form1_Load(object sender, EventArgs e)
{
    DirectoryInfo dir = new DirectoryInfo("C:\\");
    treeViewHierarchyBinding1.DataSource = new FileSystemHierarchyData(dir);
}

(Примечание - здесь используется пример FileSystemHierarchyData, который находится на странице MSDN для IHierarchyData . Пример не очень надежный, он не проверяет UnauthorizedAccessException или что-либо еще, но это хорошо достаточно продемонстрировать это).

И это все. Запустите свое приложение и наблюдайте за его связыванием. Теперь вы можете повторно использовать компонент TreeViewHierarchyBinding в любом месте - просто перетащите его на форму, назначьте ему TreeView и присвойте ему экземпляр IHierarchyData в качестве источника данных.

Я поместил полный код в PasteBin , если вам нужна версия для копирования и вставки.

Веселись!

1 голос
/ 17 марта 2010

Здесь есть интересная статья , в которой показано, как создавать методы расширения для достижения того, что, я думаю, вы ищете. В System.Windows.Forms.TreeView отсутствует встроенная возможность привязки к коллекции из того, что я могу найти.

Вы МОЖЕТЕ включить System.Web.UI в свой проект, чтобы сделать доступными интерфейсы IHierarchyData и IHierarchicalEnumerable, но TreeView не сможет подключаться к ним без методов расширения.

Пример исходного кода с веб-сайта позволит вам связать любую коллекцию IDictionary с TreeView.

1 голос
/ 15 марта 2010

Интерфейсы доступны, но вам потребуется добавить ссылку на System.Web.UI. (Это может также потребовать от вас использования полного распространяемого пакета .NET Framework, а не профиля клиента, хотя я не уверен в этом.)

Большой вопрос: понимает ли элемент управления WinForms TreeView, как автоматически работать с этими интерфейсами? Я считаю, что ответом на этот вопрос является «Нет», но вам необходимо проверить / проверить это.

...