需求的简短说明:我需要使用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工作。
谢谢你们!
答案 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
类的任何其他属性或类本身。Page
(x: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模式......