UWP 数据绑定:如何将按钮命令设置为 DataTemplate 中的父 DataContext

UWP Databinding: How to set button command to parent DataContext inside DataTemplate

需求的简短说明:我需要使用 ViewModel 的 DataContext 中的方法触发 DataTemplate 中按钮的命令。

问题的简短说明:模板化按钮命令似乎只能绑定到项目本身的数据上下文。 WPF 和 Windows 8.1 应用程序在可视化树中向上移动所使用的语法似乎不起作用,包括 ElementName 和 Ancestor 绑定。我非常不希望我的按钮命令位于模型中。

旁注:这是使用 MVVM 设计方法构建的。

以下代码生成 VIEW 上的项目列表。该列表是每个列表项的一个按钮。

            <ItemsControl x:Name="listView" Tag="listOfStories" Grid.Row="0" Grid.Column="1"
            ItemsSource="{x:Bind ViewModel.ListOfStories}"
            ItemTemplate="{StaticResource storyTemplate}"
            Background="Transparent"
            IsRightTapEnabled="False"
            IsHoldingEnabled="False"
            IsDoubleTapEnabled="False"
                 />

在同一VIEW的页面资源中,我创建了一个DataTemplate,其中包含有问题的按钮。我继续并删除了按钮内的大部分格式,例如文本,以使代码在这一侧更易于阅读。与按钮相关的一切都正常,除了列出的问题,即命令的绑定。

<Page.Resources>
        <DataTemplate x:Name="storyTemplate" x:DataType="m:Story">
            <Button
                Margin="0,6,0,0"
                Width="{Binding ColumnDefinitions[1].ActualWidth, ElementName=storyGrid, Mode=OneWay}"
                HorizontalContentAlignment="Stretch"
                CommandParameter="{Binding DataContext, ElementName=Page}"
                Command="{Binding Source={StaticResource Locator}}">

                <StackPanel HorizontalAlignment="Stretch" >
                    <TextBlock Text="{x:Bind StoryTitle, Mode=OneWay}"
                        FontSize="30"
                        TextTrimming="WordEllipsis"
                        TextAlignment="Left"/>
                </StackPanel>
            </Button>
        </DataTemplate>
    </Page.Resources>

因为这是一个 DataTemplate,DataContext 已设置为构成列表 (MODEL) 的各个项目。我需要做的是 select 列表本身的 DataContext (VIEWMODEL),这样我就可以访问导航命令。

如果您对VIEW页面的代码隐藏感兴趣,请看下面。

    public sealed partial class ChooseStoryToPlay_View : Page
    {
    public ChooseStoryToPlay_View()
    {
        this.InitializeComponent();
        this.DataContextChanged += (s, e) => { ViewModel = DataContext as ChooseStoryToPlay_ViewModel; };
    }
    public ChooseStoryToPlay_ViewModel ViewModel { get; set; }
}

我试过通过 ElementName 设置它,还有许多其他尝试,但都失败了。当输入 ElementName 时,Intellisense 将 "storyTemplate" 检测为一个选项,这是该问题第一个代码块中显示的 DataTemplate 的名称。

我不认为我的问题是独一无二的,但是我很难找到 UWP 的解决方案。请允许我提前道歉,这是一个简单的问题,但我花了近两天时间研究答案,none 似乎适用于 UWP。

谢谢大家!

有几种方法可以做到这一点。但我认为命令改变得更好......

例如,您有一个(网格,列表)视图,其中包含一些像这样的项目模板:

                    <GridView.ItemTemplate>
                        <DataTemplate>

                            <Grid
                                    x:Name="gdVehicleImage"
                                    Height="140"
                                    Width="140"
                                    Background="Gray"
                                    Margin="2"

                                >

                           </Grid>

                 </GridView.ItemTemplate>

您是否要向 FlyoutMenu 发出命令...但是该命令在 ViewModel 中而不是在 GridView.SelectedItem...

你能做的是...

                        <Grid
                                    x:Name="gdVehicleImage"
                                    Height="140"
                                    Width="140"
                                    Background="Gray"
                                    Margin="2"

                                >

                                <FlyoutBase.AttachedFlyout>
                                    <MenuFlyout
                                            Opened="MenuFlyout_Opened"
                                            Closed="MenuFlyout_Closed"
                                        >

                                        <MenuFlyout.MenuFlyoutPresenterStyle>
                                            <Style TargetType="MenuFlyoutPresenter">
                                                <Setter Property="Background" Value="DarkCyan"/>
                                                <Setter Property="Foreground" Value="White"/>
                                            </Style>
                                        </MenuFlyout.MenuFlyoutPresenterStyle>

                                        <MenuFlyoutItem 

                                                Loaded="mfiSetAsDefaultPic_Loaded" 
                                                CommandParameter="{Binding}"
                                                />
                                        <MenuFlyoutItem 

                                                Loaded="mfiDeletePic_Loaded" 
                                                CommandParameter="{Binding}"
                                                />

                                    </MenuFlyout>
                                </FlyoutBase.AttachedFlyout>


             </Grid>

并且在加载的事件中:

    private void mfiDeletePic_Loaded(object sender, RoutedEventArgs e)
    {
        var m = (MenuFlyoutItem)sender;

        if (m != null)
        {

            m.Command = Vm.DeleteImageCommand;
            //Vm is the ViewModel instance...
        }
    }

并不完全漂亮...但是您不会像这样破坏 mvvm 模式...

UWP 中没有祖先绑定真的很不幸。这使得像您这样的场景更难实施。

我能想到的唯一方法是在 Page:

上为 ViewModel 创建一个 DependencyProperty
public ChooseStoryToPlay_ViewModel ViewModel
{
    get { return (ChooseStoryToPlay_ViewModel)GetValue(ViewModelProperty); }
    set { SetValue(ViewModelProperty, value); }
}

public static readonly DependencyProperty ViewModelProperty =
    DependencyProperty.Register("ViewModel", typeof(ChooseStoryToPlay_ViewModel), typeof(MainPage), new PropertyMetadata(0));

现在您可以从您的数据模板绑定到它:

<DataTemplate x:Name="storyTemplate" x:DataType="local:Story">
    <Button
        Margin="0,6,0,0"
        Width="{Binding ColumnDefinitions[1].ActualWidth, ElementName=storyGrid, Mode=OneWay}"
        HorizontalContentAlignment="Stretch"
        CommandParameter="{x:Bind Page}"
        Command="{Binding ViewModel.NavigateCommand, ElementName=Page}">

        <StackPanel HorizontalAlignment="Stretch" >
            <TextBlock Text="{x:Bind StoryTitle, Mode=OneWay}"
                FontSize="30"
                TextTrimming="WordEllipsis"
                TextAlignment="Left"/>
        </StackPanel>
    </Button>
</DataTemplate>

有两点需要注意:

  • CommandParameter 我假设在你的 Story class 中有一个 Page 属性 你想作为参数传递给你的命令.您可以在此处绑定到 Story class 的任何其他 属性 或 class 本身。
  • 您必须将页面名称设置为 Page (x:name="Page"),以便您可以在数据模板中使用 ElementName 引用它。
  • 我假设您在 ViewModel 上调用的命令名为 NavigateCommand 并接受与 属性 绑定到的相同类型的参数CommandParameter:

    public ICommand NavigateCommand { get; } = 
        new RelayCommand<string>(name => Debug.WriteLine(name));
    

我希望这对您有所帮助并且适用于您的场景。

您使用的是什么 MVVM 工具包(如果有)?在 MVVM Light 中,您可以通过为视图设置 DataContext 的相同方式从 DataTemplate 获取 ViewModel:

<DataTemplate x:Key="SomeTemplate">
    <Button Command="{Binding Main.MyCommand, Source={StaticResource ViewModelLocator}}"/>
</DataTemplate>