I am working on a .NET MAUI application where I have combined three ViewModels (ProjectDetailsViewModel, TODO_ViewModel, and TaskMaViewModel) into one MainPageViewModels class to display them on a single page. I’m using the CommunityToolkit.Mvvm.ComponentModel and CommunityToolkit.Mvvm.Input libraries to manage my ViewModels.
In my XAML, I am displaying a list of tasks, and I want to control the IsEnabled state of some Entry fields based on a property in TaskMaViewModel. I’m also trying to show a list of projects using a Picker inside the DataTemplate. However, I’m running into the following issues:
Issues:
Binding Read-Only State: I am trying to bind the IsEnabled property of an Entry to a boolean property (IsEditable) in TaskMaViewModel, but it doesn't seem to work. The fields are not respecting the IsEnabled binding.
Using Multiple ViewModels in a DataTemplate: I want to include another ViewModel (ProjectDetailsViewModel) inside the DataTemplate to display a list of projects in a Picker. How can I correctly bind the Picker to this ViewModel and display the list of projects?
Questions:
How can I correctly bind data inside a DataTemplate in .NET MAUI, particularly when trying to control the IsEnabled state of an Entry field? Is there something wrong with how I’m using RelativeSource or the binding path? How can I include another ViewModel (ProjectDetailsViewModel) inside the DataTemplate to bind the Picker control to a list of projects? What’s the correct approach to achieve this in .NET MAUI? Any guidance would be greatly appreciated! Thank you.
public partial class MainPageViewModels : ObservableObject
{
[ObservableProperty]
private bool isToDoListVisible;
public bool isTaskListVisible => !isToDoListVisible;
private string _btnColor = "#512BD4";
public string BtnColor
{
get => _btnColor;
set => SetProperty(ref _btnColor, value);
}
private string _btnText = "To-Do";
public string BtnText
{
get => _btnText;
set => SetProperty(ref _btnText, value);
}
public ProjectDetailsViewModel projectvm { get; set; }
public TODO_ViewModel todovm { get; set; }
public TaskMaViewModel taskvm { get; set; }
public MainPageViewModels()
{
projectvm = new ProjectDetailsViewModel();
todovm = new TODO_ViewModel();
taskvm = new TaskMaViewModel(projectvm);
isToDoListVisible = false;
}
[RelayCommand]
private void ToggleToDoList()
{
IsToDoListVisible = !IsToDoListVisible;
}
partial void OnIsToDoListVisibleChanged(bool value)
{
if (isTaskListVisible)
{
BtnColor = "#512BD4";
BtnText = "To-Do";
}
else
{
BtnColor = "#ADD8E6";
BtnText = "Task List";
}
OnPropertyChanged(nameof(isTaskListVisible));
}
}
Below is the code snippet I’m working with:
<StackLayout Grid.Row="2" IsVisible="{Binding isTaskListVisible}">
<!-- Header Row -->
<Grid Padding="10" BackgroundColor="#f0f0f0">
<Grid.ColumnDefinitions>
<ColumnDefinition Width="Auto" />
<ColumnDefinition Width="*" />
<ColumnDefinition Width="*" />
<ColumnDefinition Width="*" />
<ColumnDefinition Width="*" />
<ColumnDefinition Width="*" />
</Grid.ColumnDefinitions>
<Label Text="ID" Grid.Column="0" FontAttributes="Bold" HorizontalTextAlignment="Center"/>
<Label Text="Task Name" Grid.Column="1" FontAttributes="Bold" HorizontalTextAlignment="Center"/>
<Label Text="Start Time" Grid.Column="2" FontAttributes="Bold" HorizontalTextAlignment="Center"/>
<Label Text="End Time" Grid.Column="3" FontAttributes="Bold" HorizontalTextAlignment="Center"/>
<Label Text="Time Taken" Grid.Column="4" FontAttributes="Bold" HorizontalTextAlignment="Center"/>
<Label Text="Project Name" Grid.Column="5" FontAttributes="Bold" HorizontalTextAlignment="Center"/>
</Grid>
<ScrollView HeightRequest="400">
<ListView ItemsSource="{Binding taskvm.Tasks}" SelectionMode="Single">
<ListView.ItemTemplate>
<DataTemplate>
<ViewCell>
<SwipeView>
<SwipeView.LeftItems>
<SwipeItems>
<SwipeItem Text="Edit"
BackgroundColor="LightBlue"
Command="{Binding Source={RelativeSource AncestorType={x:Type ContentPage}}, Path=BindingContext.taskvm.DeleteTaskCommand}"
CommandParameter="{Binding Source={RelativeSource AncestorType={x:Type xs:TaskMang}}, Path=id}" />
<SwipeItem Text="Delete"
BackgroundColor="LightCoral"
CommandParameter="{Binding .}" />
</SwipeItems>
</SwipeView.LeftItems>
<SwipeView.Content>
<Grid Padding="10">
<Grid.ColumnDefinitions>
<ColumnDefinition Width="Auto" />
<ColumnDefinition Width="*" />
<ColumnDefinition Width="*" />
<ColumnDefinition Width="*" />
<ColumnDefinition Width="*" />
<ColumnDefinition Width="*" />
</Grid.ColumnDefinitions>
<Entry Text="{Binding Source={RelativeSource AncestorType={x:Type xs:TaskMang}}, Path=id}" Grid.Column="0" />
<Entry Text="{Binding Source={RelativeSource AncestorType={x:Type xs:TaskMang}}, Path=TaskName}" Grid.Column="1" IsEnabled="{Binding Source={RelativeSource AncestorType={x:Type vm:TaskMaViewModel}}, Path=IsEditable}" />
<Entry Text="{Binding Source={RelativeSource AncestorType={x:Type xs:TaskMang}}, Path=TaskStartTime}" Grid.Column="2" />
<Entry Text="{Binding Source={RelativeSource AncestorType={x:Type xs:TaskMang}}, Path=TaskEndTime}" Grid.Column="3" />
<Entry Text="{Binding Source={RelativeSource AncestorType={x:Type xs:TaskMang}}, Path=TaskTimeTaken}" Grid.Column="4" />
<!--<Entry Text="{Binding Source={RelativeSource AncestorType={x:Type xs:TaskMang}}, Path=ProjectName}" Grid.Column="5"/>-->
<Picker Title="Select Project"
WidthRequest="150"
ItemsSource="{Binding Source={RelativeSource AncestorType={x:Type xs:ProjectModels}}, StringFormat='{0}'}"
ItemDisplayBinding="{Binding ProjectName}"
SelectedItem="{Binding Source={RelativeSource AncestorType={x:Type xs:TaskMang}}, Path=ProjectName}"
Grid.Column="5"
FontSize="10"/>
</Grid>
</SwipeView.Content>
</SwipeView>
</ViewCell>
</DataTemplate>
</ListView.ItemTemplate>
</ListView>
</ScrollView>
</StackLayout>
To answer your question of not fetching data when using CollectionView, I made a demo to share my solution.
Background: we have a MainPage
which contains a CollectionView
and one MainPageViewModels
in which we instantiate three other viewModels, projectvm, todovm and taskvm.
First, I will determine the BindingContext of MainPage
, so we set it in MainPage.cs,
public MainPage()
{
InitializeComponent();
this.BindingContext = new MainPageViewModels();
}
Please note that you don't set BindingContext or use any compiled bindings in XAML. If you really want to do that, please share the code.
Then, we may focus on the ListView
part. Let's say we define the Delete or Edit command in the taskvm.
Here is the code for TaskMaViewModel. You may see that I create a collection of TaskMang
and add some test data for debugging.
public partial class TaskMaViewModel : ObservableObject
{
[ObservableProperty]
ObservableCollection<TaskMang> tasks = new ObservableCollection<TaskMang>();
[ObservableProperty]
bool isEditable = false;
[RelayCommand]
public void DeleteTask(TaskMang taskMang)
{
Tasks.Remove(taskMang);
}
[RelayCommand]
public void EditTask(TaskMang taskMang)
{
taskMang.TaskName = "I change it";
}
public TaskMaViewModel(ProjectDetailsViewModel projectvm)
{
// Add some test data for debugging
Tasks.Add(new TaskMang { Id = "id1", TaskName = "TaskName1", TaskTimeTaken = "1H" });
...
}
}
Then I need to consume it in XAML. For the SwipeItem
, we should use the Relative Bindings to bind the Command
property to EditTaskCommand/DeleteTaskCommand in taskvm. Please refer to the following code,
<ScrollView HeightRequest="400">
<ListView ItemsSource="{Binding taskvm.Tasks}" SelectionMode="Single">
<ListView.ItemTemplate>
<DataTemplate>
<ViewCell>
<SwipeView>
<SwipeView.LeftItems>
<SwipeItems>
<SwipeItem Text="Edit"
BackgroundColor="LightBlue"
Command="{Binding Source={RelativeSource AncestorType={x:Type vm:MainPageViewModels}}, Path=taskvm.EditTaskCommand}"
CommandParameter="{Binding .}" />
<SwipeItem Text="Delete"
BackgroundColor="LightCoral"
Command="{Binding Source={RelativeSource AncestorType={x:Type vm:MainPageViewModels}}, Path=taskvm.DeleteTaskCommand}"
CommandParameter="{Binding .}" />
</SwipeItems>
</SwipeView.LeftItems>
Okay let's see the Entry
part. As I said before, you don't have to use RelativeBinding for the Entry
. When setting the ItemsSource
to taskvm.Tasks, then each ViewCell
can directly access the Id
or TaskName
of each object in the collection. That means the BindingContext
of each ViewCell
is set to the corresponding Item in the Task collection.
But IsEnabled
property is different. It should first find taskvm and then find the IsEditable
property in taskvm.
<Entry Text="{Binding Id}" Grid.Column="0" />
<Entry Text="{Binding TaskName}" Grid.Column="1" IsEnabled="{Binding Source={RelativeSource AncestorType={x:Type vm:MainPageViewModels}}, Path=taskvm.IsEditable}" />
<Entry Text="{Binding TaskStartTime}" Grid.Column="2" />
<Entry Text="{Binding TaskEndTime}" Grid.Column="3" />
<Entry Text="{Binding TaskTimeTaken}" Grid.Column="4" />
This is the TaskMang
,
public partial class TaskMang : ObservableObject
{
[ObservableProperty]
string id;
[ObservableProperty]
string taskName;
[ObservableProperty]
string taskTimeTaken;
}
Below is the origin answer
Let's say you set the BindingContext for the MianPage,
public MainPage()
{
InitializeComponent();
this.BindingContext = new MainPageViewModels();
}
In MainPageViewModels
, you define a TaskMaViewModel
instance named taskvm. So if you want to use Bind to an ancestor, you may try this,
<Entry .... IsEnabled="{Binding Source={RelativeSource AncestorType={x:Type vm:MainPageViewModels}}, Path=taskvm.IsEditable}" />
In this way, IsEnabled Property will first find its parent's BindingContext MainPageViewModels
and bind to taskvm.IsEditable
property.
By the way, is taskvm.Tasks
a Collection of TaskMang
object which contains id
, TaskName
and other properties? Then why not just use
<Entry Text="{Binding id}" Grid.Column="0" />
Since you also define a ProjectDetailsViewModel
instance called projectvm in MainPageViewModels
, and you pass it into the TaskMaViewModel
Constructor.
public class TaskMaViewModel
{
public ProjectDetailsViewModel ProjectVM { get; set; }
...
public TaskMaViewModel(ProjectDetailsViewModel projectvm)
{
this.ProjectVM = projectvm;
And since we could get the TaskMaViewModel
as BindingContext, we could also get ProjectVM.
Let's say there is a PickerItems
in ProjectVM, and we use some test code for debugging, like below,
public partial class ProjectDetailsViewModel : ObservableObject
{
[ObservableProperty]
ObservableCollection<PickerItem> pickerItems = new ObservableCollection<PickerItem>();
public ProjectDetailsViewModel()
{
PickerItems.Add(new PickerItem() { ProjectId = 1, ProjectName = "project1" });
PickerItems.Add(new PickerItem() { ProjectId = 2, ProjectName = "project2" });
PickerItems.Add(new PickerItem() { ProjectId = 3, ProjectName = "project3" });
}
}
public class PickerItem
{
public string ProjectName { get; set; }
public int ProjectId { get; set; }
}
Now, we want to use data binding for Picker in DataTemplate,
<Picker Title="Select Project"
WidthRequest="150"
ItemsSource="{Binding Source={RelativeSource AncestorType={x:Type vm:MainPageViewModel}}, Path=taskvm.ProjectVM.PickerItems}"
ItemDisplayBinding="{Binding ProjectName}"
Grid.Column="5"
FontSize="10"/>
For more info, you may refer to Populate a Picker with data using data binding
Here is a screenshot,