Дизайн С# TreeView - лучший способ отобразить древовидную структуру?

Я пытаюсь использовать TreeView для отображения древовидной структуры объектов. У меня есть дерево из четырех типов объектов: Компания (корневой узел), Город, Магазин и Сотрудник.

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

Мне интересно, как правильно заставить TreeView отображать древовидную структуру и получать обновления при ее изменении.

Я думаю, что объект Company должен иметь события, такие как company.CityAdded и company.CityRemoved, тогда любая оболочка, которую я помещаю вокруг TreeView, отвечает на эти события? Когда TreeView будет создан, будет узел для каждого города/магазина/сотрудника. Затем каждый узел может реагировать на события узла, который он представляет в дереве.

Это правильная идея? Или есть лучший метод?


person Community    schedule 06.02.2009    source источник


Ответы (5)


Я просто хотел добавить, что если WPF является вариантом для этого, это становится невероятно простым, используя иерархическую привязку данных и наблюдаемые коллекции. По сути, он делает за вас всю обработку событий и позволяет вам просто взаимодействовать с вашими бизнес-объектами.

person Jacob Adams    schedule 07.02.2009

Похоже, вы на правильном пути. Мне пришлось сделать нечто подобное, несколько советов, которыми я хотел бы поделиться:

  1. Сохранить ссылку на объект в свойстве тега TreeNode.

  2. Дайте каждому Treenode уникальное имя, которое может легко идентифицировать объект, например: хэш-код объекта, идентификатор компании и т. д.

Таким образом, вы можете легко найти и обновить TreeNode при изменении состояния объекта. И когда пользователь выбирает узел, вы можете получить объект, который он представляет, из свойства Tag.

Удачи.

person WebMatrix    schedule 06.02.2009

Вы правы в отношении концепции прослушивания событий (это стандартный шаблон издателя/подписчика).

Для фактического обновления древовидной структуры у меня есть два метода: AddOrUpdateTreeItem и RemoveTreeItem. Метод add или update делает то, что говорит, ищет элемент дерева (на основе пути) и обновляет или добавляет его. Конечно, если модель обновляется в потоке, отличном от того, в котором была создана форма, вам нужно будет маршалировать вызов, используя Control.BeginInvoke().

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

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

private void AddOrUpdateListItem(DomainModelObject item)
{
    ListViewItem li = lvwListView.Items[GetKey(item)];

    if (li == null)
    {
        li = new ListViewItem
                 {
                     Name = GetKey(item), 
                     Tag = item
                 };
        li.SubItems.Add(new ListViewItem.ListViewSubItem());
        li.SubItems.Add(new ListViewItem.ListViewSubItem());
        li.SubItems.Add(new ListViewItem.ListViewSubItem());
        li.ImageIndex = 0;
        lvwListView.Items.Add(li);
    }

    li.Text = [Itemtext];
    li.SubItems[1].Text = [Itemtext];
    li.SubItems[2].Text = [Itemtext];
    li.SubItems[3].Text = [Itemtext];
}

Вот пример того, как можно реализовать BeginInvoke():

public class MyForm : Form
{
    ...

    void data_Changed(object sender, DataChangedEventArgs e)
    {
        if (this.InvokeRequired)
        {
            this.BeginInvoke(new EventHandler<DataChangedEventArgs>(data_Changed), sender, e);
            return;
        }

        AddOrUpdateListItem(e.DataItem);
    }

    ...
}
person Neil Barnwell    schedule 06.02.2009
comment
+1 за упоминание BeginInvoke(). Однако вы можете изменить свой пример, чтобы показать это. - person Matt Jordan; 10.02.2009

Вместо ...

  • Бизнес-объекты подписываются на события пользовательского интерфейса
  • Команды обновляют пользовательский интерфейс
  • Бизнес-объекты обновляются при обновлении пользовательского интерфейса.

... вы также можете сделать это наоборот (т.е. команды обновляют дерево бизнес-объектов, что приводит к соответствующему обновлению пользовательского интерфейса).

person ChrisW    schedule 06.02.2009

Часть ключа к шаблону публикации/подписки для обновлений заключается в том, как обернуть информацию о том, что делать, когда срабатывает событие.

Когда объект, представляющий «Магазин X», обновляется с новым именем и запускает событие, чтобы объявить, что это произошло, какой объект потребляет событие?

Точно так же, когда добавляется город Y, какой объект должен быть уведомлен о создании?

Один из распространенных подходов — иметь какой-то большой класс убер-менеджера, который обрабатывает весь процесс — он подписывается на все события и делает все.

Другой подход, который я использовал с хорошим эффектом, заключается в создании гораздо более простых объектов-оболочек/координаторов, которые обрабатывают только одну часть головоломки. Обычно я добавляю к имени этих классов суффикс «Editor».

Таким образом, у вас может быть класс CityEditor, конструктор которого принимает как объект City, так и TreeNode, представляющий этот объект. CityEditor будет подписываться на события как для объекта City, так и для TreeNode, и позаботится о заполнении TreeNode заголовком и выборе значка.

Когда объект City обновляется, CityEditor реагирует на инициированное событие, обновляя файл TreeNode. Когда объект City удаляется, CityEditor убеждается, что узел удален из Treeview.

Когда новый объект Store добавляется в City, CityEditor может позаботиться о создании StoreEditor для координации обновлений на этом уровне. Точно так же, когда Employee добавляется к Store, экземпляр EmployeeEditor обрабатывает обновления Treeview.

person Bevan    schedule 08.02.2009