Search code examples
listviewxamarin.formsdata-binding

ListView Cell Binding not finding Methods in Collection


I am trying to use MVVM and ListView for the first time. I am working from Xamarin.Forms multi column table GUI

My ViewModel looks like this:

public class CodeTableViewModel : BaseViewModel
{
    public static ObservableCollection<CodeTableRow> CodeTable { get; set; }

    public CodeTableViewModel()
    {
    }

    public class CodeTableRow : INotifyPropertyChanged
    {
        public event PropertyChangedEventHandler PropertyChanged;
        int codePoint;

        public CodeTableRow(int point)
        {
            codePoint = point;
        }

        public string Decimal
        {
            get
            {
                return codePoint.ToString("D");
            }
        }

        public string Hex
        {
            get
            {
                return codePoint.ToString("X2");
            }
        }

        public string Ascii
        {
            get
            {
                return ((char)codePoint).ToString();
            }
        }
...

My XAML looks like this

<ContentPage xmlns="http://xamarin.com/schemas/2014/forms"
             xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml" xmlns:viewmodels="clr-namespace:RefCard.ViewModels"
             x:Class="RefCard.Views.CodeTablePage"
             Title="Code Table">
    <ContentPage.Content>
        <ListView x:DataType="viewmodels:CodeTableViewModel" ItemsSource="{Binding CodeTable}">
            <ListView.Header>
                <Grid BackgroundColor="Black">
                    <Grid.ColumnDefinitions>
                        <ColumnDefinition Width="*"></ColumnDefinition>
                        <ColumnDefinition Width="*"></ColumnDefinition>
                        <ColumnDefinition Width="*"></ColumnDefinition>
                    </Grid.ColumnDefinitions>
                    <Label Text="Dec" HorizontalOptions="Fill"  Grid.Column="0"   FontSize="Medium" FontAttributes="Bold" BackgroundColor="MediumBlue" TextColor="White" HorizontalTextAlignment="Center" Margin="1"/>
                    <Label Text="Hex" HorizontalOptions="Fill"  Grid.Column="1"  FontSize="Medium" FontAttributes="Bold" BackgroundColor="MediumBlue" TextColor="White" HorizontalTextAlignment="Center" Margin="0"/>
                    <Label Text="ASCII" HorizontalOptions="Fill"  Grid.Column="2"  FontSize="Medium" FontAttributes="Bold" BackgroundColor="MediumBlue" TextColor="White" HorizontalTextAlignment="Center" Margin="0"/>
                </Grid>
            </ListView.Header>
            <ListView.ItemTemplate>
                <DataTemplate>
                    <ViewCell>
                        <Grid BackgroundColor="Black">
                            <Grid.ColumnDefinitions>
                                <ColumnDefinition Width="*"></ColumnDefinition>
                                <ColumnDefinition Width="*"></ColumnDefinition>
                                <ColumnDefinition Width="*"></ColumnDefinition>
                            </Grid.ColumnDefinitions>
                            <Label Grid.Column="0" Text ="{Binding Decimal}" HorizontalOptions="Fill" BackgroundColor="LightBlue" HorizontalTextAlignment="Center" Margin="1" TextColor="Black"></Label>
                            <Label Grid.Column="1" Text ="{Binding Hex}" HorizontalOptions="Fill" BackgroundColor="LightBlue" HorizontalTextAlignment="Center" Margin="0"></Label>
                            <Label Grid.Column="2" Text ="{Binding Ascii}" HorizontalOptions="Fill" BackgroundColor="LightBlue" HorizontalTextAlignment="Center" Margin="0"></Label>
                        </Grid>
                    </ViewCell>
                </DataTemplate>
            </ListView.ItemTemplate>
        </ListView>
    </ContentPage.Content>
</ContentPage>

In the page constructor I do CodeTableViewModel.CodeTable = new ObservableCollection<CodeTableViewModel.CodeTableRow>(); and a bunch of Add's.

I have tried hacking a bunch of variations, but the basic error is:

XFC0045 Binding: Property "Decimal" not found on "RefCard.ViewModels.CodeTableViewModel". RefCard C:\Users\Charles\source\repos\RefCard\RefCard\Views\CodeTablePage.xaml 38

How do I resolve that?


Solution

  • From your code, Jason have provided three suggestions for you.

    Firstly, you don't need to add x:DataType="viewmodels:CodeTableViewModel", because the ListView's ItemsSource="{Binding CodeTable}", then CodeTable list of CodeTableRow.

    Secondly, from this document Model-View-ViewModel Pattern,

    View—The view is responsible for defining the structure, layout, and appearance of what the user sees on screen.

    ViewModel—The view model implements properties and commands to which the view can data bind to, and notifies the view of any state changes through change notification events.

    Model—Model classes are non-visual classes that encapsulate the app's data.

    So I suggest you don't add the Model class in your ViewModel class. These are two independent classes.

    public class CodeTableViewModel
    {
        public  ObservableCollection<CodeTableRow> CodeTable { get; set; }
    
        public CodeTableViewModel()
        {
            CodeTable = new ObservableCollection<CodeTableRow>();
        }
    }
    
    public class CodeTableRow : INotifyPropertyChanged
    {
        public event PropertyChangedEventHandler PropertyChanged;
        int codePoint;
    
        public CodeTableRow(int point)
        {
            codePoint = point;
        }
    
        public string Decimal
        {
            get
            {
                return codePoint.ToString("D");
            }
        }
    
        public string Hex
        {
            get
            {
                return codePoint.ToString("X2");
            }
        }
    
        public string Ascii
        {
            get
            {
                return ((char)codePoint).ToString();
            }
        }
     }
    

    Usually, we get some data in ViewModel class, then binding ViewModel to current contentPage's BindingContext.

    public class CodeTableViewModel
    {
        public  ObservableCollection<CodeTableRow> CodeTable { get; set; }
    
        public CodeTableViewModel()
        {
            CodeTable = new ObservableCollection<CodeTableRow>()
            {
                new CodeTableRow(12),
                new CodeTableRow(18),
                new CodeTableRow(19),
                new CodeTableRow(121),
                new CodeTableRow(124)
            };
    
        }
    }
    
    public Page22()
    {
        InitializeComponent();
    
        this.BindingContext = new CodeTableViewModel();
    }
    

    If you still want to get data in ContentPage's constructor, you can take a look the following code, but I don't suggest it.

    public partial class Page22 : ContentPage, INotifyPropertyChanged
    {
        private CodeTableViewModel _viewmodel;
    
        public CodeTableViewModel viewmodel
        {
            get { return _viewmodel; }
            set
            {
                _viewmodel = value;
                RaisePropertyChanged("viewmodel");
            }
        }
    
        public Page22()
        {
            InitializeComponent();
            viewmodel = new CodeTableViewModel();
    
            viewmodel.CodeTable = new ObservableCollection<CodeTableRow>()
            {
                new CodeTableRow(12),
                new CodeTableRow(18),
                new CodeTableRow(19),
                new CodeTableRow(121),
                new CodeTableRow(124)
            };
    
            this.BindingContext = viewmodel;
        }
    
        public event PropertyChangedEventHandler PropertyChanged;
    
        public void RaisePropertyChanged(string propertyName)
        {
            PropertyChangedEventHandler handler = PropertyChanged;
            if (handler != null)
            {
                handler(this, new PropertyChangedEventArgs(propertyName));
            }
        }
    }