📜 ⬆️ ⬇️

We use HTML and WebBrowser control as a UI for ordinary windows applications in C #

As you know, the WebBrowser control is just a wrapper over the ActiveX component of Internet Explorer. Therefore, it provides access to a full-fledged layout engine with all the modern buns. And if so, then we will try (I don’t know why) on its basis, to make a user interface for a regular windows application.

You could, of course, run a mini web server in the same process (for example, on HttpListener) and catch requests through it, but this is too easy, boring and unsportsmanlike. Let's try to do without network components, just for fun.

So, the task is simple - it is necessary to intercept the sending of HTML forms and display a new HTML document generated by the application logic depending on the POST parameters.

First of all, we need a Windows Forms Application with a single form on which there will be a single control - WebBrowser occupying all the space.
')
For example, we draw a couple of simple interface pages (with minimal content for short, and add scripts and styles to taste).

First page:

<!DOCTYPE html> <html> <head><meta http-equiv="X-UA-Compatible" content="IE=11"></head> <body> <form method="post"> <input type="text" name="TEXT1" value="Some text" /> <input type="submit" value="Open page 2" name="page2" /> </form> </body> </html> 

Second page:

 <!DOCTYPE html> <html> <head><meta http-equiv="X-UA-Compatible" content="IE=11"></head> <body> <div>%TEXT1%</div> <form method="post"> <input type="submit" value="Back to page 1" name="page1" /> </form> </body> </html> 

Note: X-UA-Compatible is required for correct display of some styles, in this example they are not used, but there is a problem. I will not go into details, but without this the component draws pages in compatibility mode with something very old, with all the ensuing consequences.

WebBrowser provides several events, for example, Navigating, which is triggered before submitting a form or following a link.
Practically what is needed, but this event does not provide any information about post-parameters. GET-parameters cannot be used because our HTML-forms have no action attribute, which means that the URL will always be about: blank and there will be no information about the GET parameters.
For more detailed information about the request, you must subscribe to the BeforeNavigate2 event (read more here ) from the browser's internal COM object, since it is available through the ActiveXInstance property.
The easiest way to do this is through dynamic, so as not to mess with declaring COM interfaces:
We declare the delegate:

 private delegate void BeforeNavigate2(object pDisp, string url, int Flags, string TargetFrameName, byte[] PostData, string Headers, ref bool Cancel); 

Then we subscribe to the event (in the form constructor, the WebXrowser ActiveXInstance property will be null, because this is best done after the window is loaded, that is, in OnLoad for example):

 ((dynamic)webBrowser.ActiveXInstance).BeforeNavigate2 += new BeforeNavigate2(OnBeforeNavigate2); 

So, in PostData are post-parameters in the form of a string consisting of pairs of key = value combined by &. Separate them and put them into the dictionary:

 var parameters = (PostData == null ? string.Empty : Encoding.UTF8.GetString(PostData)) .Split('&') .Select(x => x.Split('=')) .Where(x => x.Length == 2) .ToDictionary(x => WebUtility.UrlDecode(x[0]), x => WebUtility.UrlDecode(x[1])); 

In addition, in this handler, it is better to cancel the action via the Cancel parameter, so as not to fall for about: blank once again.

Having parameters we generate the text of the new page. Theoretically, a handler manager is screwed here, choosing the necessary handler depending on the parameters, which by some patterns will collect pages from pieces, but for short, for the sake of brevity, just for an example, just open the first page using the page2 button ( It is necessary to indicate the buttons in the markup, otherwise it is impossible to determine which button was pressed from the post-parameters, and also we will replace all the lines in parentheses with the values ​​of the parameters with the following names:

  private static string Handler(IReadOnlyDictionary<string, string> parameters) { // do some useful stuff here var newPage = "Not found"; if (parameters.ContainsKey(nameof(page1))) newPage = page1; if (parameters.ContainsKey(nameof(page2))) newPage = page2; parameters.ToList().ForEach(x => newPage = newPage.Replace("{" + x.Key + "}", x.Value)); return newPage; } 

The resulting string with HTML text is written to the DocumentText property of the WebBrowser.
And here we will get an infinite page reload cycle, since setting this property will trigger a new call to OnBeforeNavigate2.
It is not possible to temporarily unsubscribe from this event, since it is called from somewhere in the message loop after the DocumentText installation returns control.
So You should always ignore every second call to the handler, since the first response is a form submission as a result of user actions that need to be processed, and the second is the display of the result that is not needed. For simplicity, we will switch the bool variable at the beginning of the OnBeforeNavigate2 handler.

 ignore = !ignore; if (ignore) return; 


And here is the result:



This is all that is needed for a minimal application. But not everything will work this way - for example, getting data about files for input type = “file”, as well as working with XMLHttpRequest for correct operation of scripts with all kinds of ajax there, is left out of the box.

Source
 using System; using System.Collections.Generic; using System.Linq; using System.Net; using System.Text; using System.Windows.Forms; namespace TestHtmlUI { internal sealed class MainForm : Form { private const string page1 = @"<!DOCTYPE html> <html> <head><meta http-equiv=""X-UA-Compatible"" content=""IE=11""></head> <body> <form method = ""post"" > <input type=""text"" name=""TEXT1"" value=""Some text"" /> <input type=""submit"" value=""Open page 2"" name=""" + nameof(page2) + @""" /> </form> </body> </html>"; private const string page2 = @"<!DOCTYPE html> <html> <head><meta http-equiv=""X-UA-Compatible"" content=""IE=11""></head> <body> <div>{TEXT1}</div> <form method=""post""> <input type=""submit"" value=""Back to page 1"" name=""" + nameof(page1) + @""" /> </form> </body> </html>"; private delegate void BeforeNavigate2(object pDisp, string url, int Flags, string TargetFrameName, byte[] PostData, string Headers, ref bool Cancel); private readonly WebBrowser webBrowser = new WebBrowser { Dock = DockStyle.Fill }; private bool ignore = true; private MainForm() { StartPosition = FormStartPosition.CenterScreen; Controls.Add(webBrowser); Load += (sender, e) => ((dynamic)webBrowser.ActiveXInstance).BeforeNavigate2 += new BeforeNavigate2(OnBeforeNavigate2); webBrowser.DocumentText = Handler(new Dictionary<string, string> { { nameof(page1), string.Empty } }); } private void OnBeforeNavigate2(object pDisp, string url, int Flags, string TargetFrameName, byte[] PostData, string Headers, ref bool Cancel) { ignore = !ignore; if (ignore) return; Cancel = true; var parameters = (PostData == null ? string.Empty : Encoding.UTF8.GetString(PostData)) .Split('&') .Select(x => x.Split('=')) .Where(x => x.Length == 2) .ToDictionary(x => WebUtility.UrlDecode(x[0]), x => WebUtility.UrlDecode(x[1])); webBrowser.DocumentText = Handler(parameters); } [STAThread] private static void Main() { Application.EnableVisualStyles(); Application.Run(new MainForm()); } private static string Handler(IReadOnlyDictionary<string, string> parameters) { var newPage = "Not found"; if (parameters.ContainsKey(nameof(page1))) newPage = page1; if (parameters.ContainsKey(nameof(page2))) newPage = page2; // do dome usefull stuff here parameters.ToList().ForEach(x => newPage = newPage.Replace("{" + x.Key + "}", x.Value)); return newPage; } } } 

Source: https://habr.com/ru/post/302432/


All Articles