Управление контролами в ItemsControl (часть 1 - с помощью VisualTreeHelper)
Сегодня мы поговорим об ItemsControl, DataBinding, VisualTreeHelper. Рано или поздно вы начнете создавать более или менее серьезные приложения, которые будут решать конкретные задачи. И конечно же современным приложениям не обойтись без базы данных, а нам как программистам не обойтись без средств доступа к данным и их вывода.
Естественно существуют контролы, которые позволяют нам выводить повторяющиеся структуры, например GridView или ListBox. Первый все таки подходит для табличных данных, текстовой информации, да и дизайн мы особо то изменить не можем, я имею ввиду места вывода данных. ListBox более гибче, и он в принципе нам подходит, единственный недостаток его стандартный темплейт. Хотя мы и можем поменять темплейт и заставить его работать, как это нам надо, задача у нас сейчас состоит немного в другом.
Мы будем использовать ItemsControl он удобен для вывода любых данных с помощью DataBinding и позволяет нам менять шаблон, а в шаблоне по умолчанию содержится только StackPanel.
И так приступим. Кто не читал статью про DataBinding, советую для начала прочесть ее, чтобы вам было более понятно, что мы будем делать.
1) Создаем веб – сервис
2) Создаем наш класс, в нашем случае Film
3) Создаем метод которые вернет список наших Film
4) Получаем данные на стороне Silverlight
5) Биндим их к нашему ItemControls
Создайте проект. Затем в веб проекте добавляем веб сервис и у него создаем класс Film и метод отдающий список Film.
Подключаем Service Reference в проекте Silverlight.
Код нашего Веб сервиса:
using System;
using System.Collections.Generic;
using System.Linq;
using System.Web;
using System.Web.Services;
namespace DataBindingExamples.Web
{
[WebService(Namespace = "http://tempuri.org/")]
[WebServiceBinding(ConformsTo = WsiProfiles.BasicProfile1_1)]
[System.ComponentModel.ToolboxItem(false)]
public class MyWebService : System.Web.Services.WebService
{
[WebMethod]
public string HelloWorld()
{
return "Hello World";
}
[WebMethod]
public List GetFimls ()
{
List list = new List();
for (var i = 0; i < 10; i++)
{
Film film = new Film();
film.Id = i;
film.Title = String.Format("Film Title {0} ", i);
film.FileSize = String.Format("{0}", 100*i);
film.Time = i*200;
film.Image = String.Format("http://localhost/silverlight/{0}.jpg", i);
list.Add(film);
}
return list;
}
}
public class Film
{
public int Id { get; set; }
public string Title { get; set; }
public int Time { get; set; }
public string FileSize { get; set; }
public string Image { get; set; }
}
}
Теперь нам нужен контрол, который будет отображать наши фильмы. Создаем ItemsControl и внутри прописываем DataBinding для всех полей нашего класса.
Код XAML:
<UserControl x:Class="DataBindingExamples.Page"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
Width="600" Height="800">
<Grid x:Name="LayoutRoot" Background="White">
<ItemsControl x:Name="FilmList">
<ItemsControl.ItemTemplate>
<DataTemplate>
<StackPanel Orientation="Horizontal" Background="Bisque" Margin="0,5,0,5">
<Image Source="{Binding Image}" Width="30" Height="30" Margin="5" />
<TextBlock Text="{Binding Title}" Margin="5" />
<TextBlock Text="{Binding Time}" Margin="5" />
<TextBlock Text="{Binding FileSize}" Margin="5" />
</StackPanel>
</DataTemplate>
</ItemsControl.ItemTemplate>
</ItemsControl>
</Grid>
</UserControl>
Все очень просто теперь в CodeBehind добавлеем полученные данные от WebService в наш ItemsCotrol.
Код C#
using System;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Net;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Documents;
using System.Windows.Input;
using System.Windows.Media;
using System.Windows.Media.Animation;
using System.Windows.Shapes;
using DataBindingExamples.WebSrv;
namespace DataBindingExamples
{
public partial class Page : UserControl
{
public Page()
{
InitializeComponent();
var connector = new MyWebServiceSoapClient();
connector.GetFimlsCompleted += connector_GetFimlsCompleted;
connector.GetFimlsAsync();
}
void connector_GetFimlsCompleted(object sender, GetFimlsCompletedEventArgs e)
{
if (e.Error != null)
return;
if (e.Result.Count > 0)
{
List list = new List(e.Result);
FilmList.ItemsSource = list;
}
}
}
}
Посмотрим на результат. Все отлично. Выводиться как мы хотели. Но есть один очень большой недостаток. Который мне очень долго не удавлось исправить. У нас нет никакого события, которые позволяет, что то делать с элементами, которые генерируются, на основе нашего List.
Например, у нас стоит задача выделять какой то элемент при клике на него. Мы это можем сделать, повесив обработчик OnClick на наш StackPanel. Вот так:
Код XAML:
<StackPanel Orientation="Horizontal" Background="Bisque" Margin="0,5,0,5" MouseLeftButtonDown="StackPanel_MouseLeftButtonDown" >
<Image Source="{Binding Image}" Width="30" Height="30" Margin="5" />
<TextBlock Text="{Binding Title}" Margin="5" />
<TextBlock Text="{Binding Time}" Margin="5" />
<TextBlock Text="{Binding FileSize}" Margin="5" />
</StackPanel>
Обрабатываем событие:
Код C#:
private void StackPanel_MouseLeftButtonDown(object sender, MouseButtonEventArgs e)
{
//для примера мы меняем его фон
((StackPanel)sender).Background = new SolidColorBrush(Colors.Red);
}
Но если мы кликнем на другой элемент, мы поменяем его фон, а вот предыдущий у нас тоже останется выделенным, то есть мы не имеем к нему доступ. То есть мы не может получить доступ к коллекции элементов ItemsControl.
Конечно, это создает очень много неудобств и ограничение функционала. Изучение справки и помощь моего друга, помогло нам найти решение. VisualTreeHelper класс помогающий нам проходить по коллекции элементов контрола с помощью его методов.
Для начала немного объясню, что у нас получается или точнее во что у нас генерируется ItemsControl. После того, как мы присвоили свойству ItemSource наш List мы получаем пример такую вот структуру.
<!-- Корневой элемент представления контента ItemsControl --> <ItemsPresenter> <!-- StackPanek по умолчанию, про которую я говорил содержит коллекцию элементов ItemsControl --> <StackPanel> <ContentPresenter> <!-- Наша текущая панель которую нам надо получить --> <StackPanel> <Image /> <TextBlock /> <TextBlock /> <TextBlock /> </StackPanel> </ContentPresenter> <ContentPresenter> <StackPanel> <Image /> <TextBlock /> <TextBlock /> <TextBlock /> </StackPanel> </ContentPresenter> <ContentPresenter> <StackPanel> <Image /> <TextBlock /> <TextBlock /> <TextBlock /> </StackPanel> </ContentPresenter> </StackPanel> </ItemsPresenter>
И так нам нужно достучаться до внутренних элементов и делаем мы это вот так:
using System;
using System.Collections.Generic;
using System.Linq;
using System.Net;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Documents;
using System.Windows.Input;
using System.Windows.Media;
using System.Windows.Media.Animation;
using System.Windows.Shapes;
using DataBindingExamples.WebSrv;
namespace DataBindingExamples
{
public partial class Page : UserControl
{
public Page()
{
InitializeComponent();
var connector = new MyWebServiceSoapClient();
connector.GetFimlsCompleted += connector_GetFimlsCompleted;
connector.GetFimlsAsync();
}
void connector_GetFimlsCompleted(object sender, GetFimlsCompletedEventArgs e)
{
if (e.Error != null)
return;
if (e.Result.Count > 0)
{
List list = new List(e.Result);
FilmList.ItemsSource = list;
}
}
private void StackPanel_MouseLeftButtonDown(object sender, MouseButtonEventArgs e)
{
//Достаем ItemsPresenter для Нашего ItemsControl
ItemsPresenter itemsPresenter = (ItemsPresenter)VisualTreeHelper.GetChild(FilmList, 0);
//Достаем StackPanel для Нашего ItemsControl
StackPanel rootStackPanel = (StackPanel)VisualTreeHelper.GetChild(itemsPresenter, 0);
//бежим по коллекции ContentPresenter которые у нас генерируются в ItemsControl
foreach (ContentPresenter current in rootStackPanel.Children)
{
//Наконец то получаем нужную нам StackPanel проверяем является ли она текущей, и выделяем ее
StackPanel currentStackPanel = (StackPanel)VisualTreeHelper.GetChild(current, 0);
if (currentStackPanel == (StackPanel)sender)
currentStackPanel.Background = new SolidColorBrush(Colors.Red);
else
currentStackPanel.Background = new SolidColorBrush(Colors.Magenta);
}
}
}
}
Вот что у нас получается, то есть мы можем теперь делать, что хотим с коллекцией элементов генерируемых в ItemsControl.
Достоинва:
Нет ни каких ограничений с доступом к любым элементам.
Недостатки:
Метод получения элементов не совсем очевиден. Громоздкий способ их получения, тем более, если внутренний элмент сложный. Придется переделывать весь доступ, если мы измени порядок или набор элементов.
PS – статья может показаться не совсем понятной некоторым посетителям из-за ее сложности. Постараюсь ответить на все вопросы.
Чтобы у вас отображались картинки. Создайте в IIS виртуальный каталог с именем silverlight, если у вас не стоит IIS (а он у вас должен стоять!), то воспользуйтесь другим вариантом. Положите файлы в корень проекта silverlight. Назовите все файлы соотвественно: 1.jpg, 2.jpg .. и т.д.
Popularity: 90% [?]





Декабрь 8th, 2008 at 17:07
[...] Управление контролами в ItemsControl (часть 1 - с помощью VisualTr… [...]
Декабрь 11th, 2008 at 15:00
[...] Управление контролами в ItemsControl (часть 1 - с помощью VisualTr… Posted ноя 29 2008, 12:36 by СильверРобот Помечено как: Блог, [...]
Март 15th, 2009 at 11:00
Цитата: “После того, как мы присвоили свойству ItemSource наш List мы получаем пример такую вот структуру.”
Интересует как я могу увидеть то же самое для своего ItemSource? Как увидеть во что превратиться мой шаблон DataTemplate?