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.
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