When using a barcode scanner, the Entry.Completed event is fired with previous content
- Dominant language
- C#
- Stars
- 23.3k
- Forks
- 2k
- Avg merge
- 1d 15h
- Merged PRs (30d)
- 290
Description
### Description
When using a barcode scanner the Entry.Completed event is fired before the control is populated with the scanned barcode, and the Text value is the value, before the barcode scan happend and only after the event the Entry control is populated with the correct scanned value, but no Completed event is thrown again, since it already was triggered before.
The Entry.ReturnCommand is also affected with this behavior.
The TextChanged Event is also triggered after the Completed Event.
This happens with barcode scanners acting as keyboard devices and emitting key strokes in a very fast manner. To reproduce some keyboardboard simulator code ist included.
This bug seems to be the same as #10663, which was closed due to missing information and inactivity
### Steps to Reproduce
1. dotnet new maui -n maui-scanned-entry
2. in MainPage.xaml
```xml
```
3. in MainPage.xaml.cs
```csharp
using System.Diagnostics;
namespace maui_scanned_entry;
public partial class MainPage : ContentPage {
int count = 0;
public MainPage() {
InitializeComponent();
}
private void Entry_Completed(object? sender, EventArgs e) {
Entry entry = (Entry) sender!;
Trace.WriteLine($"Completed: '{entry.Text}'");
}
private void Entry_TextChanged(object sender, TextChangedEventArgs e) {
Entry entry = (Entry) sender!;
Trace.WriteLine($"changed: '{entry.Text}'");
#if WINDOWS
if (e.NewTextValue == "a") {
Platforms.Windows.KeyboardSimulator.SendBarcodeWithEnter();
}
#endif
}
}
```
4. Add Platzforms/Windows/KeyboardSimulator.cs
```csharp
using System.Runtime.InteropServices;
namespace maui_scanned_entry.Platforms.Windows;
internal static class KeyboardSimulator {
[DllImport("user32.dll")]
private static extern uint SendInput(uint nInputs, INPUT[] pInputs, int cbSize);
[DllImport("user32.dll")]
private static extern ushort VkKeyScanW(char ch);
private const int INPUT_KEYBOARD = 1;
private const uint KEYEVENTF_KEYDOWN = 0x0000;
private const uint KEYEVENTF_KEYUP = 0x0002;
private const uint KEYEVENTF_UNICODE = 0x0004;
[StructLayout(LayoutKind.Sequential)]
private struct INPUT {
public int type;
public InputUnion u;
}
[StructLayout(LayoutKind.Explicit)]
private struct InputUnion {
[FieldOffset(0)]
public MOUSEINPUT mi;
[FieldOffset(0)]
public KEYBDINPUT ki;
[FieldOffset(0)]
public HARDWAREINPUT hi;
}
[StructLayout(LayoutKind.Sequential)]
private struct KEYBDINPUT {
public ushort wVk;
public ushort wScan;
public uint dwFlags;
public uint time;
public IntPtr dwExtraInfo;
}
[StructLayout(LayoutKind.Sequential)]
private struct MOUSEINPUT {
public int dx;
public int dy;
public uint mouseData;
public uint dwFlags;
public uint time;
public IntPtr dwExtraInfo;
}
[StructLayout(LayoutKind.Sequential)]
private struct HARDWAREINPUT {
public uint uMsg;
public ushort wParamL;
public ushort wParamH;
}
///
/// Simuliert Tastatureingaben für den angegebenen Text
///
private static void SendText(string text) {
foreach (char c in text) {
SendChar(c);
}
}
///
/// Simuliert eine einzelne Tastatureingabe
///
private static void SendChar(char c) {
ushort vkAndShift = VkKeyScanW(c);
ushort vk = (ushort) (vkAndShift & 0xFF);
bool needsShift = (vkAndShift & 0x100) != 0;
List inputs = new List();
// Shift drücken wenn nötig
if (needsShift) {
inputs.Add(CreateKeyboardInput(0x10, KEYEVENTF_KEYDOWN)); // VK_SHIFT
}
// Taste drücken und loslassen
inputs.Add(CreateKeyboardInput(vk, KEYEVENTF_KEYDOWN));
inputs.Add(CreateKeyboardInput(vk, KEYEVENTF_KEYUP));
// Shift loslassen wenn nötig
if (needsShift) {
inputs.Add(CreateKeyboardInput(0x10, KEYEVENTF_KEYUP)); // VK_SHIFT
}
SendInput((uint) inputs.Count, inputs.ToArray(), Marshal.SizeOf(typeof(INPUT)));
}
///
/// Simuliert einen Enter-Tastendruck
///
private static void SendEnter() {
INPUT[] inputs = new INPUT[2];
inputs[0] = CreateKeyboardInput(0x0D, KEYEVENTF_KEYDOWN); // VK_RETURN
inputs[1] = CreateKeyboardInput(0x0D, KEYEVENTF_KEYUP);
SendInput((uint) inputs.Length, inputs, Marshal.SizeOf(typeof(INPUT)));
}
///
/// Erstellt eine INPUT-Struktur für Keyboard-Events
///
private static INPUT CreateKeyboardInput(ushort virtualKey, uint flags) {
return new INPUT {
type = INPUT_KEYBOARD,
u = new InputUnion {
ki = new KEYBDINPUT {
wVk = virtualKey,
wScan = 0,
dwFlags = flags,
time = 0,
dwExtraInfo = IntPtr.Zero
}
}
};
}
///
/// Simuliert "BARCODE" + Enter
///
public static void SendBarcodeWithEnter() {
SendText("BARCODE");
Thread.Sleep(10);
SendEnter();
}
}
```
5. Run Program (with Debugger for Trace output)
Use Barcodescanner acting as keyboard wedge to scan a barcode or press "a" to trigger simulated barcode scanning
### Expected Trace output
```
changed: 'a'
changed: 'aBARCODE'
Completed: 'aBARCODE'
```
### Actual Trace output
Trace output (using simulated barcode scanning) should be:
```
changed: 'a'
Completed: 'a'
changed: 'aBARCODE'
```
### Link to public reproduction project repository
https://github.com/kilianluetkemeyer-boop/maui-scanned-entry
### Version with bug
9.0.60 SR6
### Is this a regression from previous behavior?
Not sure, did not test other versions
### Last version that worked well
Unknown/Other
### Affected platforms
Windows, I was *not* able test on other platforms
### Affected platform versions
net9.0-windows10.0.19041.0
### Did you find any workaround?
_No response_
### Relevant log output
```shell
```
Contributor guide
Research direction
Start with the linked reproduction project and run it on Windows using the MainPage.xaml Entry handlers and Platforms/Windows/KeyboardSimulator.cs. Reproduce the barcode or simulated key sequence, then trace the Windows Entry.Completed, ReturnCommand, and TextChanged ordering. Done means the completed and return actions observe the scanned text after the control is populated, with regression coverage for the reported sequence.
Written by the indexing model from the issue text.
Assessment
- Tech stack
- csharp
- Domain
- desktop, frontend
- Issue type
- Bug
- Difficulty
- 4/5
- Estimated time
- 3-5 days
- Activity status
- Stale
- Clarity
- Clearly specified
- Newbie friendliness
- 38/100