WPF - partial class (xaml) instantiation does not seems to occurs as expected - c#

I have a class (code included below) that I used as my Startup class.
This class instanciate my regular partial class app.cs (app.xaml).
I thought that it was the compiler which instantiate my partial class and set parameters (defined in xaml). But it should be something else because my assertion defined in SingleInstanceApp.cs is false.
Why my assertion is false, why StartupUri is null ????????
Thanks,
Eric
Startup class:
namespace MonitorMe
{
public class SingleInstanceApp
{
[STAThread]
public static void Main(string[] args)
{
Mutex _mutexSingleInstance = new Mutex(true, "MonitorMeSingleInstance");
if (_mutexSingleInstance.WaitOne(TimeSpan.Zero, true))
{
try
{
var app = new App();
// ASSERTION HERE is FALSE ... WHY
Debug.Assert(app.StartupUri != null);
app.Run(new MainWindow());
}
finally
{
_mutexSingleInstance.ReleaseMutex();
_mutexSingleInstance.Close();
}
}
else
{
MessageBox.Show("One instance is already running.");
var processes = Process.GetProcessesByName(Assembly.GetEntryAssembly().GetName().Name);
{
if (processes.Length > 1)
{
foreach (var process in processes)
{
if (process.Id != Process.GetCurrentProcess().Id)
{
WindowHelper.SetForegroundWindow(process.MainWindowHandle);
}
}
}
}
}
}
}
}
My regular App class xaml (.cs has nothing in it):
<Application x:Class="MonitorMe.App"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
StartupUri="MainWindow.xaml">
<Application.Resources>
</Application.Resources>
</Application>

If you have ever looked at generated code (*.g.cs) located in obj folder for any WPF project, there will be a call to InitializeComponent - which loads the corrresponding XAML by calling LoadComponent.
Quoting a sample from the MSDN page Application Management Overview
// Create new instance of application subclass
App app = new App();
// Code to register events and set properties that were
// defined in XAML in the application definition
app.InitializeComponent();
// Start running the application
app.Run();
So, you need call InitializeComponent on app object after instantiate.
I am also adding InitializeComponent code fro app.g.cs for one of my sample apps:
[System.Diagnostics.DebuggerNonUserCodeAttribute()]
public void InitializeComponent() {
#line 4 "..\..\..\App.xaml"
this.StartupUri = new System.Uri("MainWindow.xaml", System.UriKind.Relative);
#line default
#line hidden
}

Related

How can I get Visual Studio cmd parameters for debugging working?

Currently I'm working on a WPF-App. It should be launched by command line with a single parameter.
I defined:
public App([Optional] string[] args)
{
//string[] args = new string[] { "UK356715586" };
Console.WriteLine("accessed app");
if (args.Length == 0)
{
Environment.Exit(-1);
}
else
{
Console.WriteLine("Before PONumber Setting");
PONumber = args[0].ToString();
}
//PONumber = "UK356715586";
}
I set this debug setting for the given parameter:
By launching in VS I'm getting:
instance of an object."
"args" war "null".
What can i do?
Do not create a constructor with a parameter declared as [Optional]. It is never assigned. If you remove the attribute, you will even get a compilation error.
Instead use the built in Startup event of the Application type. From the documentation:
A typical Windows Presentation Foundation application may perform a variety of initialization tasks when it starts up, including:
Processing command-line parameters.
[...] application-scope properties and command-line parameters can only be used programmatically. Programmatic initialization can be performed by handling the Startup event [...]
Assign an event handler in App.xaml and implement it in App.xaml.cs.
<Application x:Class="YourWpfApp.App"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
StartupUri="MainWindow.xaml"
Startup="App_OnStartup">
<Application.Resources>
<!-- ...your resources. -->
</Application.Resources>
</Application>
public partial class App : Application
{
private void App_OnStartup(object sender, StartupEventArgs e)
{
Console.WriteLine("accessed app");
if (e.Args.Length == 0)
{
Shutdown(-1);
}
else
{
Console.WriteLine("Before PONumber Setting");
PONumber = e.Args[0];
}
}
// ...other code.
}
An alternative is to override the OnStartup method in your App type. From the documentation:
OnStartup raises the Startup event.
A type that derives from Application may override OnStartup. The overridden method must call OnStartup in the base class if the Startup event needs to be raised.
public partial class App : Application
{
protected override void OnStartup(StartupEventArgs e)
{
base.OnStartup(e);
Console.WriteLine("accessed app");
if (e.Args.Length == 0)
{
Shutdown(-1);
}
else
{
Console.WriteLine("Before PONumber Setting");
PONumber = e.Args[0];
}
}
// ...other code.
}

Start WPF Application in Console Application

Is it possible to start WPF Application in Console mode?
public partial class App : Application
{
public App()
{
InitializeComponent();
}
}
<Application x:Class="WPF.App"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml">
</Application>
[STAThread]
static void Main(string[] args)
{
if (args.Length > 0)
{
switch (args[0].ToLower())
{
case "/g": RunApplication(); break;
}
}
}
private static void RunApplication()
{
var application = new System.Windows.Application();
application.Run(new App());
}
It will show Argument type 'WPF.app' is not assignable to parameter type 'System.Windows.Window'.
Any solution to work around it??
Any different between
1.public partial class App : Application
2.public partial class App : Window
You could declare a Window and then start your app this way:
var application = new System.Windows.Application();
application.Run(new Window());
EDIT:
You seem a bit confused, so let me explain:
Say you have a program:
using System;
namespace ConsoleApplication
{
class Program
{
[STAThread]
static void Main(string[] args)
{
RunApplication();
}
private static void RunApplication()
{
var application = new System.Windows.Application();
application.Run();
}
}
}
This will run a WPF application with no Window.
If, on the other hand, you pass a Window into application.Run(), you will get a WPF window. App should not derive from Window, since it should derive from Application.
Application.Run method either takes no arguments or a Window. It does not take Application. Therefore, if you want to start a previously created Application, as you have over there, you should do something like this:
private static void RunApplication()
{
var application = new App();
application.Run(); // add Window if you want a window.
}
Lastly, if you want to just use application.Run() and not have to pass a specific Window, just declare a starting Window in your Application XAML using StartupUri:
<Application x:Class="WPF.App"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
StartupUri="SomeWindow.xaml">
</Application>

How to create a separate STA thread for a WPF form that is inside of a dll and not a WPF application?

I have a dll that contains a WPF form. This dll will be called by another WPF application, which unfortunately I can't touch it. The problem is that there is no Main method in my WPF form to place
[STAThread]
static void Main() {
//...
}
The exception is thrown right on the constructor of my class. How can I get around this STA problem in this case. Again, this is not a WPF application, but a WPF form that is places inside of a dll.
namespace VisualScannerTest
{
/// <summary>
/// Interaction logic for VisualScanner.xaml
/// </summary>
public partial class VisualScanner :Window,IPeripheral<VisualScanner.VisualScannerSettings>
{
private VisualScannerSettings _settings;
private readonly IEventManager _eventManager;
public VisualScanner(IEventManager eventManager):base()
{
InitializeComponent();
_eventManager = eventManager;
}
private void SubmitButton_Click(object sender, RoutedEventArgs e)
{
string input = InpuTextBox.Text;
InpuTextBox.Text = string.Empty;
if (!string.IsNullOrWhiteSpace(input))
{
_eventManager.GetEvent<PeripheralEvent>().Publish(new PeripheralEventData {RawData = input});
}
}
public void Initialize(VisualScannerSettings settings)
{
_settings = settings;
}
public IEnumerable<string> RegisterImpulses()
{
yield break;
}
public string Type
{
get { return GetType().FullName; }
}
public sealed class VisualScannerSettings:PeripheralSettings
{
public string Data { get; set; }
}
}
}
I would 1st suggest you turn the dll into another WPF application that has it's own thread then you need a different way to communicate with it - such as using another dll both applications reference or using some merhod of RPC, e.g. a TCP Socket.
Alternatively you could try expose in the dll what needs to be called on the STA thread of the consumer. So you application could call it, e.g.:
CreateForm();
for example.
Though that may not work - you need to setup a few things to make WPF tick it is not just matter of using an STA thread:
Set the SetSynchronizationContext to a DispatcherSynchronizationContext
SynchronizationContext.SetSynchronizationContext(new DispatcherSynchronizationContext(Dispatcher.CurrentDispatcher));
Create a System.Windows.Application()
Load and add any XAML Resources with Application.LoadComponent()`.
var resources = System.Windows.Application.LoadComponent(new Uri("/Styles.xaml", UriKind.Relative));
Resources.MergedDictionaries.Add(resources);
You would need to programmatically pump the Dispatcher:
Dispatcher.CurrentDispatcher.PushFrame();
I had the same problem. You need to use the TaskScheduler. Important is to set the ApartmentState.
public static async Task Main()
{
var taskScheduler = new SingleThreadTaskScheduler(ApartmentState.STA);
var taskList = new List<Task>();
var updateTask = Task.Run(InstallHelper.CheckForUpdatesAsync);
updateTask.Wait();
taskList.Add(updateTask);
var tasks = await Task.Factory.ContinueWhenAll(taskList.ToArray(), result =>
Task.Factory.StartNew( ()=>MyAppSynchronMethodToRunIn_STAThread,
taskScheduler));
}

Implementation to change current user in winforms app

I want to realize to change current user in my application. I have the following code:
public class Framework
{
private MainForm mainForm = null;
... // other fields
public virtual void run()
{
if (appInitializer!=null)
{
ISecurityManager securityManager = appInitializer.SecurityManager;
if (securityManager!=null)
{
if (securityManager.DoLogin())
{
RegisterDefaultActionsGroup();
InitializePlugins(appInitializer.Plugins);
// Apply rights for user
ActionsManager.Inst.ApplySecurity(securityManager, securityManager.CurrentUser);
mainForm = new MainForm();
mainForm.Text = appInitializer.ApplicationTitle;
if (appInitializer.ApplicationIcon != null)
{
mainForm.Icon = appInitializer.ApplicationIcon;
}
CorrectFormSizes(mainForm);
Context[Constants.MainForm] = mainForm;
MenuManager.Inst.FillMenu(DefaultGroups.MAIN_MENU, mainForm.MainMenu, ActionClick);
if(appInitializer.IsHaveToCreatePanelInfo) PanelInfoManager.Inst.FillInfo(mainForm);
if (appInitializer.IsHaveToCreateToolBar)
{
MenuManager.Inst.FillToolbar(DefaultGroups.MAIN_TOOLBAR, mainForm.MainToolStrip, ActionClick);
}
mainForm.MainToolStrip.Visible = mainForm.MainToolStrip.Items.Count > 0;
NotifyPluginsAboutShowing(appInitializer.Plugins);
Application.Run(mainForm);
}
}
}
}
...//other methods
}
static class Program
{
/// <summary>
/// The main entry point for the application.
/// </summary>
[STAThread]
static void Main()
{
Application.EnableVisualStyles();
Application.SetCompatibleTextRenderingDefault(false);
Framework framework = new Framework(new EArchiveInitializer());
framework.run();
}
}
In the button for change user I have:
Framework.Instance.MainForm.MainMenuStrip.Items.Clear();
Framework.Instance.run();
But, I got error: Starting a second message loop on a single thread is not a valid operation. Use Form.ShowDialog instead.
I know that means this error, but I can't to rewrite my code.
Can you help me?
Thanks.
SOLUTION:
Rewrite the last line in run method:
if (!Application.MessageLoop)
Application.Run(mainForm);
else
mainForm.Show();
Thanks Jonathan.
The problem is actually quite easy, the issue is you are calling Application.Run twice (first on load, second on the button)
A quick work around for this, would be to have the Application.Run an ApplicationContext, instead of a form initially, and from your public virtual void run() method, load the required form.
public class Framework
{
private MainForm mainForm = null;
... // other fields
public virtual void run()
{
if (appInitializer!=null)
{
ISecurityManager securityManager = appInitializer.SecurityManager;
if (securityManager!=null)
{
if (securityManager.DoLogin())
{
RegisterDefaultActionsGroup();
InitializePlugins(appInitializer.Plugins);
// Apply rights for user
ActionsManager.Inst.ApplySecurity(securityManager, securityManager.CurrentUser);
mainForm = new MainForm();
mainForm.Text = appInitializer.ApplicationTitle;
if (appInitializer.ApplicationIcon != null)
{
mainForm.Icon = appInitializer.ApplicationIcon;
}
CorrectFormSizes(mainForm);
Context[Constants.MainForm] = mainForm;
MenuManager.Inst.FillMenu(DefaultGroups.MAIN_MENU, mainForm.MainMenu, ActionClick);
if(appInitializer.IsHaveToCreatePanelInfo) PanelInfoManager.Inst.FillInfo(mainForm);
if (appInitializer.IsHaveToCreateToolBar)
{
MenuManager.Inst.FillToolbar(DefaultGroups.MAIN_TOOLBAR, mainForm.MainToolStrip, ActionClick);
}
mainForm.MainToolStrip.Visible = mainForm.MainToolStrip.Items.Count > 0;
NotifyPluginsAboutShowing(appInitializer.Plugins);
mainForm.Show();
}
}
}
}
...//other methods
}
static class Program
{
/// <summary>
/// The main entry point for the application.
/// </summary>
[STAThread]
static void Main()
{
Application.EnableVisualStyles();
Application.SetCompatibleTextRenderingDefault(false);
Application.Run(new MyHiddenContext());
}
}
public class MyHiddenContext
: ApplicationContext
{
private static Form activeFormInstance;
public MyHiddenContext()
{
this.RunFramework();
}
public void RunFramework()
{
Framework framework = new Framework(new EArchiveInitializer());
this.framework.run();
activeFormInstance = Framework.Instance.MainForm;
}
public static void ChangeUser()
{
activeFormInstance.Close();
activeFormInstance.Dispose();
Framework.Instance.MainForm.MainMenuStrip.Items.Clear();
Framework.Instance.run();
}
}
Don't quote me on the code actually working, but its more to give an idea on which way to go. The problem though is you can't call Application.Run more than once, so the principal is to have a containing instance or context (in any such sense, form, console etc)

getting string from c# command line and passing it to wpf window

Very newbie question.
I want to overwrite the Main in my WPF app so if I double-click on a file, it will be loaded. My main function is:
[STAThread]
static void Main(string[] args)
{
FileConvert.App app = new FileConvert.App();
app.InitializeComponent();
if (args.Length > 0)
{
Window1 wnd1 = (Window1)(app.MainWindow);
wnd1.SetProjectFile(args[0]);
}
app.Run();
My problem is that wnd1 is null. How do I get access to this window so I can pass it the filename to load?
Thanks!
Instead of overwriting the Main method, try overriding the OnStartup method in App.xaml.cs:
public partial class App : Application
{
protected override void OnStartup(StartupEventArgs e)
{
if (e.Args.Length > 0)
((Window1) MainWindow).SetProjectFile(e.Args[0]);
}
}

Categories