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

时间:2016-01-23 20:02:24

标签: c# xaml data-binding uwp datacontext

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

问题的简短说明:模板化按钮命令似乎只能绑定到项目本身的datacontext。 WPF和Windows 8.1应用程序用于浏览可视化树的语法似乎不起作用,包括ElementName和Ancestor绑定。我非常希望不让我的按钮命令位于MODEL内。

附注:这是使用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设置它,以及其他许多尝试,但都失败了。当输入ElementName时,Intellisense会检测“storyTemplate”作为选项,这是此问题的第一个代码块中显示的DataTemplate的名称。

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

谢谢你们!

3 个答案:

答案 0 :(得分:8)

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

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

答案 1 :(得分:3)

UWP中没有祖先约束真的很不幸。这使得像你这样的场景更难实现。

我能想到的唯一方法是在DependencyProperty上为ViewModel创建Page

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类的任何其他属性或类本身。
  • 您必须将网页名称设置为Pagex:name="Page"),以便您可以使用数据模板中的ElementName来引用它。
  • 我假设您在ViewModel上调用的命令名为NavigateCommand,并接受与CommandParameter绑定的属性相同类型的参数:

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

我希望这会有所帮助,适用于您的情况。

答案 2 :(得分:0)

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

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

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