英文:
.Net Maui Google Maps Polyline drawning route feature
问题
我正在构建一个 .NET Maui 智能手机运动应用程序,该应用程序抓取运动活动的纬度和经度列表(使用新的 Location 类),并在地图上绘制一条线(折线)以显示路线。我可以从数据库中获取运动活动的列表,并在地图上绘制折线,但问题是我不知道如何在我的 ViewModel 类中将地图功能进行数据绑定,因此无法同时执行这两个操作。
以下是 ExercisePage.xaml 中的 XAML 代码:
<ContentPage xmlns="http://schemas.microsoft.com/dotnet/2021/maui"
xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
x:Class="DoradSmartphone.Views.ExercisePage"
xmlns:model="clr-namespace:DoradSmartphone.Models"
xmlns:viewmodel="clr-namespace:DoradSmartphone.ViewModels"
xmlns:maps="clr-namespace:Microsoft.Maui.Controls.Maps;assembly=Microsoft.Maui.Controls.Maps"
xmlns:sensors="clr-namespace:Microsoft.Maui.Devices.Sensors;assembly=Microsoft.Maui.Essentials"
x:DataType="viewmodel:ExerciseViewModel"
Title="{Binding Title}">
<Grid Padding="5" Margin="5" RowSpacing="5" ColumnSpacing="3">
<Grid.RowDefinitions>
<RowDefinition Height="2*"/>
<RowDefinition Height="150"/>
</Grid.RowDefinitions>
<maps:Map Grid.Row="0" x:Name="routeMap" VerticalOptions="CenterAndExpand" Grid.ColumnSpan="3" HeightRequest="400" IsZoomEnabled="False" IsEnabled="False">
<x:Arguments>
<MapSpan>
<x:Arguments>
<sensors:Location>
<x:Arguments>
<x:Double>38.744418137669875</x:Double>
<x:Double>-9.128544160596851</x:Double>
</x:Arguments>
</sensors:Location>
<x:Double>0.7</x:Double>
<x:Double>0.7</x:Double>
</x:Arguments>
</MapSpan>
</x:Arguments>
</maps:Map>
<CarouselView ItemsSource="{Binding Exercises}" Grid.Row="1" PeekAreaInsets="100">
<CarouselView.ItemTemplate>
<DataTemplate x:DataType="model:Exercise">
<Frame HeightRequest="90" Margin="5">
<Frame.GestureRecognizers>
<TapGestureRecognizer Command="{Binding Source={RelativeSource AncestorType={x:Type viewmodel:ExerciseViewModel}}, Path=ExerciseDetailsCommand}" CommandParameter="{Binding .}"></TapGestureRecognizer>
</Frame.GestureRecognizers>
<HorizontalStackLayout Padding="10" Spacing="5">
<Label Text="{Binding Id}"></Label>
<Label Text="{Binding Date}"></Label>
</HorizontalStackLayout>
</Frame>
</DataTemplate>
</CarouselView.ItemTemplate>
</CarouselView>
</Grid>
</ContentPage>
如您所见,我已经声明了地图的名称为 routeMap,并设置了初始位置。我还声明了我的模型和 ViewModel,以便在 CarouselView 中进行数据绑定。点击功能正常工作,并将我带到一个名为 ExerciseDetailsPage 的新视图。
这是 ExercisePage.xaml.cs 的代码:
using DoradSmartphone.Models;
using DoradSmartphone.ViewModels;
using Microsoft.Maui.Controls.Maps;
using Microsoft.Maui.Maps;
namespace DoradSmartphone.Views
{
public partial class ExercisePage : ContentPage
{
public ExercisePage(ExerciseViewModel exerciseViewModel)
{
InitializeComponent();
BindingContext = exerciseViewModel;
}
private void OnTapGestureRouteUpdate(object sender, EventArgs e)
{
// 这里是添加绘制折线的代码,但无法访问 routeMap 对象。
}
}
}
在 ExercisePage.xaml.cs 中,我无法访问 ViewModel、Services 或 Model 类。
这是 ExerciseViewModel.cs 类:
using CommunityToolkit.Mvvm.ComponentModel;
using CommunityToolkit.Mvvm.Input;
using DoradSmartphone.Models;
using DoradSmartphone.Services;
using Microsoft.Maui.Controls.Maps;
using Microsoft.Maui.Maps;
using System.Collections.ObjectModel;
namespace DoradSmartphone.ViewModels
{
public partial class ExerciseViewModel : BaseViewModel
{
// 这里是您的 ViewModel 代码
}
}
在 ExerciseViewModel.cs 中,您有一个 RelayCommand,但无法访问 routeMap 对象。您还尝试在 ViewModel 类中声明一个 Map 类,但遇到了无法创建静态类实例的错误。
这是 MauiProgram.cs:
using DoradSmartphone.Data;
using DoradSmartphone.Services;
using DoradSmartphone.ViewModels;
using DoradSmartphone.Views;
namespace DoradSmartphone
{
public static class MauiProgram
{
// 这里是您的 MauiProgram 代码
}
}
希望这些信息对您有所帮助。如果您需要更多帮助,请随时提出具体问题。
英文:
I have this situation:
I'm building a .net Maui smartphone sports app that grabs a list of latitude and longitude (new Location class) of a running activity and draws a line (polyline) in the map to display the route. I can grab the list of exercises from the database and I can draw a polyline in the map, the problem is that I can't do both together because I don't know how to databind the Map functionalitys in my ViewModel class.
Here is my xaml code for the ExercisePage.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="DoradSmartphone.Views.ExercisePage"
xmlns:model="clr-namespace:DoradSmartphone.Models"
xmlns:viewmodel="clr-namespace:DoradSmartphone.ViewModels"
xmlns:maps="clr-namespace:Microsoft.Maui.Controls.Maps;assembly=Microsoft.Maui.Controls.Maps"
xmlns:sensors="clr-namespace:Microsoft.Maui.Devices.Sensors;assembly=Microsoft.Maui.Essentials"
x:DataType ="viewmodel:ExerciseViewModel"
Title="{Binding Title}">
<Grid Padding="5" Margin="5" RowSpacing="5" ColumnSpacing="3">
<Grid.RowDefinitions>
<RowDefinition Height="2*"/>
<RowDefinition Height="150"/>
</Grid.RowDefinitions>
<maps:Map Grid.Row="0" x:Name="routeMap" VerticalOptions="CenterAndExpand" Grid.ColumnSpan="3" HeightRequest="400" IsZoomEnabled="False" IsEnabled="False">
<x:Arguments>
<MapSpan>
<x:Arguments>
<sensors:Location>
<x:Arguments>
<x:Double>38.744418137669875</x:Double>
<x:Double>-9.128544160596851</x:Double>
</x:Arguments>
</sensors:Location>
<x:Double>0.7</x:Double>
<x:Double>0.7</x:Double>
</x:Arguments>
</MapSpan>
</x:Arguments>
</maps:Map>
<CarouselView ItemsSource="{Binding Exercises}" Grid.Row="1" PeekAreaInsets="100">
<CarouselView.ItemTemplate>
<DataTemplate x:DataType="model:Exercise">
<Frame HeightRequest="90" Margin="5">
<Frame.GestureRecognizers>
<TapGestureRecognizer Command="{Binding Source={RelativeSource AncestorType={x:Type viewmodel:ExerciseViewModel}}, Path=ExerciseDetailsCommand}
" CommandParameter="{Binding .}"></TapGestureRecognizer>
</Frame.GestureRecognizers>
<HorizontalStackLayout Padding="10" Spacing="5" >
<Label Text="{Binding Id}"></Label>
<Label Text="{Binding Date}"></Label>
</HorizontalStackLayout>
</Frame>
</DataTemplate>
</CarouselView.ItemTemplate>
</CarouselView>
</Grid>
</ContentPage>
As you can see I have my map name declared as routeMap and the first location just to start in somewhere. I also has my model and viewmodel declared for DataBinding of the exercise list in the CarouselView. The tap feature works fine and take me to a new view called ExerciseDetailsPage.
This is the code behind ExercisePage.xaml.cs
using DoradSmartphone.Models;
using DoradSmartphone.ViewModels;
using Microsoft.Maui.Controls.Maps;
using Microsoft.Maui.Maps;
namespace DoradSmartphone.Views;
public partial class ExercisePage : ContentPage
{
public ExercisePage(ExerciseViewModel exerciseViewModel)
{
InitializeComponent();
BindingContext = exerciseViewModel;
}
private void OnTapGestureRouteUpdate(object sender, EventArgs e)
{
var route = new Polyline
{
StrokeColor = Colors.Red,
StrokeWidth = 12,
Geopath =
{
new Location(38.70061856336034 , -8.957381918676203 ),
new Location(38.70671683905933 , -8.945225024701308 ),
new Location(38.701985630081595, -8.944503277546072 ),
new Location(38.701872978433386, -8.940750192338834 ),
new Location(38.71054663609023 , -8.939162348597312 ),
new Location(38.717755109243214, -8.942193686649311 ),
new Location(38.7435419727561 , -8.928480490699792 ),
new Location(38.78327379379296 , -8.880556478454272 ),
new Location(38.925473761602376, -8.881999972299806 ),
new Location(38.93692729913667 , -8.869585920414709 ),
new Location(38.93493556584553 , -8.86536198145887 )
}
};
routeMap.MoveToRegion(
MapSpan.FromCenterAndRadius(
new Location(38.93479161472441, -8.865352563545757), Distance.FromMiles(1)));
// Add the polyline to the map
routeMap.MapElements.Add(route);
}
}
If I change the actual tap functionality to this tap event, I can drawn any line and other stuffs with in the Map because I can read the map name defined in the xaml code. But in this codebehind class I can't reach my ViewModel, Services or Model class.
This is my ExerciseViewModel.cs class:
using CommunityToolkit.Mvvm.ComponentModel;
using CommunityToolkit.Mvvm.Input;
using DoradSmartphone;
using DoradSmartphone.Models;
using DoradSmartphone.Services;
using DoradSmartphone.Views;
using Microsoft.Maui.Controls.Maps;
using Microsoft.Maui.Maps;
using System.Collections.ObjectModel;
namespace DoradSmartphone.ViewModels
{
public partial class ExerciseViewModel : BaseViewModel
{
private readonly ExerciseService exerciseService;
public ObservableCollection<Exercise> Exercises { get; private set; } = new();
public ExerciseViewModel(ExerciseService exerciseService)
{
Title = "Training Routes";
this.exerciseService = exerciseService;
_ = GetExerciseList();
}
[ObservableProperty]
bool isRefreshing;
async Task GetExerciseList()
{
if (IsLoading) return;
try
{
IsLoading = true;
if (Exercises.Any()) Exercises.Clear();
var exercices = exerciseService.GetExercises();
foreach (var exercise in exercices) Exercises.Add(exercise);
} catch(Exception ex) {
Console.WriteLine(ex.ToString());
await Shell.Current.DisplayAlert("Error", "Failed to retrieve the exercice list", "Ok");
}
finally {
IsLoading = false;
isRefreshing= false;
}
}
[RelayCommand]
async Task ExerciseDetails(Exercise exercise)
{
if(exercise == null) return;
var routes = GetLocations(exercise.Id);
DrawRoutes(routes);
}
public List<Location> GetLocations(int exerciseId)
{
if (exerciseId == 1)
{
return new List<Location>
{
new Location(35.6823324582143, 139.7620853729577),
new Location(35.679263477092704, 139.75773939496295),
new Location(35.68748054650018, 139.761486207315),
new Location(35.690745005825136, 139.7560362984393),
new Location(35.68966608916097, 139.75147199952355),
new Location(35.68427128680411, 139.7442168083328)
};
}
else if (exerciseId == 2)
{
return new List<Location>
{
new Location(35.6823324582143, 139.7620853729577),
new Location(35.679263477092704, 139.75773939496295),
new Location(35.68748054650018, 139.761486207315),
new Location(35.690745005825136, 139.7560362984393),
new Location(35.68966608916097, 139.75147199952355),
new Location(35.68427128680411, 139.7442168083328)
};
}
else
{
return new List<Location>
{
new Location(35.6823324582143, 139.7620853729577),
new Location(35.679263477092704, 139.75773939496295),
new Location(35.68748054650018, 139.761486207315),
new Location(35.690745005825136, 139.7560362984393),
new Location(35.68966608916097, 139.75147199952355),
new Location(35.68427128680411, 139.7442168083328)
};
}
}
private void DrawRoutes(List<Location> routes)
{
var polylines = new Polyline
{
StrokeColor = Colors.Red,
StrokeWidth = 12,
};
foreach(var route in routes)
{
polylines.Geopath.Add(route);
}
routeMap.MoveToRegion(
MapSpan.FromCenterAndRadius(
routes.FirstOrDefault(), Distance.FromMiles(1)));
// Add the polyline to the map
routeMap.MapElements.Add(polylines);
}
}
}
This class inherits the BaseViewModel that inherits ObservableObject and has some common properties for all others classes. In the ExerciseViewModel I have my RelayCommand related to the tap feature that grabs the exercise object and add the route, but I cant access the routeMap object. I've tried also to declare a Map class in my viewmodel class, but I get the error all the time that I can't create a instance of a static class.
This is my MauiProgram.cs just in case there's something wrong:
using DoradSmartphone.Data;
using DoradSmartphone.Services;
using DoradSmartphone.ViewModels;
using DoradSmartphone.Views;
namespace DoradSmartphone;
public static class MauiProgram
{
public static MauiApp CreateMauiApp()
{
var builder = MauiApp.CreateBuilder();
builder
.UseMauiApp<App>()
.UseMauiMaps()
.ConfigureFonts(fonts =>
{
fonts.AddFont("OpenSans-Regular.ttf", "OpenSansRegular");
fonts.AddFont("OpenSans-Semibold.ttf", "OpenSansSemibold");
});
builder.Services.AddSingleton<DatabaseConn>();
builder.Services.AddScoped<IRepository, DatabaseConn>();
builder.Services.AddSingleton<MainPage>();
builder.Services.AddSingleton<UserPage>();
builder.Services.AddSingleton<LoginPage>();
builder.Services.AddSingleton<LoadingPage>();
builder.Services.AddSingleton<ExercisePage>();
builder.Services.AddSingleton<DashboardPage>();
builder.Services.AddSingleton<ExerciseDetailsPage>();
builder.Services.AddSingleton<UserService>();
builder.Services.AddSingleton<LoginService>();
builder.Services.AddSingleton<ExerciseService>();
builder.Services.AddSingleton<DashboardService>();
builder.Services.AddSingleton<UserViewModel>();
builder.Services.AddSingleton<LoginViewModel>();
builder.Services.AddSingleton<LoadingViewModel>();
builder.Services.AddSingleton<ExerciseViewModel>();
builder.Services.AddSingleton<DashboardViewModel>();
builder.Services.AddTransient<ExerciseDetailsViewModel>();
return builder.Build();
}
}
Thank you in advance!
答案1
得分: 1
抱歉,MapElements
不是一个可绑定的属性。不过,您可以通过几种方式来解决这个问题。
例如,您可以在您的 VM 中创建一个公共方法来返回路线数据。
public Polyline GetRouteData()
{
var polylines = new Polyline
{
StrokeColor = Colors.Red,
StrokeWidth = 12,
};
foreach(var route in routes)
{
polylines.Geopath.Add(route);
}
return polylines;
}
然后,在您的代码后台,首先创建一个对 VM 的类引用。
ExerciseViewModel ViewModel;
public ExercisePage(ExerciseViewModel exerciseViewModel)
{
InitializeComponent();
BindingContext = ViewModel = exerciseViewModel;
}
接着,您的代码后台可以从 VM 获取更新地图所需的数据。
routeMap.MapElements.Add(ViewModel.GetRouteData());
英文:
unfortunately, MapElements
is not a bindable property. However, you can work around that in a couple of ways
for example, create a public method in your VM that returns the route data
public Polyline GetRouteData()
{
var polylines = new Polyline
{
StrokeColor = Colors.Red,
StrokeWidth = 12,
};
foreach(var route in routes)
{
polylines.Geopath.Add(route);
}
return polylines;
}
then in your code behind, first create a class reference to the VM
ExerciseViewModel ViewModel;
public ExercisePage(ExerciseViewModel exerciseViewModel)
{
InitializeComponent();
BindingContext = ViewModel = exerciseViewModel;
}
then your code behind can get the data from the VM that it needs to update the map
routeMap.MapElements.Add(ViewModel.GetRouteData());
答案2
得分: 0
是的,Jason的解决方案有效!!!我不得不改一些东西,但最后它结束得很好。
我必须注入VM类
private ExerciseViewModel viewModel;
public ExercisePage(ExerciseViewModel exerciseViewModel)
{
InitializeComponent();
BindingContext = exerciseViewModel;
viewModel = exerciseViewModel;
}
然后我使用一个轻击手势从数据库中获取并绘制路线。
private void OnTapGestureRouteUpdate(object sender, EventArgs e)
{
routeMap.MapElements.Clear();
var frame = (Frame)sender;
var exercise = (Exercise)frame.BindingContext;
var exerciseId = exercise.Id;
var routes = viewModel.GetLocations(exerciseId);
var polyline = new Polyline
{
StrokeColor = Colors.Red,
StrokeWidth = 12,
};
foreach (var route in routes)
{
polyline.Geopath.Add(route);
}
routeMap.MoveToRegion(
MapSpan.FromCenterAndRadius(
routes.FirstOrDefault(), Distance.FromMiles(0.5)));
routeMap.MapElements.Add(polyline);
}
稍后我会应用一些清理代码,并将折线逻辑放在VM类中,但是,是的,它正在工作!
英文:
Yes, Jason's solution works!!! I had to change a few stuffs, but in the end it end well.
I had to inject the VM class
private ExerciseViewModel viewModel;
public ExercisePage(ExerciseViewModel exerciseViewModel)
{
InitializeComponent();
BindingContext = exerciseViewModel;
viewModel = exerciseViewModel;
}
Then I use a tap gesture to get the routes from database and draw.
private void OnTapGestureRouteUpdate(object sender, EventArgs e)
{
routeMap.MapElements.Clear();
var frame = (Frame)sender;
var exercise = (Exercise)frame.BindingContext;
var exerciseId = exercise.Id;
var routes = viewModel.GetLocations(exerciseId);
var polyline = new Polyline
{
StrokeColor = Colors.Red,
StrokeWidth = 12,
};
foreach (var route in routes)
{
polyline.Geopath.Add(route);
}
routeMap.MoveToRegion(
MapSpan.FromCenterAndRadius(
routes.FirstOrDefault(), Distance.FromMiles(0.5)));
routeMap.MapElements.Add(polyline);
}
I'll aply some clean code and put the polyline logic in the VM class later, but yeah, it's working!
通过集体智慧和协作来改善编程学习和解决问题的方式。致力于成为全球开发者共同参与的知识库,让每个人都能够通过互相帮助和分享经验来进步。
评论