我有一个现有的应用程序需要升级到 MAUI,显示端有一个小问题。我最近刚刚迁移到 .NET MAUI,我知道我即将显示这些傻瓜,尽管我遇到了一个小问题,我只是不知道具体在哪里。
我的 API 调用按预期从数据库返回数据,但调试时我的
ObservableCollection
中的项目未显示在应用程序中。
这是我的 XAML 标记:
<ContentPage xmlns="http://schemas.microsoft.com/dotnet/2021/maui"
xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
x:Class="SaleBeaconMobile.Views.MainPageCus"
Title="Sale Alerts"
xmlns:ViewModels="clr-namespace:SaleBeaconMobile.ViewModels">
<ContentPage.BindingContext>
<ViewModels:MainPageCusViewModel />
</ContentPage.BindingContext>
<ContentPage.ToolbarItems>
<ToolbarItem Text="Debug" Clicked="ToolbarItem_Clicked_3" Order="Secondary" />
<ToolbarItem Text="Settings" Clicked="ToolbarItem_Clicked" Order="Secondary"/>
<ToolbarItem Text="Support" Clicked="ToolbarItem_Clicked_1" Order="Secondary"/>
<ToolbarItem Text="Log Out" Clicked="ToolbarItem_Clicked_2" Order="Secondary"/>
</ContentPage.ToolbarItems>
<ContentPage.Content>
<RefreshView>
<CollectionView x:Name="CVSaleAlerts"
ItemsSource="{Binding Items}"
ItemsLayout="VerticalList"
RemainingItemsThreshold="4"
RemainingItemsThresholdReachedCommand="{Binding LoadMoreItemsCommand}"
HeightRequest="300">
<CollectionView.ItemTemplate>
<DataTemplate>
<Grid Padding="10">
<Grid.RowDefinitions>
<RowDefinition Height="280" />
</Grid.RowDefinitions>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="350" />
</Grid.ColumnDefinitions>
<Image Source="{Binding bg}"
Grid.Row="0"
Grid.Column="0"
Aspect="AspectFill"
WidthRequest="350"
HeightRequest="280"
MinimumHeightRequest="280"
VerticalOptions="FillAndExpand" />
</Grid>
</DataTemplate>
</CollectionView.ItemTemplate>
</CollectionView>
</RefreshView>
</ContentPage.Content>
</ContentPage>
这是我的视图模型:
using SaleBeaconMobile.Models;
using SaleBeaconMobile.Services;
using System.Collections.ObjectModel;
using System.Threading.Tasks;
using System.Windows.Input;
using Microsoft.Maui.Controls.Xaml;
namespace SaleBeaconMobile.ViewModels
{
public class MainPageCusViewModel : BaseViewModel
{
private readonly DataService _dataService = new DataService();
private const int PageSize = 10;
private int _currentPage = 0; // Track the current page for loading more data
public ObservableCollection<Sale> Items { get; }
private bool _isRefreshing;
public bool IsRefreshing
{
get => _isRefreshing;
set
{
_isRefreshing = value;
OnPropertyChanged();
}
}
public ICommand LoadMoreItemsCommand { get; }
public ICommand RefreshCommand => new Command(async () => await DownloadDataAsync());
public MainPageCusViewModel()
{
Items = new ObservableCollection<Sale>();
// Assign the command to LoadMoreItemsCommand
LoadMoreItemsCommand = new Command(async () => await LoadMoreItemsAsync());
// Trigger initial data load
Task.Run(async () => await DownloadDataAsync());
}
// Method to load initial data
public async Task DownloadDataAsync()
{
try
{
IsRefreshing = true;
// Fetch initial data (page 0 or starting page)
var items = await _dataService.GetItemsAsync(0, PageSize);
// Clear existing items and add new items
Items.Clear();
foreach (var item in items)
{
Items.Add(item);
}
// Update the current page
_currentPage = 1; // Assuming pages start from 0
}
catch (Exception ex)
{
// Handle exceptions as needed
}
finally
{
IsRefreshing = false;
}
}
// Method to load more items for pagination or infinite scrolling
public async Task LoadMoreItemsAsync()
{
try
{
if (IsRefreshing)
return; // Prevent re-entry
IsRefreshing = true;
// Fetch more data based on the current page
var items = await _dataService.GetItemsAsync(_currentPage, PageSize);
// Add new items to the existing collection
foreach (var item in items)
{
Items.Add(item);
}
// Increment the current page for the next load
_currentPage++;
}
catch (Exception ex)
{
// Handle exceptions as needed
}
finally
{
IsRefreshing = false;
}
}
}
}
我知道我已经非常接近了,因为数据正在正确检索,想法是从我的 API 调用中一次带回 10 条记录,所有这些都可以在邮递员中以及我的旧应用程序中使用。我只是不确定当前的显示端,不确定它是否是我缺少的属性或什么。在此之前,我使用的是带有集合的 ListView,一切都有效,但我认为我应该将其全部升级到 .MAUI,并使用随之而来的新属性和功能。我目前没有收到任何错误。
感谢您的指点和帮助!干杯,乔:)
所以我最初要解决的问题是从 Xamarin 迁移到 .net 8.0 Maui mobile。 我的旧解决方案工作正常,我有一个列表视图等,但它真的很糟糕,加载时间太长。 我的新目标是更新到 .net 8.0,并使我的新 CollectionView 比 ListView 的功能更好。 现在确实如此。 我的应用程序在 1 秒内加载(如果不是更快的话),我的服务器位于不列颠哥伦比亚省温哥华,我现在正在从我的商业合作伙伴居住的南卡罗来纳州和我居住的新斯科舍省哈利法克斯测试我的应用程序。尽管我仍在使用开发虚拟服务器(提示:如果您在开发期间使用虚拟服务器,您可能会考虑创建一个计划任务来保持 API 的活动状态......直到您将其移动到“我遇到的最后一个问题是尝试围绕 CollectionView 实现刷新视图,直到今天我才让它工作。 直到我将layoutbounds从collectionview向上移动到refreshview标签! 我知道我已经很接近了:)这是我的代码,适合那些和我一样苦苦挣扎的人。 请放心使用!我仍然在某些地方进行了一些整理和错误处理代码明智的工作,但是天哪,我终于对这个版本感到满意了。我真诚地感谢所有在整个过程中为我提供建议和想法的人。抱歉花了这么长时间,但我想在发布所有内容之前先纠正一下。 请一切安好:)
我更新的 XAML 代码:
<ContentPage xmlns="http://schemas.microsoft.com/dotnet/2021/maui"
xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
x:Class="yournamespace.Views.MainPageCus"
xmlns:ViewModels="clr-namespace:yournamespace.ViewModels"
xmlns:Models="clr-namespace:yournamespace.Models"
Title="Your Website/app name.com, etc">
<ContentPage.BindingContext>
<ViewModels:MainPageCusViewModel />
</ContentPage.BindingContext>
<ContentPage.ToolbarItems x:Uid="tbOne" >
<ToolbarItem Text="Debug" Clicked="ToolbarItem_Clicked_3" Order="Secondary" x:Name="tbDebug" />
<ToolbarItem Text="Settings" Clicked="ToolbarItem_Clicked" Order="Secondary" x:Name="tbSettings" />
<ToolbarItem Text="Support" Clicked="ToolbarItem_Clicked_1" Order="Secondary" x:Name="tbSupport" />
<ToolbarItem Text="Buy us a Beer!" Order="Secondary" Clicked="ToolbarItem4_Clicked" x:Name="tbBeer" />
<ToolbarItem Text="Log Out" Clicked="ToolbarItem_Clicked_2" Order="Secondary" x:Name="tbLogOut" />
</ContentPage.ToolbarItems>
<ContentPage.Content>
<AbsoluteLayout>
<Image IsVisible="{Binding ShowLabels}" Source="splashmaster.png"
VerticalOptions="CenterAndExpand" HorizontalOptions="CenterAndExpand"
AbsoluteLayout.LayoutBounds="0.5, 0.5, AutoSize, AutoSize" AbsoluteLayout.LayoutFlags="PositionProportional"/>
<RefreshView IsRefreshing="{Binding IsRefreshing}"
Command="{Binding RefreshCommand}" AbsoluteLayout.LayoutBounds="0, 0, 1, 1"
AbsoluteLayout.LayoutFlags="All">
<!-- CollectionView -->
<CollectionView x:Name="CVSaleAlerts"
ItemsSource="{Binding Items}"
ItemsLayout="VerticalList"
RemainingItemsThreshold="10"
RemainingItemsThresholdReachedCommand="{Binding LoadMoreItemsCommand}"
SelectionMode="Single"
SelectionChanged="CVSaleAlerts_SelectionChanged"
HorizontalOptions="CenterAndExpand" BackgroundColor="{Binding Color}">
<CollectionView.ItemTemplate>
<DataTemplate x:DataType="Models:Sale">
<Grid Padding="2" Margin="0, 0, 0, 0" HorizontalOptions="Center">
<Grid.RowDefinitions>
<RowDefinition Height="*" />
<RowDefinition Height="*" />
</Grid.RowDefinitions>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="350" />
</Grid.ColumnDefinitions>
<Image Source="{Binding bg}"
Grid.Row="0"
Grid.Column="0"
Aspect="AspectFill"
WidthRequest="350"
HeightRequest="280"
MinimumHeightRequest="280"
VerticalOptions="FillAndExpand"
HorizontalOptions="Center" />
</Grid>
</DataTemplate>
</CollectionView.ItemTemplate>
</CollectionView>
</RefreshView>
</AbsoluteLayout>
</ContentPage.Content>
</ContentPage>
My Code Behind Code:
using yournamespace.ViewModels;
using yournamespace.Models;
namespace yournamespace.Views;
public partial class MainPageCus : ContentPage
{
MainPageCusViewModel viewModel;
string debugtoggle = string.Empty;
bool debug = false;
public MainPageCus()
{
InitializeComponent();
BindingContext = viewModel = new MainPageCusViewModel();
}
protected override async void OnAppearing()
{
base.OnAppearing();
try
{
string? accesstoken = await SecureStorage.GetAsync("accesstoken");
if (accesstoken == null)
{
App.Current.MainPage = new LoginPage();
}
string? accesstokenexpdate = await SecureStorage.GetAsync("accesstokenexpirydate");
if (accesstokenexpdate == "" || accesstokenexpdate == null)
{
if (DateTime.Parse(accesstokenexpdate.ToString()) < DateTime.Now)
{
App.Current.MainPage = new LoginPage();
}
}
}
catch (Exception ex)
{
}
}
//settings page
private async void ToolbarItem_Clicked(object sender, EventArgs e)
{
await Navigation.PushAsync(new Settings());
}
//support page
private async void ToolbarItem_Clicked_1(object sender, EventArgs e)
{
await Navigation.PushAsync(new Support());
}
//buyusabeer page
private async void ToolbarItem3_Clicked(object sender, EventArgs e)
{
await Navigation.PushAsync(new BuyUsABeer());
}
//logout
private void ToolbarItem_Clicked_2(object sender, EventArgs e)
{
try
{
// Clear the navigation stack
//Navigation.InsertPageBefore(new LoginPage(), Navigation.NavigationStack[0]);
//await Navigation.PopToRootAsync();
App.Current.MainPage = new NavigationPage(new LoginPage());
}
catch (Exception ex)
{
Console.WriteLine("ERROR!!!:" + ex.ToString());
}
}
//debug
private async void ToolbarItem_Clicked_3(object sender, EventArgs e)
{
await Navigation.PushAsync(new Debug());
}
//beer page
private async void ToolbarItem4_Clicked(object sender, EventArgs e)
{
await Navigation.PushAsync(new BuyUsABeer());
}
private async void CVSaleAlerts_SelectionChanged(object sender, SelectionChangedEventArgs e)
{
// Ensure we have a valid selected item
var item = e.CurrentSelection.FirstOrDefault() as Sale;
if (item == null)
return;
// Check if the item has a URL and handle accordingly
if (!string.IsNullOrWhiteSpace(item.adurl))
{
// Open the ad URL in the system's default browser
await Browser.OpenAsync(item.adurl, BrowserLaunchMode.SystemPreferred);
}
else
{
// Navigate to the detailed sales page if no ad URL is present
await Navigation.PushAsync(new CU_Sales_Active_Details(new CU_Sale_Active_Details_ViewModel(item)));
}
// Send click data to the server or database
if (BindingContext is MainPageCusViewModel viewModel)
{
await viewModel.SendClickData(item);
}
// Optionally, deselect the item after processing
//((CollectionView)sender).SelectedItem = null;
}
private void OnRefreshButtonClicked(object sender, EventArgs e)
{
// Your refresh logic here, such as triggering the ViewModel command
var viewModel = BindingContext as MainPageCusViewModel;
if (viewModel != null)
{
viewModel.RefreshCommand.Execute(null);
}
}
}
My ViewModel Code:
using yournamespace.Models;
using yournamespace.Services;
using System.Collections.ObjectModel;
using System.Threading.Tasks;
using System.Windows.Input;
using Microsoft.Maui.Controls.Xaml;
namespace yournamespace.ViewModels
{
public class MainPageCusViewModel : BaseViewModel
{
private readonly DataService _dataService = new DataService();
private const int PageSize = 10;
private int _currentPage = 0; // Track the current page for loading more data
private bool _isLoadingMore = false;
public ObservableCollection<Sale> Items { get; }
private bool isRefreshing;
public bool IsRefreshing
{
get => isRefreshing;
set => SetProperty(ref isRefreshing, value);
}
private bool isBusy;
public bool IsBusy
{
get => isBusy;
set => SetProperty(ref isBusy, value);
}
private bool _Debug;
public bool Debug
{
get => _Debug;
set
{
_Debug = value;
OnPropertyChanged();
}
}
private bool _ShowLabels;
public bool ShowLabels
{
get => _ShowLabels;
set
{
_ShowLabels = value;
OnPropertyChanged();
}
}
private int _totalPageCount = 0;
public int TotalPageCount
{
get => _totalPageCount;
set
{
_totalPageCount = value;
OnPropertyChanged();
}
}
private string _Color = "#003366";
public string Color
{
get => _Color;
set
{
_Color = value;
OnPropertyChanged();
}
}
private int _totalCount = 0;
public int TotalCount
{
get => _totalCount;
set
{
_totalCount = value;
OnPropertyChanged();
}
}
private int _currentCountSalesAds;
public int CurrentCountSalesAds
{
get => _currentCountSalesAds;
set
{
_currentCountSalesAds = value;
OnPropertyChanged();
}
}
public ICommand LoadMoreItemsCommand => new Command(async () => await LoadMoreItemsAsync());
public ICommand RefreshCommand => new Command(async () => await DownloadDataAsync());
public MainPageCusViewModel()
{
Items = new ObservableCollection<Sale>();
ShowLabels = true;
// Trigger initial data load
Task.Run(async () => await DownloadDataAsync());
}
// Method to load initial data
public async Task DownloadDataAsync()
{
try
{
IsRefreshing = true;
IsBusy = true; // Show the ActivityIndicator
Color = "#ffffff";
var items = await _dataService.GetItemsAsync(0, PageSize);
if (items != null && items.Count > 0)
{
Items.Clear();
foreach (var item in items)
{
Items.Add(item);
}
TotalCount = items[0].totalcount; // Assuming first item's totalcount is representative
CurrentCountSalesAds = items.Count;
_currentPage = 1; // Reset to the first page after initial load
}
}
catch (Exception ex)
{
// Handle exceptions as needed
}
finally
{
IsRefreshing = false;
IsBusy = false;
ShowLabels = false;
}
}
// Method to load more items for pagination or infinite scrolling
public async Task LoadMoreItemsAsync()
{
// Check if loading is already in progress or if we have loaded all items
if (_isLoadingMore || CurrentCountSalesAds >= TotalCount)
return;
_isLoadingMore = true;
try
{
//IsRefreshing = true;
IsBusy = true; // Show the ActivityIndicator
// Fetch more data based on the current page
var items = await _dataService.GetItemsAsync(_currentPage, PageSize);
if (items != null && items.Count > 0)
{
foreach (var item in items)
{
Items.Add(item);
}
CurrentCountSalesAds += items.Count;
_currentPage++;
}
}
catch (Exception ex)
{
// Handle exceptions as needed
}
finally
{
IsBusy = false;
//IsRefreshing = false;
_isLoadingMore = false;
}
}
//send click data to db...
public async Task SendClickData(Sale item)
{
await _dataService.SendClickData(item);
}
}
}
干杯,乔:)