0
votes

For my application I am trying to accomplish the following:

Image of desired result

  • When a switch gets Toggled (either true/false) set the content of the date label to a new value that gets generated by the Switch toggle and refresh the listview to immediate display the new content of that label.

The values get changed correctly behind the scenes when in the debugger but the label's won't update.

The idea of the code : In the App.Xaml.CS we fill each ObservableCollection from the DbClub class which is used as a sort of hardcoded Database class.

On my TabbedPage there are dynamic created ContentPages based on how many items are saved in the corresponding ObservableCollection.

foreach (var toets in dbClub.Toetsen)
 {
     MaakTabjeDynamischAan(toets); // GenerateTabs which takes toets as parameter
  }

On each ContentPage I dynamicly create a new ListView which takes a grid to display the items. The grid/listview has 2 labels and a switch per ViewCell as children.

private void MaakTabjeDynamischAan(Toets toets)
 {        
    ListView listview = new ListView 
     {
        BindingContext = this,

        ItemTemplate = new DataTemplate(() =>
        {
          // Create views with bindings for displaying each property.
           Label nameLabel = new Label();
           Label birthdayLabel = new Label();                   
           Switch sSwitch = new Switch();

          // Set Bindings for each property
           nameLabel.SetBinding(Label.TextProperty, "opdrNaam");
           birthdayLabel.SetBinding(Label.TextProperty, new Binding("sBehaaldInfo"));
           sSwitch.SetBinding(Switch.IsToggledProperty, new Binding("YesNo"));

          nameLabel.TextColor = Color.White;
          birthdayLabel.TextColor = Color.White;


          // Attach toggle event to switch
           sSwitch.Toggled += mooieSwitch_Toggled;


          // Create Content Grid for layouting listview itemsource
            var grid = new Grid();

          // Define Background for listview grid

          grid.Padding = 5;
          grid.BackgroundColor = Color.FromRgb(34, 35, 38);

          // Add labels to their representative grid columns
          grid.Children.Add(nameLabel);
          grid.Children.Add(birthdayLabel, 1, 0);
          grid.Children.Add(sSwitch, 2, 0);


          ColumnDefinition gridCol1 = new ColumnDefinition();
          ColumnDefinition gridCol2 = new ColumnDefinition();
          ColumnDefinition gridCol3 = new ColumnDefinition();

          gridCol1.Width = new GridLength(60, GridUnitType.Star);
          gridCol2.Width = new GridLength(20, GridUnitType.Star);
          gridCol3.Width = new GridLength(20, GridUnitType.Star);

          grid.ColumnDefinitions.Add(gridCol1);
          grid.ColumnDefinitions.Add(gridCol2);
          grid.ColumnDefinitions.Add(gridCol3);

          // Return an assembled ViewCell.
         return new ViewCell {View = grid};
       }),

            ItemsSource = toets.Opdrachten
        };
        listview.SeparatorVisibility = SeparatorVisibility.None;

 // Code for creating the actual new ContentPage

        var page = new ContentPage
        {

            Title = toets.ToetsNaam,
            Content = new StackLayout
            {
                Children =
                {

                    listview
                }
            }

        };
        this.Children.Add(page);
    }

The content of the labels and the IsToggled property of those children are bound to seperate classes. The primary class is in this case 'DbClub' which holds 3 ObservableCollections from their inherited classes which have the properties that the ListView gets bound to.

public class DbClub
{
    public string Clubnaam; // Don't bother with this property
    public ObservableCollection<Speler> Spelers;
    public ObservableCollection<Instelling> Instellingen;
    public ObservableCollection<Toets> Toetsen;

   // Each Toets takes 3 properties : int Id, string Name, OC<Opdracht> 
   // Where OC<Opdracht> takes Opdrachten to display
}

When a Switch gets toggled the Opdracht becomes true and in this case a DateTime is generated which we want to display in the bounded Label. In order to accomplish this we have made a class called 'ToetsCijfers'

public class ToetsCijfers
{
    public int toetsNr;
    public ObservableCollection<Behaald> opdrachten;
}

public class Behaald
{
    public int opdrNr;
    public DateTime datumBehaald;
    public string dtString; 

}

public class Opdracht
{
    public int opdrNr { get; set; }
    public string opdrNaam { get; set; }
    public bool YesNo { get; set; }
    public string sBehaaldInfo { get; set; }

}

So whenever a switch is Toggled we call the SetBehaaldInfo method after we decided whether the Opdracht has been achieved, if its true the DateTime becomes > 2001 and if it is false we set the DateTime to 1999, 1, 1

private void mooieSwitch_Toggled(object sender, ToggledEventArgs e)
{

   if (curOpdracht == null)
   {
      curOpdracht = new Behaald {opdrNr = tabbedopdrNr};
      curToetsSpeler.opdrachten.Add(curOpdracht);


  }

    curOpdracht.datumBehaald = e.Value ? DateTime.Now.Date : new DateTime(1999, 1, 1);
SetBehaaldInfo(toets.ToetsId, curOpdracht);

SetBehaaldInfo() method :

    private void SetBehaaldInfo(int curToetsNr, Behaald curBehaald)
    {


        var currentToets = dbClub.Toetsen.FirstOrDefault(t => t.ToetsId == curToetsNr);
        if (currentToets != null)
        {
            // var curOpdr = currentToets.Opdrachten.FirstOrDefault(o => o.opdrNr == curBehaald.opdrNr);
            var curOpdracht = currentToets.Opdrachten.FirstOrDefault(o => o.opdrNr == curBehaald.opdrNr);
            if (curOpdracht != null && curBehaald.datumBehaald >= new DateTime(2000, 1, 1))
            {
                curOpdracht.YesNo = true;

                curOpdracht.sBehaaldInfo = curBehaald.datumBehaald.ToString("dd-MM-yy");


            }
            else
            {
                if (curOpdracht != null)
                    curOpdracht.sBehaaldInfo = "n.n.b";
        }
    }
}

Methods I've tried:

  • I've tried to change the ItemsSource to an empty Collection or to null and afterwards set the ItemsSource to its original ItemsSource to force a refresh
  • I've tried to use PropertyChanged() but I am not certain on how to implement this the correct way, and makes no difference at the moment.

I am aware that OC's only update when its collection is changed by Add/Remove or Replace. Not its content, so that's the problem here.

Any ideas? Help would be appreciated. Excuse me if the code is not formatted properly, quite new to asking questions on SO. But don't reply with use Google more often, check SO for solutions, been there done that, none of them apply to what I am doing.

If requested I will post a link to the SourceCode to review.

1
The example is nowhere near minimal.aaron
Please explain yourself instead of referring to different pages.ldebrouwer
if you want changes to properties IN your Behaald class to be reflected in the UI, then Behaald needs to implement INotifyPropertyChanged.Jason
I get a can't load memory exception when I implement INotifyPropertyChanged I tried that, maybe I'm doing that wrong. Could you give me an example?ldebrouwer
there are already thousands of examples of INPC on SO and elsewhere on the web. If you're getting an error, update your question to show the specific error/exception.Jason

1 Answers

0
votes

Alright. After a week of research, I've figured out how to solve my problem. I was implementing INotifypropertyChanged on the wrong classes which caused a null reference because no properties were changed and this wasn't handled properly.

Instead of applying it to my Opdrachten class I applied it to the Behaald class. I applied it on the YesNo & sBehaaldInfo properties like this where the actual properties change and then it worked.

public class Opdracht : INotifyPropertyChanged
{
    private bool _yesNo;
    private string _sBehaaldInfo;


    public int OpdrNr { get; set; }
    public string OpdrNaam { get; set; }

    public bool YesNo
    {
        get { return _yesNo; }
        set
        {
            _yesNo = value;
            OnPropertyChanged(nameof(YesNo));
        }
    }

    public string SBehaaldInfo
    {
        get { return _sBehaaldInfo; }
        set
        {
            _sBehaaldInfo = value;
            OnPropertyChanged(nameof(SBehaaldInfo));
        }
    }

    public event PropertyChangedEventHandler PropertyChanged;

    [NotifyPropertyChangedInvocator]
    protected virtual void OnPropertyChanged([CallerMemberName] string propertyName = null)
    {
        PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));

    }
}