Bug 59220 - Dot binding syntax binds to wrong object in ItemTemplate
Summary: Dot binding syntax binds to wrong object in ItemTemplate
Status: CLOSED INVALID
Alias: None
Product: Forms
Classification: Xamarin
Component: Forms ()
Version: 2.3.4
Hardware: PC Windows
: --- normal
Target Milestone: ---
Assignee: Bugzilla
URL:
Depends on:
Blocks:
 
Reported: 2017-09-05 00:52 UTC by Edward Brey
Modified: 2017-09-15 14:21 UTC (History)
3 users (show)

Tags:
Is this bug a regression?: ---
Last known good build:

Notice (2018-05-24): bugzilla.xamarin.com is now in read-only mode.

Please join us on Visual Studio Developer Community and in the Xamarin and Mono organizations on GitHub to continue tracking issues. Bugzilla will remain available for reference in read-only mode. We will continue to work on open Bugzilla bugs, copy them to the new locations as needed for follow-up, and add the new items under Related Links.

Our sincere thanks to everyone who has contributed on this bug tracker over the years. Thanks also for your understanding as we make these adjustments and improvements for the future.


Please create a new report on Developer Community or GitHub with your current version information, steps to reproduce, and relevant error messages or log files if you are hitting an issue that looks similar to this resolved bug and you do not yet see a matching new report.

Related Links:
Status:
CLOSED INVALID

Description Edward Brey 2017-09-05 00:52:12 UTC
In a ListView ItemTemplate, if you using the dot syntax for a data binding, the list item is not the bound object. Here's the key code to reproduce:

<ContentPage x:Name="page" ...>
  <ListView>
    <ListView.ItemTemplate>
      <DataTemplate>
        <TextCell Text="{Binding ., Converter={x:Reference page}}" />
      </DataTemplate>
    </ListView.ItemTemplate>
  </ListView>
</ContentPage>

I don't know if the converter is necessary to repro the bug. I happened to need it for the work I was doing.

To work around the bug, in the class for the source items, I added this property:

public class MyItem
{
  // ...
  public MyItem Self => this;
}

Then I changed the binding from "." to "Self", so it looked like this:
{Binding Self, Converter={x:Reference page}}

Then it worked. I tested the failure and the workaround on UWP.
Comment 1 Paul DiPietro [MSFT] 2017-09-05 14:40:31 UTC
Stephane, do you know anything about this at all? Edward, have you also tested against the 2.4.0 prerelease or nightly builds to see if the issue is still not resolved?
Comment 2 Edward Brey 2017-09-05 14:42:19 UTC
I've only tested against 2.3.4.270.
Comment 3 Stephane Delcroix 2017-09-06 09:03:50 UTC
I don't understand what you're trying to do here with your page as converter.

you should attach the smallest possible reproduction project so we can:
- acknowledge that the issue exists,
- debug it and solve it,
- validate that the fix solve your issue.
Comment 4 Edward Brey 2017-09-13 13:30:36 UTC
I'm using the page as the converter because the page has state that is needed for the conversion. In my particular case, the page is a for a filtered list item, and the page contains the search string. The main code is below, but I expect you should be able to reproduce the bug by wiring up any page as a converter. Let me know if not.

	public partial class FilteredListPage : ContentPage, IValueConverter
	{
		readonly List<ListItem> items;

		public FilteredListPage(List<ListItem> items)
		{
			this.items = items;
			BindingContext = this;
			InitializeComponent();
			Filter(null, null);
		}

		void Filter(object sender, TextChangedEventArgs e)
		{
			string search = searchBar.Text;
			if (string.IsNullOrEmpty(search)) {
				listView.ItemsSource = items;
			} else {
				var includedItems = new List<IncludedListItem>();
				foreach (var item in items) {
					var searchIndex = item.Name.IndexOf(search, StringComparison.CurrentCultureIgnoreCase);
					if (searchIndex >= 0) includedItems.Add(new IncludedListItem { Item = item, SearchIndex = searchIndex });
				}
				listView.ItemsSource = includedItems;
			}
		}

		/// <summary>
		/// Makes bold the first occurrence of the search term in the text.
		/// </summary>
		public object Convert(object value, Type targetType, object parameter, CultureInfo culture)
		{
			if (value == null) return null;
			if (value is string detail) return HideDetail ? null : detail;
			if (value is ListItem filteredItem) return filteredItem.Name;

			var includedItem = (IncludedListItem)value;
			string text = includedItem.Item.Name;
			int index = includedItem.SearchIndex;
			string search = searchBar.Text;

			var highlighted = new FormattedString();
			highlighted.Spans.Add(new Span { Text = text.Substring(0, index) });
			highlighted.Spans.Add(new Span { Text = text.Substring(index, search.Length), FontAttributes = FontAttributes.Bold });
			highlighted.Spans.Add(new Span { Text = text.Substring(index + search.Length) });
			return highlighted;
		}

		public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture) => throw new NotSupportedException();

		public struct IncludedListItem
		{
			public ListItem Item;
			public int SearchIndex;
			public string Detail => Item.Detail;

			public IncludedListItem Self => this; // Workaround for bug: https://bugzilla.xamarin.com/show_bug.cgi?id=59220
		}
	}

	public class ListItem
	{
		public ListItem(string id, string name, string detail = null) { Id = id; Name = name; Detail = detail; }
		public readonly string Id;
		public string Name { get; }
		public string Detail { get; }

		public ListItem Self => this; // Workaround for bug: https://bugzilla.xamarin.com/show_bug.cgi?id=59220
	}
Comment 5 Stephane Delcroix 2017-09-14 13:43:46 UTC
We did try to reproduce your issue with the following Xaml

<ContentPage xmlns="http://xamarin.com/schemas/2014/forms"
    xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
    x:Class="Xamarin.Forms.Controls.Issues.Bugzilla59220"
    x:Name="page"
    Title="59220">
    <ListView ItemsSource="{Binding Source}">
     <ListView.ItemTemplate>
      <DataTemplate>
        <TextCell Text="{Binding ., Converter={x:Reference page}}" />
      </DataTemplate>
    </ListView.ItemTemplate>
  </ListView>
</ContentPage>

and code behind

	public partial class Bugzilla59220 : ContentPage, IValueConverter
	{
		public Bugzilla59220()
		{
			InitializeComponent();
			BindingContext = new {
				Source = new List<ListItem> {
					new ListItem {Name="Foo"},
					new ListItem {Name="Bar"},
				}
			};
		}

		public object Convert(object value, Type targetType, object parameter, CultureInfo culture)
			=> (value as ListItem)?.Name;

		public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture)
			=> throw new NotImplementedException();

		public class ListItem
		{
			public string Name { get; set; }
		}
	}

Everything works correctly, on iOS and UWP. The dot binding is resolved, the converter is invoked.

I'm closing this bug
Comment 6 Edward Brey 2017-09-14 23:16:18 UTC
It looks like the problem only occurs when the list items are of value types instead of reference types. Meaning in my repro, "class ListItem" should be "struct ListItem". I thought when I reported the bug, however, that there was something else involved, and that class vs. struct didn't matter. But when playing with your repro a bit, using a struct was the only way I could break it.

So if it's of interest to you how a list of value types works with ListView.ItemsSource, feel free to take a look. If that's not a supported approach anyway, you might as well just keep the bug closed.
Comment 7 Stephane Delcroix 2017-09-15 08:11:26 UTC
I tested yesterday with value types and retested today. works fine.
Comment 8 Edward Brey 2017-09-15 13:10:18 UTC
When I changed ListItem from class to struct, your repro no longer compiled. The expression in the converter is invalid for structs:

    (value as ListItem)?.Name

so I changed it to this:

    ((ListItem)value).Name

When I did that, your repro crashed. The reason isn't related to value or reference types after all; it crashes if ListItem is a class, too. The problem is that Convert is being called too often. For each item in the list, it's being called three times, once with null, once with the page, and once with the list item. This is the actual bug. Convert should be called only once for each list item, and only with ListItem as its type.
Comment 9 Stephane Delcroix 2017-09-15 13:49:26 UTC
> It looks like the problem only occurs when the list items are of value types 

and then

> The reason isn't related to value or reference types after all

please make up your mind, create a valid repro then create a new ticket with a valid description and the repro ATTACHED to the bug. Continuing the discussion here with what appear to be a different issue would be confusing.

Thanks a lot

The XF Team
Comment 10 Edward Brey 2017-09-15 13:57:04 UTC
Remember, we're working collaboratively to track down an odd problem. I was trying to discover the difference between your repro and mine, and see why yours worked and mine failed. None of knew. I put forth what seemed like the cause (value vs. reference), but I did qualify that suggestion as a guess by saying, "I thought when I reported the bug, however, that there was something else involved, and that class vs. struct didn't matter."

I didn't know enough at the time to make up my mind - nor did anyone. We were all just doing the best we could. Now with further digging, the problem is more clear. I hope you don't feel frustrated; there are lots of scenarios that can trigger bugs, and it's hard to know which path to go down in making a minimal repro, especially when none of us wants to spend too much time on it.

Fortunately, I think it should be easy now with what we know to create a new minimal bug report, as you requested.
Comment 11 Edward Brey 2017-09-15 14:21:42 UTC
This is the replacement bug report: https://bugzilla.xamarin.com/show_bug.cgi?id=59520