C#应用程序 GUI 和命令行同时存在

52

目前我有一个带有图形用户界面的应用程序。

是否可以使用相同的应用程序从命令行中运行(无需GUI,使用参数)。

还是说我必须创建一个单独的.exe文件(和应用程序)来作为命令行工具?


或者,您可以查看这个问题的答案:https://dev59.com/eHM_5IYBdhLWcg3w43lt - Alastair Pitts
@PeeHaa:你使用哪个GUI框架?WinForms还是WPF? - Merlyn Morgan-Graham
@Merlyn Morgan-Graham:WinForms - PeeHaa
2
@PeeHaa:只需不运行Application.Run(new MyMainForm());这一行即可。将其放置在一个if块中,如果您获得了参数,则不要运行该代码行。 - Merlyn Morgan-Graham
1
@Merlyn Morgan-Graham:天啊,原来如此简单... 太好了!谢谢。 - PeeHaa
显示剩余3条评论
5个回答

68
  1. 编辑项目属性,将应用程序设置为“Windows 应用程序”(而不是“控制台应用程序”)。这样做可以仍然接受命令行参数。如果不这样做,则双击应用程序图标时会弹出控制台窗口。
  2. 确保你的 Main 函数能够接受命令行参数。
  3. 如果有任何命令行参数,请不要显示窗口。

以下是一个简短的示例:

[STAThread]
static void Main(string[] args)
{
    if(args.Length == 0)
    {
        Application.Run(new MyMainForm());
    }
    else
    {
        // Do command line/silent logic here...
    }
}

如果您的应用程序还没有清晰地进行静默处理(如果所有逻辑都塞在WinForm代码中),则可以像CharithJ的答案一样通过黑客式的方式进行静默处理

OP编辑 抱歉占用您的答案,Merlyn。只是想让这里有所有信息供其他人使用。

要能够在WinForms应用程序中写入控制台,请执行以下操作:

static class Program
{
    // defines for commandline output
    [DllImport("kernel32.dll")]
    static extern bool AttachConsole(int dwProcessId);
    private const int ATTACH_PARENT_PROCESS = -1;

    /// <summary>
    /// The main entry point for the application.
    /// </summary>
    [STAThread]
    static void Main(string[] args)
    {
        // redirect console output to parent process;
        // must be before any calls to Console.WriteLine()
        AttachConsole(ATTACH_PARENT_PROCESS);

        if (args.Length > 0)
        {
            Console.WriteLine("Yay! I have just created a commandline tool.");
            // sending the enter key is not really needed, but otherwise the user thinks the app is still running by looking at the commandline. The enter key takes care of displaying the prompt again.
            System.Windows.Forms.SendKeys.SendWait("{ENTER}");
            Application.Exit();
        }
        else
        {
            Application.EnableVisualStyles();
            Application.SetCompatibleTextRenderingDefault(false);
            Application.Run(new QrCodeSampleApp());
        }
    }
}

1
完全不介意。请注意,如果您双击应用程序,则Console.WriteLine将与此解决方案一起进入位桶。此外,如果您关心附加失败的情况,可以检查Marshal.GetLastWin32Error - Merlyn Morgan-Graham
1
@PeeHaa 谢谢,这个很好用,但是对我来说它似乎在一个单独的线程中运行(例如,命令提示符立即返回而不等待在主函数中启动的处理完成。这是预期的吗? - Kohanz
1
需要使用 'DllImport',必须添加 'using System.Runtime.InteropServices;'。 - Kai Hartmann
3
使用AttachConsole的缺点是,在控制台被附加之前,cmd调用将立即返回,因此在输出被写入之前就已经返回。这也是为什么需要在末尾添加额外的回车键的原因。如果您想将输出导向其他位置,您只会得到一个空的输出。迄今为止我没有找到真正的解决方案。所提供的解决方案对我来说不适用,但它是最接近的解决方案。 - Robert S.
1
所以我正在尝试做这件事,但我遇到的问题是当我从命令提示符中使用命令行参数运行应用程序时,它会按预期运行,但是它立即返回控制权给命令提示符。我期望它一直运行到结束并返回一个退出代码。我在错误时使用Environment.Exit(-1),在成功时使用(0),但由于它立即返回,这不会正确地工作。 - Chris Ward
显示剩余8条评论

10

在您的Program.cs类中,保持Main方法不变,但将string[] Args添加到主窗体。例如...

    [STAThread]
    static void Main(string[] Args)
    {
        ....
        Application.Run(new mainform(Args));
    }

在 mainform.cs 构造函数中

    public mainform(string[] Args)
    {
        InitializeComponent();

        if (Args.Length > 0)
         {
             // Do what you want to do as command line application.
             // You can hide the form and do processing silently.
             // Remember to close the form after processing.
         }
    }

2
+1;但如果您正在作为控制台应用程序运行,则甚至不要尝试执行Application.Run。隐藏窗口可以起作用,但这是一种hack :) - Merlyn Morgan-Graham
3
缺点是你将无法在控制台窗口中看到任何输出,因为该应用程序没有关联的真实控制台窗口。Console.WriteLine不会产生任何可见的输出。 - Robert S.

1

我是C#编程的新手。但我改进了来自OP和Merlyn的代码提示。当我使用他们的代码提示时,我遇到的问题是在通过双击app.exe调用app.exe或从CMD调用它时参数长度不同。当app.exe从CMD作为CLI运行时,app.exe本身成为第一个参数。下面是我的改进代码,既可以作为GUI双击app.exe,也可以作为从CMD的CLI运行而令人满意。

[STAThread]
    static void Main(/*string[] args*/)
    {
        string[] args = Environment.GetCommandLineArgs();
        Console.WriteLine(args.Length);
        if (args.Length <= 1)
        {
            //calling gui part
            Application.EnableVisualStyles();
            Application.SetCompatibleTextRenderingDefault(false);
            Application.Run(new ACVSAppForm());
        }
        else
        {
            //calling cli part                
            string opt = args[1];
            //Console.WriteLine(args[0]);                
            if(opt == "LastBuild")
            {
                if(args.Length == 3)
                {
                    var defSettings = Properties.Settings.Default;
                    defSettings.CIBuildHistPath = args[2];
                }
                else
                {
                    //
                }
                CIBuildParser cibuildlst = new CIBuildParser();
                cibuildlst.XMLParser();
            }
        }

    }

我希望这能对某些人有所帮助。我的解决方案唯一的缺点是当app.exe作为GUI运行时,它会打开一个CMD作为控制台输出窗口。但对于我的工作来说这没问题。

-2

你可能需要将你的应用程序结构化为控制台应用程序,将像点击按钮这样的“操作”单独放在一个类中,包括一个表单,如果没有提供命令行参数,则可以显示该表单,并通过将事件路由到你的“操作”类中的公共方法来处理事件。


3
不要让它成为控制台应用程序。否则,当您将其作为图形用户界面启动时,控制台将弹出。 - Merlyn Morgan-Graham

-3

我认为这是可能的,只需将您的子系统设置为“控制台”,您将看到一个控制台窗口以及GUI窗口。

但是为了从控制台窗口接受命令,我想您需要创建一个额外的线程来完成它。


这不是一个好的选择,因为如果他们通过双击文件图标启动它,它将生成一个额外的控制台。此外,您不一定需要生成另一个线程 - 例如,如果您通过命令行参数传递您的“命令”。 - Merlyn Morgan-Graham

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