UWP 数据绑定:如何在数据模板中将按钮命令设置为父数据上下文
本文关键字:数据 按钮 命令 设置 上下文 数据绑定 UWP | 更新日期: 2023-09-27 18:31:32
需求的简短解释:我需要使用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 的页面资源中,我创建了一个数据模板,其中包含有问题的按钮。 我继续删除了按钮内的大部分格式,例如文本,以使代码更易于阅读。 与按钮有关的所有内容都有效,除了列出的问题,即命令的绑定。
<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>
由于这是一个数据模板,因此数据上下文已设置为构成列表的各个项 (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 时,智能感知将"storyTemplate"检测为一个选项,这是本问题的第一个代码块中显示的数据模板的名称。
我不相信我的问题可能是独一无二的,但是我很难找到 UWP 的解决方案。请允许我提前道歉,这是一个简单的问题,但我花了将近两天的时间研究答案,似乎没有一个对 UWP 有用。
谢谢你们!
您使用的是什么 MVVM 工具包(如果有的话)?在 MVVM Light 中,您可以从 DataTemplate 获取 ViewModel,就像为视图设置 DataContext 一样:
<DataTemplate x:Key="SomeTemplate">
<Button Command="{Binding Main.MyCommand, Source={StaticResource ViewModelLocator}}"/>
</DataTemplate>
,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));
我希望这有所帮助,并且适用于您的方案。
有几种方法可以做到这一点。但我认为命令改变得更好...
例如,您有一个(网格,列表)视图,其中包含一些项模板,如下所示:
<GridView.ItemTemplate>
<DataTemplate>
<Grid
x:Name="gdVehicleImage"
Height="140"
Width="140"
Background="Gray"
Margin="2"
>
</Grid>
</GridView.ItemTemplate>
您是否要发出命令,例如浮出控件菜单...但是命令它在视图模型中,而不是在网格视图中。
你能做的是...
<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 模式......