我使用 C# 从头开始开发不到 3 个月,目前得到的是一个使用 Visual Studio 2015 制作的控制台应用程序。该应用程序使用 Web 服务,XML 被反序列化、格式化并保存在 Access 数据库中,最后将成功或错误 XML 发送回 Web 服务器。该应用程序由 3 个类组成:主流类、SOAP 客户端类和 DB 类。
现在我发布此内容的原因是因为我需要将控制台应用程序与 GUI 集成,其中应显示从反序列化 XML 获取的一些数据(这是利益相关者没有想到的项目的扩展)大约在进入开发阶段之前),但我不知道该怎么做。
到目前为止,我正在阅读有关 “构建 WPF 应用程序” 和 “Windows 窗体” 的内容,但是文档太多了,我不知道我是否走在正确的道路上,所以你们可以在我之前给出一些指导吗?开始浪费时间编码可能不是将我的控制台应用程序与 GUI 集成的最佳选择的东西吗?有没有办法让控制台应用程序成为基于 GUI 的应用程序?
如果您为我提供快速实现 GUI 的教程的实用链接,我将不胜感激,我的时间已经不多了,我需要立即开始编码。非常感谢您阅读本文并帮助我。
您想要实现的目标实际上相对简单且直接。您需要从新的
Windows Forms
或 WPF
应用程序启动现有的控制台应用程序进程并订阅其输入和输出流。
如果您不想将当前控制台应用程序显示为可见窗口,请记住将 CreateNoWindow 设置为 true。
var processStartInfo = new ProcessStartInfo(fileName, arguments);
processStartInfo.UseShellExecute = false;
processStartInfo.ErrorDialog = false;
processStartInfo.CreateNoWindow = true;
// etc.
这里有一篇文章介绍了所有步骤:在 C# 应用程序中嵌入控制台
如果你想仔细查看的话,可以在GitHub上找到上面文章的代码。以下是可以进行哪种嵌入的示例:
通过该项目,您可以轻松隐藏整个控制台或将其与一些自定义 UI 控件并排放置,以进一步处理您的数据。
如果您只是在寻找一个准系统 GUI 并且不太担心它看起来是否美观,我建议您右键单击您的项目 -> 添加 -> Windows 窗体。
然后,将控制台应用程序转换为基于 GUI 的应用程序就像实例化 Form 派生类并调用 .ShowDialog() 一样简单。
示例:
using System.Windows.Forms;
//Note: if you create a new class by clicking PROJECT->Add Windows Form,
//then this class definition and the constructor are automatically created for you.
//Open MyFancyForm.cs, right click on the form, and click View Code to see it.
public partial class MyFancyForm : Form
{
public MyFancyForm()
{
InitializeComponent();
}
}
static void Main(string[] args)
{
var guiForm = new MyFancyForm();
guiForm.ShowDialog();//This "opens" the GUI on your screen
}
就这么简单。我建议您将一个新类作为 WindowsForm 添加到您的项目中,而不仅仅是一个类,以便您可以访问表单设计器。当然,您可以将字段添加到表单类并使其适当地填充 GUI。
编辑:如果您希望完全替换控制台,请右键单击您的项目->属性,然后将“输出类型”更改为 Windows 应用程序。
您可以在表单中放置一个文本框,并将控制台中的任何内容重定向到文本框。
请参阅此了解更多信息 将 Console.WriteLine() 重定向到文本框
这是一个复杂的示例,可以毫不费力地复制/粘贴和更改,只需更改进程的可执行文件 - 及其环境变量(如果需要环境)。我经常将它用于通常的 C# 生态系统之外的任何事情,通常是 Python。它向表单上的控件提供实时输出 - 在本例中为“textBoxStatus”。如果控制范围是公共的或内部的,那么控制效果最好。没有必要在任务或线程中运行该进程,但如果您想提高应用程序的响应能力或将您的应用程序与外部可执行文件/进程内的有害事件隔离,则可以这样做。另外,如果您不异步运行它,那么标准输出和标准错误的行会被延迟 - 在正常情况下,在所有内容返回到 UI 线程之后。包含 ToolBox.Tell 只是为了说明。不完美,又快又脏,但效果很好。
C# 8.X,在 VS22 中调试
Task parcelPoly = Task.Run(() =>
{
// Creating geopackage parcel_polygon.gpkg
using (Process ps = new Process())
{
int psExitCode = 0;
try
{
ps.StartInfo.EnvironmentVariables["OSGEO4W_ROOT"] = @"C:\Program Files\QGIS 3.24.3";
ps.StartInfo.EnvironmentVariables["PATH"] = ps.StartInfo.EnvironmentVariables["OSGEO4W_ROOT"] + @"\bin;" + Environment.GetEnvironmentVariable("PATH");
ps.StartInfo.EnvironmentVariables["PATH"] = Environment.GetEnvironmentVariable("WINDIR") + @"\system32;" + ps.StartInfo.EnvironmentVariables["PATH"];
ps.StartInfo.EnvironmentVariables["PATH"] = Environment.GetEnvironmentVariable("WINDIR") + @";" + Environment.GetEnvironmentVariable("WINDIR") + @"\system32\WBem;" + ps.StartInfo.EnvironmentVariables["PATH"];
ps.StartInfo.EnvironmentVariables["GDAL_DATA"] = ps.StartInfo.EnvironmentVariables["OSGEO4W_ROOT"] + @"\share\gdal";
ps.StartInfo.EnvironmentVariables["GDAL_DRIVER_PATH"] = ps.StartInfo.EnvironmentVariables["OSGEO4W_ROOT"] + @"\bin\gdalplugins";
ps.StartInfo.EnvironmentVariables["PROJ_LIB"] = ps.StartInfo.EnvironmentVariables["OSGEO4W_ROOT"] + @"\share\proj";
ps.StartInfo.EnvironmentVariables["GDAL_FILENAME_IS_UTF8"] = @"YES";
ps.StartInfo.WorkingDirectory = "C:\HereIdoTheWork";
ps.StartInfo.FileName = @"C:\Program Files\QGIS 3.24.3\bin\ogr2ogr.exe";
ps.StartInfo.Arguments = @"-f GPKG parcel_polygon.gpkg parcel_polygon.shp -nlt MULTIPOLYGON -lco SPATIAL_INDEX=NO -sql ""SELECT pin,longtitude,latitude,x,y FROM parcel_polygon"""; //argument
ps.StartInfo.UseShellExecute = false;
ps.StartInfo.RedirectStandardOutput = true;
ps.StartInfo.RedirectStandardError = true;
ps.OutputDataReceived += (sender, args) => ToolBox.Tell(textBoxStatus, @"OGRSTD POL : " + args.Data); // Handle output line by line
ps.ErrorDataReceived += (sender, args) => ToolBox.Tell(textBoxStatus, @"OGRERR POL : " + args.Data); // Handle errors line by line
ps.StartInfo.WindowStyle = ProcessWindowStyle.Hidden;
ps.StartInfo.CreateNoWindow = true;
ps.Start();
ps.BeginOutputReadLine();
ps.BeginErrorReadLine();
ps.WaitForExit();
psExitCode = 0;
// Exit evaluation
if (psExitCode != 0)
{
ToolBox.Tell(textBoxStatus, "Failed ogr2ogr shapefile conversion to gpkg for Parcel Polygon");
throw new Exception(psExitCode.ToString());
}
else
{
ToolBox.Tell(textBoxStatus, "Ogr2ogr shapefile conversion to gpkg for Parcel Polygon executed successfully.");
}
// Force the creation of timestamp on the local file
File.SetCreationTime(@"C:\HereIdoTheWork\" + @"parcel_polygon.gpkg", DateTime.Now);
}
catch (Exception ex)
{
ToolBox.Tell(textBoxStatus, $"Failed ogr2ogr shapefile conversion to gpkg for Parcel Polygon, error code : {ex}");
ToolBox.Error(ex, "Failed ogr2ogr shapefile conversion to gpkg for Parcel Polygon");
MessageBox.Show(
@"==> Take screenshot & Notify Admin <==" + Environment.NewLine + Environment.NewLine +
@$"Failed ogr2ogr shapefile conversion to gpkg for Parcel Polygon, error code : {ex}", "Critical Error", MessageBoxButtons.OK, MessageBoxIcon.Error);
MessageBox.Show("Will exit application", "Critical Error - Exiting Application", MessageBoxButtons.OK, MessageBoxIcon.Error);
Application.Exit();
}
finally
{
// Ensure that all processes, including subprocesses, are killed
try
{
ps.Kill(entireProcessTree: true); // Kill parent and child processes
}
catch (InvalidOperationException)
{
// Handle the case where the process might have already exited
ToolBox.Tell(textBoxStatus, "Process for ogr2ogr for Parcel Polygons already exited.");
}
// Clean up resources
ps.Close();
ps.Dispose();
}
}
});
await parcelPoly; // for async execution
internal class ToolBox
{
private static readonly object _fileLockForTell = new object();
internal static void Tell(TextBox textBoxStatus, string msg = "", bool sameline = false, bool suppresstime = false)
{
string text = msg + @" " + (string.IsNullOrEmpty(msg) ? " " : suppresstime ? "" : DateTime.Now.ToString("yyyy-MM-dd HH:mm:ss")) + Environment.NewLine;
string logfile = @"C:\HereIdoTheWork\" + @"lgdr\" + DateTime.Now.ToString("yyyy-MM-dd");
DirectoryInfo dirInfo = new (@"C:\HereIdoTheWork\" + @"lgdr");
if (!dirInfo.Exists)
{
dirInfo.Create();
}
// Log the msg to file - append if exists and create if not
lock (_fileLockForTell)
{
File.AppendAllText(logfile, text);
}
if (textBoxStatus == null)
return;
if (sameline == true)
{
...
...
}
if (textBoxStatus.InvokeRequired)
{
// If we're not the UI thread ==> invoke
textBoxStatus.Invoke(new Action(() =>
{
textBoxStatus.AppendText(text);
}));
}
else
{
// If we're on the UI thread ==> no need to invoke
textBoxStatus.AppendText(text);
}
return;
}
}