如何在 Xamarin Forms 中使用 MVVM 在 ListView 中实现增量加载(ItemAppearing)项目?
How to implement Loading Incrementally (ItemAppearing) Items in ListView using MVVM in Xamarin Forms?
我有一个 ListView
,其中使用 ItemAppearing
逐渐添加项目。我希望它通过我的 ViewModel 来实现它。 ItemAppearing
仅从 View.cs 调用一个方法,因此,有什么方法可以在我的 ViewModel class.
请注意,我可以在从 View.cs 添加项目时逐步加载项目。我只想从 ViewModel 加载更多项目。
这是我的 XAML 代码:
<ListView ItemsSource="{Binding JobsList}" HasUnevenRows="True"
SelectedItem="{Binding SelectedJob}" ItemAppearing="LoadMoreItems">
<ListView.ItemTemplate>
<DataTemplate>
<ViewCell>
<Label Text="{Binding Title}" />
</ViewCell>
</DataTemplate>
</ListView.ItemTemplate>
</ListView>
如果您添加(到您的问题)更多关于您正在尝试做的事情的详细信息,那将是最好的。
以下技术模糊了“View 做什么”和“ViewModel 做什么”之间的界限 - 并且“紧密耦合”了您的 View 和 ViewModel - 可能有更好的方法来实现您的目标。不过,这是一个很有用的技术,所以我会展示它。
Jason 的评论“从事件处理程序调用您的 VM 方法”的详细信息。
向您的 VM 添加一个 public 方法:
public class MyVM
{
public void MyMethod() {
// whatever you need to do to prepare the item.
}
}
在 LoadMoreItems 中,调用该方法:
((MyVM)BindingContext).MyMethod();
根据需要添加参数。
如果您需要“回调”到视图中的方法,请通过操作参数执行此操作:
public void MyMethod(Action<...> action) {
...
action(...);
}
有关详细信息,google C# 将 Action 作为参数传递。
还有其他用于在 View 和 ViewModel 之间进行通信的技术 - 搜索有关该主题的更多信息。
此答案会比较冗长,但您不会在其他任何地方找到它。我已经试了2天了
这个答案很长,因为我已经证明了三件事:
- 将
ItemAppearing
绑定到 Command
,然后逐步加载项目。
- 从
ListView
中选择一个项目并显示它。
- 在增量加载新项目时显示动画。
在 MVVM 中,ViewModel 应该不知道 View。因此 ViewModel 一定不知道 View 中的 ListView
是否滚动到最后一项或者 LoadMoreItems 应该在滚动时调用。
我们需要将 ItemAppearing
事件转换为 Command
并将此命令绑定到 ItemAppearing
事件。
为此,我们需要安装 Xamarin.CommunityToolkit Nuget 包。此包受 .NetFoundation、Xamarin Community 和 Microsoft 支持,作者为 微软。这是官方包,是大多数高级 Xamarin.Forms 所必需的。在 Nuget.org 上查看更多信息,下载最新的稳定版本:https://www.nuget.org/packages/Xamarin.CommunityToolkit(安装在您所有共享的项目中,Android、iOS、UWP、WPF、Tizen 等)
假设您的模型:
public class Job
{
public int Id { get; set; }
public string Title { get; set; }
}
现在在你的 XAML
<ContentPage xmlns="http://xamarin.com/schemas/2014/forms"
...
xmlns:xct="http://xamarin.com/schemas/2020/toolkit">
<StackLayout>
<RefreshView IsRefreshing="{Binding IsLoading}">
<ListView ItemsSource="{Binding JobsList}" SelectedItem="{Binding SelectedJob}">
<ListView.Behaviors>
<xct:EventToCommandBehavior EventName="ItemAppearing"
Command="{Binding LoadMoreItemsCommand}"
CommandParameter="{Binding ItemVisibilityEventArgs}"/>
</ListView.Behaviors>
<ListView.ItemTemplate>
<DataTemplate>
<ViewCell>
<StackLayout>
<Label Text="{Binding Id}" />
<Label Text="{Binding Title}" />
</StackLayout>
</ViewCell>
</DataTemplate>
</ListView.ItemTemplate>
</ListView>
</StackLayout>
</ContentPage>
在后面的代码中将 BindingContext 设置为 ViewModel 的实例
public partial class ListPage : ContentPage
{
ListPageViewModel ListPageVM;
public JobsListPage()
{
InitializeComponent();
ListPageVM = new ListPageViewModel();
BindingContext = ListPageVM;
}
}
在您的 ViewModel 中
public class ListPageViewModel : INotifyPropertyChanged
{
public ObservableCollection<Models.Job> JobsList
{
get { return jobsList; }
set { jobsList = value; OnPropertyChanged(nameof(JobsList)); }
}
public ICommand LoadMoreItemsCommand { get; private set; }
// Used to show Loading Animation in Refresh View
public bool IsLoading
{
get { return isLoading; }
set { isLoading = value; OnPropertyChanged(nameof(IsLoading)); }
}
public Models.Job SelectedJob
{
get { return selectedJob; }
set
{
if (value != null)
{
selectedJob = value;
var page = Application.Current.MainPage;
page.DisplayAlert("Alert", $"Selected: {selectedJob.JobTitle}", "OK");
OnPropertyChanged(nameof(SelectedJob));
}
}
}
ObservableCollection<Models.Job> jobsList;
Models.Job selectedJob;
bool isLoading;
public ListPageViewModel() // ViewModel Constructor
{
// Initialize your List
JobsList = new ObservableCollection<Models.Job>
{
new Models.Job() { Id = 0001, Title = "Product Manager" },
new Models.Job() { Id = 0002, Title = "Senior Executive" },
}
LoadMoreItemsCommand = new Command<ItemVisibilityEventArgs>(
execute: async (ItemVisibilityEventArgs args) =>
{
if ((args.Item as Models.Job).Id >= JobsList[JobsList.Count - 1].Id)
{
IsLoading = true;
for (int i = 0; i < 10; i++)
{
JobsList.Add(new Models.Job()
{
Id = JobsList.Count + 1, JobTitle = JobsList[i].Title
});
}
await System.Threading.Tasks.Task.Delay(2000); // Fake delay
IsLoading = false;
}
});
}
public event PropertyChangedEventHandler PropertyChanged;
protected virtual void OnPropertyChanged(string propertyName)
{
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
}
}
我有一个 ListView
,其中使用 ItemAppearing
逐渐添加项目。我希望它通过我的 ViewModel 来实现它。 ItemAppearing
仅从 View.cs 调用一个方法,因此,有什么方法可以在我的 ViewModel class.
请注意,我可以在从 View.cs 添加项目时逐步加载项目。我只想从 ViewModel 加载更多项目。
这是我的 XAML 代码:
<ListView ItemsSource="{Binding JobsList}" HasUnevenRows="True"
SelectedItem="{Binding SelectedJob}" ItemAppearing="LoadMoreItems">
<ListView.ItemTemplate>
<DataTemplate>
<ViewCell>
<Label Text="{Binding Title}" />
</ViewCell>
</DataTemplate>
</ListView.ItemTemplate>
</ListView>
如果您添加(到您的问题)更多关于您正在尝试做的事情的详细信息,那将是最好的。
以下技术模糊了“View 做什么”和“ViewModel 做什么”之间的界限 - 并且“紧密耦合”了您的 View 和 ViewModel - 可能有更好的方法来实现您的目标。不过,这是一个很有用的技术,所以我会展示它。
Jason 的评论“从事件处理程序调用您的 VM 方法”的详细信息。
向您的 VM 添加一个 public 方法:
public class MyVM
{
public void MyMethod() {
// whatever you need to do to prepare the item.
}
}
在 LoadMoreItems 中,调用该方法:
((MyVM)BindingContext).MyMethod();
根据需要添加参数。
如果您需要“回调”到视图中的方法,请通过操作参数执行此操作:
public void MyMethod(Action<...> action) {
...
action(...);
}
有关详细信息,google C# 将 Action 作为参数传递。
还有其他用于在 View 和 ViewModel 之间进行通信的技术 - 搜索有关该主题的更多信息。
此答案会比较冗长,但您不会在其他任何地方找到它。我已经试了2天了
这个答案很长,因为我已经证明了三件事:
- 将
ItemAppearing
绑定到Command
,然后逐步加载项目。 - 从
ListView
中选择一个项目并显示它。 - 在增量加载新项目时显示动画。
在 MVVM 中,ViewModel 应该不知道 View。因此 ViewModel 一定不知道 View 中的 ListView
是否滚动到最后一项或者 LoadMoreItems 应该在滚动时调用。
我们需要将 ItemAppearing
事件转换为 Command
并将此命令绑定到 ItemAppearing
事件。
为此,我们需要安装 Xamarin.CommunityToolkit Nuget 包。此包受 .NetFoundation、Xamarin Community 和 Microsoft 支持,作者为 微软。这是官方包,是大多数高级 Xamarin.Forms 所必需的。在 Nuget.org 上查看更多信息,下载最新的稳定版本:https://www.nuget.org/packages/Xamarin.CommunityToolkit(安装在您所有共享的项目中,Android、iOS、UWP、WPF、Tizen 等)
假设您的模型:
public class Job
{
public int Id { get; set; }
public string Title { get; set; }
}
现在在你的 XAML
<ContentPage xmlns="http://xamarin.com/schemas/2014/forms"
...
xmlns:xct="http://xamarin.com/schemas/2020/toolkit">
<StackLayout>
<RefreshView IsRefreshing="{Binding IsLoading}">
<ListView ItemsSource="{Binding JobsList}" SelectedItem="{Binding SelectedJob}">
<ListView.Behaviors>
<xct:EventToCommandBehavior EventName="ItemAppearing"
Command="{Binding LoadMoreItemsCommand}"
CommandParameter="{Binding ItemVisibilityEventArgs}"/>
</ListView.Behaviors>
<ListView.ItemTemplate>
<DataTemplate>
<ViewCell>
<StackLayout>
<Label Text="{Binding Id}" />
<Label Text="{Binding Title}" />
</StackLayout>
</ViewCell>
</DataTemplate>
</ListView.ItemTemplate>
</ListView>
</StackLayout>
</ContentPage>
在后面的代码中将 BindingContext 设置为 ViewModel 的实例
public partial class ListPage : ContentPage
{
ListPageViewModel ListPageVM;
public JobsListPage()
{
InitializeComponent();
ListPageVM = new ListPageViewModel();
BindingContext = ListPageVM;
}
}
在您的 ViewModel 中
public class ListPageViewModel : INotifyPropertyChanged
{
public ObservableCollection<Models.Job> JobsList
{
get { return jobsList; }
set { jobsList = value; OnPropertyChanged(nameof(JobsList)); }
}
public ICommand LoadMoreItemsCommand { get; private set; }
// Used to show Loading Animation in Refresh View
public bool IsLoading
{
get { return isLoading; }
set { isLoading = value; OnPropertyChanged(nameof(IsLoading)); }
}
public Models.Job SelectedJob
{
get { return selectedJob; }
set
{
if (value != null)
{
selectedJob = value;
var page = Application.Current.MainPage;
page.DisplayAlert("Alert", $"Selected: {selectedJob.JobTitle}", "OK");
OnPropertyChanged(nameof(SelectedJob));
}
}
}
ObservableCollection<Models.Job> jobsList;
Models.Job selectedJob;
bool isLoading;
public ListPageViewModel() // ViewModel Constructor
{
// Initialize your List
JobsList = new ObservableCollection<Models.Job>
{
new Models.Job() { Id = 0001, Title = "Product Manager" },
new Models.Job() { Id = 0002, Title = "Senior Executive" },
}
LoadMoreItemsCommand = new Command<ItemVisibilityEventArgs>(
execute: async (ItemVisibilityEventArgs args) =>
{
if ((args.Item as Models.Job).Id >= JobsList[JobsList.Count - 1].Id)
{
IsLoading = true;
for (int i = 0; i < 10; i++)
{
JobsList.Add(new Models.Job()
{
Id = JobsList.Count + 1, JobTitle = JobsList[i].Title
});
}
await System.Threading.Tasks.Task.Delay(2000); // Fake delay
IsLoading = false;
}
});
}
public event PropertyChangedEventHandler PropertyChanged;
protected virtual void OnPropertyChanged(string propertyName)
{
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
}
}