-
Notifications
You must be signed in to change notification settings - Fork 23
Open
Description
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
Labels
No labels