Skip to content

Only 1st row is editable on datagrid for some reason #190

@nouredeen06

Description

@nouredeen06

I'm making a simple invoicing app and I'm using a datagrid for the items, for some reason only the 1st row is editable and the rest arent

20251025-0855-14.3629671.mp4

I tried quite literally everything I can think of but couldnt figure out the problem,
here is my XAML

<DataGrid Height="225" ItemsSource="{Binding InvoiceDetails}"
                  IsReadOnly="{Binding IsInputReadOnly}" GridLinesVisibility="All"
                  BorderThickness="1"
                  BorderBrush="Gray" FlowDirection="RightToLeft" CanUserSortColumns="False"
                  CanUserResizeColumns="True" CanUserReorderColumns="False" Name="dataGrid1">
            <DataGrid.Columns>
                <DataGridTextColumn Header="Barcode"
                                    Binding="{Binding ItemBarcode, UpdateSourceTrigger=LostFocus}"
                                    Width="110" />
                <DataGridTextColumn Header="البيان"
                                    Binding="{Binding ItemName, UpdateSourceTrigger=LostFocus}"
                                    Width="215" />
                <DataGridTextColumn Header="العدد"
                                    Binding="{Binding Qty, StringFormat='F1', UpdateSourceTrigger=LostFocus}" />
                <DataGridTextColumn Header="سعر البيع"
                                    Binding="{Binding SalePrice, StringFormat='F3', UpdateSourceTrigger=LostFocus}" />
                <DataGridTextColumn Header="المجموع" Binding="{Binding Amount, StringFormat='F3'}" />
                <DataGridTemplateColumn Header="الضريبة%" IsReadOnly="True">
                    <DataGridTemplateColumn.CellTemplate>
                        <DataTemplate>
                            <TextBlock Text="{Binding TaxPerc}"
                                       HorizontalAlignment="Center"
                                       VerticalAlignment="Center" />
                        </DataTemplate>
                    </DataGridTemplateColumn.CellTemplate>
                </DataGridTemplateColumn>

                <DataGridTextColumn Header="الضريبة"
                                    Binding="{Binding TaxValue, StringFormat='F3'}"
                                    IsReadOnly="True" />

            </DataGrid.Columns>
        </DataGrid>

and here is the viewmodel code that interacts with the datagrid

using System;
using System.Collections.Generic;
using System.Collections.ObjectModel;
using System.Threading.Tasks;
using System.Windows.Input;
using Microsoft.Data.SqlClient;
using CommunityToolkit.Mvvm.ComponentModel;
using CommunityToolkit.Mvvm.Input;
using MsBox.Avalonia;
using MsBox.Avalonia.Dto;
using MsBox.Avalonia.Enums;
using Avalonia.Layout;
using Avalonia.Controls;
using ArqaamTestApp.Views;
using Avalonia.Interactivity;
using Avalonia.Xaml.Interactivity;
using ReactiveUI;
using System.Reactive;
using System.Reactive.Linq;
using ArqaamTestApp.Services;
using Microsoft.Extensions.DependencyInjection;


namespace ArqaamTestApp.ViewModels;

public partial class MainWindowViewModel : ViewModelBase
{
    public ICommand OpenWindowCommand { get; }
    public Interaction<CustomersWindowViewModel, Unit> ShowDialog { get; }

    [ObservableProperty] private string _invoiceNo;
    [ObservableProperty] private string _selectedDate;
    [ObservableProperty] private string _customerID;
    [ObservableProperty] private string _customerName;
    [ObservableProperty] private string _salesTaxNo;
    [ObservableProperty] private int _paymentType;
    [ObservableProperty] private int _taxType;
    [ObservableProperty] private string _subTotal;
    [ObservableProperty] private string _discount;
    [ObservableProperty] private string _netTotal;
    [ObservableProperty] private string _taxTotal;
    [ObservableProperty] private string _amount;
    [ObservableProperty] private string _remarks;
    [ObservableProperty] private string _invoiceUUID;

    private List<InvoiceHeader> _allInvoiceHeaders = new List<InvoiceHeader>();
    public ObservableCollection<Item> InvoiceDetails { get; } = new ObservableCollection<Item>();

    private string connectionString =
        @"connection string";


    [ObservableProperty]
    [NotifyCanExecuteChangedFor(nameof(AddRecordCommand))]
    [NotifyCanExecuteChangedFor(nameof(SaveRecordCommand))]
    private bool _adding; // 0 View, 1 Add

    [ObservableProperty] private bool _isInputReadOnly = true;
    [ObservableProperty] private bool _isAutoVisible = true;

    private readonly IWindowManager _windowManager;
    private readonly IServiceProvider _serviceProvider;
    private bool processing;

    public ICommand OpenCustomerWindowCommand { get; }

    public MainWindowViewModel() // for design preview
    {
        LoadAllInvoices();
        DisplayCurrentInvoice();
        Console.WriteLine("its running smh");
    }

    public MainWindowViewModel(IWindowManager windowManager, IServiceProvider serviceProvider)
    {
        LoadAllInvoices();
        DisplayCurrentInvoice();

        _windowManager = windowManager;
        _serviceProvider = serviceProvider;
    }


    private void LoadInvoiceDetails(int CurrentInvoiceID)
    {
        try
        {
            {
/* loading from sql logic */
            }

            InvoiceDetails.Add(item);
        }
        catch (Exception ex)
        {
            Console.WriteLine(ex);
            throw;
        }
    }

    private void DisplayCurrentInvoice()
    {
        if (CurrentIndex < 0 || CurrentIndex >= _allInvoiceHeaders.Count)
        {
            InvoiceNo = "";
            SelectedDate = (DateTime.Today).ToString("dd/MM/yyyy");
            CustomerID = "";
            CustomerName = "";
            SalesTaxNo = "";
            PaymentType = 0;
            TaxType = 0;
            SubTotal = "0.000";
            Discount = "0.000";
            NetTotal = "0.000";
            TaxTotal = "0.000";
            Amount = "0.000";
            Remarks = "";
            InvoiceUUID = "";
            InvoiceDetails.Clear();
            return;
        }

        var currentHeader = _allInvoiceHeaders[CurrentIndex];
        InvoiceNo = currentHeader.InvoiceNo.ToString();
        SelectedDate = currentHeader.InvoiceDate;
        CustomerID = currentHeader.CustomerID.ToString();
        CustomerName = currentHeader.CustomerName;
        SalesTaxNo = currentHeader.SalesTaxNo;
        PaymentType = currentHeader.PaymentType;
        TaxType = currentHeader.TaxType;
        SubTotal = currentHeader.SubTotal.ToString("F3");
        Discount = currentHeader.Discount.ToString("F3");
        NetTotal = currentHeader.NetTotal.ToString("F3");
        TaxTotal = currentHeader.TaxTotal.ToString("F3");
        Amount = currentHeader.Amount.ToString("F3");
        Remarks = currentHeader.Remarks;
        InvoiceUUID = currentHeader.InvoiceUUID;
        LoadInvoiceDetails(currentHeader.InvoiceID);
    }

    [RelayCommand(CanExecute = nameof(CanAdd))]
    private void AddRecord()
    {
        if (CanAdd())
        {
            CurrentIndex = -1;
            DisplayCurrentInvoice();
            Adding = true;
            IsAutoVisible = false;
            InvoiceDetails.Add(new Item(-1, -1, "", "", 0, 0, 0, 0, 0));
            InvoiceDetails.Add(new Item(-1, -1, "", "", 0, 0, 0, 0, 0));
            InvoiceDetails.Add(new Item(-1, -1, "", "", 0, 0, 0, 0, 0));
            IsInputReadOnly = false;
        }
    }

    public void AddNewDetailsRow()
    {
        if (Adding)
        {
            InvoiceDetails.Add(new Item(-1, -1, "", "", 0, 0, 0, 0, 0));
        }
    }

    [RelayCommand(CanExecute = nameof(CanSave))]
    private void SaveRecord()
    {
        {
            /* saving to sql logic */
        }
        LoadAllInvoices();
        Adding = false;
        IsAutoVisible = true;
        CurrentIndex = _allInvoiceHeaders.Count - 1;
    }
}

[RelayCommand(CanExecute = nameof(CanCancel))]
private async Task CancelAction()
{
    if (CanCancel())
    {
        var messageBox = MessageBoxManager.GetMessageBoxStandard(
            "Confirm Cancel",
            "Are you sure you want to cancel this operation?",
            ButtonEnum.OkCancel,
            Icon.Question
        );

        var result = await messageBox.ShowWindowDialogAsync(App.MainWindow);

        if (result == ButtonResult.Ok)
        {
            Adding = false;
            IsAutoVisible = true;
            CurrentIndex = _allInvoiceHeaders.Count - 1;
            DisplayCurrentInvoice();
            IsInputReadOnly = true;
        }
    }
}

private bool CanAdd() => !Adding;

public class Product
{
    public int ProductID { get; set; }
    public string Barcode { get; set; }
    public string ProductName { get; set; }
    public double MinSalePrice { get; set; }
    public double SalePrice { get; set; }
    public int TaxPerc { get; set; }

    public Product(int productID, string barcode, string productName, double minSalePrice,
        double salePrice, int taxPerc)
    {
        ProductID = productID;
        Barcode = barcode;
        ProductName = productName;
        MinSalePrice = minSalePrice;
        SalePrice = salePrice;
        TaxPerc = taxPerc;
    }
}

public partial class Item : ObservableObject
{
    private string connectionString =
        @"connectionstring";

    [ObservableProperty] private int _invoiceID;
    [ObservableProperty] private int _itemID;
    [ObservableProperty] private string _itemBarcode; // editable
    [ObservableProperty] private string _itemName; // editable
    [ObservableProperty] private double _qty; // editable
    [ObservableProperty] private double _salePrice; // editable
    [ObservableProperty] private double _amount;
    [ObservableProperty] private int _taxPerc;
    [ObservableProperty] private double _taxValue;
    public bool processing = false;


    partial void OnItemBarcodeChanged(string value) => itemPropertyUpdate("itemBarcode", value);
    partial void OnItemNameChanged(string value) => itemPropertyUpdate("itemName", value);
    partial void OnQtyChanged(double value) => itemPropertyUpdate("qty", value);
    partial void OnSalePriceChanged(double value) => itemPropertyUpdate("salePrice", value);

    public void itemPropertyUpdate(string propertyName, object newValue)
    {
        if (!processing)
        {
            switch (propertyName)
            {
                case "itemBarcode":
                {
                    fetchProductInfo("itemBarcode", (string)newValue);
                    break;
                }
                case "itemName":
                {
                    fetchProductInfo("itemName", (string)newValue);
                    break;
                }
                case "qty":
                {
                    recalculateItemValues();
                    break;
                }
                case "salePrice":
                {
                    recalculateItemValues();
                    break;
                }
            }
        }
    }

    private void recalculateItemValues()
    {
        processing = true;
        Amount = SalePrice * Qty;
        TaxValue = Amount * ((double)TaxPerc / 100);
        processing = false;
    }

    public void fetchProductInfo(string changedProperty, string newValue)
    {
        processing = true;
        List<Product> allProducts = new List<Product>();
        using (SqlConnection connection = new SqlConnection(connectionString))
        {
            connection.Open();
            string sqlQuery = string.Empty;
            if (changedProperty == "itemBarcode")
            {
                sqlQuery = @"SELECT p.product_id, p.barcode,p.product_name,
	                                p.min_sale_price,p.sale_price,t.tax_perc 
                             FROM Products p, Taxes t
                             WHERE p.sales_tax_id = t.sales_tax_id 
                             AND barcode LIKE @itemBarcode + '%'";
            }
            else
            {
                sqlQuery = @"SELECT p.product_id, p.barcode,p.product_name,
	                                p.min_sale_price,p.sale_price,t.tax_perc 
                             FROM Products p, Taxes t
                             WHERE p.sales_tax_id = t.sales_tax_id 
                             AND product_name LIKE '%' + @itemName + '%'";
            }

            using (SqlCommand command = new SqlCommand(sqlQuery, connection))
            {
                command.Parameters.AddWithValue(changedProperty, newValue);
                using (SqlDataReader reader = command.ExecuteReader())
                {
                    while (reader.Read())
                    {
                        Product product = new Product
                        (
                            (int)reader["product_id"],
                            (string)reader["barcode"],
                            (string)reader["product_name"],
                            Convert.ToDouble((decimal)reader["min_sale_price"]),
                            Convert.ToDouble((decimal)reader["sale_price"]),
                            (int)reader["tax_perc"]
                        );
                        allProducts.Add(product);
                    }

                    if (allProducts.Count == 1)
                    {
                        ItemID = allProducts[0].ProductID;
                        ItemBarcode = allProducts[0].Barcode;
                        ItemName = allProducts[0].ProductName;
                        Qty = 1;
                        SalePrice = allProducts[0].SalePrice;
                        Amount = Qty * SalePrice;
                        TaxPerc = allProducts[0].TaxPerc;
                        TaxValue = Amount * TaxPerc / 100;
                    }
                }
            }
        }

        processing = false;
    }


    public Item(int invoiceID, int itemID, string itemBarcode, string itemName, double qty,
        double salePrice, double amount, int taxPerc, double taxValue)
    {
        InvoiceID = invoiceID;
        ItemID = itemID;
        ItemBarcode = itemBarcode;
        ItemName = itemName;
        Qty = qty;
        SalePrice = salePrice;
        Amount = amount;
        TaxPerc = taxPerc;
        TaxValue = taxValue; // 
    }
}

and here is the code-behind that interacts with the datagrid

using System;
using System.Collections.Generic;
using Microsoft.Data.SqlClient;
using System.Collections.ObjectModel;
using ArqaamTestApp.ViewModels;
using Avalonia.Controls;
using Avalonia.Input;
using System.Linq;
using Avalonia;
using Avalonia.Interactivity;
using Avalonia.Threading;
using Avalonia.Input.Navigation;
using Avalonia.VisualTree;
using Avalonia.Input;

namespace ArqaamTestApp.Views;

public partial class MainWindow : Window
{
    private string? _textInputToInject;

    public MainWindow()
    {
        InitializeComponent();

        this.Loaded += MainWindow_Loaded;
    }


    private void MainWindow_Loaded(object sender, RoutedEventArgs e)
    {
        this.AddHandler(KeyDownEvent, WindowKeyDown, RoutingStrategies.Tunnel);
        var dataGrid = this.FindControl<DataGrid>("dataGrid1");
        var MaskedtextBox = this.FindControl<MaskedTextBox>("txtDate");
        if (dataGrid != null)
        {
            dataGrid.AddHandler(KeyDownEvent, DataGridKeyDown, RoutingStrategies.Tunnel);
            dataGrid.GotFocus += DataGridOnGotFocus;
            dataGrid.TextInput += DataGridTextInput;
            dataGrid.PreparingCellForEdit += DataGrid_PreparingCellForEdit;
        }
    }


    private void DataGridOnGotFocus(object? sender, GotFocusEventArgs e)
    {
        if (sender is DataGrid dataGrid)
        {
            dataGrid.SelectedIndex = 0;
        }
    }


    private void DataGridTextInput(object? sender, TextInputEventArgs e)
    {
        var grid = sender as DataGrid;
        if (grid != null && DataContext is MainWindowViewModel viewModel && viewModel.Adding &&
            grid.SelectedItem != null)
        {
            string inputText = e.Text;

            if (grid.BeginEdit())
            {
                Dispatcher.UIThread.Post(() =>
                {
                    var focusedElement = FocusManager.GetFocusedElement();
                    if (focusedElement is TextBox editor)
                    {
                        editor.Text = inputText;
                        editor.CaretIndex = editor.Text.Length;
                    }
                }, DispatcherPriority.Input);
            }

            e.Handled = true;
        }
    }


    private void DataGridKeyDown(object sender, KeyEventArgs e)
    {
        if (DataContext is MainWindowViewModel viewModel && e.Source is not TextBox textBox)
        {
            var grid = sender as DataGrid;
            switch (e.Key)
            {
                case Key.Tab:
                {
                    if (grid.CurrentColumn != grid.Columns[grid.Columns.Count - 1])
                    {
                        grid.CurrentColumn = grid.Columns[grid.CurrentColumn.DisplayIndex + 1];
                    }
                    else
                    {
                        dataGrid1.RaiseEvent(new KeyEventArgs
                            { RoutedEvent = KeyDownEvent, Key = Key.Enter });
                        grid.CurrentColumn = grid.Columns[0];
                    }

                    grid.Focus();
                    e.Handled = true;
                    break;
                }
                case Key.Enter:
                {
                    Console.WriteLine("enter pressed, in grid");
                    int itemCount = viewModel.InvoiceDetails.Count;
                    if (grid != null && grid.SelectedIndex == itemCount - 1 &&
                        viewModel.InvoiceDetails.Last().ItemBarcode != string.Empty)
                    {
                        viewModel.AddNewDetailsRow();
                        Dispatcher.UIThread.Post(() =>
                            {
                                grid.SelectedIndex = itemCount;
                                grid.ScrollIntoView(viewModel.InvoiceDetails[viewModel.InvoiceDetails.Count - 1],
                                    grid.Columns[0]);
                                grid.IsReadOnly = true;
                                grid.IsReadOnly = false;
                            }, DispatcherPriority.Background
                        );
                    }

                    Console.WriteLine(grid.SelectedIndex + "after keydown");
                    e.Handled = true;
                    break;
                }
                case Key.Down:
                {
                    int itemCount = viewModel.InvoiceDetails.Count;
                    if (grid != null && grid.SelectedIndex == itemCount - 1 &&
                        viewModel.InvoiceDetails.Last().ItemBarcode != string.Empty)
                    {
                        viewModel.AddNewDetailsRow();
                        Dispatcher.UIThread.InvokeAsync(() =>
                            {
                                grid.CurrentColumn = grid.Columns[0];
                                grid.SelectedIndex = itemCount;
                                grid.ScrollIntoView(viewModel.InvoiceDetails.Last(),
                                    grid.Columns[0]);
                                grid.Focus();
                            }, DispatcherPriority.Background
                        );
                    }

                    break;
                }
                case Key.Up:
                {
                    if (viewModel.InvoiceDetails.Last().ItemBarcode == string.Empty &&
                        viewModel.InvoiceDetails.Count > 1)
                    {
                        viewModel.InvoiceDetails.Remove(viewModel.InvoiceDetails.Last());
                        Dispatcher.UIThread.Post(() =>
                            {
                                grid.SelectedItem = viewModel.InvoiceDetails.Last();
                                grid.ScrollIntoView(viewModel.InvoiceDetails.Last(),
                                    grid.Columns[0]);
                            }, DispatcherPriority.Background
                        );
                    }

                    break;
                }
                case Key.Left:
                {
                    if (grid.Columns.IndexOf(grid.CurrentColumn) < grid.Columns.Count - 1)
                    {
                        int newColumnIndex = grid.Columns.IndexOf(grid.CurrentColumn) + 1;
                        grid.CurrentColumn = grid.Columns[newColumnIndex];
                        e.Handled = true;
                    }

                    break;
                }
                case Key.Right:
                {
                    if (grid.Columns.IndexOf(grid.CurrentColumn) > 0)
                    {
                        int newColumnIndex = grid.Columns.IndexOf(grid.CurrentColumn) - 1;
                        grid.CurrentColumn = grid.Columns[newColumnIndex];
                        e.Handled = true;
                    }

                    break;
                }
                case Key.Delete:
                {
                    viewModel.InvoiceDetails.Remove(viewModel.InvoiceDetails[grid.SelectedIndex]);
                    if (viewModel.InvoiceDetails.Count == 0)
                    {
                        viewModel.AddNewDetailsRow();
                        Dispatcher.UIThread.Post(() =>
                            {
                                grid.SelectedItem = viewModel.InvoiceDetails.Last();
                                grid.ScrollIntoView(viewModel.InvoiceDetails.Last(),
                                    grid.Columns[0]);
                            }, DispatcherPriority.Background
                        );
                    }

                    break;
                }
            }
        }
    }
}

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions