Search code examples
uwpuwp-xamlwindows-community-toolkit

Items not updating properly in UWP Community Toolkit's MasterDetailsView


I'm attempting to use the UWP Community Toolkit MasterDetailsView to provide an editing interface. However, I can't seem to get the individual items in it's listview to update their properties when I make a change.

Here's a portion of the XAML that I'm using to wire up the control:

        <controls:MasterDetailsView x:Name="masterDetailsView" Foreground="Black"
                                ItemsSource="{x:Bind libraryVM.Recipes, Mode=TwoWay}"
                                SelectedItem="{x:Bind libraryVM.SelectedRecipe, Mode=TwoWay}"
                                NoSelectionContent="Select an item to view">
        <controls:MasterDetailsView.ItemTemplate>
            <DataTemplate x:DataType="recipes:RecipeVM">
                <StackPanel Margin="0,8">
                    <TextBlock Style="{ThemeResource SubtitleTextBlockStyle}"
                               Text="{x:Bind Name}" />
                </StackPanel>
            </DataTemplate>
        </controls:MasterDetailsView.ItemTemplate>
...

LibraryVM

public class LibraryVM : INotifyPropertyChanged
{
    Library library;
    ObservableCollection<RecipeVM> _Recipes;
    public event PropertyChangedEventHandler PropertyChanged;

    public LibraryVM()
    {
        library = new Library();
        _Recipes = new ObservableCollection<RecipeVM>();
        foreach (var rec in library.Recipes)
        {
            var recipe = _Recipes.FirstOrDefault(r => r.Id == rec.Id);

            if (recipe == null)
            {
                var r = new RecipeVM(rec);
                _Recipes.Add(r);
            }
            else
            {
                recipe.Name = rec.Name;
                recipe.Description = rec.Description;
            }
        }
    }

    public ObservableCollection<RecipeVM> Recipes
    {
        get { return _Recipes; }
        set
        {
            _Recipes = value;
            OnPropertyChanged();
        }
    }

    RecipeVM _SelectedRecipe;


    public RecipeVM SelectedRecipe
    {
        get
        {
            return _SelectedRecipe;
        }

        set
        {
            _SelectedRecipe = value;
            OnPropertyChanged();
        }
    }

    public void SaveChanges()
    {
        if (_SelectedRecipe.Id == Guid.Empty)
        {
            library.CreateNew(_SelectedRecipe.Recipe);
        }
        else
        {
            library.SaveChanges(_SelectedRecipe.Recipe);
        }
    }

    void OnPropertyChanged([CallerMemberName]string propertyName = "")
    {
        if (PropertyChanged != null)
        {
            PropertyChanged(this, new PropertyChangedEventArgs(propertyName));
        }
    }

}

RecipeVM

public class RecipeVM : INotifyPropertyChanged
{
    Recipe _recipe;
    Recipe _backup;
    public event PropertyChangedEventHandler PropertyChanged;

    public Recipe Recipe
    {
        get
        {
            return _recipe;
        }
    }

    public RecipeVM(Recipe recipe = null)
    {
        _recipe = recipe;

        if(_recipe == null)
        {
            _recipe = new Recipe();
            _recipe.Name = "New Recipe";
        }

        this.IsEditing = false;
    }

    public Guid Id
    {
        get
        {
            return _recipe.Id;
        }

        set
        {
            _recipe.Id = value;
            OnPropertyChanged();
        }
    }

    bool _IsEditing;


    public bool IsEditing
    {
        get
        {
            return _IsEditing;
        }

        set
        {
            _IsEditing = value;
            OnPropertyChanged();
        }
    }

    public string Name
    {
        get
        {
            return _recipe.Name;
        }

        set
        {
            _recipe.Name = value;
            OnPropertyChanged();
        }
    }

    public string Description
    {
        get
        {
            return _recipe.Description;
        }

        set
        {
            _recipe.Description = value;
            OnPropertyChanged();
        }
    }

    public void MakeBackup()
    {
        _backup = new Recipe()
        {
            Id = _recipe.Id,
            Name = _recipe.Name,
            Description = _recipe.Description
        };
    }

    public void RestoreBackup()
    {
        Name = _backup.Name;
        Description = _backup.Description;
    }

    void OnPropertyChanged([CallerMemberName]string propertyName = "")
    {
        if(PropertyChanged != null)
        {
            PropertyChanged(this, new PropertyChangedEventArgs(propertyName));
        }
    }
}

However, when I update any RecipeVM item, I can't seem to get it's corresponding item in the MasterDetailsView's ListView to update. I believe that I've implemented INotifyPropertyChanged everywhere appropriate, but I'm obviously missing something.

Update I've verified that binding is working everywhere else. If I step through the code after a change is made, the RecipeVM and underlying Recipe are updated appropriately.


Solution

  • Kudos to Chirag Shah, who got me pointed in roughly the right direction.

    Changing {x:Bind Name} to {Binding Name} worked, but not because there is anything wrong with x:Bind, but rather my understanding of it.

    What I was missing is that x:Bind defaults to a much more conservative binding mode.

    Concerning it's mode property, the documentation says:

    Specifies the binding mode, as one of these strings: "OneTime", "OneWay", or "TwoWay". The default is "OneTime". Note that this differs from the default for {Binding}, which is "OneWay" in most cases.

    Updating my XAML to explicitly declare the mode solved the problem.

    <TextBlock Style="{ThemeResource SubtitleTextBlockStyle}"
        Text="{x:Bind Name, Mode=OneWay}" />
    

    Another good reference is the answer to this question: Difference between Binding and x:Bind