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

Posted

技术标签:

【中文标题】UWP 数据绑定:如何将按钮命令设置为 DataTemplate 中的父 DataContext【英文标题】:UWP Databinding: How to set button command to parent DataContext inside DataTemplate 【发布时间】:2016-04-30 07:44:44 【问题描述】:

需求的简短说明:我需要使用 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) 的各个项目。我需要做的是选择列表本身的 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 设置它,以及许多其他尝试,但都失败了。 Intellisense 在输入 ElementName 时检测到“storyTemplate”作为选项,即本题第一个代码块中显示的 DataTemplate 的名称。

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

谢谢你们!

【问题讨论】:

我从未尝试过 UWP,但在其他基于 XAML 的框架中,它通常使用 RelativeSource 处理。如果它没有帮助,那么要么是因为 DataTemplate 在资源中(尽管值得怀疑),要么是 UWP XAML 有自己的一些特殊性。 是的,先生,这就是导致我如此沮丧的原因。我做的第一件事是一个 RelativeSource 祖先绑定,就像我在过去的平台上所做的一样,但这次它不起作用。谢谢。 【参考方案1】:

您正在使用什么 MVVM 工具包(如果有)?在 MVVM Light 中,您可以像为视图设置 DataContext 一样从 DataTemplate 中获取 ViewModel:

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

【讨论】:

这会实例化一个新的、断开连接的 ViewModel 版本吗?谢谢你的回答! 不,不会。在您的 ViewModelLocator Main 中应该只从默认的 ServiceLocator 获取 MainViewModel 的实例,该实例返回单例。 Some more info. 拍脑门我现在感觉自己像个白痴。辛,请允许我最诚挚地感谢你。看了这段代码,我睡眼惺忪,但我认为你的代码可以工作! 确保您的定位器中的所有内容都正确无误,它会像魅力一样发挥作用。很高兴能提供帮助。 顺便说一句,爱你的头像。我是薛帕德指挥官,我同意你的回答。【参考方案2】:

很遗憾,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 类中有一个Page 属性,您希望将其作为参数传递给您的命令。您可以在此处绑定到 Story 类的任何其他属性或类本身。 您必须将页面名称设置为Page (x:name="Page"),以便您可以在数据模板中使用ElementName 引用它。

我假设您在ViewModel 上调用的命令名为NavigateCommand,并接受与绑定到CommandParameter 的属性相同类型的参数:

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

我希望这有助于并适用于您的场景。

【讨论】:

太棒了。非常感谢您解决祖先绑定是否可用 UWP 的问题。我几乎决定在无法绑定到我的视图模型命令的情况下,有两个选项可用。在 MODEL 上创建一个命令,并将故事作为参数传递,我不愿意这样做......或者将点击参数应用于 VIEW 后端的按钮。在这里小声咆哮,但我发现微软吹捧为程序的未来的平台剥夺了该功能是不可理解的。再次感谢您,达米尔! 如果 DataContext 在 ResourceDictionary 中,这将无济于事。有没有人找到合适的解决方案?【参考方案3】:

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

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

                    <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 模式...

【讨论】:

非常感谢您花时间阅读我的问题并将代码放在一起。恐怕它对我的应用程序不是很有帮助。我只需要触发按钮命令来激活导航到另一个页面的视图模型上的支持命令,同时传递任何参数。不过,再次感谢您! 是的,只是想你像 flyoutmenu 一样的按钮......把你的按钮命令放在加载的事件中,但保持 CommandParameter 与绑定......你将有你的命令与参数导航到另一个页面:D

以上是关于UWP 数据绑定:如何将按钮命令设置为 DataTemplate 中的父 DataContext的主要内容,如果未能解决你的问题,请参考以下文章

如何在 UWP XAML 上将样式触发器设置为路径绑定父状态

如何在UWP中数据绑定到RichEditBox的纯文本值?

UWP MVVMCross将属性绑定到方法

如何将 WPF 按钮绑定到 ViewModelBase 中的命令?

Shell BackButtonBehavior 命令绑定在 UWP 中不起作用

uwp命令栏更多按钮未显示DynamicOverflowEnabled