В настоящее время я знаю, как сделать ленивую реализацию процедуры загрузки узлов в управлении treeview и считать связанные вопросы в stackoverflow, но я также читаю об интерфейсах IHierarchyData и IHierarchicalEnumerable в asp.net (я не знал для кодирования asp.net), которые позволяют связывать набор с treeview для отображения объектов автоматически.
Это хотело бы знать, могу ли я сделать то же в winforms и C#. Я думаю, что интерфейсы, предыдущие упомянутый, не доступны в winforms.
Спасибо.
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
. Теперь мы можем обойти это, добавив его в свойство Tag
в TreeNode
, но давайте постараемся сделать этот компонент как можно более неинвазивным и отслеживать его собственное состояние. Следовательно, мы будем использовать словарь. Добавьте это поле в класс:
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);
}
}
}
Легко.У нас есть свойство 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 , если вам нужна версия для копирования и вставки.
Удачи!
Интерфейсы доступны, но вам потребуется добавить ссылку на System.Web.UI. (Также может потребоваться, чтобы вы использовали полный распространяемый компонент .NET Framework, а не профиль клиента, хотя я не уверен в этом.)
Более важный вопрос: понимает ли элемент управления WinForms TreeView автоматически, как работать с этими интерфейсы? Я считаю, что ответ на этот вопрос - «Нет», но вам нужно будет проверить / подтвердить это.
Есть интересная статья здесь, которая показывает, как создавать методы расширения для достижения того, что, я думаю, вы ищете. Насколько я могу судить, в System.Windows.Forms.TreeView нет возможности привязки к коллекции.
Вы можете включить System.Web.UI в свой проект, чтобы сделать доступными интерфейсы IHierarchyData и IHierarchicalEnumerable, но TreeView не сможет привязаться к ним без методов расширения.
Пример исходного кода с веб-сайта позволит вам привязать любую коллекцию IDictionary к TreeView.