3
\$\begingroup\$

The need is to get the possibility to differentiate between a Barcode-Scanner (which behaves like a keyboard) from a human pressing keys in a...let's say a Textbox (the use case would be all over everything, wherever I expect a barcode scanner).

The basic idea was to create a buffer which holds the typed string (converted from the Keys-Enum, which I already have a function for) and "releases" it to a callback which then handles it. The problem with those scanners is that they behave like normal keyboards, there's no way to differentiate between them and the user except for the amount of time spend between keypresses. To minimize on-screen flickering, it is shoveled into a buffer instead of the control.

Despite that I mostly need it to hold Strings, I wrote it as a generic method because I can imagine to stuff pretty much everything in there when I need it in a certain amount of time back.

So, let's cut to the case:

public class TimeoutBuffer<T>
{
    private Thread backgroundThread;
    private T buffer;
    private DateTime lastChange = DateTime.MaxValue;
    private Object syncObject = new Object();

    public Action<T> Callback { get; set; }
    public int Timeout { get; set; }

    public TimeoutBuffer(int timeout)
    {
        this.Timeout = timeout;
    }

    /// <summary>
    /// Retrieves the value from the buffer (and clears the buffer). Resets the timer.
    /// </summary>
    /// <returns></returns>
    public T Get()
    {
        lock (syncObject)
        {
            T temp = buffer;
            buffer = default(T);
            lastChange = DateTime.MaxValue;
            return temp;
        }
    }

    /// <summary>
    /// Stores the value into the buffer. Resets the timer.
    /// </summary>
    /// <param name="buffer"></param>
    public void Store(T buffer)
    {
        lock (syncObject)
        {
            this.buffer = buffer;
            lastChange = DateTime.Now;
            if (backgroundThread == null || backgroundThread.ThreadState == ThreadState.Stopped)
            {
                backgroundThread = new Thread(Check);
                backgroundThread.IsBackground = true;
                backgroundThread.Start();
            }
        }
    }

    private void Check()
    {
        while (syncObject != null && buffer != null) // Just to be sure...I think...
        {
            lock (syncObject)
            {
                if ((DateTime.Now - lastChange).TotalMilliseconds >= Timeout)
                {
                    if (Callback != null)
                    {
                        Callback.Invoke(Get());
                    }
                    else
                    {
                        // Clear it anyway...their loss.
                        buffer = default(T);
                        lastChange = DateTime.MaxValue;
                    }

                    return;
                }
            }
            Thread.Sleep(1);
        }
    }
}

What you're looking at here is a threaded-beast:

  1. It gets created
  2. Something gets stored in the buffer
  3. The background-thread is started
  4. [Optional]: Go back to 2
  5. The background-thread finds that it is time and calls the callback
  6. The buffer is cleared and the background-thread terminates
  7. Go back to 2

The original design had a constantly running background-thread (with an if(buffer != null) right inside the lock), which I'm not very fond of...I'm not very fond of creating a thread every keypress, either.

Example usage:

private TimeoutBuffer<String> buffer;

// In the form constructor
buffer = new TimeoutBuffer<String>(20);
buffer.Callback = new Action<String>(buffer_TimedOut);

// Keypress-Event
buffer.Store((buffer.Get() ?? "") + keyData.ToString());

void buffer_TimedOut(String value)
{
    if (this.InvokeRequired)
    {
        this.BeginInvoke(new Action<String>(buffer_TimedOut), new Object[] { value });
    }
    else
    {
        if (value.Length == 13)
        {
    // Barcode!
        }
        else
        {
            // Typed text...append to Textbox f.e.
        }
    }
}

What I feel odd about:

  • The locking
  • The handling of T
  • The thread
\$\endgroup\$
4
  • \$\begingroup\$ so i'm assuming this is a HID scanner. Is there a SDK that came with it? if there is you might have a better way of checking if the scanner was used. Another possibility is if you can use the SDK to fire the scanner. Our honeywell, and Newland scanners both had that possibility, and it worked reasonably well. as a tip for right now though, look into Queue and Monitor the Queue you can empty out and fill up as needed. The monitor will lock a background thread until you pulse (saving you lots of cycles and power) \$\endgroup\$ Jun 4, 2015 at 3:13
  • \$\begingroup\$ @RobertSnyder Thanks for the suggestions, those scanners did not have an SDK, they simply reported as keyboard (quite cheap and simple scanners. Though, I'm not working there anymore, so I can't tell if the code was changed somehow or another solution was found. \$\endgroup\$
    – Bobby
    Jun 4, 2015 at 8:47
  • \$\begingroup\$ Bummer. I just looked at the date stamp on this thread! WOW! 3 YEARS! hahaha. Well.. Do you want a review of your code becuse i'm fixing to throw it all in VS and see about improvments. \$\endgroup\$ Jun 4, 2015 at 12:26
  • \$\begingroup\$ Sure, I'd like to know what you think about (though, I'll have to read it again). Also don't worry, happened to me more often than I'd like to admit. \$\endgroup\$
    – Bobby
    Jun 4, 2015 at 16:00

2 Answers 2

2
\$\begingroup\$

Using DateTime to check the timespan is not as accurate or as good as just using the System.Diagnostic.Stopwatch class. Plus the stopwatch class has a ElapsedMilliseconds already. So you can switch out LastChange with a stopwatch, and everytime you set it to DateTime.MaxValue instead call stopwatch.Reset()

There are places when you use the Object version of an object instead of its primitive counterpart. I haven't seen much discussion on it lately, but most people would prefer to use the primitive counterpart unless using one of the static methods (such as String.Format and even then some people would argue...meh)

What I don't get about your logic and your description is how this would differentiate between Barcode input and UserInput. I say that because if you follow your logic you check if your buffer is null (and actually since it is generic that isn't a good idea you should check if it is equal to default(T), and even that isn't a great idea because you very well may want to pass in null/default value) after you check if it is null you call the callback. Essentially you are forcing your program to wait 20ms to return the data you want. Overall that isn't so bad, and yes, who has the ability to type 13 characters in 20ms??? but... wouldn't it be better to do a circular average of the time between when the buffer gets filled, and return that as part of your callback? You could then check that the AverageTimeBetweenKeyPresses is ???1ms??? and assume that it is a scanner. Meh.. I even see draw backs to that approach as well.

One other thing I would prefer to see is the use of a Monitor pulse and wait system. You can create a timer that just does Monitor.Pulse on a object. In your background thread you would have your while loop but instead of continuasly looping you'd Lock the object to be Pulsed by Monitor. here is a good article that discuses it in length - MSDN Article

\$\endgroup\$
2
  • \$\begingroup\$ Thank you. Yes, the only difference between user input the scanner is the amount of time that it takes. As I said, I don't have access to that code anymore, but your suggestions sound very reasonable. \$\endgroup\$
    – Bobby
    Jun 5, 2015 at 18:59
  • \$\begingroup\$ @Bobby yeah, I had to do work with scanners and user input for a few years too. Fun work. \$\endgroup\$ Jun 6, 2015 at 11:52
0
\$\begingroup\$

most of the barcode scanners enables the input of a prefix and a suffix to the data they will send to the computer. so, a solution in c# is to use the "PreviewTextInput" event to capture each letter as they go to the app (before they get to a specific control) in the form of strings. then, if it is your prefixe, you take all the following letters as long as you don't reach your suffix and put them together in a string. for these letters, you can specify that they are "handled" so they won't make it trought to a textbox without your consentment.

otherwise, if it is not your prefixe, you can assume it is a hand made entry and simply do nothing. the app will then handle it as usual...

\$\endgroup\$
2
  • \$\begingroup\$ Does this review the code in question? \$\endgroup\$
    – Mast
    Jan 30, 2017 at 16:09
  • \$\begingroup\$ @Mast Well, I'd allow it because it "reviews" the basic ideas and assumptions of the code. Something which I have also done multiple times. But changing the scanner to have a prefix is/was not an option, as far as I remember. \$\endgroup\$
    – Bobby
    Jan 30, 2017 at 22:01

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Not the answer you're looking for? Browse other questions tagged or ask your own question.