问题描述
我基本上和这个人问了同样的问题,但在较新的 x:Bind
的上下文中.
I'm basically asking the same question as this person, but in the context of the newer x:Bind
.
ViewModels 的 DataContext 是这样定义的
ViewModels' DataContext is defined like so
<Page.DataContext>
<vm:ChapterPageViewModel x:Name="ViewModel" />
</Page.DataContext>
所以每当我需要绑定某些东西时,我都会像这样明确地将它绑定到 ViewModel
So whenever I need to bind something I do it explicitely to the ViewModel like so
ItemsSource="{x:Bind ViewModel.pageList, Mode=OneWay}"
但是这在模板中不起作用
However that doesn't work within templates
<FlipView ItemsSource="{x:Bind ViewModel.pageList, Mode=OneWay}">
<FlipView.ItemTemplate>
<DataTemplate x:DataType="models:Image">
<ScrollViewer SizeChanged="{x:Bind ViewModel.PageResized}"> <-- this here is the culprit
<Image Source="{x:Bind url}"/>
</ScrollViewer>
</DataTemplate>
</FlipView.ItemTemplate>
</FlipView>
阅读文档,我发现使用 Path
应该基本上将上下文重置为页面,但是这个 (x:Bind Path=ViewModel.PageResizeEvent
不起作用要么.我仍然得到 Object reference not set to an instance of an object
,这应该意味着它没有看到该方法(但为空).
Reading the documentation, I found that using Path
should basically reset the context to the page, but this (x:Bind Path=ViewModel.PageResizeEvent
didn't work either. I'm still getting Object reference not set to an instance of an object
, which should mean that it doesn't see the method (but a null).
图像类:
public class Image {
public int page { get; set; }
public string url { get; set; }
public int width { get; set; }
public int heigth { get; set; }
}
在 ChapterPageViewModel 中
And in the ChapterPageViewModel
private List<Image> _pageList;
public List<Image> pageList {
get { return _pageList; }
set { Set(ref _pageList, value); }
}
public override async Task OnNavigatedToAsync(object parameter, NavigationMode mode,
IDictionary<string, object> suspensionState)
{
Initialize();
await Task.CompletedTask;
}
private async void Initialize()
{
pageList = await ComicChapterGet.GetAsync(_chapterId);
}
public void PageResized(object sender, SizeChangedEventArgs e)
{
//resizing logic happens here
}
推荐答案
我们这里有两个问题:
首先,尝试将事件直接绑定到事件处理程序委托
简单地说,这永远行不通.
在 MVVM 模式上处理事件的一种方法是使用 EventTrigger 和 ICommand.
它需要一个实现 ICommand 的类.这篇文章会在不知道如何操作的情况下为您提供帮助.我将调用我的 DelegateCommand
.
That will never work, simply put.
One way to handle an event on MVVM pattern is by using EventTrigger and ICommand.
It requires a class that implements ICommand. This post will help you if don't know how to do it. I'll call mine DelegateCommand
.
以下是我分两步重构它的方法:
Here's how I would refactor it in two steps:
1) 向虚拟机添加命令:
1) Add a Command to the VM:
public class ChapterPageViewModel
{
public ChapterPageViewModel()
{
this.PageResizedCommand = new DelegateCommand(OnPageResized);
}
public DelegateCommand PageResizedCommand { get; }
private void OnPageResized()
{ }
}
2) 使用 EventTrigger 和 InvokeCommandAction 将该命令绑定到 SizeChanged 事件.
2) Bind that Command to the SizeChanged event with EventTrigger and InvokeCommandAction.
<Page (...)
xmlns:i="using:Microsoft.Xaml.Interactivity"
xmlns:core="using:Microsoft.Xaml.Interactions.Core">
(...)
<FlipView ItemsSource="{x:Bind ViewModel.pageList, Mode=OneWay}" >
<FlipView.ItemTemplate>
<DataTemplate x:DataType="models:Image">
<ScrollViewer>
<i:Interaction.Behaviors>
<core:EventTriggerBehavior EventName="SizeChanged">
<core:InvokeCommandAction
Command="{x:Bind ViewModel.PageResizedCommand }" />
</core:EventTriggerBehavior>
</i:Interaction.Behaviors>
<Image Source="{x:Bind url}"/>
</ScrollViewer>
</DataTemplate>
</FlipView.ItemTemplate>
</FlipView>
</Page>
但是加布里埃尔",你说,那没用!"
我知道!这是因为第二个问题,即尝试 x:Bind 一个不属于 DataTemplate 类的属性
I know! And that's because of the second problem, which is trying to x:Bind a property that does not belong to the DataTemplate class
这个与这个密切相关问题,所以我会从那里借一些信息.
This one is closely related to this question, so I´ll borrow some info from there.
来自 MSDN,关于 DataTemplate 和 x:Bind
From MSDN, regarding DataTemplate and x:Bind
在 DataTemplate 内部(无论用作项目模板、内容模板,或标题模板),Path 的值不被解释在页面的上下文中,但在数据对象的上下文中被模板化.以便可以验证其绑定(并且有效为它们生成的代码)在编译时,DataTemplate 需要使用 x:DataType 声明其数据对象的类型.
因此,当您执行 <ScrollViewer SizeChanged="{x:Bind ViewModel.PageResized}">
时,您实际上是在该 模型上搜索名为 ViewModel 的属性:Image
类,即DataTemplate 的x:DataType
.并且该类中不存在这样的属性.
So, when you do <ScrollViewer SizeChanged="{x:Bind ViewModel.PageResized}">
, you're actually searching for a property named ViewModel on the that models:Image
class, which is the DataTemplate's x:DataType
. And such a property does not exist on that class.
在这里,我可以看到两个选项.选择其中之一:
Here, I can see two options. Choose one of them:
将该 ViewModel 添加为 Image 类的属性,并在 VM 上填充它.
public class Image {
(...)
public ChapterPageViewModel ViewModel { get; set; }
}
public class ChapterPageViewModel
{
(...)
private async void Initialize() {
pageList = await ComicChapterGet.GetAsync(_chapterId);
foreach(Image img in pageList)
img.ViewModel = this;
}
}
仅此而已,之前的代码应该可以正常工作,无需更改任何其他内容.
With only this, that previous code should work with no need to change anything else.
删除 x:Bind 并返回到 ElementName 的良好 ol'Binding.
<FlipView ItemsSource="{x:Bind ViewModel.pageList, Mode=OneWay}" x:Name="flipView">
<FlipView.ItemTemplate>
<DataTemplate x:DataType="models:Image">
<ScrollViewer>
<i:Interaction.Behaviors>
<core:EventTriggerBehavior EventName="SizeChanged">
<core:InvokeCommandAction
Command="{Binding DataContext.PageResizedCommand
, ElementName=flipView}" />
</core:EventTriggerBehavior>
</i:Interaction.Behaviors>
<Image Source="{x:Bind url}"/>
</ScrollViewer>
</DataTemplate>
</FlipView.ItemTemplate>
</FlipView>
这违背了你的问题的目的,但它确实有效,而且比前一个更容易实现.
This one kind of defeat the purpose of your question, but it does work and it's easier to pull off then the previous one.
这篇关于x:将 ViewModel 方法绑定到 DataTemplate 内的事件的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持!