Версия для печати
Нажмите сюда для просмотра этой темы в оригинальном формате
Форум на Исходниках.RU > .NET FAQ > Как в WPF ListView можно обработать двойной клик ListViewItem'а


Автор: Pit-Bul 10.06.09, 20:42
Описание начну с количества лирики. Многие только увидев WPF говорят что он очень беден в отношении функционала стандартных контролов, если чесно то по началу и я так думал (хотя может быть только я один так думал :whistle: ). Такие мысли возникали когда приделывал ListView сортировки, выравнивание строк в ячейках и несколько других функций которыми не обладал стандартный ListView, но проделав все это понимаешь что на самом деле это не есть недостаток, а даже совсем наоборот. Т.е. я хочу сказать что весь функционал в итоге можно сделать так как ты себе его представляешь, а не пытаться переделывать то что кто то сделал ранее, как это было в WindowsForms и раньше. Ну да ладно, приступим к самой сути которуя я собираюсь описать в этой статье.

Оговорюсь сразу, все подходы будут расматриваться относительно модификации паттерна MVC которую вы могли видеть в предыдущей моей статье и которая успешно применяется в проекте разрабатываемом на фирме в которой я тружусь. Так вот, до недавнего времени для обработки двойного клика у ListViewItem применялась следующая нехитрая конструкция:

простой пример XAML разметки для списка

<{CODE_COLLAPSE_OFF}><{CODE_WRAP_OFF}>
            <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.


<{CODE_COLLAPSE_OFF}><{CODE_WRAP_OFF}>
            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 сделаю набольшое отступление. Рассмотрим следующий код

<{CODE_COLLAPSE_OFF}><{CODE_WRAP_OFF}>
            <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

<{CODE_COLLAPSE_OFF}><{CODE_WRAP_OFF}>
    <Style TargetType="ListViewItem">
                <EventSetter Event="MouseDoubleClick" Handler="ListViewItem_MouseDoubleClick"/>
    </Style>


Наткнувшись на него я было обрадовался простоте решения, но немного подумал и пришол к выводу что данный вариант почти ничем не отличается от моего первого решения. По сути он так же перенаправляет ListView_MouseDoubleClick на ListViewItem_MouseDoubleClick. И второе самое главное что не нравилось мне в обоих случаях это невозможность использовать команды. Продолжив поиски решения на просторах гугла я не нашол больше ниодного более или менее интересного и тем более работающего примера. Но не опускать же руки. Так вот в очередной раз пришлось как говориться пораскинуть мозгами и вот к чему я пришол. Первое что пришло в голову это то что почему бы не научить ListViewItem принимать вызовы команд самому не ожидая тычка от родителя. Нет ничего проще скажете вы, отнаследуемся от ListViewItem и вперед, но данный подход меня не устраивает, особенно учитывая то что в последнее время мне попалось несколько статей в которых наследование не особо поощрялось и говорилось о том что оно таит в себе больше зла чем добра, углубляться в эту тему не буду, а просто отмажусь фразой что мода на наследование прошла. Но какая альтернатива, а вот какая, основная мощь WPF это всякого рода темплейты, почему бы не применить их. И вот что у меня получилось.

<{CODE_COLLAPSE_OFF}><{CODE_WRAP_OFF}>
                <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 главной загвоздкой является отсутсвие фантазии........

Powered by Invision Power Board (https://www.invisionboard.com)
© Invision Power Services (https://www.invisionpower.com)