5

I noticed this strange behavior yesterday when putting together a demo application for WP. Usually I never pass in plain items, and never the ‘same’- but this time I did. When bound to an observablecollection of type int or string, if the value is the same, the control will add first one item, then two, then three, then four. Instead of adding one each time. Basically it does this:

Items.Count ++

If I however change the item, say string, to something unique (a GUID.ToString for example) it has the expected behavior.

This is regardless of how the DataContext is set, or if I add LayoutMode="List" IsGroupingEnabled="False" to the control.

Why does it do this? And is this expected behavior?

I spent hours searching for an answer, so please don’t just paste random links about the control :)

Code for the view:

<phone:PhoneApplicationPage
x:Class="Bug.MainPage"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:phone="clr-namespace:Microsoft.Phone.Controls;assembly=Microsoft.Phone"
xmlns:shell="clr-namespace:Microsoft.Phone.Shell;assembly=Microsoft.Phone"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
mc:Ignorable="d"
FontFamily="{StaticResource PhoneFontFamilyNormal}"
FontSize="{StaticResource PhoneFontSizeNormal}"
Foreground="{StaticResource PhoneForegroundBrush}"
SupportedOrientations="Portrait" Orientation="Portrait"
shell:SystemTray.IsVisible="True"
DataContext="{Binding RelativeSource={RelativeSource Self}}">

<StackPanel>
    <Button Click="Button_Click">Add</Button>
    <phone:LongListSelector  Width="300" Height="600" ItemsSource="{Binding Items}"/>
</StackPanel>
</phone:PhoneApplicationPage>

Code behind:

using System.Collections.ObjectModel;
using System.Windows;


namespace Bug
{
    public partial class MainPage
    {
        // Constructor
        public MainPage()
        {
            InitializeComponent();

            Items = new ObservableCollection<string>();

            //DataContext = this;
        }

        public ObservableCollection<string> Items { get; set; }

        private void Button_Click(object sender, RoutedEventArgs e)
        {
            Items.Add("A");

            // This works as expected
            //Items.Add(Guid.NewGuid().ToString());
        }
    }

}
Iris Classon
  • 5,752
  • 3
  • 33
  • 52
  • So Items.Add is only hit once everytime, but the collection goes from 1, 3, 6, 10, etc. ? – invalidusername Mar 25 '14 at 12:29
  • 1
    I've also [spotted this problem](http://stackoverflow.com/questions/19356668/long-list-selector-observable-collection-and-visual-tree-problems), and unfortunately I haven't found solution up to now. If you do not use Grouping in LLS then use ListBox - without this problem. – Romasz Mar 25 '14 at 12:31
  • ivalidusername: no it adds items.count plus one. Try the code. @Romasz the docs state that the LLS is recommended over LB for performance reasons AFAIK – Iris Classon Mar 25 '14 at 12:43
  • @IrisClasson I hadn't worked with many itmes in a collection so I can't say about performance issues. Because of the problem with LLS, I've resinged from using it. ANd as I remember now there where also some problems while deleting items (with the size of LLS). – Romasz Mar 25 '14 at 12:59

4 Answers4

2

This is a bug in LongListSelector. The reason it works with Guid is because this'll do a reference comparison and avoid the bug.

Here's a workaround by using a reference object instead:

public partial class MainPage : PhoneApplicationPage
{
    // Constructor
    public MainPage()
    {
        InitializeComponent();

        // Sample code to localize the ApplicationBar
        Items = new ObservableCollection<Object>();

    }
    public ObservableCollection<Object> Items { get; set; }

    private void Button_Click(object sender, RoutedEventArgs e)
    {
        Items.Add(new Object() { Value = "A" });

        // This works as expected
        //Items.Add(Guid.NewGuid().ToString());
    }

}
public class Object
{
    public string Value { get; set; }

    public override string ToString()
    {
        return Value;
    }
}
dotMorten
  • 1,953
  • 1
  • 14
  • 10
  • Laurent helped me submit the bug to the WP team, but I think by now with so many trying this we can confirm its a bug. I selected this as the answer since it provides a solution (I know it wasn't the question- but many might come to the Q looking for a solution). Thank, as always :) – Iris Classon Mar 25 '14 at 14:38
  • No worries. Btw, I didn't mean to say "reference comparison", I meant that the value you add there each time is different. The issue also happens with for instance 'int'. Add the same number again and again and you see the same behavior. Add a different number and you don't. – dotMorten Mar 25 '14 at 14:41
  • Yup, I noticed that. I'd like to know what it actually does when it adds the items.. – Iris Classon Mar 25 '14 at 14:42
  • I don't have the assemblies to reflect on right here, but it's pretty safe to assume there's a "value == value2" some where and that logic probably shouldn't be there or is wrong. So the trick is to avoid value==value2 resolving to true. – dotMorten Mar 25 '14 at 14:48
0

This is by no means an answer, but I modified @IrisClasson's original code a bit to highlight the problem more. If you alternate between adding an item "A" and an item "B" the list will show weird behavior when adding either item twice in a row, but will correct itself when you add the other item. I added a ListBox bound to the same data so that you can see how the list should look.

View:

<phone:PhoneApplicationPage
    x:Class="Bug.MainPage"
    xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
    xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
    xmlns:phone="clr-namespace:Microsoft.Phone.Controls;assembly=Microsoft.Phone"
    xmlns:shell="clr-namespace:Microsoft.Phone.Shell;assembly=Microsoft.Phone"
    xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
    xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
    mc:Ignorable="d"
    FontFamily="{StaticResource PhoneFontFamilyNormal}"
    FontSize="{StaticResource PhoneFontSizeNormal}"
    Foreground="{StaticResource PhoneForegroundBrush}"
    SupportedOrientations="Portrait" Orientation="Portrait"
    shell:SystemTray.IsVisible="True"
    DataContext="{Binding RelativeSource={RelativeSource Self}}">

    <StackPanel>
        <StackPanel Orientation="Horizontal">
            <Button Click="Button_Click">Add A</Button>
            <Button Click="Button_Click1">Add B</Button>
            <Button Click="Button_Click2">Clear</Button>
        </StackPanel>
        <StackPanel Orientation="Horizontal" HorizontalAlignment="Center">
        <phone:LongListSelector x:Name="MyLongList"  Width="200" Height="600" 
                                 ItemsSource="{Binding Items}"/>
        <ListBox x:Name="MyListBox" Width="200" Height="600" 
                 ItemsSource="{Binding Items}" />
    </StackPanel>
    </StackPanel>
</phone:PhoneApplicationPage>

CodeBehind:

using System.Collections.ObjectModel;
using System.Diagnostics;
using System.Windows;
using Microsoft.Phone.Controls;

namespace Bug
{
    public partial class MainPage : PhoneApplicationPage
    {
        // Constructor
        public MainPage()
        {
            InitializeComponent();

            Items = new ObservableCollection<string>();

            Items.CollectionChanged += Items_CollectionChanged;
        }

        void Items_CollectionChanged(object sender, System.Collections.Specialized.NotifyCollectionChangedEventArgs e)
        {
            Debug.WriteLine("CollectionChanged");
        }

        public ObservableCollection<string> Items { get; private set; }

        private void Button_Click(object sender, RoutedEventArgs e)
        {
            Items.Add("A");

            // This works as expected
            //Items.Add(Guid.NewGuid().ToString());
        }

        private void Button_Click1(object sender, RoutedEventArgs e)
        {
            Items.Add("B");

            // This works as expected
            //Items.Add(Guid.NewGuid().ToString());
        }

        private void Button_Click2(object sender, RoutedEventArgs e)
        {
            Items.Clear();
        }
    }
}
robwirving
  • 1,790
  • 12
  • 17
0

I think it is somehow related to Two different "strings" are the same object instance?, so called String Interning http://en.wikipedia.org/wiki/String_interning, which means that it doesn't matter how many "A" string you will create it will still point to the same memory.

For example: if you try to save "Guid.NewGuid().ToString())" into a private field and use that instance for "Items.Add(obj)" it will result in the same issue.

Also the count for Items is correct, so it must be a bug on LLS and the way it is rendering same-instance-objects.

Community
  • 1
  • 1
Jevgeni Tsaikin
  • 321
  • 1
  • 5
-1

I am pretty sure it's bug of LongListSelector.

I think, when it loads diff between data, it takes all apperance of "A" in collection into list.

Let's wait for WP8.1, probably LongListSelector will be deprecated :-)

VJ

vjirovsky
  • 208
  • 3
  • 5