Описание начну с количества лирики. Многие только увидев WPF говорят что он очень беден в отношении функционала стандартных контролов, если честно то по началу и я так думал (хотя может быть только я один так думал :). Такие мысли возникали когда приделывал ListView сортировки, выравнивание строк в ячейках и несколько других функций которыми не обладал стандартный ListView, но проделав все это понимаешь что на самом деле это не есть недостаток, а даже совсем наоборот. Т.е. я хочу сказать что весь функционал в итоге можно сделать так как ты себе его представляешь, а не пытаться переделывать то что кто то сделал ранее, как это было в WindowsForms и раньше. Ну да ладно, приступим к самой сути которую я собираюсь описать в этой статье.
Оговорюсь сразу, все подходы будут рассматриваться относительно модификации паттерна MVC которую вы могли видеть в предыдущей моей статье и которая успешно применяется в проекте разрабатываемом на фирме в которой я тружусь. Так вот, до недавнего времени для обработки двойного клика у ListViewItem применялась следующая нехитрая конструкция:
простой пример XAML разметки для списка
<ListView x:Name='lv' Grid.Row="2" ItemsSource="{Binding Data}" SelectedValue="{Binding SelectedItem}" MouseDoubleClick="ListView_MouseDoubleClick"> <ListView.View> <GridView AllowsColumnReorder="true"> <GridViewColumn DisplayMemberBinding="{Binding Path=Id}" Width="50" Header="Код"/> //..................................... <GridViewColumn DisplayMemberBinding="{Binding Path=Notes}" Width="150" Header="Примечание"/> </GridView> </ListView.View> </ListView>
ну и естественно обработчик двойного клика MouseDoubleClick=«ListView_MouseDoubleClick». Но что здесь не так, а то что данный клик обрабатывается для всего ListView независимо от того где был он сделан. Дополнительная проблема открывается если мы имеем два списка, то есть один в ListViewItem.Template другого, суть проблемы вы увидите в том какой код нужен для того чтобы вычленить именно клик ListViewItem.
private void ListView_MouseDoubleClick(object sender, MouseButtonEventArgs e) { var dep = (DependencyObject)e.OriginalSource; while (dep != null) { dep = VisualTreeHelper.GetParent(dep); if (dep is ListViewItem) { Presenter.EditDoc(); return; } } }
Как видите нам приходится идти по всему визуальному дереву пока мы не определим нужный элемент. Для тех кто не понимает почему нельзя просто привязать событие к ListViewItem сделаю набольшое отступление. Рассмотрим следующий код:
<ListView x:Name='lv' Grid.Row="2" ItemsSource="{Binding Data}" SelectedValue="{Binding SelectedItem}"> <ListViewItem MouseDoubleClick="ListView_MouseDoubleClick"/> <ListView.View> <GridView AllowsColumnReorder="true"> <GridViewColumn DisplayMemberBinding="{Binding Path=Id}" Width="50" Header="Код"/> //..................................... <GridViewColumn DisplayMemberBinding="{Binding Path=Notes}" Width="150" Header="Примечание"/> </GridView> </ListView.View> </ListView>
В данном случае мы получим ошибку гласящую примерно о следующем, «чтобы назначить ItemsSource список ListViewItems должен быть пуст», ну или что то вроде этого. Как же обойти данную проблему. Так вот, немного погуглив я наткнулся на следующее решение на сайте Microsoft:
<Style TargetType="ListViewItem"> <EventSetter Event="MouseDoubleClick" Handler="ListViewItem_MouseDoubleClick"/> </Style>
Наткнувшись на него я было обрадовался простоте решения, но немного подумал и пришёл к выводу что данный вариант почти ничем не отличается от моего первого решения. По сути он так же перенаправляет ListView_MouseDoubleClick на ListViewItem_MouseDoubleClick. И второе самое главное что не нравилось мне в обоих случаях это невозможность использовать команды. Продолжив поиски решения на просторах гугла я не нашёл больше ни одного более или менее интересного и тем более работающего примера. Но не опускать же руки. Так вот в очередной раз пришлось как говориться пораскинуть мозгами и вот к чему я пришёл. Первое что пришло в голову это то что почему бы не научить ListViewItem принимать вызовы команд самому не ожидая тычка от родителя. Нет ничего проще скажете вы, отнаследуемся от ListViewItem и вперед, но данный подход меня не устраивает, особенно учитывая то что в последнее время мне попалось несколько статей в которых наследование не особо поощрялось и говорилось о том что оно таит в себе больше зла чем добра, углубляться в эту тему не буду, а просто отмажусь фразой что мода на наследование прошла. Но какая альтернатива, а вот какая, основная мощь WPF это всякого рода темплейты, почему бы не применить их. И вот что у меня получилось.
<Grid> <Grid.Resources> <ControlTemplate x:Key="xTmpl" TargetType='{x:Type ListViewItem}'> <StackPanel> <StackPanel.InputBindings> <MouseBinding Command="ApplicationCommands.Open" MouseAction="LeftDoubleClick"/> </StackPanel.InputBindings> <GridViewRowPresenter Content="{TemplateBinding Content}" Columns="{TemplateBinding GridView.ColumnCollection}"/> </StackPanel> </ControlTemplate> <ControlTemplate x:Key="Selected" TargetType='{x:Type ListViewItem}'> <StackPanel Background="{DynamicResource {x:Static SystemColors.HighlightBrushKey}}"> <StackPanel.InputBindings> <MouseBinding Command="ApplicationCommands.Open" MouseAction="LeftDoubleClick"/> </StackPanel.InputBindings> <GridViewRowPresenter Content="{TemplateBinding Content}" Columns="{TemplateBinding GridView.ColumnCollection}" TextBlock.Foreground="{DynamicResource {x:Static SystemColors.HighlightTextBrushKey}}"/> </StackPanel> </ControlTemplate> </Grid.Resources> <ListView ItemsSource="{Binding }" SelectedValue="{Binding }" > <ListView.ItemContainerStyle> <Style TargetType="{x:Type ListViewItem}" > <Setter Property="Template" Value="{StaticResource xTmpl}"/> <Style.Triggers> <Trigger Property="IsSelected" Value="True"> <Setter Property="Template" Value="{StaticResource Selected}"/> </Trigger> </Style.Triggers> </Style> </ListView.ItemContainerStyle> <ListView.View> <GridView AllowsColumnReorder="True"> //................................ </GridView> </ListView.View> </ListView> </Grid>
Думаю суть предельна ясна, в ControlTemplate итема кладём контейнер который принимает на себя клики и их же мы ловим и обрабатываем. Пару слов о том, зачем я сделал два ControlTemplate, ответ прост, один для ListViewItem в обычном состоянии, а второй для выбранного ListViewItem. Вот и все, надеюсь что эта статья помогла вам и показала что в WPF главной загвоздкой является отсутствие фантазии…