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
. Единственный действительно интересный способ этого метода:
Добавление фиктивного узла, когда у экземпляра IHierarchyData
есть дочерние элементы; это делает «+» видимым в древовидной структуре, иначе мы не смогли бы расширяться глубже; и
Добавление вновь добавленного узла в словарь с экземпляром 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 , если вам нужна версия для копирования и вставки.
Веселись!