简体   繁体   English

将命令绑定到 ContextMenu 项

[英]Bind Command To ContextMenu Item

I am getting this error/warning:我收到此错误/警告:

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

When I press 'Refresh', the command does not fire.当我按“刷新”时,该命令不会触发。 I am guessing since it is a ContextMenu, I need to somehow access the parent control in the binding path and then I can use MouseDownCommand .我猜,因为它是一个 ContextMenu,我需要以某种方式访问​​绑定路径中的父控件,然后我可以使用MouseDownCommand

MouseDownCommand is located in my tab viewmodel, TabItemViewModel . MouseDownCommand位于我的选项卡视图TabItemViewModel中。 My MainWindowViewModel contains a list of TabItemViewModel s, and that is the source of the TabControl 's items.我的MainWindowViewModel包含TabItemViewModel的列表,这是TabControl项目的来源。

What I've tried:我试过的:

1) 1)

I've tried setting the ContextMenu Opened event to set the DataContext manually like this to see if it would fix the DataContext somehow:我尝试设置 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;
}

The problem with this is the fact that I cannot seem to get the tabControl from the grid .问题在于我似乎无法从grid获取tabControl Doing .Parent just returns null for some unknown reason.执行.Parent只是出于某种未知原因返回null

2) 2)

I also tried setting the Tag of the control, which did not work either:我还尝试设置控件的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>

Here is my XAML这是我的 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>

Here is the ViewModel.cs这是 ViewModel.cs

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

private void MouseDown(object commandParameter)
{

}

First of all, MouseDown event doesn't seem to work well in the case of MenuItem .首先,在MenuItem的情况下, MouseDown事件似乎无法正常工作。 So, you need to use PreviewMouseDown or probaby Click event instread.因此,您需要使用PreviewMouseDown或 probaby Click事件插入。 Also, you cannot use ElementName for referring elements outside of ContextMenu .此外,您不能使用 ElementName 来引用ContextMenu之外的元素。

Then, assuming DataContext of the ListView is implicitly bound to TabItemViewModel and you want to specify that ListView as CommandParameter , it could be modified as follows:然后,假设ListViewDataContext隐式绑定到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>

As stated in the documentation , [the] menu [...] is specific to the context of the control .文档中所述[the] menu [...] 特定于控件的上下文
In other words, the ContextMenu has the same data context as it's parent control (here the ListView ).换句话说, ContextMenu具有与其父控件(此处为ListView )相同的数据上下文。
To follow the MVVM pattern, you then only need to add a ICommand in the data context of the ListView and bind it to the MenuItem.Command property.要遵循 MVVM 模式,您只需在ListView的数据上下文中添加一个ICommand并将其绑定到MenuItem.Command属性。

The View:风景:

<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>

The ViewModel:视图模型:

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

    public IReadOnlyList<DriveViewModel> Drives { get; }

    public ICommand RefreshCommand { get; }

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

Working demo available here .在此处获得工作演示。

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM