替换WPF入口点

66

WPF定义了自己的Main()方法。我该如何将其替换为我的Main方法,以便(通常)打开WPF MainWindow(例如,通过命令行参数添加非WPF脚本模式)?

6个回答

69

有些示例展示了将 App.xaml 的 Build Action 从 ApplicationDefinition 更改为Page,并编写自己的 Main() 实例化 App 类并调用其 Run() 方法,但这可能会对 App.xaml 中应用程序范围资源的解析产生一些不必要的后果。

相反,我建议在其自己的类中创建您自己的 Main() 并将 Startup Object 设置为该类在项目属性中:

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();
        }
    }
}

我这样做是为了利用一些必须在其他任何事情发生之前订阅的AppDomain事件(例如AssemblyResolve)。将App.xaml设置为Page所带来的不良后果包括我在设计时使用的UserControl视图(M-V-VM)无法解析在App.xaml中保存的资源。


好的,我调用了App.Main()而不是Run(),因为Main()会调用InitializeComponent(),这会安装启动事件处理程序。我猜想如果你将Build Action更改为Page(因为Main()会消失),那么你必须调用Run(),但我只是将其保留为ApplicationDefinition。 - Qwertie
4
生成的 Main() 方法只是实例化了 App 并调用了 Run()System.Windows.Application 的构造函数会触发 Startup 事件。Run() 方法附加了一个 Dispatcher 并开始消息泵。InitializeComponent() 应该在 App 的构造函数中被调用。是吗? - Joel B Fant
2
我在 App 中添加了一个构造函数,并在其中调用 InitializeComponent(),以避免使用 App.Main()。为了简洁起见,我将其余部分过度简化了。StartupApplication.OnStartup() 触发,只有派生的 App 类的构造函数可以在其触发之前订阅 Startup。这是因为 Application 的构造函数异步调用一个方法来调用 OnStartup(),所以它实际上是在基类和派生类构造函数完成后运行的。 - Joel B Fant
1
不必在定义的'Main'入口点内解析命令行参数,有没有一种方法可以将这些参数传递给在方法中定义的WPF应用程序实例,以便它们可以通过定义的'Startup'(或OnStartup)重写(通过e.Args属性)进行处理? - Will
1
你可以不必移动(或复制)App.Main(),而是添加以下代码并将其设置为项目的入口点,然后直接调用App.Main() - Jason Goemaat
显示剩余3条评论

23

通常我会编辑App.xaml来添加此支持:

<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">

关键部分是我将StartupUri更改为带有事件处理程序的Startup,并在App.xaml.cs中进行了更改。以下是一个例子:

/// <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();
    }
}

虽然使用这种方法,除非您从命令窗口运行它,否则您将看不到任何 Console.* 输出。 - user7116
这种方法让我将构造函数参数传递给主窗口,非常不错。我甚至可以与Joel的方法相结合。 - Qwertie
感谢您指出它是“Startup”而不是“StartupUri”! - Kevin K

20

大家好, 问题在于你的程序有两个静态的Main()方法,这会导致编译器出现错误提示;为了解决这个问题,请尝试以下建议:

  • 告诉编译器你的静态Main()方法应该是执行入口点——将项目的“启动对象”设置为包含静态Main()方法的类(在“解决方案资源管理器”中右键单击项目,选择“属性”,然后在“应用程序”选项卡下查找“启动对象”设置)。
  • 关闭App.g.cs自动生成的静态Main()方法——在“解决方案资源管理器”中,右键单击App.xaml,选择“属性”,然后将“生成操作”从“ApplicationDefinition”更改为“Page”。

3
谢谢;第二个要点非常重要 - 微妙地放在那里了! - Jeb

5

创建一个包含自定义静态Main方法的新类。在该方法的结尾处,只需调用由WPF生成的原始App.Main()方法:

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

然后将项目的“启动对象”设置为包含静态Main()方法的类。

0
我发布这个答案是因为以上的答案都对我无效。在我的情况下,StartupUri已经从App.xaml中删除,但我仍然遇到了错误。最终我添加了以下代码到项目文件(Foo.csproj)中,问题得到了解决。
<ItemGroup>
    <ApplicationDefinition Remove="App.xaml" />
    <Page Include="App.xaml" />
</ItemGroup>

0

如果使用自定义的Main()方法,则可能会遇到问题,因为未设置StartupUri。

您可以在App类中使用以下代码轻松设置它(不要忘记从App.xaml中删除StartupUri并将其Build Action设置为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");
...
}

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接