Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

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

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

like image 362
RTDev Avatar asked Feb 15 '16 20:02

RTDev


2 Answers

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));
    }
  }
}
like image 180
Iuri Farenzena Avatar answered Oct 06 '22 13:10

Iuri Farenzena


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>

like image 36
TryToSolveItSimple Avatar answered Oct 06 '22 14:10

TryToSolveItSimple