'Inputted Items sometimes appear empty in Data grid

I have ItemsControl and a DataGrid in a WPF UserControl. this is how it looks like

when the "Add to card" button is pressed a ViewModel instance is added to ObservableCollection bound to the DataGrid.

 <ItemsControl
            ItemsSource="{Binding Meals}"
            x:Name="MealList"                       
            Margin="5">
            <ItemsControl.ItemTemplate>
                <DataTemplate>
                    <components:MealCardCustomer
                        BorderBrush="OrangeRed"
                        BorderThickness="5px"/>
                </DataTemplate>
            </ItemsControl.ItemTemplate>
        </ItemsControl>
    </ScrollViewer>
    <ScrollViewer
        HorizontalScrollBarVisibility="Auto"
        VerticalScrollBarVisibility="Disabled">
        <DataGrid
            HorizontalAlignment="Stretch"
            IsReadOnly="True"            
            Background="Orange"
            x:Name="OrderedMeals"
            SelectionMode="Single"
            ItemsSource="{Binding OrderedMeals, UpdateSourceTrigger=PropertyChanged, Mode=OneWay}"    
            SelectedIndex="{Binding SelectedOrderedMeal, UpdateSourceTrigger=PropertyChanged, Mode=OneWayToSource}"
            FontSize="26"
            Grid.Column="0"
            Grid.Row="0"
            Margin="5"
            AutoGenerateColumns="False">
            <DataGrid.Columns>
                <DataGridTextColumn Binding="{Binding Name, Mode=OneWay}" Header="Name" />
                <DataGridTextColumn Binding= "{Binding Price, Mode=OneWay}" Header="Price" />
                <DataGridTextColumn Binding="{Binding Ingredients, Mode=OneWay}" Header="Ingredients" />
            </DataGrid.Columns>
    </DataGrid>
    </ScrollViewer>

The problem is that sometimes when I add new items it appears like an empty column.

I tried to add a button which refreshes the data grid but when pressed it makes the all of the items blank.

Also I've wrapped the DataGrid in a ScrollViewer with a horizontal scroll which for some reason doesn't work.

That's the ViewModel of the View

private string? address;
    public string? Address
    {
        get { return address; }
        set { address = value; OnPropertyChaneg(nameof(Address)); }
    }

    private int selectedOrderedMeal = -1;
    public int SelectedOrderedMeal
    {
        get { return selectedOrderedMeal; }
        set { selectedOrderedMeal = value; OnPropertyChaneg(nameof(SelectedOrderedMeal)); }
    }

    private ObservableCollection<MealCardCustomerViewModel> meals;
    public ObservableCollection<MealCardCustomerViewModel> Meals
    {
        get { return meals; }
        set { meals = value; }
    }

    private ObservableCollection<MealCardCustomerViewModel> orderedMeals;
    public ObservableCollection<MealCardCustomerViewModel> OrderedMeals
    {
        get { return orderedMeals; }
        set { orderedMeals = value; OnPropertyChaneg(nameof(OrderedMeals)); }
    }

    public BaseCommand RemoveCommand { get; }
    public BaseCommand FinishOrderCommand { get; }
    public NavigateCommand NavigateToCustomerListOfOtders { get; }
    public BaseCommand LoadMealsCommand { get; }

    public CustomerOrderingViewModel(NavigationService customerListOfOrdersNavigationService, NavigationService helpNavigationService, IMealService mealService)
        : base(helpNavigationService, mealService)
    {
        Meals = new ObservableCollection<MealCardCustomerViewModel>();
        OrderedMeals = new ObservableCollection<MealCardCustomerViewModel>();

        RemoveCommand = new RemoveMeal(this);
        FinishOrderCommand = new FinishOrder(this, customerListOfOrdersNavigationService);
        NavigateToCustomerListOfOtders = new NavigateCommand(customerListOfOrdersNavigationService);

        LoadMealsCommand = new LoadMeals<CustomerOrderingViewModel>(this);
    }

    public static CustomerOrderingViewModel LoadViewModel(NavigationService customerListOfOrders, NavigationService helpNavigationService, IMealService mealService)
    {
        CustomerOrderingViewModel viewModel = new CustomerOrderingViewModel(customerListOfOrders, helpNavigationService, mealService);
        viewModel.LoadMealsCommand.Execute(null);

        return viewModel;
    }

    public override void LoadMealsList(List<Meal> meals)
    {
        Meals.Clear();
        foreach (var meal in meals)
        {
            Meals.Add(new MealCardCustomerViewModel(meal,this));
        }
    }

That the Views which act like ItemTemplates for the ItemsControl

 <Image            
        Source="{Binding MealImage, Converter ={StaticResource imageConverter}, Mode=TwoWay, TargetNullValue=DefaultImage}"
        Stretch="Uniform"/>

    <DockPanel
        Grid.Row="1"
        VerticalAlignment="Center"
        Margin="5">
        <TextBlock
            FontSize="20"
            Margin="5"
            Text="Name :"/>
        <TextBox
            Text="{Binding Name,Mode=TwoWay, UpdateSourceTrigger=PropertyChanged}"
            FontSize="20"
            Margin="5"/>
    </DockPanel>

    <DockPanel
        Grid.Row="2"
        VerticalAlignment="Center"
        Margin="5">
        <TextBlock
            FontSize="20"
            Margin="5"
            Text="Price :"/>
        <TextBox
            Text="{Binding Price, Mode=TwoWay, UpdateSourceTrigger=PropertyChanged, StringFormat={}{0:f2}}"
            FontSize="20"
            Margin="5"/>
    </DockPanel>

    <DockPanel
        Grid.Row="3"
        VerticalAlignment="Center"
        Margin="5">
        <TextBlock
            FontSize="20"
            Margin="5"
            Text="Ingredients:"/>
        <TextBox
            Text="{Binding Ingredients, Mode=TwoWay, UpdateSourceTrigger=PropertyChanged}"
            FontSize="20"
            Margin="5" 
            TextWrapping="Wrap"
            VerticalScrollBarVisibility="Visible"
            HorizontalScrollBarVisibility="Visible"
            HorizontalAlignment="Stretch"
            VerticalAlignment="Stretch"
            />
    </DockPanel>
    <Button
        Command="{Binding AddToCardCommand}"
        Background="OrangeRed"
        Grid.Row="4"
        Margin="10 5 10 5"
        Content="Add to cart"
        FontSize="20"/>

and that's the command that adds the item to the ObservableCollection

        private CustomerOrderingViewModel customerOrderingViewModel;
    private MealCardCustomerViewModel mealCardCustomerViewModel;

    public AddToCard(CustomerOrderingViewModel customerOrderingViewModel, MealCardCustomerViewModel mealCardCustomerViewModel)
    {
        this.customerOrderingViewModel = customerOrderingViewModel;
        this.mealCardCustomerViewModel = mealCardCustomerViewModel;
    }

    public override void Execute(object? parameter)
    {
        customerOrderingViewModel.OrderedMeals.Add(mealCardCustomerViewModel);
    }


Solution 1:[1]

The problem was with the images in the objects which are non existing right now and so they are null.

For some reason the null value cause infinite loop in the converter and so the view models could not load the properties of the entity but the collection could read that the count was changed thus displaying the empty rows.

Solution 2:[2]

The way you add items to the cart is not thread safe.

Immagine the AddToCart() being called wich will update your customerOrderingViewModel and mealCardCustomerViewModel. Then immagine that before Execute is called, some other thread changes customerOrderingViewModel or mealCardCustomerViewModel. This could result in Execute() adding the wrong (or a Null) meal to your order. If that is the reason for your error, the following code shoud solve it:

public AddToCard(CustomerOrderingViewModel customerOrderingViewModel, MealCardCustomerViewModel mealCardCustomerViewModel)
{
    customerOrderingViewModel.OrderedMeals.Add(mealCardCustomerViewModel);
    this.customerOrderingViewModel = customerOrderingViewModel;
    this.mealCardCustomerViewModel = mealCardCustomerViewModel;
}

If you dont need customerOrderingViewModel and mealCardCustomerViewModel in the class owning AddToCart(), you could even spare those variables completely.

Side note: If you dont plan on changing the observable collections but only their content, you can simply declare them as public fiels and not as propertys. The setter of the propertys wil only be accessed when thwo whole ObservableCollection object is changed but not if its content is changed. PropertyChanged notifications for changes inside the ObservableCollection are handlled by the ObservableCollection implementation.

Sources

This article follows the attribution requirements of Stack Overflow and is licensed under CC BY-SA 3.0.

Source: Stack Overflow

Solution Source
Solution 1 Yordan Mandevski
Solution 2 Felix