Reputation: 409
I've been developing using C# from scratch for less than 3 months and what I got at present is a console application made with Visual Studio 2015. This application consumes a web service, the XML is deserialized, formatted and saved in an Access database, and lastly a success or error XML is sent back to the web server. 3 classes make up the application: The main stream class, a SOAP client class and a DB class.
Now the reason why I'm posting this it's because I need to get that console app integrated with a GUI in which some data gotten from the deserialized XML should be shown (this is an extension of the project that the stakeholders didn't think about before stepping on the developing stage), but I have no idea how to do it.
So far I am reading about "Building WPF Applications" and "Windows Forms" but the documentation is overwhelming and I don't know if I'm on the right path, so can you guys give some guidelines before I start wasting time coding stuff that maybe are not the best option to integrate my console application with a GUI? Is there a way to make the console application become a GUI based application?
I would appreciate if you provide me with practical links to tutorials in which they quickly implement GUIs, my time is running out and I need to start coding right now. Thank you very much for reading this and helping me.
Upvotes: 12
Views: 31002
Reputation: 343
Here is a complex example which can be copy / paste and change with little or no effort, just change the executable for the process - and its environment vars if you need the env. I'm using this a lot for anything outside the usual c# ecosystem, frequently Python. It provides realtime output to the control on the form - in this case "textBoxStatus". The control best works if its scope is public or internal. Not necessary to run the process in Task or Thread, though one may do so if you want to improve responsivenes of your app or isolate your app from harmful events inside the foreing executable / process. Plus, if you do not run it async then the lines from standard output and standard error are delayed - under normal circuumstances after everything return back to the UI thread. The ToolBox.Tell is included just for ilustration. Not perfect, quick and dirty, but works pretty well.
C# 8.X, Debug in 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;
}
}
Upvotes: 0
Reputation: 584
If you're just looking for a barebones GUI and aren't too worried about it looking polished, I'd suggest you right click on your project -> add->Windows Form.
Then, turning your your Console App into a GUI based application is as simple as instantiating your Form-derived class and calling .ShowDialog().
Example:
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
}
It's that simple. I suggest you add add a new class to your project as a WindowsForm instead of just a class so that you have access to the form designer. Of course, it's up to you to add fields to your form class and have it populate the GUI appropriately.
Edit: if you wish replace the Console entirely, right click on your project->properties and then change "output type" to Windows Application.
Upvotes: 4
Reputation: 7189
What you're trying to achieve is actually relatively simple and straight forward. You need to launch your existing console application process from a new Windows Forms
or WPF
application and subscribe to its input and output streams.
If you don't want to show your current console application as a visible window, remember to set CreateNoWindow to true.
var processStartInfo = new ProcessStartInfo(fileName, arguments);
processStartInfo.UseShellExecute = false;
processStartInfo.ErrorDialog = false;
processStartInfo.CreateNoWindow = true;
// etc.
Here's an article that goes through all the steps: Embedding a Console in a C# Application
The above article's code can be found on GitHub if you want to take a closer look. Here's an example of what sort of embedding can be done:
With the project it's easy for you to either hide the whole console or have it side to side with some custom UI controls that do further processing with your data.
Upvotes: 6
Reputation: 764
Without entering in "why" territories, if you have a console application project you can right click the Project -> Properties -> Application and change the Output type to Class Library (or to Widows Application).
If you choose class library then you can consume your library from wherever.
Upvotes: 0
Reputation: 46
You can put a TextBox inside your form and redirect anything that would go in the console to the TextBox.
see this for more info Redirecting Console.WriteLine() to Textbox
Upvotes: 0