65.9K
CodeProject 正在变化。 阅读更多。
Home

RunCmd - (一个WPF-MVVM批处理文件编辑器/运行程序。使用命令行批处理文件自动化重复性任务)

starIconstarIconstarIconstarIconstarIcon

5.00/5 (10投票s)

2014年11月5日

CPOL

7分钟阅读

viewsIcon

31039

downloadIcon

1462

RunCmd 是一款Windows批处理文件编辑器和运行程序。它可以用于通过命令行批处理文件来自动化我们重复性的任务。

引言

RunCmd 是一款用WPF - MVVM模式编写的微型批处理文件编辑器/运行程序。它可以用于创建/编辑批处理文件并执行它们。RunCmd 也充当一个存储批处理文件的通用场所。我们可以快速找到我们的批处理文件(使用过滤文本框)并运行它们(双击文件名或选择后点击运行)。

MVVM框架是**这篇文章**中所描述的极简MVVM框架。我不会详细介绍我们的MVVM实现,您可以在**此链接**的文章中探索。

背景

很多时候,我们在Windows环境中需要执行一些重复性的任务,而这些任务可以通过命令行快速完成。(例如,**这里**和**这里**是一些可以通过命令行运行的Windows系统应用程序列表。)

另外,作为开发者,我们经常需要查找一些特定的位置或执行一些特定的程序——我们知道这些都可以通过命令行自动化。例如:

  • 自动化启动我们喜欢的应用程序和文件夹——这样,我们的应用程序和文件夹每天早上都可以一键访问。
  • 运行我们的/bin/Debug/myprogram.exe/bin/Release/myprogram.exe而不附加Visual Studio调试器。
  • 或者运行带命令行参数的myprogram.exe
  • 自动化启动Windows控制面板应用程序(如“**程序和功能**”、“**IIS管理**”、“**系统属性**”、“**设备管理器**”等)——这可以通过命令行/批处理文件轻松完成。
  • 以管理员模式启动Visual Studio {就像我使用nircmd elevate来做的那样}
  • 运行NAnt构建或运行其他命令行工具。
  • 这些是我发现这款应用对我很有帮助的一些用途。基本上,这个简单的工具可以有很多种用途。

此外,记住所有命令可能也不太方便。如果我们有一个可以帮助我们创建、编辑、查找和运行批处理文件的工具,这可以极大地加快我们的日常工作速度。

RunCmd 试图通过提供一个通用的地方来创建、编辑、查找和运行我们的批处理文件来实现这一点。

使用RunCmd

RunCmd 的使用非常简单。

  1. 主视图
    1. 主视图是我们应用程序的主要工作区。左侧是我们的批处理文件列表。右侧顶部是输入文本框(批处理文件内容),底部是输出文本框(运行批处理文件的输出)。我们有一些按钮(保存、新建、运行等)可以用来执行操作。
    2. 我们可以单击文件列表中的文件来选择它进行编辑或运行。我们可以双击左侧文件列表中的任何文件来运行它。
    3. 一旦选中,批处理文件的内容将显示在右侧顶部的文本框中。我们可以编辑内容并保存(仅保存,不运行)或运行它,或者点击“新建”来创建一个新文件。
    4. 一旦我们运行了批处理文件,我们将在右侧面板的“**输出**”文本框中看到控制台的输出。
  2. 选项窗口
    • 选项窗口包含一些控件用于更改设置。设置的更改会通知MainWindow并保存在config文件中。
  3. 日志视图
    • 日志视图只是显示RunCmd生成的日志。如果我们正在将日志定向到控制台,我们通常会想要日志。如果不需要,请在“选项”窗口中关闭日志记录。
    • 这里显示的是文件列表。我们可以双击打开文件。点击“**返回**”按钮回到主视图。

工作原理

如前所述,该应用程序的MVVM部分已经在这篇文章中进行了介绍。如果您想了解我们正在使用的MVVM框架,请访问上面的链接。我将在此介绍我们为实现RunCmd添加的功能。

1. 主视图

HomeView.xaml页面的文档大纲和设计视图中(上面),我们将注意到以下几点:

1.a 左侧面板(批处理文件列表)

在左侧面板中,我们有一个带有ListViewScrollViewer,它绑定到从工作目录中提取的批处理文件名顶部的ListCollectionView(名为BatFilesView)。

来自 MSDN

“您可以将CollectionView看作是绑定源集合之上的一个层,它允许您基于排序、过滤和分组查询来导航和显示源集合,而无需修改底层源集合本身。如果源集合实现了INotifyCollectionChanged接口,由CollectionChanged事件引发的更改会传播到视图。”

因此,我们的ListCollectionViewBatFilesView)添加了一个过滤器,该过滤器在FileNames过滤文本框的Text更改时触发(该文本框绑定到HomeViewModel中的属性BatFilesFilterText。一旦此属性发生更改,我们就会触发过滤器,从而触发List相应地更新。)

1.b 右侧面板(批处理文件编辑文本框和输出文本框)

HomeView的右侧,我们将注意到用于编辑Batch文件的NameContentsTextbox。我们有一些基本按钮,它们根据名称执行标准操作。(“**保存**”按钮将当前内容保存到指定的fileName。“**新建**”按钮创建一个新文件并将SelectedBatFile设置为新文件。)这些基本上是基本的CRUD操作,我们也可以在我们的MVVM示例中看到。

我们这里感兴趣的是“**运行**”按钮的命令是如何执行的。下面是运行命令的代码。我们注意到的是,命令只是通过将刚刚保存的批处理文件作为参数传递给命令提示符来创建一个进程。我们运行这个进程的方式是:

  1. 首先,我们在单独的线程中运行此进程,因为我们不希望阻塞UI线程。我们也可以在这里直接运行,只是它会简单地阻塞UI线程,并且直到进程完成我们才会收到通知。对于长时间运行的进程(或构建),这有点令人烦恼。所以我们在单独的线程中运行此进程。并且在进程线程运行时,我已将我的处理程序附加到进程上,以使UI线程保持最新,并提供当前输出或错误。
  2. 我们附加了一个处理程序(SortOutputHandler)来捕获进程的所有输出并将其附加到我们的缓冲区(StringBuilder)中。我们将在进程结束时使用此缓冲区来创建我们的日志。
  3. 我们附加了一个处理程序来将输出附加到OutputText
  4. 我们还附加了一个处理程序来捕获Error的输出并将其附加到OutputText(以防我们的进程出错)。
  5. 所以,我们只是在新线程中运行进程,并尽量确保我们的进程能够正确终止,并将进程的输出记录到我们的日志(BuildCompleted处理程序)。

“**运行**”按钮或我们的HomeView已中继到HomeViewModel中的以下Command

private void ExecRunCmd(object obj)
        {
            if (ConfirmBeforeRun)
            {
                MessageBoxResult confirmRunResult = MessageBox.Show
                ("Are you sure you want to run this file?", "Run Batch File?", 
                MessageBoxButton.OKCancel);
                if (confirmRunResult == MessageBoxResult.Cancel)
                {
                    return;
                }
            }

            SaveOrUpdateCurrentBatFile();
            try
            {
                string[] cmdArgs = new string[]
                    {
                        "cmd /C ",
                        "\""+SelectedBatFile.TextFileName+"\""
                    };

                _process = new Process();
                _process.StartInfo = new ProcessStartInfo(ExeFileName)
                {
                    Arguments = string.Join(" ",cmdArgs),
                    UseShellExecute = false,
                    RedirectStandardOutput = true,
                    RedirectStandardError = true,
                    RedirectStandardInput = true,
                    CreateNoWindow = true,
                    WorkingDirectory = Utility.getAppBasePath()
                };

                _process.OutputDataReceived += new DataReceivedEventHandler(SortOutputHandler);
                _process.OutputDataReceived += 
                         new DataReceivedEventHandler((o, d) => AppendText(d.Data));
                _process.ErrorDataReceived += 
                         new DataReceivedEventHandler((o, d) => AppendError(d.Data));
                _process.EnableRaisingEvents = true;
                _process.Exited += new EventHandler(BuildCompleted);

                TxtOutput = string.Empty; //Initialize the output first

                    ThreadStart ths = new ThreadStart(() =>
                    {
                        bool ret = _process.Start();
                        _process.BeginOutputReadLine();
                        _process.BeginErrorReadLine();
                        //is ret what you expect it to be....
                    });
                    _workerThread = new Thread(ths);
                    _workerThread.IsBackground = true;
                    _workerThread.Start();
                    IsCmdRunning = true;

                _workerThread.Join();

            }
            catch (Exception exc)
            {
                throw (exc);
            }
        }

2. 选项窗口(弹出窗口)

选项窗口包含以网格排列的基本控件。一些标签、复选框、用于设置文件名的按钮和重置按钮。这些控件的功能非常简单,更新OptionsWindowViewModel中的属性,然后将属性保存到Settings文件(一个简单的XmlSerialized\config\AppConfig.cfg文件)。

这个窗口的有趣之处在于消息机制。一旦任何一个属性被更新,这个视图就会发布一个ObjMessage来通知所有观察者有关更改的信息。主窗口订阅我们将在选项窗口中发布的ObjMessage。我们可以在“HomeViewModel”中注意到我们ObjMessage的以下Handler。(这就是它如何在收到属性更改通知后立即更新自身。)

例如,OptionsWindowViewModel中的以下代码在其完成更新ConfirmBeforeRun属性后发布一个ObjMessage

        public bool ConfirmBeforeRun
        {
            get { return _ConfirmBeforeRun; }
            set
            {
                _ConfirmBeforeRun = value;
                Settings.Instance.ConfirmBeforeRun = value;
                Settings.Instance.Save();
                OnPropertyChanged("ConfirmBeforeRun");
                StatusMessage = string.Format
                   ("Will {0} confirm before running batch files!", value ? "" : "not");
                ObjMessage message = new ObjMessage("MainView","ConfirmBeforeRun");
                EventAggregator.Publish(message);
            }
        }

HomeWindowViewModelObjMessageHandler

        private void HandleOptionsUpdated(ObjMessage obj)
        {
            if (obj.Notification.Equals("MainView"))
            {
                switch (obj.PayLoad.ToString())
                {
                    case ("UpdateSavedCommandsPath"):
                        SavedCommandsLoc = Settings.Instance.SavedCommandsPath;
                        break;
                    case ("UpdateExePath"):
                        ExeFileName = Settings.Instance.ExePath;
                        break;
                    case ("ResetSettings"):
                        ExeFileName = Settings.Instance.ExePath;
                        SavedCommandsLoc = Settings.Instance.SavedCommandsPath;
                        ConfirmBeforeRun = Settings.Instance.ConfirmBeforeRun;
                        break;
                    case ("ConfirmBeforeRun"):
                        ConfirmBeforeRun = Settings.Instance.ConfirmBeforeRun;
                        break;
                    default:
                        break;
                }
            }
        }

3. 日志视图

LogsView只是一个简单的列表显示,显示来自RunCmd.exe的相对路径\log\*目录下的日志文件。双击文件将调用System以如下方式打开文件:

        private void ExecViewLogCmd(object obj)
        {
            System.Diagnostics.Process.Start(SelectedLogFile.TextFileName);
        }

我们使用标准的导航系统在以下内容之间进行来回导航:

        private void ExecGoBackCmd(object obj)
        {
            _eventAggregator.Publish(new NavMessage("HomeView"));
        }

有用链接

历史

  • 2014年11月5日:首次发布
© . All rights reserved.