本文介绍了x:将 ViewModel 方法绑定到 DataTemplate 内的事件的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

我基本上和这个人问了同样的问题,但在较新的 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 内的事件的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持!

08-20 21:43