Replacing the WPF entry point
Asked Answered
K

6

67

WPF defines its own Main() method. How should I go about replacing it with my own Main method that (normally) opens the WPF MainWindow (e.g. to add a non-WPF scripting mode via command-line arguments)?

Kampala answered 27/5, 2011 at 18:56 Comment(0)
B
69

Some examples depict changing App.xaml's Build Action from ApplicationDefinition to Page and writing your own Main() that instantiates the App class and calls its Run() method, but this can produce some unwanted consequences in the resolution of application-wide resources in App.xaml.

Instead, I suggest making your own Main() in its own class and setting the Startup Object to that class in the project properties:

public class EntryPoint {
    [STAThread]
    public static void Main(string[] args) {
        if (args != null && args.Length > 0) {
            // ...
        } else {
            var app = new App();
            app.InitializeComponent();
            app.Run();
        }
    }
}

I do this to take advantage of some AppDomain events that must be subscribed to before anything else happens (such as AssemblyResolve). The unwanted consequences of setting App.xaml to Page that I experienced included my UserControl Views (M-V-VM) not resolving resources held in App.xaml during design-time.

Ballata answered 27/5, 2011 at 19:10 Comment(8)
Okay, I'm calling App.Main() instead of Run() because Main() calls InitializeComponent(), which installs the Startup event handler. I'm guessing you have to call Run() if you change Build Action to Page (since Main() disappears) but I just left it as ApplicationDefinition.Kampala
The generated Main() just instantiates App and calls Run(). The Startup event is fired in System.Windows.Application's constructor. Run() attaches a Dispatcher and begins the message pump. InitializeComponent() should be called in Apps's constructor. Is it not?Ballata
Nope, the microsoft-generated code for App does not include a constructor, so App.Main() should be called in order to call both InitializeComponent() and Run(). I'm using VS2008/.NET3.5, if that makes a difference. Note that System.Windows.Application itself cannot call InitializeComponent, because InitializeComponent only exists in the derived class and is non-virtual. It also would not fire the Startup event because it is not possible that anyone has subscribed to that event until after the constructor returns.Kampala
I add a constructor to App and call InitializeComponent() there to avoid App.Main(). The rest I oversimplified for brevity. Startup is fired by Application.OnStartup() and only the derived App class's constructor can subscribe to Startup before it is fired. This works because Application's constructor asynchronously invokes a method that calls OnStartup(), so it actually runs after base and derived constructors have finished.Ballata
Rather than having to parse the command line arguments within the defined 'Main' entry point, is there some way to instead pass those arguments to the WPF Application instance defined in the method, so that they can be handled by a defined 'Startup' ( or OnStartup ) override ( via the e.Args property )?Helfrich
@JoelBFant "[...] but this can produce some unwanted consequences in the resolution of application-wide resources in App.xaml." Are you referring to the phenomena illustrated in this tutorial? ludovic.chabant.com/devblog/2010/04/20/… The author states that initializing the App class before the MainWindow will prevent these issues. Are there other problems that this person has overlooked? Can you give further clarification?Hagiography
Instead of moving (or duplicating) App.Main(), you could just add this and set it as the entry point for your project and call App.Main() directly.Hecto
MAN this solved a problem I was having! Why didn't I think just to create a new object? I was trying to graft on my own main method onto the app, but this approach doesn't even load the app! Brilliant (and so freaking simple too!)Bobinette
B
23

Typically I edit App.xaml to add this support:

<Application x:Class="SomeNamespace.App"
         xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
         xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
         Startup="Application_Startup">

The relevant part being I changed from StartupUri to Startup with an event handler in App.xaml.cs. Here is an example:

/// <summary>
/// Interaction logic for App.xaml
/// </summary>
public partial class App : Application
{
    private void Application_Startup(object sender, StartupEventArgs e)
    {
        int verbose = 0;
        var optionSet = new OptionSet
        {
            { "v|verbose", "verbose output, repeat for more verbosity.",   
                    arg => verbose++ }
        };

        var extra = optionSet.Parse(e.Args);
        var mainWindow = new MainWindow(verbose);
        mainWindow.Show();
    }
}
Batrachian answered 27/5, 2011 at 19:3 Comment(3)
Although with this approach, unless you run it from a command window you will not see any Console.* output.Batrachian
This approach lets me pass constructor arguments to the main window, which is nice. I might even combine it with Joel's approach.Kampala
Thanks for pointing out that it is "Startup" and not "StartupUri"!Bonar
C
21

guys The problem is that your program has two static Main() methods, that will cause the compiler to complain between; To resolve this, try one of the following:

  • Tell the compiler that your static Main() method should be the execution entry point—Set your project’s “Startup object” setting to the class containing your static Main() method (right-click on the project in Solution Explorer, choose “Properties,” then look for the “Startup object” setting under the “Application” tab).
  • Turn off auto-generation of App.g.cs’s static Main() method—In Solution Explorer, right click on App.xaml, choose “Properties,” then change the “Build Action” from “ApplicationDefinition” to “Page”.
Calculator answered 29/4, 2014 at 10:17 Comment(1)
Thanks; second bullet point was crucial - subtly put away there!Thirtytwo
M
5

Create new class with your custom static Main method. At the end of this method just call original App.Main() generated by WPF:

public class Program
{
    [STAThread]
    public static void Main(string[] args)
    {
        // Your initialization code
        App.Main();
    }
}

Then set your project’s “Startup object” setting to the class containing your static Main().

Morehouse answered 15/2, 2013 at 21:41 Comment(0)
O
0

Using a custom Main() you might run into problems because StartupUri is not set.

You can use this to set it without headaches in your App class (Don't forget to remove StartupUri from App.xaml and set its Build Action to Page):

[STAThread]
static void Main()
{
    App app = new App();
    app.InitializeComponent();
    app.Run();
}

protected void OnStartup(object sender, StartupEventArgs e)
{
        var toUri = new UriTypeConverter();
        StartupUri = (Uri)toUri.ConvertFrom("MainWindow.xaml");
...
}
Oeildeboeuf answered 5/8, 2015 at 14:33 Comment(0)
L
0

I am posting this answer as none of the above answers work for me. In my case, StartupUri was removed from App.xaml and I was still getting the error. what I end up doing I added the following code to the project file (Foo.csproj) and it solved the issue

<ItemGroup>
    <ApplicationDefinition Remove="App.xaml" />
    <Page Include="App.xaml" />
</ItemGroup>
Lovable answered 25/8, 2022 at 2:25 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.