Desperate for help with ListView/Observable Collection Display/Binding Issues!

Hi all,
I'm running into infuriating issues with my ListView returning empty cells. I know for sure that it is receiving data from the OnPropertyChanged because I can count how many elements are in the list and it corresponds to how many I expect in the collection. The cells also scale to how large the text within them is. I can even tap on the cells and receive ItemSelected notifications!

However, I can't for the life of me get the cells to display the actual text. I've tried everything from switching to XAML to clearing the collection and reading elements when the ListView is rendered, to changing the ListView to a stacklayout instead of contentView, and also changing the HasUnevenRows and Row Height.

I have a pretty standard View hierarchy, but I'll let the code speak for itself.

Here is the top of the View heirarchy in my DashboardPage.

DashboardPage.cs
</p> <pre><code> #region jobs-in-dashboard view var dashboardJobsView = new DashboardJobsView() { BindingContext = _DashboardJobsViewModel = new DashboardJobsViewModel() }; #endregion #region create view heirarchy _ScrollView = new ScrollView { Content = new StackLayout { Padding = new Thickness(10, 0), Spacing = 0, Children = { dashboardJobsView } } }; Content = _ScrollView; #endregion

Then the DashboardJobsViewModel...
<br /> public class DashboardJobsViewModel : BaseViewModel<br /> {<br /> IDataService _DataClient;</p> <pre><code> ObservableCollection<Job> _Jobs; public ObservableCollection<Job> Jobs { get { return _Jobs; } set { _Jobs = value; OnPropertyChanged("Jobs"); System.Diagnostics.Debug.WriteLine("JOBS PROPERTY CHANGED"); } } User _User; public User User { get { return _User; } set { _User = value; OnPropertyChanged("User"); } } string _AuthToken; public string AuthToken { get { return _AuthToken; } set { _AuthToken = value; OnPropertyChanged("AuthToken"); } } public bool NeedsRefresh { get; set; } public DashboardJobsViewModel(INavigation navigation = null) : base(navigation) { Jobs = new ObservableCollection<Job>(); _DataClient = DependencyService.Get<IDataService>(); MessagingCenter.Subscribe<User>(this, "SAVE_ACCOUNT", async (sender) => { User = sender; await ExecuteLoadSeedDataCommand(); }); IsInitialized = false; } Command _LoadJobsCommand; /// <summary> /// Used for pull-to-refresh of Jobs list /// </summary> /// <value>The load leads command, used for pull-to-refresh.</value> public Command LoadJobsCommand { get { return _LoadJobsCommand ?? (_LoadJobsCommand = new Command(ExecuteLoadJobsCommand, () => !IsBusy)); } } Command _LoadSeedDataCommand; /// <summary> /// When this view is instantiated, get a fresh copy of the data /// </summary> /// <value>The load seed data command.</value> public Command LoadSeedDataCommand { get { return _LoadSeedDataCommand ?? (_LoadSeedDataCommand = new Command(async () => await ExecuteLoadSeedDataCommand())); } } /// <summary> /// Gets the observable collection of jobs that we will use. /// </summary> /// <returns>The load seed data command.</returns> public async Task ExecuteLoadSeedDataCommand() { var tempJobs = new ObservableCollection<Job>(); if (IsBusy) return; IsBusy = true; _DataClient.AzureAuthToken = _User.CurrentAuthToken; //await _DataClient.SeedLocalDataAsync(); tempJobs = (await _DataClient.GetJobsAsync()).ToObservableCollection(); Jobs = tempJobs; IsInitialized = true; IsBusy = false; } /// <summary> /// Executes the LoadJobsCommand. /// </summary> public async void ExecuteLoadJobsCommand() { if (IsBusy) return; IsBusy = true; LoadJobsCommand.ChangeCanExecute(); Jobs.Clear(); Jobs.AddRange(await _DataClient.GetJobsAsync()); IsBusy = false; LoadJobsCommand.ChangeCanExecute(); } }

}

Then the DashboardJobsView itself.
<br /> public class DashboardJobsView : ModelBoundContentView<DashboardJobsViewModel><br /> {</p> <pre><code> public DashboardJobsView() { #region Header label for this section var JobsViewLabel = new PoppinsLightLabel(); JobsViewLabel.Text = "Job Matches"; JobsViewLabel.FontSize = 20; #endregion #region dashboardJobListView var dashboardJobListView = new DashboardJobListView(); dashboardJobListView.SetBinding(ItemsView<Cell>.ItemsSourceProperty, "Jobs"); dashboardJobListView.ItemTapped += (sender, e) => { System.Diagnostics.Debug.WriteLine("Item in dashboardJobList tapped"); }; #endregion #region compose view hierarchy Content = new StackLayout() { Spacing = 0, Children = { JobsViewLabel, dashboardJobListView } }; #endregion } }

}

Then DashboardJobsListView:
<br /> public class DashboardJobListView : NonPersistentSelectedItemListView<br /> {<br /> public DashboardJobListView()<br /> {<br /> HasUnevenRows = true; // Circumvents calculating heights for each cell individually. The rows of this list view will have a static height.<br /> RowHeight = 100; // set the row height for the list view items<br /> SeparatorVisibility = SeparatorVisibility.None; // make the row separators invisible, per the intended design of this app<br /> ItemTemplate = new DataTemplate(typeof(JobListItemCell));<br /> }<br /> }<br />

...And finally the custom ViewCell!
<br /> using System;<br /> using Xamarin.Forms;<br /> using Anyopp.Shared.Renderers;<br /> namespace Anyopp.Shared.Cells<br /> {<br /> public class JobListItemCell : ViewCell<br /> {<br /> public Label JobNameLabel { get; private set; }</p> <pre><code> public Label JobDescriptionLabel { get; private set; } public Label JobLocalityLabel { get; private set; } //public Label JobStartDateLabel { get; private set; } public JobListItemCell() { #region JobNameLabel JobNameLabel = new Label () { TextColor = Color.FromHex("FF7B13"), FontSize = Device.GetNamedSize(NamedSize.Small, typeof(Label)) * 1.2, VerticalTextAlignment = TextAlignment.End, LineBreakMode = LineBreakMode.TailTruncation }; JobNameLabel.SetBinding( Label.TextProperty, new Binding("Job.Name")); #endregion #region JobDescriptionLabel JobLocalityLabel = new Label() { TextColor = Color.FromHex("5E6163"), FontSize = Device.GetNamedSize(NamedSize.Small, typeof(Label))*0.8, VerticalTextAlignment = TextAlignment.End, LineBreakMode = LineBreakMode.TailTruncation }; JobLocalityLabel.SetBinding( Label.TextProperty, new Binding("Job.Locality")); #endregion #region JobDescriptionLabel JobDescriptionLabel = new Label() { TextColor = Color.FromHex("5E6163"), FontSize = Device.GetNamedSize(NamedSize.Small, typeof(Label)), VerticalTextAlignment = TextAlignment.End, LineBreakMode = LineBreakMode.TailTruncation }; JobDescriptionLabel.SetBinding( Label.TextProperty, new Binding("Job.Description")); #endregion // A ContentView, which will serve as the "top-level" of the cell's view hierarchy. // It also allows a Padding to be set; something that can't be done with a plain View. var contentView = new ContentView(); // set the padding of the contentView contentView.Padding = new Thickness(10, 0); // A container for the "top-level" of the cell's view hierarchy. RelativeLayout relativeLayout = new RelativeLayout(); // add the JobNameLabel to the relativeLayout relativeLayout.Children.Add( view: JobNameLabel, xConstraint: Constraint.RelativeToParent(parent => 0), yConstraint: Constraint.RelativeToParent(parent => 0), widthConstraint: Constraint.RelativeToParent(parent => parent.Width), heightConstraint: Constraint.RelativeToParent(parent => parent.Height / 3)); // add the JobLocalityLabel to the relativeLayout relativeLayout.Children.Add( view: JobLocalityLabel, xConstraint: Constraint.RelativeToParent(parent => 0), yConstraint: Constraint.RelativeToParent(parent => parent.Height / 3), widthConstraint: Constraint.RelativeToParent(parent => parent.Width / 2), heightConstraint: Constraint.RelativeToParent(parent => parent.Height / 3)); // add the JobDescriptionLabel to the relativeLayout relativeLayout.Children.Add( view: JobDescriptionLabel, xConstraint: Constraint.RelativeToParent(parent => 0), yConstraint: Constraint.RelativeToParent(parent => parent.Height), widthConstraint: Constraint.RelativeToParent(parent => parent.Width), heightConstraint: Constraint.RelativeToParent(parent => parent.Height / 2)); // Assign the relativeLayout to Content of contentView // This lets us take advantage of ContentView's padding. contentView.Content = relativeLayout; // assign contentView to the View property View = contentView; } }

}

Answers

  • Nick-BNick-B USMember ✭✭

    did you ever get help on this? I am stuck with the same thing, and my code is damn near the same as yours.

  • GaetanFGaetanF USMember ✭✭

    Try modify the bindings: the target name should be exactly the name of the property you want to bind to, like "JobNameLabel" instead of "Job.Name"

Sign In or Register to comment.