So i have an infinite loop in my Form constructor that will load up a json string from Twitch.tv API. However this application will be ran from the taskbar. So I have set up global hotkey for when the user hits F1 a messagebox should show up. The problem I'm facing is trying to detect a keypress while in the loop. Th reason I need to loop is i need to know when the twitch streamer has gone online or offline. I tried doing a windows service so I don't have to do the infinite loop but I can't detect key presses in a service. I know the best course of action is to get rid of the loop but I'm not sure how. Here is the code.
private KeyHandler ghk;
private System.Timers.Timer liveTime = new System.Timers.Timer();
UserInfo info;
public Form1()
{
InitializeComponent();
ghk = new KeyHandler(Keys.F1, this);
ghk.Register();
while (true)
{
using (var webClient = new System.Net.WebClient())
{
var json = webClient.DownloadString("https://api.twitch.tv/kraken/streams/lirik");
// Now parse with JSON.Net
info = new UserInfo(json);
}
info.streamLive();
if (info.streamLive() && !liveTime.Enabled)
{
liveTime.Start();
}
if (!info.streamLive())
{
liveTime.Stop();
}
}
}
private void HandleHotkey()
{
MessageBox.Show("hello");
}
protected override void WndProc(ref Message m)
{
if (m.Msg == Constants.WM_HOTKEY_MSG_ID)
HandleHotkey();
base.WndProc(ref m);
}
}
Well, the most simple workaround is to run the infinite loop in another thread so it will not block your main thread which will make it possible to recieve and handle events, something like this:
new Thread(delegate() {
while(true)
{
//...
}
}).Start();
Related
I create parallel process and DataTable dtUser have two rows, it should create two browser:
Parallel.ForEach(dtUser.AsEnumerable(), items =>
OpenBrowser(items["user"].ToString(), items["pass"].ToString()));
Lapsoft_OneDriver browser;
public void OpenBrowser(string username, string password)
{
browser = new Lapsoft_OneDriver(Browsers.Chrome);
browser.GoToUrl(link);
browser.FindElementById("txtUserName").SendKeys(username);
browser.FindElementById("txtpassword").SendKeys(password);
}
It create two Chrome process but only first process running line code block:
browser.GoToUrl(link);
browser.FindElementById("txtUserName").SendKeys(username);
browser.FindElementById("txtpassword").SendKeys(password);
The second process only initializes new browser and not do anything.
If I change this line:
browser = new Lapsoft_OneDriver(Browsers.Chrome);
to
var browser = new Lapsoft_OneDriver(Browsers.Chrome);
It's working.
But another method continues to use variable browser to execute other code.
So, I must declare global variable Lapsoft_OneDriver browser out of a function to use in another method use it.
My problem is:
Why using Lapsoft_OneDriver browser; it create two Chrome process but only first process active, it will insert to browser.FindElementById("txtUserName") two values of variable username and second process not do anything?
Updated:
When to change the code, I have any problem.
I will add more code of frmMain_Load:
private void frmMain_Load(object sender, EventArgs e)
{
thread = new LThread();
thread.StartedEvent += new LThread.startDelegate(AllCaseProgram);
numLog = int.Parse(dtSetting.Rows[0]["num_Log"].ToString());
}
int numProcess;
private void AllCaseProgram(object args)
{
try
{
switch (numProcess)
{
case 0:
Parallel.ForEach(dtUser.AsEnumerable(), items => Start(items["user"].ToString(), items["pass"].ToString()));
break;
case 1:
ClickCart();
break;
case 2:
Result();
break;
}
}
catch (Exception ex)
{
if (browser != null)
browser.Cleanup();
numProcess = 0;
AllCaseProgram(null);
}
}
At event of button StartProgram()_Click. I start Thread like: thread.Start();
You said: should be add this function to my program.
public static void Start(string user, string pwd)
{
var test = new frmMain();
test.OpenBrowser(user, pwd);
test.ClickCart();
}
My update question is:
Seem function Start(string user, string pwd) should be change to function AllCaseProgram include all switch case.
And variable numLog in frmMain_Load have values = 3. In function test.ClickCart() I also use this variable but values auto change to 0.
Have any issues with code? Thanks.
And LThread class is:
public class LThread : BackgroundWorker
{
#region Members
public delegate void startDelegate(string ID);
public event startDelegate StartedEvent;
private static int RandNumber(int Low, int High)
{
Random rndNum = new Random(int.Parse(Guid.NewGuid().ToString().Substring(0, 8), System.Globalization.NumberStyles.HexNumber));
int rnd = rndNum.Next(Low, High);
return rnd;
}
protected override void OnDoWork(DoWorkEventArgs e)
{
StartedEvent(RandNumber(100,10000).ToString()); //put whatever parameter suits you or nothing
base.OnDoWork(e);
e.Result = e.Argument;
}
BackgroundWorker bwThread;
// Main thread sets this event to stop worker thread:
public Boolean bwIsRun;
int m_time_delay = 10000;
Delegate m_form_method_run;
Delegate m_form_method_stop;
Form m_type_form;
#endregion
#region Functions
public void Start()
{
try
{
bwIsRun = true;
this.RunWorkerAsync();
}
catch { }
}
public void Stop()
{
try
{
bwIsRun = false;
}
catch { }
}
private void StartToListen(object sender, DoWorkEventArgs e)
{
while (true)
{
Thread.Sleep(m_time_delay);
if (bwIsRun == true)
{
m_type_form.Invoke(m_form_method_run);
}
else
{
BackgroundWorker bwAsync = sender as BackgroundWorker;
if (bwAsync.CancellationPending)
{
e.Cancel = true;
return;
}
break;
}
}
}
#endregion
}
You should encapsulate your state for each test run. That way you'll have a class that has the responsibility the start a browser, execute one or more actions, while keeping all the required state belonging to a single run private for just one instance, while you can have a many instances as you like (if resources permit).
// this is NOT a winform, this is a new and seperate class ...
// don't try to mix this with an WinForm, that will fail
public class BrowserTestRunner
{
// only this Test instances uses this browser
Lapsoft_OneDriver browser;
private void OpenBrowser(string username, string password)
{
browser = new Lapsoft_OneDriver(Browsers.Chrome);
browser.GoToUrl(link);
browser.FindElementById("txtUserName").SendKeys(username);
browser.FindElementById("txtpassword").SendKeys(password);
// you probably want to click on something here
}
// some other test
private void ClickCart()
{
browser.FindElementById("btnCart").Click();
}
// add other actions here
// this starts the test for ONE browser
public static void Start(string user, string pwd)
{
var runner = new BrowserTestRunner();
runner.OpenBrowser(user, pwd);
// wait for stuff, check data, prepare the next steps
// for example
// runner.ClickCart();
// other actons here
}
}
Now you can create as many Test class instances as you like, while each instance of the class manages its own internal state, without interfering with other instances:
Parallel.ForEach(dtUser.AsEnumerable(), items =>
BrowserTestRunner.Start(items["user"].ToString(), items["pass"].ToString()));
If you want to start that from your backgroundworker do:
private void AllCaseProgram(object args)
{
try
{
switch (numProcess)
{
case 0:
Parallel.ForEach(
dtUser.AsEnumerable(),
items => BrowserTestRunner.Start(items["user"].ToString(), items["pass"].ToString()));
break;
case 1:
ClickCart();
break;
case 2:
Result();
break;
}
}
catch (Exception ex)
{
if (browser != null)
browser.Cleanup();
numProcess = 0;
AllCaseProgram(null);
}
}
By all means: don't start the main form again. Just separate your WinForm from the code you use to operate the browser. That does mean that you have to move the code that interacts with the browser to the BrowserTestRunner. Don't try in keeping the logic for your selenium stuff in the WinForm class because that is doomed to fail. As you are already experiencing.
What you got here is sort of a race condition. You got two threads not getting along when handling a single field in the class. Your problem is only that you don't have sufficient space to store all the browser instances you require.
What happens is basically that the first thread enters the method, creates a instance of the chrome browser and stores it in the variable. Then the second thread enters the function and does the same thing. But it also stores the instance in the same variable. Now the first thread continues and goes to a link. But the instance it is working with is already replaced by the second thread. And so on. This may happen with the threads the other way around or the overlapping may happen after more lines where handled. But it is bound to go wrong.
The way to resolve it, is as you noticed to make the variable local by adding a var. This way both threads are working with distinct variables.
Now you said you need the variable in another function. The question is: Do you need both? Do you need only one? Do you need a specific one?
In case you need only one, you just store the variable in the global variable by adding a line like this in your function:
this.browser = browser;
So it would look like this in total:
Lapsoft_OneDriver browser;
public void OpenBrowser(string username, string password)
{
var localBrowser = new Lapsoft_OneDriver(Browsers.Chrome);
localBrowser.GoToUrl(link);
localBrowser.FindElementById("txtUserName").SendKeys(username);
localBrowser.FindElementById("txtpassword").SendKeys(password);
this.browser = localBrowser;
}
I changed the name of the local browser variable, so it gets clearer what variable is used. Do note that either one of the created browsers could end up in the variable.
In case you need a specific one you have to determine if you have the correct one and store the result after this.
If you need both you have to store them in a list. The namespace System.Collections.Concurrent offers lists that can be handled by multiple threads at once.
I'm trying to build a TAPI based phone call system using JulMar's Atapi x86. One of the functions is to pop a specific form on an inbound call. However, whenever the form pops, it comes up incorrect, as shown below (I have tried several forms as a test and they all do the same thing). There is no error, nothing in the output window to suggest what the issue is.
Code:
private void incomingcall(object sender, NewCallEventArgs e)
{
string phonenumber = e.Call.CallerId; //get the phone number of the call
SqlCommand getincoming = new SqlCommand(Querystrings.getincomingquery(), DB);
getincoming.Parameters.AddWithValue("##TELEPHONE", phonenumber);
DataTable results = new DataTable();
try
{
DB.Open();
using (var results = getincoming.ExecuteReader())
{
results.Load(results);
}
}
catch (Exception ex)
{
Inbound ib = new Inbound(phonenumber, null);
ib.Show();
}
finally
{
DB.Close();
}
if (results.Rows.Count == 1)
{
loadcontactrequest(Convert.ToInt32(results.Rows[0].ItemArray[0]), phonenumber);
}
else
{
loadinbound(phonenumber, results);
}
}
I have loaded these forms outside of this function at other points, meaning it is something to do with this function. Does anybody know where I'm going wrong?
EDIT:
private void loadcontactrequest(int ContactID, string phonenumber)
{
ContactRequest cr = new ContactRequest(ContactID, Global.loginbound("Single customer found", phonenumber));
cr.Show();
}
These functions have been tested elsewhere and work correctly individually, I believe it might be TAPI related.
EDIT 2 - Delegate:
public static void inittapi()
{
if (TestOptions.notapi)
return;
tapi = new TapiManager("Omitted");
tapi.Initialize();
foreach (TapiLine ad in tapi.Lines) //Get all lines available to this PC
{
if (ad.Name.ToUpper().Contains("Omitted"))
{
phoneline = ad;
phoneline.Open(MediaModes.All); //Open the phone line for making and receiving calls
phoneline.NewCall += new EventHandler<NewCallEventArgs>(new TAPI().incomingcall); //Add the incoming call event handler
}
}
}
It's possible that this event is triggered on a different thread than the UI thread of your application.
Modify the method like this to test whether this is the problem:
private void incomingcall(object sender, NewCallEventArgs e)
{
Form form;
if(Application.OpenForms.Count > 0)
{
form = Application.OpenForms[0];
}
if (form != null && form.InvokeRequired)
{
form.BeginInvoke(new Action(() => { incomingcall(sender, e); }));
return;
}
// Your current code goes here
}
This will identify that we are in a different thread than your main form (form) was created on and then execute the function again on the main form's thread.
i created simple application to send and receive message using C# and GsmComm Library. if there is a new incoming message my application will show messageBox that new message arrived. my problem is when i have many new message, messageBox will show so many messageBox notication. How can I just show the last single message box using code?
this mycode:
private void comm_MessageReceived(object sender, MessageReceivedEventArgs e)
{
var obj = e.IndicationObject;
if (obj is MemoryLocation)
{
var loc = (MemoryLocation)obj;
var msg = string.Format("New message received in storage \"{0}\", index {1}.",
loc.Storage, loc.Index);
MessageBox.Show(msg);
return;
}
}
i confuse to fix this, i tried to another way using form to show new incoming notif form1.showDialog(); but same problem first form show cannot be closed when new form opened. this my reference: https://stackoverflow.com/a/13445167/3319555
I really thanks if anyone can help me..thanks
If you're using your second solution of displaying a form with form.ShowDialog() you can store the forms in a list. Then, when a new form needs to be displayed, you can iterate through the list and close each open form with form.Close(). Assuming that your comm_MessageReceieved method is run on another thread, which I assume is driven via an IO completion port, then something like this perhaps?
List<MyForm> formList = new List<MyForm>();
readonly object formListLock = new object();
private void comm_MessageReceived(object sender, MessageReceivedEventArgs e)
{
/// you need to lock the List for thread safe access
lock (formListLock)
{
/// iterate over a copy of the list to avoid mutating the list under iteration
foreach (MyForm form in formList.ToList())
{
form.ThreadSafeClose();
}
}
string msg = "message";
using (MyForm form = new MyForm(msg))
{
lock (formListLock) { formList.Add(form); }
form.ShowDialog();
lock (formListLock) { formList.Remove(form); }
}
}
This was just off the top of my head but might be another possible direction you could take.
You will have to make a thread safe call to form.Close() so that it is run on the form's UI thread. Read about invoke here. There's a lot of information on SO about this topic. This could be as simple as adding something like the following method to your form class:
public void ThreadSafeClose()
{
if (this.InvokeRequired)
{
this.Invoke(new Action(Close)); /// or BeginInvoke...
}
else
{
Close();
}
}
Read more about Lists here: https://msdn.microsoft.com/en-us/library/6sh2ey19%28v=vs.110%29.aspx
Read more about the lock statement here: https://msdn.microsoft.com/en-us/library/c5kehkcz.aspx
Read more about thread synchronisation here: https://msdn.microsoft.com/en-us/library/ms173179.aspx
There are also numerous thread-safe collections that could possibly suit your needs, e.g. ConcurrentBag.
I am having an odd problem with protecting a section of code. My application is a tray app. I create a NotifyIcon inside my class (ApplicationContext). I have assigned a balloon click handler and a double click handler to the NotifyIcon object. there is also a context menu but I am not showing all code. Only important pieces.
public class SysTrayApplicationContext: ApplicationContext
{
private NotifyIcon notifyIcon;
private MainForm afDashBoardForm;
public SysTrayApplicationContext()
{
this.notifyIcon = new NotifyIcon();
this.notifyIcon.BalloonTipClicked += notifyIcon_BalloonTipClicked;
this.notifyIcon.MouseDoubleClick += notifyIcon_MouseDoubleClick;
// ... more code
}
Both handlers launch or create/show my form:
private void notifyIcon_MouseDoubleClick(object sender, MouseEventArgs e)
{
if (e.Button == MouseButtons.Left)
{
openDashboard();
}
}
private void notifyIcon_BalloonTipClicked(object sender, EventArgs e)
{
openDashboard();
}
private void openDashboard()
{
if (dashBoardForm != null)
{
log.Debug("Dashboard form created already, so Activate it");
dashBoardForm.Activate();
}
else
{
log.Debug("Dashboard form does not exist, create it");
dashBoardForm = new MainForm();
dashBoardForm.Show();
}
}
There is a problem with the above code. Maybe more than 1. Issue: it is possible to display 2 dashboard forms which is not what I want. If user double clicks on tray icon while balloon message is displaying causes a race condition in openDashboard. I can reproduce this easily. So I added a lock around the code in openDashboard code and, to my surprise, that did NOT prevent 2 dashboard forms from displaying. I should not be able to create 2 MainForms. Where am I going wrong here?
here is the updated code with lock statement:
private void openDashboard()
{
lock (dashBoardFormlocker)
{
if (dashBoardForm != null)
{
log.Debug("Dashboard form created already, so Activate it");
dashBoardForm.Activate();
}
else
{
log.Debug("Dashboard form does not exist, create it");
dashBoardForm = new MainForm();
dashBoardForm.Show();
}
}
}
Note: lock object was added to the class and initialized in constructor.
private object dashBoardFormlocker;
UPDATE: Showing more code. this is how code gets started :
static void Main()
{
if (SingleInstance.Start())
{
Application.EnableVisualStyles();
Application.SetCompatibleTextRenderingDefault(false);
XmlConfigurator.Configure();
// For a system tray application we don't want to create
// a form, we instead create a new ApplicationContext. The Run method takes
Application.Run(new SysTrayApplicationContext());
SingleInstance.Stop();
SingleInstance.Dispose();
}
}
}
UPDATE 2: Provide more code for clarity
public partial class MainForm : Form
{
public MainForm()
{
log.Trace("MainForm constructor...");
InitializeComponent();
// ... code not shown
this.label_OSVersion.Text = getOSFriendlyName();
// .. more code
}
private string getOSFriendlyName()
{
try
{
string result = string.Empty;
var mgmtObj = (from x in new ManagementObjectSearcher("SELECT Caption FROM Win32_OperatingSystem").Get().OfType<ManagementObject>()
select x.GetPropertyValue("Caption")).FirstOrDefault();
result = mgmtObj != null ? mgmtObj.ToString() : string.Empty;
OperatingSystem os = Environment.OSVersion;
String sp = os.ServicePack ?? string.Empty;
return !string.IsNullOrWhiteSpace(result) ? result + sp : "Unknown";
}
catch (System.Exception ex)
{
log.Error("Error trying to get the OS version", ex);
return "Unknown";
}
}
}
The main UI thread must always pump a message loop to support communication from COM components.
So when you do a blocking operation from the UI thread like locking or joining a thread, (EDIT: edited based on Peter Duniho's fix) the UI thread will enter an 'alertable' state, allowing COM to dispatch certain type of messages, which in turn can cause re-entrancy issues like in your scenario.
Look at the answer to this question (Why did entering a lock on a UI thread trigger an OnPaint event?) for a much more accurate explanation.
Looking at the source code of ManagementObjectSearcher.Get there is a lock (inside Initialize), and since you call it from the constructor of your form, it may lead to the second event triggering while the form's constructor has not finished. The assignment to the dashBoardFormlocker variable only happens after the constructor finishes, so that would explain why it was null on the second entry.
The moral of the story is never do blocking operations on the UI thread.
Without a good, minimal, complete code example that reliably reproduces the problem, it's impossible to know for sure what the problem is. But the guess by answerer tzachs seems reasonable. If so, you can fix your problem by changing your method to look like this:
private bool _dashboardOpen;
private void openDashboard()
{
if (_dashboardOpen)
{
if (dashBoardForm != null)
{
log.Debug("Dashboard form created already, so Activate it");
dashBoardForm.Activate();
}
}
else
{
log.Debug("Dashboard form does not exist, create it");
_dashboardOpen = true;
dashBoardForm = new MainForm();
dashBoardForm.Show();
}
}
In that way, any re-entrant attempt to open the window will be detected. Note that you still need the check for null before actually activating; you can't activate a window that hasn't actually finished being created yet. The subsequent call to Show() will take care of activation anyway, so ignoring the activation in the re-entrant case shouldn't matter.
I have two forms.
One of them is the main form (let's call it MainForm)
the other one is for showing some warning (let's call it dialogForm)
. dialogForm has a label in it. When i click a button in MainForm, dialogForm opens.
But label in dialogForm is blank. It doesn't have time to load actually. I want to check if the dialogForm fully loaded then proccess can continue in MainForm.
For example:
dialogForm tempFrm = new dialogForm();
tempFrm.Show(); // I want to wait till the dialogForm is fully loaded. Then continue to "while" loop.
while(..)
{
...
}
Why not create a boolean value, and a method to access it..
private bool Ready = false;
public ConstructorMethod()
{
// Constructor code etc.
Ready = true;
}
public bool isReady()
{
return Ready;
}
you can try the following
private bool Is_Form_Loaded_Already(string FormName)
{
foreach (Form form_loaded in Application.OpenForms)
{
if (form_loaded.Text.IndexOf(FormName) >= 0)
{
return true;
}
}
return false;
}
you can also look in this
Notification when my form is fully loaded in C# (.Net Compact Framework)?
So you need to consume the forms Shown event:
tempFrm.Shown += (s, e) =>
{
while(..)
{
}
}
But you're going to have another problem. It's going to block the thread. You need to run this while loop on another thread by leveraging a BackgroundWorker or Thread.
Your while(...) blocks the UI thread so child form will never got messages and will not be loaded.
To achive you goal you should subscribe to the Load event and continue your code in the handler.
void Click()
{
var tempFrm = new dialogForm();
tempFrm.Load += frmLoad;
tempFrm.Show();
}
void frmLoad(object s, EventArgs ea)
{
// form loaded continue your code here!
}
You can use Form.IsActive property.
Or just;
public bool IsFormLoaded;
public MyForm()
{
InitializeComponent();
Load += new System.EventHandler(FormLoaded);
}
private void FormLoaded(object sender, EventArgs e)
{
IsFormLoaded = true;
}
and check if YourForm.IsFormLoaded, true or false