【问题标题】:x:Bind ViewModel method to an Event inside DataTemplatex:将 ViewModel 方法绑定到 DataTemplate 中的事件
【发布时间】:2017-04-07 20:17:43
【问题描述】:

我问的问题基本上与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
}

【问题讨论】:

  • models:Image 类是否有 ViewModel 属性?如果没有,并且您尝试引用绑定到FlipView.ItemsSource 的同一个 ViewModel,那么您将无法以这种方式访问​​它,因为 DataTemplate 的 DataContext 现在是一个模型:图像对象。
  • 确实如此。如果我取出SizeChanged="{x:Bind ViewModel.PageResized}",代码就可以工作。但是我需要能够在 ScrollView 的大小中调整图像的大小,这需要我从模板中访问 ViewModel 属性。
  • 您介意添加该 Image 类的代码吗?
  • 您是否尝试过使用旧的良好 Binding 而不是 x:Bind?
  • 还有一个问题:即使那个 Image 类确实有一个 ViewModel 属性,你也不能像这样绑定两个事件。您应该改用EventTrigger and Command。但首先,添加 Image 类的代码,以便我们正确回答。

标签: c# mvvm uwp uwp-xaml


【解决方案1】:

这里有两个问题:

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

简单地说,这永远行不通。
处理 MVVM 模式上的事件的一种方法是使用 EventTrigger and ICommand.
它需要一个实现 ICommand 的类。 This post 会帮助你,如果你不知道怎么做。我会打电话给我的DelegateCommand

我将分两步重构它:

1) 向虚拟机添加命令:

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:Bind 一个不属于 DataTemplate 类的属性

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

来自 MSDN,关于 DataTemplate and x:Bind

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

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

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

将该 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:Bind 并返回良好的 ol'Binding 与 ElementName。

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

这种方式违背了你的问题的目的,但它确实有效,而且比前一种更容易解决。

【讨论】:

  • 成功了。并引入了其他问题 - 无法获取事件参数。然而,这让我意识到我只是通过阅读模板子项的尺寸让我的生活变得更加艰难,而我也可以(并且更容易)阅读 FlipView 尺寸。并且该命令示例在将来肯定也会有用。谢谢!
  • @rancor1223 好吧,那个事件 args 是 MVVM 的一个老问题。所以,I'll just leave this here
猜你喜欢
  • 2021-10-25
  • 1970-01-01
  • 2011-12-17
  • 2012-04-21
  • 1970-01-01
  • 2017-12-14
  • 1970-01-01
  • 2011-05-03
  • 2012-11-17
相关资源
最近更新 更多