WPF发票应用:如何在UserControl动态添加TextBox并实现自动聚焦
Hey there! Let's work through the dynamic TextBox row addition and auto-focus functionality for your WPF invoice app. Based on what you described, here's a practical, step-by-step solution:
We'll use an ItemsControl bound to an ObservableCollection to manage dynamic rows—this makes adding/removing rows seamless since the UI automatically updates when the collection changes. Each row will be a DataTemplate containing your two TextBoxes, and we'll handle events to trigger adding a new row and focusing the next TextBox once the current row is filled.
First, create a simple class to represent each invoice line. This implements INotifyPropertyChanged so the UI updates when data changes, and includes logic to fetch the item name from your database when the code is entered:
public class InvoiceLine : INotifyPropertyChanged { private string _itemCode; public string ItemCode { get => _itemCode; set { _itemCode = value; OnPropertyChanged(nameof(ItemCode)); // Fetch item name from database once code is entered if (!string.IsNullOrEmpty(value)) { ItemName = FetchItemNameFromDatabase(value); // Replace with your actual DB call } } } private string _itemName; public string ItemName { get => _itemName; set { _itemName = value; OnPropertyChanged(nameof(ItemName)); } } public event PropertyChangedEventHandler PropertyChanged; protected void OnPropertyChanged(string propertyName) { PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName)); } // Replace this with your real database query logic private string FetchItemNameFromDatabase(string itemCode) { // Example: Query your DB and return the matching item name return "Sample Product Name"; } }
Modify your UserControl to use an ItemsControl for dynamic rows. The DataTemplate defines the layout of each row's TextBoxes:
<UserControl x:Class="YourAppNamespace.InvoiceRowControl" xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation" xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" xmlns:local="clr-namespace:YourAppNamespace"> <Grid Margin="10"> <ItemsControl x:Name="InvoiceLinesControl" ItemsSource="{Binding InvoiceLines}"> <ItemsControl.ItemTemplate> <DataTemplate> <StackPanel Orientation="Horizontal" Margin="0,5,0,0"> <TextBox x:Name="ItemCodeInput" Width="180" Text="{Binding ItemCode, UpdateSourceTrigger=PropertyChanged}" Margin="0,0,15,0" LostFocus="ItemCodeInput_LostFocus"/> <TextBox x:Name="ItemNameDisplay" Width="280" Text="{Binding ItemName}" IsReadOnly="True"/> <!-- Make this read-only if users shouldn't edit it --> </StackPanel> </DataTemplate> </ItemsControl.ItemTemplate> </ItemsControl> </Grid> </UserControl>
In the code-behind, we'll initialize the collection, handle the LostFocus event to add new rows, and auto-focus the next TextBox:
public partial class InvoiceRowControl : UserControl { public ObservableCollection<InvoiceLine> InvoiceLines { get; set; } public InvoiceRowControl() { InitializeComponent(); InvoiceLines = new ObservableCollection<InvoiceLine>(); // Add the first row by default InvoiceLines.Add(new InvoiceLine()); DataContext = this; } private void ItemCodeInput_LostFocus(object sender, RoutedEventArgs e) { var currentTextBox = sender as TextBox; var currentLine = currentTextBox.DataContext as InvoiceLine; // Check if the current row is fully filled (adjust this condition to your needs) if (!string.IsNullOrWhiteSpace(currentLine.ItemCode) && !string.IsNullOrWhiteSpace(currentLine.ItemName)) { int currentIndex = InvoiceLines.IndexOf(currentLine); // Only add a new row if we're on the last row if (currentIndex == InvoiceLines.Count - 1) { InvoiceLines.Add(new InvoiceLine()); // Wait for the UI to render the new row, then focus its ItemCode TextBox Dispatcher.BeginInvoke(new Action(() => { var newRowContainer = InvoiceLinesControl.ItemContainerGenerator.ContainerFromIndex(InvoiceLines.Count - 1) as FrameworkElement; var newCodeTextBox = newRowContainer.FindName("ItemCodeInput") as TextBox; newCodeTextBox.Focus(); })); } } } }
- Condition for Adding Rows: The example checks if both
ItemCodeandItemNameare filled. You can tweak this—for example, trigger adding a new row as soon asItemCodeis entered andItemNameis fetched. - Dispatcher Usage: We use
Dispatcher.BeginInvoketo wait for the UI to finish rendering the new row before trying to focus the TextBox. Skipping this might result in a null reference since the new control hasn't been created yet. - ReadOnly TextBox: If users shouldn't edit the
ItemNameTextBox, setIsReadOnly="True"like in the XAML example.
内容的提问来源于stack exchange,提问作者lavanyaabu

