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?
CodePudding user response:
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 a CollectionView
that reads from the Projects
property of your MainPageViewModel
. So far, so good.
However, inside of your CollectionView
, especially inside of your ItemTemplate
you 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 case Project
. 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 at Project
objects and not the MainPageViewModel
anymore.
There is multiple options to solve this. The most obvious, but not necessarily best, is to add the GetProjectByCommand
to your Project
object. However, technically this is probably a responsibility of your MainPageViewModel
.
The other option is to change your binding to make sure that it looks at your MainPageViewModel
again. In order to do this, change this line:
<Button
Text="Click me"
SemanticProperties.Hint="Counts the number of times you click"
Command="{Binding GetProjectByCommand}"
CommandParameter="2"
HorizontalOptions="Center" />
to this:
<Button
Text="Click me"
SemanticProperties.Hint="Counts the number of times you click"
Command="{Binding GetProjectByCommand, Source={x:Reference mainPage.BindingContext}}"
CommandParameter="2"
HorizontalOptions="Center" />
In order for this to work make sure that you add the x:Name="mainPage"
attribute to your ContentPage
node.
What this does is set the source of the binding to the mainPage.BindingContext
which is your MainPageViewModel
.
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:
<Button
Text="Click me"
SemanticProperties.Hint="Counts the number of times you click"
Command="{Binding Path=BindingContext.GetProjectByCommand, Source={Reference mainPage}}"
CommandParameter="2"
HorizontalOptions="Center" />
Which is essentially the same but written different.