Привязка данных 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, как это...