Привязать команду к элементу ContextMenu

Я получаю эту ошибку/предупреждение:

System.Windows.Data Error: 4 : Cannot find source for binding with reference 'ElementName=DrivesListView'

Когда я нажимаю «Обновить», команда не срабатывает. Я предполагаю, что поскольку это ContextMenu, мне нужно каким-то образом получить доступ к родительскому элементу управления в пути привязки, а затем я могу использовать MouseDownCommand.

MouseDownCommand находится в моей вкладке viewmodel, TabItemViewModel. Мой MainWindowViewModel содержит список TabItemViewModel, и это источник элементов TabControl.

Что я пробовал:

1)

Я попытался настроить событие ContextMenu Opened, чтобы вручную установить DataContext, чтобы посмотреть, исправит ли он как-то DataContext:

private void ContextMenu_Opened(object sender, RoutedEventArgs e)
{
    ContextMenu menu = sender as ContextMenu;
    ListView listView = menu.PlacementTarget as ListView;
    Grid grid = listView.Parent as Grid;
    TabControl tabControl = grid.Parent as TabControl;
    menu.DataContext = (TabItemViewModel)tabControl.SelectedItem;
}

Проблема в том, что я не могу получить tabControl из grid. Выполнение .Parent просто возвращает null по какой-то неизвестной причине.

2)

Я также попытался установить Tag элемента управления, что тоже не сработало:

<ListView Grid.Row="1" Name="DrivesListView" ItemsSource="{Binding Drives}"
                                  Tag="{Binding DataContext,RelativeSource={RelativeSource Mode=FindAncestor,AncestorType=Window}}">>
    <ListView.ContextMenu>
        <ContextMenu>
            <MenuItem Header="Refresh">
                <i:Interaction.Triggers>
                    <i:EventTrigger EventName="MouseDown">
                        <i:InvokeCommandAction 
                                                    Command="{Binding Path=PlacementTarget.Tag.MouseDownCommand, RelativeSource={RelativeSource Mode=FindAncestor, AncestorType=ContextMenu}}" 
                                                       CommandParameter="{Binding ElementName=DrivesListView}"/>
                    </i:EventTrigger>
                </i:Interaction.Triggers>
            </MenuItem>
        </ContextMenu>
    </ListView.ContextMenu>
    <ListView.Style>
        <Style TargetType="{x:Type ListView}" BasedOn="{StaticResource MahApps.Styles.ListView}">
            <Style.Triggers>
                <DataTrigger Binding="{Binding DrivesListViewEnabled}" Value="true">
                    <Setter Property="Visibility" Value="Visible" />
                </DataTrigger>
                <DataTrigger Binding="{Binding DrivesListViewEnabled}" Value="false">
                    <Setter Property="Visibility" Value="Hidden" />
                </DataTrigger>
            </Style.Triggers>
        </Style>
    </ListView.Style>
    <i:Interaction.Triggers>
        <i:EventTrigger EventName="MouseDoubleClick">
            <i:InvokeCommandAction Command="{Binding Path=MouseDoubleClickCommand}" 
                                                       CommandParameter="{Binding ElementName=DrivesListView}"/>
        </i:EventTrigger>
    </i:Interaction.Triggers>
    <ListView.View>
        <GridView>
            <GridViewColumn x:Name="NameHeader" Header="Name" DisplayMemberBinding="{Binding Name}"/>
            <GridViewColumn x:Name="TypeHeader" Header="Type" DisplayMemberBinding="{Binding Type}"/>
            <GridViewColumn x:Name="TotalSizeHeader" Header="Total Size" DisplayMemberBinding="{Binding TotalSize}"/>
            <GridViewColumn x:Name="FreeSpaceHeader" Header="Free Space" DisplayMemberBinding="{Binding FreeSpace}"/>
        </GridView>
    </ListView.View>
</ListView>

Вот мой XAML

<UserControl x:Class="FileExplorerModuleServer.Views.FileBrowserTabView"
             xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
             xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
             xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006" 
             xmlns:d="http://schemas.microsoft.com/expression/blend/2008" 
             xmlns:i="http://schemas.microsoft.com/expression/2010/interactivity" xmlns:mah="http://metro.mahapps.com/winfx/xaml/controls"
             mc:Ignorable="d" 
             d:DesignHeight="450" d:DesignWidth="800">
    <Grid>
        <Grid.RowDefinitions>
            <RowDefinition Height="Auto"/>
            <RowDefinition Height="*"/>
        </Grid.RowDefinitions>

        <!--ItemsSource is bound to the 'Tabs' property on the view-
                        model, while DisplayMemeberPath tells TabControl 
                        which property on each tab has the tab's name -->
        <TabControl Name="SubTabControl" Grid.Row="1" 
                    ItemsSource="{Binding Tabs}" 
                    DisplayMemberPath="Header" SelectedIndex="{Binding SelectedIndex}">
            <i:Interaction.Triggers>
                <i:EventTrigger EventName="SelectionChanged">
                    <i:InvokeCommandAction Command="{Binding Path=TabChangedCommand}" 
                                                       CommandParameter="{Binding ElementName=SubTabControl}"/>
                </i:EventTrigger>
            </i:Interaction.Triggers>
            <TabControl.ContentTemplate>
                <DataTemplate>
                    <Grid>
                        <Grid.RowDefinitions>
                            <RowDefinition Height="Auto"/>
                            <RowDefinition Height="*"/>
                        </Grid.RowDefinitions>

                        <TextBox Grid.Row="0" HorizontalAlignment="Left" Width="300" Text="{Binding Path}">

                        </TextBox>

                        <mah:ProgressRing Grid.Row="1" Height="1">
                            <mah:ProgressRing.Style>
                                <Style TargetType="{x:Type mah:ProgressRing}">
                                    <Style.Triggers>
                                        <DataTrigger Binding="{Binding IsLoading}" Value="true">
                                            <Setter Property="Visibility" Value="Visible" />
                                        </DataTrigger>
                                        <DataTrigger Binding="{Binding IsLoading}" Value="false">
                                            <Setter Property="Visibility" Value="Hidden" />
                                        </DataTrigger>
                                    </Style.Triggers>
                                </Style>
                            </mah:ProgressRing.Style>
                        </mah:ProgressRing>

                        <ListView Grid.Row="1" Name="DrivesListView" ItemsSource="{Binding Drives}">
                            <ListView.ContextMenu>
                                <ContextMenu Opened="ContextMenu_Opened">
                                    <MenuItem Header="Refresh">
                                        <i:Interaction.Triggers>
                                            <i:EventTrigger EventName="MouseDown">
                                                <i:InvokeCommandAction 
                                                    Command="{Binding Path=MouseDownCommand}" 
                                                       CommandParameter="{Binding ElementName=DrivesListView}"/>
                                            </i:EventTrigger>
                                        </i:Interaction.Triggers>
                                    </MenuItem>
                                </ContextMenu>
                            </ListView.ContextMenu>
                            <ListView.Style>
                                <Style TargetType="{x:Type ListView}" BasedOn="{StaticResource MahApps.Styles.ListView}">
                                    <Style.Triggers>
                                        <DataTrigger Binding="{Binding DrivesListViewEnabled}" Value="true">
                                            <Setter Property="Visibility" Value="Visible" />
                                        </DataTrigger>
                                        <DataTrigger Binding="{Binding DrivesListViewEnabled}" Value="false">
                                            <Setter Property="Visibility" Value="Hidden" />
                                        </DataTrigger>
                                    </Style.Triggers>
                                </Style>
                            </ListView.Style>
                            <i:Interaction.Triggers>
                                <i:EventTrigger EventName="MouseDoubleClick">
                                    <i:InvokeCommandAction Command="{Binding Path=MouseDoubleClickCommand}" 
                                                       CommandParameter="{Binding ElementName=DrivesListView}"/>
                                </i:EventTrigger>
                            </i:Interaction.Triggers>
                            <ListView.View>
                                <GridView>
                                    <GridViewColumn x:Name="NameHeader" Header="Name" DisplayMemberBinding="{Binding Name}"/>
                                    <GridViewColumn x:Name="TypeHeader" Header="Type" DisplayMemberBinding="{Binding Type}"/>
                                    <GridViewColumn x:Name="TotalSizeHeader" Header="Total Size" DisplayMemberBinding="{Binding TotalSize}"/>
                                    <GridViewColumn x:Name="FreeSpaceHeader" Header="Free Space" DisplayMemberBinding="{Binding FreeSpace}"/>
                                </GridView>
                            </ListView.View>
                        </ListView>

Вот ViewModel.cs

public ICommand MouseDownCommand
    => new RelayCommand<object>(e => MouseDown(e));

private void MouseDown(object commandParameter)
{

}

Итак, DataContext в ListView привязан к TabItemViewModel, а Drives является одним из его свойств? Затем вы хотите установить этот ListView в CommandParameter?

emoacht 17.05.2022 10:28

Триггер события SelectionChanged — это беспорядок, просто используйте привязку: SelectedItem="{Binding SelectedTab, Mode=OneWayToSource}" ; затем в виртуальной машине добавьте свойство SelectedTab типа объектов в коллекции Tabs. Затем переместите код TabChangedCommand в установщик свойства SelectedTab.

Orace 17.05.2022 11:34

ListView.Style тоже беспорядок, просто используйте BooleanToVisibilityConverter: Visibility="{Binding DrivesListViewEnabled, Converter={StaticResource BooleanToVisibilityConverter}}"

Orace 17.05.2022 11:43

Я рассмотрю эти два изменения, которые вы предложили. Последнее, однако, требует добавления пользовательского класса, если я не ошибаюсь, и это довольно раздражает, чего я пытался избежать.

Meme Machine 17.05.2022 12:43
Формы c голосовым вводом в React с помощью Speechly
Формы c голосовым вводом в React с помощью Speechly
Пытались ли вы когда-нибудь заполнить веб-форму в области электронной коммерции, которая требует много кликов и выбора? Вас попросят заполнить дату,...
Стилизация и валидация html-формы без использования JavaScript (только HTML/CSS)
Стилизация и валидация html-формы без использования JavaScript (только HTML/CSS)
Будучи разработчиком веб-приложений, легко впасть в заблуждение, считая, что приложение без JavaScript не имеет права на жизнь. Нам становится удобно...
Flatpickr: простой модуль календаря для вашего приложения на React
Flatpickr: простой модуль календаря для вашего приложения на React
Если вы ищете пакет для быстрой интеграции календаря с выбором даты в ваше приложения, то библиотека Flatpickr отлично справится с этой задачей....
В чем разница между Promise и Observable?
В чем разница между Promise и Observable?
Разберитесь в этом вопросе, и вы значительно повысите уровень своей компетенции.
Что такое cURL в PHP? Встроенные функции и пример GET запроса
Что такое cURL в PHP? Встроенные функции и пример GET запроса
Клиент для URL-адресов, cURL, позволяет взаимодействовать с множеством различных серверов по множеству различных протоколов с синтаксисом URL.
Четыре эффективных способа центрирования блочных элементов в CSS
Четыре эффективных способа центрирования блочных элементов в CSS
У каждого из нас бывали случаи, когда нам нужно отцентрировать блочный элемент, но мы не знаем, как это сделать. Даже если мы реализуем какой-то...
0
5
43
2
Перейти к ответу Данный вопрос помечен как решенный

Ответы 2

Во-первых, событие MouseDown не работает в случае с MenuItem. Итак, вам нужно использовать событие PreviewMouseDown или, возможно, Click внутри цепочки. Кроме того, вы не можете использовать ElementName для ссылки на элементы за пределами ContextMenu.

Затем, если предположить, что DataContext из ListView неявно связано с TabItemViewModel, и вы хотите указать, что ListView как CommandParameter, его можно изменить следующим образом:

<MenuItem Header="Refresh">
    <i:Interaction.Triggers>
        <i:EventTrigger EventName="Click">
            <i:InvokeCommandAction
                Command="{Binding PlacementTarget.DataContext.MouseDownCommand, RelativeSource={RelativeSource AncestorType={x:Type ContextMenu}}}"
                CommandParameter="{Binding PlacementTarget, RelativeSource={RelativeSource AncestorType={x:Type ContextMenu}}}"/>
        </i:EventTrigger>
    </i:Interaction.Triggers>
</MenuItem>

Зачем вам использовать триггер, когда свойство Command доступно?

Orace 17.05.2022 11:07

Ты прав. В случае события «Клик» использовать InvokeCommandAction не обязательно.

emoacht 17.05.2022 11:12

Можете ли вы объяснить, как работает ваш CommandParameter? Я использовал смесь обоих ваших ответов, так как @Orace забыл добавить к нему параметр CommandParameter, и ответ из вашего ответа отлично работает. Хотел бы я выбрать оба ваших ответа как правильный. Спасибо!

Meme Machine 17.05.2022 12:57

Привязка для CommandParameter относится к свойству ContextMenu.PlacementTarget, и это свойство предоставляет элемент, к которому прикреплено ContextMenu. В данном случае это ListView. Вот и все.

emoacht 17.05.2022 13:08

@MemeMachine, зачем тебе параметр команды? Чтобы передать ListView ? так вы передаете объект представления в модель представления?! Что вы будете делать с этим?

Orace 17.05.2022 13:52
Ответ принят как подходящий

Как указано в документация, [the] меню [...] зависит от контекста элемента управления.
Другими словами, ContextMenu имеет тот же контекст данных, что и его родительский элемент управления (здесь ListView).
Чтобы следовать шаблону MVVM, вам нужно всего лишь добавить ICommand в контекст данных ListView и привязать его к свойству MenuItem.Command.

Вид:

<ListView ItemsSource="{Binding Drives}">
    <ListView.ContextMenu>
        <ContextMenu>
            <MenuItem Header="Refresh" Command="{Binding RefreshCommand}" />
        </ContextMenu>
    </ListView.ContextMenu>
    <ListView.View>
        <GridView>
            <GridViewColumn Header="Name" DisplayMemberBinding="{Binding Name}"/>
            <GridViewColumn Header="Type" DisplayMemberBinding="{Binding Type}"/>
            <GridViewColumn Header="Total Size" DisplayMemberBinding="{Binding TotalSize}"/>
            <GridViewColumn Header="Free Space" DisplayMemberBinding="{Binding FreeSpace, StringFormat='{}{0:0.00}'}"/>
        </GridView>
    </ListView.View>
</ListView>

Модель представления:

public class ViewModel
{
    public ViewModel()
    {
        RefreshCommand = new ActionCommand(Refresh);
    }

    public IReadOnlyList<DriveViewModel> Drives { get; }

    public ICommand RefreshCommand { get; }

    private void Refresh()
    {
        // ...
    }
}

Доступна рабочая демонстрация здесь.

Если вам действительно нужно запустить команду по событию MouseDown, а не только при нажатии/выборе пункта меню (через клавиатуру), то ответ здесь. Но это не стандартное поведение, и я не понимаю, почему кто-то может захотеть сделать это таким образом.

Orace 17.05.2022 11:43

Я исправил это, изменив его на Click. MouseDown вообще не работает для ContextMenu. Не уверен, почему это даже доступно как событие.

Meme Machine 17.05.2022 12:52

@MemeMachine I fixed it by changing it to Click для обработки события Click вам не нужно поддерево Interaction.Triggers\EventTrigger\InvokeCommandAction, просто используйте атрибуты Command и CommandParameter пункта меню. И опять же, я не верю, что вам нужно передавать параметр, поэтому CommandParameter в вашем случае бесполезен.

Orace 17.05.2022 14:27

Другие вопросы по теме