2

I built a very simple extension of the Winforms WebBrowser class called EditableWebBrowser, which works pretty well except when the instantiation is randomly removed from the designer code in the form where it's used. I also seem to get some occasional hanging behavior when starting the app. There's nothing special (the custom parts are primarily string manipulation) except for 2 things:

  1. It uses the SHDocVw reference in order to access the IHTMLDocument2 object.
  2. It uses MSHTML advanced interfaces in order to execute the save routine without prompting the user (using the ExecWB call).

Neither the SHDocVw nor MSHTML interfaces are used in the constructor, though, and they aren't invoked when the freezing occurs.

The control looks like this:

using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Drawing;
using System.Data;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using System.Windows.Forms;
using System.Text.RegularExpressions;
using System.IO;
using SHDocVw;
using mshtml;

namespace WindowsFormsApp1.UserControls
{
    public partial class EditableWebBrowser : System.Windows.Forms.WebBrowser
    {
        public string TempFile = "";
        public string TempFileURL { get { return "file:///" + TempFile.Replace('\\', '/'); } }

        // Private base constructor - we always want the parameterized public constructor
        private EditableWebBrowser()
        {
            InitializeComponent();
        }

        public EditableWebBrowser(bool editableOnDocumentCompleted = true, string localFile = "tmpEditableWebBrowser.html") : this()
        {
            // Set temp file
            this.TempFile = Path.GetTempPath() + localFile;

            // Make the loaded document editable?
            if(editableOnDocumentCompleted)
            {
                this.DocumentCompleted += WebBrowser1_DocumentCompleted;
            }
        }

        // Handler for making the loaded document editable
        private void WebBrowser1_DocumentCompleted(object sender, WebBrowserDocumentCompletedEventArgs e)
        {
            IHTMLDocument2 doc = (IHTMLDocument2)this.Document.DomDocument;
            doc.designMode = "On";
        }

        // Some Regex objects and some custom methods here

        private string _cleanupImportedHTML(string HTML)
        {
            ...
        }

        public void Import(string HTML = null)
        {
            ...
        }

        public void Clear()
        {
            ...
        }

        public void SaveWithoutPrompt()
        {
            // Execute the browser's "Save" command without prompting
            object inVal = "";
            object outVal = "";
            ((IWebBrowser2)this.ActiveXInstance).ExecWB(SHDocVw.OLECMDID.OLECMDID_SAVE, SHDocVw.OLECMDEXECOPT.OLECMDEXECOPT_DONTPROMPTUSER, ref inVal, ref outVal);
        }
    }
}

In the designer of the form where it's used (frmMain.Designer.cs), it's simply:

this.ewbContentEditor = new UserControls.EditableWebBrowser();

This is the line that is occasionally removed without any apparent pattern. However, the rest of the designer code that references it still stays in place, which obviously breaks the rest of the designer code that references it:

// 
// ewbContentEditor
// 
this.ewbContentEditor.Dock = System.Windows.Forms.DockStyle.Fill;
this.ewbContentEditor.Location = new System.Drawing.Point(3, 3);
this.ewbContentEditor.MinimumSize = new System.Drawing.Size(20, 20);
this.ewbContentEditor.Name = "ewbContentEditor";
this.ewbContentEditor.Size = new System.Drawing.Size(599, 375);
this.ewbContentEditor.TabIndex = 3;

I'm wondering if the issue could be related to the fact that the default parameters for the public constructor subscribe to a DocumentCompleted event, but that's my only idea at this point, and I don't know of a way to positively confirm that theory (without a definitive pattern, I don't want to just try something and assume it's fixed if it doesn't break soon).

Anyone else have any ideas or see the problem?

Also, the MSHTML interfaces were implemented using the approach described in this article: https://www.codeproject.com/Articles/2491/Using-MSHTML-Advanced-Hosting-Interfaces

marc_s
  • 732,580
  • 175
  • 1,330
  • 1,459
jhilgeman
  • 1,543
  • 10
  • 27

1 Answers1

2

You don't have a public default constructor (you made it private) and designer is picky about this. So if you have a control that should be supported from the designer you'll need a default constructor.

Also it can make sense to check if you are in Design Mode and maybe don't do some stuff that you would normally at runtime, cause they don't make real sense in design mode.

Oliver
  • 43,366
  • 8
  • 94
  • 151
  • And make sure no exceptions are thrown in design mode. – Otterprinz Feb 07 '18 at 10:45
  • What's confusing to me is that it works for a while - the designer seems to be okay (temporarily) with the public constructor that just has the default parameters. Can you elaborate on what you mean by "the design is picky about this" ? – jhilgeman Feb 07 '18 at 15:39
  • 1
    Your code example shows `private EditableWebBrowser()`. That makes problems. – Oliver Feb 07 '18 at 15:41
  • When you edit the designer and open afterwards the design mode, everything looks find. But as soon as you edit the form through the designer your designer.cs will be completely re-written and than the problems arise. – Oliver Feb 07 '18 at 15:42