ListView Binding to ObservableCollection changes all values

Hi :)

I'm trying to create a page where I have a list of informations (each row should contain from, to, what and where). I also want to be able to add or remove rows.
Therefore I made a ListView with a DataTemplate fitting to what I need.
I created a DataType containing the four values I need for a row. Then I made an ObservableCollection of this DataType where I put all my rows in.
I'm binding this ObservableCollection as ItemsSource for my ListView and in my DataTemplate I'm binding the values from my Datatype.

When I run my application everything displays as I want it to, also adding/removing rows works. When I edit a field, e.g. the 'fromTime' (TimePicker), the value in this field changes - fine so far.
But now I'm facing a problem I can't get rid of:
It may look nice on the UI, but when I'm looking at the data, there's something wrong..
I have a seperate class for my data containing the ObservableCollection and a standart value (for adding new rows).
When I change a value, no matter in which row, all values of this type in my data-class change! (All of these values in the observableCollection and also the one in my standart value.)

Seems to me like I need to bind to these values inside the observableCollection at the right index.. But I'm new to Xamarin so I don't get it. I tried to find a solution for this in forums, but couldn't find anyone facing the same problem.
Now I hope you can help me as I'm starting to despair :s ;)


Here's my code:

XAML layout:

Text on top (inside ContentPage), because it's not rendered here ^^
xmlns="http://xamarin.com/schemas/2014/forms"
xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
xmlns:local="clr-namespace:xObjectMobile"
x:Class="xObjectMobile.PensumDetailPage"
BindingContext="local:Pensum"

<ContentPage.Content>
    <ListView x:Name="p_lstView" RowHeight="160" SeparatorColor="DodgerBlue" ItemsSource="{Binding pensumFields}">
        <ListView.Header>
            <StackLayout Orientation="Horizontal" HorizontalOptions="Fill">
                <Label x:Name="p_lblPensumDetailPage" Text="Pensum" HorizontalOptions="Center" Margin="20" FontSize="Large"/>
                <DatePicker x:Name="p_dpPensumDate" HorizontalOptions="EndAndExpand" Date="{Binding selectedDate}" IsEnabled="False"/>
            </StackLayout>
        </ListView.Header>
        <ListView.ItemTemplate>
            <DataTemplate>
                <ViewCell>
                    <StackLayout Orientation="Vertical" HorizontalOptions="Fill" Margin="15,0,15,0">
                        <StackLayout Orientation="Horizontal">
                            <Label x:Name="p_lblFrom" Text="Von" Margin="0,10,0,0"/>
                            <TimePicker x:Name="p_tpFromTimePensum" HorizontalOptions="FillAndExpand" Margin="0,15,0,0"/>
                            <Label x:Name="p_lblTo" Text="Bis" Margin="0,10,0,0"/>
                            <TimePicker x:Name="p_tpToTimePensum" HorizontalOptions="FillAndExpand" Time="{Binding toTime}" Margin="0,15,0,0"/>
                        </StackLayout>
                        <StackLayout Orientation="Horizontal">
                            <Label x:Name="p_lblType" Text="Art"/>
                            <Picker x:Name="p_picType" HorizontalOptions="FillAndExpand" SelectedIndex="{Binding selectedType}" ItemsSource="{x:Static local:Pensum.pensumTypes}"/>
                        </StackLayout>
                        <StackLayout Orientation="Horizontal">
                            <Label x:Name="p_lblLocation" Text="Ort"/>
                            <Entry x:Name="p_entLocation" HorizontalOptions="FillAndExpand" Text="{Binding location}"/>
                        </StackLayout>
                    </StackLayout>
                </ViewCell>
            </DataTemplate>
        </ListView.ItemTemplate>
        <ListView.Footer>
            <StackLayout Orientation="Horizontal" HeightRequest="60" VerticalOptions="End">
                <Image x:Name="p_btnAddField" Source="@drawable/plus.png" Margin="5">
                    <Image.GestureRecognizers>
                        <TapGestureRecognizer
                            Tapped="p_btnAddField_Clicked"/>
                    </Image.GestureRecognizers>
                </Image>
                <Image x:Name="p_btnRemoveField" Source="@drawable/minus.png" Margin="5">
                    <Image.GestureRecognizers>
                        <TapGestureRecognizer
                            Tapped="p_btnRemoveField_Clicked"/>
                    </Image.GestureRecognizers>
                </Image>
                <Button x:Name="p_btnSave" Text="Speichern" Clicked="p_btnSave_Clicked" FontSize="Large" HorizontalOptions="EndAndExpand"/>
            </StackLayout>
        </ListView.Footer>
    </ListView>
</ContentPage.Content>

DataType:
using System;

namespace xObjectMobile
{
public class PensumField
{
public TimeSpan fromTime { get; set; }
public TimeSpan toTime { get; set; }
public int selectedType { get; set; }
public string location { get; set; }
}

}

Data Class:
using System;
using System.Collections.Generic;
using System.Collections.ObjectModel;

namespace xObjectMobile
{
public class Pensum
{
public DateTime selectedDate { get; set; } = new DateTime(1998, 11, 21); //for now just a fixed date, not important
public PensumField standartPensumField { get; set; } = new PensumField //the standart field for adding a new row
{
fromTime = new TimeSpan(0, 8, 0, 0),
toTime = new TimeSpan(0, 16, 0, 0),
selectedType = 1,
location = "Berndorf"
};

    public static List<String> pensumTypes { get; set; }    //list for the picker to choose from
    public ObservableCollection<PensumField> pensumFields { get; set; } //ObservableCollection where the rows are stored in

    public Pensum()
    {
        pensumFields = new ObservableCollection<PensumField>();

        pensumTypes = new List<String>();
        //Fill PensumTypes
        pensumTypes.Add("type1");
        pensumTypes.Add("type2");
        pensumTypes.Add("type3");
    }
}

}

Code behind:
using System.Linq;
using Xamarin.Forms;
using Xamarin.Forms.Xaml;

namespace xObjectMobile
{
[XamlCompilation(XamlCompilationOptions.Compile)]
public partial class PensumDetailPage : ContentPage
{
Pensum data = new Pensum();
public PensumDetailPage()
{
InitializeComponent();
BindingContext = data; //setting the binding context (correct?)
setStandartPensumFields(); //create the first three rows
}
private void p_btnSave_Clicked(object sender, EventArgs e)
{
//To be added
}
private void p_btnAddField_Clicked(object sender, EventArgs e)
{
if (data.pensumFields.Count < 9)
{
data.pensumFields.Add(data.standartPensumField);
}
}
private void p_btnRemoveField_Clicked(object sender, EventArgs e)
{
if (data.pensumFields.Count >= 1)
{
data.pensumFields.Remove(data.pensumFields.Last()); //also very strange: it removes the top one in the list
}
}
private void setStandartPensumFields()
{
for (int i = 0; i < 3; i++)
{
data.pensumFields.Add(data.standartPensumField);
}
}
}
}

Best Answer

Answers

  • hi,
    i dont know :D

  • ClintStLaurentClintStLaurent USUniversity ✭✭✭✭✭

    Two things.
    1) Don't rely on the site parser to get your code right. Use proper markdown to tag your code.Tutorial here
    2) Your list is static - so yeah, its the same list used everywhere.
    public static List<String> pensumTypes { get; set; } //list for the picker to choose from

    If that's not the part you're referring to:
    1) Screen shots would help use see what you see
    2) Fixing the code would help.

  • MartinWechtitschMartinWechtitsch ATMember ✭✭

    @Mabrouk THANK YOU!

    Wow, that was an answer! Thank's a lot.

    It's not only working, I also understand how it should be done - what if very helpful for the next times ;)

    I just want to say that there's one thing that doesn't work: The SelectedType Picker
    I think it's pretty obvious why, because we're binding a string to the selectedIndex property of the picker, but haven't it fixed yet. :#

    Anyway, thank you for this fast and helpful answer.

    Greetings,
    Martin

  • MabroukMabrouk USMember ✭✭✭

    @MartinWechtitsch ,

    You can add to your ListView <ListView x:Name="p_lstView" RowHeight="160" SeparatorColor="DodgerBlue" ItemsSource="{Binding Items}" > the ItemSelected Event to be like :

    <ListView x:Name="p_lstView" RowHeight="160" SeparatorColor="DodgerBlue" ItemsSource="{Binding Items}" ItemSelected="ListView_ItemSelected">

    and you will get all of the SelectedItem as object:

    [XamlCompilation(XamlCompilationOptions.Compile)]
        public partial class PensumPage : ContentPage
        {
            private readonly PensumCollection _collection = new PensumCollection();
            public PensumPage()
            {
                InitializeComponent();
                BindingContext = _collection;
            }
            private void ListView_ItemSelected(object sender, SelectedItemChangedEventArgs e)
            {
                var item = e.SelectedItem as PensumField;
               // do something with your item :)
            }
        }
    

    Best regards from Regensburg,
    Mabrouk.

  • MartinWechtitschMartinWechtitsch ATMember ✭✭

    @Mabrouk ,
    Thanks :smile:

    I want to save my ObservableCollection in a seperate Data Class and load from there (if not null).
    I set this class as a public static class with an public static ObservableCollection in it (if not static then it would be gone after closing that page, right?) and in the PensumCollection.cs I either create a new ObservableCollection or use this one:
    Items = MyDataClass.StoredPensumFields;
    When I click on the save button I set:
    MyDataClass.SavedPensumFields = Items;

    But no matter if I click save or just go back with the back-button, it always saves and I don't understand why as I never call the set accessor :flushed:

    Best regards from Styria,
    Martin

  • MabroukMabrouk USMember ✭✭✭
    edited August 2017

    If i understand you well, you Need an AppDataContext, like :

    public class AppData
        {
            private static AppData _instance = null;
            public static AppData Instannce => _instance ?? (_instance = new AppData()
            {
        //here you can initialize your Items by default values ...
                SavedPensumFields = new ObservableCollection<PensumField>(),
                StoredPensumFields = new ObservableCollection<PensumField>()
            });
            public ObservableCollection<PensumField> StoredPensumFields { get; set; }
            public ObservableCollection<PensumField> SavedPensumFields { get; set; }
        }
    

    Than you can change your collection to :

    public class PensumCollection
        {
            public ICommand TapCommand { get; set; }
    
            public ObservableCollection<PensumField> Items
            {
                get { return AppData.Instannce.StoredPensumFields; }
                set { AppData.Instannce.SavedPensumFields = value; }
            }
    // ......
    }
    

    So, with that you can also add a cancel Event,

     case "save":
                        // do you work
                        AppData.Instannce.StoredPensumFields = AppData.Instannce.SavedPensumFields;
                        break;
     case "cancel":
                        // do you work
                        AppData.Instannce.SavedPensumFields = AppData.Instannce.StoredPensumFields;
                        break;
    

    Regards,
    Mabrouk.

  • MartinWechtitschMartinWechtitsch ATMember ✭✭

    @Mabrouk ,

    this works only for the first time and only when I go back with the cancel button I created.
    But when I go back with the navigation button it is still saved (also when I open the page for the first time).

    Maybe it was unclear what I wanted to do, so i'll try to explain again using a screenshot:

    When I open this page i usually want to change some values, for example the yellow underlined ones in the picture.
    When I'm done with that I need to save my changes by clicking on that purple marked button. But when I don't want to save my changes then I want to be able to go back without saving any changes by clicking the red marked button (or my hardware back-button).

    Right now no matter how I leave this page, all changes get saved and I don't know why :neutral:

    Greetings,
    Martin

  • MabroukMabrouk USMember ✭✭✭
    edited August 2017

    Now i get it, uin this case it's more easy :smile:

    1. AppData.cs

      public class AppData
              {
                  private static AppData _instance = null;
                  public static AppData Instannce => _instance ?? (_instance = new AppData()
                  {
                      SavedPensumFields = new ObservableCollection<PensumField>()
                  });
                  public ObservableCollection<PensumField> SavedPensumFields { get; set; }
              }
      
    2. In your collection's **constructor **:

          public ObservableCollection<PensumField> Items { get; set; } // Items prop should be like this
          public PensumCollection()
          {
              Items = AppData.Instannce.SavedPensumFields;
              TapCommand = new Command(OnImgTapped);
          }        
      
    3. So, in Events you dont Need to add a cancel btn, let it be like :

      case "save":
                          // do you work
                          AppData.Instannce.SavedPensumFields = Items;
                          break;
      

    I hope that helps,
    Mabrouk.

  • MartinWechtitschMartinWechtitsch ATMember ✭✭

    @Mabrouk
    I don't know what I'm doing wrong, but it still keeps saving all changes, no matter if I ever go into that "save" case or not.

    Thank you for being so patient with me,
    Martin

  • PankajNandurkarPankajNandurkar USMember ✭✭

    Hello

    In which memory(Internal or External memory) Xamarin Listview data is saved?

Sign In or Register to comment.