dotnet / command-line-api

Command line parsing, invocation, and rendering of terminal output.
https://github.com/dotnet/command-line-api/wiki
MIT License
3.38k stars 381 forks source link

How to use System.CommandLine in WinForm? #2452

Open Mapaler opened 2 months ago

Mapaler commented 2 months ago

I don't need output to console, but I need to quickly import data via parameters. Is it possible to parse with System.CommandLine inside WinForm? I set up the following code, but nothing is prompted after running.

using System.CommandLine;

namespace Move_Sample_Folder
{
    internal static class Program
    {
        /// <summary>
        ///  The main entry point for the application.
        /// </summary>
        [STAThread]
        static void Main(string[] args)
        {
            var foldersArgument = new Argument<string>
                (name: "folders",
                description: "Folders that need to be moved.");

            var destinationOption = new Option<DirectoryInfo?>(
                name: "--destination",
                description: "The file to read and display on the console.");
            destinationOption.AddAlias("-dst");

            var autoOption = new Option<bool>(
                name: "--auto",
                description: "Auto start process.");
            autoOption.AddAlias("-a");

            var rootCommand = new RootCommand("Move/Rename SkyScan Dataset Folder acquire by Sample Changer to new Destination.");
            rootCommand.AddOption(destinationOption);
            rootCommand.AddOption(autoOption);
            rootCommand.Add(foldersArgument);

            rootCommand.SetHandler((foldersValue, destinationOptionValue, autoOptionValue) =>
            {
                MessageBox.Show($"folders = {foldersValue}");
                MessageBox.Show($"--destination = {destinationOptionValue}");
                MessageBox.Show($"--auto = {autoOptionValue}");
            },
                foldersArgument, destinationOption, autoOption);

            rootCommand.Invoke(args);

            // To customize application configuration such as set high DPI settings or default font,
            // see https://aka.ms/applicationconfiguration.
            ApplicationConfiguration.Initialize();
            Application.Run(new MainForm(args));
        }
    }
}

devenv_SkyScan_(正在运行)_-_Microsoft_Visual_Studio_0204


Also, as for #1389, I understand it to be in this form. For example, Notepad2 and DataViewer are desktop programs, but support many startup parameters. While -?, an additional window will pop up to display the help content

TotalCMD64_Total_Commander_(x64)_11 03_-_David_Attie_0206

DataViewer_Untitled_-_DataViewer_0205

KalleOlaviNiemitalo commented 2 months ago

System.CommandLine 2.0.0-beta4.22272.1 itself does not use Windows Forms and thus does not display help in a MessageBox.

I suppose your application could define a class that implements IConsole and collects the output to a StringBuilder, set that as InvocationContext.Console, and display the result in a MessageBox. However, the columns of the two-column help layout would be misaligned if the MessageBox has a proportional font.

elgonzo commented 2 months ago

System.CommandLine is tailored to console applications and thus by default attempts to write all its output (help and error output) to the console. But you got a WinForms app, which doesn't have a console attached to it, hence there will be no output from System.CommandLine be visible.


I set up the following code, but nothing is prompted after running.

Because quite probably you provided invalid or incomplete CLI arguments, which would lead System.CommandLine to write an error message to the console (which your WinForms app doesn't have) and not invoke the (root) command handler.


I'd suggest you create a class implementing System.CommandLine.IConsole and pass an instance of it to the Invoke method. System.CommandLine would then use this IConsole implementation for all its output. The implementation of this class would be responsible for gathering the output generated by System.CommandLine. After the Invoke method returns, your program might then - depending on whether and which output has been collected - create a dialog with a text field/text area displaying the output from System.CommandLine using a mono-spaced font.

Mapaler commented 2 months ago

Thanks for the answer.

Part1

About first question, the rootCommand.SetHandler was not executed, I found the problem.

Previous Code

I'll start by following the tutorial to build such code in console mode.

using System;
using System.CommandLine;

var foldersArgument = new Argument<string>
    (name: "folders",
    description: "Folders that need to be moved.");

var destinationOption = new Option<DirectoryInfo>(
    name: "--destination",
    description: "The file to read and display on the console.");
destinationOption.AddAlias("-dst");

var autoOption = new Option<bool>(
    name: "--auto",
    description: "Auto start process.");
autoOption.AddAlias("-a");

var rootCommand = new RootCommand("Move/Rename SkyScan Dataset Folder acquire by Sample Changer to new Destination.");
rootCommand.AddOption(destinationOption);
rootCommand.AddOption(autoOption);
rootCommand.Add(foldersArgument);

rootCommand.SetHandler((foldersValue, destinationOptionValue, autoOptionValue) =>
{
    Console.WriteLine($"folders = {foldersValue}");
    Console.WriteLine($"--destination = {destinationOptionValue}");
    Console.WriteLine($"--auto = {autoOptionValue}");
},
    foldersArgument, destinationOption, autoOption);

rootCommand.Invoke(args);

Changing new Argument<string> to new Argument<string[]>

using System;
using System.CommandLine;

var foldersArgument = new Argument<string[]> //Changed
    (name: "folders",
    description: "Folders that need to be moved.");

var destinationOption = new Option<DirectoryInfo>(
    name: "--destination",
    description: "The file to read and display on the console.");
destinationOption.AddAlias("-dst");

var autoOption = new Option<bool>(
    name: "--auto",
    description: "Auto start process.");
autoOption.AddAlias("-a");

var rootCommand = new RootCommand("Move/Rename SkyScan Dataset Folder acquire by Sample Changer to new Destination.");
rootCommand.AddOption(destinationOption);
rootCommand.AddOption(autoOption);
rootCommand.Add(foldersArgument);

rootCommand.SetHandler((foldersValue, destinationOptionValue, autoOptionValue) =>
{
    Console.WriteLine($"folders = {string.Join(", ", foldersValue)}"); //Changed
    Console.WriteLine($"--destination = {destinationOptionValue}");
    Console.WriteLine($"--auto = {autoOptionValue}");
},
    foldersArgument, destinationOption, autoOption);

rootCommand.Invoke(args);

Starts with the following parameters: D:\Folder1 D:\Folder2 D:\Folder3 --auto -dst D:\target WindowsTerminal_Microsoft_Visual_Studio_调试控制台_0213

Apply this change to WinForm

In the previous code in WinForm, rootCommand.SetHandler only be executed with only one Argument passed. After modifying the Argument to an array, I can finally get the multiple arguments passed.

using System.CommandLine;

namespace Move_Sample_Folder
{
    internal static class Program
    {
        /// <summary>
        ///  The main entry point for the application.
        /// </summary>
        [STAThread]
        static void Main(string[] args)
        {

            var foldersArgument = new Argument<DirectoryInfo[]>
                (name: "folders",
                description: "Folders that need to be moved.");

            var destinationOption = new Option<DirectoryInfo?>(
                name: "--destination",
                description: "The destination of folder witch you want to move.");
            destinationOption.AddAlias("-dst");

            var autoOption = new Option<bool>(
                name: "--auto",
                description: "Auto start process.");
            autoOption.AddAlias("-a");

            var rootCommand = new RootCommand("Move/Rename SkyScan Dataset Folder acquire by Sample Changer to new destination.");
            rootCommand.AddOption(destinationOption);
            rootCommand.AddOption(autoOption);
            rootCommand.Add(foldersArgument);

            rootCommand.SetHandler((foldersValue, destinationOptionValue, autoOptionValue) =>
            {
                MessageBox.Show($"folders = {string.Join(", ",foldersValue.Select(e => e.FullName))}\n" +
                    $"--destination = {destinationOptionValue}\n" +
                    $"--auto = {autoOptionValue}");
            },
                foldersArgument, destinationOption, autoOption);

            rootCommand.Invoke(args);

            // To customize application configuration such as set high DPI settings or default font,
            // see https://aka.ms/applicationconfiguration.
            ApplicationConfiguration.Initialize();
            Application.Run(new MainForm(args));
        }
    }
}

devenv_SkyScan_(正在运行)_-_Microsoft_Visual_Studio_0214

Part2

About second question, show help dialog. Based on your suggestions, I found this reference Using a Custom IConsole with System.CommandLine I still don't understand the principle, so I just used his sample code and I can get the output. devenv_SkyScan_(正在运行)_-_Microsoft_Visual_Studio_0217