为什么我不能在数据模板中使用{x:Bind {RelativeSource Self}}?

6
如果我在数据模板中使用{x:Bind {RelativeSource Self}},则编译时会出现以下错误:

对象引用未设置为对象的实例。

想法是将模板化的对象传递给类似命令参数的属性。这里是一个示例MainPage.xaml
<Page
    x:Class="XBindTest5.MainPage"
    xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
    xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
    xmlns:local="using:XBindTest5"
    xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
    xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
    mc:Ignorable="d">

    <Page.Resources>
        <ResourceDictionary>
            <local:OpenItemCommand x:Key="OpenCommand"/>
        </ResourceDictionary>
    </Page.Resources>

    <StackPanel Background="{ThemeResource ApplicationPageBackgroundThemeBrush}">
        <ItemsControl ItemsSource="{x:Bind NewsItems, Mode=OneWay}">
            <ItemsControl.ItemTemplate>
                <DataTemplate x:DataType="local:NewsItem">
                    <StackPanel>
                        <Button Command="{x:Bind {StaticResource OpenCommand}}" CommandParameter="{x:Bind {RelativeSource Self}}">
                            <TextBlock Text="{x:Bind Title}"/>
                        </Button>
                    </StackPanel>
                </DataTemplate>
            </ItemsControl.ItemTemplate>
        </ItemsControl>
    </StackPanel>
</Page>

在代码后台文件MainPage.xaml.cs中定义了一个简单的模型:
using System;
using System.Collections.ObjectModel;
using System.Windows.Input;
using Windows.UI.Xaml.Controls;


namespace XBindTest5 {

    public class NewsItem {
        public string Title { get; set; }
    }

    /// <summary>
    ///     command to open the item
    /// </summary>
    public class OpenItemCommand : ICommand {

        public event EventHandler CanExecuteChanged;

        public bool CanExecute(object parameter) {
            return true;
        }

        public void Execute(object parameter) {
            // ... example ...
        }
    }

    public sealed partial class MainPage : Page {

        public ObservableCollection<NewsItem> NewsItems { get; set; }
            = new ObservableCollection<NewsItem>(new[] {
                new NewsItem() { Title = "Item 1" },
                new NewsItem() { Title = "Item 2" } });

        public MainPage() {
            this.InitializeComponent();
        }
    }
}

抱歉,你是对的。看看这个链接吧:https://dev59.com/V47ea4cB1Zd3GeqPFL1O - Camilo Terevinto
哦,你说得对。答案说:_RelativeSource(使用x:Bind)不受支持_。所以我必须更改我的模型...我只搜索了datatemplate,所以错过了这个答案。 - ventiseis
我再次对XAML编译器的错误信息质量感到惊讶... - ventiseis
1
当你说“模板化对象”时,是指模板可视化表示的视图模型吗?我认为如果只使用 {Binding},那应该可以工作。来自 x:Bind 的文档: “您不能使用 {x:Bind} 与 DataContext 属性一起使用,该属性的类型为 Object,并且也可能在运行时更改。” ,即不支持任何依赖于运行时类型信息的技术,例如绑定到模板化对象,无法与 x:Bind 一起使用。因此,您需要改回使用 {Binding} - Peter Duniho
1
在我看来,你应该将代码以其工作形式发布为答案,然后自行接受该答案。同时包括来自文档的引用和解释。这将为未来的读者提供所需的信息,并清楚地标记问题已被回答。我不太愿意自己发布答案,因为(如我所提到的)我还没有升级到VS2015,因此无法测试任何UWP/Windows 10代码;我相信您的陈述,即使用CommandParameter={x:Bind}起到了预期的作用,但我会确保我发布的任何答案都只包含我个人验证过的代码。 - Peter Duniho
显示剩余2条评论
2个回答

12
虽然看起来您已经解决了问题,但我仍然想澄清一些问题,以避免混淆并使未来的读者更清楚。
正如@Peter Duniho所提到的,{x:Bind}无法与DataContext属性配合使用,并且{x:Bind}没有Source属性,因此你不能在{x:Bind}中使用StaticResource作为数据上下文,但您可以使用一个属性或静态路径代替。使用{x:Bind}时,它使用后台类作为其数据上下文。例如,当您设置ItemsSource="{x:Bind NewsItems, Mode=OneWay}"时,它将使用XBindTest5.MainPage类作为其数据上下文,并将该类的NewsItems属性绑定到ItemsSource。而在DataTemplate内部时,{x:Bind}使用在x:DataType中声明的类作为其数据上下文。请注意DataTemplate和x:DataType中的以下说明:
在DataTemplate(无论用作项模板、内容模板还是标题模板)中,Path的值都不是在页面上下文中解释的,而是在被模板化的数据对象上下文中解释的。因此,为了使其绑定可以在编译时进行验证(并生成高效的代码),DataTemplate需要使用x:DataType声明其数据对象类型。
在您的情况下,您在DataTemplate中使用Command,因此您可以在NewsItem中添加一个OpenCommand属性,并将该属性绑定到Command以使用它。
在您的代码后台:
public class NewsItem
{
    public string Title { get; set; }
    public OpenItemCommand OpenCommand { get; set; }
}

在 XAML 中:

<DataTemplate x:DataType="local:NewsItem">
    <StackPanel>
        <Button Command="{x:Bind OpenCommand}" CommandParameter="{x:Bind}">
            <TextBlock Text="{x:Bind Title}" />
        </Button>
    </StackPanel>
</DataTemplate>

此外,{x:Bind} 不支持 {RelativeSource},通常您可以为元素命名并在 Path 中使用其名称作为一种替代方法。有关更多信息,请参见{x:Bind} 和 {Binding} 特性比较

但是这不能在 DataTemplate 中使用,因为所有的 Path 都应该是 NewsItem 的属性。而在您的情况下,我认为你想传递的是 NewsItem 而不是 Button,所以您可以使用 CommandParameter="{x:Bind}"NewsItem 作为 CommandParameter 传递。

PS:XAML 设计器中存在一个小错误,您可能仍然会得到一个Object reference not set to an instance of an object. 错误。您可以在 Bind 后添加一个空格,如 {x:Bind } 作为解决方法。


1
非常感谢您详细的解释。但是有一个小注释:通过使用RelativeSource...作为命令参数,我尝试引用当前的NewsItem而不是按钮。所以我也误解了这个标记扩展。 - ventiseis

4
让我更具体地回答这个问题。x:bind只有一个可能的数据上下文,那就是底层类。在页面上,它是页面(或代码后台)。在数据模板中,它是在数据模板的targettype属性中指定的支持类。顺便说一下,在控件模板中,根本不支持x:bind - 虽然这只是时间问题。
所有这些都是为了说x:bind的数据上下文是固定的,在使用它的地方取决于它的位置,我可以告诉你数据上下文而无需查看您的XAML。为什么这么严格?部分原因是为了使其周围的代码生成更简单。此外,也为了使实现更简单。在任何情况下,这是一个固定的规则,并且不支持RelativeSource、ElementName和Source。
这并不意味着您不能引用相对源self,您只需使用指定的x:name即可。您可以像这样做 <Tag x:Name="Jerry" Tag="Nixon" Text="{x:Bind Jerry.Tag}" />
那个特定示例为什么会失败?与{binding}不同,{x:bind}需要匹配的类型,这意味着可以将Text的字符串设置为down-cast并设置为Tag的对象,但是Tag的对象无法up-cast并设置为Text的字符串值。对您来说,使用x:bind意味着您的类型必须匹配。
希望这可以帮助您更进一步。
祝您好运。

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接