X:将ViewModel方法绑定到DataTemplate中的事件



我基本上问了与这个人相同的问题,但在较新的x:Bind中。

ViewModels的DataContext定义如

<Page.DataContext>
    <vm:ChapterPageViewModel x:Name="ViewModel" />
</Page.DataContext>

因此,每当我需要绑定某件事时,我都会明确地将其做到

ItemsSource="{x:Bind ViewModel.pageList, Mode=OneWay}"

但是,在模板中不起作用

<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,这应该意味着它看不到方法(但是一个空)。

图像类:

public class Image {
    public int page { get; set; }
    public string url { get; set; }
    public int width { get; set; }
    public int heigth { get; set; }
}

和章节pageViewModel

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

这是我将其分两个步骤重构的方式:

1)向VM添加命令:

public class ChapterPageViewModel
{
    public ChapterPageViewModel()
    {
        this.PageResizedCommand = new DelegateCommand(OnPageResized);
    }
    public DelegateCommand PageResizedCommand { get; }
    private void OnPageResized()
    {  }
}

2)将该命令与EventTrigger和InvokeCommandaction一起绑定到Sizechanged事件。

<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>

"但是Gabriel" ,您说,"那不起作用!"

我知道!这是因为第二个问题,即尝试x:绑定不属于datateMplate类的属性

这个问题与此问题密切相关,因此我将从那里借一些信息。

来自MSDN,关于DataTemplate和X:bind

在数据板板内(是否用作项目模板,内容 模板或标题模板),未解释路径的值 在页面的上下文中,但在数据对象的上下文中 被模板。以便可以验证其绑定(有效 为它们生成的代码)在编译时,数据板需要 使用x:datatype。

声明其数据对象的类型

因此,当您进行<ScrollViewer SizeChanged="{x:Bind ViewModel.PageResized}">时,实际上您正在搜索该 models:Image类中名为ViewModel的属性,即DatateMplate的x:DataType。在该类别上不存在这样的属性。

在这里,我可以看到两个选项。选择其中一个

添加该ViewModel作为图像类上的属性,然后将其填充在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;
    }
}

仅此而已,以前的代码应该不需要更改其他任何内容。

丢弃那个x:绑定并返回元素名称。

<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>

这是一种击败您的问题的目的,但它确实有效,并且更容易实现。