簡體   English   中英

當 MVVM 中的 ItemsSource 更改時 UI 不會更新

[英]UI not update when ItemsSource change in MVVM

所以我的應用程序有一個主 window 顯示食譜列表,第二個 window (從主窗口打開)將新食譜添加到列表中(帶有信息)。 兩者 windows 具有相同的視圖 model。 測試后新配方成功添加到列表中,但是 UI 沒有更新新配方。 我在每個配方的主 window 中也有一個刪除按鈕,它工作正常。 刪除的配方在視圖 model 和 UI 中被刪除。 我的代碼很長。 我希望它是可讀的。 這是我的代碼(還有很多其他 UI,所以我只顯示相關代碼):

window主視圖

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

二 window 查看

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

查看 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

編輯:對不起,我忘記在后面包含代碼。 所以這就是我設置 windows 的 DataContext 的方式:

主營Window

public partial class MainWindow : Window
    {
        public MainWindow()
        {
            InitializeComponent();
            DataContext = new MainWindowViewModel(null);
        }
    }

第二 Window

public partial class RecipeWindow : Window
    {
        public RecipeWindow()
        {
            InitializeComponent();
            this.DataContext = new MainWindowViewModel(this.Close);
        }
    }

也許我已經為每個 window 創建了視圖 model 的兩個實例。 從這里我該如何解決?

兩者 windows 具有相同的視圖 model。

我懷疑他們這樣做。 我認為他們有自己的相同 class 實例。

您可以嘗試在打開 window 時設置它的DataContext

private void ExecuteOpenAddRecipeWindowCommand()
{
    RecipeWindow newWindow = new RecipeWindow();
    newWindow.DataContext = this;
    newWindow.ShowDialog();
}

那么 windows 應該有相同的DataContext

另請注意,在視圖 model 中直接創建 windows 有效地破壞了 MVVM 設計模式。

暫無
暫無

聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.

 
粵ICP備18138465號  © 2020-2024 STACKOOM.COM