I want to open a WPF Window from a Console application. After referring to this post, it works fine.
The problem is: When the user closed the WPF Window (manually), it can no long be re-opened from the Console, throwing the exception message: "Cannot create more than one System.Windows.Application instance in the same AppDomain."
Here is the code:
class Program
{
static void Main(string[] args)
{
string input=null;
while ((input = Console.ReadLine()) == "y")
{
//Works fine at the first iteration,
//But failed at the second iteration.
StartWpfThread();
}
}
private static void OpenWindow()
{
//Exception(Cannot create more than one System.Windows.Application instance in the same AppDomain.)
//is thrown at the second iteration.
var app = new System.Windows.Application();
var window = new System.Windows.Window();
app.Run(window);
//User closes the opened window manually.
}
private static void StartWpfThread()
{
var thread = new Thread(() =>
{
OpenWindow();
});
thread.SetApartmentState(ApartmentState.STA);
thread.IsBackground = false;
thread.Start();
}
}
How can I re-open the WPF Window?
You should not create the application together with the window but only once separately, also make sure that it does not exit after the window is closed by setting the ShutdownMode respectively, e.g.
class Program
{
static Application app;
static void Main(string[] args)
{
var appthread = new Thread(new ThreadStart(() =>
{
app = new Application();
app.ShutdownMode = ShutdownMode.OnExplicitShutdown;
app.Run();
}));
appthread.SetApartmentState(ApartmentState.STA);
appthread.Start();
while (true)
{
var key =Console.ReadKey().Key;
// Press 1 to create a window
if (key == ConsoleKey.D1)
{
// Use of dispatcher necessary as this is a cross-thread operation
DispatchToApp(() => new Window().Show());
}
// Press 2 to exit
if (key == ConsoleKey.D2)
{
DispatchToApp(() => app.Shutdown());
break;
}
}
}
static void DispatchToApp(Action action)
{
app.Dispatcher.Invoke(action);
}
}
Also if you want to re-open the very same window make sure it is never closed completely, to do that you can handle the Closing event and cancel it using e.Cancel = true;, then just call Hide on the window to "close" it and Show to "open" it again later.
When you add window as a parameter to app.Run you link the lifetime of your app to your window. Don't do that:
private static void OpenWindow()
{
//Exception(Cannot create more than one System.Windows.Application instance in the same AppDomain.)
//is thrown at the second iteration.
var app = new System.Windows.Application();
var window = new System.Windows.Window();
app.Run();
window.Show();
//User closes the opened window manually.
}
I've not had chance to test this myself, but after reading up on your error, I found some information. Basically, it sounds like the AppDomain you are running in can only be used once per application - so perhaps you need to create a new AppDomain each time you want to re-create the application. See here for some more information on this.
Alternately, you could use System.Diagnostics.Process.Start(...) in order to fire off the application properly. Refer here for the documentation for the Process class.
Finally, if you are just wanting to allow the user to run the application from the command line, it might be simpler to write a command script and execute that from the command line.
Related
I have recently made a Class Library (dll) for my other project to program a Bluetooth device via serial port (COM). The library is used to transfer firmware via COM port. It works fine until the requirement comes, which requires a WPF window to show the progress of programming. I have successfully created the progress bar using standard WPF app template. However, the standard WPF does not allow me to generate dll. After searching here, I found this link that teaches you how to add a WPF window to existing Class Library project. Also, someone teaches you how to show the window from here. Everything look good until I tried, there is nothing shows up when I call the method ProgrammBluetooth() from LabVIEW.
My main method, which is in a separate .cs file:
namespace BTMProg
{
public class BTMProgrammer
{
private bool _uut1Status = false;
private string _uut1Message = "";
public bool UUT1Status
{
get { return _uut1Status; }
set { _uut1Status = value; }
}
public string UUT1Message
{
get { return _uut1Message; }
set { _uut1Message = value; }
}
public void ProgramBluetooth (string ioPort, string firmwareFile)
{
List<UUT> uutList = new List<UUT>();
uutList.Add(new UUT(ioPort, "UUT1", 1));
Thread thread = new Thread(() =>
{
var wn = new MainWindow(uutList, firmwareFile);
wn.ShowDialog();
wn.Closed += (s, e) => wn.Dispatcher.InvokeShutdown();
Dispatcher.Run();
if (wn.TaskList[0].Result.ToUpper().Contains("SUCCESS"))
{
_uut1Status = true;
_uut1Message = wn.TaskList[0].Result;
}
else
{
_uut1Status = false;
_uut1Message = wn.TaskList[0].Result;
}
});
thread.SetApartmentState(ApartmentState.STA);
thread.Start();
}
}
}
My WPF code in MainWindow.xaml.cs:
ProgrammingViewModel _pvm = new ProgrammingViewModel();
private List<string> _viewModeList = new List<string>();
private List<Task<string>> _taskList = new List<Task<string>>();
public List<Task<string>> TaskList {
get => _taskList;
set => _taskList = value;
}
public MainWindow(List<UUT> uutList, string firmwareFile)
{
InitializeComponent();
foreach (var uut in uutList)
{
_viewModeList.Add(uut.UutName);
}
_pvm.AddProcessViewModels(_viewModeList);
ProgressBarView.DataContext = _pvm.ProcessModels;
StartProgramming(uutList, firmwareFile);
Application.Current.MainWindow.Close();
}
The issue before was that if I don't use dispatcher to create a new thread, an exception saying "The calling thread must be STA, because many UI components require this...." thrown. After I use the new thread, no error but the window does not show up as expected. What could be the problem? Thanks.
The ShowDialog function will stop execution of the thread until the window closes, meaning the rest of that code may not run and the dispatcher may not be started. You should try the Show method instead, which returns as soon as the window is shown.
Also, what is going on with these lines in the constructor of the window?
StartProgramming(uutList, firmwareFile);
Application.Current.MainWindow.Close();
Whatever that first line does, it needs to return and not do a bunch of work if you want the window to finish getting constructed. The second line makes no sense at all. Why are you closing the main window of the application? Did you even set and open a window associated with that property at some point?
I suspect one or more of these things is preventing the thread from ever reaching the point where it can show the window.
I having terrible when i execute the same method second time.i am not getting WPF screen, I don't know why?
refer my code
TestWindow Button click method(it is windows application project type) and i have removed STA thread in my
Main()
TestClass test;
private void button1_Click(object sender, EventArgs e)
{
test =TestClass.Instance; //singleton pattern
test.ShowScreen();
}
TestClass
public void ShowScreen()
{
var thread = new Thread(() =>
{
Explorer explorer = new Explorer();
explorer.Show();
explorer.Closed += (s, args) =>
explorer.Dispatcher.InvokeShutdown();
System.Windows.Threading.Dispatcher.Run();
});
thread.SetApartmentState(ApartmentState.STA);
thread.Start();
}
Above the code working fine when i run 1st time. i can able view my explorer screen.
But the problem is when i close 1st screen and call once again the same method(test.ShowScreen();) the explorer screen not showing
Note : I have noticed If i didn't close the 1st window(instance) then i can able open many explorer screen. using the same code. If i closed the 1st window(instance) and i am unable open explorer screen and i am not getting any error message.
The issue is resolved Adding the following line in the TestClass Constructors
using SW = System.Windows;
private TestClass()
{
if (SW.Application.Current == null)
{
new SW.Application
{
ShutdownMode = SW.ShutdownMode.OnExplicitShutdown
};
}
}
My current situation is as follow:
Application A injects my library into Application B
Application A then uses GetProcessAddress and GetModule to call the OnStart method in my library
Within my OnStart/OnStop method I initiate/hide a winform, in context its something like this:
private static PluginManager _pluginManager;
public static void OnStart()
{
// some log file code here
if (_pluginManager == null)
{
_pluginManager = new PluginManager();
}
// some log file code here
_pluginManager.Show();
// some log file code here
}
public static void OnStop()
{
if (_pluginManager != null)
{
// some log file code here
_pluginManager.Hide();
// some log file code here
}
}
The PluginManager winform close event is override to call Hide() so technically(I assume) my winform will never be disposed/closed?
Once application A is done its tasks and will close, it uses GetProcessAddress and GetModule to call the OnStop method of my library
Everything works as expected so far the winform shows up at OnStart and hides on OnStop as expected.
Now when I start Application A again and it calls the OnStart method, the winform never shows up, I don't get any error messages nor anything it simple doesn't open.
I know the function is called because it outputs to the log file the line before and the line after it calls Show().
As the title says, why the winform doesn't show up in the second iteration?
What else can I do to find the issue?
On further tests I found out that if I dispose of the winform and reinitialize it, the winform will work for every call:
public static void OnStart()
{
// some log file code here
if (_pluginManager == null)
{
_pluginManager = new PluginManager();
}
else
{
_pluginManager.Dispose();
_pluginManager = new PluginManager();
}
// some log file code here
_pluginManager.Show();
// some log file code here
}
However I am still unaware of why this is required given the winform is never disposed of with the initial code.
My case is probably off-topic? As I can't provide you with a reproducible code of the issue and unhappily the above is all I could collect, but hopefully some one will be able to point me in the right direction or where to look at.
This is probably a threading problem. You should only call .Show() from an [STAThread] and you should call .Hide() from the same thread that called .Show(). So do this:
private static ISynchronizeInvoke _invoker = null;
public static void OnStart()
{
_invoker.Invoke((Action)(() => {
// some log file code here
if (_pluginManager == null)
{
_pluginManager = new PluginManager();
}
// some log file code here
_pluginManager.Show();
// some log file code here
}), null);
}
public static void OnStop()
{
if (_pluginManager != null)
{
// some log file code here
_pluginManager.Invoke((Action)(() => _pluginManager.Hide()));
// some log file code here
}
}
I don't know how the application's main is structured, but you can fill in _invoker from any open form.
[STAThread]
static void Main()
{
Application.EnableVisualStyles();
Application.SetCompatibleTextRenderingDefault(false);
// Application.Run(new Form1()); <-- This is the usual startup
// Instead, do this to grab an object to invoke on
Form1 form1 = new Form1();
_invoker = form1;
Application.Run(form1);
}
You could also create a wholly separate STAThread and call Application.Run(_pluginManager) instead of pluginManager.Show().
You have mentioned that your PluginManager winform close event is override to call Hide(), did you set e.Cancel = true?
protected override void OnClosing(CancelEventArgs e)
{
e.Cancel = true;
this.Hide();
base.OnClosing(e);
}
In my application I startup a hidden dummyForm that is merely created to keep track of the main UI thread. So If a new form is about to be created InvokeRequired is used on the dummy form to make sure that we are on the main UI thread when creating the new form.
Directly after instantiating my frmStart form I check the frmStart.InvokeRequired and it is set to false so no need for invoke here (the same goes for dummyForm.InvokeRequired).
Then I got a frmMyDialog that will use frmStart as parent/owner something like this:
using(Create frmMyDialog on main UI thread)
{
frmMyDialog.Show(frmStart);
}
This will throw a cross thread exception and the strange thing here is that:
frmMyDialog.InvokeRequired = false
dummyForm.InvokeRequired = false
frmStart.InvokeRequired = true
And this is even when I'm checking that dummyForm.InvokeRequired is false when creating the frmStart?
The frmMyDialog.InvokeRequired should always be the same value as dummyForm.InvokeRequired? What is happening here?
I have checked that the frmStart and dummyForm is not re-created at all after the first instance has been created.
Edit1:
This is how the application starts :
public static void Main(string[] args)
{
_instance = new MyClientMain(parameters);
Application.Run(_instance);
}
The constructor of MyClientMain class will run Setup on a static class called MainControl. MainControler will in the setup method instanciate a dummyform like this :
if (_dummyForm == null)
_dummyForm = new Form();
After this is done a login form will handle a login and this form is multithreaded. When the login is finished the MainController will be called again to instanciate and open the main MDI windo that holds frmStart. To make sure that we are on the same thread the following is done :
public static StartApplication()
{
if (_dummyForm.InvokeRequired)
_dummyForm.Invoke(new MethodInvoker(delegate { OpenMainOrbitWindow(); }));
//Instanciate mainform and frmStart then open mainForm with frmStart as a MDI child
}
There is no multithreading here.
Then when the service goes offline a event will be triggered and I need to popup a frmMyDialog but when using .ShowDialog() it dialog will be placed behind forms so the parent/owner most be found and set like this :
public static Form GetActiveForm()
{
Form activeForm = Form.ActiveForm;
if (activeForm != null)
return activeForm;
if (MainOrbitForm.TopMost)
return MainOrbitForm;
else
{
FormCollection openForms = Application.OpenForms;
for (int i = 0; i < openForms.Count && activeForm == null; ++i)
{
Form openForm = openForms[i];
if (openForm.IsMdiContainer)
return openForm.ActiveMdiChild;
}
}
if (_patientForm != null)
{
if (_patientForm.TopMost)
return _patientForm;
}
return null;
}
public static string ShowOrbitDialogReName()
{
frmMyDialog myDialog;
Form testForm;
//Makes sures that the frmOrbitDialog is created with the same thread as the dummyForm
//InvokeRequired is used for this
using (myDialog = MainController.CreateForm<frmOrbitDialog>())
{
//Settings...
testForm = GetActiveForm();
myDialog.ShowDialog(GetActiveForm(testForm));
}
}
The problem is that
myDialog.InvokeRequired = false
testForm.InvokeRequired = true;
MainController.DummyForm.InvokeRequired = false;
Edit2:
Startup and creates the dummyform :
dummyForm.InvokeRequired = false
Thread.CurrentThread.ManagedThreadId = 9
After success login we create the mainform
_mainForm.InvokeRequired = false
MainControl.DummyForm.InvokeRequired = false
Thread.CurrentThread.ManagedThreadId = 9
Everything looks fine so far. Then a callback is received(WCF) and a event creates a frmMyDialog on the same thread(Invoke is used on the dummyForm) and then the ShowDialog is used :
frmMyCustomDialog.ShowDialog(_mainForm)
This throws a CrossThreadException and this is how it looks like at this point :
_mainForm.InvokeRequired = true
frmMyCustomDialog.InvokeRequired = false
MainControl.DummyForm.InvokeRequired = false
Thread.CurrentThread.ManagedThreadId = 12
Why is the MainControl.DummyForm not true? The ManageThreadId is not 9 but 12?
You should use System.Threading.SynchronizationContext.Current. It was created directly for purposes like this.
You may access it anywhere, after the first form of your app have been created. Judging by your example below, this should not be a problem, since you create a form right o the start of application.
public static void Main(string[] args)
{
_instance = new MyClientMain(parameters);
Application.Run(_instance);
}
Then anywhere, you need the code to be executed on UI thread, you simply use
System.Threading.SynchronizationContext.Current.Send() // To execute your code synchronously
System.Threading.SynchronizationContext.Current.Post() // To execute your code synchronously
Mind, that SynchronizationContext is smartenough to see, that you call it already from UI thread, and then it will simply execute your delegate directly.
And also, remember that you need to create some WinForms form or control before you use SynchronizationContext for the first time, becase when you do this, context will be initialized to appropriate implementation.
There are 3 implementation: Default, that does nothing - just always run code in sync, it stays in the Current until you create WinForms control or WPF control.
Then Current will be populated with either context for Winforms, or context for WPF dispatcher.
This is just off the top of my head, but as Vladimir Perevalov has stated in a different discussion, you have to make your form visible.
If your frmDummy is never shown, then it never has it's window handle created and assigned, and therefore it will always reply False to "InvokeRequired". This would mean that all the code you mean to sync through frmDummy is never actually sent to the initial UI thread, but is always run in the current thread. (which becomes an UI thread of its own for the control it has just created).
The important thing to note is that InvokeRequired tries to determine if the said control's window handle is owned by another thread. It has nothing to do with the constructor.
If you do not want to show the frmDummy, you can call CreateControl right after you instantiate it, to make sure it has it's handle assigned.
I didn't fully understand your question, because your examples don't really show anything about multithreading - however, if you want to create a form where the parent is another form from another thread, you could use this code:
public void CreateShowDialogForm()
{
if (this.InvokeRequired)
{
this.Invoke(new Action(CreateShowDialogForm));
}
else
{
Form frmMyDialog = new Form();
frmMyDialog.Show(this);
}
}
private void Form4_Load(object sender, EventArgs e)
{
Task t = new Task(() => CreateShowDialogForm());
t.Start();
t.ContinueWith(task => true);
}
I wanna use AxWebBrowser on console application, but it give me following exception:
Exception of type 'System.Windows.Forms.AxHost+InvalidActiveXStateException' was thrown.
anybody please help me on this by any sample code for using AxWebBrowser in console application c# without any exeption ...
Yes, the [STAThread] attribute is required on your Main() method so that COM is initialized properly to make the main thread a Single Threaded Apartment. That's not all though, you will also need to pump a message loop. That's a requirement for an STA. Without one, WebBrowser cannot update its state or run its event handlers, you'll never get the DocumentCompleted event for example. You can get a message loop with Application.Run().
Your console application is now indistinguishable from a Windows Forms application. It is actually easier to get everything right by starting a new project with the Windows Forms application project template, then Project + Properties, Output type = Console Application. Edit the Application.Run() call in Program.cs so it doesn't create a form. It won't make dealing with Application.Run() any easier, consider a Timer to run code.
Add the STAThread attribute to your Main method.
However, you should not be using the "raw" ActiveX control.
Instead, add a reference to System.Windows.Forms.dll and use the WebBrowser class. (Yes, you can do that in a Console app)
Also, automating IE is not ideal. You should consider using the WebCLient class.
My class is as below but in the run time it gives me System.Windows.Forms.AxHost+InvalidActiveXStateException:
public class Browse
{
private static AxWebBrowser wBrowser;
public static Result StartBrowse(string url)
{
var validUri = (url.Contains("http://") ? url : "http://" + url);
wBrowser = new AxWebBrowser();
System.Resources.ResourceManager resources = new System.Resources.ResourceManager(typeof(AxWebBrowser));
((ISupportInitialize) (wBrowser)).BeginInit();
wBrowser.OcxState = ((AxHost.State)(resources.GetObject("wBrowser.OcxState")));
wBrowser.NewWindow2 += wBrowser_NewWindow2;
wBrowser.NewWindow3 += wBrowser_NewWindow3;
wBrowser.DocumentComplete += wBrowser_DocumentComplete;
wBrowser.DownloadComplete += wBrowser_DownloadComplete;
if (string.IsNullOrEmpty(html) || validUri != url)
{
object empty = System.Reflection.Missing.Value;
wBrowser.Silent = true;
wBrowser.Navigate(validUri, ref empty, ref empty, ref empty, ref empty);
}
return null;
}
static void wBrowser_DownloadComplete(object sender, EventArgs e)
{
doAlgorithm();
}
static void wBrowser_DocumentComplete(object sender, DWebBrowserEvents2_DocumentCompleteEvent e)
{
doAlgorithm();
}
static void wBrowser_NewWindow3(object sender, DWebBrowserEvents2_NewWindow3Event e)
{
e.cancel = true;
}
static void wBrowser_NewWindow2(object sender, DWebBrowserEvents2_NewWindow2Event e)
{
e.cancel = true;
}
}
First, the thread in which the control is hosted must be in single-threaded apartment, you can either put the STAThread in your Main method, or create a separated Thread like this:
var thread = new Thread(() =>
{
//My code
});
thread.SetApartmentState(ApartmentState.STA);
thread.Start();
thread.Join(); //Wait for thread termination
Second, you must start a message loop:
while (true) //Put some exit condition
System.Windows.Forms.Application.DoEvents();
Third the control must be hosted in a visible form. The form must be visible just once, so to avoid "flickering", you can write this code:
var browser = new AxWebBrowser();
var hostForm = new Form();
//Set form 0 size, without any control box / title / icon
hostForm.Width = 0;
hostForm.Height = 0;
hostForm.ShowInTaskbar = false;
hostForm.ControlBox = false;
hostForm.ShowIcon = false;
hostForm.MinimizeBox = false;
hostForm.MaximizeBox = false;
//Add browser control
hostForm.Controls.Add(browser);
//Show and immediately hide
hostForm.Show();
hostForm.Hide();
Finally you might want to disable the "click" sound (How to disable click sound in WebBrowser Control)
The final code:
class Program
{
[STAThread]
static void Main(string[] args)
{
URLSecurityZoneAPI.InternetSetFeatureEnabled(URLSecurityZoneAPI.InternetFeaturelist.DISABLE_NAVIGATION_SOUNDS, URLSecurityZoneAPI.SetFeatureOn.PROCESS, true);
var browser = new AxWebBrowser();
var hostForm = new Form();
hostForm.Width = 0;
hostForm.Height = 0;
hostForm.ShowInTaskbar = false;
hostForm.ControlBox = false;
hostForm.ShowIcon = false;
hostForm.MinimizeBox = false;
hostForm.MaximizeBox = false;
hostForm.Controls.Add(browser);
hostForm.Show();
hostForm.Hide();
browser.DocumentComplete += delegate(object sender, DWebBrowserEvents2_DocumentCompleteEvent e)
{
var doc = (IHTMLDocument3)browser.Document;
Console.WriteLine(doc.documentElement.innerHTML);
};
browser.Navigate("www.google.com");
while (true)
System.Windows.Forms.Application.DoEvents();
}
}