C# WinForm + Barcode Scanner input, TextChanged Error - c#

UPDATE: SOLUTION AT END
I have a Winform, label1 will display some info returned from a SQL Search using the input (MemberID) received from barcode scanner via txtBoxCatchScanner.
Scenario is people swiping their MemberID Cards under the scanner as they pass through reception and the Winform automatically doing a Search on that MemberID and returning their info including for example "Expired Membership" etc on the receptionist's PC which has the winForm in a corner of her desktop.
I have the Below Code working fine on first swipe (eg. first person)
The number MemberID, for example 00888 comes up in the text box, ADO.NET pulls the data from SQL and displays it fine.
one thing to note maybe, the cursor is at the end of the memberID: 00888|
All good so far, THEN:
when swipe 2 (eg. next person) happens
their number (say, 00999) gets put onto the end of the first in the txtBox eg: 0088800999 so naturally when TextChanged Fires it searches for 0088800999 instead of 00999 ....
I've tried:
txtBoxCatchScanner.Clear();
and
txtBoxCatchScanner.Text = "";
and
reloading the form
at the end of my code to "refresh" the text box
but i guess they trigger the TextChanged Event
How can i refocus or ... clear the old number and cursor back to start of txtBox after the previous swipe has done its stuff...
I'm a beginner so I'm sure the code below is pretty crap....
But if anyone has time, please let me know how fix it to do what i want.
UPDATE:
Ok after much experimenting I''ve managed to get this 1/2 working now hopefully someone more experience can help me to completion! :P
if (txtBoxCatchScanner.Text.Length == 5)
{
label1.Text = txtBoxCatchScanner.Text; // just a label for testing .. shows the memmber ID
txtBoxCatchScanner.Select(0, 5);
}
SO scan 1, say 00888 , then that gets highlighted, scan 2 , say 00997 ... sweet! overwrites (not appends to) 00888 and does it's thing ... scan 2 0011289 ... DOH!!
Problem: not all barcodes are 5 digits!! they are random lengths!! Memeber ID range from 2 digit (eg. 25) to 10 digits, and would grow in the future...
Edit: Something I've discovered that is that the barcodes are read as indvidual key presses. I think this is why answer 1 below does not work and while the big probmlems:
for example with 00675 the input (?output) from the scanner is:
Down: Do
Up: Do
Down: Do
Up: Do
Down: D6
Up: D6
Down: D7
Up: D7
Down: D5
Up: D5
down: Retunn
Up: Return
other info: barcode scanner is: an Opticon OPL6845 USB
Thanks
private void txtBoxCatchScanner_TextChanged(object sender, EventArgs e)
{
Member member = new Member();
member.FirstName = "";
member.LastName = "";
//Get BarCode
//VALIDATE: Is a Number
double numTest = 0;
if (Double.TryParse(txtBoxCatchScanner.Text, out numTest))
{
//IS A NUMBER
member.MemberID = Convert.ToInt32(txtBoxCatchScanner.Text);
//SEARCH
//Search Member by MemberID (barcode)
List<Member> searchMembers = Search.SearchForMember(member);
if (searchMembers.Count == 0)
{
lblAlert.Text = "No Member Found";
}
else
{
foreach (Member mem in searchMembers)
{
lblMemberStatus.Text = mem.MemberStatus;
lblMemberName.Text = mem.FirstName + " " + mem.LastName;
lblMemberID.Text = mem.MemberID.ToString();
lblMessages.Text = mem.Notes;
if (mem.MemberStatus == "OVERDUE") // OR .. OR .. OR ...
{
lblAlert.Visible = true;
lblAlert.Text = "!! OVERDUE !!";
//PLAY SIREN aLERT SOUND
//C:\\WORKTEMP\\siren.wav
SoundPlayer simpleSound =
new SoundPlayer(#"C:\\WORKTEMP\\siren.wav");
simpleSound.Play();
}
else
{
lblAlert.Visible = true;
lblAlert.Text = mem.MemberStatus;
}
}
}
}
else
{
//IS NOT A NUMBER
lblAlert.Text = "INVALID - NOT A NUMBER";
////
//lblMemberName.Text = "";
//lblMemberID.Text = "";
//lblMemberID.Text = "";
}
SOLUTION:
The System won't let me answer my own question for another 3 hours, as I'm a newbie only 1 post, so will put here:
First thanks everyone for your help and Patience.
I Have finally figured a solition, not fully tested yet as its 2am and bed time.
following along from my updates where I had success but hit the variable length of MemberID problem. I've now overcome that with the Code below:
namespace SCAN_TESTING
{
public partial class Form1 : Form
{
public Form1()
{
InitializeComponent();
}
private void txtBoxCatchScanner_KeyUp(object sender, KeyEventArgs e)
{
if (e.KeyValue == (char)Keys.Return)
{
e.Handled = true;
int barcodeLength = txtBoxCatchScanner.TextLength;
txtBoxCatchScanner.Select(0, barcodeLength);
//TEST
label3.Text = barcodeLength.ToString();
//TEST
label2.Text = txtBoxCatchScanner.Text;
}
}
I'll add this to my previous "real" code and test in the morning
But at this stage is doing exactly what I want! =]
Update: Tested it .. works exactly what needed:
private void txtBoxCatchScanner_KeyUp(object sender, KeyEventArgs e)
{
if (e.KeyValue == (char)Keys.Return)
{
e.Handled = true;
int barcodeLength = txtBoxCatchScanner.TextLength;
txtBoxCatchScanner.Select(0, barcodeLength);
//
//INSERT ORGINAL CODE HERE. No Changes were needed.
//
}//end of if e.KeyValue ...
}//end txtBoxCatchScanner_KeyUp
Hope that helps anyone in the future!! :)
Thanks again for the 2 very good solutions, I can see how they work, and learnt alot.
Just didn't work in my case - more likely due to myself or my error/not understanding, or scanner type.

I´m not exactly sure what the actual problem is.
txtBoxCatchScanner.Clear();
txtBoxCatchScanner.Text = "";
both trigger the "Changed" Event.
But they also clear the box. So that should be what you want to do.
You could check at the beginning if the box is actually empty, and return in case it is. Like:
if(txtBoxCatchScanner.Text == "" |txtBoxCatchScanner.Text == string.Empty)
return;
So nothing else happens, if the box is empty.
If I misunderstood your problem, please specify and I will try to help.
Regards
EDIT:
Your function should work if it looked something like this:
private void txtBoxCatchScanner_TextChanged(object sender, EventArgs e)
{
Member member = new Member();
member.FirstName = "";
member.LastName = "";
if(txtBoxCatchScanner.Text == "" | txtBoxCatchScanner.Text == string.Empty)
return; // Leave function if the box is empty
//Get BarCode
//VALIDATE: Is a Number
int numTest = 0;
if (int.TryParse(txtBoxCatchScanner.Text, out numTest))
{
//IS A NUMBER
//member.MemberID = Convert.ToInt32(txtBoxCatchScanner.Text);
member.MemberID = numTest; // you already converted to a number...
//SEARCH
//Search Member by MemberID (barcode)
List<Member> searchMembers = Search.SearchForMember(member);
if (searchMembers.Count == 0)
{
lblAlert.Text = "No Member Found";
}
else
{
foreach (Member mem in searchMembers)
{
lblMemberStatus.Text = mem.MemberStatus;
lblMemberName.Text = mem.FirstName + " " + mem.LastName;
lblMemberID.Text = mem.MemberID.ToString();
lblMessages.Text = mem.Notes;
if (mem.MemberStatus == "OVERDUE") // OR .. OR .. OR ...
{
lblAlert.Visible = true;
lblAlert.Text = "!! OVERDUE !!";
//PLAY SIREN aLERT SOUND
//C:\\WORKTEMP\\siren.wav
SoundPlayer simpleSound =
new SoundPlayer(#"C:\\WORKTEMP\\siren.wav");
simpleSound.Play();
}
else
{
lblAlert.Visible = true;
lblAlert.Text = mem.MemberStatus;
}
}
}
}
else
{
//IS NOT A NUMBER
lblAlert.Text = "INVALID - NOT A NUMBER";
////
//lblMemberName.Text = "";
//lblMemberID.Text = "";
//lblMemberID.Text = "";
}
txtBoxCatchScanner.Clear();
}

The barcode scanner you use seems to function as a HID - a keyboard emulation. Every simple barcode scanner I know (and I'm working with them on a daily basis) has the option of specifying a suffix for the scanned barcode. Change the suffix to CRLF and add a default button to your form. Scanning a barcode that ends with CRLF will then automatically "push the button".
Move the code that performs the checks from TextChanged event in to the event handler for the buttons Click event and remove the TextChanged event handler. Then, when the button is clicked, also clear the text box and set the focus back to the text box.
You should be good to go, now.
You can easily check whether the barcode scanner already has the correct suffix configured: Open up Notepad and scan some barcodes. If they all appear on separate lines, then everything's fine. Otherwise you'll need to scan some configuration barcodes from the scanner's manual.
To sum it all up, this should be the code for the button's Click event:
private void btnCheckMember_Click(object sender, EventArgs e)
{
Member member = new Member();
member.FirstName = "";
member.LastName = "";
string memberText = txtBoxCatchScanner.Text.Trim();
txtBoxCatchScanner.Text = String.Empty;
int numTest = 0;
if (String.IsNullOrEmpty(memberText) ||!Int32.TryParse(memberText, out numTest))
{
//IS NOT A NUMBER
lblAlert.Text = "INVALID - NOT A NUMBER";
return;
}
member.MemberID = numTest;
List<Member> searchMembers = Search.SearchForMember(member);
if (searchMembers.Count == 0)
{
lblAlert.Text = "No Member Found";
}
else
{
foreach (Member mem in searchMembers)
{
lblMemberStatus.Text = mem.MemberStatus;
lblMemberName.Text = mem.FirstName + " " + mem.LastName;
lblMemberID.Text = mem.MemberID.ToString();
lblMessages.Text = mem.Notes;
if (mem.MemberStatus == "OVERDUE") // OR .. OR .. OR ...
{
lblAlert.Visible = true;
lblAlert.Text = "!! OVERDUE !!";
SoundPlayer simpleSound = new SoundPlayer(#"C:\\WORKTEMP\\siren.wav");
simpleSound.Play();
}
else
{
lblAlert.Visible = true;
lblAlert.Text = mem.MemberStatus;
}
}
}
This solution avoids the following problems:
The event being triggered upon every character added/removed from the content of the text box (which is also the case when scanning a barcode: They are added one by one as if they were entered on a keyboard)
Resulting from 1. the problem that a member check is performed upon every entered character
Resulting from 2. the problem that member XYZ will never be found if there is a member XY in the database, as the check stops after finding XY
Resulting from 3. the problem that member XY will also not be found, but only member Z, because in 3. the text box is cleared and Z is the only character being entered.

The best way to clear the textBox on the next textChange event.
Insert this line
txtBoxCatchScanner.SelectAll();
at the end of TextChange function.. This will select the text, so that i can be replaced easily on the next event.

Related

I messed up a "While" loop... Can i fix it?

I tryed to make a while loop, and i know the problem, i just have no idea how to fix it. The int I use just never updates, making the code useless... I use Visual Studio, windows fom app, if that changes something... Sorry for the lenght, but i don't know where's the problem. (Input 1 and 2 are textboxes...) The text file I use looks like this:
Username: new line
(costum text) new line
Password: new line
(costum text) new line
Username: new line
...
Here's the code:
public partial class Form1 : Form
{
//This is part of the problem
int search = 0;
//This is part of the problem (end)
public void OK_Click(object sender, EventArgs e)
{
string path = #"filePath.txt";
var count = File.ReadLines(path).Count();
string user = File.ReadLines(path).Skip(search + 1).Take(1).First();
string pass = File.ReadLines(path).Skip(search + 3).Take(1).First();
//Main problem
if (Input1.Text != "" && Input2.Text != "")
{
while (Input1.Text == user && Input2.Text == pass)
{
if (search < count)
{
search = search + 4;
}
}
if (search < count)
{
MessageBox.Show("worked");
search = 0;
}
}
//Main problem (end)
}
}
This can be greatly simplified. The usernames and passwords are on alternating lines, so they need to be declared inside of the loop. You can also use a for loop to control skipping to the next username/password combination at the end of each iteration of the loop. And you don't need to do File.ReadLines multiple times, that causes it to hit the disk multiple times for something you could just hold in memory once.
You should also rename your textboxes to have names that represent the data they should contain. So UsernameTextbox instead of Input1 for example.
public void OK_Click(object sender, EventArgs e)
{
string path = #"filePath.txt";
var fileLines = File.ReadLines(path);
var authenticatedSuccessfully = false;
for (int line = 0; line < fileLines.Length - 1; line += 2)
{
var user = fileLines[line];
var password = fileLines[line + 1];
if (UsernameTextbox.Text == user && PasswordTextBox.Text == pass)
{
authenticatedSuccessfully = true;
break;
}
}
if (authenticatedSuccessfully)
{
MessageBox.Show("You are logged in!");
}
else
{
MessageBox.Show("Incorrect username or password!");
}
}
Of course...you should keep in mind that this is not secure at all. In the real world, passwords should be one way hashed and salted. Then when a user wants to authenticate you hash the password again and compare it to the stored value. If they match, then they provided the correct password. Storing passwords in plaintext is not a good idea.
Your while condition is wrong. You can use for loop for that(using counters), or you need to rebuild your while loop. You must put if conditions inside While loop dont make them separate. https://www.w3schools.com/cs/cs_while_loop.asp. Using while loop is good chance to go for infinite. So I am using For loops all the times.

async function - first start first end - C# WPF

i have a problem , i have a textbox controle, i activated the keyup function, so when the user write an ID in the system , the programe start searching until the it found the user.
im using it with this example code below
private async void PNationalNo_KeyUp(object sender, KeyEventArgs e)
{
string Ptext = Textbox.text;
string ResposeDataP = "";
await Task.Run(() =>
{
ResposeDataP = RunAsyncGetOnePatient(Ptext, GV.AccountID).Result;
});
Patient1 = JsonConvert.DeserializeObject<PatientM>(ResposeDataP);
if (Patient1 != null)
{
WrongMsg1.Text = "user '" + Patient1 .PatientFullName.ToLower() + "' have this ID number!";
}
else
{
WrongMsg1.Text = "there is no user have this ID!!";
}
}
the problem with this code , sometimes when the code find the result in the last async tanks , 1 of the tasks take more time than the finale one , so the programe print the result of that late function !! not the final one ?!! (( there is no user have this ID!!)), becouse its not the last function in the async code , so how can i solve this problem ?!!
Updated from comments
i mean if the user ID = "123" in the database, when i press 1 this is the first function.
Then when i press 2, the textbox = "12"
When i press 3 textbox = "123"
So it should print in the WrongMsg1.Text that the user is found, but the problem is sometimes the function of textbox = "12" finish after the function of "123", so it print in the"there is no user have this id!!".
did u understand me? because async function number 2 , finished after the last function.
It seems to be a typical race condition. Since Task.Run executes in a background thread you no longer have control over the order of execution which means that the input of the second character is evaluated after the third character. So you have to find a way to make sure the order of execution is still correct when you are back in control.
A possible solution might be to check whether the text in the text box is still the same after the async Task finished:
private async void PNationalNo_KeyUp(object sender, KeyEventArgs e)
{
string Ptext = Textbox.text;
string ResposeDataP = "";
await Task.Run(() =>
{
ResposeDataP = RunAsyncGetOnePatient(Ptext, GV.AccountID).Result;
});
if (Textbox.text != Ptext)
{
return;
}
Patient1 = JsonConvert.DeserializeObject<PatientM>(ResposeDataP);
if (Patient1 != null)
{
WrongMsg1.Text = "user '" + Patient1 .PatientFullName.ToLower() + "' have this ID number!";
}
else
{
WrongMsg1.Text = "there is no user have this ID!!";
}
}

How to remove the zero value?

private void serialPort_DataReceived(object sender, System.IO.Ports.SerialDataReceivedEventArgs e)
{
if (Clos_flag) return;
try
{
Listening = true;
if (serialPort.IsOpen)
{
this.txt_weight.Invoke(new MethodInvoker(delegate
{
//txt_weight.Text = serialPort.ReadLine();
serialPort.NewLine = "\r";
string weight = serialPort.ReadLine();
if (weight.IndexOf(")") > 0)
{
weight = weight.Substring(3, 8);
// txt_weight.Text = weight.Substring(0, weight.LastIndexOf("0") + 1);
}
}));
}
}
catch (Exception eg)
{
MessageBox.Show(eg.ToString());
}
finally
{
Listening = false;
}
}
What should i change to remove the first zero in the picture, above is the code i use. I tried to change the substring to (2,8) but still it is not working.
I get the value from a weighing machine, once the user click the "Open Serial Port" button
Change this line:
string weight = serialPort.ReadLine();
to this:
string weight = serialPort.ReadLine().TrimStart('0').Trim();
What that will do is readline, trim the first 0, then trim the outside (start amd end) blank spaces before loading the value into the declared variable
Seems like this could be caused by many factors.
Looking at the code, the leading zero should be stripped out, since you already substring out the first two char.
I see that your function serialPort_DataReceived reads the data from a device in your port, so I presume your screen shot is from the output device.
Try and hard code
weight = "12345";
If the problem still persists, it would be caused not by the code, but by your device, etc..
Hope this helps~

Position cursor on textbox that has error

I have a simple form that takes 9 decimal numbers from 9 textboxes and I put some validation so that the users can only enter decimal numbers and nothing else.
Now the challenge I'm having is how to set the cursor in the textbox that had no decimal number after showing the error message in the try-catch statement?
Here's my code:
private void btn_Aceptar_Click(object sender, EventArgs e)
{
POI GPI = new POI();
POI VIM = new POI();
POI ST = new POI();
try
{
GPI.POI_x = Convert.ToDecimal(txt_GPIx.Text);
GPI.POI_y = Convert.ToDecimal(txt_GPIy.Text);
GPI.POI_z = Convert.ToDecimal(txt_GPIz.Text);
VIM.POI_x = Convert.ToDecimal(txt_VIMx.Text);
VIM.POI_y = Convert.ToDecimal(txt_VIMy.Text);
VIM.POI_z = Convert.ToDecimal(txt_VIMz.Text);
ST.POI_x = Convert.ToDecimal(txt_STx.Text);
ST.POI_y = Convert.ToDecimal(txt_STy.Text);
ST.POI_z = Convert.ToDecimal(txt_STz.Text);
}
catch (Exception)
{
MessageBox.Show("Ingrese solamente números en las variables GPI/VIM/ST", "Error", MessageBoxButtons.OK, MessageBoxIcon.Error);
//Set the cursor in the first textbox that had no decimals..
return;
}
Comisurales Comisurales = new Comisurales();
Comisurales.calculo_coord_comisurales(PC, AC, IHP, GPI, VIM, ST);
}
Let me add that I also have a function to ensure the user is only limited to enter decimals but I wasn't able to figure how to avoid the "." only or this for example: "1."
As an addition to my question, here's what gets validated every time the user press a key in the textbox:
private void ValidarDecimal(object sender, KeyPressEventArgs e)
{
// permitir 0-9, backspace, y decimal
if (((e.KeyChar < 48 || e.KeyChar > 57) && e.KeyChar != 8 && e.KeyChar != 46))
{
e.Handled = true;
return;
}
// chequear solamente un decimal
if (e.KeyChar == 46)
{
if ((sender as TextBox).Text.IndexOf(e.KeyChar) != -1)
e.Handled = true;
}
}
I guess I have 2 ways to resolve my issue. Number one would be find a way to ensure the user never ever enters something weird in the textbox (which I've done partially) and number 2 would be to use the try-catch with the current limitations I mentioned above and then point the user to the textbox that has issues, both are acceptable.
The Decimal class has a TryParse method that could be used to avoid all this logic driven by catching exceptions (a very expensive approach in terms of performance)
decimal value;
if(decimal.TryParse(txt_GPIx.Text, out value))
GPI.POI_x = value;
else
{
MessageBox.Show("Invalid decimal value");
txt_GPIx.Focus();
}
Of course this code needs to be repeated for every control in your list, but you could write a generic function like this one
private decimal GetValueAndValidate(Textbox txt, out bool isOK)
{
isOK = true;
decimal value = 0m;
if(!decimal.TryParse(txt.Text, out value))
{
MessageBox.Show("Invalid decimal value");
txt.Focus();
isOK = false;
}
return value;
}
and then use the following approach in your code inside the button click
bool isOK = true;
if(isOK) GPI.POI_x = GetValueAndValidate(txt_GPIx, out isOK);
if(isOK) GPI.POI_y = GetValueAndValidate(txt_GPIy, out isOK);
.... and so on for the other fields ....
For the second part of your question, finding a way to completely control the input logic is not easy. What happens for example if your user PASTE an invalid text in your textbox? There are very edge case situations that takes a lot of effort to code correctly. It is a lot more easy to leave freedom of typing to your user and apply a strict logic when you get that input.

files in C# - "file is used by another process"

I have following code:
private void askforlocation()
{
if (File.Exists("location.txt"))
{
System.IO.StreamReader loc = new System.IO.StreamReader("location.txt");
string loca = loc.ReadToEnd();
if (loca != "")
{
int index = comboBox1.FindString(loca);
comboBox1.SelectedIndex = index;
}
else
{
label6.Text = "Please select the location!";
}
loc.Close();
}
else label6.Text = "Please select the location!";
}
It is supposed to read value "location" from the file and put it to the combo box, which works ok.
I run this script on Form1_Load.
Now, I have another script:
private void comboBox1_SelectedIndexChanged(object sender, EventArgs e)
{
string value = comboBox1.SelectedItem.ToString();
System.IO.File.WriteAllText("location.txt", value);
}
This one is supposed to record the choice so that user doesn't need to enter location every time.
What is happening is when I start a program, so the value is already set, then I try to change it (so that theoretically it should overwrite the previous one), but I get an exception, saying that file is already being used by another process.
I do close the file after I used it. I also tried FILE.DISPOSE.
What am I doing wrong?
I think what's happening here is that this code:
if (loca != "")
{
int index = comboBox1.FindString(loca);
comboBox1.SelectedIndex = index;
}
is causing the SelectedIndexChanged event to be raised on the combobox. When that event is raised, comboBox1_SelectedIndexChanged is called, and that method again tries to access location.txt.
To fix, I would first change the code in askforlocation to something like this:
if (File.Exists("location.txt"))
{
var loca = string.Emtpy;
using(var loc = new System.IO.StreamReader("location.txt"))
{
loca = loc.ReadToEnd();
}
....
}
since there's no need to keep the file open for longer than necessary (note that the using block will call the Dispose() method on the StreamReader when it exits, which in turn will call the Close() method). After that, I'd consider coming up with a way to keep the event from being fired when you set the selected index on the combobox (maybe use a flag or unwire/rewire the event handler).
It seems that you're changing the index of your combobox, thus writing to the same file before closing it. Call loca.Close() before writing to the file again.
comboBox1.SelectedIndex = index;
this will fire the event SelectedIndexChanged, so invoke the Close() method right behind ReadToEnd():
private void askforlocation()
{
if (File.Exists("location.txt"))
{
System.IO.StreamReader loc = new System.IO.StreamReader("location.txt");
string loca = loc.ReadToEnd();
loc.Close();//move that code to here
if (loca != "")
{
int index = comboBox1.FindString(loca);
comboBox1.SelectedIndex = index;
}
else
{
label6.Text = "Please select the location!";
}
//loc.Close();
}
else label6.Text = "Please select the location!";
}
Give this line loc.Close(); before setting the index of the combo box because the event is being raised earlier than you think.
You never need to call file.Close() or file.Dispose().
Please use a using statement ALWAYS (or mostly) when using a class that implements IDisposable. It will call the Dispose method for you.
using(System.IO.StreamReader loc = new System.IO.StreamReader("location.txt"))
{
string loca = loc.ReadToEnd();
}

Categories

Resources