UWP 数据绑定:如何将按钮命令设置为 DataTemplate 中的父 DataContext
UWP Databinding: How to set button command to parent DataContext inside DataTemplate
需求的简短说明:我需要使用 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) 的各个项目。我需要做的是 select 列表本身的 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 的解决方案。请允许我提前道歉,这是一个简单的问题,但我花了近两天时间研究答案,none 似乎适用于 UWP。
谢谢大家!
有几种方法可以做到这一点。但我认为命令改变得更好......
例如,您有一个(网格,列表)视图,其中包含一些像这样的项目模板:
<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 模式...
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
class 中有一个 Page
属性 你想作为参数传递给你的命令.您可以在此处绑定到 Story
class 的任何其他 属性 或 class 本身。
- 您必须将页面名称设置为
Page
(x:name="Page"
),以便您可以在数据模板中使用 ElementName
引用它。
我假设您在 ViewModel
上调用的命令名为 NavigateCommand
并接受与 属性 绑定到的相同类型的参数CommandParameter
:
public ICommand NavigateCommand { get; } =
new RelayCommand<string>(name => Debug.WriteLine(name));
我希望这对您有所帮助并且适用于您的场景。
您使用的是什么 MVVM 工具包(如果有)?在 MVVM Light 中,您可以通过为视图设置 DataContext 的相同方式从 DataTemplate 获取 ViewModel:
<DataTemplate x:Key="SomeTemplate">
<Button Command="{Binding Main.MyCommand, Source={StaticResource ViewModelLocator}}"/>
</DataTemplate>
需求的简短说明:我需要使用 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) 的各个项目。我需要做的是 select 列表本身的 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 的解决方案。请允许我提前道歉,这是一个简单的问题,但我花了近两天时间研究答案,none 似乎适用于 UWP。
谢谢大家!
有几种方法可以做到这一点。但我认为命令改变得更好......
例如,您有一个(网格,列表)视图,其中包含一些像这样的项目模板:
<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 模式...
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
class 中有一个Page
属性 你想作为参数传递给你的命令.您可以在此处绑定到Story
class 的任何其他 属性 或 class 本身。 - 您必须将页面名称设置为
Page
(x:name="Page"
),以便您可以在数据模板中使用ElementName
引用它。 我假设您在
ViewModel
上调用的命令名为NavigateCommand
并接受与 属性 绑定到的相同类型的参数CommandParameter
:public ICommand NavigateCommand { get; } = new RelayCommand<string>(name => Debug.WriteLine(name));
我希望这对您有所帮助并且适用于您的场景。
您使用的是什么 MVVM 工具包(如果有)?在 MVVM Light 中,您可以通过为视图设置 DataContext 的相同方式从 DataTemplate 获取 ViewModel:
<DataTemplate x:Key="SomeTemplate">
<Button Command="{Binding Main.MyCommand, Source={StaticResource ViewModelLocator}}"/>
</DataTemplate>