I created a model class called Project and also created a ViewModel class called MainPageViewModel.
What I want to implement is a simple table that has multiple columns. There should be one column named "Action" and that column should have one button.
Here is the 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="SharpTool.UI.MainPage"
xmlns:viewmodel="clr-namespace:SharpTool.UI.ViewModels"
xmlns:models ="clr-namespace:SharpTool.UI.Models"
x:DataType="viewmodel:MainPageViewModel">
<ScrollView>
<VerticalStackLayout
Padding="10, 10">
<Border MinimumHeightRequest="50" StrokeThickness ="0.5">
<Grid>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="*" />
<ColumnDefinition Width="*"/>
<ColumnDefinition Width="*"/>
<ColumnDefinition Width="*"/>
<ColumnDefinition Width="*"/>
</Grid.ColumnDefinitions>
<Border StrokeThickness="0.1" Grid.Column="0">
<Label HorizontalOptions="Center" VerticalOptions="Center" FontFamily="icon" FontSize="15" Text="Id" TextColor="Blue"/>
</Border>
<Border StrokeThickness="0.1" Grid.Column="1">
<Label HorizontalOptions="Center" VerticalOptions="Center" FontFamily="icon" FontSize="15" Text="Name" TextColor="Blue"/>
</Border>
<Border StrokeThickness="0.1" Grid.Column="2">
<Label HorizontalOptions="Center" VerticalOptions="Center" FontFamily="icon" FontSize="15" Text="Discription" TextColor="Blue"/>
</Border>
<Border StrokeThickness="0.1" Grid.Column="3">
<Label HorizontalOptions="Center" VerticalOptions="Center" FontFamily="icon" FontSize="15" Text="Version" TextColor="Blue"/>
</Border>
<Border StrokeThickness="0.1" Grid.Column="4">
<Label HorizontalOptions="Center" VerticalOptions="Center" FontFamily="icon" FontSize="15" Text="Action" TextColor="Blue"/>
</Border>
</Grid>
</Border>
<CollectionView ItemsSource="{Binding Projects}" >
<CollectionView.ItemsLayout>
<LinearItemsLayout Orientation="Vertical"/>
</CollectionView.ItemsLayout>
<CollectionView.ItemTemplate>
<DataTemplate x:DataType="models:Project">
<Border MinimumHeightRequest="50" StrokeThickness ="0.5" >
<Grid>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="*" />
<ColumnDefinition Width="*"/>
<ColumnDefinition Width="*"/>
<ColumnDefinition Width="*"/>
<ColumnDefinition Width="*"/>
</Grid.ColumnDefinitions>
<Border StrokeThickness="0.1" Grid.Column="0">
<Label HorizontalOptions="Center" VerticalOptions="Center" FontFamily="icon" FontSize="15" Text="{Binding Id}" TextColor="Blue"/>
</Border>
<Border StrokeThickness="0.1" Grid.Column="1">
<Label HorizontalOptions="Center" VerticalOptions="Center" FontFamily="icon" FontSize="15" Text="{Binding Name}" TextColor="Blue"/>
</Border>
<Border StrokeThickness="0.1" Grid.Column="2">
<Label HorizontalOptions="Center" VerticalOptions="Center" FontFamily="icon" FontSize="15" Text="{Binding Description}" TextColor="Blue"/>
</Border>
<Border StrokeThickness="0.1" Grid.Column="3">
<Label HorizontalOptions="Center" VerticalOptions="Center" FontFamily="icon" FontSize="15" Text="{Binding Version}" TextColor="Blue"/>
</Border>
<Border StrokeThickness="0.1" Grid.Column="4">
<Button
Text="Click me"
SemanticProperties.Hint="Counts the number of times you click"
Command="{Binding GetProjectByCommand}"
CommandParameter="2"
HorizontalOptions="Center" />
</Border>
</Grid>
</Border>
</DataTemplate>
</CollectionView.ItemTemplate>
</CollectionView>
</VerticalStackLayout>
</ScrollView>
</ContentPage>
I managed to implement the UI code but I have a problem. When I set the x:DataType="models:Project" in the DataTemplate I can't bind the ViewModel methods to buttons or
any other components.
It gives me an error:
Binding: Property "GetProjectByCommand" not found on "SharpTool.UI.Models.Project".
If I remove this part:
Command="{Binding GetProjectByCommand}" in the button inside the Grid,
then the project builds and runs without any issues.
This is the ViewModel code:
[INotifyPropertyChanged]
public partial class MainPageViewModel
{
[ObservableProperty]
ObservableCollection<Project> projects = new();
[ObservableProperty]
public bool isBusy = false;
public ICommand GetProjectByCommand { private set; get; }
public MainPageViewModel()
{
projects.Add(new Project { Id = 1, Name = "Project 1 Name", Description = "Project 1 Description", Version = "1.0" });
projects.Add(new Project { Id = 2, Name = "Project 2 Name", Description = "Project 2 Description", Version = "1.0" });
projects.Add(new Project { Id = 3, Name = "Project 3 Name", Description = "Project 3 Description", Version = "1.0" });
projects.Add(new Project { Id = 4, Name = "Project 1 Name", Description = "Project 1 Description", Version = "1.0" });
projects.Add(new Project { Id = 5, Name = "Project 2 Name", Description = "Project 2 Description", Version = "1.0" });
projects.Add(new Project { Id = 6, Name = "Project 3 Name", Description = "Project 3 Description", Version = "1.0" });
projects.Add(new Project { Id = 7, Name = "Project 1 Name", Description = "Project 1 Description", Version = "1.0" });
projects.Add(new Project { Id = 8, Name = "Project 2 Name", Description = "Project 2 Description", Version = "1.0" });
projects.Add(new Project { Id = 9, Name = "Project 3 Name", Description = "Project 3 Description", Version = "1.0" });
projects.Add(new Project { Id = 10, Name = "Project 3 Name", Description = "Project 3 Description", Version = "1.0" });
GetProjectByCommand = new Command<string>(GetProjectById);
}
private void GetProjectById(string id)
{
//query project list
}
}
So, how do I bind that GetProjectById method to that button inside the Grid?
This is something that happens a lot. The issue here is that whenever you are in a control that shows items, the data-binding scope will change.
For you full page the view model is your
MainPageViewModel. You then have aCollectionViewthat reads from theProjectsproperty of yourMainPageViewModel. So far, so good.However, inside of your
CollectionView, especially inside of yourItemTemplateyou are no longer scoped to the rest of your page, but the binding context is now the type of whatever item you put in there, in your caseProject. You can also see this because you have to specify<DataTemplate x:DataType="models:Project">which should indicate to you that you're now looking atProjectobjects and not theMainPageViewModelanymore.There is multiple options to solve this. The most obvious, but not necessarily best, is to add the
GetProjectByCommandto yourProjectobject. However, technically this is probably a responsibility of yourMainPageViewModel.The other option is to change your binding to make sure that it looks at your
MainPageViewModelagain. In order to do this, change this line:to this:
In order for this to work make sure that you add the
x:Name="mainPage"attribute to yourContentPagenode.What this does is set the source of the binding to the
mainPage.BindingContextwhich is yourMainPageViewModel.More information about this can also be found in my video about it: https://www.youtube.com/watch?v=Or_qn8i8jVM
I just noticed that in my video I use a different syntax:
Which is essentially the same but written different.