Тема 3.9. TreeView и DataGrid Оглавление TreeView ....................................................................................................................................................... 1 Пример 1: ................................................................................................................................................. 1 Пример 2: ................................................................................................................................................. 2 Пример 3: Динамическое добавление узлов в дерево при разворачивании узла. .......................... 3 Пример 4: DockPanel в узле TreeView .................................................................................................... 7 DataGrid ........................................................................................................................................................ 7 Пример 5: ................................................................................................................................................. 7 Пример 6: ................................................................................................................................................. 8 Некоторые полезные свойства DataGrid ............................................................................................... 9 Пример 7: ...............................................................................................................................................10 TreeView Данный элемент управления предназначен для древовидного отображения данных в окне приложения. Может содержать как коллекцию элементов TreeViewItem, так и другое содержимое, например, текстовые блоки: Пример 1: <TreeView> <TextBox>Элемент TreeView</TextBox> <TreeViewItem Header="Базы данных"> <TreeViewItem Header="MS SQL Server" /> <TreeViewItem Header="MySQL" /> <TreeViewItem Header="MongoDB" /> <TreeViewItem Header="Postgres" /> </TreeViewItem> <TreeViewItem Header="Языки программирования"> <TreeViewItem Header="C-языки"> <TreeViewItem Header="C#" /> <TreeViewItem Header="C/C++" /> <TreeViewItem Header="Java" /> </TreeViewItem> <TreeViewItem Header="Basic"> <TreeViewItem Header="Visual Basic" /> <TreeViewItem Header="VB.Net" /> <TreeViewItem Header="PureBasic" /> </TreeViewItem> </TreeViewItem> </TreeView> Однако все же лучше обертывать элементы в объекты TreeViewItem. С помощью его свойства Header мы можем установить текстовую метку или заголовок узла дерева. Элемент TreeViewItem предлагает также ряд свойств для управления состоянием: IsExpanded (принимает логическое значение и показывает, раскрыт ли узел) и IsSelected (показывает, выбран ли узел). Чтобы отследить выбор или раскрытие узла, мы можем обработать соответствующие события. Событие Expanded возникает при раскрытии узла, а событие Collapsed, наоборот, при его сворачивании. Выбор узла дерева мы можем обработать с помощью обработки события Selected. Пример 2: <TreeView> <TreeViewItem Header="C-языки" Expanded="TreeViewItem_Expanded"> <TreeViewItem Header="C#" Selected="TreeViewItem_Selected" /> <TreeViewItem Header="C/C++" Selected="TreeViewItem_Selected" /> <TreeViewItem Header="Java" Selected="TreeViewItem_Selected" /> </TreeViewItem> </TreeView> Теперь добавим в файл связанного кода C# обработчики для этих событий: private void TreeViewItem_Expanded(object sender, RoutedEventArgs e) { TreeViewItem tvItem = (TreeViewItem)sender; MessageBox.Show("Узел " + tvItem.Header.ToString() + " раскрыт"); } private void TreeViewItem_Selected(object sender, RoutedEventArgs e) { TreeViewItem tvItem = (TreeViewItem)sender; MessageBox.Show("Выбран узел: " + tvItem.Header.ToString()); } Элементы управления TreeView часто применяются для размещения больших объемов данных. Объясняется это тем, что TreeView обладает сворачиваеморазворачиваемой структурой. Даже в случае прокрутки TreeView пользователем сверху донизу, видимой не обязательно будет вся доступная в нем информация. Информация, не являющаяся видимой, может вообще пропускаться в элементе управления TreeView, сокращая накладные расходы (и время, необходимое для его заполнения). Даже еще лучше то, что при открытии элемента TreeViewItem инициируется событие Expanded, а при закрытии — событие Collapsed. Этот момент очень удобно использовать для добавления недостающих узлов или удаления тех, что уже больше не нужны. Такой подход называется оперативным (just-in-time) созданием узлов. Оперативное создание узлов может применяться в приложениях, в которых данные извлекаются из базы, но классическим примером, несомненно, является приложение, предназначенное для просмотра каталогов. В настоящее время большинство жестких дисков имеют огромную емкость и постоянно разрастающуюся структуру каталогов. Хотя элемент управления TreeView и можно заполнить структурой каталогов жесткого диска, этот процесс является удручающе медленным. Гораздо лучше, когда сначала отображается частично свернутое представление, и пользователю предлагается самостоятельно добираться до конкретных каталогов. При разворачивании каждого узла в дерево добавляются соответствующие подкаталоги, и протекает этот процесс практически мгновенно. Пример 3: Динамическое разворачивании узла. добавление узлов в дерево при Сначала добавляется в TreeView список дисков при первой загрузке окна. Изначально узел для каждого диска представляется в свернутом виде. Буква диска отображается в заголовке, а объект DriveInfo хранится в свойстве TreeViewItem.Tag для упрощения поиска вложенных каталогов в дальнейшем без воссоздания этого объекта. (Это увеличивает накладные расходы приложения, связанные с памятью, но при этом сокращает количество проверок безопасности доступа к файлам. Общий эффект является незначительным, но зато немного улучшает производительность и упрощает код.) Ниже приведен код, в котором TreeView заполняется списком дисков с помощью класса System.IO.DriveInfo: Данный код добавляет под узлом каждого диска указатель места заполнения (строку со звездочкой). Этот указатель не отображается, поскольку узел сначала находится в свернутом состоянии. При разворачивании узла этот указатель можно удалить и добавить на его месте список подкаталогов. Указатель места заполнения представляет собой удобный инструмент, который можно использовать для определения того, развернул ли уже пользователь данную папку для просмотра ее содержимого. Однако его главной обязанностью является отображение рядом с каждым элементом специального значка, указывающего на возможность разворачивания. Без него пользователь просто не сможет разворачивать каталог и отображать содержащиеся в нем подпапки. Если в каталоге нет никаких подпапок, этот значок будет просто исчезать при попытке пользователя развернуть его, что похоже на поведение проводника Windows при просмотре сетевых папок. Для реализации оперативного создания узлов необходимо обрабатывать событие TreeViewItem.Expanded. Поскольку это событие поддерживает пузырьковое распространение, обработчик событий можно присоединять прямо к элементу TreeView, чтобы он обрабатывал событие Expanded любого находящегося внутри него элемента TreeViewItem. Код Xaml Обработчик события раскрытия узла (разместить после public MainWindow() , полн ый код приложения смотрите ниже в приложении) Пример 4: DockPanel в узле TreeView <TreeView> <TreeViewItem Header="Animals"> <TreeViewItem.Items> <DockPanel> <Image Source="data\fish.png"/> <TextBlock Margin="5" Foreground="Brown" FontSize="12">Fish</TextBlock> </DockPanel> <DockPanel> <Image Source="data\dog.png"/> <TextBlock Margin="5" Foreground="Brown" FontSize="12">Dog</TextBlock> </DockPanel> <DockPanel> <Image Source="data\cat.png"/> <TextBlock Margin="5" Foreground="Brown" FontSize="12">Cat</TextBlock> </DockPanel> </TreeViewItem.Items> </TreeViewItem> </TreeView> DataGrid DataGrid во многом похож на ListView, но более сложный по характеру и допускает редактирование содержимого таблицы. Пример 5: Создадим класс Phone: public class Phone { public string Title { get; set; } public string Company { get; set; } public int Price { get; set; } } Теперь же выведем объекты в таблицу DataGrid. Чтобы DataGrid автоматически разбивал таблицу на столбцы, установим свойство AutoGenerateColumns="True": <Window x:Class="WpfApp5.MainWindow" xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation" xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" xmlns:d="http://schemas.microsoft.com/expression/blend/2008" xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006" xmlns:local="clr-namespace:WpfApp5" xmlns:col="clr-namespace:System.Collections;assembly=mscorlib" mc:Ignorable="d" Title="MainWindow" Height="450" Width="800"> <Grid Background="Lavender"> <DataGrid x:Name="phonesGrid" AutoGenerateColumns="True" ItemsSource="{DynamicResource ResourceKey=phones}"> <DataGrid.Resources> <col:ArrayList x:Key="phones"> <local:Phone Title="iPhone 6S" Company="Apple" Price="54990" /> <local:Phone Title="Lumia 950" Company="Microsoft" Price="39990" /> <local:Phone Title="Nexus 5X" Company="Google" Price="29990" /> </col:ArrayList> </DataGrid.Resources> </DataGrid> </Grid> </Window> В данном случае префикс local ссылается на пространство имен текущего проекта, в котором определен класс Phone (xmlns:local="clr-namespace:Controls"), а col - префиксссылка на пространство имен System.Collections (xmlns:col="clr- namespace:System.Collections;assembly=mscorlib"). И это даст в итоге следующий вывод: Пример 6: Программная установка источника для DataGrid: List<Phone> phonesList = new List<Phone> { new Phone { Title="iPhone 6S", Company="Apple", Price=54990 }, new Phone {Title="Lumia 950", Company="Microsoft", Price=39990 }, new Phone {Title="Nexus 5X", Company="Google", Price=29990 } }; phonesGrid.ItemsSource = phonesList; При этом в xaml останется только <DataGrid x:Name="phonesGrid" AutoGenerateColumns="True"> </DataGrid> Некоторые полезные свойства DataGrid RowBackground и Устанавливают фон строки. Если установлены оба свойства, цветовой фон AlternatingRowBackground чередуется: RowBackground - для нечетных строк и AlternatingRowBackground - для четных ColumnHeaderHeight Устанавливает высоту строки названий столбцов. ColumnWidth Устанавливает ширину столбцов. RowHeight Устанавливает высоту строк. GridLinesVisibility Устанавливает видимость линий, разделяющих столбцы и строки. Имеет четыре значения - All видны все линии, Horizontal - видны только горизонтальные линии, Vertical - видны только вертикальные линии, None - линии отсутствуют HeadersVisibility Задает видимость заголовков HorizontalGridLinesBrush и Задает цвет горизонтальных и вертикальных линий соответственно VerticalGridLinesBrush Хотя предыдущий пример довольно прост, в нем есть несколько недочетов. Во-первых, у нас нет возможности повлиять на расстановку столбцов. Во-вторых, заголовки определены по названиям свойств, которые на английском языке, а хотелось бы на русском. В этом случае мы должны определить свойства отображения столбцов сами. Для этого надо воспользоваться свойством DataGrid.Columns и определить коллекцию столбцов для отображения в таблице. Причем можно задать также и другой тип столбца, отличный от текстового. DataGrid поддерживает следующие варианты столбцов: DataGridTextColumn Отображает элемент TextBlock или TextBox при редактировании DataGridHyperlinkColumn Представляет гиперссылку и позволяет переходить по указанному адресу DataGridCheckBoxColumn Отображает элемент CheckBox DataGridComboBoxColumn Отображает выпадающий список - элемент ComboBox DataGridTemplateColumn Позволяет задать специфичный шаблон для отображения столбца Пример 7: Перепишем предыдущий пример с учетом новой информации: <DataGrid x:Name="phonesGrid" AutoGenerateColumns="False" HorizontalGridLinesBrush="DarkGray" RowBackground="LightGray" AlternatingRowBackground="White"> <DataGrid.Items> <local:Phone Title="iPhone 6S" Company="Apple" Price="54990" /> <local:Phone Title="Lumia 950" Company="Microsoft" Price="39990" /> <local:Phone Title="Nexus 5X" Company="Google" Price="29990" /> </DataGrid.Items> <DataGrid.Columns> <DataGridTextColumn Header="Модель" Binding="{Binding Path=Title}" Width="90" /> <DataGridHyperlinkColumn Header="Компания" Binding="{Binding Path=Company}" Width="80" /> <DataGridTextColumn Header="Цена" Binding="{Binding Path=Price}" Width="50" /> </DataGrid.Columns> </DataGrid> Среди свойств DataGrid одним из самых интересных является RowDetailsTemplate. Оно позволяет задать шаблон отображения дополнительной информации касательно данной строки. Измени элемент DataGrid: <DataGrid x:Name="phonesGrid" AutoGenerateColumns="False" HorizontalGridLinesBrush="DarkGray" RowBackground="LightGray" AlternatingRowBackground="White"> <DataGrid.Items> <local:Phone Title="iPhone 6S" Company="Apple" Price="54990" /> <local:Phone Title="Lumia 950" Company="Microsoft" Price="39990" /> <local:Phone Title="Nexus 5X" Company="Google" Price="29990" /> </DataGrid.Items> <DataGrid.Columns> <DataGridTextColumn Header="Модель" Binding="{Binding Path=Title}" Width="90" /> <DataGridHyperlinkColumn Header="Компания" Binding="{Binding Path=Company}" Width="80" /> <DataGridTextColumn Header="Цена" Binding="{Binding Path=Price}" Width="50" /> </DataGrid.Columns> <DataGrid.RowDetailsTemplate> <DataTemplate> <StackPanel Orientation="Horizontal"> <TextBlock Text="{Binding Path=Price}" /> <TextBlock Text=" рублей по скидке" /> </StackPanel> </DataTemplate> </DataGrid.RowDetailsTemplate> </DataGrid> При щелчке по строке появляется дополнительная строка: Приложение: Код раскрытия узла списка из примера 3 using using using using using using using using using using using using using using using System; System.Collections.Generic; System.Linq; System.Text; System.Threading.Tasks; System.Windows; System.Windows.Controls; System.Windows.Data; System.Windows.Documents; System.Windows.Input; System.Windows.Media; System.Windows.Media.Imaging; System.Windows.Navigation; System.Windows.Shapes; System.IO; namespace WpfApp6 { public partial class MainWindow : Window { public MainWindow() { InitializeComponent(); foreach (DriveInfo drive in DriveInfo.GetDrives()) { TreeViewItem item = new TreeViewItem(); item.Tag = drive; item.Header = drive.ToString(); item.Items.Add("*"); trw_Products.Items.Add(item); } } private void trw_Products_Expanded(object sender, RoutedEventArgs e) { TreeViewItem item = (TreeViewItem)e.OriginalSource; item.Items.Clear(); DirectoryInfo dir; if (item.Tag is DriveInfo) { DriveInfo drive = (DriveInfo)item.Tag; dir = drive.RootDirectory; } else dir = (DirectoryInfo)item.Tag; try { foreach (DirectoryInfo subDir in dir.GetDirectories()) { TreeViewItem newItem = new TreeViewItem(); newItem.Tag = subDir; newItem.Header = subDir.ToString(); newItem.Items.Add("*"); item.Items.Add(newItem); } } catch { } } } }