兼顾两者:命令行和 GUI
快速开发具有许多选项的控制台应用程序,并为命令行程序提供 GUI
引言
您可能已经开发了一些命令行程序(.NET 中的控制台应用程序),它们可能是用于批处理等的实用工具或业务应用程序。通常,控制台应用程序会接受一些参数和选项,您需要编写代码来解析这些参数。我相信我们许多人都曾为此目的编写过一些临时解析器。最好有一个共享库来处理解析工作,特别是当您的参数组合超过 5 个时。我曾使用 Peter Palotas 的 Plossum 库在几个项目中解析命令行选项。这个库启发了我开发 Command Line GUI。
本文介绍:
- 如何快速开发一个带有几个参数和许多选项的控制台应用程序。Fonlow.CommandLineGui.Core.dll 兼容
Plossum
库,并具有附加功能。 - 控制台应用程序可以拥有一个 GUI,而无需进一步编程。
- 对于任何平台上的现有命令行程序,您甚至可以为其提供一个 GUI。
备注
Command Line GUI 的源代码包含 Plossum
库的一些片段,位于 Core/PlossumFragment 中,旨在为您提供相同的声明式编程 API。如果您一直在使用 Plossum
库,您可以轻松迁移到 Command Line GUI 并移除对 Plossum
库的依赖。
使用 Plossum 的命令行选项
如果您已阅读 C# 中强大而简单的命令行解析并在您的控制台应用程序中使用过 Plossum
,您可以跳过本章,直接进入下一章。
下面的演示程序通过声明式编程向您展示了在控制台应用程序中使用 Plossum
的快照。
using System;
using Plossum.CommandLine;
using Fonlow.CommandLine;
namespace MyPlossum
{
class Program
{
static int Main(string[] args)
{
var options = new Options();
CommandLineParser parser = new CommandLineParser(options);
Console.WriteLine(parser.ApplicationDescription);
parser.Parse();
Console.WriteLine(options.DurationInSecond);
if (options.Help)
{
Console.WriteLine(parser.UsageInfo.GetOptionsAsString(78));
return 0;
}
if (parser.HasErrors)
{
Console.WriteLine(parser.UsageInfo.ToString(78, true));
return 1;
}
Console.WriteLine(String.Format("{0} is executed.", parser.ApplicationName));
return 0;
}
}
[CommandLineManager(ApplicationName = "MyPlossum",
Description = "Demonstrate the power of Plossum",
EnabledOptionStyles = OptionStyles.Windows,
Copyright="Fonlow (c) 2013", Version="1.1")]
[CommandLineOptionGroup("detail", Name = "Detail")]
[CommandLineOptionGroup("other", Name = "Other")]
public class Options
{
[CommandLineOption(Aliases = "F",
Description = "Function name, e.g., /F=FirstFunction")]
public string Function { get; set; }
[CommandLineOption(Description = "URL.
e.g., /Url=http://csharpoptparse.sourceforge.net/", GroupId = "detail")]
public string Url { get; set; }
[CommandLineOption(Aliases = "du", Name = "Duration",
Description = "Duration in second.", RequireExplicitAssignment = true,
DefaultAssignmentValue = 3600.08d, GroupId = "detail")]
public double DurationInSecond { get; set; }
[CommandLineOption(Aliases = "h", Description = "Shows this help text",
GroupId = "other")]
public bool Help
{
get;
set;
}
[CommandLineOption(Aliases = "OE",
Description = "enum text", GroupId = "detail")]
public MyEnum OkEnum
{
get;
set;
}
}
public enum MyEnum { None, Hello, World, Plossum };
因此,您需要定义一个数据对象类 Options
,描述作为数据模型的选项,并用 CommandLineOptionAttribute
装饰属性,Plossum
库的命令行解析器将完成将参数解析为 Options
对象的繁重工作。
[MyPlosum.jpeg]
命令行 GUI
您可能会问,为什么一个现有的命令行程序需要 GUI。
背景
回溯到多年前,我曾发现 Robocopy.exe 用于同步两个驱动器之间的文件。这个命令行程序非常强大,拥有几十个选项,除了在线帮助外,甚至还有一个手册 robocopy.doc。对于包括我在内的许多人来说,记住选项或查阅手册显然很不方便。实际上,微软有一个Robocopy GUI,如果您在网上搜索,还有相当多的其他类似程序,然而,它们都没有让我满意。因此,我在 2009 年开发了 Better Robocopy GUI。
通常,IT 管理员喜欢 Robocopy
,而普通用户和非技术人员喜欢 Robocopy
GUI。我是一名软件开发人员,也是 Robocopy
的普通用户,所以我需要一个 Better Robocopy GUI(codeplex.com 上的死链接)。
有时,您可能开发了一个名为 DoWonderfulThings
的命令行程序,IT 管理员或支持人员将其用于日常操作;然后市场部的 Alice 也想 DoWonderfulThings
,但是,她显然不是那种会欣赏记住和键入命令行选项的人。所以您将编写 DoWonderfulThings
GUI,就像微软的 Derk Benisch 为 Robocopy
所做的那样。无论如何,您会因开发 DoWonderfulThings
GUI 而获得报酬。下次您将开发 DoBetterThings
,然后是 DoBetterThings
GUI,依此类推。如果您不介意重复这样的开发过程,并一次又一次地执行这样的工作,那么您可能对下面描述的内容不感兴趣。
发布 Better Robocopy
GUI 后不久,我发现重构程序结构使其支持人们可能希望拥有 GUI 的其他命令行程序相当容易,然后我手动创建了 Better Robocopy GUI 的一个分支,名为 Command Line GUI。Robocopy
相关功能成为 Command Line GUI 的主要插件,而您将能够为其他现有命令行程序开发插件。
一个典型的插件需要定义一个数据模型,将选项描述为属性,并用 PropertyGrid 理解的一些属性来装饰每个属性,PropertyGrid 将使用适当的 GUI 控件呈现每个属性。
namespace Fonlow.CommandLineGui.Robocopy
{
public class Options
{
const string TOP_CATEGORY = " ";
const string COPY_OPTIONS = "Copy Options";
const string LOGGING_OPTIONS = "Logging Options";
const string RETRY_OPTIONS = "Retry Options";
const string FILE_SELECTION_OPTIONS = "File Selection Options";
[Category(COPY_OPTIONS)]
[Description("Copies subdirectories (excluding empty ones).")]
[DisplayName("/S")]
[DefaultValue(false)]
public bool SlashS
{
get;set;
}
const CopyFlags fullCopy = CopyFlags.A | CopyFlags.D |
CopyFlags.O | CopyFlags.S | CopyFlags.T | CopyFlags.U;
const CopyFlags secCopy = CopyFlags.D | CopyFlags.A | CopyFlags.T | CopyFlags.S;
[Category(COPY_OPTIONS)]
[Description("Copies the file information specified by copyflags,
which can be any combination of the following :" + "\n\r" +
"D – file Data. S – file Security (NTFS ACLs)." + "\n\r" +
"A – file Attributes. O – file Ownership information." + "\n\r" +
"T – file Timestamps. U – file aUditing information." + "\n\r" +
"Source and destination volumes must both be NTFS to copy Security,
Ownership or Auditing information.")]
[DisplayName("/COPY:")]
[DefaultValue(CopyFlags.None)]
[NameValueNoSpace]
[Editor(typeof(CopyFlagsEditor), typeof(UITypeEditor))]
public CopyFlags SlashCopy
{
get;set;
}
基本上,DisplayNameAttribute
提供选项名称,DescriptionAttribute
定义显示在 PropertyGrid
提示区域的内容,DefaultValueAttribute
将让 PropertyGrid
知道是否以粗体
渲染修改后的选项值,而 CategoryAttribute
对选项进行分组,EditorAttribute
为某种类型的选项(如标记枚举)引入自定义编辑器。
这就是 Command Line GUI v1.x 的工作方式。而 v2.0 经历了主要的结构更改和界面更新,因此为命令行程序提供 GUI 变得更加容易。
带有 Plossum 的命令行 GUI
正如您到目前为止所看到的,Command Line GUI 和 Plossum
共享使用数据模型类和属性来描述选项的相同设计概念。使用 Plossum
的控制台应用程序通过使用 DisplayNameAttribute
、DefaultValueAttribute
和 EditorAttribute
等进一步装饰,可以很容易地集成到 Command Line GUI 中,这听起来很自然。然而,显然那些 BCL 属性所描述的内容与 Plossum
的 CommandLineOptionAttribute
的命名属性所描述的内容重叠。
BCL 属性 | CommandLineOptionAttribute 的命名属性 |
DisplayName | Name 或 Alias |
描述 | 描述 |
默认值 | DefaultAssignmentValue |
类别 | GroupId |
PropertyGrid
需要这些 BCL 属性来渲染 GUI 控件,而 Plossum
需要 CommandLineOptionAttribute
来解析命令行选项。并且 Options
数据模型类的这些属性的冗余描述看起来不美观且效率低下,尽管它们能正常工作。
为了简化操作,在 Command Line GUI v2.0 和 v3.0 中,插件的开发工作只需要 CommandLineOptionAttribute
,因此您可以按如下方式定义 Option
的属性
[CommandLineOption(Name = "A-", GroupId = OptionGroups.COPY_OPTIONS,
Description = "Turns off the specified attributes in copied files.
\n\rThe following attributes can be turned off:\n\r" +
"R – Read only S – System N – Not content indexed\n\rA –
Archive H – Hidden T – Temporary")]
[Editor(typeof(RashFlagsEditor), typeof(UITypeEditor))]
public Rashcneto SlashAMinus
{
get;
set;
}
[CommandLineOption(Name = "CREATE", GroupId = OptionGroups.COPY_OPTIONS,
Description = "Creates a directory tree structure
containing zero-length files only (that is, no file data is copied).")]
public bool SlashCreate { get; set; }
然后,Command Line GUI 将在运行时动态生成 PropertyGrid
可用的选项类型的 BCL 属性。
必备组件
要开发一个使用 Plossum
的控制台应用程序或为 Command Line GUI 编写插件,您需要下载 Command Line GUI 二进制文件或其源代码。
文件名 | 描述 |
Fonlow.CommandLineGui.Core.dll | 核心库,用于命令行程序 |
Fonlow.CommandLineGui.Gui.dll | GUI 组件 |
Antlr4.Runtime.v4.0.dll | Antlr .NET |
RobocopyParameters.dll | Robocopy 插件 |
CommandLineGui.exe | 主可执行文件 |
您可以查看源代码,特别是 Examples 解决方案文件夹中的 MyPlossum
和 RobocopyParameters
项目。
为现有本地命令行应用程序开发插件
以 Robocopy
为例,您可以按照以下步骤操作
步骤 1:为 Robocopy
创建一个类库。
步骤 2:添加对 Fonlow.CommandLineGui.Core.dll 的项目引用,如果某些选项需要自定义编辑器,则可选地添加对 Fonlow.CommandLineGui.Gui.dll 的引用。
步骤 3:编写一个参数数据模型类,通过属性描述每个固定参数或选项;使用 FixedParameterAttribute
装饰表示固定参数的每个属性;使用 CommandLineOptionAttribute
装饰表示选项的每个属性;并可选地使用 EditorAttribute
装饰以引入自定义编辑器。
步骤 4:在 CommandLineGui.exe.config 中注册类库的程序集。并将 RobocopyParameters.dll 复制到 Command Line GUI 的程序目录中。
以下代码适用于 Robocopy
[CommandLineManager(ApplicationName = "Robocopy",
Description = "Robocopy options", RequireExplicitAssignment = true)]
public class RobocopyOptions
{
[FixedParameter(Category = OptionGroups.TOP_CATEGORY,
Description = @"Source Directory (drive:\path or \\server\share\path).",
DisplayName = "Source", Order = 1, DefaultValue = "SourceDir")]
public string Source { get; set; }
[FixedParameter(Category = OptionGroups.TOP_CATEGORY,
Description = @"Destination Dir (drive:\path or \\server\share\path).",
DisplayName = "Destination", Order = 2, DefaultValue = "DestDir")]
public string Destination { get; set; }
[FixedParameter(Category = OptionGroups.TOP_CATEGORY,
Description = "File(s) to copy (names/wildcards: default is \"*.*\").",
DisplayName = "Files", Order = 3, DefaultValue = "FilesSeparatedBySpace")]
public string[] Files { get; set; }
[CommandLineOption(Name = "?",
Description = "Usage info and help.")]
public bool Help
{ get; set; }
[CommandLineOption(Name = "S", GroupId = OptionGroups.COPY_OPTIONS,
Description = "Copies subdirectories (excluding empty ones).")]
public bool SlashS
{
get;
set;
}
[CommandLineOption(Name = "E", GroupId = OptionGroups.COPY_OPTIONS,
Description = "Copies all subdirectories (including empty ones).")]
public bool SlashE
{
get;
set;
}
提示
- Command Line GUI 将实例化程序集中第一个被
CommandLineManagerAttribute
装饰的类,因此请确保您在程序集中只定义一个这样的类。 - 如果您想定义选项之间的包含、排除和组合规则,您可以查看
Robocopy
插件的源代码以获取一些线索。
为使用 Plossum 的控制台应用程序开发插件
您已经开发了一个使用 Plossum
的 .NET 控制台应用程序 DoWonderfulThings.exe,现在您的客户想要一个 GUI。
步骤 1:添加对 Fonlow.CommandLineGui.Core.dll 的项目引用,如果某些选项需要自定义编辑器,则可选地添加对 Fonlow.CommandLineGui.Gui.dll 的引用;并移除对 Plossum.dll 的引用。
步骤 2:在 CommandLineGUI.exe.config 中注册 DoWonderfulThings
。并将 DoWonderfulThings.exe 复制到 Command Line GUI 的程序目录中。
简而言之,您只需使用新的引用重新构建程序集,然后您就得到了 GUI。
您只需 Plossum 属性,无需引用 Plossum.dll
在 Command Line GUI v2.0 的开发过程中,我发现 Plossum
0.4 中存在一些小缺陷,这些缺陷阻止了 Command Line GUI 使用它。对于控制台应用程序,Plossum
0.4 实际上完美地实现了其最初的目的。现在 Plossum
在 Winforms 程序中运行,代码无法处理这种新环境,因此我不得不从 Plossum
0.4 中创建一个分支并修复了这些小缺陷。如果您对我所做的更改感兴趣,可以访问 https://sourceforge.net/p/commandlinegui/code/91/tree/branches/Plossum/ 并与 https://sourceforge.net/p/plossum/code/25/tree/trunk/ 进行比较。
除了这些小修复之外,在 Command Line GUI v2.0 中,我移除了 Plossum
对 C5.dll 的依赖。Peter 在 2007 年曾明确表示,他选择 C5.dll 是因为它比 .NET Framework 2 更强大且易于使用。然而,Command Line GUI v2.0 针对的是 .NET Framework 4,因此 C5.dll 在这种环境下所具有的优势正在逐渐减弱。因此,您应该使用 Plossum
v0.5,它在 API 层面与 v0.4 兼容,同时没有对 C5.dll 的依赖。
在 Command Line GUI v3.0 中,我在 ANTLR 之上重写了命令行参数解析器,并且只保留了 Plossum
代码的一小部分,以提供相同的声明式编程 API。因此,您不再需要 Plossum.dll,而是需要 Antlr4.Runtime.v4.0.dll。Fonlow.CommandLineGui.Core.dll 中的 API 与 Plossum
库兼容,并额外支持固定参数、数组以及选项的排除、包含和组合规则。
配置
配置存储在 CommandLineGui.exe.config 中。
<applicationSettings>
<Fonlow.CommandLineGui.Settings>
<setting name="PluginAllocationMethod" serializeAs="String">
<!-- Registration for handling multiple command line programs
or Mono for only 1 program listed in the first item of AsssemblyNames.-->
<value>Registration</value>
</setting>
<setting name="AssemblyNames" serializeAs="Xml">
<!--Each assembly name should not contain
file extension name such as dll or exe.-->
<value>
<ArrayOfString xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:xsd="http://www.w3.org/2001/XMLSchema">
<string>RobocopyParameters</string>
<string>MyPlossum</string>
</ArrayOfString>
</value>
</setting>
</Fonlow.CommandLineGui.Settings>
</applicationSettings>
<userSettings>
<Fonlow.CommandLineGui.Settings>
<!--This setting is effective only if PluginAllocationMethod is Registration.
And the actual value is in AppData of the user profile area.-->
<setting name="AssemblyNameOfCommand" serializeAs="String">
<value>RobocopyParameters</value>
</setting>
</Fonlow.CommandLineGui.Settings>
</userSettings>
Mono
您可以将 Command Line GUI 设置为仅通过将 PluginAllocationMethod
的值设为 Mono 来运行一个程序。
注册
如果您希望 Command Line GUI 的一个实例处理多个插件,您可以将 PluginAllocationMethod
的值指定为 Registration
。此外,您需要将相应的程序集复制到 Command Line GUI 的程序目录中,并在 AssemblyNames
设置中注册每个程序集,其中每个程序集不应包含文件扩展名,如 dll 或 exe。
摘要
Command Line GUI 项目包含一个与 Plossum 兼容的库,因此您可以使用声明式编程来定义选项并跳过编写解析器。该库支持更复杂的选项组合,如 Robocopy
中所示。
Command Line GUI 为基于 Fonlow.CommandLineGui.Core.dll 中 API 构建的命令行程序渲染 GUI 控件。
所以您将得到两全其美的结果:命令行和 GUI。
兴趣点
如果您在 Google 上搜索“C# 命令行参数解析器”,您会找到一大堆用于解析命令行参数的代码或库。您可以检查它们是否
- 支持强类型选项。
- 支持包含、排除和组合规则。
- 在应用程序代码中引入最小的开销,同时提供专业的外观。
- 拥有全面的测试套件,以确保在常见的 Windows 命令行程序格式中的正确性。
参考文献
- GUI 与命令行:哪个更好?第 1 部分和第 2 部分
- C# 中强大而简单的命令行解析
- Robocopy GUI
- RoboMirror
- 另一个 Robocopy GUI
- Better Robocopy GUI
- 命令行解析器库
历史
- 2013 年 9 月 9 日:初始版本
- 2023 年 7 月 29 日:更新