在 Xamarin Forms Prism 导航期间视图不会更改
View Doesn't Change During Xamarin Forms Prism Navigation
我的背景是 WPF(使用 MVVMLight 工具包)和 WinForms,但我是 Xamarin Forms 和 Prism 的新手。我正在尝试为我从 Swift 移植的应用程序实施两步登录。在第一步中,用户输入一个客户 ID(我们的每个客户都有很多员工。)在第二步中,他们会看到该客户 ID 的用户列表,然后选择他们的名字并输入密码。
我正在为 Mac、Xamarin Forms 2.3.4.231 和 Prism/Unity 6.3.0
使用 Xamarin Studio 6.3
我的问题是,当我输入客户 ID 并单击提交时,第二个登录视图中的 "OnNavigatingTo" 代码运行,我成功地从我的 Web 服务检索用户列表(此代码在第二个 ViewModel),但第二个视图从未出现。当我单步执行代码时,所有业务逻辑都按预期运行,但从用户的角度来看,视图永远不会改变。我确定我遗漏了一些简单的东西,但在阅读了有关 Prism 导航的多篇博客文章并查看了 S.O.
上的类似问题后,我没有找到任何帮助
app.xaml.cs:
public partial class App : PrismApplication
{
public App(IPlatformInitializer initializer = null) : base(initializer) { }
protected override void OnInitialized()
{
InitializeComponent();
NavigationService.NavigateAsync("DealerLoginPage");
}
protected override void RegisterTypes()
{
Container.RegisterTypeForNavigation<DealerLoginPage>();
Container.RegisterTypeForNavigation<UserLoginPage>();
Container.RegisterTypeForNavigation<SalesSummaryPage>();
// Uncomment this section for design data
//Container.RegisterType<ISalesDataRepo, DesignSalesDataRepo>();
// Uncomment this section for runtime data
Container.RegisterType<ISalesDataRepo, AzureSalesDataRepo>();
}
}
DealerLoginPageViewModel.cs(登录的第一步 - 也是初始 view/VM)
public class DealerLoginPageViewModel : BindableBase
{
private string _chosenDealer;
public string ChosenDealer
{
get { return _chosenDealer; }
set { SetProperty(ref _chosenDealer, value); }
}
private INavigationService _navigationService;
public DelegateCommand Submit { get; set; }
public DealerLoginPageViewModel()
{
}
public DealerLoginPageViewModel(INavigationService navigationService)
{
_navigationService = navigationService;
Submit = new DelegateCommand(Submit_Clicked);
}
void Submit_Clicked()
{
NavigationParameters parameters = new NavigationParameters();
parameters.Add("ChosenDealer", ChosenDealer);
_navigationService.NavigateAsync("UserLoginPage", parameters);
}
}
UserLoginViewModel.cs(登录流程第二步-导航目标)
public class UserLoginPageViewModel : BindableBase, INavigationAware
{
private List<string> _dealerUsers;
public List<string> DealerUsers
{
get { return _dealerUsers; }
set { SetProperty(ref _dealerUsers, value); }
}
private string _selectedUser;
public string SelectedUser
{
get { return _selectedUser; }
set { SetProperty(ref _selectedUser, value); }
}
private string _selectedDealer;
public string SelectedDealer
{
get { return _selectedDealer; }
set { SetProperty(ref _selectedDealer, value); }
}
private INavigationService _navigationService;
private ISalesDataRepo _salesDataRepo;
public DelegateCommand Submit { get; set; }
public UserLoginPageViewModel()
{
}
public UserLoginPageViewModel(INavigationService navigationService, ISalesDataRepo salesDataRepo)
{
_navigationService = navigationService;
_salesDataRepo = salesDataRepo;
Submit = new DelegateCommand(Submit_Clicked);
}
void Submit_Clicked()
{
// unrelated code
}
public void OnNavigatedFrom(NavigationParameters parameters)
{
}
public void OnNavigatedTo(NavigationParameters parameters)
{
}
public void OnNavigatingTo(NavigationParameters parameters)
{
// a breakpoint here gets hit - this code runs, but related view never appears!
if (parameters.ContainsKey("ChosenDealer"))
SelectedDealer = (string)parameters["ChosenDealer"];
if (DealerUsers == null)
{
// we have the dealership, but not the dealership's users yet. Get them asynchronously
var getUsers = _salesDataRepo.GetUserListAsync(SelectedDealer);
getUsers.Start();
// set up the listview once we have the users.
var setUsers = getUsers.ContinueWith((antecedent) =>
{
if (getUsers.Status == System.Threading.Tasks.TaskStatus.RanToCompletion)
{
DealerUsers = getUsers.Result;
}
});
}
}
}
我正在导航到第二个视图(不是 ViewModel),第二个 ViewModel 中的预期代码是 运行 成功,因此似乎一切都已正确连接。有没有人看到我做错了什么?为什么视图没有改变?
* EXTRA CREDIT *: 有谁知道在我的 app.xaml.cs 中创建一个 if-else 语句来根据我是否在中注册适当的存储库的方法设计模式?我已经看到变通方法,您可以在其中检查 App.Current == null 以确定您是否处于设计模式,但显然 Xamarin.Forms 6.2 "broke" 该方法...
提前致谢!
要处理设计 time/mock 服务与生产服务,您可以使用多种技术。但是你会想要一种在它们之间轻松切换的方法,为此我建议使用编译器符号。您将希望在您的项目中创建一个新的构建配置文件,例如您可以创建一个名为 Mock
的配置文件,然后为该配置文件定义一个符号 MOCK
.
- 您可以做的第一件事就是使用编译器指令注册适当的服务
#if MOCK
Container.Register<IFooService,MockFooService>();
#else
Container.Register<IFooService,FooService>();
#endif
- 您可以使用的另一种技术是向文件添加条件编译。此技术会将这两个文件保存在解决方案资源管理器中,并允许您实际命名 类 完全相同(尽管文件本身需要以不同的方式命名)。
您的 csproj 文件可能如下所示:
<ItemGroup>
<Compile Include="Services\MockFooService.cs" Condition=" '$(Configuration)' == 'Mock' " />
<Compile Include="Services\FooService.cs" Condition=" '$(Configuration)' != 'Mock' " />
</ItemGroup>
我想我已经解决了这个问题,它与 Prism 或 Xamarin.Forms 无关;我只是对我的 MVVM 技能组生疏了。
1) 因为我正在异步加载数据,视图试图在 DealerUsers 属性 被赋值之前绑定到它,因此视图可能抛出并吞下 NullReferenceException 并且未能加载(如果对正在发生的事情有更多的可见性,这将更容易诊断。)在 ViewModel 构造函数中,我必须更新列表(DealerUsers = new List<string>();
,然后而不是分配它(DealerUsers = getUsers.Result
在异步方法中,我需要遍历结果并将 getUsers.Result
中的每个字符串一次一个地添加到现有(空)列表中。
2) 正如我应该记得的那样,这仍然不起作用,因为即使填充了列表,View 中的 ListView 仍显示为空。要解决此问题,我必须添加 using System.Collections.ObjectModel;
并将 List<string>
更改为 ObservableCollection<string>
.
** 注意 ** 这解决了我的大部分问题 - 我将正确答案授予 Dan S.,感谢他在故障排除方面的帮助以及他对我的额外学分问题的有效解决方案!
我的背景是 WPF(使用 MVVMLight 工具包)和 WinForms,但我是 Xamarin Forms 和 Prism 的新手。我正在尝试为我从 Swift 移植的应用程序实施两步登录。在第一步中,用户输入一个客户 ID(我们的每个客户都有很多员工。)在第二步中,他们会看到该客户 ID 的用户列表,然后选择他们的名字并输入密码。
我正在为 Mac、Xamarin Forms 2.3.4.231 和 Prism/Unity 6.3.0
使用 Xamarin Studio 6.3我的问题是,当我输入客户 ID 并单击提交时,第二个登录视图中的 "OnNavigatingTo" 代码运行,我成功地从我的 Web 服务检索用户列表(此代码在第二个 ViewModel),但第二个视图从未出现。当我单步执行代码时,所有业务逻辑都按预期运行,但从用户的角度来看,视图永远不会改变。我确定我遗漏了一些简单的东西,但在阅读了有关 Prism 导航的多篇博客文章并查看了 S.O.
上的类似问题后,我没有找到任何帮助app.xaml.cs:
public partial class App : PrismApplication
{
public App(IPlatformInitializer initializer = null) : base(initializer) { }
protected override void OnInitialized()
{
InitializeComponent();
NavigationService.NavigateAsync("DealerLoginPage");
}
protected override void RegisterTypes()
{
Container.RegisterTypeForNavigation<DealerLoginPage>();
Container.RegisterTypeForNavigation<UserLoginPage>();
Container.RegisterTypeForNavigation<SalesSummaryPage>();
// Uncomment this section for design data
//Container.RegisterType<ISalesDataRepo, DesignSalesDataRepo>();
// Uncomment this section for runtime data
Container.RegisterType<ISalesDataRepo, AzureSalesDataRepo>();
}
}
DealerLoginPageViewModel.cs(登录的第一步 - 也是初始 view/VM)
public class DealerLoginPageViewModel : BindableBase
{
private string _chosenDealer;
public string ChosenDealer
{
get { return _chosenDealer; }
set { SetProperty(ref _chosenDealer, value); }
}
private INavigationService _navigationService;
public DelegateCommand Submit { get; set; }
public DealerLoginPageViewModel()
{
}
public DealerLoginPageViewModel(INavigationService navigationService)
{
_navigationService = navigationService;
Submit = new DelegateCommand(Submit_Clicked);
}
void Submit_Clicked()
{
NavigationParameters parameters = new NavigationParameters();
parameters.Add("ChosenDealer", ChosenDealer);
_navigationService.NavigateAsync("UserLoginPage", parameters);
}
}
UserLoginViewModel.cs(登录流程第二步-导航目标)
public class UserLoginPageViewModel : BindableBase, INavigationAware
{
private List<string> _dealerUsers;
public List<string> DealerUsers
{
get { return _dealerUsers; }
set { SetProperty(ref _dealerUsers, value); }
}
private string _selectedUser;
public string SelectedUser
{
get { return _selectedUser; }
set { SetProperty(ref _selectedUser, value); }
}
private string _selectedDealer;
public string SelectedDealer
{
get { return _selectedDealer; }
set { SetProperty(ref _selectedDealer, value); }
}
private INavigationService _navigationService;
private ISalesDataRepo _salesDataRepo;
public DelegateCommand Submit { get; set; }
public UserLoginPageViewModel()
{
}
public UserLoginPageViewModel(INavigationService navigationService, ISalesDataRepo salesDataRepo)
{
_navigationService = navigationService;
_salesDataRepo = salesDataRepo;
Submit = new DelegateCommand(Submit_Clicked);
}
void Submit_Clicked()
{
// unrelated code
}
public void OnNavigatedFrom(NavigationParameters parameters)
{
}
public void OnNavigatedTo(NavigationParameters parameters)
{
}
public void OnNavigatingTo(NavigationParameters parameters)
{
// a breakpoint here gets hit - this code runs, but related view never appears!
if (parameters.ContainsKey("ChosenDealer"))
SelectedDealer = (string)parameters["ChosenDealer"];
if (DealerUsers == null)
{
// we have the dealership, but not the dealership's users yet. Get them asynchronously
var getUsers = _salesDataRepo.GetUserListAsync(SelectedDealer);
getUsers.Start();
// set up the listview once we have the users.
var setUsers = getUsers.ContinueWith((antecedent) =>
{
if (getUsers.Status == System.Threading.Tasks.TaskStatus.RanToCompletion)
{
DealerUsers = getUsers.Result;
}
});
}
}
}
我正在导航到第二个视图(不是 ViewModel),第二个 ViewModel 中的预期代码是 运行 成功,因此似乎一切都已正确连接。有没有人看到我做错了什么?为什么视图没有改变?
* EXTRA CREDIT *: 有谁知道在我的 app.xaml.cs 中创建一个 if-else 语句来根据我是否在中注册适当的存储库的方法设计模式?我已经看到变通方法,您可以在其中检查 App.Current == null 以确定您是否处于设计模式,但显然 Xamarin.Forms 6.2 "broke" 该方法...
提前致谢!
要处理设计 time/mock 服务与生产服务,您可以使用多种技术。但是你会想要一种在它们之间轻松切换的方法,为此我建议使用编译器符号。您将希望在您的项目中创建一个新的构建配置文件,例如您可以创建一个名为 Mock
的配置文件,然后为该配置文件定义一个符号 MOCK
.
- 您可以做的第一件事就是使用编译器指令注册适当的服务
#if MOCK
Container.Register<IFooService,MockFooService>();
#else
Container.Register<IFooService,FooService>();
#endif
- 您可以使用的另一种技术是向文件添加条件编译。此技术会将这两个文件保存在解决方案资源管理器中,并允许您实际命名 类 完全相同(尽管文件本身需要以不同的方式命名)。
您的 csproj 文件可能如下所示:
<ItemGroup>
<Compile Include="Services\MockFooService.cs" Condition=" '$(Configuration)' == 'Mock' " />
<Compile Include="Services\FooService.cs" Condition=" '$(Configuration)' != 'Mock' " />
</ItemGroup>
我想我已经解决了这个问题,它与 Prism 或 Xamarin.Forms 无关;我只是对我的 MVVM 技能组生疏了。
1) 因为我正在异步加载数据,视图试图在 DealerUsers 属性 被赋值之前绑定到它,因此视图可能抛出并吞下 NullReferenceException 并且未能加载(如果对正在发生的事情有更多的可见性,这将更容易诊断。)在 ViewModel 构造函数中,我必须更新列表(DealerUsers = new List<string>();
,然后而不是分配它(DealerUsers = getUsers.Result
在异步方法中,我需要遍历结果并将 getUsers.Result
中的每个字符串一次一个地添加到现有(空)列表中。
2) 正如我应该记得的那样,这仍然不起作用,因为即使填充了列表,View 中的 ListView 仍显示为空。要解决此问题,我必须添加 using System.Collections.ObjectModel;
并将 List<string>
更改为 ObservableCollection<string>
.
** 注意 ** 这解决了我的大部分问题 - 我将正确答案授予 Dan S.,感谢他在故障排除方面的帮助以及他对我的额外学分问题的有效解决方案!