I'm developing an application in which I have a window with a MediaElement. The user can play the movie in this window. I would like to add an option to play the movie with subtitles. I know how to display text over the MediaElement but the problem is, how to display subtitles at certain intervals.
My solution (which doesn't work): I will parse a .src file to a dictionary where Key is start time and value is text.
Next, I have a timer with a 1 ms interval and at each interval I would check if the movie-time exists in the dictionary. If yes, I will show the value. Problem is, that I'm not able to check dictionary every millisecond but the interval is about 20 ms and this is the problem. So do you know how to call it every 1 millisecond?
private void timer_Tick(object sender, EventArgs e)
{
string text = MediaElement.Position.ToString("HH:mm:ss.fff");
Thread t = new Thread(() => SearchThread(text));
t.Start();
if (MediaElement.NaturalDuration.HasTimeSpan)
timer.Text = String.Format("{0} / {1}", MediaElement.Position,
MediaElement.NaturalDuration.TimeSpan.ToString());
}
private void SearchThread(string pos)
{
string text = srcFileControler.Get(pos); //take value from dictionary if exist
if (text != "")
this.txtSub.Dispatcher.Invoke(DispatcherPriority.Normal,
new Action(() => { txtSub.Text = text; }));
}
I'd recommend a more reusable approach that let's you seek, skip and replay. Since a lot of your code is missing in the question I've made a couple of assumptions of what it might look like.
Keep your subtitles in a simple class that contains at least a timestamp at which it should appear and the text to display. If at any time you want to display no text at all, simply add an entry with String.Empty for the Text.
public class SubtitleEntry
{
public TimeSpan TimeStamp { get; set; }
public string Text { get; set; }
}
To keep track of which position (timestamp and subtitle-index) you are, check if the next entry's timestamp is earlier than the last known timestamp. If the "current" subtitle entry has changed, raise an event to update the text.
public class SubtitleManager
{
public event EventHandler<string> UpdateSubtitles;
private List<SubtitleEntry> _entries;
private int _currentIndex = -1;
private TimeSpan _currentTimeStamp = TimeSpan.MinValue;
public SubtitleManager()
{
_entries = new List<SubtitleEntry>();
}
public void SetEntries(IEnumerable<SubtitleEntry> entries)
{
// Set entries and reset previous "last" entry
_entries = new List<SubtitleEntry>(entries);
_currentTimeStamp = TimeSpan.MinValue;
_currentIndex = -1;
}
public void UpdateTime(TimeSpan timestamp)
{
// If there are no entries, there is nothing to do
if (_entries == null || _entries.Count == 0)
return;
// Remember position of last displayed subtitle entry
int previousIndex = _currentIndex;
// User must have skipped backwards, re-find "current" entry
if (timestamp < _currentTimeStamp)
_currentIndex = FindPreviousEntry(timestamp);
// Remember current timestamp
_currentTimeStamp = timestamp;
// First entry not hit yet
if (_currentIndex < 0 && timestamp < _entries[0].TimeStamp)
return;
// Try to find a later entry than the current to be displayed
while (_currentIndex + 1 < _entries.Count && _entries[_currentIndex + 1].TimeStamp < timestamp)
{
_currentIndex++;
}
// Has the current entry changed? Notify!
if(_currentIndex >= 0 && _currentIndex < _entries.Count && _currentIndex != previousIndex)
OnUpdateSubtitles(_entries[_currentIndex].Text);
}
private int FindPreviousEntry(TimeSpan timestamp)
{
// Look for the last entry that is "earlier" than the specified timestamp
for (int i = _entries.Count - 1; i >= 0; i--)
{
if (_entries[i].TimeStamp < timestamp)
return i;
}
return -1;
}
protected virtual void OnUpdateSubtitles(string e)
{
UpdateSubtitles?.Invoke(this, e);
}
}
In your window, that would look something like this:
private DispatcherTimer _timer;
private SubtitleManager _manager;
public MainWindow()
{
InitializeComponent();
_manager = new SubtitleManager();
_manager.SetEntries(new List<SubtitleEntry>()
{
new SubtitleEntry{Text = "1s", TimeStamp = TimeSpan.FromSeconds(1)},
new SubtitleEntry{Text = "2s", TimeStamp = TimeSpan.FromSeconds(2)},
new SubtitleEntry{Text = "4s", TimeStamp = TimeSpan.FromSeconds(4)},
new SubtitleEntry{Text = "10s", TimeStamp = TimeSpan.FromSeconds(10)},
new SubtitleEntry{Text = "12s", TimeStamp = TimeSpan.FromSeconds(12)},
});
_manager.UpdateSubtitles += ManagerOnUpdateSubtitles;
}
private void ManagerOnUpdateSubtitles(object sender, string text)
{
txtSubtitle.Text = text;
}
private void BtnLoadVideo_Click(object sender, RoutedEventArgs e)
{
OpenFileDialog dialog = new OpenFileDialog();
if (dialog.ShowDialog(this) != true) return;
element.Source = new Uri(dialog.FileName, UriKind.Absolute);
_timer = new DispatcherTimer();
_timer.Tick += Timer_Tick;
_timer.Interval = new TimeSpan(0,0,0,0,50); //50 ms is fast enough
_timer.Start();
}
private void Timer_Tick(object sender, EventArgs eventArgs)
{
_manager.UpdateTime(element.Position);
}
I would go with an approach similar to Evk's solution but slightly different.
From an ordered list of subtitle (by time of appearance):
Take the first subtitle
Compute the remaining timespan before its moment of showing
Wait for that duration
Finally display it
Take the next subtitle, and repeat.
Here is a code using .NET async/await and Task.
public class Subtitle
{
/// <summary>
/// Gets the absolute (in the movie timespan) moment where the subtitle must be displayed.
/// </summary>
public TimeSpan Moment { get; set; }
/// <summary>
/// Gets the text of the subtitle.
/// </summary>
public string Text { get; set; }
}
public class SubtitleManager
{
/// <summary>
/// Starts a task that display the specified subtitles at the right moment, considering the movie playing start date.
/// </summary>
/// <param name="movieStartDate"></param>
/// <param name="subtitles"></param>
/// <returns></returns>
public Task ProgramSubtitles(DateTime movieStartDate, IEnumerable<Subtitle> subtitles)
{
return Task.Run(async () =>
{
foreach (var subtitle in subtitles.OrderBy(s => s.Moment))
{
// Computes for each subtitle the time to sleep from the current DateTime.Now to avoid shifting due to the duration of the subtitle display for example
var sleep = DateTime.Now - (movieStartDate + subtitle.Moment);
// Waits for the right moment to display the subtitle
await Task.Delay(sleep);
// Show the subtitle
this.ShowText(subtitle.Text);
}
});
}
private void ShowText(string text)
{
// Do your stuff here
// Since the calling thread is not the UI thread, you will probably need to call the text display in the dispatcher thread
}
}
You could add some other stuff like:
If the moment is past, do nothing and take the next subtitle
You could use a shared Timespan variable to manually shift all the subtitle moment of apparition (if the subtitles are not synchronized with the movie)
Do not run the Task in the ProgramSubtitles function but let the caller run the function in a Task? (Depending on your needs)
I'd say it's better to take another approach. When movie starts - grab first subtitle interval from the list (say it's 00:01:00) and start timer which will fire after this time. Then when this timer fires all you need is just show corresponding subtitle and repeat by grabbing next interval time and starting timer again. Some sketch code:
// assuming queue is "sorted" by interval
private readonly Queue<Tuple<TimeSpan, string>> _subtitles = new Queue<Tuple<TimeSpan, string>>();
// call this once, when your movie starts playing
private void CreateTimer() {
var next = _subtitles.Dequeue();
if (next == null) {
ShowText(null);
return;
}
System.Threading.Timer timer = null;
timer = new System.Threading.Timer(_ => {
timer.Dispose();
ShowText(next.Item2);
CreateTimer();
}, null, next.Item1, Timeout.InfiniteTimeSpan);
}
private void ShowText(string text) {
Dispatcher.Invoke(() =>
{
// show text
});
}
Related
I have a sub-classed RichTextBox that I use to display chat information scrubbed from a game. Each chat line is saved into a custom object that stores the text as well as some meta-data. The RTB then uses this meta data to do some formatting. Here's the relevant code section:
public class ChatLine
{ //stub-class with code irrelevant to the question removed
public string Text { get; set; };
public Color Color { get; set; };
public DateTime Timestamp { get; set; };
public string Name { get; set; }; //name of the player where this text originated
}
public partial class CustomRichTextbox : RichTextBox
{
private readonly Object syncRoot = new();
private readonly List<ChatLine> displayedChatLines = new();
internal IReadOnlyCollection<ChatLine> DisplayedChatLines
{
get
{
return displayedChatLines.AsReadOnly();
}
}
public CustomRichTextbox()
{
InitializeComponent();
}
protected override void OnPaint(PaintEventArgs pe)
{
base.OnPaint(pe);
}
/// <summary>
/// Appends text to the current text of the textbox, ends the line, and returns the first line position that was appended.
/// </summary>
/// <remarks>
/// The second parameter should be supplied if you want to enable name display per line, and supplying the name you don't want displayed.
/// </remarks>
internal int AppendLines(IEnumerable<ChatLine> lines, string name = "")
{
//when the RTB is empty line count will be 0, which is what we should return
//when the RTB is not empty then we need to return line count - 1
//the Math.Max call ensures we take care of both cases
var pos = Math.Max(0, Lines.Length - 1);
lock (syncRoot)
{
base.AppendText(String.Join(String.Empty, lines.Select(FormatLine)));
displayedChatLines.AddRange(lines);
}
//paint the lines after we append (THIS LOOP IS TIME CONSUMING)
for (int i = lines.Count() - 1; i >= 0; i--)
{
var index = pos + i;
var line = displayedChatLines[index];
var start = this.GetFirstCharIndexFromLine(index);
var timestampEnd = this.Find(">", start, RichTextBoxFinds.None);
var textStart = timestampEnd + 2;
var lineLength = this.Lines[index].Length;
//color in timestamp
this.Select(start, timestampEnd - start + 1);
this.SelectionColor = this.ForeColor;
this.SelectionBackColor = this.BackColor;
this.SelectionFont = this.Font;
this.SelectionAlignment = HorizontalAlignment.Left;
if (String.IsNullOrEmpty(name) || name == line.Name)
{ //line doesn't contain name
this.Select(textStart, start + lineLength - textStart + 1);
this.SelectionColor = line.Color;
this.SelectionBackColor = (line.Color == Color.White) ? Color.DarkGray : this.BackColor;
}
else
{ //line contains name
var nameStart = this.Find($" {line.Name}", start, start + lineLength, RichTextBoxFinds.Reverse);
this.Select(textStart, nameStart - textStart);
this.SelectionColor = line.Color;
this.SelectionBackColor = (line.Color == Color.White) ? Color.DarkGray : this.BackColor;
this.Select(nameStart, start + lineLength - nameStart + 1);
this.SelectionColor = Color.DarkGray;
this.SelectionBackColor = this.BackColor;
this.SelectionFont = new Font(this.Font.FontFamily, 8);
this.SelectionAlignment = HorizontalAlignment.Right; //this actually causes the entire line to be right aligned
}
}
this.DeselectAll(); //necessary otherwise restoring scroll position won't work
return pos;
string FormatLine(ChatLine line)
{
return (String.IsNullOrEmpty(name) || name == line.Name) ?
$"<{line.Timestamp: hh:mm:ss tt}> {line.Text}{Environment.NewLine}" :
$"<{line.Timestamp: hh:mm:ss tt}> {line.Text} {line.Name}{Environment.NewLine}";
}
}
}
This works fine...for updates that have few lines. When it needs to update more (say 1000) the UI thread chokes up for a few seconds to do all the painting. I've identified the time-consuming code (noted in the code above) because when I comment that loop out then there's no problem.
Normally I'd just move the time-consuming code to a backgroundworker or use an async/await pattern, but the problem is all the code that's in the time consuming section requires UI-thread access! What do I do when I can't push the time-consuming stuff to another thread?
Hello I'm trying to update my chart(s) every second, all chart(s) should be always at the same time. For better understanding I'll include an image but first off I'm gonna explain what actually happens.
So I'm ping requests are sent, every time an result is there, it writes it down in an data point array called file. Everything fine, works as expected.
At the same time, two timers are running, one timer calls a method that prepares the data (let's say at a specific time no data is found in the array -> it should just set value 0). The prepared data is than in a buffer.
The second timer is updating the UI and reading from the tempData but this isn't quite working as expected or wished.
Timers:
myTimer.Interval = 1000;
myTimer.Tick += FileReadFunction;
aTimer.Elapsed += new System.Timers.ElapsedEventHandler(prepData);
aTimer.Interval = 1000;
Button Click which starts timers:
private void _tbStartAll_Click(object sender, EventArgs e)
{
lock (_hosts)
{
foreach (HostPinger hp in _hosts)
hp.Start();
myTimer.Start();
aTimer.Enabled = true;
}
}
Method for preparing Data in Form Class:
public void prepData(object objectInfo, EventArgs e)
{
foreach (NetPinger.source.AddGraph b in graphList)
{
b.prepareData();
}
}
Prep Data Method:
public void prepareData()
{
double unixTimestamp = (Int32)(DateTime.UtcNow.Subtract(new DateTime(1970, 1, 1))).TotalSeconds;
for (double i = unixTimestamp; unixTimestamp - graphSizing < i; i--)
{
bool exists;
try
{
exists = Array.Exists(file, element => element.XValue == i);
exists = true;
}
catch
{
exists = false;
}
try
{
if (exists == false)
{
TempBuffer = TempBuffer.Skip(1).Concat(new DataPoint[] { new DataPoint(i, 0) }).ToArray();
}
else
{
DataPoint point = Array.Find(file, element => element.XValue == i);
TempBuffer = TempBuffer.Skip(1).Concat(new DataPoint[] { (point) }).ToArray();
}
}
catch (Exception ex)
{
//just for debugging...
}
}
}
File Read Method in Form Class:
private void FileReadFunction(object objectInfo, EventArgs e)
{
foreach (NetPinger.source.AddGraph b in graphList)
{
b.fileRead();
}
}
Method FileRead / Update Chart:
public void fileRead()
{
//double unixTimestamp = (Int32)(DateTime.UtcNow.Subtract(new DateTime(1970, 1, 1))).TotalSeconds;
chart_holder.Series[0].Points.Clear();
foreach (DataPoint a in TempBuffer)
{
chart_holder.Series[0].Points.Add(a);
}
}
Image Example of what I mean with time synchronization:
I'm kinda out of ideas why it's not working out, is it because a thread is faster than another? Or what is the reason and how could I fix it? I'm very thankful for your help.
Greetings C.User
I solved the problem by changing the code a bit. To keep it synchronized I prepare the data first, before displaying it at all. After the data is prepared than all the data is getting displayed. Also I only use one timer now instead of two.
I have a CSV file that will read LineByLine ... like :
0
2.25
5
5.30
I need to Timer interval to change ... but there is no effect when its changed...
I need to fill a textbox.
please let me know your solution
while ((currentLine = sr.ReadLine()) != null)
{
string[] temp = currentLine.Split(',');
timerinterval = (int)(Convert.ToDouble(temp[0])*1000);
if ((int)(Convert.ToDouble(temp[0]) * 1000) != 0)
{
mytimer.Stop();
mytimer.Interval = (int)(Convert.ToDouble(temp[0]) * 1000);
mytimer.Start();
txtCurrentLine.Text = currentLine;
txtTime.Text = timerinterval.ToString();
}
}
public TimeCapture
{
public TimeCapture() => Start = DateTime.Now;
public Start { get; }
public TimeSpan Duration => DateTime.Now.Subtract(Start).Value.TotalSeconds;
}
Then when you intend to read each line.
var timer = new TimeCapture();
while(...)
{
...
txtTime.Text = timer.Duration;
}
Now as you iterate every single line you're capturing the duration as it is reading each line. If you are interested in line by line duration, simply create a new object per line, then put duration and it'll be for the linear code block.
I'm currently using the following code as a LineTransformer with an AvalonEdit TextEditor. I want to be able to highlight the current single search result with a selection, however the selection is barely visible because the formatting of the DocumentColorizingTransformer has precedence over showing highlighted text. How do I get the highlighted selection to show instead of or before the formatting?
public class ColorizeSearchResults : DocumentColorizingTransformer {
public ColorizeSearchResults() : base() {
SearchTerm = "";
MatchCase = false;
}
public string SearchTerm { get; set; }
public bool MatchCase { get; set; }
protected override void ColorizeLine(DocumentLine line) {
if (SearchTerm.Length == 0)
return;
int lineStartOffset = line.Offset;
string text = CurrentContext.Document.GetText(line);
int count = 0;
int start = 0;
int index;
while ((index = text.IndexOf(SearchTerm, start, MatchCase ? StringComparison.CurrentCulture : StringComparison.CurrentCultureIgnoreCase)) >= 0) {
base.ChangeLinePart(
lineStartOffset + index,
lineStartOffset + index + SearchTerm.Length,
(VisualLineElement element) => {
element.TextRunProperties.SetForegroundBrush(Brushes.White);
element.TextRunProperties.SetBackgroundBrush(Brushes.Magenta);
});
start = index + 1;
count++;
}
}
}
Example of formatting showing over selection
See http://avalonedit.net/documentation/html/c06e9832-9ef0-4d65-ac2e-11f7ce9c7774.htm for AvalonEdit render flow.
The selection layer is rendered before text. So if the text has background, it overrides the selection background. Fortunately we can set the background to Brush.Transparent (or a mix of the Selection.Brush and your own color).
Solution: I've modified the SelectionColorizer code to reset selection background to transparent:
class SelectionColorizerWithBackground : ColorizingTransformer
{
ICSharpCode.AvalonEdit.Editing.TextArea _textArea;
public SelectionColorizerWithBackground(
ICSharpCode.AvalonEdit.Editing.TextArea textArea)
{
if (textArea == null)
throw new ArgumentNullException("textArea");
this._textArea = textArea;
}
protected override void Colorize(ITextRunConstructionContext context)
{
int lineStartOffset = context.VisualLine.FirstDocumentLine.Offset;
int lineEndOffset = context.VisualLine.LastDocumentLine.Offset +
context.VisualLine.LastDocumentLine.TotalLength;
foreach (var segment in _textArea.Selection.Segments)
{
int segmentStart = segment.StartOffset;
if (segmentStart >= lineEndOffset)
continue;
int segmentEnd = segment.EndOffset;
if (segmentEnd <= lineStartOffset)
continue;
int startColumn;
if (segmentStart < lineStartOffset)
startColumn = 0;
else
startColumn = context.VisualLine.ValidateVisualColumn(
segment.StartOffset, segment.StartVisualColumn,
_textArea.Selection.EnableVirtualSpace);
int endColumn;
if (segmentEnd > lineEndOffset)
endColumn =
_textArea.Selection.EnableVirtualSpace
? int.MaxValue
: context.VisualLine
.VisualLengthWithEndOfLineMarker;
else
endColumn = context.VisualLine.ValidateVisualColumn(
segment.EndOffset, segment.EndVisualColumn,
_textArea.Selection.EnableVirtualSpace);
ChangeVisualElements(
startColumn, endColumn,
element => {
element.TextRunProperties.SetBackgroundBrush(
System.Windows.Media.Brushes.Transparent);
if (_textArea.SelectionForeground != null)
{
element.TextRunProperties.SetForegroundBrush(
_textArea.SelectionForeground);
}
});
}
}
}
To use the code you are supposed to do the following:
var lineTransformers = textEditor.TextArea.TextView.LineTransformers;
// Remove the original SelectionColorizer.
// Note: if you have syntax highlighting you need to do something else
// to avoid clearing other colorizers. If too complicated you can skip
// this step but to suffer a 2x performance penalty.
lineTransformers.Clear();
lineTransformers.Add(new ColorizeSearchResults());
lineTransformers.Add(
new SelectionColorizerWithBackground(textEditor.TextArea));
After I've tried my solutions extensively, I'd like to add a few points:
While my other solution above appears to work, you'll have some subpixel artefacts when the rectangles are supposed to be tiled. If that is unacceptable you can implement an IBackgroundRenderer. (That happens to be my chosen solution.) If you want some code you may request here, but I doubt whether it will be useful.
BTW, since your question is about search result, most likely you can use https://github.com/icsharpcode/AvalonEdit/blob/697ff0d38c95c9e5a536fbc05ae2307ec9ef2a63/ICSharpCode.AvalonEdit/Search/SearchResultBackgroundRenderer.cs unmodified (or modify it if you don't want the rounded borders).
You may use element.BackgroundBrush = Brushes.Magenta; instead of element.TextRunProperties.SetBackgroundBrush(Brushes.Magenta);. AvalonEdit appears to draw the background with a rectangle with 3px radius.
There is also a RichTextColorizer starting from AvalonEdit 5.01. I don't know how to use it though because it is not referenced in other files. And the (most likely unwanted) rounded rectangles in the previous paragraph are likely to be present.
So here's my final product based almost entirely off of the existing AvalonEdit SearchResultBackgroundRenderer.
This works a little different than my post's colorizer as you have to modify the search results manually instead of it doing it for you. But that may also save some computation time.
If your search doesn't use Regex, then you can easily modify SearchResult to instead just pass in a start offset and length for the constructor.
/// <summary>A search result storing a match and text segment.</summary>
public class SearchResult : TextSegment {
/// <summary>The regex match for the search result.</summary>
public Match Match { get; }
/// <summary>Constructs the search result from the match.</summary>
public SearchResult(Match match) {
this.StartOffset = match.Index;
this.Length = match.Length;
this.Match = match;
}
}
/// <summary>Colorizes search results behind the selection.</summary>
public class ColorizeSearchResultsBackgroundRenderer : IBackgroundRenderer {
/// <summary>The search results to be modified.</summary>
TextSegmentCollection<SearchResult> currentResults = new TextSegmentCollection<SearchResult>();
/// <summary>Constructs the search result colorizer.</summary>
public ColorizeSearchResultsBackgroundRenderer() {
Background = new SolidColorBrush(Color.FromRgb(246, 185, 77));
Background.Freeze();
}
/// <summary>Gets the layer on which this background renderer should draw.</summary>
public KnownLayer Layer {
get {
// draw behind selection
return KnownLayer.Selection;
}
}
/// <summary>Causes the background renderer to draw.</summary>
public void Draw(TextView textView, DrawingContext drawingContext) {
if (textView == null)
throw new ArgumentNullException("textView");
if (drawingContext == null)
throw new ArgumentNullException("drawingContext");
if (currentResults == null || !textView.VisualLinesValid)
return;
var visualLines = textView.VisualLines;
if (visualLines.Count == 0)
return;
int viewStart = visualLines.First().FirstDocumentLine.Offset;
int viewEnd = visualLines.Last().LastDocumentLine.EndOffset;
foreach (SearchResult result in currentResults.FindOverlappingSegments(viewStart, viewEnd - viewStart)) {
BackgroundGeometryBuilder geoBuilder = new BackgroundGeometryBuilder();
geoBuilder.AlignToWholePixels = true;
geoBuilder.BorderThickness = 0;
geoBuilder.CornerRadius = 0;
geoBuilder.AddSegment(textView, result);
Geometry geometry = geoBuilder.CreateGeometry();
if (geometry != null) {
drawingContext.DrawGeometry(Background, null, geometry);
}
}
}
/// <summary>Gets the search results for modification.</summary>
public TextSegmentCollection<SearchResult> CurrentResults {
get { return currentResults; }
}
/// <summary>Gets or sets the background brush for the search results.</summary>
public Brush Background { get; set; }
}
In order to make use of the background renderer:
var searchColorizor = new ColorizeSearchResultsBackgroundRenderer();
textEditor.TextArea.TextView.BackgroundRenderers.Add(searchColorizor);
I have a USB barcode scanner attached.
I'm currently detecting whether key presses were sent to a text box from it via keyups, by having it send a special key combination before it sends the barcode.
However I was wondering if there is another way to do it.
I took at the KeyEventArgs
private void TextBox_KeyUp(object sender, KeyEventArgs e)
{
this.TextBlock1.Text = e.KeyboardDevice.ToString();
}
I thought e.KeyboardDevice might give me some info on which "keyboard" e.g. the standard keyboard or the "usb barcode scanner keyboard" but I can't seem to find any of that information.
I just thought there may be a neater way of doing this than sending the special key combination from the barcode scanner and using that.
You could determine it by timing,
if time between keypresses is less than 50ms, then it's Scanner
private readonly Stopwatch _stopwatch;
private long _lastKeyPressedAgo;
public Constructor()
{
_stopwatch = new Stopwatch();
_stopwatch.Start();
_lastKeyPressedAgo = -1;
}
private async Task KeyDown(RoutedEventArgs routedEvent)
{
if (routedEvent is not KeyEventArgs keyEventArgs)
{
return;
}
_stopwatch.Stop();
_lastKeyPressedAgo = _stopwatch.ElapsedMilliseconds;
_stopwatch.Restart();
if (_lastKeyPressedAgo is > 0 and < 50)
{
//This means it's from scanner
}
}
Something like this should work. Although in this case, first key press will not be registered, but you could think of workarounds for it, save it in variable for example, and then if you confirm it's scanner, you know what first press was.
I thought Id contribute my solution. Its not that elegant but it only looks for numeric key presses and then looks at the time it takes to respond. If the time is longer than the maximum threshold then it throws out those values from the array. I hope this helps someone.
class BarcodeReader
{
ArrayList barCode = new ArrayList();
ArrayList barCodeTimes = new ArrayList();
ArrayList barCodeDeltaTimes = new ArrayList();
/// <summary>
/// Input 1: delayTime (ms) - time for scanner to return values (threshold)[30 seems good],
/// Input 2: KeyEventArgs - put in key [this.KeyDown += new KeyEventHandler(Form1_KeyDown)],
/// Output 1: String of barcode read
/// </summary>
/// <param name="delayTime"></param>
/// <param name="e"></param>
/// <returns></returns>
public string BarcodeValue(int delayTime, KeyEventArgs e)
{
string barCodeString = null;
var isNumber = e.KeyCode >= Keys.D0 && e.KeyCode <= Keys.D9;
var rtn = e.KeyCode == Keys.Enter;
if (isNumber)
{
barCode.Add(Encoding.ASCII.GetString(new byte[] { (byte)e.KeyValue }));
barCodeTimes.Add(DateTime.Now.TimeOfDay.TotalMilliseconds);
}
if (rtn)
{
barCodeString = ValuesToString(delayTime);
}
return barCodeString;
}
private string ValuesToString(int delayTime)
{
string barCodeString = null;
foreach (double d in barCodeTimes)
{
double diff = 0;
int index = barCodeTimes.IndexOf(d);
if (index < barCodeTimes.Count - 1)
{
diff = (double)barCodeTimes[index + 1] - (double)barCodeTimes[index];
}
barCodeDeltaTimes.Add(diff);
}
foreach (double d in barCodeDeltaTimes)
{
if (d > delayTime)
{
barCode.RemoveAt(0);
}
}
foreach (string s in barCode)
{
barCodeString += s;
}
barCode.Clear();
barCodeTimes.Clear();
barCodeDeltaTimes.Clear();
return barCodeString;
}
}