I only want to have a single instance of my Window in WPF.
My code in the window:
public static bool IsOpen { get; private set; }
private void Window_Loaded(object sender, RoutedEventArgs e)
{
IsOpen = true;
}
private void Window_Unloaded(object sender, RoutedEventArgs e)
{
IsOpen = false;
}
My code in the open function
if (MyWindow!= null)
{
if (MyWindowName.IsOpen)
{
MyWindow.Activate();
}
else
{
MyWindow.Close();
MyWindow= null;
}
}
if (MyWindow!= null) return;
MyWindow= new MyWindowName();
MyWindow.Show();
MyWindow.Activate();
But I am able to open many instances of the window if I click fast 3-5 times.
Thanks
There will be a delay between you opening with window with the show command (which will then process the message queue allowing the processing of the other clicks) and then the firing of the windows loaded event.
An extremely quick and dirty fix would be to move the IsOpen assignment to just before the show command, you could also use a return inside the if to remove the need for a second check.
if (MyWindow!= null)
{
if (MyWindowName.IsOpen)
{
MyWindow.Activate();
return;
}
else
{
MyWindow.Close();
MyWindow= null;
}
}
MyWindow= new MyWindowName();
MyWindow.IsOpen = true;
MyWindow.Show();
MyWindow.Activate();
Clearly you're hitting a race condition. It's not that surprising considering you're having to leverage the event model of the Window. Just declare an object in the same class MyWindow is declared in, like this:
private object _lockObj = new object();
and then when doing your work, do this:
lock (_lockObj)
{
if (MyWindow!= null)
{
if (MyWindowName.IsOpen)
{
MyWindow.Activate();
}
else
{
MyWindow.Close();
MyWindow= null;
}
}
if (MyWindow!= null) return;
MyWindow= new MyWindowName();
MyWindow.Show();
MyWindow.Activate();
}
How about this simple code.
Window2 win;
object locker = new Object();
private void OnShow(object sender, RoutedEventArgs e)
{
lock (locker)
{
if (win == null)
win = new Window2();
win.Show();
}
}
private void OnHide(object sender, RoutedEventArgs e)
{
if (win != null)
win.Hide();
}
Related
I have a button on Form1 that opens Form2(Leaderboard) if Form2 is not open. What I am trying to do is: On Button click, if Form2 is closed, open Form2. Else Form2 is open, bring Form2 to front.
With the below code, clicking the button when leaderboardOpen == true; does nothing at all.
public static bool leaderboardOpen = false;
private void leaderButton_Click(object sender, EventArgs e)
{
if (leaderboardOpen == false)
{
Leaderboard leaderboard = new Leaderboard();
leaderboard.Show();
leaderboardOpen = true;
}
else
{
Leaderboard leaderboard = new Leaderboard();
//Tried the below
//leaderboard.Focus();
//leaderboard.BringToFront();
//leaderboard.TopMost = true;
//leaderboard.Activate();
}
}
Instead of a bool, keep a reference to your instance of Leaderboard itself:
private Leaderboard leader = null;
private void leaderButton_Click(object sender, EventArgs e)
{
if (leader == null || leader.IsDisposed)
{
leader = new Leaderboard();
leader.FormClosed += (s2, e2) => { leader = null; };
leader.Show();
}
else
{
if (leader.WindowState == FormWindowState.Minimized)
{
leader.WindowState = FormWindowState.Normal;
}
leader.BringToFront();
}
}
Got it working with the below code.
private void leaderButton_Click(object sender, EventArgs e)
{
var form = Application.OpenForms.OfType<Leaderboard>().FirstOrDefault();
if (form != null)
{
form.Activate();
}
else
{
new Leaderboard().Show();
}
}
Here is a working code for me
public static bool leaderboardOpen = false;
public static Leaderboard? leaderboardForm = null;
private void leaderButton_Click(object sender, EventArgs e)
{
if (!leaderboardOpened || leaderboardForm is null)
{
// Initialize Leaderboard form if it doesn't exist
leaderboardForm = new Leaderboard();
leaderboardForm.Show();
leaderboardOpened = true;
}
else
{
// Focus on the form
leaderboardForm.Focus();
}
}
Make sure to make leaderboardOpened = false; if the Leaderboard form is closed.
This is rather a difficult situation to explain. I have a Windows Forms application that uses a notification icon with a context menu. The app can be configured to start with no form shown or the form can be closed by the user leaving just the notify icon present. Selecting the "Show" option from the context menu strip of the notification icon will create (if closed)/restore(if minimized) the main form. Now this all works fine as the events generated from the menu are running on the STA thread. The problem is the single instance implementation. A notify icon type application really should only ever be running one instance, otherwise you'd have multiple notify icons in the tray and it'd be a huge mess. This is accomplished using a named EventWaitHandle and also includes detection of an attempt to run a second instance, when this happens it signals the main running instance which then restores/creates the main form thus:
public static bool InitSingleInstance(this Control control, string handleName, Action? NewInstanceAttempt = null)
{
EventWaitHandle ewh = new(false, EventResetMode.ManualReset, handleName, out bool isNew);
if (isNew)
{
Task.Run(() =>
{
while (!control.IsDisposed)
{
ewh.WaitOne();
ewh.Reset();
NewInstanceAttempt?.Invoke();
}
});
}
else
{
Task.Run(() =>
{
EventWaitHandle.SignalAndWait(ewh, ewh, 100, true);
}).Wait();
}
return isNew;
}
The issue I have is that the loop waiting for a signal from a second instance runs in another Thread and thus needs to be delegated to the STA thread to restore/create the form. I use a UserControl to contain the NotifyIcon and Menu (So I can edit them in the designer) and this is created in Program.cs in place of Application.Run(new Form()) but this control is never actually shown itself so it never gets a Handle assigned to it that I can Invoke on so I get an exception when the second instance is run.
public partial class NotifyIconAndMenu : UserControl
{
private Form? mainForm = null;
private FormWindowState mainFormState = FormWindowState.Normal;
private readonly Func<Form> NewForm;
public NotifyIconAndMenu(Func<Form> newForm)
{
NewForm = newForm;
if(!this.InitSingleInstance("FOOBFOOB387846", NewInstanceAttempt))
return;
InitializeComponent();
Application.Run();
}
private void ShowMainForm()
{
if (mainForm == null || mainForm.IsDisposed)
{
mainForm = NewForm();
mainForm.Visible = true;
mainForm.Resize += MainForm_Resize;
}
mainForm.WindowState = mainFormState;
}
private void Quit()
{
Dispose();
Application.Exit();
}
private void MainForm_Resize(object? sender, EventArgs e)
{
if (mainForm != null && mainForm.WindowState != FormWindowState.Minimized)
mainFormState = mainForm.WindowState;
}
private void NewInstanceAttempt() => Invoke(ShowMainForm); // << exception
private void IconMenu_ItemClicked(object sender, ToolStripItemClickedEventArgs e)
{
if (e.ClickedItem == MI_Show) ShowMainForm();
else
if (e.ClickedItem == MI_Quit) Quit();
}
}
Perhaps there is a better way to do this? I did think of running a loop in the constructor waiting on a locked object and pulsing it from the other thread. Like this
public NotifyIconAndMenu(Func<Form> newForm)
{
NewForm = newForm;
if (!this.InitSingleInstance("NotIconDemo387846", NewInstanceAttempt))
return;
InitializeComponent();
lock (this)
{
while (!IsDisposed)
{
Monitor.Wait(this);
ShowMainForm();
}
}
}
private void NewInstanceAttempt()
{
lock(this)
{
Monitor.Pulse(this);
}
}
But this seems messy.
EDIT: And indeed wouldn't work as it would lock up the STA thread.
I managed to solve it like this:
public partial class NotifyIconAndMenu : UserControl
{
private Form? mainForm = null;
private FormWindowState mainFormState = FormWindowState.Normal;
private readonly Func<Form> NewForm;
public NotifyIconAndMenu(Func<Form> newForm)
{
NewForm = newForm;
if (!this.InitSingleInstance("GROWPLENTYOFCHEESE4444", NewInstanceAttempt))
return;
InitializeComponent();
FormShowLoop();
Application.Run();
}
private async void FormShowLoop()
{
while (!IsDisposed)
{
await Task.Run(() =>
{
lock (this)
{
Monitor.Wait(this);
}
});
if(!IsDisposed)
ShowMainForm();
}
}
private void ShowMainForm()
{
if (mainForm == null || mainForm.IsDisposed)
{
mainForm = NewForm();
mainForm.Resize += MainForm_Resize;
mainForm.Visible = true;
}
mainForm.WindowState = mainFormState;
}
private void Pulse()
{
lock (this)
{
Monitor.Pulse(this);
}
}
private void Quit()
{
Dispose();
Pulse();
Application.Exit();
}
private void MainForm_Resize(object? sender, EventArgs e)
{
if (mainForm != null && mainForm.WindowState != FormWindowState.Minimized)
mainFormState = mainForm.WindowState;
}
private void NewInstanceAttempt() => Pulse();
private void IconMenu_ItemClicked(object sender, ToolStripItemClickedEventArgs e)
{
if (e.ClickedItem == MI_Show) Pulse();
else
if (e.ClickedItem == MI_Quit) Quit();
}
}
I have a C# form application that communicate with PLC via Kepware OPC Server.
But this communication slowdown my GUI. I use thread for communication but form tabpages still very slow. I am sending a part of my code. Where am I wrong?
public Form1()
{
InitializeComponent();
Connect_Opc_Server("Kepware.KEPServerEX.V5");
ConnectToSqlToRead();
ShowPartType(1);
}
private void timer_Kepware_Tick(object sender, EventArgs e)
{
Thread KepwareThread = new Thread(new ThreadStart(Kepware_Read_Write));
if (KepwareThread.IsAlive)
{ }
else
{
KepwareThread.Start();
}
}
public void Kepware_Read_Write()
{
if (KepwarePLCReadError == false)
{
synch_read();
}
if (KepwarePLCReadOK == true)
{
synch_write();
}
}
You might want to create only 1 thread.
private void timer_Kepware_Tick(object sender, EventArgs e)
{
if (_KepwareThread == null)
{
_KepwareThread = new Thread(...);
}
if (!_KepwareThread.IsAlive)
{
_KewpareThread.Start();
}
}
I have a singletone form that can be opened from a ribbon button or that will check every minute whether it should be open after passing a few conditional checks.
When opening the form from the ribbon button, it works correctly every time.
When opening on the timer, the form does not get rendered correctly, any place a control should be is just displayed as a white rectangle. Screenshots below.
ThisAddIn.cs
using Timer = System.Timers.Timer;
public partial class ThisAddIn
{
private Timer ticker;
private void ThisAddIn_Startup(object sender, System.EventArgs e) {
ticker = new Timer(5 * 60 * 1000);
ticker.AutoReset = true;
ticker.Elapsed += new System.Timers.ElapsedEventHandler(checkForOverdue);
ticker.Start();
}
private void checkForOverdue(object sender, System.Timers.ElapsedEventArgs e)
{
bool overdue = false;
foreach (Reminder reminder in reminders)
{
DateTime now = DateTime.Now;
if (reminder.time <= now)
{
overdue = true;
break;
}
}
if (overdue)
{
RemindersList form = RemindersList.CreateInstance();
if (form != null)
{
form.Show();
}
}
}
}
Ribbon.cs
public partial class Ribbon
{
private void reminderListButton_Click(object sender, RibbonControlEventArgs e)
{
RemindersList form = RemindersList.CreateInstance();
if (form != null)
{
form.Show();
}
}
}
RemindersList.cs
public partial class RemindersList : Form
{
private static RemindersList _singleton;
private RemindersList()
{
InitializeComponent();
this.FormClosed += new FormClosedEventHandler(f_formClosed);
}
private static void f_formClosed(object sender, FormClosedEventArgs e)
{
_singleton = null;
}
public static RemindersList CreateInstance(List<Reminder> rs)
{
if (_singleton == null)
{
_singleton = new RemindersList(rs);
_singleton.Activate();
// Flash in taskbar if not active window
FlashWindow.Flash(_singleton);
return _singleton;
}
else
{
return null;
}
}
}
EDIT - SOLUTION
Per sa_ddam213's answer, I changed out the System.Timers.Timer for a Windows.Forms.Timer and it's now working just how I wanted.
Code changes:
ThisAddIn.cs
using Timer = System.Windows.Forms.Timer;
public partial class ThisAddIn {
private void ThisAddIn_Startup(object sender, System.EventArgs e) {
ticker = new Timer();
ticker.Interval = 5 * 60 * 1000;
ticker.Tick += new EventHandler(checkForOverdue);
ticker.Start();
}
// Also needed to change the checkForOverdue prototype as follows:
private void checkForOverdue(object sender, EventArgs e)
}
You can't touch UI controls/elements with any other thread than the UI thread, in your case the System.Timer is running on another thread and the window will never open
Try switching to a Windows.Forms.Timer
Or invoke the call back to the UI thread.
private void checkForOverdue(object sender, System.Timers.ElapsedEventArgs e)
{
base.Invoke(new Action(() =>
{
/// all your code here
}));
}
I suspect that the timer event handler is not launched on the UI thread, which could cause all sorts of problems. I would check that first and ensure that the UI stuff is actually done on the UI thread.
I have the following WaitScreen class that shows a "please wait..." message when doing a background process:
public class WaitScreen
{
// Fields
private object lockObject = new object();
private string message = "Please Wait...";
private Form waitScreen;
// Methods
public void Close()
{
lock (this.lockObject)
{
if (this.IsShowing)
{
try
{
this.waitScreen.Invoke(new MethodInvoker(this.CloseWindow));
}
catch (NullReferenceException)
{
}
this.waitScreen = null;
}
}
}
private void CloseWindow()
{
this.waitScreen.Dispose();
}
public void Show(string message)
{
if (this.IsShowing)
{
this.Close();
}
if (!string.IsNullOrEmpty(message))
{
this.message = message;
}
using (ManualResetEvent event2 = new ManualResetEvent(false))
{
Thread thread = new Thread(new ParameterizedThreadStart(this.ThreadStart));
thread.SetApartmentState(ApartmentState.STA);
thread.Start(event2);
event2.WaitOne();
}
}
private void ThreadStart(object parameter)
{
Application.SetUnhandledExceptionMode(UnhandledExceptionMode.ThrowException);
ManualResetEvent event2 = (ManualResetEvent)parameter;
Application.EnableVisualStyles();
this.waitScreen = new Form();
this.waitScreen.Tag = event2;
this.waitScreen.ShowIcon = false;
this.waitScreen.ShowInTaskbar = false;
this.waitScreen.AutoSize = true;
this.waitScreen.AutoSizeMode = AutoSizeMode.GrowAndShrink;
this.waitScreen.BackColor = SystemColors.Window;
this.waitScreen.ControlBox = false;
this.waitScreen.FormBorderStyle = FormBorderStyle.FixedToolWindow;
this.waitScreen.StartPosition = FormStartPosition.CenterScreen;
this.waitScreen.Cursor = Cursors.WaitCursor;
this.waitScreen.Text = "";
this.waitScreen.FormClosing += new FormClosingEventHandler(this.WaitScreenClosing);
this.waitScreen.Shown += new EventHandler(this.WaitScreenShown);
Label label = new Label();
label.Text = this.message;
label.AutoSize = true;
label.Padding = new Padding(20, 40, 20, 30);
this.waitScreen.Controls.Add(label);
Application.Run(this.waitScreen);
Application.ExitThread();
}
private void WaitScreenClosing(object sender, FormClosingEventArgs e)
{
if (e.CloseReason == CloseReason.UserClosing)
{
e.Cancel = true;
}
}
private void WaitScreenShown(object sender, EventArgs e)
{
Form form = (Form)sender;
form.Shown -= new EventHandler(this.WaitScreenShown);
ManualResetEvent tag = (ManualResetEvent)form.Tag;
form.Tag = null;
tag.Set();
}
// Properties
public bool IsShowing
{
get
{
return (this.waitScreen != null);
}
}
}
The way I use it is:
waitScreen = new WaitScreen();
waitScreen.Show("Please wait...");
I have a MainForm, inside a mainform I have a button, when clicked I show a Dialog that on load will get some data from database in a Backgroundworker. Before running the backgroundworker I show my WaitScreen.
Its working great but when the WaitScreen is displayed and If I click on the back Dialog then the WaitScreen is gone. So I want to block so I can't click on the back Dialog until the worker has finish and then I close my WaitScreen.
Any clue on how to do that?
Thanks a lot.
I think you are overcomplicating this. What you want is a modal dialog window which the user can't close which will close when a given task is finished.
You can make a standard class which derives from Form and implements a constructor or a property which you can pass a Task or a callback.
Here's what the code could look like in your WaitScreen form:
public partial class WaitScreen : Form
{
public Action Worker { get; set; }
public WaitScreen(Action worker)
{
InitializeComponent();
if (worker == null)
throw new ArgumentNullException();
Worker = worker;
}
protected override void OnLoad(EventArgs e)
{
base.OnLoad(e);
Task.Factory.StartNew(Worker).ContinueWith(t => { this.Close(); }, TaskScheduler.FromCurrentSynchronizationContext());
}
}
Here's what your code would look like in the consumer of this WaitScreen form:
private void someButton_Click(object sender, EventArgs e)
{
using (var waitScreen = new WaitScreen(SomeWorker))
waitScreen.ShowDialog(this);
}
private void SomeWorker()
{
// Load stuff from the database and store it in local variables.
// Remember, this is running on a background thread and not the UI thread, don't touch controls.
}
You probably want to use FormBorderStyle.None in the WaitScreen so that the user can't close it. Then the task is complete the WaitScreen will close itself and the caller will continue execution of code after the ShowDialog() call. ShowDialog() blocks the calling thread.