Search code examples
c#xamllistviewmaui

ListView not working when bound to an object


I can bind ItemsSource normally if the object is just a string, but when it's a record or class, the app won't start. And after a while, I get this error:

System.InvalidCastException: Specified cast is not valid

Here's my XAML markup:

<ListView x:Name="Posts">
    <ListView.ItemTemplate>
        <DataTemplate>
            <VerticalStackLayout Spacing="25" Margin="10,0" VerticalOptions="Center">
                <Frame BackgroundColor="#191919">
                    <VerticalStackLayout>
                        <VerticalStackLayout Margin="5">
                            <Label Text="{Binding Name}" FontSize="18" TextColor="Gray" />
                            <Label Text="{Binding Title}" FontSize="Large" FontAttributes="Bold" />
                        </VerticalStackLayout>
                        <Border>
                            <Border.StrokeShape>
                                <RoundRectangle CornerRadius="5" />
                            </Border.StrokeShape>
                            <Image MinimumHeightRequest="200" MaximumHeightRequest="350" Aspect="AspectFill" Source="{Binding Image}" />
                        </Border>
                        <HorizontalStackLayout Margin="0,5,0,0">
                            <Button ImageSource="upvote.png" BackgroundColor="Transparent" />
                            <Label Text="{Binding Votes}" FontSize="Medium" Margin="0, 10, 0, 0" TextColor="Gray" />
                            <Button ImageSource="downvote.png" BackgroundColor="Transparent" />
                        </HorizontalStackLayout>
                    </VerticalStackLayout>
                </Frame>
            </VerticalStackLayout>
        </DataTemplate>
    </ListView.ItemTemplate>
</ListView>

And here's the binding code:

List<Data> items = new() { new Data("User", "Lorem ipsum dolor sit amet", "21", "<Image url>") };
Posts.ItemsSource = items;

What am I doing wrong here?


Solution

  • Yes,you need to add <ViewCell> inside of your <DataTemplate>, the code will like this:

     <ListView x:Name="mListView" HasUnevenRows="True">
        <ListView.ItemTemplate>
            <DataTemplate>
                  <!-- add  ViewCell  here  -->
                <ViewCell>
                  <VerticalStackLayout Spacing="25" Margin="10,0" VerticalOptions="Center" >
                    <Frame BackgroundColor="Yellow">
                        <!--  other code -->
                    </Frame>
                </VerticalStackLayout>
                </ViewCell>
                
            </DataTemplate>
        </ListView.ItemTemplate>
    </ListView>
    

    And I find that you add two buttons( upvote and downvote) inside of the item, so I strongly recommend that you use the MVVM pattern.Then you can add ICommand to the ViewModel of your page and bind it to the Buttons.

    Besides,if you want to update the UI while changing the value of the binded property(e.g. Votes), you can implement interface INotifyPropertyChanged for your ViewModel and call event OnPropertyChanged for the property.

    I created a demo and achieved this function, you can refer to the following code:

    1.implement INotifyPropertyChanged for Data.cs and add property Votes as follows:

        private int _votes;
        public int Votes
        {
            set { SetProperty(ref _votes, value); }
            get { return _votes; }
        }
    

    Data.cs

       public class Data: INotifyPropertyChanged 
    {
        public Data(string v1, string v2, int v3, string v4)
        {
            this.Name = v1;
            this.Title = v2;
            this.Votes = v3;
            this.Image = v4;
        }
    
        public string Name { get; set; }
        public string Title { get; set; }
        //public string Votes { get; set; }
    
        private int _votes;
        public int Votes
        {
            set { SetProperty(ref _votes, value); }
            get { return _votes; }
        }
    
        public string Image { get; set; }
        bool SetProperty<T>(ref T storage, T value, [CallerMemberName] string propertyName = null)
        {
            if (Object.Equals(storage, value))
                return false;
            storage = value;
            OnPropertyChanged(propertyName);
            return true;
        }
        protected void OnPropertyChanged([CallerMemberName] string propertyName = null)
        {
            PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
        }
        public event PropertyChangedEventHandler PropertyChanged;
    }
    

    2.create view model for the page (MyViewModel.cs)

    public class MyViewModel 
    {
        public List<Data> Items { get; set; }
    
        public ICommand UpVoteCommand { get; set; }
        public ICommand DownVoteCommand { get; set; }
    
        public MyViewModel() {
    
            Items = new List<Data>();
    
            Items.Add(new Data("User1", "Lorem ipsum dolor sit amet1", 21, "<Image url>"));
            Items.Add(new Data("User2", "Lorem ipsum dolor sit amet2", 22, "<Image url>"));
            Items.Add(new Data("User3", "Lorem ipsum dolor sit amet3", 23   , "<Image url>"));
    
    
            UpVoteCommand = new Command((object item) => {
                if (item != null && item is Data)
                {
                    Data data = (Data)item;
                    data.Votes++;
    
                }
            });
    
            DownVoteCommand = new Command((object item) => {
                if (item != null && item is Data)
                {
                    Data data = (Data)item;
                    data.Votes--;
    
                }
            });
    
        }
    }
    

    3.Usage example:

    MainPage.xaml

    <?xml version="1.0" encoding="utf-8" ?> 
    <ContentPage xmlns="http://schemas.microsoft.com/dotnet/2021/maui"
                 xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
                 x:Class="MauiApp618.MainPage">
    
        <ListView x:Name="mListView" HasUnevenRows="True" ItemsSource="{Binding Items}">
            <ListView.ItemTemplate>
                <DataTemplate>
                    <ViewCell>
                      <VerticalStackLayout Spacing="25" Margin="10,0" VerticalOptions="Center" >
                        <Frame BackgroundColor="Yellow">
                            <VerticalStackLayout>
                                <VerticalStackLayout Margin="5">
                                    <Label Text="{Binding Name}" FontSize="18" TextColor="Gray" />
                                    <Label Text="{Binding Title}" FontSize="Large" FontAttributes="Bold" />
                                </VerticalStackLayout>
                                <Border>
                                    <Border.StrokeShape>
                                        <RoundRectangle CornerRadius="5" />
                                    </Border.StrokeShape>
                                    <Image MinimumHeightRequest="100" MaximumHeightRequest="100" Aspect="AspectFill" Source="{Binding Image}" />
                                </Border>
                                <HorizontalStackLayout Margin="0,5,0,0">
                                     <Button ImageSource="upvote.png" WidthRequest="60"  HeightRequest="60" BackgroundColor="Transparent" 
                                             Command="{Binding Source={x:Reference mListView}, Path=BindingContext.UpVoteCommand}"
                                             CommandParameter="{Binding .}"
                                             />
                                    <Label Text="{Binding Votes}" FontSize="Medium" Margin="0, 10, 0, 0" TextColor="Gray" />
                                    <Button ImageSource="downvote.png"  WidthRequest="60"  HeightRequest="60" BackgroundColor="Transparent" 
                                            Command="{Binding Source={x:Reference mListView}, Path=BindingContext.DownVoteCommand}"
                                            CommandParameter="{Binding .}"
                                            
                                            />
                                </HorizontalStackLayout>
                            </VerticalStackLayout>
                        </Frame>
                    </VerticalStackLayout>
                    </ViewCell>
                    
                </DataTemplate>
            </ListView.ItemTemplate>
        </ListView>
    
    </ContentPage>
    

    MainPage.xaml.cs

    public partial class MainPage : ContentPage 
    {
          public MainPage()
          {
                InitializeComponent();
    
            //List<Data> items = new() { new Data("User", "Lorem ipsum dolor sit amet", "21", "<Image url>") };
            //mListView.ItemsSource = items;
    
            this.BindingContext= new MyViewModel();
        }
    }