Reputation: 421
I've gone through the suggestions given in a similar question: Xamarin passing values using QueryProperty but still can't find a solution to my issue.
When I connect to my MongoDb my EventIssues (Main Page)view displays a list of Issues in a collection view correctly. I'm using shell to navigate to the details page view. The skeleton of the page (title, a few labels) appear when I tap on a record, so the navigation appears to be working correctly. The EventIssuesViewModel RelayCommand GoToDetails shows the properties for my model are available. But those don't get passed. Here's my code:
I've added PropertyChanged in my DetailsViewModel, based on the previously noted question, but my properties don't seem to change.
I'm using CommunityToolkit-MVVM, all my references are in a globalusings.cs file
Model
#nullable enable
namespace Inspirmedia.Kajian.Models
{
public class ImEventIssue
{
public ObjectId Id { get; set; }
public string? IssueId { get; set; }
public string EventName { get; set; }
public string? QuoteNumber { get; set; }
public string IssueDescription { get; set; }
public string IssueType { get; set; }
public string IssueTypeIndicator { get; set; }
public string? IssueNotes { get; set; }
public string? EventLocation { get; set; }
public string? EventDate { get; set; }
}
MainPage.xaml (EventIssues page)
<?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"
xmlns:viewmodel="clr-namespace:Inspirmedia.Kajian.ViewModels"
xmlns:model="clr-namespace:Inspirmedia.Kajian.Models"
x:DataType="viewmodel:EventIssuesViewModel"
x:Class="Inspirmedia.Kajian.Views.MainPage"
Title="{Binding Title}">
<Grid
ColumnDefinitions="*,*"
ColumnSpacing="5"
RowDefinitions="*,Auto,Auto"
RowSpacing="0">
<CollectionView ItemsSource="{Binding ImEventIssues}"
Grid.ColumnSpan="2"
Grid.Row="0">
<CollectionView.ItemTemplate>
<DataTemplate x:DataType="model:ImEventIssue">
<Grid Padding="10,5,10,5">
<Frame HeightRequest="130">
<Frame.GestureRecognizers>
<TapGestureRecognizer
Command="{Binding Source={RelativeSource AncestorType={x:Type viewmodel:EventIssuesViewModel}}, Path=GoToDetailsCommand}"
CommandParameter="{Binding .}"/>
</Frame.GestureRecognizers>
<Grid Padding="0"
ColumnDefinitions="10,*">
<Image Source="{Binding IssueTypeIndicator}"
Aspect="Fill"
Grid.Column="0"
HeightRequest="100"
WidthRequest="3"/>
<VerticalStackLayout Grid.Column="1"
Spacing="0"
Margin="4,0,0,0">
<!-- This label shows the MongoDb ObjectId - use for testing-->
<Label Text="{Binding Id}"
FontSize="9"
TextColor="Red"
FontAttributes="None"
Margin="0,8,0,0"/>
<Label Text="{Binding EventName}"
FontSize="16"
TextColor="Black"
FontAttributes="Bold"/>
<Label Text="{Binding IssueDescription}"
FontSize="14"
TextColor="Black"
FontAttributes="Bold"/>
<Label Text="{Binding IssueNotes}"
FontSize="12"
TextColor="Black"
FontAttributes="Bold"/>
<Label Text="{Binding IssueType}"
FontSize="9"
TextColor="Black"
FontAttributes="None"
Margin="0,4,0,0"/>
</VerticalStackLayout>
</Grid>
</Frame>
</Grid>
</DataTemplate>
</CollectionView.ItemTemplate>
</CollectionView>
<ActivityIndicator IsVisible="{Binding IsBusy}"
IsRunning="{Binding IsBusy}"
HorizontalOptions="Fill"
VerticalOptions="Center"
Color="{StaticResource Primary}"
Grid.RowSpan="3"
Grid.ColumnSpan="2"/>
<!--
<Button Text="Back"
Grid.Row="1"
Grid.Column="0"
Margin="8,8,8,4"/>
<Button Text="Export CSV Report"
Grid.Row="1"
Grid.Column="1"
Margin="8,8,8,4"
Command="{Binding ExportCsvReportCommand}"/>
-->
<Button Text="Refresh Page"
Command="{Binding GetImEventIssuesCommand}"
Grid.Row="2"
Grid.Column="0"
Grid.ColumnSpan="2"
Margin="8,4,8,8"/>
</Grid>
</ContentPage>
The EventIssuesViewModel
namespace Inspirmedia.Kajian.ViewModels
{
public partial class EventIssuesViewModel : BaseViewModel
{
public ObservableCollection<ImEventIssue> ImEventIssues { get; } = new();
private readonly EventIssueService _eventIssueService;
public EventIssuesViewModel(EventIssueService eventIssueService)
{
Title = "Event Issues Viewer";
this._eventIssueService = eventIssueService;
}
[RelayCommand]
private async Task GoToDetails(ImEventIssue imEventIssue)
{
if (IsBusy)
return;
try
{
IsBusy = true;
if (imEventIssue == null) return;
//var selectedIssue = imEventIssue.Id;
await Shell.Current.GoToAsync(nameof(DetailsPage), true, new Dictionary<string, object>
{
{"Event Issues", imEventIssue}
});
}
catch (Exception e)
{
Debug.WriteLine(e.Message);
await Shell.Current.DisplayAlert("Error", $"Unable to retrieve Details: {e.Message}", "OK");
}
finally
{
IsBusy = false;
}
}
}
}
The DetailsViewModel - Even though I only need the Id of the the record, I'm passing my entire object in case I need something else in the future.
#nullable enable
namespace Inspirmedia.Kajian.ViewModels
{
[QueryProperty(nameof(ImEventIssue), "imEventIssue")]
public partial class DetailsViewModel : BaseViewModel
{
private EventIssueService _eventIssueService;
public DetailsViewModel(EventIssueService eventIssueService)
{
Title = "Event Issue Details";
this._eventIssueService = eventIssueService;
}
[ObservableProperty]
ImEventIssue imEventIssue;
public async Task GetEventIssueDetails(ObjectId id)
{
if (IsBusy)
return;
try
{
IsBusy = true;
//check to see if the id for the record is being passed
Debug.WriteLine(id);
}
catch (Exception e)
{
Console.WriteLine(e);
throw;
}
finally
{
IsBusy = false;
}
}
partial void OnImEventIssueChanged(ImEventIssue imEventIssue)
{
GetEventIssueDetails(imEventIssue.Id);
}
}
}
See any errors I'm just overlooking?
Upvotes: 0
Views: 1795
Reputation: 3907
May I suggest, different approach.
Instead of using parameters Annotations and checking if they have changed...
You can use this instead:
public void ApplyQueryAttributes(IDictionary<string, object> query)
(By implementing IQueryAttributable interface, in your ViewModel)
Accessing data from this query is quite easy:
SomeProperty = query[nameof(MyModel)] as MyModel;
Provided that in the calling ViewModel:
Dictionary<string, object> query = new()
{
{ nameof(MyModel), someObject}
};
await Shell.Current.GoToAsync($"{nameof(SecondPage)}", query);
It is much cleaner and less prone to errors.
Upvotes: 0
Reputation: 2143
This combination is not correct.
[QueryProperty(nameof(ImEventIssue), "imEventIssue")]
The first argument for the QueryPropertyAttribute specifies the name of the property that will receive the data, with the second argument specifying the parameter id.
Name of your "Property" should have been "ImEventIssue". But instead, you have a field, which is not correct.
You are passing parameters like this.
await Shell.Current.GoToAsync(nameof(DetailsPage), true, new Dictionary<string, object>
{
{"Event Issues", imEventIssue}
});
This means it's trying to map to a Property called "Event Issues". That's also wrong. Parameter name should have been "ImEventIssue".
Also have a look at how to use IQueryAttributable interface. It's a cleaner pattern.
Upvotes: 0
Reputation: 1629
You need to inherit ObservableObject for class ImEventIssue.
Please refer to the sample code below:
public class ImEventIssue : ObservableObject
{
public ObjectId id;
public ObjectId Id
{
get => id;
set =>SetProperty(ref id, value);
}
...
...
}
Upvotes: 0