x:Bind StackOverflow when trying to x:Bind ListView.SelectedItem using TwoWay mode
Asked Answered
A

2

5

I'm trying to bind ListView.SelectedItem using new x:Bind. My code:

View:

//MainPage.xaml:

<Page
x:Class="BrokenListSample.MainPage"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:local="using:BrokenListSample"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
mc:Ignorable="d">

<Grid Background="Beige">
    <Grid.RowDefinitions>
        <RowDefinition></RowDefinition>
        <RowDefinition></RowDefinition>
    </Grid.RowDefinitions>

    <ListView Grid.Row="0" Background="LawnGreen" 
              ItemsSource="{x:Bind ViewModel.MyItems, Mode=OneWay}"
              SelectedItem="{x:Bind ViewModel.BestItem, Mode=TwoWay}"
              Width="300" Height="300"/>

    <ListView Grid.Row="1" Background="LawnGreen" 
              ItemsSource="{Binding MyItems, Mode=OneWay}"
              SelectedItem="{Binding BestItem, Mode=TwoWay}"
              Width="300" Height="300"/>
</Grid>

Code-behind:

//MainPage.xaml.cs:

using Windows.UI.Xaml.Controls;

namespace BrokenListSample
{
    public sealed partial class MainPage : Page
    {
        public MainPageViewModel ViewModel { get; set; }

        public MainPage()
        {
            InitializeComponent();
            DataContextChanged += (s, e) => { ViewModel = DataContext as MainPageViewModel; };
            DataContext = new MainPageViewModel();
        }
    }
}

and finally ViewModel:

//MainPageViewModel.cs:

using System.Collections.ObjectModel;
using System.ComponentModel;

namespace BrokenListSample
{
    public class MainPageViewModel : INotifyPropertyChanged
    {
        public event PropertyChangedEventHandler PropertyChanged;

        public void OnPropertyChanged(string name)
        {
            PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(name));
        }

        private ObservableCollection<string> _myItems;
        public ObservableCollection<string> MyItems
        {
            get { return _myItems; }
            set { _myItems = value; OnPropertyChanged("MyItems"); }
        }

        private string _bestItem;
        public string BestItem
        {
            get { return _bestItem; }
            set { _bestItem = value; OnPropertyChanged("BestItem"); }
        }

        public MainPageViewModel()
        {
            MyItems = new ObservableCollection<string>() { "One", "Two", "Three", "Four" };
        }
    }
}

As you see I have two ListView controls on my MainPage. If you are trying to run this code, please comment one of them depending of what kind of binding you want to check. ListView from second row uses old good Binding, that just simply works. No surprise here.

Surprise comes with the one that uses new x:Bind which is causing StackOverflowException. Works fine with OneWay mode - but TwoWay throws StackOverflowException whenever I click one of the items... hilarious...

My question is very simple - "Why and how to solve that?"

Anticlockwise answered 15/2, 2016 at 20:47 Comment(1)
I'm unable to confirm the report, as I don't have a copy of VS2015 handy at the moment. However, your exception should include a stack trace, presumably one which includes the compiler-generated code to support the {x:Bind} expressions, and which should at least explain where the recursion causing the exception originates. Once you find the origin of the recursion, you should be able to ascertain whether the code causing the recursion was reasonably generated by the XAML compiler or not; if not, consider filing a bug report on connect.microsoft.comGomar
C
4

I was facing the same issue. Looking at the stack trace, I've figured out that my listview was modifying the viewmodel, that was rising the OnPropertyChanged, that modifies the listview... To solve that, you should modify the setter of the bound property:

public string BestItem
{
  get { return _bestItem; }
  set
  {
    if (_bestItem != value)
    {
      _bestItem = value;
      OnPropertyChanged(nameof(BestItem));
    }
  }
}
Coatbridge answered 27/4, 2016 at 12:34 Comment(0)
N
3

The SelectedItem of your ListView is from type Object but you try to refer to it as String so what you need is a simple converter.

I have found that you can just use a generic one without doing anything but implementing IValueConverter - that was mentioned in this answer

public class GenericConverter : IValueConverter
{
    public object Convert(object value, Type targetType, object parameter, string language)
    {
        return value;
    }

    public object ConvertBack(object value, Type targetType, object parameter, string language)
    {
        // no convert to a specific type needed -> the "value" is already an instance of the correct type.
        return value;
    }
}

Now you just have to reference this converter in your x:Bind

<ListView ...
          ItemsSource="{x:Bind ViewModel.MyItems, Mode=OneWay}"
          SelectedItem="{x:Bind ViewModel.BestItem, Mode=TwoWay, 
          Converter={StaticResource GenericConverter}}"
          .../>

And in your App.xaml (to make this converter available to all your views):

<Application
x:Class="Namespace.App"
...
xmlns:Converters="using:Namespace.Converters">
<Application.Resources>
    <ResourceDictionary>
        ...
        <Converters:GenericConverter x:Key="GenericConverter"/>
        ...
    </ResourceDictionary>
</Application.Resources>

Nadia answered 18/5, 2016 at 8:12 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.