Question to Auto logout C# desktop application - c#

I need to implement an auto logout feature in C#. Previously i have asked a similiar question before and i managed to implement it using the System.Windows.Forms.Timer . But right now i have a additional requirement apart from resetting the timer when the user move the mouse or enters a key i also need to reset the timer when a new message is received via the serial port ( DataReceived event handler ).
serialPort.DataReceived += port_DataRecieved;
I need to include the reset function in a portion of the port_DataRecieved function. I cannot simply add another delegate method to the serialPort.DataReceived which will perform the reset as the serialPort.DataReceived will received a lot of other messages that i am not interested in. I want to perform a reset when the message that i am interested in arrives. And i know where to put the reset feature. The issue is that the timer does not reset in port_DataRecieved method. And i cannot achieve the desired result using the System.Threading.Timer. Anyone can guide me or provide some suggestion on this issue ? Any help provided will be greatly apperciated.
public partial class Form1: Form
{
private System.Windows.Forms.Timer sessionTimer = new System.Windows.Forms.Timer();
public Form1()
{
initialiseTimer();
}
private void port_DataRecieved(object sender, SerialDataReceivedEventArgs e)
{
try
{
serialPort= (SerialPort)sender;
str = serialPort.ReadExisting();
string[] split = str.Split(Convert.ToChar(10));
for (int i = 1; i < split.Length; i++)
{
str = split[i];
if (split[i].StartsWith("+CMTI:"))
{
sessionTimer.Stop();
sessionTimer.Start();
//Other codes
}
}
}
catch (Exception)
{
MessageBox.Show("Error processing received commands !", "CONNECTION ERROR", MessageBoxButtons.OK, MessageBoxIcon.Error);
sendRecPort.Close();
}
}
private void Form1_KeyDown(object sender, KeyEventArgs e)
{
sessionTimer.Stop();
sessionTimer.Start();
}
private void Form1_MouseMove(object sender, MouseEventArgs e)
{
sessionTimer.Stop();
sessionTimer.Start();
}
private void initialiseTimer()
{
sessionTimer.Interval = (5 * 60 * 1000);
sessionTimer.Tick += new EventHandler(logOutUser);
sessionTimer.Stop();
sessionTimer.Start();
}
private void logOutUser(object sender, EventArgs e)
{
// logout the user
this.Hide();
//Open up the login Form
login.Show();
}
}

Your problem is that the the DataReceived event is being executed on a thread other than the UI thread. You're trying to modify the timer (a UI object) from a non-UI thread. This typically throws an exception, but it's possible that the method that issues the DataReceived event is swallowing that exception.
From the documentation for the DataReceived event:
The DataReceived event is raised on a
secondary thread when data is received
from the SerialPort object. Because
this event is raised on a secondary
thread, and not the main thread,
attempting to modify some elements in
the main thread, such as UI elements,
could raise a threading exception. If
it is necessary to modify elements in
the main Form or Control, post change
requests back using Invoke, which will
do the work on the proper thread.
You need to synchronize with the UI thread to set the timer.
void ResetTimer()
{
sessionTimer.Stop();
sessionTimer.Start();
}
private void port_DataRecieved(object sender, SerialDataReceivedEventArgs e)
{
//Other codes
this.Invoke((MethodInvoker)delegate { ResetTimer(); });
//Other codes
}

I need to include the reset function in a portion of the port_DataReceived function.
Ok. Gotcha.
I cannot simply add another delegate method to the serialPort.DataReceived which will perform the reset as the serialPort.DataReceived will receive a lot of other messages that I am not interested in.
Ok, but I thought you said:
I want to perform a reset when the message that I am interested in arrives.
So you either have to listen to that DataReceived method, or you won't know when that message arrives.
I'm confused. What is it you want to do? Magic?
if (dataReceived == "someValue1")
{
//action if matches "someValue1"
}
else if (dataReceived.Contains("someValue2"))
{
// action if contains "someValue2"
}
else if (dataReceived.IndexOf("someValue3") != -1 )
{
// action if contains "someValue3"
}
else if (dataReceived == "someValue4")
{
// action if matches "someValue4"
}
else
{
// default action
}

Related

How to make a program wait for user to type in a textbox?

When I run a WinForm program to a line, I would like to check if a textbox already has user input, if not, I will ask user to type in the textbox and wait till user types in some input, before running the next line of the code. I was wondering how to do the wait?
The program has to wait for the information required as input for the next line of code.
Thanks.
Waiting for something to happen in the GUI (using a timer, loops from other threads, etc...) is a massive waste of resources. Almost all functional programming languages have Events including C#
From Wikipedia:
event-driven programming is a programming paradigm in which the flow
of the program is determined by events such as user actions (mouse
clicks, key presses), sensor outputs, or message passing from other
programs or threads. Event-driven programming is the dominant paradigm
used in graphical user interfaces and other applications (e.g.,
JavaScript web applications) that are centered on performing certain
actions in response to user input. This is also true of programming
for device drivers (e.g., P in USB device driver stacks).
You can do it like this with the help of Control.TextChanged event inherited by the Textbox control:
private void Form1_Load(object sender, EventArgs e)
{
ValidateGUI();
}
private const int MIN_CHARS_TO_DO_SOMETHING = 8;
private const string NOT_VALID = "Oh No There is No User Input )-:";
private const string VALID = "Great We Can Do Something (-:";
private void textBox1_TextChanged(object sender, EventArgs e)
{
ValidateGUI();
}
private void ValidateGUI()
{
if (textBox1.Text.Length < MIN_CHARS_TO_DO_SOMETHING)
{
lblMessege.Text = NOT_VALID;
}
else
{
lblMessege.Text = VALID;
// Execute some code..
//...
//...
}
}
I'm going to assume you have a valid reason for waiting instead of monitoring input.
You just need to use background workers and then you need to set the DoWork event to wait for a specified amount of time, and the RunWorkerCompleted event to run your code checking if input is being made.
Here's an example assuming a label and a textbox are on the form already. Alternatively you can just add the background worker as a form element instead of creating it in code:
public Form1()
{
InitializeComponent();
waitForInput();
}
private void waitForInput()
{
BackgroundWorker waiter = new BackgroundWorker();
waiter.WorkerSupportsCancellation = true;
waiter.WorkerReportsProgress = true;
waiter.DoWork += wait10Seconds;
waiter.RunWorkerCompleted += doneWaiting;
waiter.RunWorkerAsync();
}
private void wait10Seconds(object sender, DoWorkEventArgs e)
{
System.Threading.Thread.Sleep(10000);
}
private void doneWaiting(object sender, RunWorkerCompletedEventArgs e)
{
if(textBox1.Text == "")
{
label1.Text = "Why haven't you typed anything?";
}
}
If I understand what you're getting at, a synchronization object like SemaphoreSlim might be a good fit for something like this. This declaration sets the initial count to 0 so the semaphore will block. The initSync method pauses halfway through and will await the textbox Enter key to release the semaphoreSlim before executing the next line. However, the UI thread is not blocked during the waiting period.
SemaphoreSlim _waitForText = new SemaphoreSlim(0, maxCount: 1);
private async Task initAsync()
{
richTextBox1.AppendText(
$"The async method that populates this RichTextBox waits indefinitely for input.");
richTextBox1.AppendText($"{Environment.NewLine}>");;
richTextBox1.SelectionColor = Color.Red;
// "check if a textbox already has user input"
if (string.IsNullOrWhiteSpace(textBox1.Text))
{
// "ask user to type"
textBox1.Text = "Enter ID";
await _waitForText.WaitAsync();
}
else onUserInputOK();
// The MOCK login has completed. Enable the app.
richTextBox1.Enabled = true;
richTextBox1.AppendText(
$"{Environment.NewLine}Now this method will complete, and you'll see a message box in 5 seconds");
}
Example
public MainForm() => InitializeComponent();
protected override async void OnLoad(EventArgs e)
{
base.OnLoad(e);
// Disable UI response until "logged in"
richTextBox1.Enabled = false;
// Subscribe to TextChanged event
textBox1.KeyDown += detectUserInput;
await initAsync();
await Task.Delay(TimeSpan.FromSeconds(5));
MessageBox.Show("All done");
}
private void detectUserInput(object sender, KeyEventArgs e)
{
if (e.KeyData == Keys.Return)
{
e.Handled = e.SuppressKeyPress = true;
if (!string.IsNullOrWhiteSpace(textBox1.Text))
{
onUserInputOK();
}
}
}
private void onUserInputOK()
{
richTextBox1.AppendText($"{textBox1.Text}");
Text = textBox1.Text; // Start echo to title bar
richTextBox1.SelectionColor = Color.DarkGreen;
// Remove this listener. Install normal runtime hook.
textBox1.KeyDown -= detectUserInput;
textBox1.TextChanged += normalTextboxListener;
_waitForText.Release();
}
private void normalTextboxListener(object sender, EventArgs e) => Text = textBox1.Text;

How to make a timer that redo the event programmatically?

So I want to perform some button clicks say every in 10 second, and here is my code:
using System;
using System.Timers;
public class Main : Form {
public Main() {
InitializeComponent();
// add timer to do button clicking every 10 seconds
double elapse = 10000;
System.Timers.Timer timer2 = new Time(elapse);
timer2.Elapsed += new ElapsedEventHandler(ResetEvent);
timer2.AutoReset = true;
timer2.Start();
}
private void ResetEvent(object source, ElapsedEventArgs e) {
try {
Refresh_Button.PerformClick();
Process_Button.PerformClick();
} catch { }
}
private void Refresh_Button_Click(object sender, EventArgs e) {
// some code
}
private void Process_Button_Click(object sender, EventArgs e) {
// some code
}
}
However, it doesn't work. Is there anything wrong with the code? How can I make it works?
The problem is accessing UI thread illegally in Elapsed event of System.Timers.Timer.
You are calling Refresh_Button.PerformClick(); in Elapsed event of timer that cause an cross thread exception that you are hiding it.
To access UI thtread and call PerformClick() method of Refresh_Button:
Refresh_Button.Invoke(new Action(() => { Refresh_Button.PerformClick(); }));
Also you can use System.Windows.Forms.Timer instead and handle Tick event and call Refresh_Button.PerformClick(); manually.
Note:
Don't hide exceptions. If you hide exceptions, such problems will hide and finding them will be really hard.
It's better to put the logic a method and instead of calling PerformClick, call that method.
If you don't need a different thread, System.Windows.Forms.Timer whould be enough.

MessageBox.Show called from backgound worker with the handle of the main UI thread

I have this code:
public void Blah(IWin32Window _this)
{
for (int i = 0; i < item_quantity; i++)
{
try { File.Delete(item[0, i]); }
catch (Exception ex)
{
if (MessageBox.Show(_this, String.Format("Error while accessing {0}\n{1}"
, item[0, i], ex.Message), "Error", MessageBoxButtons.RetryCancel
, MessageBoxIcon.Error) == DialogResult.Retry)
{ i--; }
}
}
}
...and this code in the main UI thread:
private void backgroundWorker_DoWork(object sender, DoWorkEventArgs e)
{
AnotherClass.Blah(this);
}
When I execute this code, I get the unsafe cross-thread exception. What's the safe way to do this operation?
What's the safe way to do this operation?
There is no real safe way to do this. The message box pops out of nowhere, without any direct connection to a command that the user gave. One failure mode is that the user continues working with your UI, clicking the mouse or pressing the space bar. And your message box pops up a millisecond before he clicked the mouse or pressed a key. He'll never see the message.
So something was supposed to be done, it didn't get done and the user is completely unaware of it. Not a good thing. You'll need to doctor your UI so this condition can never occur. Clearly that will require that you do error reporting a different way than by using a temporary message box. Many possible alternatives of course, could be as simple as a Label that reports state. StatusStrip is good for this.
The actual exception is a bogus one. It is triggered by the built-in diagnostics that checks that code uses UI in a thread-safe way. The underlying winapi call is GetParent(), one of the very few user32 Windows functions that can safely be called, and used, from a worker thread. The only legitimate reason I know where using Control.CheckForIllegalCrossThreadCalls to work around the problem is okay. But fix the real problem instead.
I'm not condoning the design, but you can pass in the Form to Blah() and then Invoke() against the referenced form:
public partial class Form1 : Form
{
public Form1()
{
InitializeComponent();
}
private void button1_Click(object sender, EventArgs e)
{
if (!backgroundWorker.IsBusy)
{
button1.Enabled = false;
backgroundWorker.RunWorkerAsync();
}
}
private void backgroundWorker_DoWork(object sender, DoWorkEventArgs e)
{
SomeClass AnotherClass = new SomeClass();
AnotherClass.Blah(this);
}
private void backgroundWorker_RunWorkerCompleted(object sender, RunWorkerCompletedEventArgs e)
{
button1.Enabled = true;
MessageBox.Show("Done!");
}
}
public class SomeClass
{
public void Blah(Form frm)
{
int item_quantity = 5;
for (int i = 0; i < item_quantity; i++)
{
try
{
//File.Delete(item[0, i]);
Console.WriteLine("i = " + i.ToString());
throw new Exception("duh");
}
catch (Exception ex)
{
frm.Invoke(new Action(() =>
{
DialogResult result = MessageBox.Show(frm, String.Format("Error while accessing {0}\n{1}", "something", ex.Message), "Error", MessageBoxButtons.RetryCancel, MessageBoxIcon.Error);
if (result == DialogResult.Retry)
{
i--;
}
}));
}
}
}
}
You are trying to do UI work on a background thread, hence the cross-thread exception. RunWorkerCompletedEventArgs has a property called Error that will hold any exception that gets thrown by the RunWorkerAsync delegate. Set up a handler for RunWorkerCompleted on your BackgroundWorker and check if the Error property has a value. If it does, prompt the MessageBox in the handler because you will be on the UI thread at that point. Call the BackgroundWorker's RunWorkerAsync method again on the DialogResult.Retry scenario.
(You will probably have to tweak your BackgroundWorker and AnotherClass.Blah to take in the value of i to prime your loop condition for that second call to your BackgroundWorker. The DoWorkEventArgs has a property called Argument that you can use to pass in that value.)
You need to execute UI code like this when calling it from another thread:
// must use invoke because the timer event is running on a separate thread
this.Invoke(new Action(() =>
{
MessageBox.Show("Message");
}));

serialPort.DataReceived running in thread not working

I'm working in a thread to check the GPRS connection in CompactFramework.
The idea of the thread is simple: If the program isn't connected then I run the code to connect (this code is giving me errors), but if the connection is OK then I recheck again in 60 seconds and so on.
Now, focusing in connection code. The following code check if it's connected or not, if it isn't then I subscribe to DataReceive event.
void initFormText()
{
if (isThereConnect()) //true if it is connected
{
//enable timer to recheck if it's connected
}
else //it isn't connected
{
serialPort1.DataReceived += new System.IO.Ports.SerialDataReceivedEventHandler(serialPort1_DataReceived);
if (serialPort1.IsOpen)
{
serialPort1.Close();
}
serialPort1.Open();
timerStep.Enabled = true;
}
}
Now comes the issue, in the serialPort1_DataReceived I check the data and set a variable which is tested by the timerStep and it make some steps.
The problem occurs in the DataReceived event, the thing is that when I run the following code outside of a thread it works fine, it does all the job and make the connection, but in the thread it doesn't work. I test this adding some MessageBoxand I realize that the ones inside the DataReceive never appear.
void serialPort1_DataReceived(object sender, System.IO.Ports.SerialDataReceivedEventArgs e)
{
byte[] data = new byte[1024];
int n = serialPort1.Read(data, 0, data.Length);
string rec = Encoding.GetEncoding("windows-1252").GetString(data, 0, n);
if (string.IsNullOrEmpty(rec))
{
return;
}
if (rec.Contains("AT+CIMI") && rec.Contains("OK"))
{
MessageBox.Show("serialPort 1");
currState = 1;
}
else if (rec.Contains("READY"))
{
MessageBox.Show("serialPort 11");
currState = 1;
}
else if (rec.Contains("0,1") || rec.Contains("0,5"))
{
MessageBox.Show("serialPort 2");
currState = 2;
}
}
So by some reason the serialPort isn't receiving anything and I can't figure it out why. The fact that it works outside the thread but not in the thread is frustrating me.
I appreciate any help. Thanks in advanced!
Yes, but I think that your thread finishes before event is fired. You should create your Form in a following manner, please note that this is code for desktop but simulates what is available in CompactFramework since I don't have it installed here. First Form1 is main form and it starts thread in which is the Form2. The Form2 has a button and Click EventHandler that is working, but you need to show your Form2 with Application.Run(). Here is the sample code:
public partial class Form1 : Form
{
public Form1()
{
InitializeComponent();
}
private void button1_Click(object sender, EventArgs e)
{
Thread thread = new Thread(new ThreadStart(ThreadMethod));
thread.SetApartmentState(ApartmentState.STA);
thread.Start();
}
void ThreadMethod()
{
Form2 f = new Form2();
Application.Run(f);
}
}
public partial class Form2 : Form
{
public Form2()
{
InitializeComponent();
}
private void button1_Click(object sender, EventArgs e)
{
MessageBox.Show("Something");
}
}
Hope it will work this way.
The event must run in the same thread (I suppose UI thread) where you have already declared serialPort1. You can execute the code from the serialPort1_DataReceived event in different thread. That thread should be started by serialPort1_DataReceived event handler. The problem is that CompactFramework doesn't have ParameterisedThreadStart so you can not effectively pass received data to the thread. You will need to set some global field using delegates.

Timer says it's enabled, but never executes

I have a sub which starts one of two timers (depending on 'zone' condition). This sub called 'CheckAndActivateRelays' is itself called by a Serial Port _DataReceived event. I am inserting break points to help me troubleshoot and am seeing that the tmrSoundSirensAfterDelay.Start() line is being executed successfully with the status of the timer even changing to enabled. However the associated Tick event never executes any of the code contained within it.
If I do the same thing by calling the sub from within button24's click event, it works perfectly. Everything is on the same Form with no threaded processes.
Anyone? Thanks
private void checkAndActivateRelays(int zoneNumber)
{
if (globalFullAlarmSet || globalNightAlarmSet || globalDoorsAlarmSet)
{
if (zoneNumber == 1) //Entry zone
{
//kick off a timer after delay specified in Settings1 file,
if (Settings1.Default.alarmSirenDurationInMinutes != 0)
{
//activates the relays if global alarm flags are still set to true
//(i.e. user has not entered code in time)
globalAlarmEntryDurationTicks = 0;
tmrSoundSirensAfterDelay.Start();
}
}
else //If any other zone is activated during alarm set condition
{
if (Settings1.Default.alarmSirenDurationInMinutes != 0)
{
//Output to relays 1 & 2
spIOCard.Write("~out10=1~");
spIOCard.Write("~out11=1~");
//then close after duration from Settings1 file
globalAlarmSirenDurationTicks = 0;
tmrSoundSirens.Start();
}
}
}
}
private void tmrSoundSirensAfterDelay_Tick(object sender, EventArgs e)
{
globalAlarmEntryDurationTicks = globalAlarmEntryDurationTicks + 1;
if (globalAlarmEntryDurationTicks == Settings1.Default.alarmEntryDelayInSeconds) //Value from Settings1 file
{
spIOCard.Write("~out10=1~");
spIOCard.Write("~out11=1~");
globalAlarmEntryDurationTicks = 0;
tmrSoundSirensAfterDelay.Stop();
tmrSoundSirens.Start();
}
}
private void tmrSoundSirens_Tick(object sender, EventArgs e)
{
globalAlarmSirenDurationTicks = globalAlarmSirenDurationTicks + 1;
if (globalAlarmSirenDurationTicks == (Settings1.Default.alarmSirenDurationInMinutes * 5)) //*60 Value from Settings1 file
{
spIOCard.Write("~out10=0~");
spIOCard.Write("~out11=0~");
globalAlarmSirenDurationTicks = 0;
tmrSoundSirens.Stop();
}
}
private void button24_Click(object sender, EventArgs e)
{
globalFullAlarmSet = true;
checkAndActivateRelays(1);
}
Serial Port Data Received Code:
private void spIO_DataReceived(object sender, System.IO.Ports.SerialDataReceivedEventArgs e)
{
RxString = spIOCard.ReadExisting();
if (RxString == "~in00=1~")
{
checkAndActivateRelays(1);
button10.BackColor = System.Drawing.Color.Red;
}
if (RxString == "~in00=0~")
{
button10.BackColor = System.Drawing.Color.LightGray;
}
if (RxString == "~in01=1~")
{
checkAndActivateRelays(2);
button11.BackColor = System.Drawing.Color.Red;
}
if (RxString == "~in01=0~")
{
button11.BackColor = System.Drawing.Color.LightGray;
}
if (RxString == "~in02=1~")
{
button12.BackColor = System.Drawing.Color.Red;
}
if (RxString == "~in02=0~")
{
button12.BackColor = System.Drawing.Color.LightGray;
}
}
Something to think about since you are using the DataReceivedEvent. According to MSDN it is raised on a secondary thread. This is probably causing your issue.
The DataReceived event is raised on a secondary thread when data is
received from the SerialPort object. Because this event is raised on a
secondary thread, and not the main thread, attempting to modify some
elements in the main thread, such as UI elements, could raise a
threading exception. If it is necessary to modify elements in the main
Form or Control, post change requests back using Invoke, which will do
the work on the proper thread.
Since calling Start() is not the problem the timer setup is where you need to look. Make sure you handle the tick event AND set an interval.
myTimer.Tick += new EventHandler(TimerEventProcessor);
// Sets the timer interval to 5 seconds.
myTimer.Interval = 5000;
myTimer.Start();
The key here is that you are doing this in the SerialPort DataReceived event. This event is fired on a separate thread. Thats important because you probably registered for the Tick event on the main thread, but you start the timer on a different one. You'll need to register the Tick event in the checkAndActivateRelays function. Then it should be happy.
The DataReceived event is raised on a secondary thread when data is received from the SerialPort object. Because this event is raised on a secondary thread, and not the main thread, attempting to modify some elements in the main thread, such as UI elements, could raise a threading exception. If it is necessary to modify elements in the main Form or Control, post change requests back using Invoke, which will do the work on the proper thread.

Categories