我在“添加和详细信息”页面上有一个共享视图。由于详细信息页面中的某些原因,视图模型不会绑定到该子视图(页面显示为空白,因为api服务中没有填充值)。有什么想法吗?
对此进行调试,并且来自Web api的数据同时包含 CategoryList 和 _activity 。
如何调试此绑定过程?
ActivityView.xaml
<ContentView xmlns="http://xamarin.com/schemas/2014/forms"
xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
x:Class="AthlosifyMobileApp.Views.ActivityView">
<StackLayout Spacing="12">
<Entry x:Name="txtName" Text="{Binding Name}" HeightRequest="40" BackgroundColor="White" Placeholder="Name" HorizontalOptions="FillAndExpand"/>
<Entry x:Name="txtNoOfMinutes" Keyboard="Numeric" Text="{Binding NoOfMinutes}" BackgroundColor="White" Placeholder="NoOfMinutes" HorizontalOptions="FillAndExpand"/>
<Entry x:Name="txtDescription" Text="{Binding Description}" HeightRequest="40" BackgroundColor="White" Placeholder="Description" HorizontalOptions="FillAndExpand"/>
<Picker ItemsSource="{Binding CategoryList}" ItemDisplayBinding="{Binding Name}" SelectedItem="{Binding SelectedCategory}"></Picker>
</StackLayout>
</ContentView>
ActivityView.xaml.cs
namespace AthlosifyMobileApp.Views
{
[XamlCompilation(XamlCompilationOptions.Compile)]
public partial class ActivityView : ContentView
{
public ActivityView ()
{
InitializeComponent ();
}
}
}
ActivityDetailViewModel.cs
namespace AthlosifyMobileApp.ViewModels
{
public class ActivityDetailViewModel : ActivityBaseViewModel
{
public ICommand DeleteCommand { get; private set; }
public ICommand UpdateCommand { get; private set; }
public ActivityDetailViewModel(INavigation navigation, int selectedActivityId)
{
_navigation = navigation;
_activityValidator = new ActivityValidator();
_activity = new Activity();
_activity.Id = selectedActivityId;
_apiService = new ApiService();
//DeleteCommand = new Command(async () => await HandleDeleteActivity());
UpdateCommand = new Command(async () => await UpdateActivity());
FetchActivityDetail();
FetchCategories();
}
async void FetchActivityDetail()
{
_activity = await _apiService.GetActivity(_activity.Id);
}
async void FetchCategories()
{
CategoryResult categoryResult = await _apiService.GetCategories();
CategoryList = categoryResult.Results;
}
async Task UpdateActivity()
{
_activity.OwnerId = Preferences.Get(Constant.Setting_UserId, "");
_activity.CategoryId = SelectedCategory.Id;
_activity.CategoryName = SelectedCategory.Name;
var validationResults = _activityValidator.Validate(_activity);
if (validationResults.IsValid)
{
bool isUserAccept = await Application.Current.MainPage.DisplayAlert("Contact Details", "Update Contact Details", "OK", "Cancel");
if (isUserAccept)
{
var response = await _apiService.UpdateActivity(_activity.Id,_activity);
if (!response)
{
await Application.Current.MainPage.DisplayAlert("Add Activity", "Error", "Alright");
}
else
{
await _navigation.PushAsync(new ActivityListPage());
}
await _navigation.PopAsync();
}
}
else
{
await Application.Current.MainPage.DisplayAlert("Add Contact", validationResults.Errors[0].ErrorMessage, "Ok");
}
}
public async Task HandleDeleteActivity(int id)
{
var alert = await Application.Current.MainPage.DisplayAlert("Warning", "Do you want to delete this item?", "Yes", "Cancel");
if (alert)
{
var response = await _apiService.DeleteActivity(id);
if (!response)
{
await Application.Current.MainPage.DisplayAlert("Error", "Something wrong", "Alright");
}
else
{
await _navigation.PushAsync(new ActivityListPage());
}
}
}
}
}
ActivityBaseViewModel.cs
namespace AthlosifyMobileApp.ViewModels
{
public class ActivityBaseViewModel : INotifyPropertyChanged
{
public Activity _activity;
public INavigation _navigation;
public IValidator _activityValidator;
public ApiService _apiService;
public string Name
{
get
{
return _activity.Name;
}
set
{
_activity.Name = value;
NotifyPropertyChanged("Name");
}
}
public string Description
{
get { return _activity.Description; }
set
{
_activity.Description = value;
NotifyPropertyChanged("Description");
}
}
public int NoOfMinutes
{
get { return _activity.NoOfMinutes; }
set
{
_activity.NoOfMinutes = value;
NotifyPropertyChanged("NoOfMinutes");
}
}
public int CategoryId
{
get { return _activity.CategoryId; }
set
{
_activity.CategoryId = value;
NotifyPropertyChanged("CategoryId");
}
}
public string CategoryName
{
get { return _activity.CategoryName; }
set
{
_activity.CategoryName = value;
NotifyPropertyChanged("CategoryName");
}
}
//List<Activity> _activityList;
InfiniteScrollCollection<Activity> _activityList;
//public List<Activity> ActivityList
public InfiniteScrollCollection<Activity> ActivityList
{
get => _activityList;
set
{
_activityList = value;
NotifyPropertyChanged("ActivityList");
}
}
List<Category> _categoryList;
public List<Category> CategoryList
{
get { return _categoryList; }
set
{
_categoryList = value;
NotifyPropertyChanged("CategoryList");
}
}
public Category SelectedCategory
{
get
{
return _activity.SelectedCategory;
}
set
{
_activity.SelectedCategory = value;
NotifyPropertyChanged("SelectedCategory");
}
}
#region INotifyPropertyChanged
public event PropertyChangedEventHandler PropertyChanged;
protected void NotifyPropertyChanged([CallerMemberName] string propertyName = "")
{
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
}
#endregion
}
}
ActivityDetailPage.xaml
<ContentPage xmlns="http://xamarin.com/schemas/2014/forms"
xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
xmlns:local="clr-namespace:AthlosifyMobileApp.Views"
x:Class="AthlosifyMobileApp.Views.ActivityDetailPage"
Title="Detail Activity">
<ContentPage.ToolbarItems>
<ToolbarItem Command="">
<ToolbarItem.IconImageSource>
<FontImageSource Glyph="" FontFamily="{StaticResource MaterialFontFamily}"/>
</ToolbarItem.IconImageSource>
</ToolbarItem>
<ToolbarItem Command="{Binding UpdateCommand}">
<ToolbarItem.IconImageSource>
<FontImageSource Size="30" Glyph="" FontFamily="{StaticResource MaterialFontFamily}"/>
</ToolbarItem.IconImageSource>
</ToolbarItem>
</ContentPage.ToolbarItems>
<ContentPage.Content>
<StackLayout Padding="20" Spacing="12">
<local:ActivityView />
</StackLayout>
</ContentPage.Content>
</ContentPage>
ActivityDetailPage.xaml.cs
namespace AthlosifyMobileApp.Views
{
[XamlCompilation(XamlCompilationOptions.Compile)]
public partial class ActivityDetailPage : ContentPage
{
public ActivityDetailPage(int activityId)
{
InitializeComponent ();
BindingContext = new ActivityDetailViewModel(Navigation, activityId);
}
}
}
答案 0 :(得分:0)
'我不确定,但是显然页面正在更改视图的绑定上下文。
在视图的OnBindingContextChanged重写方法内设置一个断点并进行调试。如果已确认,请从页面实例化视图模型。
干杯
答案 1 :(得分:0)
基于共享的代码,我认为您可能没有在活动详细信息页面上看到任何数据,因为您正在通过未等待的异步方法(FetchActivityData)来获取数据。顺便说一句,如果可能,应避免使用异步无效方法。无法捕获/处理从它们引发的异常。
由于您正在从viewmodel的构造函数调用,因此您似乎没有等待。实际发生的是构造函数立即返回,而FetchActivityDetail()和FetchCategories()继续在后台运行。显示页面,但是尚无数据,因此您看不到任何显示。然后,当FetchActivityDetail完成时,它将设置_activity,但这是一个字段,因此不会触发PropertyChanged事件,因此页面不知道需要更新。
以下是一些建议:
不要在构造函数中执行长时间运行的过程(如获取数据)。通常可以传入现有数据(例如您的activityid),尽管如果最终想要这样做,则使用依赖注入会更困难。
导航到需要获取数据的视图模型时,通常建议等到显示视图/ vm后再进行api调用。为此,我在视图模型中将所有视图都调用了OnAppearing方法。可以轻松地将其移至所有继承的BasePage和BaseViewModel中。然后,您可以执行诸如设置IsBusy状态(以触发微调框之类的UI)之类的操作,并填充数据。看起来可能像这样:
public override async Task OnAppearing()
{
await base.OnAppearing();
try
{
IsBusy = true;
await FetchActivityDetail();
await FetchCategories();
}
catch (Exception ex)
{
//handle/display error
}
finally
{
IsBusy = false;
}
}
另一个选择是使它成为导航之前被调用的方法,但这将需要首先创建viewmodel,这与您在此处使用的导航模式不同。有一些很好的例子,其中有viewmodel-first导航,但是这里我不再赘述。
答案 2 :(得分:0)
根据您的描述,您想在Xamarin.Forms中绑定自定义视图,建议您不要在自定义控件内部分配绑定,请使用以下方法:
<ContentView
x:Class="demo2.simplecontrol.View1"
xmlns="http://xamarin.com/schemas/2014/forms"
xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml">
<ContentView.Content>
<StackLayout>
<Entry x:Name="label1" />
<Entry x:Name="label2" />
</StackLayout>
</ContentView.Content>
public partial class View1 : ContentView
{
public View1 ()
{
InitializeComponent ();
}
public static readonly BindableProperty Label1Property= BindableProperty.Create(
nameof(Label1),
typeof(string),
typeof(View1),
"",
BindingMode.TwoWay,
propertyChanged: (bindable, oldValue, newValue) =>
{
if (newValue != null && bindable is View1 control)
{
var actualNewValue = (string)newValue;
control.label1.Text = actualNewValue;
}
});
public string Label1 { get; set; }
public static readonly BindableProperty Label2Property = BindableProperty.Create(
nameof(Label2),
typeof(string),
typeof(View1),
"",
BindingMode.TwoWay,
propertyChanged: (bindable, oldValue, newValue) =>
{
if (newValue != null && bindable is View1 control)
{
var actualNewValue = (string)newValue;
control.label2.Text = actualNewValue;
}
});
public string Label2 { get; set; }
}
然后您可以在ContentPage中使用此自定义视图。
<ContentPage
x:Class="demo2.simplecontrol.Page10"
xmlns="http://xamarin.com/schemas/2014/forms"
xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
xmlns:local="clr-namespace:demo2.simplecontrol">
<ContentPage.Content>
<StackLayout>
<Label
HorizontalOptions="CenterAndExpand"
Text="Welcome to Xamarin.Forms!"
VerticalOptions="CenterAndExpand" />
<local:View1 Label1="{Binding text1}" Label2="{Binding text2}" />
</StackLayout>
</ContentPage.Content>
public partial class Page10 : ContentPage, INotifyPropertyChanged
{
private string _text1;
public string text1
{
get { return _text1; }
set
{
_text1 = value;
RaisePropertyChanged("text1");
}
}
private string _text2;
public string text2
{
get { return _text2; }
set
{
_text2 = value;
RaisePropertyChanged("text2");
}
}
public Page10 ()
{
InitializeComponent ();
text1 = "test1";
text2 = "test2";
this.BindingContext = this;
}
public event PropertyChangedEventHandler PropertyChanged;
public void RaisePropertyChanged(string propertyName)
{
PropertyChangedEventHandler handler = PropertyChanged;
if (handler != null)
{
handler(this, new PropertyChangedEventArgs(propertyName));
}
}
}
最后,您从Web API获得CategoryList的数据,因此可以添加断点以检查是否有数据。