Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

UWP - x:Bind Mode=TwoWay throws errors without converter

There was a similar question to this, but the answer seemed to be off-base to the original question.

I've got a DataTemplate that has 3 comboboxes in it. The first two work fine, but the 3rd throws a compiler error.

<ComboBox ItemsSource="{x:Bind ListOne, Mode=OneWay}" 
              SelectedItem="{Binding SelectedOne, Mode=TwoWay}"
              DisplayMemberPath="Name"/>
<ComboBox ItemsSource="{x:Bind ListTwo}" 
              SelectedItem="{x:Bind SelectedTwo, Mode=TwoWay, Converter={StaticResource GenericConverter}}"
              DisplayMemberPath="Name"/>
<ComboBox ItemsSource="{x:Bind ListThree}" 
              SelectedItem="{x:Bind SelectedThree, Mode=TwoWay}"
              DisplayMemberPath="Name"/>

For ListOne, the "Binding" works perfectly as is.

For ListTwo, the "x: Bind" only works if a value converter is added. The "GenericConverter" only returns the "value" passed in. Other than that, it does nothing.

For ListThree, without a value converter, the compiler throws an error:

"Invalid binding path 'SelectedThree': Cannot bind type 'IdNameVm' to 'SystemObject' without a converter.

In all cases, the ItemSource is an ObservableCollection and the selected items are of type IdNameVm.

Using the converter works, but it feels wrong to be forced to put in a dummy converter on x: Bind just to get the XAML to compile.

As requested, here is how the ObservableCollection is defined plus the IdNameVm:

public ObservableCollection<IdNameVm> ListOne { get; set; }


public class IdNameVm : BindableBase
{
    private int _id;
    private string _name;

    public int Id
    {
        get { return _id; }
        set { Set(ref _id, value); }
    }

    public string Name
    {
        get { return _name; }
        set { Set(ref _name, value); }
    }

    protected bool Equals(IdNameVm other)
    {
        return Id == other.Id;
    }

    public override bool Equals(object obj)
    {
        if (ReferenceEquals(null, obj)) return false;
        if (ReferenceEquals(this, obj)) return true;
        if (obj.GetType() != this.GetType()) return false;
        return Equals((IdNameVm)obj);
    }

    public override int GetHashCode()
    {
        unchecked
        {
            var hashCode = Id;
            return hashCode;
        }
    }
}
like image 833
Paul Mouchet Avatar asked Mar 12 '23 20:03

Paul Mouchet


1 Answers

If you check the documentation of ComboBox, you'll see that the ItemsSource and SelectedItem properties are both of type object. The controls runs without knowing what type of viewmodels it is handling. So you can basically pass a collection of any type into ItemsSource. And SelectedItem works without knowing what type of object it is handling.

This behavior works fine with traditional two-way {Binding} as the bindings are evaluated at runtime. So {Binding} can convert the SelectItem's object type into IdNameVm at runtime.

However, things are a bit different with {x:Bind}. As you've noticed one-way binding works - this is because your ObservableCollection can be implicitly cast to object (the type of ItemsSource) and IdNameVm to object (the type of SelectedItem). But two-way binding won't work anymore, as the compiler can't cast object (the type of SelectedItem) to IdNameVm. You need a converter for it.

It's like you can do this in C#:

IdNameVm vm = new IdNameVm();
object selectedItem = vm;

But you can't do:

IdNameVm vm = new IdNameVm();
object selectedItem = vm;
IdNameVm vm2 = selectedItem; // You need: IdNameVm vm2 = (IdNameVm)selectedItem;

What is the cast in the comment is the converter in XAML.

The easiest way is to use traditional {Binding} in these cases.

like image 71
SebastianR Avatar answered Mar 25 '23 08:03

SebastianR