I'm trying to setup a listview that, after a call to the Azure GraphAPI will load a list of users. Everything up to the binding of the data works. The call to Graph works, the returned data works, all without issue.
I have a listview setup in a view:
<ListView ItemsSource="{Binding NoPicUsers}" >
<ListView.View>
<GridView>
<GridViewColumn
Header="Display Name"
Width="200">
<GridViewColumn.CellTemplate>
<DataTemplate>
<TextBlock Text="{Binding NoPicUsers.DisplayName}"></TextBlock>
</DataTemplate>
</GridViewColumn.CellTemplate>
</GridViewColumn>
</GridView>
</ListView.View>
</ListView>
The corresponding ViewModel has the following code:
public class ContentPanelHomeViewModel : ViewModelBase
{
private List<UserModel> _noPicUsers;//NotifyTask<List<UserModel>> _noPicUsers;
public List<UserModel> NoPicUsers
{
get
{
return _noPicUsers;
}
set
{
_noPicUsers = value;
//OnPropertyChanged Method is in ViewModelBase
OnPropertyChanged("NoPicUsers");
}
}
public ContentPanelHomeViewModel()
{
//NoPicUsers = new NotifyTask<List<UserModel>>.Create(GetNoPicUsersAsync());
//NoPicUsers = NotifyTask.Create(GetNoPicUsersAsync);
var users = GetNoPicUsersAsync();
}
private async Task<List<UserModel>> GetNoPicUsersAsync()
{
GraphServiceClient graphClient = await GraphAPIServices.SignInAndInitGraph(AzureAppInfo.UserReadAll);
List<UserModel> users = new List<UserModel>();
var listOfUsers = await graphClient.Users
.Request()
.Filter("accountEnabled eq true")
.Select("displayname, id")
.GetAsync();
foreach (var user in listOfUsers)
{
UserModel azureUser = new UserModel();
azureUser.DisplayName = user.DisplayName;
azureUser.ObjectID = user.Id;
users.Add(azureUser);
}
NoPicUsers = users;
return users;
}
}
My listview does not display any data and I cannot figure out why. There are no reported errors and no binding errors reported when the WPF window loads so I'm not sure what would be the issue?
EDIT: I've updated my code. I'm implementing the NugetPackage Nito.Mvvm. I get the same problem of no data binding to my list view if I set my NoPicUsers inside of the GetNoPicUsersAsync() the WPF window yells saying NoPicUsers property no found on object of Type UserModel.
So it appears that it thinks my ViewModel as a UserModel property instead of a List property. What would cause that?
In stepping through the code the task never seems to complete. The Task sits at Status = WaitingForActivation, Method = "{Null}", Result = "{Not yet computed}".
CodePudding user response:
This is a threading issue where the UI is unaware of changes being made because the Task
is being invoked on a separate thread since it is not awaited.
I suggest using an async event raised by the view model
public class ContentPanelHomeViewModel : ViewModelBase {
private List<UserModel> _noPicUsers;//NotifyTask<List<UserModel>> _noPicUsers;
public List<UserModel> NoPicUsers {
get {
return _noPicUsers;
}
set {
_noPicUsers = value;
//OnPropertyChanged Method is in ViewModelBase
OnPropertyChanged("NoPicUsers");
}
}
public ContentPanelHomeViewModel() {
starting = onStarting;
starting(this, EventArgs.Empty);
}
private event EventHandler starting = delegate { };
private async void onStarting(object sender, EventArgs args) {
starting -= onStarting; //optional
// the following runs on background thread
List<UserModel> users = await GetNoPicUsersAsync();
// returned to the UI thread
NoPicUsers = users; //notifies UI
}
private async Task<List<UserModel>> GetNoPicUsersAsync() {
GraphServiceClient graphClient = await GraphAPIServices.SignInAndInitGraph(AzureAppInfo.UserReadAll);
List<UserModel> users = new List<UserModel>();
var listOfUsers = await graphClient.Users
.Request()
.Filter("accountEnabled eq true")
.Select("displayname, id")
.GetAsync();
foreach (var user in listOfUsers) {
UserModel azureUser = new UserModel();
azureUser.DisplayName = user.DisplayName;
azureUser.ObjectID = user.Id;
users.Add(azureUser);
}
return users;
}
}
the OnPropertyChanged
would have triggered the UI to update if it was invoked on the same thread as the UI. Because the view model trying to update the property on a background thread the UI was not getting the notification so was unaware that it needed to update.
By doing the heavy lifting on a background thread the UI is kept responsive while the work is being done. When the task returns to the UI thread then the property can be updated so the UI can receive the event trigger to update itself.
There is also an issue with the binding declared in the UI. The cell template needs to bind to the property of the item, not the top level property
<ListView ItemsSource="{Binding NoPicUsers}" >
<ListView.View>
<GridView>
<GridViewColumn
Header="Display Name"
Width="200">
<GridViewColumn.CellTemplate>
<DataTemplate>
<TextBlock Text="{Binding DisplayName}"></TextBlock>
</DataTemplate>
</GridViewColumn.CellTemplate>
</GridViewColumn>
</GridView>
</ListView.View>
</ListView>