Привязка данных UWP: как установить команду button в родительский DataContext внутри DataTemplate

краткое объяснение необходимости: мне нужно запустить команду кнопки внутри DataTemplate, используя метод из DataContext ViewModel.

краткое объяснение проблемы: команда templated button только кажется привязанной к datacontext самого элемента. Синтаксис, используемый приложениями WPF и Windows 8.1 для просмотра визуального дерева, не работает, включая привязку ElementName и Ancestor. Я бы очень предпочел, чтобы моя команда кнопки не находилась внутри модели.

боковое Примечание: это построено с помощью метода проектирования MVVM.

приведенный ниже код генерирует список элементов в представлении. Этот список-одна кнопка для каждого элемента списка.

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

внутри ресурсов страницы того же представления я создал 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 был установлен на отдельные элементы, которые составляют список (модель). Что мне нужно сделать, это выбрать DataContext самого списка (VIEWMODEL), чтобы я мог получить доступ к команде навигации.

Если вас интересует код за страницей просмотра, см. ниже.

    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 определяет "storyTemplate" как параметр при вводе ElementName, который является именем DataTemplate, показанным в первом блоке кода этого вопроса.

Я не считаю, что моя проблема может быть уникальной, однако мне очень трудно найти решение для UWP. Позвольте мне заранее извиниться в этом простом вопросе, но я провел почти два дня, исследуя ответы, без каких-либо кажущаяся на работу для UWP.

Спасибо, ребята!

3 ответов


какой инструментарий MVVM вы используете (если есть)? В MVVM Light вы можете получить ViewModel из DataTemplate так же, как вы установили DataContext для своего представления:

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

действительно жаль, что в 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 класса или сам класс.
  • вы должны указать Название вашей страницы Page (x:name="Page"), так что вы можете ссылаться на него, используя ElementName в шаблоне данных.
  • я предположил, что команда, которую вы вызываете на ViewModel называется NavigateCommand и принимает параметр того же типа, что и свойство, привязанное к CommandParameter:

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

я надеюсь, что это поможет и применимо к вашему сценарию.


есть несколько способов сделать это. Но я думаю, что команда меняется лучше...

пример,у вас есть (сетка, список)вид с некоторым itemtemplate, как это:

                    <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, как это...