Question

I have a WinRT app with a number of Users, Projects, Meetings, etc.

I have a main screen, with a main screen view model, which should display CurrentUser and has a ListView bound to CurrentUser.ProjectList.

I initialise CurrentUser in the ViewModel using a UserProvider class that gets all the required information from the database.

My problem then becomes very similar to this: Subscribe to INotifyPropertyChanged for nested (child) objects

I have a user and project model:

public class User
{
    public int id { get; set; }
    public string ForeName { get; set; }
    public string Surname { get; set; }
    ... etc ...
    public ObservableCollection<Project> ProjectList { get; set; }
    public ObservableCollection<User> FriendList { get; set; }

     ... constructor

}

public class Project 
{
    public String Name { get; set; }
    public int Id { get; set; }
    public List<User> Users { get; set; }
    public List<Meeting> Meetings { get; set; }

     .. constructor ...
 }

A view model with the following:

class HomeScreenViewModel : INotifyPropertyChanged {

private User _currentUser;
public User CurrentUser
 {
    get { return this._currentUser; }
    set
        {
          if (Equals(_currentUser, value)) return;

          this._currentUser = value;
          RaisePropertyChanged("CurrentUser");
        }
  }

    //[field: NonSerialized]
    public event PropertyChangedEventHandler PropertyChanged;

    protected void RaisePropertyChanged(string propertyName)
    {
        PropertyChangedEventHandler handler = PropertyChanged;
        if (handler != null)
        {
            handler(this, new PropertyChangedEventArgs(propertyName));
        }
    }

... I have a method in this view model that gets the current user

    public async Task<bool> GetLoggedInUserAsync()
    {
        int testId = 0;
        CurrentUser = await userProvider.GetCurrentUser(testId);

        UserProjects = await userProvider.GetUsersProject(CurrentUser);
        CurrentUser.ProjectList = UserProjects;

        return true;
    }

That is called in the view's loadState

    public MainPage()
    {
        this.InitializeComponent();
        addMeeting = new AddMeetingFlyout();

        _vm = new HomeScreenViewModel();
        this.DataContext = _vm;
    }

    protected override void LoadState(Object navigationParameter, Dictionary<String, Object> pageState)
    {

         await _vm.GetLoggedInUserAsync()
    }

And my bindings in the XAML, for ProjectList and ForeName, for example, are as follows:

        <CollectionViewSource
        x:Name="projectsViewSource"
        Source="{Binding CurrentUser.ProjectList}"/>

   ...
    <ListView
        x:Name="projectList"
        ItemsSource="{Binding Source={StaticResource projectsViewSource}}"
        Grid.Row="1"
        SelectionMode="None"
        Style="{StaticResource DraggableListView}" 
        ScrollViewer.VerticalScrollBarVisibility="Visible"
        IsItemClickEnabled="True" 
    >
        <ListView.ItemTemplate>
            <DataTemplate>
                <Button Style="{StaticResource ProjectTileButton}" Content="{Binding                Name}" Click="ProjectItem_Click"   />
            </DataTemplate>
        </ListView.ItemTemplate>
        <AddDeleteThemeTransition/>
    </ListView>

      ...
         <Button  ...>
            <TextBlock ...">
             <Run Text="{Binding CurrentUser.ForeName}" />
            </TextBlock>
        </Button>

The button content, CurrentUser.ForeName fires an INotifyPropertyChanged event when CurrentUser is first initialised in the viewmodel. This is reflected in the view - but any further changes to CurrentUser.ForeName do not fire any subsequent INotifyPropertyChanged events. The ProjectList is also not displayed in the view and does not fire an INotifyPropertyChanged event even though I know it is there.

I have spent many days looking at implementing INotifyPropertyChanged so that changes to nested child complex objects (such as CurrentUser.ProjectList) will propagate up to the view. At the minute, the only way this happens is if I force a call to

            this._currentUser = value;
            RaisePropertyChanged("CurrentUser");

which I am testing with a button that calls a method called MakeChange() in the viewmodel

    public void MakeChange()
    {
        User updatedCurrentUser = CurrentUser;
        CurrentUser = updatedCurrentUser;

    }    

This works, so I know for a fact all the data is coming correctly from the database and all is as it should be - one less thing to worry about!

However, I simply cannot get the view to display user projects on page load, or when new projects are added.

I tried implementing this solution: https://gist.github.com/thojaw/705450, however, the WinRT reflection capabilites have changed and I am not sure how to get the following liens to work within the context of my project, as this is beyond me:

     //from property
      //in _type.GetProperties(BindingFlags.Instance | BindingFlags.Public)
      //where _inotifyType.IsAssignableFrom(property.PropertyType)
      //select property;

Any help would be greatly appreciated - I honestly thought all I had to do was bind CurrentUser.ProjectList to a ListView.

Was it helpful?

Solution

As you are replacing the entire ObservableCollection itself, then you will also need to introduce another property changed event and backing field for the collection property.

There is a good example of this here

Licensed under: CC-BY-SA with attribution
Not affiliated with StackOverflow
scroll top