Hey everyone. I was requested to remake this guide, so I am. It's a lot to rewrite, so don't expect perfect grammar and spelling (I don't have the time to check). Also, I'm not saying that this is the most efficient way of doing things - I'm just showing you how I do it for my server. I'm also assuming that you're working on a Conquer Online server, but remember that this can be applied to any C# application, really.
Creating the Graphical User Interface:
- I'm going to be using an example project for this guide. As I said, you can use any project. If your project isn't a Windows Application (it's a Console Application), then you'll have to change that. To do that, right click on your project and select "Properties" (or select it and press Alt+Enter).
In the properties window, on the Application tab (default if you've never opened properties before), you'll see "Output Type". Change that to "Windows Application".
- Cool. So let's make the graphical user interface (GUI) now. The technology I'll be using in this tutorial is Windows Forms. It's all we need for something like this that has no need for heavy graphical updates (what we're trying to avoid with a server application). I don't care where you put your form, but you're going to right click on your project again and this time select "Add" and "New item" (from the submenu).
Feel free to name this whatever you want. I called mine "Server.cs" for this example. Remember to keep the ".cs" at the end of the name (it's the file extension for C-Sharp). All references needed to run the GUI will be added to your project automatically.
- Alright. So now you have a little GUI to play around with. Let's customize a few things before we get started. To customize the GUI, right click on it and select "Properties". It'll bring up the side bar that you see below.
Changing the text will change the name of the window upon start up. You can also set the icon for the task bar using the Properties window.
- Now we need to add the GUI to the program. We can to do this in the main function (the entry point for the program). It's important that we do not put the GUI on the main thread (because we want to run the server on the main thread). In this example, I'll be creating the GUI on its own thread. To do this, you'll have to import the following namespaces like so:
After adding the namespaces, you'll start a new thread to run the server. You can do this in an anonymous inner function (shown below). You can also create a separate function if you wish (this is a bit more compact though).Code:using System.Threading; using System.Windows.Forms;
You'll also have to add the "[MTAThread]" attribute to your main function so the server loads on a multi-threaded apartment level main thread. This is very important towards the performance of your server.Code:[MTAThread] static void Main(string[] args) { Server server = new Server(); new Thread(() => { Application.EnableVisualStyles(); Application.SetCompatibleTextRenderingDefault(fals e); Application.Run(server); }) { Priority = ThreadPriority.Lowest }.Start(); }
- Now if you run your project, you'll see the GUI pop up (as shown below). Unfortunately, the close option will not always work (because your server will hopefully make use of multithreading). We'll have to make a close action to terminate the server upon close.
To create a new event that closes the server when the form closes, go back to your GUI properties window and click on the lightning bolt icon. To the right of "FormClosed", double click and it will create a new event automatically for you.
Then you'll want to terminate the server (using "System.Environment.Exit" as shown below).
Code:private void Server_FormClosed(object sender, FormClosedEventArgs e) { // TODO: Whatever Logic you want to do before the server terminates. Environment.Exit(0); }
- Great, so now we both have a running example of a GUI. Our goal is to create a console inside of the GUI. First, we need to prepare the GUI so it's capable of accepting text from the other threads (without the use of cross-threading since Windows Forms are not thread safe). First, let's create a Text Box. To do that, you'll need to open the Toolbox window (Ctrl + Alt + X). Select the "TextBox" item and make the output box in the GUI. This is going to be out console.
Now click on the textbox and select "Properties". From the properties window, select Multiline and change that to "True" (as shown above). You can also change the colors of the window here. I also recommend you change the "Readonly" to true (if you don't want people writing to the console). If you do that, you'll have to change the backcolor back to "Window". In that window, you'll also want to change it's name to "consoleTextBox".
- With the textbox in place, it's now time to add support methods for writing to it. We're going to do this by creating a delegate method declaration and definition. I created a class for my
that will do this for you. Below is that class (change the namespace to fit wherever you're putting it).
Code:// Created by Spirited Fang for the Burning Skies project. // Released for public use. namespace Kibou.OutputManagement { using System.Windows.Forms; using System.IO; // Delegate Function Declarations: /// <summary> /// This method appends a line to the application's text box. If the text being passed is empty, it will not /// execute and return to the parent function / method. If there is no room for the new line in the text box, /// the text box will be cut to make room. /// </summary> /// <param name="line">The line being appended to the text box.</param> public delegate void LineWriter(string line); /// <summary> /// This class creates a new implementation of the abstract string text stream class to redirect an output stream /// to a text box. It accepts a text box from the active application and is programmed to override the Console /// class defined by System in the .NET Framework. /// </summary> public sealed class ConsoleRedirect : TextWriter { // Class Scope Variable Declarations: private TextBox _outputTextBox; // This text box is where the output will be redirected to. private LineWriter _writer; // Controls all appending lines to the text box console. // Constructor: /// <summary> /// This class creates a new implementation of the abstract string text stream class to redirect an output /// stream to a text box. It accepts a text box from the active application and is programmed to override the /// Console class defined by System in the .NET Framework. /// </summary> /// <param name="textBox">Where the output will be redirected to.</param> /// <param name="writer">Controls all appending lines to the text box console.</param> public ConsoleRedirect(TextBox textBox, LineWriter writer) { // Set the output textbox. _outputTextBox = textBox; _writer = writer; } /// <summary> /// This method appends a line to the application's text box. If the text being passed is empty, it will not /// execute and return to the parent function / method. If there is no room for the new line in the text box, /// the text box will be cut to make room. /// </summary> /// <param name="line">The line being appended to the text box.</param> public void AppendLine(string line) { try { // Error check the string contents & text box, then invoke the append line method: if (line != string.Empty && _outputTextBox != null) _outputTextBox.BeginInvoke(_writer, line); } catch (System.Exception e) { // If the text box isn't null, try to display an error: if (_outputTextBox != null && _outputTextBox.Created && !_outputTextBox.Disposing) _outputTextBox.BeginInvoke(_writer, e); } } #region Property Overrides // Encoding and Format Overrides: public override System.Text.Encoding Encoding { get { return System.Text.Encoding.UTF8; } } public override System.IFormatProvider FormatProvider { get { return System.Threading.Thread.CurrentThread.CurrentCultu re; } } public override string NewLine { get { return "\r\n"; } set { throw new System.NotImplementedException(); } } #endregion #region Method Overrides // Write Overrides: public override void Write(bool value) { AppendLine(value.ToString()); } public override void Write(char value) { AppendLine(value.ToString()); } public override void Write(char[] buffer) { string value = ""; foreach (var character in buffer) value += character.ToString() + " "; AppendLine(value); } public override void Write(char[] buffer, int index, int count) { string value = ""; for (; index < index + count; index++) value += buffer[index].ToString() + " "; AppendLine(value); } public override void Write(decimal value) { AppendLine(value.ToString()); } public override void Write(double value) { AppendLine(value.ToString()); } public override void Write(float value) { AppendLine(value.ToString()); } public override void Write(int value) { AppendLine(value.ToString()); } public override void Write(long value) { AppendLine(value.ToString()); } public override void Write(object value) { AppendLine(value.ToString()); } public override void Write(string format, object arg0) { AppendLine(string.Format(this.FormatProvider, format, new object[] { arg0 }) .Replace("\n", NewLine)); } public override void Write(string format, object arg0, object arg1) { AppendLine(string.Format(this.FormatProvider, format, new object[] { arg0, arg1 }) .Replace("\n", NewLine)); } public override void Write(string format, object arg0, object arg1, object arg2) { AppendLine(string.Format(this.FormatProvider, format, new object[] { arg0, arg1, arg2 }) .Replace("\n", NewLine)); } public override void Write(string format, params object[] arg) { if (arg != null) AppendLine(string.Format(this.FormatProvider, format, arg) .Replace("\n", NewLine)); else AppendLine(string.Format(this.FormatProvider, format, null) .Replace("\n", NewLine)); } public override void Write(string value) { AppendLine(value.Replace("\n", NewLine)); } public override void Write(uint value) { AppendLine(value.ToString()); } public override void Write(ulong value) { AppendLine(value.ToString()); } // Write Line Overrides: public override void WriteLine() { AppendLine(NewLine); } public override void WriteLine(bool value) { AppendLine(value.ToString() + NewLine); } public override void WriteLine(char value) { AppendLine(value.ToString() + NewLine); } public override void WriteLine(char[] buffer) { string value = ""; foreach (var character in buffer) value += character.ToString() + " "; AppendLine(value + NewLine); } public override void WriteLine(char[] buffer, int index, int count) { string value = ""; for (; index < index + count; index++) value += buffer[index].ToString() + " "; AppendLine(value + NewLine); } public override void WriteLine(decimal value) { AppendLine(value.ToString() + NewLine); } public override void WriteLine(double value) { AppendLine(value.ToString() + NewLine); } public override void WriteLine(float value) { AppendLine(value.ToString() + NewLine); } public override void WriteLine(int value) { AppendLine(value.ToString() + NewLine); } public override void WriteLine(long value) { AppendLine(value.ToString() + NewLine); } public override void WriteLine(object value) { AppendLine(value.ToString() + NewLine); } public override void WriteLine(string format, object arg0) { AppendLine(string.Format(this.FormatProvider, format, new object[] { arg0 }) .Replace("\n", NewLine) + NewLine); } public override void WriteLine(string format, object arg0, object arg1) { AppendLine(string.Format(this.FormatProvider, format, new object[] { arg0, arg1 }) .Replace("\n", NewLine) + NewLine); } public override void WriteLine(string format, object arg0, object arg1, object arg2) { AppendLine(string.Format(this.FormatProvider, format, new object[] { arg0, arg1, arg2 }) .Replace("\n", NewLine) + NewLine); } public override void WriteLine(string format, params object[] arg) { if (arg != null) AppendLine(string.Format(this.FormatProvider, format, arg) .Replace("\n", NewLine) + NewLine); else AppendLine(string.Format(this.FormatProvider, format, null) .Replace("\n", NewLine) + NewLine); } public override void WriteLine(string value) { AppendLine(value.Replace("\n", NewLine) + NewLine); } public override void WriteLine(uint value) { AppendLine(value.ToString() + NewLine); } public override void WriteLine(ulong value) { AppendLine(value.ToString() + NewLine); } #endregion } } - With that code, you'll be able to redirect all output in your project. You'll be initializing it in your GUI's constructor, but first we need to add the append line method. Right click on your GUI and select "View Code". Then, add the following code to your class:
The word wrapping is a bit inefficient for servers, so try to avoid having to use it too much. It's only there for protection (you shouldn't have to use it if you're doing normal amounts of text).Code:/// <summary> /// This method appends a line to the application's text box. If the text being passed is empty, it will not /// execute and return to the parent function / method. If there is no room for the new line in the text box, /// the text box will be cut to make room. /// </summary> /// <param name="line">The line being appended to the text box.</param> public void AppendLine(string line) { // Since the text box only has room for a certain amount of characters, we must make room for the new line: while (consoleTextBox.Text.Length + line.Length > consoleTextBox.MaxLength) consoleTextBox.Text = consoleTextBox.Text.Remove(0, consoleTextBox.Text.IndexOf(Console.Out.NewLine) + 2); // Append the new line: consoleTextBox.AppendText(line); }
- With the append line method finished, we're now going to add the console redirect to the form's constructor. Here's an example showing that:
Code:public Server() { InitializeComponent(); Console.SetOut(new ConsoleRedirect(consoleTextBox, new LineWriter(AppendLine))); } - Take note that you will not be able to use the console output until the GUI window is created (don't worry, it won't crash using the class I provided you with). We can tell the server to wait though until the server's GUI has completed loading (the constructor has been run). You do that by writing the following loop (below). Feel free to add that to the main function after the thread start.
That that will do is check the GUI every 100 milliseconds to see if it's created. If it hasn't been created, it will keep waiting. After doing that, you should have a working console in your GUI (that doesn't rely on print timers)!Code:while (!server.Created) Thread.Sleep(100);
- Now, this is all great, and you're done and all, but servers really shouldn't have a GUI. They're programmed for efficiency, not consumer usability (unless you're releasing the server as a consumer product, of course). Even servers like Minecraft have a switch though that allows it to run as a console program. Let's make that switch using command line arguments (this is completely optional).
The above code is going to be true when the command line argument is "true". This is how the user is going to run the server as a console program.Code:// Check if the user wants to start the program in console mode: Boolean consoleMode; if (args.Length > 0 && Boolean.TryParse(args[0], out consoleMode)) { }
- Now we need to create a console window for the server. You can do this using a class I created (again) for my
. The class allocates memory and creates a new console for the server (unless the program is being created in a console window already, in which case it will attach to that process). Again, you will change the namespace to your default namespace.
Code:// Created by Spirited Fang for the Burning Skies project. // Released for public use. namespace Kibou.OutputManagement { using System; using System.Diagnostics; using System.Runtime.InteropServices; /// <summary> /// This class encapsulates a function that creates a new console window in a Windows Application project. The /// servers use this function to open a new command window (if the command line arguments at index one is set to /// "true"). /// </summary> public static class ConsoleCreation { /// <summary> Allocates a new console for the calling process. </summary> [DllImport("kernel32.dll", SetLastError = true)] static extern bool AllocConsole(); /// <summary> Detaches the calling process from its console. </summary> [DllImport("kernel32.dll", SetLastError = true)] static extern bool FreeConsole(); /// <summary> Attaches the calling process to the console of the specified process. </summary> /// <param name="dwProcessId">The identifier of the process whose console is to be used.</param> [DllImport("kernel32", SetLastError = true)] static extern bool AttachConsole(int dwProcessId); /// <summary> /// Retrieves a handle to the foreground window (the window with which the user is currently working). /// The system assigns a slightly higher priority to the thread that creates the foreground window than /// it does to other threads. /// </summary> [DllImport("user32.dll")] static extern IntPtr GetForegroundWindow(); /// <summary> /// Retrieves the identifier of the thread that created the specified window and, optionally, the identifier /// of the process that created the window. /// </summary> /// <param name="hWnd">A handle to the window. </param> /// <param name="lpdwProcessId">A pointer to a variable that receives the process identifier.</param> [DllImport("user32.dll", SetLastError = true)] static extern uint GetWindowThreadProcessId(IntPtr hWnd, out int lpdwProcessId); /// <summary> /// This function creates a new console command window using invoke methods from Window's standard libraries. /// If the window already exists (the program is being run from the command prompt), this function will attach /// the program to it. It returns true if the console is being created. /// </summary> public static void Handle() { // Get the process of the uppermost window (taking that that's the shell calling it, if it exists). If it // exists, and the process name is "cmd", then attach to that console window. IntPtr ptr = GetForegroundWindow(); int processId; GetWindowThreadProcessId(ptr, out processId); Process process = Process.GetProcessById(processId); // If the process exists, and the process name is "cmd", then attach to that console window. if (process != null && process.ProcessName == "cmd") AttachConsole(processId); else // There is no console. Let's allocate memory for a new one and run it: AllocConsole(); } /// <summary> This function frees the console allocated by the program. </summary> public static void Free() { FreeConsole(); } } } - All you have to do now is implement the class similarly to how I implemented it below. You can also put your server logic in a new function so you don't have to duplicate anything (I highly suggest you do that since that's one of the main points of using an object oriented language).
Code:[MTAThread] static void Main(string[] args) { // Check if the user wants to start the program in console mode: Boolean consoleMode; if (args.Length > 0 && Boolean.TryParse(args[0], out consoleMode) && consoleMode) { ConsoleCreation.Handle(); Console.Title = "Example"; StartServer(); ConsoleCreation.Free(); // Frees the console when the application is closing. } else { // Create the GUI: Server server = new Server(); new Thread(() => { Application.EnableVisualStyles(); Application.SetCompatibleTextRenderingDefault(fals e); Application.Run(server); }) { Priority = ThreadPriority.Lowest }.Start(); while (!server.Created) Thread.Sleep(100); StartServer(); } } static void StartServer() { // TODO: Server Logic } - Now it's time to test it. You can test it by opening your project's properties again and going to the "Debug" tab. In that tab, you have "Command line arguments" under "Start Options". Put "true" there and you'll be able to test console mode.
Creating Menus for Commands:
- This is another optional group of instructions for if you have command line server commands and want to implement them into your GUI. There are many ways you can do this, but the cleanest way would be to create a menu. Reopen your GUI and the toolbox (Ctrl + Alt + X) and double click on "MenuStrip".
For my example, I chose "System" for my render mode (click the arrow in the top right corner after selecting it). I chose that because we want to minimize rendering.
- Alright. Now type something in the "Type Here" field. I'm going to type "File" in as the name for my example menu. From there, you'll get a quick feel for the process of creating menu items and submenus. Now, when you click on a menu item, it's supposed to perform an action (which we'll need to define). Doing so is quite easy, just double click on the menu item that you want to create an action for (and it will be created automatically). In my example, I'm creating a "Clear Console" command.
Code:private void clearConsoleToolStripMenuItem_Click(object sender, EventArgs e) { consoleTextBox.Clear(); }
- Cool. Now let's create a command that will kill all open clients (since they tend to crash a lot when updating packets and such). I created this menu to hold it (below). Now, the process of killing all clients is quite simple. For each process running, if the process is "Conquer", kill the process. Below is also my code from my Burning Skies project for killing processes.
Code:/// <summary> This method kills all open clients. </summary> private void killAllClientsToolStripMenuItem_Click(object sender, EventArgs e) { Console.WriteLine("Killing clients..."); Process[] processes = Process.GetProcessesByName("Conquer"); foreach (Process client in processes) if (client.ProcessName.Length == "Conquer".Length) try { client.Kill(); } catch { /* The process cannot be killed at this time. */ } }
Alright, so we have a great working GUI for your server and my example project. Feel free to customize your GUI in any way you want. Explore the properties window for the GUI and the components in the GUI (such as your text box console). Here's the final product of my example GUI project:
I made it 90% opaque just to show you something a bit different. Keep it simple if you're running it on a server though (do 100% opaque or better use the console mode). So that's it. Congrats on making a GUI from scratch. Good luck with your project. Feel free to check out mine as well (in my signature where I have a lot of other releases that you could also check out).
Post a picture of your result because I'd love to see it.
Cheers,
Spirited Fang






