x:将ViewModel方法绑定到DataTemplate中的Event

时间:2016-11-23 17:36:54

标签: c# mvvm uwp uwp-xaml

我基本上问的问题与this person相同,但是在较新的x:Bind的背景下。

ViewModels的DataContext定义如下

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

因此,每当我需要绑定某些内容时,我就会明确地对ViewModel这样做

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

并在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
}

1 个答案:

答案 0 :(得分:3)

我们在这里有两个问题:

首先,尝试将事件直接绑定到事件处理程序委托

这根本不会奏效,简单地说 处理MVVM模式事件的一种方法是使用EventTrigger and ICommand.
它需要一个实现ICommand的类。如果不知道如何操作,This post会对您有所帮助。我会打电话给我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>

“但是加布里埃尔”,你说,“那不起作用!”

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

这个与this question密切相关,所以我会从那里借一些信息。

来自MSDN,关于DataTemplate and x:Bind

  

在DataTemplate内部(无论是否用作项目模板,内容   模板或标题模板),不解释Path的值   在页面的上下文中,但在数据对象的上下文中   模仿。这样它的绑定就可以得到验证(并且有效   为它们生成的代码)在编译时,DataTemplate需要   使用x:DataType声明其数据对象的类型。

因此,当您执行<ScrollViewer SizeChanged="{x:Bind ViewModel.PageResized}">时,您实际上是在models:Image类上搜索名为ViewModel的属性,该类是DataTemplate的x:DataType。并且该类不存在这样的属性。

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

将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;
    }
}

只有这一点,以前的代码应该无需更改任何其他内容。

删除x:绑定并使用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>

这种方式会破坏你的问题的目的,但它确实有效,而且比以前更容易实现。