So my app has a main window displaying a list of recipes, and a second window (opened from main window) adding new recipe to the list (with information). Both windows have the same view model. After testing the new recipe is successfully added to the list, however the UI did not update new recipe. I also have a delete button in the main window for each recipe and it works normally. The deleted recipe is deleted in the view model and the UI. My code is quite long. I hope that it’s readable. Here is my code (there are lots of other UI so I just show only the relevant code) :
Main window view
<!--Button to open second window--> <Button Command="{Binding OpenAddRecipeWindowCommand}" Name="AddRecipeButton" Grid.Column="0" Margin="5" Padding="5" FontSize="15" Content="Add new recipe"/> <!--The list--> <ListView Name="RecipeList" Grid.Row="2" Margin="5" ItemsSource="{Binding RecipeModels}"> <!-- Set the style for item container to stretch to full width--> <ListView.ItemContainerStyle> <Style TargetType="ListViewItem"> <Setter Property="HorizontalContentAlignment" Value="Stretch"/> </Style> </ListView.ItemContainerStyle> <!-- Template of each item in the ListView --> <ListView.ItemTemplate> <DataTemplate> <Grid> <Grid.ColumnDefinitions> <ColumnDefinition></ColumnDefinition> <ColumnDefinition Width="Auto"></ColumnDefinition> </Grid.ColumnDefinitions> <TextBlock Name="RecipeName" Grid.Column="0" FontSize="15" Margin="5" VerticalAlignment="Center" Text="{Binding Name}"/> <Button Command="{Binding RelativeSource={RelativeSource FindAncestor, AncestorType={x:Type Window}}, Path=DataContext.DeleteRecipeCommand}" CommandParameter="{Binding}" Name="Delete" Grid.Column="1" FontSize="15" Margin="5" Padding="5" HorizontalAlignment="Right" Content="Delete"/> </Grid> </DataTemplate> </ListView.ItemTemplate> </ListView>
Second window view
<DockPanel Margin="5"> <!-- Recipe information --> <StackPanel DockPanel.Dock="Top"> <!-- Name, amount, price input--> <Grid> <Grid.ColumnDefinitions> <ColumnDefinition Width="Auto"></ColumnDefinition> <ColumnDefinition Width="2*"></ColumnDefinition> <ColumnDefinition Width="Auto"></ColumnDefinition> <ColumnDefinition></ColumnDefinition> <ColumnDefinition Width="Auto"></ColumnDefinition> <ColumnDefinition></ColumnDefinition> </Grid.ColumnDefinitions> <TextBlock Grid.Column="0" Margin="5 5 0 5" Padding="5" FontSize="15" VerticalAlignment="Center" Text="Tên"/> <TextBox Grid.Column="1" Margin="0 5 5 5" Padding="2" VerticalAlignment="Center" Text="{Binding NewRecipe.Name}" FontSize="13"/> <TextBlock Grid.Column="2" Margin="5 5 0 5" Padding="5" FontSize="15" VerticalAlignment="Center" Text="Số lượng"/> <TextBox Grid.Column="3" Margin="0 5 5 5" Padding="2" VerticalAlignment="Center" Text="{Binding NewRecipe.Amount}" FontSize="13" MaxLength="9" PreviewTextInput="NumberValidationTextBox" /> <TextBlock Grid.Column="4" Margin="5 5 0 5" Padding="5" FontSize="15" VerticalAlignment="Center" Text="Giá thành"/> <TextBox Grid.Column="5" Margin="0 5 5 5" Padding="2" VerticalAlignment="Center" Text="{Binding NewRecipe.Price}" FontSize="13" MaxLength="9" PreviewTextInput="NumberValidationTextBox" /> </Grid> <!-- Checkbox and add ingredient button --> <Grid> <Grid.RowDefinitions> <RowDefinition></RowDefinition> <RowDefinition></RowDefinition> </Grid.RowDefinitions> <CheckBox Grid.Row="0" Margin="10 5 10 5" VerticalContentAlignment="Center" VerticalAlignment="Center" FontSize="15" Content="Use as ingredient"/> <Button Grid.Row="1" Margin="10 5 10 5" Padding="5" FontSize="15" HorizontalAlignment="Left" Command="{Binding AddIngredientCommand}" Content="Add new ingredient"/> </Grid> </StackPanel> <!-- ListView and buttons --> <Grid> <Grid.RowDefinitions> <RowDefinition></RowDefinition> <RowDefinition Height="Auto"></RowDefinition> </Grid.RowDefinitions> <ListView Name="IngredientList" ItemsSource="{Binding Ingredients}" Grid.Row="0" Margin="5" > <ListView.ItemContainerStyle> <Style TargetType="ListViewItem"> <Setter Property="HorizontalContentAlignment" Value="Stretch"/> </Style> </ListView.ItemContainerStyle> <!-- ListViewItem template --> <ListView.ItemTemplate> <DataTemplate> <Grid> <Grid.ColumnDefinitions> <ColumnDefinition Width="Auto"></ColumnDefinition> <ColumnDefinition Width="2*"></ColumnDefinition> <ColumnDefinition Width="Auto"></ColumnDefinition> <ColumnDefinition></ColumnDefinition> <ColumnDefinition Width="Auto"></ColumnDefinition> <ColumnDefinition></ColumnDefinition> <ColumnDefinition Width="Auto"></ColumnDefinition> <ColumnDefinition></ColumnDefinition> <ColumnDefinition Width="Auto"></ColumnDefinition> </Grid.ColumnDefinitions> <TextBlock Grid.Column="0" Margin="5 5 0 5" Padding="5" FontSize="13" Text="Name"/> <TextBox Grid.Column="1" Margin="0 5 5 5" Padding="5" MaxLength="37" FontSize="13"/> <TextBlock Grid.Column="2" Margin="5 5 0 5" Padding="5" FontSize="13" Text="Amount"/> <TextBox Grid.Column="3" Margin="0 5 5 5" Padding="5" MaxLength="9" FontSize="13"/> <TextBlock Grid.Column="4" Margin="5 5 0 5" Padding="5" FontSize="13" Text="Unit"/> <TextBox Grid.Column="5" Margin="0 5 5 5" Padding="5" MaxLength="38" FontSize="13"/> <TextBlock Grid.Column="6" Margin="5 5 0 5" Padding="5" FontSize="13" Text="Price"/> <TextBox Grid.Column="7" Margin="0 5 5 5" Padding="5" MaxLength="9" FontSize="13"/> <Button Grid.Column="8" Name="DeleteItemButton" Margin="5" Padding="5" Command="{Binding RelativeSource={RelativeSource FindAncestor, AncestorType={x:Type Window}}, Path=DataContext.DeleteIngredientCommand}" CommandParameter="{Binding}" FontSize="13" Content="Delete"/> </Grid> </DataTemplate> </ListView.ItemTemplate> </ListView> <!-- Buttons --> <DockPanel Grid.Row="1"> <Button DockPanel.Dock="Right" Name="SaveButton" Margin="5" Padding="20 5 20 5" Command="{Binding SaveRecipeCommand}" FontSize="15" Content="Save"/> <Button DockPanel.Dock="Left" Name="CancelButton" Margin="5" Padding="20 5 20 5" HorizontalAlignment="Left" Command="{Binding CancelCommand}" FontSize="15" Content="Cancel"/> </DockPanel> </Grid> </DockPanel>
View Model
public class MainWindowViewModel : INotifyPropertyChanged { /// <summary> /// Implementation for Main window UI. /// </summary> #region Main Window View Model // List of recipes which is the ItemsSource for ListViewItem #region ObservableCollection<RecipeModel> recipeModels private ObservableCollection<RecipeModel> recipeModels; public ObservableCollection<RecipeModel> RecipeModels { get { return recipeModels; } set { if (recipeModels != value) { recipeModels = value; RaisePropertyChanged(nameof(recipeModels)); } } } #endregion #region Buttons // Add Recipe Window Button public RelayCommand OpenAddRecipeWindowCommand { get; set; } private void ExecuteOpenAddRecipeWindowCommand() { RecipeWindow newWindow = new RecipeWindow(); newWindow.ShowDialog(); } // Add Ingredient Window Button public RelayCommand OpenAddIngredientWindowCommand { get; set; } private void ExecuteOpenAddIngredientWindowCommand() { RecipeWindow newWindow = new RecipeWindow(); newWindow.ShowDialog(); } // Product Calculation Window Button public RelayCommand OpenProductCalculationWindowCommand { get; set; } private void ExecuteOpenProductCalculationWindowCommand() { RecipeWindow newWindow = new RecipeWindow(); newWindow.ShowDialog(); } // Product Calculation Window Button public RelayCommand OpenIngredientCalculationWindowCommand { get; set; } private void ExecuteOpenIngredientCalculationWindowCommand() { RecipeWindow newWindow = new RecipeWindow(); newWindow.ShowDialog(); } // Delete Recipe Command public RelayCommand<object> DeleteRecipeCommand { get; set; } private void ExecuteDeleteRecipeCommand(object recipe) { RecipeModels.Remove((RecipeModel)recipe); Save(); } #endregion #endregion /*------------------------------------------------------------------------------------------------------------------------------------*/ /*------------------------------------------------------------------------------------------------------------------------------------*/ /// <summary> /// Implementation for Recipe window UI /// </summary> #region Recipe Window View Model // List of ingredients which is ItemsSource for ListView #region ObservableCollection<RecipeModel> ingredients private ObservableCollection<IngredientModel> ingredients = new ObservableCollection<IngredientModel>(); public ObservableCollection<IngredientModel> Ingredients { get { return ingredients; } set { if (ingredients != value) { ingredients = value; RaisePropertyChanged(nameof(ingredients)); } } } #endregion // The recipe to be newly created, or edited. #region RecipeModel newRecipe private RecipeModel newRecipe = new RecipeModel(); public RecipeModel NewRecipe { get { return newRecipe; } set { if (newRecipe != value) { newRecipe = value; } RaisePropertyChanged(nameof(newRecipe)); } } #endregion #region Buttons // Cancel command public RelayCommand CancelCommand { get; set; } public Action CloseWindow { get; set; } public void ExecuteCancelCommand() { CloseWindow(); } // Add ingredient command public RelayCommand AddIngredientCommand { get; set; } public void ExecuteAddIngredientCommand() { Ingredients.Add(new IngredientModel()); } // Delete ingredient command public RelayCommand<object> DeleteIngredientCommand { get; set; } public void ExecuteDeleteIngredientCommand(object ingredient) { Ingredients.Remove((IngredientModel)ingredient); } /* THIS IS THE PART WHERE I TRY TO SAVE THE NEW RECIPE TO THE LIST BUT UI DOES NOT CHANGE */ // Save command public RelayCommand SaveRecipeCommand { get; set; } public void ExecuteSaveRecipeCommand() { NewRecipe.ingredients = Ingredients; RecipeModels.Add(NewRecipe); /* I TESTED WITH A MESSAGE BOX TO SHOW THAT THE NEW RECIPE IS ADDED */ string res = ""; foreach (var item in RecipeModels) { res += item.Name + " "; } MessageBox.Show(res); CloseWindow(); } #endregion #endregion /*------------------------------------------------------------------------------------------------------------------------------------*/ /*------------------------------------------------------------------------------------------------------------------------------------*/ /// <summary> /// Mutual Section /// </summary> #region Constructor public MainWindowViewModel(Action action) { // Load data from file Load(); CloseWindow = action; #region Main Window buttons OpenAddRecipeWindowCommand = new RelayCommand(ExecuteOpenAddRecipeWindowCommand); OpenAddIngredientWindowCommand = new RelayCommand(ExecuteOpenAddIngredientWindowCommand); OpenProductCalculationWindowCommand = new RelayCommand(ExecuteOpenProductCalculationWindowCommand); OpenIngredientCalculationWindowCommand = new RelayCommand(ExecuteOpenIngredientCalculationWindowCommand); DeleteRecipeCommand = new RelayCommand<object>(ExecuteDeleteRecipeCommand); #endregion #region Add Ingredient Window buttons CancelCommand = new RelayCommand(ExecuteCancelCommand); SaveRecipeCommand = new RelayCommand(ExecuteSaveRecipeCommand); AddIngredientCommand = new RelayCommand(ExecuteAddIngredientCommand); DeleteIngredientCommand = new RelayCommand<object>(ExecuteDeleteIngredientCommand); #endregion } #endregion #region Property Change Notification public event PropertyChangedEventHandler PropertyChanged; private void RaisePropertyChanged(string property) { PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(property)); } #endregion
EDIT: Sorry I forget to include the Code behind. So this is how I set the DataContext of the windows:
Main Window
public partial class MainWindow : Window { public MainWindow() { InitializeComponent(); DataContext = new MainWindowViewModel(null); } }
Second Window
public partial class RecipeWindow : Window { public RecipeWindow() { InitializeComponent(); this.DataContext = new MainWindowViewModel(this.Close); } }
Maybe I’ve created two instance of the view model to each window. From here how can I fix it?
Answer
Both windows have the same view model.
I doubt they do. I think they have their own instance of the same class.
You could try to set the DataContext
of the window when you open it:
private void ExecuteOpenAddRecipeWindowCommand() { RecipeWindow newWindow = new RecipeWindow(); newWindow.DataContext = this; newWindow.ShowDialog(); }
Then both windows should have the same DataContext
.
Also note that directly creating windows in a view model effectively breaks the MVVM design pattern.