x:将 ViewModel 方法绑定到 DataTemplate 内的事件 [英] x:Bind ViewModel method to an Event inside DataTemplate

查看:36
本文介绍了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 声明其数据对象的类型.

Inside a DataTemplate (whether used as an item template, a content template, or a header template), the value of Path is not interpreted in the context of the page, but in the context of the data object being templated. So that its bindings can be validated (and efficient code generated for them) at compile-time, a DataTemplate needs to declare the type of its data object using 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 内的事件的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆