My goal:
User should be able to get the html source of a page, when clicked on a button. This event opens a new form with a geckoWebBrowser component and navigates to a given url.
Once this is done it fires a documentCompleted event.
Then I can start loading the DOM.
The problem:
While the loading of the page needs time, I have to wait untill in the second form class the DOM (or just the value of a div) is loaded. This is exactly the problem! Every wait or loop stucks the second form (geckoBrowser) so I can't get the value.
This is the code of the first form:
using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Data;
using System.Drawing;
using System.Linq;
using System.Text;
using System.Windows.Forms;
using System.Threading;
namespace mozillaFirefox2
{
public partial class Form1 : Form
{
string source = "";
public Form1()
{
InitializeComponent();
}
private void button1_Click(object sender, EventArgs e)
{
//BackgroundWorker bw = new BackgroundWorker();
//bw.DoWork += new DoWorkEventHandler(bw_DoWork);
Browser br = new Browser();
//object parameters = br;
//bw.RunWorkerAsync(parameters);
br.navigate("http://www.google.com/#hl=en&q=superman&aq=f&aqi=&oq=&fp=1&cad=b");
Thread th = new Thread(delegate() { this.dw(br); });
th.Start();
th.Join(2000);
richTextBox1.AppendText(br.GetSource + "\n");
}
private void dw(Browser br)
{
while (br.workDone == false)
{
//donothing
}
source = br.GetSource;
}
//void bw_DoWork(object sender, DoWorkEventArgs e)
//{
// Browser br = (Browser)e.Argument;
// while (br.workDone == false)
// {
// //donothing
// }
// richTextBox1.AppendText(br.GetSource + "\n");
//}
}
}
This is the second:
using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Data;
using System.Drawing;
using System.Linq;
using System.Text;
using System.Windows.Forms;
using System.Threading;
namespace mozillaFirefox2
{
//Declare a delegate who points to a function / signature
public delegate void GotDataEventHandler(object sender, EventArgs e);
class Browser : Form
{
public event GotDataEventHandler GotData;
private System.ComponentModel.IContainer components = null;
protected override void Dispose(bool disposing)
{
if (disposing && (components != null))
{
components.Dispose();
}
base.Dispose(disposing);
}
#region Windows Form Designer generated code
/// <summary>
/// Required method for Designer support - do not modify
/// the contents of this method with the code editor.
/// </summary>
private void InitializeComponent()
{
this.geckoWebBrowser1 = new Skybound.Gecko.GeckoWebBrowser();
this.SuspendLayout();
//
// geckoWebBrowser1
//
this.geckoWebBrowser1.Location = new System.Drawing.Point(14, 4);
this.geckoWebBrowser1.Name = "geckoWebBrowser1";
this.geckoWebBrowser1.Size = new System.Drawing.Size(261, 67);
this.geckoWebBrowser1.TabIndex = 2;
this.geckoWebBrowser1.DocumentCompleted += new EventHandler(geckoWebBrowser1_DocumentCompleted);
//Never forget this. Otherwise this error is raised "Cannot call Navigate() before the window handle is created"
this.geckoWebBrowser1.CreateControl();
//
// Form1
//
this.AutoScaleDimensions = new System.Drawing.SizeF(6F, 13F);
this.AutoScaleMode = System.Windows.Forms.AutoScaleMode.Font;
this.ClientSize = new System.Drawing.Size(788, 577);
this.Controls.Add(this.geckoWebBrowser1);
this.Name = "Form1";
this.Text = "Form1";
this.ResumeLayout(false);
}
#endregion
public Skybound.Gecko.GeckoWebBrowser geckoWebBrowser1;
public string source = "";
public bool workDone = false;
public bool navCall = false;
[STAThread]
public Browser()
{
Skybound.Gecko.Xpcom.Initialize(#"C:\Program Files\Mozilla-Gecko ActiveX WebBrowserControl\xulrunner-1.9.1.2.en-US.win32\xulrunner");
Skybound.Gecko.GeckoPreferences.User["general.useragent.override"] = "Mozilla/5.0 (Windows; U; Windows NT 6.0; nl; rv:1.9.1.5) Gecko/20091102 Firefox/3.5.5 (.NET CLR 3.5.30729)";
this.InitializeComponent();
this.Show();
}
public delegate void NavigationHandler(string url);
public void navigate(string url)
{
if (this.InvokeRequired)
{
object[] parameters = { url };
this.geckoWebBrowser1.Invoke(new NavigationHandler(navigate), parameters);
}
else
{
navCall = true;
this.geckoWebBrowser1.Navigate(url);
}
}
public string GetSource
{
get { return source; }
}
public string getSource()
{
return source;
}
public void geckoWebBrowser1_DocumentCompleted(object sender, EventArgs e)
{
if(navCall == true){
source = this.geckoWebBrowser1.Document.GetElementById("res").InnerHtml.ToString();
workDone = true;
navCall = false;
//
GotDataEventHandler gd;
lock (this)
{
gd = GotData;
}
if (gd != null)gd(this, EventArgs.Empty);
}
}
}
}
Now I should be able to just wait till I get an answer within the button1_Click function or catch the event within this function or a function on the second form so I can return this (than the code will wait by itself). Don't know how to do this.
The basic idea would be to split the function into two parts, putting the code that should be run after the event occurs in an anonymous delegate:
Instead of
void doStuff() {
firstPart();
waitEvent();
secondPart();
}
you do
void doStuff() {
firstPart();
some.Event += delegate {
secondPart();
}
}
The only alternative I know of is to put the whole processing into a second thread and use Monitor.Wait and Monitor.Pulse to tell the second thread when it may continue. This will create more overhead and will be generally slower, though. It might be a good idea, if you need to wait for 10 events and continue when the last one arrives (and you don't know the order), but not in that simple case you outline above.
Related
I have this strange situation where I'm trying to call a control's method from
a thread. It's very simple code but for some reason, the call never gets executed if InvokeRequired is true. It hangs forever with no exception thrown.
Below is a simplified version and it also hangs once the control is added to a parent form. If it was not added to a form InvokeRequired is false and it works fine.
MyControl _ctl;
Thread _thread;
void Main()
{
_ctl = new MyControl
{
Dock = DockStyle.Fill
};
Form frm = new Form();
frm.Controls.Add(_ctl);
frm.Show();
(_thread = new Thread(Test)).Start();
//Thread.Sleep(2000);
_thread.Join();
}
void Test()
{
_ctl.Write("Hello...!");
}
class MyControl : TextBox
{
int _inputStart = -1;
public void Write(string value)
{
Action act = () =>
{
AppendText(value);
_inputStart = SelectionStart;
};
if (InvokeRequired)
Invoke(act, Array.Empty<object>()); // this is where line execution hangs
else
act();
}
}
I'd like to understand why does it hang? and what is the correct way to implement such scenario?
EDIT:
OK, I made another sample code to run as a WinForm small app in response to comments of #Sami Kuhmonen
using System;
using System.Drawing;
using System.Threading;
using System.Windows.Forms;
namespace testThreads
{
public class Form1 : Form
{
/// <summary>
/// Required designer variable.
/// </summary>
private System.ComponentModel.IContainer components;
private MyControl _ctl;
private Button _button;
private Thread _thread;
public Form1()
{
InitializeComponent();
}
protected override void Dispose(bool disposing)
{
if (disposing)
{
components?.Dispose();
components = null;
}
base.Dispose(disposing);
}
private void TestThreading()
{
(_thread = new Thread(Test)).Start();
_thread.Join();
}
void Test()
{
_ctl.Write("Hello...!");
}
private void InitializeComponent()
{
SuspendLayout();
//
// _ctl
//
_ctl = new MyControl
{
Multiline = true,
Location = Point.Empty,
Size = new Size(300, 175)
};
Controls.Add(_ctl);
//
// _button
//
_button = new Button
{
Location = new Point(0, 177),
Size = new Size(60, 21),
Text = "Test"
};
Controls.Add(_button);
_button.Click += (s, e) => TestThreading();
//
// Form1
//
ClientSize = new Size(300, 200);
Name = "Form1";
Text = "Form1";
ResumeLayout(false);
}
}
class MyControl : TextBox
{
int _inputStart = -1;
public void Write(string value)
{
Action act = () =>
{
AppendText(value);
_inputStart = SelectionStart;
};
if (InvokeRequired)
Invoke(act, Array.Empty<object>());
else
act();
}
}
}
Now, if I don't use _thread.Join(); the code executes fine. But in my real scenario, I'm not using Thread.Join but waiting for a WaitHandle and it goes like this:
using System;
using System.Drawing;
using System.Threading;
using System.Windows.Forms;
namespace testThreads
{
public class Form1 : Form
{
/// <summary>
/// Required designer variable.
/// </summary>
private System.ComponentModel.IContainer components;
private MyControl _ctl;
private Button _button;
private Thread _thread;
private readonly AutoResetEvent _evt = new AutoResetEvent(false);
public Form1()
{
InitializeComponent();
}
protected override void Dispose(bool disposing)
{
if (disposing)
{
components?.Dispose();
components = null;
}
base.Dispose(disposing);
}
private void TestThreading()
{
(_thread = new Thread(Test)).Start();
_evt.WaitOne();
}
void Test()
{
_ctl.Write("Hello...!");
// simulate a long running task
Thread.Sleep(2000);
_ctl.Write("Done");
_evt.Set();
}
private void InitializeComponent()
{
SuspendLayout();
//
// _ctl
//
_ctl = new MyControl
{
Multiline = true,
Location = Point.Empty,
Size = new Size(300, 175)
};
Controls.Add(_ctl);
//
// _button
//
_button = new Button
{
Location = new Point(0, 177),
Size = new Size(60, 21),
Text = "Test"
};
Controls.Add(_button);
_button.Click += (s, e) => TestThreading();
//
// Form1
//
ClientSize = new Size(300, 200);
Name = "Form1";
Text = "Form1";
ResumeLayout(false);
}
}
class MyControl : TextBox
{
int _inputStart = -1;
public void Write(string value)
{
Action act = () =>
{
AppendText(value);
_inputStart = SelectionStart;
};
if (InvokeRequired)
Invoke(act, Array.Empty<object>());// hangs
else
act();
}
}
}
Of course, this is a sample and it won't be a single thread.
So I guess I need to block and wait until execution finishes while updating the control's UI. Or if there might be another non-blocking approach..
I am creating a C# VSTO addin and am having trouble with setting the owner window parameter in Form.ShowDialog() when the form is shown in a secondary thread and the owner window is on the main thread.
When using VSTO, Excel only supports changes to the Excel object model on the main thread (it can be done on a separate thread but is dangerous and will throw COM exceptions if Excel is busy). I would like to show a progress form while executing a long operation. To make the progress form fluid, I show the form on a separate thread and update the progress asynchronously from the main thread using Control.BeginInvoke(). This all works fine, but I seem to only be able to show the form using Form.ShowDialog() with no parameters. If I pass an IWin32Window or NativeWindow as a parameter to ShowDialog, the form freezes up and does not update the progress. This may be because the owner IWin32Window parameter is a Window that exists on the main thread and not the secondary thread that the progress form is displayed on.
Is there any trick I can try to pass a IWin32Window to the ShowDialog function when the form is on a separate thread. Technically I don't need to set the form's owner, but rather the form's parent if there is such a difference.
I'd like my dialog to be linked with the Excel Window so that when Excel is minimized or maximized, the dialog will be hidden or shown accordingly.
Please note that I have already tried going the BackgroundWorker route and it was not successful for what I was trying to accomplish.
----Updated with sample code:
Below is a trimmed down version of what I am trying to do and how I am trying to do it. The MainForm is not actually used in my application, as I am trying to use it to represent the Excel Window in a VSTO application.
Program.cs:
using System;
using System.Windows.Forms;
namespace WindowsFormsApplication1
{
static class Program
{
[STAThread]
static void Main()
{
Application.EnableVisualStyles();
Application.SetCompatibleTextRenderingDefault(false);
Application.Run(new MainForm());
}
}
}
MainForm.cs:
using System;
using System.Windows.Forms;
using System.Threading;
namespace WindowsFormsApplication1
{
public partial class MainForm : Form
{
public ManualResetEvent SignalEvent = new ManualResetEvent(false);
private ProgressForm _progressForm;
public volatile bool CancelTask;
public MainForm()
{
InitializeComponent();
this.Name = "MainForm";
var button = new Button();
button.Text = "Run";
button.Click += Button_Click;
button.Dock = DockStyle.Fill;
this.Controls.Add(button);
}
private void Button_Click(object sender, EventArgs e)
{
CancelTask = false;
ShowProgressFormInNewThread();
}
internal void ShowProgressFormInNewThread()
{
var thread = new Thread(new ThreadStart(ShowProgressForm));
thread.Start();
//The main thread will block here until the signal event is set in the ProgressForm_Load.
//this will allow us to do the work load in the main thread (required by VSTO projects that access the Excel object model),
SignalEvent.WaitOne();
SignalEvent.Reset();
ExecuteTask();
}
private void ExecuteTask()
{
for (int i = 1; i <= 100 && !CancelTask; i++)
{
ReportProgress(i);
Thread.Sleep(100);
}
}
private void ReportProgress(int percent)
{
if (CancelTask)
return;
_progressForm.BeginInvoke(new Action(() => _progressForm.UpdateProgress(percent)));
}
private void ShowProgressForm()
{
_progressForm = new ProgressForm(this);
_progressForm.StartPosition = FormStartPosition.CenterParent;
//this works, but I want to pass an owner parameter
_progressForm.ShowDialog();
/*
* This gives an exception:
* An unhandled exception of type 'System.InvalidOperationException' occurred in System.Windows.Forms.dll
* Additional information: Cross-thread operation not valid: Control 'MainForm' accessed from a thread other than the thread it was created on.
*/
//var window = new Win32Window(this);
//_progressForm.ShowDialog(window);
}
}
}
ProgressForm.cs:
using System;
using System.Windows.Forms;
namespace WindowsFormsApplication1
{
public partial class ProgressForm : Form
{
private ProgressBar _progressBar;
private Label _progressLabel;
private MainForm _mainForm;
public ProgressForm(MainForm mainForm)
{
InitializeComponent();
_mainForm = mainForm;
this.Width = 300;
this.Height = 150;
_progressBar = new ProgressBar();
_progressBar.Dock = DockStyle.Top;
_progressLabel = new Label();
_progressLabel.Dock = DockStyle.Bottom;
this.Controls.Add(_progressBar);
this.Controls.Add(_progressLabel);
this.Load += ProgressForm_Load;
this.Closed += ProgressForm_Close;
}
public void UpdateProgress(int percent)
{
if(percent >= 100)
Close();
_progressBar.Value = percent;
_progressLabel.Text = percent + "%";
}
public void ProgressForm_Load(object sender, EventArgs e)
{
_mainForm.SignalEvent.Set();
}
public void ProgressForm_Close(object sender, EventArgs e)
{
_mainForm.CancelTask = true;
}
}
}
Win32Window.cs:
using System;
using System.Windows.Forms;
namespace WindowsFormsApplication1
{
public class Win32Window : IWin32Window
{
private readonly IntPtr _handle;
public Win32Window(IWin32Window window)
{
_handle = window.Handle;
}
IntPtr IWin32Window.Handle
{
get { return _handle; }
}
}
}
Adding another answer because although it can be done this way, it's not the recommended way (e.g. should never have to call Application.DoEvents()).
Use the pinvoke SetWindowLong to set the owner, however doing so then causes DoEvents to be required.
A couple of your requirements don't make sense either. You say you want the dialog to minimize and maximize with the Excel window, but your code is locking up the UI thread, which prevents clicking on the Excel window. Also, you are using ShowDialog. So if the progress dialog was left open after finishing, the user still cannot minimize the Excel window because ShowDialog is used.
public partial class MainForm : UserControl
{
public ManualResetEvent SignalEvent = new ManualResetEvent(false);
private ProgressForm2 _progressForm;
public volatile bool CancelTask;
public MainForm()
{
InitializeComponent();
this.Name = "MainForm";
var button = new Button();
button.Text = "Run";
//button.Click += button1_Click;
button.Dock = DockStyle.Fill;
this.Controls.Add(button);
}
private void button1_Click(object sender, EventArgs e)
{
CancelTask = false;
ShowProgressFormInNewThread();
}
internal void ShowProgressFormInNewThread()
{
var thread = new Thread(new ParameterizedThreadStart(ShowProgressForm));
thread.Start(Globals.ThisAddIn.Application.Hwnd);
//The main thread will block here until the signal event is set in the ProgressForm_Load.
//this will allow us to do the work load in the main thread (required by VSTO projects that access the Excel object model),
SignalEvent.WaitOne();
SignalEvent.Reset();
ExecuteTask();
}
private void ExecuteTask()
{
for (int i = 1; i <= 100 && !CancelTask; i++)
{
ReportProgress(i);
Thread.Sleep(100);
// as soon as the Excel window becomes the owner of the progress dialog
// then DoEvents() is required for the progress bar to update
Application.DoEvents();
}
}
private void ReportProgress(int percent)
{
if (CancelTask)
return;
_progressForm.BeginInvoke(new Action(() => _progressForm.UpdateProgress(percent)));
}
private void ShowProgressForm(Object o)
{
_progressForm = new ProgressForm2(this);
_progressForm.StartPosition = FormStartPosition.CenterParent;
SetWindowLong(_progressForm.Handle, -8, (int) o); // <-- set owner
_progressForm.ShowDialog();
}
[DllImport("user32.dll")]
static extern int SetWindowLong(IntPtr hWnd, int nIndex, int dwNewLong);
}
It's unusual to create winform controls on a non-UI thread. It's better to create the ProgressForm when the button is first clicked, then you don't need the ManualResetEvent.
Have the ProgressForm implement a simple interface (IThreadController) that allows your executing task to update the progress.
The owner of the ProgressForm is IntPtr handle = new IntPtr(Globals.ThisAddIn.Application.Hwnd);, which causes the ProgressForm to minimize and restore with the Excel window.
I don't think you need to use ShowDialog because it will block the UI thread. You can use Show instead.
E.g.
using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Drawing;
using System.Data;
using System.Linq;
using System.Text;
using System.Windows.Forms;
using System.Threading;
using System.Runtime.InteropServices;
namespace ExcelAddIn1 {
public partial class UserControl1 : UserControl {
public UserControl1() {
InitializeComponent();
}
private void button1_Click(object sender, EventArgs e)
{
button1.Enabled = false;
var pf = new ProgressForm();
IntPtr handle = new IntPtr(Globals.ThisAddIn.Application.Hwnd);
pf.Show(new SimpleWindow { Handle = handle });
Thread t = new Thread(o => {
ExecuteTask((IThreadController) o);
});
t.IsBackground = true;
t.Start(pf);
pf.FormClosed += delegate {
button1.Enabled = true;
};
}
private void ExecuteTask(IThreadController tc)
{
for (int i = 1; i <= 100 && !tc.IsStopRequested; i++)
{
Thread.Sleep(100);
tc.SetProgress(i, 100);
}
}
class SimpleWindow : IWin32Window {
public IntPtr Handle { get; set; }
}
}
interface IThreadController {
bool IsStopRequested { get; set; }
void SetProgress(int value, int max);
}
public partial class ProgressForm : Form, IThreadController {
private ProgressBar _progressBar;
private Label _progressLabel;
public ProgressForm() {
//InitializeComponent();
this.Width = 300;
this.Height = 150;
_progressBar = new ProgressBar();
_progressBar.Dock = DockStyle.Top;
_progressLabel = new Label();
_progressLabel.Dock = DockStyle.Bottom;
this.Controls.Add(_progressBar);
this.Controls.Add(_progressLabel);
}
public void UpdateProgress(int percent) {
if (percent >= 100)
Close();
_progressBar.Value = percent;
_progressLabel.Text = percent + "%";
}
protected override void OnClosed(EventArgs e) {
base.OnClosed(e);
IsStopRequested = true;
}
public void SetProgress(int value, int max) {
int percent = (int) Math.Round(100.0 * value / max);
if (InvokeRequired) {
BeginInvoke((Action) delegate {
UpdateProgress(percent);
});
}
else
UpdateProgress(percent);
}
public bool IsStopRequested { get; set; }
}
}
I'm trying to get a simple label value to change from another thread, and already tried 3 different threading mechanisms (Tasks, Thread, Background worker) and am now at a loss why the control won't update.
I have a method in an unrelated class like this:
public static void SetOverlayText(string text, bool fade = false)
{
Thread myThread = new Thread(FadeOverlayText);
OverlayForm.SetLabelText(text);
if (fade)
{
myThread.Start();
}
}
and
private static void FadeOverlayText()
{
OverlayForm.ClearLabelText();
}
My form is a regular windows form and has that method:
public void ClearLabelText()
{
this.Invoke((MethodInvoker)delegate
{
StatusText.Text = "Something should happen"
StatusText.Refresh();
});
}
The method appears to be getting called, but nothing happens.
You should not need Refresh.
This should work:
public void ClearLabelText()
{
if (StatusText.InvokeRequired)
{
this.Invoke((MethodInvoker)delegate
{
StatusText.Text = "Something should happen";
});
}
else
{
StatusText.Text = "Something should happen";
}
}
Are you shure, you use the correct control and at no other point the string is changed, so that it seems not to work? Please check every thing.
Also be sure, that you only call ClearLabelText once in your second thread, becuase after ClearLabelText is finished, the thread is not alive anymore.
This will update your text every second, as long as the application runs:
private static void FadeOverlayText()
{
var uiThread = <<Your UI Thread>>;
while(uiThread.IsAlive)
{
OverlayForm.ClearLabelText();
Thread.Sleep(1000);
}
}
EDIT:
here is a simple example i've made and it works. Additionaly to your StatusText label, I've added button1, which change the text too.
using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Data;
using System.Drawing;
using System.Linq;
using System.Text;
using System.Threading;
using System.Windows.Forms;
namespace ThreadTest2
{
public partial class Form1 : Form
{
Thread mainThread = null;
public Form1()
{
InitializeComponent();
mainThread = Thread.CurrentThread;
Thread myThread = new Thread(FadeOverlayText);
myThread.Start();
}
private void FadeOverlayText()
{
while (mainThread.IsAlive)
{
ClearLabelText();
Thread.Sleep(1000);
}
}
public void ClearLabelText()
{
if (StatusText.InvokeRequired)
{
this.Invoke((MethodInvoker)delegate
{
StatusText.Text = "Something should happen";
});
}
else
{
StatusText.Text = "Something should happen";
}
}
private void button1_Click(object sender, EventArgs e)
{
StatusText.Text = "It works!";
}
}
}
One way to make this work is to use a timer that does
StatusText.Text= yourstring;
Every n milliseconds, and make your thread update the 'yourstring' variable to whatever you want.
I did write a windows service that can connect to a network device using a dll. so everything works fine, but The event handler does not work in win service! here is my code :
My Custom Class Code :
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
namespace MyNewService
{
public class zkemkeeperHandler
{
public event EventHandler OnFinger;
public event EventHandler<VerifyEventArgs> OnVerify;
private System.Diagnostics.EventLog eventLog1 = new System.Diagnostics.EventLog();
public zkemkeeper.CZKEMClass axCZKEM1 = new zkemkeeper.CZKEMClass();
private bool bIsConnected = false;
private int iMachineNumber = 1;
public zkemkeeperHandler()
{
((System.ComponentModel.ISupportInitialize)(this.eventLog1)).BeginInit();
this.eventLog1.Log = "DoDyLog";
this.eventLog1.Source = "DoDyLogSource";
((System.ComponentModel.ISupportInitialize)(this.eventLog1)).EndInit();
eventLog1.WriteEntry("zkemkeeperHandler constructor");
}
public void startService()
{
eventLog1.WriteEntry("start service for (192.168.0.77:4370)");
bIsConnected = axCZKEM1.Connect_Net("192.168.0.77", Convert.ToInt32("4370"));
if (bIsConnected == true)
{
eventLog1.WriteEntry("bIsConnected == true !");
iMachineNumber = 1;
if (axCZKEM1.RegEvent(iMachineNumber, 65535))
{
this.axCZKEM1.OnFinger += new kemkeeper._IZKEMEvents_OnFingerEventHandler(axCZKEM1_OnFinger);
this.axCZKEM1.OnVerify += new zkemkeeper._IZKEMEvents_OnVerifyEventHandler(axCZKEM1_OnVerify);
//This Log Appears in Event Viewer
eventLog1.WriteEntry("Define events (OnFingers and OnVerify) !");
//This Line Fires Event in Service1.cs for testing event handler
Finger(EventArgs.Empty);
}
}
else
{
eventLog1.WriteEntry("Unable to connect the device");
}
}
public void stopService()
{
if (bIsConnected) {axCZKEM1.Disconnect(); bIsConnected = false;}
}
//This method doesn't run :(
private void axCZKEM1_OnFinger()
{
Finger(EventArgs.Empty);
}
//This method doesn't run too :(
private void axCZKEM1_OnVerify(int iUserID)
{
VerifyEventArgs args = new VerifyEventArgs();
args.UserID = iUserID;
Verify(args);
}
public class VerifyEventArgs : EventArgs
{
public int UserID { get; set; }
}
protected virtual void Finger(EventArgs e)
{
EventHandler handler = OnFinger;
if (handler != null)
handler(this, e);
}
protected virtual void Verify(VerifyEventArgs e)
{
EventHandler<VerifyEventArgs> handler = OnVerify;
if (handler != null)
handler(this, e);
}
}
}
My Main Service Class Code :
using System;
using System.Collections;
using System.ComponentModel;
using System.Data;
using System.Diagnostics;
using System.ServiceProcess;
using System.Linq;
using System.Threading;
namespace MyNewService
{
public class Service1 : System.ServiceProcess.ServiceBase
{
private System.Diagnostics.EventLog eventLog1;
private System.ComponentModel.Container components = null;
zkemkeeperHandler zkh;
public Service1()
{
InitializeComponent();
if (!System.Diagnostics.EventLog.SourceExists("DoDyLogSource"))
{
System.Diagnostics.EventLog.CreateEventSource("DoDyLogSource", "DoDyLog");
}
eventLog1.Source = "DoDyLogSource";
eventLog1.Log = "DoDyLog";
eventLog1.WriteEntry("Preparing to start service");
try
{
startZKHandler();
}
catch (Exception ex)
{
eventLog1.WriteEntry(ex.InnerException.Message);
}
}
private void startZKHandler()
{
eventLog1.WriteEntry("creating zkemkeeper handler class");
zkh = new zkemkeeperHandler();
zkh.OnFinger += OnFinger;
zkh.OnVerify += OnVerify;
zkh.startService();
}
private void stopZKHandler()
{
eventLog1.WriteEntry("Disconnecting from device (192.168.0.77)...");
zkh.stopService();
}
private void writeLog2DB(string message)
{
try
{
eventLog1.WriteEntry("writing to database");
DB.DBase.LogTable.AddObject(new LogTable
{
ID = ++DB.IDCounter,
deviceLog = message
});
DB.DBase.SaveChanges();
}
catch (Exception ex)
{
eventLog1.WriteEntry(ex.Message + " - " + ex.InnerException.Message);
}
this.EventLog.Log = "Event Stored in DB.";
}
// The main entry point for the process
static void Main()
{
System.ServiceProcess.ServiceBase[] ServicesToRun;
ServicesToRun = new System.ServiceProcess.ServiceBase[] { new MyNewService.Service1()};
System.ServiceProcess.ServiceBase.Run(ServicesToRun);
}
private void InitializeComponent()
{
this.eventLog1 = new System.Diagnostics.EventLog();
((System.ComponentModel.ISupportInitialize)(this.eventLog1)).BeginInit();
this.eventLog1.Log = "DoDyLog";
this.eventLog1.Source = "DoDyLogSource";
this.ServiceName = "MyNewService";
((System.ComponentModel.ISupportInitialize)(this.eventLog1)).EndInit();
}
protected override void Dispose( bool disposing )
{
if( disposing )
{
if (components != null)
{
components.Dispose();
}
}
base.Dispose( disposing );
}
protected override void OnStart(string[] args)
{
// TODO: Add code here to start your service.
eventLog1.WriteEntry("my service started");
}
protected override void OnStop()
{
// TODO: Add code here to perform any tear-down necessary to stop your service.
eventLog1.WriteEntry("my service stoped");
stopZKHandler();
}
protected override void OnContinue()
{
eventLog1.WriteEntry("my service is continuing in working");
}
private void OnFinger(object sender, EventArgs e)
{
eventLog1.WriteEntry("Finger Event Raised");
}
private void OnVerify(object sender, zkemkeeperHandler.VerifyEventArgs e)
{
eventLog1.WriteEntry("Verify Event Raised");
}
}
}
What is my mistake? please help me!
The Windows Service that I wrote, can raise custom events but cannot raise my dll events!
I know this thread is old, but I had this problem yesterday, and now I have finally found a solution, after many hours wasted.
The problem is that, the COM object must be created from an STA Thread, and then, for the events to be dispatched correctly, the same STA thread (exactly the same) must be pumping COM messages. This can be done calling Application.DoEvents() in a loop or Application.Run().
So here is my working code (it works, even as a non-interactive Windows Service in Vista+, I am using Windows 8.1)
Thread createComAndMessagePumpThread = new Thread(() =>
{
this.Device = new CZKEMClass(); //Here create COM object
Application.Run();
});
createComAndMessagePumpThread.SetApartmentState(ApartmentState.STA);
createComAndMessagePumpThread.Start();
After the device gets created you can register the events from any thread, and they get dispatched by the STA thread, that created the COM object.
In Windows Forms application, this worked without doing this, because the STA main thread run the form calling Application.Run(Form). Application.Run() then dispatch events like COM events and Windows GUI events, so there is no need to to the trick above.
Reviving this question as I've just been dealing with a related one. Apparently, the OP is using some COM STA objects which need an STA thread and a functional message pump loop to operate properly. The Windows Service execution model doesn't have that by default. Visit the linked answer for more details.
You cannot use events in Windows Service. Exists several causes why not but I would like to offer a solution just for zkemkeeper:
ZK released a zkemkeeper.dll as COM object for working with Windows Application. All device events will fired and not raised in your application when you run it as windows service. Try to add a reference System.Windows.Forms to the project and after successfully connect add row:
Application.Run();
I am working on converting a console application into a windowed format and as someone who knows little about it but has had experience with a similar application already in window format in the past I figured it wouldn't be too hard.
So I created a form and added a textbox to it just to get the logging information to start with.
This console app used to run in a single thread, I have since added a second thread to allow the form to run side by side with the console for testing. (it runs fine in a single thread strangely now too).
This is the code I am using to write text to the form except that I am not getting ANYTHING at all on the form.
static Form1 f = new Form1();
delegate void SetTextCallback(string s);
private void SetText(string text)
{
// InvokeRequired required compares the thread ID of the
// calling thread to the thread ID of the creating thread.
// If these threads are different, it returns true.
if (f.textBox1.InvokeRequired)
{
SetTextCallback d = new SetTextCallback(SetText);
f.textBox1.Invoke(d, new object[] { text });
}
else
{
f.textBox1.AppendText(text);
}
}
I can confirm that there is text entering the "text" variable but it is not getting to the form.
Any help would be appreciated.
This is the full file:
using System;
using System.Windows.Forms;
using Chraft.Properties;
using System.IO;
using Chraft.Plugins.Events.Args;
using Chraft.Plugins.Events;
namespace Chraft
{
public class Logger
{
private StreamWriter WriteLog;
private Server Server;
internal Logger(Server server, string file)
{
Server = server;
try
{
WriteLog = new StreamWriter(file, true);
WriteLog.AutoFlush = true;
}
catch
{
WriteLog = null;
}
}
~Logger()
{
try
{
WriteLog.Close();
}
catch
{
}
}
public void Log(LogLevel level, string format, params object[] arguments)
{
Log(level, string.Format(format, arguments));
}
public void Log(LogLevel level, string message)
{
//Event
LoggerEventArgs e = new LoggerEventArgs(this, level, message);
Server.PluginManager.CallEvent(Event.LOGGER_LOG, e);
if (e.EventCanceled) return;
level = e.LogLevel;
message = e.LogMessage;
//End Event
LogToConsole(level, message);
LogToForm(level, message);
LogToFile(level, message);
}
private void LogToConsole(LogLevel level, string message)
{
if ((int)level >= Settings.Default.LogConsoleLevel)
{
Console.WriteLine(Settings.Default.LogConsoleFormat, DateTime.Now, level.ToString().ToUpper(), message);
}
}
static Form1 f = new Form1();
delegate void SetTextCallback(string s);
private void SetText(string text)
{
// InvokeRequired required compares the thread ID of the
// calling thread to the thread ID of the creating thread.
// If these threads are different, it returns true.
if (f.textBox1.InvokeRequired)
{
SetTextCallback d = new SetTextCallback(SetText);
f.textBox1.Invoke(d, new object[] { text });
}
else
{
f.textBox1.AppendText(text);
}
}
private void LogToForm(LogLevel level, string message)
{
if ((int)level >= Settings.Default.LogConsoleLevel)
{
SetText(DateTime.Now + level.ToString().ToUpper() + message);
}
}
private void LogToFile(LogLevel level, string message)
{
if ((int)level >= Settings.Default.LogFileLevel && WriteLog != null)
WriteLog.WriteLine(Settings.Default.LogFileFormat, DateTime.Now, level.ToString().ToUpper(), message);
}
public void Log(Exception ex)
{
//Event
LoggerEventArgs e = new LoggerEventArgs(this, LogLevel.Debug, ex.ToString(), ex);
Server.PluginManager.CallEvent(Event.LOGGER_LOG, e);
if (e.EventCanceled) return;
//End Event
Log(LogLevel.Debug, ex.ToString());
}
public enum LogLevel : int
{
Trivial = -1,
Debug = 0,
Info = 1,
Warning = 2,
Caution = 3,
Notice = 4,
Error = 5,
Fatal = 6
}
}
}
The problem is that you are creating two Form objects. One that is created in your Program.cs file:
Application.Run(new Form1());
And the one you created in your logger class
Form f = new Form1();
The one passed to Application.Run is the one that the user is interacting with. It has become visible and responds to user interaction because of the Application.Run call.
The one you created on your logger class just sits there in memory. Its TextBox is happily adding the text you ask it to, but that one isn't visible anywhere.
There are many ways to handle this situation. You could gain access to the correct Form object through Application.OpenForms, but a more appropriate way to handle it would be to add an event on the logger that the form can subscribe to and it can handle updating the TextBox in response to the event.
Updated
class LoggerLogEventArgs : EventArgs
{
public LoggerLogEventArgs(string message)
{
this.message = message;
}
private string message;
public string Message { get { return message; } }
}
class Logger
{
public event EventHandler<LoggerLogEventArgs> Logged;
protected virtual void OnLogged(LoggerLogEventArgs e)
{
EventHandler<LoggerLogEventArgs> handler = Logged;
if (handler != null)
handler(this, e);
}
// I would change this method name to LogToEvent
private void LogToForm(LogLevel level, string message)
{
if ((int)level >= Settings.Default.LogConsoleLevel)
{
OnLogged(new LoggerLogEventArgs(message));
}
}
}
class Form1 : Form
{
// Subscribe to the logger only when we are ready to display text
protected override void OnHandleCreated(EventArgs e)
{
base.OnHandleCreated(e);
GetLog().Logged += new EventHandler<LoggerLogEventArgs>(logger_Logged);
}
// Unsubscribe from the logger before we are no longer ready to display text
protected override void OnHandleDestroyed(EventArgs e)
{
GetLog().Logged -= new EventHandler<LoggerLogEventArgs>(logger_Logged);
base.OnHandleDestroyed(e);
}
private void logger_Logged(object sender, LoggerLogEventArgs e)
{
if (InvokeRequired)
BeginInvoke(new EventHandler<LoggerLogEventArgs>(logger_Logged), e);
else
textBox1.AppendText(e.Message);
}
}
hello i try this it works ( I make a console application and I add a windows form)
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Security.Permissions;
using System.Windows.Forms;
namespace ConsoleApplication6
{
class Program
{
delegate void SetTextCallback(string s);
static Form1 f;
static void Main(string[] args)
{
f = new Form1();
f.Show();
SetText("test");
Console.ReadLine();
}
private static void SetText(string text)
{
// InvokeRequired required compares the thread ID of the
// calling thread to the thread ID of the creating thread.
// If these threads are different, it returns true.
if (f.textBox1.InvokeRequired)
{
SetTextCallback d = new SetTextCallback(SetText);
f.textBox1.Invoke(d, new object[] { text });
}
else
{
f.textBox1.AppendText(text);
}
}
}
}