Why can't I use {x:Bind {RelativeSource Self}} in a data template?
Asked Answered
T

2

6

If I use {x:Bind {RelativeSource Self}} in a data template, I get the following error while compiling:

Object reference not set to an instance of an object.

The idea is to pass the templated object to a property like a command parameter. Here is an example 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>

A simple model is defined in the code-behinde file 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();
        }
    }
}
Tineid answered 1/12, 2015 at 22:51 Comment(7)
If you wonder why I passed the command as StaticRessource: I didn't find a way to reference outer properties in the data template via X:Bind.Using a static ressource doesn't work either (creates another NullReferenceException).Tineid
Sorry, you're right. Take a look at this instead: #32372573Coulisse
Oh, you're right. The answer says: RelativeSource (with x:Bind) is not supported. So I'll have to change my model.. I searched only for datatemplate so I missed this answer.Tineid
Once again I am surprised about the quality of the error messages of the xaml compiler...Tineid
When you say "templated object", do you mean the view model the template visually represents? I think if you just use {Binding}, that should work. From the docs for x:Bind: "you cannot use {x:Bind} with the DataContext property, which is of type Object, and is also subject to change at run time.". I.e. no technique that depends on run-time type information, such as binding to the templated object, is going to work with x:Bind. You need to fall back on {Binding} instead.Kerguelen
@PeterDuniho Very nice - it works if I use only CommandParameter="{x:Bind}. If you post it as answer, I'll accept itTineid
IMHO, you yourself should post the code in its working form as an answer, and then self-accept the answer. Also include the quote from the documentation and explanation. This will provide the information future readers need, and clearly mark the question as answered. I am hesitant to post an answer myself, because (as I mentioned) I have not upgraded to VS2015 yet and so can't actually test any UWP/Windows 10 code myself; I do trust your statement that using CommandParameter={x:Bind} works as intended, but I try to make sure any answer I post contains only code I have personally verified.Kerguelen
M
12

Although it seems you have solved your problem, I still want to make some clarifications to avoid confusion and make it clearly for future readers.

As @Peter Duniho has mentioned, {x:Bind} can't work with DataContext property and {x:Bind} doesn't have a Source property, so you can't use StaticResource as data context in {x:Bind}, but you can use a property or a static path instead. While using {x:Bind}, it uses the background class as its data context. For example, when you set ItemsSource="{x:Bind NewsItems, Mode=OneWay}", it uses the XBindTest5.MainPage class as its data context and bind the NewsItems property of this class to ItemsSource. And while inside a DataTemplate, {x:Bind} uses the class declared in x:DataType as its data context. Please note following explanation in DataTemplate and 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.

In your case, you use the Command in DataTemplate, so you can add a OpenCommand property in NewsItem and bind this property to Command to use it.

In your code-behind:

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

In the XAML:

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

Also {x:Bind} doesn't support {RelativeSource}, usually you can name the element and use its name in Path as an alternative. For more information see {x:Bind} and {Binding} feature comparison.

But this can't be used in DataTemplate as all Path are supposed to be a property of NewsItem. And in your case, I think what you want to pass is the NewsItem not the Button, so you can use CommandParameter="{x:Bind}" to pass the NewsItem as the CommandParameter.

PS: There is a small bug in XAML designer, you may still get a Object reference not set to an instance of an object. error. You can add a space after Bind like {x:Bind } as a workaround.

Moriahmoriarty answered 3/12, 2015 at 14:48 Comment(1)
Thank you for your very detailled explanation. But one small comment: by using the RelativeSource... for the command parameter I tried to reference the current NewsItem, not the button. So I missunderstood that markup extension, too.Tineid
S
4

Let me more specifically answer this. There is only one possible data context to x:bind and that is the underlying class. On a page, it is the page (or the code-behind). In a data template, it is the backing class specified in the targettype property of the data template. As an aside, in a control template, x:bind is not supported at all - though it's only a matter of time.

All that is to say that the data context of x:bind is fixed, and depending on where it is being used, I can tell you the data context without looking at your XAML. Why so rigid? In part to make the code generation around it simpler. Also, to make the implementation simpler, too. In either case, this is a fixed rule, and RelativeSource, ElementName, and Source and not supported in x:bind.

This does not mean you cannot reference the relativesource self, you just have to do it with a specified x:name. You would do something like this <Tag x:Name="Jerry" Tag="Nixon" Text="{x:Bind Jerry.Tag}" />.

Why does that particular sample fail? Unlike {binding}, {x:bind} requires matching types, which means setting Text's string can be down-cast and set to Tag's object, but Tag's object cannot be up-cast and set to Text's string value. The take-away for you is using x:bind means your types must match.

I hope this helps get you further along.

Best of luck.

Supertonic answered 3/12, 2015 at 22:44 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.