I am trying to create a simple Kiosk-style web browser for work. This is supposed to rotate between two internet tabs automatically. There will NEVER be any user input. Static web pages, preassigned, and only for display in our main office. I am using a C#.NET WPF app with a TabControl and a web browser in each tab. I can preassign a web page to each tab, easy stuff. What I cannot do is generate the infinite loop to constantly switch between the two tabs. Any loop whatsoever [while(true), for(;;), do-while(true)] will keep the form from loading at all. The code provided is my attempt at just generating the web pages within the loop first, then I'll go back and add the logic for the auto-switching.
namespace LoopNet
{
public partial class MainWindow : Window
{
public MainWindow()
{
InitializeComponent();
while(true)
{
Calendar1.Navigate("http://www.google.com");
GIS1.Navigate("http://www.YouTube.com");
}
}
}
}
Add a Loaded event handler and stick it in there.
Anything called on the main thread like that will hang the app. As a comment said, use a Timer.
Note: if the Timer.Elapsed method is doing things TO the UI, then it won't like it. The quick solution to this is to use the Dispatcher invoke method.
you'd end up with something along these lines:
public void CreateTimer()
{
var timer = new System.Timers.Timer(1000); // fire every 1 second
timer.Elapsed += HandleTimerElapsed;
}
public void HandleTimerElapsed(object sender, ElapsedEventArgs e)
{
Application.Current.Dispatcher.Invoke(
() =>
{
//do things on UI thread...
SwitchTheTabs();
}
);
Related
A form should open only when there is an event if there is no event it should not display on the screen. So Basically i thought of using a timer to do this. An exe will continously be running and after every minute it checks the db to see if there is data and if there is it shows up on the screen and will only be closed manually with user interaction. After a minute it checks again and displays the form if Data is present in the DB.
I used system.threading.Timer in Program.cs file to open a window after every minute.Below is the code
timer = new System.Threading.Timer((s) => {
EL.CustomMessageBox l = new EL.CustomMessageBox();
l.ShowDialog();
}, null, TimeSpan.Zero, 60000);
After certain time I see that this exe is still running in the taskmanager but even though there is data in the DB it stops showing up on the screen. Any help is appreciated.
System.Threading.Timer runs its callback on a threadpool thread. You should never use a threadpool thread for UI work, because:
They don't run a message dispatch loop.
You don't control when the thread gets recycled. UI windows have thread affinity and if their thread exits all the associated windows go poof immediately (you won't even get WM_DESTROY messages).
A normal Application.Run loop on the main thread, with a hidden main window and a UI timer will serve you much better.
I would pass my own custom ApplicationContext to Application.Run() in program.cs.
This will allow you to have NO INTERFACE until your conditions are met. The application will also continue to run (even when you close the Forms) until you explicitly call Application.Exit().
You can keep a reference to your Form at class level. This will help you decide if you need to work with the existing one, or create a new one.
Note that I'm using the System.Windows.Forms.Timer, not the threaded timer.
Something like...
static class Program
{
/// <summary>
/// The main entry point for the application.
/// </summary>
[STAThread]
static void Main()
{
Application.EnableVisualStyles();
Application.SetCompatibleTextRenderingDefault(false);
Application.Run(new MyContext());
}
}
public class MyContext : ApplicationContext
{
private EL.CustomMessageBox l = null;
private System.Windows.Forms.Timer timer;
public MyContext()
{
timer = new System.Windows.Forms.Timer();
timer.Interval = (int)TimeSpan.FromMinutes(1).TotalMilliseconds;
timer.Tick += Timer_Tick;
timer.Start();
}
private void Timer_Tick(object sender, EventArgs e)
{
bool result = true; // hit the database and get an answer
if (result)
{
if (l == null || l.IsDisposed)
{
// no form has been created yet, or the previous one was closed
// create a new instance
l = new EL.CustomMessageBox();
l.Show();
}
else
{
// if we get in here, then the previous form is still being displayed
// if your form can be minimized, you might need to restore it
// if (l.WindowState == FormWindowState.Minimized)
// {
// restore the window in here?
// }
}
// update the form "l" with some data?
l.xxx = yyy;
}
}
}
I can't help but think that the other answers, massively technically correct as they are, don't actually solve the problem because they probably don't make sense if you aren't aware of how Windows works. Idle_Mind's is closest to what I'd do, though if the forms designer is familiar I'd go for a solution that basically just uses that - as such I present what I would do to solve the task you're faced with:
Have an app with one form (or make this form an autonomous one within another app, but for now maybe do it as a dedicated app for simplicity) - make a new Windows Forms project
Have a Timer (a Windows Forms timer, out of the toolbox, not a System.Threading timer) with an interval of 60000 and Enabled = true
Have a timer Tick event handler on your form (double click the timer in the tray under the form designer to attach an event handler) that queries the DB and finds if there are any messages
If there are new messages, adds them to a listbox or something, and calls this.Show() to show the form
Have an eventhandler attached to the FormClosing event so when the user clicks X, the form hides instead of closes:
private void MyForm_FormClosing(object sender, FormClosingEventArgs e)
{
if (e.CloseReason == CloseReason.UserClosing)
{
e.Cancel = true;
Hide();
}
}
Maybe have the FormClosing event clear the messages listbox. This way if the form opens and the user is on lunch, the messages will build up and build up, then they can read them and clear them by closing the form. Calling Show on an already-visible form does nothing, so the messages will just accumulate into the listbox if more messages come in and the form is already visible
Good quick rule of thumb; never use System.Threading Timer in a Windows Forms app. Use a timer out of the forms designer toolbox instead. Only use a threading timer if you're writing a service or Console app etc. For stability reasons, Windows controls absolutely must be accessed by the thread that originally created the control. Windows forms timer is aware of this and its Tick event can safely access the controls (a form is a control, showing it requires to access it) in a Forms app
You should call Invoke to execute your delegate on the thread that owns the control's underlying window handle.
Something like this should work:
timer = new System.Threading.Timer((s) => {
EL.CustomMessageBox l = new EL.CustomMessageBox();
l.Invoke((Action) () =>
{
l.ShowDialog();
});
}, null, TimeSpan.Zero, 60000);
Or even better, use this extension method:
public static void InvokeIfRequired(this Control c, MethodInvoker action)
{
if (c.InvokeRequired)
{
c.Invoke(action);
}
else
{
action();
}
}
And call it like this:
l.InvokeIfRequired(() => { l.ShowDialog(); });
Further information can be found at: https://learn.microsoft.com/en-us/dotnet/desktop/winforms/controls/how-to-make-thread-safe-calls-to-windows-forms-controls?view=netframeworkdesktop-4.8
I'm just trying to learn this thing and in future, wanted to use it in one of my projects.
I have a small Form with a simple Text box, stored in a .Net dll (C#). And here is my class in this dll which contains methods to interact with this Form:
using System;
using System.Collections.Generic;
using System.Text;
namespace ClassLibrary1
{
public class Class1
{
static Form1 dlg = new Form1();
public static void ShowForm()
{
dlg.ShowIcon = true;
dlg.Show();
}
public static void SetText(string MyText)
{
dlg.Text = "Form Text ";
dlg.SetText(MyText);
}
}
}
Successfully loaded this form by referencing this dll into another C# application while calling its method i.e.:
private void button1_Click(object sender, EventArgs e)
{
ClassLibrary1.Class1.ShowForm();
}
And I was able to interact with the form perfectly.
Now loading same in Powershell using:
[Reflection.Assembly]::LoadFile("D:\Playing\ClassLibrary1\ClassLibrary1\bin\Debug\ClassLibrary1.dll")
[ClassLibrary1.Class1]::ShowForm()
Now this is loaded successfully at its default position, but I can't interact with this form i.e. I can't type in its Text Box, neither I can move or even close this form by clicking on its Close (x) button on right corner. Whenever I put my mouse on it, it becomes a HourGlass i.e. waiting for some process .
To verify if form is not hanged, I called SetText at Powershell prompt:
[ClassLibrary1.Class1]::SetText("String from Powershell")
and it worked fine. TextBox received this text properly, but still I can't interact with the form with my mouse.
I feel, I have to manually set its Window Handler i.e. System.Windows.Forms.IWin32Window.
But I don't know which Handler and how to achieve this?
Please guide .... Would really appreciate for any alternative tricks.
You can't show a form from PowerShell using Form.Show() method because it needs a message pump (and it's not provided by PowerShell host process).
Here what you can do to solve this issue:
Use Form.ShowDialog() or Application.Run(), your form will have its own message pump.
It'll be modal then you need to run it in another thread. I suggest to use a background thread and BeginInvoke() in your SetText() method.
Here code to do that (I won't change your code too much so I'll keep it as a singleton instance even if this prevents to display form multiple times). Code is just an example (I wouldn't suggest to use Thread Pool for this task) to illustrate the procedure.
public static void ShowForm()
{
if (dlg != null)
dlg.BeginInvoke(new MethodInvoker(delegate { dlg.Dispose(); }));
ThreadPool.QueueUserWorkItem(delegate(object state)
{
Application.Run(_dlg = new Form1());
});
}
public static void SetText(string text)
{
_dlg.BeginInvoke(new MethodInvoker(delegate { dlg.SetText(text); }));
}
In this way Form1 will be modal in another thread (with its own message pump) and your calling PowerShell thread won't be stopped. Communication between them is still possible via message dispatching (Invoke()/BeginInvoke()).
Please note that SetText() is now asynchronous, to make it synchronous just replace BeginInvoke() with Invoke().
I have one main windows form and within that form I have custom controls that represents different screens in application. I want to access this control's child controls. There's something I'm not getting here...sometimes I get this error:
Cross-thread operation not valid:
Control 'lblText' accessed from a thread
other than the thread it was created on.
but sometimes everything works OK. I don't completelly understand why the error...probably something with external device (MEI BillAcceptor) which has an event (inside Form1 class) that does the changes to the control... so let me write a simple code...
//user control
public partial class Screen2 : UserControl
{
public void changeValue(string txt)
{
lblText.Text = txt;
}
}
and the method changeValue is called from a form1 when particular event is rised...
public partial class Form1 : Form
{
public Form1()
{
InitializeComponent();
BillAcceptor.SomeBillAcceptorEvent +=
new SomeBillAcceptorEventHandler(changeText);
}
private void changeText(object sender, EventArgs args)
{
_screen2.changeValue("some text");
}
}
So the most annoying thing is that sometimes everything actually works... So my question is "do I have to use Invoke here?" or how do I solve this with less changes to the application...
In your handler. do something like this.
if (this.InvokeRequired)
{
Invoke(new MethodInvoker(() =>
{
_screen2.changeValue("some text");
}));
}
else
{
_screen2.changeValue("some text");
}
I would guess that the event is being raised on a seperate thread other that the main UI thread.
Yes you need to use Invoke if there is a possibility of that method being called from a different thread.
You can check this.InvokeRequired(), if true, then use invoke, if false do a normal call.
This occurs due to thread unsafe call
You should make only thread safe calls in program
Check this link.
The short answer is yes, you must use Invoke. See this question and its accepted answer if you need details.
The reason the exception is only thrown some of the time, by the way, comes down to timing. You currently have a race condition in which sometimes you get lucky and sometimes you don't.
By the way, here is pretty handy pattern for this sort of thing.
Refactor any code that sets form values into its own private void method(s).
In this new method, call InvokeRequired. If it returns true, call Invoke, passing the current method so as to recurse back into it. If it returns false, go ahead and make the change.
Call this new method from the event handler.
For example:
private void ChangeScreen2() {
if (this.InvokeRequired) {
this.Invoke(new MethodInvoker(ChangeScreen2));
}
else {
_screen2.changeValue("some text");
}
}
private void changeText(object sender, EventArgs args)
{
ChangeScreen2();
}
The idea being that you sequester all code that modifies the form into these methods that always begin with a check of InvokeRequired and always Invoke themselves if so required. This pattern works with .NET 1.0 onward. For even neater approach, see the accepted answer to this question, which works with .NET 3.0 and later.
Forgive me if this is a bit garbled, I'm a bit new on Windows Forms, having spent months in ASP.NET
Basically, I am using Quartz.NET in my Windows Form application - when a job is executed, it fires another class file - the parameters it passes in do not contain a reference to the form, and I don't think I can change this.
What I want to do is refresh a grid on the page after the job executes - and the only place that 'tells' me a job has been executed are in other files, rather than the forms code. I can't figure out a way of accessing methods/objects on the form without starting a new instance of it, which I don't want to do.
EDIT: To sum up, I just want a way to sent a message or something to the already open Main form from another class
Why not raise event from your class to winform. Thats the elegant way to do this. To do send message, you can use interop to call sendMessage which requires handle of the window
Actualy, if members of a class were not static, you wont be able to access them without an instance of that class. Try to accuire the same instance of the class that your actions are applied on it.
The easiest way is to pass the instance of the main form to the class consuming the Quartz.NET event, so that the consuming class can then call methods on the main form. I'm guessing that class would be created in the main form somewhere anyway, so it would be something like:
var quartzConsumer = new QuartzConsumer(this);
...
class QuartzConsumer {
MainForm _form;
public QuartzConsumer(MainForm form) {
_form = form;
...
}
void OnTimer(..) {
_form.UpdateGrid();
}
}
EDIT as #hundryMind says, another solution is for the main form to subscribe to an event on the consuming class:
class QuartzConsumer {
public delegate void DataChangedEventHandler();
public event DataChangedEventHandler DataChanged;
void OnTimer(..) {
if (this.DataChanged != null) this.DataChanged();
}
}
// in MainForm:
var quartzConsumer = new QuartzConsumer(..);
quartzConsumer.DataChanged += this.OnDataChanged;
...
void OnDataChanged() {
// update the grid
}
In my project, whenever a long process in being executed, a small form is displayed with a small animated gif file. I used this.Show() to open the form and this.Close() to close the form.
Following is the code that I use.
public partial class PlzWaitMessage : Form
{
public PlzWaitMessage()
{
InitializeComponent();
}
public void ShowSpalshSceen()
{
this.Show();
Application.DoEvents();
}
public void CloseSpalshScreen()
{
this.Close();
}
}
When the form opens, the image file do not immediately start animating. And when it does animate, the process is usually complete or very near completion which renders the animation useless. Is there a way I can have the gif animate as soon as I load the form?
Why not using threads? It's always good idea to learn something new.
You could simply put your "long process" in background thread, and use events to report to presentation layer, for example:
// in your "long process" class
public event Action<double> ReportCompletition;
// this method will start long process in separate background thread
public void Start()
{
Thread thread = new Thread(this.LongProcess);
thread.IsBackground = true;
thread.Start();
}
private void LongProcess()
{
// do something
// report 10% completition by raising event
this.ReportCompletition(0.1);
// do something more
this.ReportCompletition(0.5);
// ... and so on
}
This way, all you have to do is implement simple method in your Form/UI, which will consume this information.
public partial class MainApplicationWindow : Form
{
private LongProcessClass _longProcess;
public MainApplicationWindow
{
this.InitializeComponent();
this._longProcess = new LongProcessClass();
// bind UI updating method to long process class event
this._longProcess.ReportCompletition += this.DisplayCompletitionInfo;
}
private void DisplayCompletitionInfo(double completition)
{
// check if control you want to display info in needs to be invoked
// - request is coming from different thread
if (control.InvokeRequired)
{
Action<double> updateMethod = this.DisplayCompletitionInfo;
control.Invoke(updateMethod, new object[] { completition });
}
// here you put code to do actual UI updating,
// eg. displaying status message
else
{
int progress = (int) completition * 10;
control.Text = "Please wait. Long process progress: "
+ progress.ToString() + "%";
}
}
Of course, you can report anything you like from within long process. Be it completition rate, ready to display string messages, anything. You can also use events to report that long process has finished, broke, or any long process data you wish.
For more detailed information on this topic you might want to check MSDN tutorials on Threading and Events.
You should do the "long process" in a separate thread. I advice using the BackgroundWorker.
This will make your code more difficult though. One of the main reasons is, that you cannot communicate with the UI from the background thread.
Also note the warning from the linked page:
Caution
When using multithreading of
any sort, you potentially expose
yourself to very serious and complex
bugs. Consult the Managed Threading
Best Practices before implementing any
solution that uses multithreading.
If this is too difficult, you could call Application.DoEvents very frequent from your "long process" code.
Whatever you choose, this will make it possible for the user to interact with your form. For instance closing it. You should be aware of this.
Use the gif in a PictureBox, and have it open using Form pWait = new Form(); pWait.Show();