如何从 MVVM 中的异步 SQLite 数据源填充选择器选项
How to populate picker options from async SQLite datasource in MVVM
我需要一个选择器(或其他控件)在 MVVM 实现(基于 Xamarin.Forms MVVM: How to Work with SQLite DB(C# — Xaml))中提供用户选项,但看不到如何将异步选项加载到绑定中。
我有一个名为 viewings 的记录类型的详细信息编辑屏幕,其中直接输入字段绑定到 ViewingDetailViewingModel。
...BindingContext = new ViewingDetailViewModel(viewModel ?? new ViewingViewModel(), viewingStore, pageService, viewingsRestStore);
在 XAML 的代码隐藏中。
除了绑定到 Viewing 模型属性的相对直接的输入单元格(如果我没有使用正确的命名法,请原谅我,这是新的),我想让其中一个字段使用选择器根据用户选择客户端保存客户端 ID。
所以我猜,由于 SQLite clientstore 的 get return 是异步的,我应该有一个从某处触发的命令,我理想地想象从代码隐藏的 onAppearing 方法更新客户端的 IList选择器绑定到 - 虽然我在这里遇到两个问题:
- 我不知道如何从那里触发命令,所以我现在尝试从视图模型的构造函数的末尾执行它。
- 这样做(虽然我不认为是因为那个),应用程序在尝试将第一个客户端添加到列表时崩溃,同时使用“...System.NullReferenceException 循环获取结果:你调用的对象是空的
在 AgentApp.ViewingDetailViewModel.LoadClients(System.Collections.Generic.IList`1[T] 个客户端)..."
所以在我的 XAML 我有
<EntryCell Label="Agt_description" Text="{Binding Path=Viewing.Agt_description}" />
<ViewCell >
<Picker Title="Select a client" ItemsSource="{Binding Clients}" ItemDisplayBinding ="{Binding Agt_FirstName}" />
</ViewCell>
在视图的代码隐藏中:
public ViewingDetailPage(ViewingViewModel viewModel)
{
Console.WriteLine("ViewingDetailPage()");
InitializeComponent();
var viewingStore = new SQLiteViewingStore(DependencyService.Get<ISQLiteDb>());
var viewingsRestStore = new RESTViewingStore();
var pageService = new PageService();
bool isNewViewing = (viewModel.Agt_name == " ");
Title = (isNewViewing) ? "New Viewing" : "Edit Viewing";
if (isNewViewing)
{
var app = Application.Current as App;
viewModel.Agt_ac = app.AgencyIdInt;
viewModel.Agt_b = app.BranchIDInt;
viewModel.Agt_at = app.AccountIdInt;
}
BindingContext = new ViewingDetailViewModel(viewModel ?? new ViewingViewModel(), viewingStore, pageService, viewingsRestStore);
}
然后是 ViewingDetailViewModel:
class ViewingDetailViewModel : BaseViewModel
{
private readonly IViewingStore _viewingStore;
private readonly IViewingStore _viewingRestStore;
private readonly IPageService _pageService;
public Viewing Viewing { get; private set; }
public ICommand SaveCommand { get; private set; }
//trying
public ICommand LoadClientsCommand { get; private set; }
private IClientStore _clientStore;
private IList<ClientViewModel> _clients;
public IList<ClientViewModel> Clients
{
get { return _clients; }
set { SetProperty(ref _clients, value); } // from BaseViewModel, implements OnPropertyChanged()
}
//
public ViewingDetailViewModel(ViewingViewModel viewModel, IViewingStore viewingStore, IPageService pageService, IViewingStore viewingRestStore)
{
var app = Application.Current as App;
if (viewModel == null)
throw new ArgumentNullException(nameof(viewModel));
_pageService = pageService;
_viewingStore = viewingStore;
_viewingRestStore = viewingRestStore;
LoadClientsCommand = new Command(async () => await LoadClients());
SaveCommand = new Command(async () =>
{
var SaveDBTask = SaveDB("primary");
var SaveRESTTask = SaveRest();
var saveTasks = new List<Task> { SaveDBTask, SaveRESTTask };
while (saveTasks.Count > 0)
{
Task finishedTask = await Task.WhenAny(saveTasks);
if (finishedTask == SaveDBTask)
{
Console.WriteLine("SaveDBTask finished:" + SaveDBTask);
}
else if (finishedTask == SaveRESTTask)
{
Console.WriteLine("SaveRESTTask finished, do db update:" + SaveRESTTask);
await SaveDB("secondary update");
}
saveTasks.Remove(finishedTask);
}
Console.WriteLine("Save tasks complete.");
});
Viewing = new Viewing
{
Id = viewModel.Id,
Agt_description = viewModel.Agt_description,
Agt_name = viewModel.Agt_name,
RemoteId = viewModel.RemoteId,
Agt_ac = viewModel.Agt_ac > 0 ? viewModel.Agt_ac : app.AgencyIdInt,
Agt_b = viewModel.Agt_b > 0 ? viewModel.Agt_b : app.BranchIDInt,
Agt_at = viewModel.Agt_at > 0 ? viewModel.Agt_at : app.AccountIdInt,
Agt_pr = viewModel.Agt_pr,
Agt_datetime_scheduled = viewModel.Agt_datetime_scheduled,
Agt_datetime_start = viewModel.Agt_datetime_start,
Agt_datetime_end = viewModel.Agt_datetime_end,
StatusLocal = viewModel.StatusLocal,
CreatedLocal = viewModel.CreatedLocal,
ModifiedLocal = viewModel.ModifiedLocal,
CreatedRemote = viewModel.CreatedRemote,
ModifiedRemote = viewModel.ModifiedRemote,
LastSync = viewModel.LastSync,
};
LoadClientsCommand.Execute(null);//doubt this is a good idea
}
async Task LoadClients()
{
_clientStore = new SQLiteClientStore(DependencyService.Get<ISQLiteDb>());
var clients = await _clientStore.GetClientsAsync();
foreach (var client in clients)
{
Console.WriteLine("for client:" + client.Agt_FirstName);
Clients.Add(new ClientViewModel(client)); //crashes around here
}
}
async Task SaveDB(string savetype)
{
if (String.IsNullOrWhiteSpace(Viewing.Agt_name))
{
await _pageService.DisplayAlert("Error", "Please enter the name.", "OK");
return;
}
if (Viewing.Id == 0)
{
await _viewingStore.AddViewing(Viewing);
MessagingCenter.Send(this, Events.ViewingAdded, Viewing);
}
else
{
await _viewingStore.UpdateViewing(Viewing);
MessagingCenter.Send(this, Events.ViewingUpdated, Viewing);
}
if (savetype == "primary")
await _pageService.PopAsync();
}
async Task SaveRest()
{
if (String.IsNullOrWhiteSpace(Viewing.Agt_name))
{
Console.WriteLine("REST save could not, invalid name.");
return;
}
if (Viewing.RemoteId == 0)
{
await _viewingRestStore.AddViewing(Viewing);
}
else
{
await _viewingRestStore.UpdateViewing(Viewing);
MessagingCenter.Send(this, Events.ViewingUpdated, Viewing);
}
}
}
我是一个来自简单 php 背景的完全新手,所有这些面向对象的复杂性让我有些困惑;我可能犯了很多非常愚蠢的错误——也许我把事情搞得太复杂了——我希望有人能给我指出一个加载选择器异步数据的简单好方法。
提前致谢!
首先,您似乎没有在任何地方实例化 Clients
属性。这就是为什么你会得到 NullReferenceException
其次,如果您要将项目添加到 IList
中,则没有人会收到有关已将某些内容添加到该集合中的通知。相反,您应该考虑将类型更改为 ObservableCollection
,这有助于您这样做。
因此将 Clients
的代码更改为:
public ObservableCollection<ClientViewModel> Clients { get; }
= new ObservableCollection<ClientViewModel>();
这可能会解决您的大部分问题,即 UI 未更新且 Clients
在您尝试填充它时为 null。
我需要一个选择器(或其他控件)在 MVVM 实现(基于 Xamarin.Forms MVVM: How to Work with SQLite DB(C# — Xaml))中提供用户选项,但看不到如何将异步选项加载到绑定中。
我有一个名为 viewings 的记录类型的详细信息编辑屏幕,其中直接输入字段绑定到 ViewingDetailViewingModel。
...BindingContext = new ViewingDetailViewModel(viewModel ?? new ViewingViewModel(), viewingStore, pageService, viewingsRestStore);
在 XAML 的代码隐藏中。
除了绑定到 Viewing 模型属性的相对直接的输入单元格(如果我没有使用正确的命名法,请原谅我,这是新的),我想让其中一个字段使用选择器根据用户选择客户端保存客户端 ID。
所以我猜,由于 SQLite clientstore 的 get return 是异步的,我应该有一个从某处触发的命令,我理想地想象从代码隐藏的 onAppearing 方法更新客户端的 IList选择器绑定到 - 虽然我在这里遇到两个问题:
- 我不知道如何从那里触发命令,所以我现在尝试从视图模型的构造函数的末尾执行它。
- 这样做(虽然我不认为是因为那个),应用程序在尝试将第一个客户端添加到列表时崩溃,同时使用“...System.NullReferenceException 循环获取结果:你调用的对象是空的 在 AgentApp.ViewingDetailViewModel.LoadClients(System.Collections.Generic.IList`1[T] 个客户端)..."
所以在我的 XAML 我有
<EntryCell Label="Agt_description" Text="{Binding Path=Viewing.Agt_description}" />
<ViewCell >
<Picker Title="Select a client" ItemsSource="{Binding Clients}" ItemDisplayBinding ="{Binding Agt_FirstName}" />
</ViewCell>
在视图的代码隐藏中:
public ViewingDetailPage(ViewingViewModel viewModel)
{
Console.WriteLine("ViewingDetailPage()");
InitializeComponent();
var viewingStore = new SQLiteViewingStore(DependencyService.Get<ISQLiteDb>());
var viewingsRestStore = new RESTViewingStore();
var pageService = new PageService();
bool isNewViewing = (viewModel.Agt_name == " ");
Title = (isNewViewing) ? "New Viewing" : "Edit Viewing";
if (isNewViewing)
{
var app = Application.Current as App;
viewModel.Agt_ac = app.AgencyIdInt;
viewModel.Agt_b = app.BranchIDInt;
viewModel.Agt_at = app.AccountIdInt;
}
BindingContext = new ViewingDetailViewModel(viewModel ?? new ViewingViewModel(), viewingStore, pageService, viewingsRestStore);
}
然后是 ViewingDetailViewModel:
class ViewingDetailViewModel : BaseViewModel
{
private readonly IViewingStore _viewingStore;
private readonly IViewingStore _viewingRestStore;
private readonly IPageService _pageService;
public Viewing Viewing { get; private set; }
public ICommand SaveCommand { get; private set; }
//trying
public ICommand LoadClientsCommand { get; private set; }
private IClientStore _clientStore;
private IList<ClientViewModel> _clients;
public IList<ClientViewModel> Clients
{
get { return _clients; }
set { SetProperty(ref _clients, value); } // from BaseViewModel, implements OnPropertyChanged()
}
//
public ViewingDetailViewModel(ViewingViewModel viewModel, IViewingStore viewingStore, IPageService pageService, IViewingStore viewingRestStore)
{
var app = Application.Current as App;
if (viewModel == null)
throw new ArgumentNullException(nameof(viewModel));
_pageService = pageService;
_viewingStore = viewingStore;
_viewingRestStore = viewingRestStore;
LoadClientsCommand = new Command(async () => await LoadClients());
SaveCommand = new Command(async () =>
{
var SaveDBTask = SaveDB("primary");
var SaveRESTTask = SaveRest();
var saveTasks = new List<Task> { SaveDBTask, SaveRESTTask };
while (saveTasks.Count > 0)
{
Task finishedTask = await Task.WhenAny(saveTasks);
if (finishedTask == SaveDBTask)
{
Console.WriteLine("SaveDBTask finished:" + SaveDBTask);
}
else if (finishedTask == SaveRESTTask)
{
Console.WriteLine("SaveRESTTask finished, do db update:" + SaveRESTTask);
await SaveDB("secondary update");
}
saveTasks.Remove(finishedTask);
}
Console.WriteLine("Save tasks complete.");
});
Viewing = new Viewing
{
Id = viewModel.Id,
Agt_description = viewModel.Agt_description,
Agt_name = viewModel.Agt_name,
RemoteId = viewModel.RemoteId,
Agt_ac = viewModel.Agt_ac > 0 ? viewModel.Agt_ac : app.AgencyIdInt,
Agt_b = viewModel.Agt_b > 0 ? viewModel.Agt_b : app.BranchIDInt,
Agt_at = viewModel.Agt_at > 0 ? viewModel.Agt_at : app.AccountIdInt,
Agt_pr = viewModel.Agt_pr,
Agt_datetime_scheduled = viewModel.Agt_datetime_scheduled,
Agt_datetime_start = viewModel.Agt_datetime_start,
Agt_datetime_end = viewModel.Agt_datetime_end,
StatusLocal = viewModel.StatusLocal,
CreatedLocal = viewModel.CreatedLocal,
ModifiedLocal = viewModel.ModifiedLocal,
CreatedRemote = viewModel.CreatedRemote,
ModifiedRemote = viewModel.ModifiedRemote,
LastSync = viewModel.LastSync,
};
LoadClientsCommand.Execute(null);//doubt this is a good idea
}
async Task LoadClients()
{
_clientStore = new SQLiteClientStore(DependencyService.Get<ISQLiteDb>());
var clients = await _clientStore.GetClientsAsync();
foreach (var client in clients)
{
Console.WriteLine("for client:" + client.Agt_FirstName);
Clients.Add(new ClientViewModel(client)); //crashes around here
}
}
async Task SaveDB(string savetype)
{
if (String.IsNullOrWhiteSpace(Viewing.Agt_name))
{
await _pageService.DisplayAlert("Error", "Please enter the name.", "OK");
return;
}
if (Viewing.Id == 0)
{
await _viewingStore.AddViewing(Viewing);
MessagingCenter.Send(this, Events.ViewingAdded, Viewing);
}
else
{
await _viewingStore.UpdateViewing(Viewing);
MessagingCenter.Send(this, Events.ViewingUpdated, Viewing);
}
if (savetype == "primary")
await _pageService.PopAsync();
}
async Task SaveRest()
{
if (String.IsNullOrWhiteSpace(Viewing.Agt_name))
{
Console.WriteLine("REST save could not, invalid name.");
return;
}
if (Viewing.RemoteId == 0)
{
await _viewingRestStore.AddViewing(Viewing);
}
else
{
await _viewingRestStore.UpdateViewing(Viewing);
MessagingCenter.Send(this, Events.ViewingUpdated, Viewing);
}
}
}
我是一个来自简单 php 背景的完全新手,所有这些面向对象的复杂性让我有些困惑;我可能犯了很多非常愚蠢的错误——也许我把事情搞得太复杂了——我希望有人能给我指出一个加载选择器异步数据的简单好方法。
提前致谢!
首先,您似乎没有在任何地方实例化 Clients
属性。这就是为什么你会得到 NullReferenceException
其次,如果您要将项目添加到 IList
中,则没有人会收到有关已将某些内容添加到该集合中的通知。相反,您应该考虑将类型更改为 ObservableCollection
,这有助于您这样做。
因此将 Clients
的代码更改为:
public ObservableCollection<ClientViewModel> Clients { get; }
= new ObservableCollection<ClientViewModel>();
这可能会解决您的大部分问题,即 UI 未更新且 Clients
在您尝试填充它时为 null。