繁体   English   中英

将命令绑定到 ContextMenu 项

[英]Bind Command To ContextMenu Item

我收到此错误/警告:

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

当我按“刷新”时,该命令不会触发。 我猜,因为它是一个 ContextMenu,我需要以某种方式访问​​绑定路径中的父控件,然后我可以使用MouseDownCommand

MouseDownCommand位于我的选项卡视图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;
}

问题在于我似乎无法从grid获取tabControl 执行.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)
{

}

首先,在MenuItem的情况下, MouseDown事件似乎无法正常工作。 因此,您需要使用PreviewMouseDown或 probaby Click事件插入。 此外,您不能使用 ElementName 来引用ContextMenu之外的元素。

然后,假设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>

文档中所述[the] menu [...] 特定于控件的上下文
换句话说, ContextMenu具有与其父控件(此处为ListView )相同的数据上下文。
要遵循 MVVM 模式,您只需在ListView的数据上下文中添加一个ICommand并将其绑定到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()
    {
        // ...
    }
}

在此处获得工作演示。

暂无
暂无

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

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