using System; using System.Diagnostics; using System.IO; using System.Runtime.InteropServices; using System.Runtime.InteropServices.ComTypes; using System.Text; using System.Windows.Forms; using System.ComponentModel; using System.Drawing; namespace onlyconnect { /// /// Implements MSHTML as active document in a control /// With thanks to Lutz Roeder /// /// also thanks to Steven Wood for the HTML Event handling idea /// and code. /// /// and thanks to Christopher Slee for the region marking /// /// and thanks to James Hancock for ideas/code re. making this a better-behaved winform /// control. /// /// and thanks to Peter Šulek (terrorix@centrum.sk) for the IHTMLTable interfaces /// /// This version does not require the Microsoft.mshtml Primary Interop Assembly. /// /// There is no specific licence but this demo code is free to use for any purpose. /// It is supplied with no warranty whatsoever, use at your own risk. /// Comments, improvements, suggestions - /// email tim@itwriting.com /// or visit the messageboard at http://www.itwriting.com/htmleditor.php /// [System.ComponentModel.ToolboxItemAttribute(true)] public class HtmlEditor : Control { #region class variables and constructor private bool bLoadDocumentWhenReady = false; private bool bLoadUrlWhenReady = false; private bool bSetComposeSettingsWhenReady = false; private int iLoadAttempts = 0; private EncodingType mDocumentEncoding = EncodingType.WindowsCurrent; private EncodingType mDefaultPreamble = EncodingType.UTF8; private bool mIsWin98 = false; private bool mAlwaysLoadAnsi = false; internal bool mLinksNewWindow = false; internal bool bNeedsActivation = false; internal bool mAcceptsTab = false; internal bool mAcceptsReturn = true; internal DocHTML mDocHTML = null; internal HtmlSite theSite; String url = String.Empty; String sDocument = String.Empty; internal bool mDesignMode = false; private bool mIsContextMenuEnabled = true; private string mOptionKeyPath = null; internal bool mAllowActivation = true; internal HTMLDocument mHtmlDoc = null; internal IHTMLElement mcurrentElement = null; private IntPtr mDocumentHandle = IntPtr.Zero; private bool mCreating = true; private NativeWindow mNativeWindow = null; private HandleWndProc mNativeDocWindow = null; private System.Windows.Forms.Timer activateTimer; private System.Windows.Forms.Timer createTimer; private System.ComponentModel.IContainer components = null; private ComposeSettings mComposeSettings = null; internal bool mEnableActiveContent = true; internal bool mEnableAutoComplete = false; internal bool mDivOnEnter = false; internal bool mShowScrollBars = true; internal bool mEnableUrlDetection = false; internal uint changeCookie; private ChangeMonitor mChangeMonitor; public HtmlEditor() { //initialize components InitializeComponent(); //Detect Windows version mIsWin98 = (System.Environment.OSVersion.Platform == PlatformID.Win32Windows); //force creation of handle, needed to host mshtml this.CreateControl(); //see OnHandleCreated for purpose of mCreating mCreating = false; } #endregion class variables and constructor #region Keyboard and mouse handling internal void InvokeOnMouseDown(MouseEventArgs e) { this.OnMouseDown(e); } internal void InvokeOnDoubleClick() { this.OnDoubleClick(EventArgs.Empty); } /// /// Executes The short cut keys that should be available and handles all of the cases of design mode versus not. /// /// The key to process. /// private bool doShortCut(Keys Key) { //fire the BeforeShortcut event and cancel if necessary BeforeShortcutEventArgs e = new BeforeShortcutEventArgs(Key); this.OnBeforeShortcut(e); //if cancelled, return True for Handled return (e.Cancel); } #endregion #region Override WndProc =============== internal void InvokeWndProc(ref Message msg) { Message message = Message.Create(this.Handle, msg.Msg, msg.WParam, msg.LParam); this.WndProc(ref message); } internal void setupWndProc() { if (this.mNativeDocWindow != null) { this.mNativeDocWindow.ReleaseHandle(); } this.mNativeDocWindow = new HandleWndProc(); this.mNativeDocWindow.thecontrol = this; this.mNativeDocWindow.AssignHandle(this.theSite.DocumentHandle); } internal void releaseWndProc() { if (this.mNativeDocWindow != null) { this.mNativeDocWindow.ReleaseHandle(); this.mNativeDocWindow = null; } } override protected void WndProc(ref Message m) { bool isHandled = false; switch (m.Msg) { case win32.WM_KEYDOWN: //if ctrl pressed, pass it to doShortCut if (win32.GetKeyState((int)Keys.ControlKey) < 0) { //check NOT alt if (!((win32.GetKeyState((int)Keys.Alt) < 0) || (win32.GetKeyState((int)Keys.LMenu) < 0) || (win32.GetKeyState((int)Keys.RMenu) < 0) )) { Keys k = (Keys)m.WParam.ToInt32(); isHandled = doShortCut(k); } } //if not handled, pass it to TranslateAccelerator if (!isHandled) { if (theSite != null) { MSG msg = new MSG(); msg.hwnd = m.HWnd; msg.wParam = m.WParam; msg.lParam = m.LParam; msg.message = m.Msg; Point pos = new Point(win32.GetMessagePos()); msg.pt_x = pos.X; msg.pt_y = pos.Y; msg.time = win32.GetMessageTime(); isHandled = theSite.CallTranslateAccelerator(msg); } } break; case win32.WM_MOUSEACTIVATE: { this.Select(); if (theSite != null) { if (!this.DesignMode) { IntPtr fromHandle = win32.GetFocus(); this.mDocumentHandle = theSite.DocumentHandle; if ((!this.Focused) & (fromHandle != this.Handle) & (fromHandle != this.mDocumentHandle)) { win32.SendMessage(this.Handle, win32.WM_SETFOCUS, (int)fromHandle, 0); } } } break; } case win32.WM_SETFOCUS: this.setFocusToMshtml(); isHandled = true; break; case win32.WM_KEYUP: break; } if (!isHandled) { base.WndProc(ref m); } } #endregion Override WndProc =============== #region Override Handle events =============== protected override void OnHandleCreated(EventArgs e) { //earlier versions of HtmlEditor always init mshtml //here. However, according to BoundsChecker that //causes a memory overrun. It also means that the //document is passed a rect with zero bounds, as //the control has not yet been sized. //Therefore, mshtml is now initialized for the first //time in OnParentChanged. In most cases, that will //be when the designer-generated code adds the control //to the form's controls collection. if (!mCreating) { initMshtml(); } } protected override void OnHandleDestroyed(EventArgs e) { CleanupControl(); base.OnHandleDestroyed(e); } #endregion #region Overrides of container control methods =============== protected override void OnParentChanged(System.EventArgs e) { if (this.Parent == null) { return; } if (theSite == null) { this.createTimer.Enabled = true; //initMshtml(); } base.OnParentChanged(e); } protected override void OnVisibleChanged(EventArgs e) { if (this.Visible) { if (this.bNeedsActivation) { this.activateTimer.Enabled = true; } } base.OnVisibleChanged(e); } protected override void OnPaint(PaintEventArgs pevent) { if (this.DesignMode) { pevent.Graphics.FillRectangle(System.Drawing.Brushes.White, this.ClientRectangle); } base.OnPaint(pevent); } public override Boolean PreProcessMessage(ref Message m) { bool callBase = true; if (m.Msg == win32.WM_KEYDOWN) { Keys k = (Keys)m.WParam.ToInt32(); if ((k == Keys.Return) && this.AcceptsReturn) { callBase = false; } if ((k == Keys.Tab) && this.mAcceptsTab) { callBase = false; } if ((k == Keys.Right) || (k == Keys.Left) || (k == Keys.Up) || (k == Keys.Down)) { callBase = false; } } if (callBase) { return base.PreProcessMessage(ref m); } else { return false; // don't call base method, don't return true } } protected override bool ProcessDialogChar(char charCode) { /* return false here to prevent this from bubbling up the hierarchy and eventually being * processed by something (a button say) that has the character set as a mnemonic, if that * happens the character is never entered into the editor so you end up missing any chars * that are a mnemonic for another control! */ return false; } #endregion #region initialization and cleanup #region initialization void initMshtml() { //don't create in design mode if (this.DesignMode) { return; } if (this.isCreated) { return; //can't create if already created } // force creating Host handle since we need it to parent MSHTML if (!this.IsHandleCreated) { IntPtr hostHandle = Handle; } theSite = new HtmlSite(this); theSite.CreateDocument(); if (this.Visible) { theSite.ActivateDocument(); } else { this.bNeedsActivation = true; this.activateTimer.Enabled = true; } this.mHtmlDoc = (HTMLDocument)theSite.Document; this.mDocHTML = new DocHTML(mHtmlDoc); if (this.mDesignMode) { IHTMLDocument2 htmldoc = (IHTMLDocument2)this.mHtmlDoc; htmldoc.SetDesignMode("On"); } theSite.SetPropertyNotifyEvents(); if (url != String.Empty) { LoadUrl(url); } else { //always initialize with at least the blank url LoadUrl("About:Blank"); } if (sDocument != String.Empty) { LoadDocument(sDocument); } else { if (this.mDesignMode) { LoadDocument(""); } } } private void InitializeComponent() { this.components = new System.ComponentModel.Container(); this.activateTimer = new System.Windows.Forms.Timer(this.components); this.createTimer = new System.Windows.Forms.Timer(this.components); this.SuspendLayout(); // // activateTimer // this.activateTimer.Interval = 1; this.activateTimer.Enabled = false; this.activateTimer.Tick += new System.EventHandler(this.activateTimer_Tick); this.createTimer.Interval = 1; this.createTimer.Enabled = false; this.createTimer.Tick += new System.EventHandler(this.createTimer_Tick); this.ResumeLayout(false); } private void activateTimer_Tick(object sender, System.EventArgs e) { this.activateTimer.Enabled = false; if (theSite != null) { theSite.ActivateDocument(); } } private void createTimer_Tick(object sender, System.EventArgs e) { this.createTimer.Enabled = false; if (!this.isCreated) { this.initMshtml(); } } #endregion #region cleanup protected override void Dispose(bool disposing) { if (disposing) { //remove timer event handlers if (this.activateTimer != null) { this.activateTimer.Tick -= new System.EventHandler(this.activateTimer_Tick); } if (this.createTimer != null) { this.createTimer.Tick -= new System.EventHandler(this.createTimer_Tick); } if (this.mComposeSettings != null) { this.mComposeSettings = null; } if (components != null) { components.Dispose(); } IntPtr ptr = Marshal.GetIDispatchForObject(this); int i = Marshal.Release(ptr); while (i > 0) { i = Marshal.Release(ptr); } if (this.mNativeWindow != null) { this.mNativeWindow.ReleaseHandle(); this.mNativeWindow = null; } } base.Dispose(disposing); } /// /// Clean up any resources being used. /// public void CleanupControl() { if (isCreated) { theSite.CloseDocument(); if (this.theSite != null) { IntPtr ptr = Marshal.GetIDispatchForObject(this.theSite); int i = Marshal.Release(ptr); while (i > 0) { i = Marshal.Release(ptr); } this.theSite.Dispose(); this.theSite = null; } this.mHtmlDoc = null; this.mDocHTML = null; } } public void ReloadMshtml() { CleanupControl(); initMshtml(); } #endregion #endregion #region Declare Events ==================================================== //declare the events public event HtmlNavigateEventHandler Navigate; public event ReadyStateChangedHandler ReadyStateChanged; public event UpdateUIHandler UpdateUI; public event HtmlKeyPressHandler HtmlKeyPress; public event HtmlEventHandler HtmlEvent; public event BeforeNavigateEventHandler BeforeNavigate; public event BeforeShortcutEventHandler BeforeShortcut; public event BeforePasteHandler BeforePaste; [Description("Fires when content is edited in design mode.")] public event EventHandler ContentChanged; #endregion Declare Events =========== #region Invoke Events ========================================================= //invoke the UpdateUI event public void InvokeUpdateUI(IHTMLElement ae) { if (UpdateUI != null) { HtmlUpdateUIEventArgs ea = new HtmlUpdateUIEventArgs(); ea.currentElement = ae; UpdateUI(this, ea); } } //invoke the ReadyStateChanged event public void InvokeReadyStateChanged(String newReadyState) { if (ReadyStateChanged != null) { ReadyStateChangedEventArgs ea = new ReadyStateChangedEventArgs(newReadyState); ReadyStateChanged(this, ea); } } public void InvokeHtmlKeyPress(ref IHTMLEventObj eobj) { if (HtmlKeyPress != null) { HtmlKeyPressEventArgs ea = new HtmlKeyPressEventArgs(ref eobj); HtmlKeyPress(this, ea); } } [DispId(0)] public void InvokeHtmlEvent() { if (HtmlEvent != null) { // Get the event. IHTMLEventObj pobjEvent = ((IHTMLDocument2) mHtmlDoc).GetParentWindow().eventobj; HtmlEventArgs ea = new HtmlEventArgs(pobjEvent); HtmlEvent(this, ea); } return; } public void InvokeNavigate(String target) { if (Navigate != null) Navigate(this, new HtmlNavigateEventArgs(target)); } internal void InvokeContentChanged() { if (ContentChanged != null) { ContentChanged(this, new System.EventArgs()); } } /// /// Fires the BeforeShortcut event. Handle this event to pre-process or cancel the /// HtmlEditor's shortcut events. /// /// Cancellable event args protected virtual void OnBeforeShortcut(onlyconnect.BeforeShortcutEventArgs e) { if (BeforeShortcut != null) BeforeShortcut(this, e); } /// /// Fires the BeforeNavigateEvent /// /// Cancellable event args protected internal virtual void OnBeforeNavigate(onlyconnect.BeforeNavigateEventArgs e) { if (BeforeNavigate != null) BeforeNavigate(this, e); } /// /// Fires the BeforePaste event /// /// protected internal virtual void OnBeforePaste(BeforePasteArgs e) { if (BeforePaste != null) BeforePaste(this, e); } #endregion Invoke Events =============== #region Setup Event Management ====================================================== internal void ReadyStateChangeActions(IHTMLEventObj o) { //defensive - I've known this to be called //after doc was deactivated if (this.mHtmlDoc == null) return; string theReadyState = this.HtmlDocument2.GetReadyState(); if (theReadyState == "complete") { //if changed to "COMPLETE", set edit designer if (this.bLoadDocumentWhenReady) { Debug.WriteLine("Now loading doc"); this.LoadDocument(string.Empty); return; } else if (this.bLoadUrlWhenReady) { Debug.WriteLine("Now loading url"); this.LoadUrl(this.url); return; } Debug.WriteLine("Setting events"); { if (this.IsDesignMode) { this.SetEditDesigner(); this.execCommand(commandids.IDM_AUTOURLDETECT_MODE, this.mEnableUrlDetection, false, false); this.setChangeNotify(); } } if (this.bSetComposeSettingsWhenReady) { this.setDefaultFont(); } //set HTMLEvents this.SetHTMLEvents(); //refresh ReadyState since the above actions could have changed it theReadyState = this.HtmlDocument2.GetReadyState(); } //invoke ready state changed event this.InvokeReadyStateChanged(theReadyState); } private void setChangeNotify() { //only set this if there is an event handler if (ContentChanged == null) { return; } int hr; this.mChangeMonitor = new ChangeMonitor(this); hr = ((IMarkupContainer2)this.HtmlDocument2).RegisterForDirtyRange(mChangeMonitor, out changeCookie); } public void SetEditDesigner() { IHTMLDocument2 htmldoc = (IHTMLDocument2)this.mHtmlDoc; htmldoc.SetDesignMode("On"); onlyconnect.IServiceProvider isp = (onlyconnect.IServiceProvider)mHtmlDoc; onlyconnect.IHTMLEditServices es; System.Guid IHtmlEditServicesGuid = new System.Guid("3050f663-98b5-11cf-bb82-00aa00bdce0b"); System.Guid SHtmlEditServicesGuid = new System.Guid(0x3050f7f9, 0x98b5, 0x11cf, 0xbb, 0x82, 0x00, 0xaa, 0x00, 0xbd, 0xce, 0x0b); IntPtr ppv; onlyconnect.IHTMLEditDesigner ds = (onlyconnect.IHTMLEditDesigner)theSite; if (isp != null) { isp.QueryService(ref SHtmlEditServicesGuid, ref IHtmlEditServicesGuid, out ppv); es = (onlyconnect.IHTMLEditServices)Marshal.GetObjectForIUnknown(ppv); int retval = es.AddDesigner(ds); Marshal.Release(ppv); } } void SetHTMLEvents() { if ((mHtmlDoc != null) && (HtmlEvent != null)) { //m_htmldoc.onkeydown = this; //keydown does not work, keypress does... IHTMLDocument2 htmldoc = (IHTMLDocument2)this.mHtmlDoc; IHTMLDocument4 htmldoc4 = (IHTMLDocument4)this.mHtmlDoc; IHTMLDocument5 htmldoc5 = (IHTMLDocument5)this.mHtmlDoc; htmldoc.SetOnkeypress(this); htmldoc.SetOnclick(this); htmldoc4.onselectionchange = this; htmldoc5.onfocusin = this; htmldoc5.onfocusout = this; } } #endregion Setup Event Management ================ #region The main public API [Browsable(false)] public IHTMLElement CurrentElement { get { return mcurrentElement; } } [System.ComponentModel.TypeConverter(typeof(System.ComponentModel.ExpandableObjectConverter)), DesignerSerializationVisibility(DesignerSerializationVisibility.Content), Description("Sets the initial font used by the editor. Disabled by default as it inserts a FONT tag.")] public ComposeSettings DefaultComposeSettings { get { if (this.mComposeSettings == null) { mComposeSettings = new ComposeSettings(this); } return mComposeSettings; } } [Description("Encoding to use when getting the string value of the HTML document"), DefaultValue(false)] public EncodingType DocumentEncoding { get { return mDocumentEncoding; } set { mDocumentEncoding = value; } } [Description("The preamble to be added by the editor if no byte order mark is detected"), DefaultValue(false)] public EncodingType DefaultPreamble { get { return mDefaultPreamble; } set { mDefaultPreamble = value; } } [Description("Indicates if return characters are accepted as input."), DefaultValue(true)] public bool AcceptsReturn { get { return this.mAcceptsReturn; } set { this.mAcceptsReturn = value; } } [Description("Indicates if tab characters are accepted as input. Set false to enable tabbing between controls."), DefaultValue(false)] public bool AcceptsTab { get { return this.mAcceptsTab; } set { this.mAcceptsTab = value; } } [Description("Turn off auto-detection of string type in LoadDocument and always treat it as an Ansi string."), DefaultValue(false)] public Boolean IsAnsiStreamAlwaysUsed { get { return this.mAlwaysLoadAnsi; } set { this.mAlwaysLoadAnsi = value; } } [Description("Is the control in edit mode so that users can edit the contents."), DefaultValue(false)] public Boolean IsDesignMode { get { return mDesignMode; } set { if (value) { mDesignMode = true; if (this.mHtmlDoc != null) { IHTMLDocument2 htmldoc = (IHTMLDocument2)this.mHtmlDoc; htmldoc.SetDesignMode("On"); if (this.mComposeSettings.Enabled) { this.setDefaultFont(); } //refresh url detection this.IsUrlDetectionEnabled = this.mEnableUrlDetection; this.LoadDocument(""); } } else { mDesignMode = false; if (this.mHtmlDoc != null) { IHTMLDocument2 htmldoc = (IHTMLDocument2)this.mHtmlDoc; htmldoc.SetDesignMode("Off"); } } } } [Description("Gets/Sets if the context menu on right clicks will be displayed."), DefaultValue(true)] public bool IsContextMenuEnabled { get { return mIsContextMenuEnabled; } set { mIsContextMenuEnabled = value; } } [Description("Gets/Sets if the control can receive the focus."), DefaultValue(true)] public bool IsActivationEnabled { get { return mAllowActivation; } set { mAllowActivation = value; } } [Description("Gets/Sets whether the Tab key, in design mode, stays within the HtmlEditor or sets focus on the next control in the tab order."), DefaultValue(true), Browsable(false)] public bool IsTabToNextControl { get { return !AcceptsTab; } set { AcceptsTab = !value; } } [Description("Gets/Sets where the control stores preferences in the registry."), DefaultValue(null)] public string OptionKeyPath { get { return this.mOptionKeyPath; } set { mOptionKeyPath = value; } } [Description("Gets/Sets if scripts, Java etc is enabled"), DefaultValue(true)] public bool IsActiveContentEnabled { //This must be set BEFORE the document is created get { return mEnableActiveContent; } set { mEnableActiveContent = value; } } [Description("Gets/Sets if autocomplete is enabled"), DefaultValue(false)] public bool IsAutoCompleteEnabled { //This must be set BEFORE the document is created get { return mEnableAutoComplete; } set { mEnableAutoComplete = value; } } [Description("Gets/Sets if urls are automatically changed to hyperlinks"), DefaultValue(false)] public bool IsUrlDetectionEnabled { get { return this.mEnableUrlDetection; } set { this.mEnableUrlDetection = value; // we does this both here and in ReadyStateCompleteActions // it doesn't take effect if doc is not ready if (this.IsDesignMode) { this.SetEditDesigner(); this.execCommand(commandids.IDM_AUTOURLDETECT_MODE, this.mEnableUrlDetection, false, true); } } } [Description("Gets/Sets if pressing Enter creates DIV rather than P"), DefaultValue(false)] public bool IsDivOnEnter { //This must be set BEFORE the document is created get { return mDivOnEnter; } set { mDivOnEnter = value; } } [Description("Gets/Sets if a vertical scroll bar is shown"), DefaultValue(true)] public bool IsScrollBarShown { //This must be set BEFORE the document is created get { return this.mShowScrollBars; } set { mShowScrollBars = value; } } [Browsable(false)] public HTMLDocument Document { get { if (theSite == null) { return null; } if (theSite.Document != null) { return (HTMLDocument)theSite.Document; } else { return null; } } } /// /// The thinking behind this is to wrap selected key properties and methods /// in a developer-friendly class - called DocHTML / DocumentHTML to distinguish /// from the other official items /// [Browsable(false)] public DocHTML DocumentHTML { get { return this.mDocHTML; } } /// /// Quick access to the key IHTMLDocument2 interface /// [Browsable(false)] public IHTMLDocument2 HtmlDocument2 { get { if (theSite == null) { return null; } if (theSite.Document != null) { return (IHTMLDocument2)theSite.Document; } else { return null; } } } [Browsable(false)] public string ReadyState { get { if (this.mHtmlDoc == null) { return String.Empty; } return HtmlDocument2.GetReadyState().ToLower(); } } /// /// Gets/Sets if links that are clicked on in the editor will be opened in the editor or launch your default browser. /// [Description("Gets/Sets if links that are clicked on in the editor will be opened in the editor or launch your default browser."), DefaultValue(false)] public bool OpenLinksInNewWindow { get { return mLinksNewWindow; } set { mLinksNewWindow = value; } } public void SetStyleSheet(string sFileName) { if (this.mHtmlDoc == null) { return; } IHTMLDocument2 htmldoc = (IHTMLDocument2)this.mHtmlDoc; if (htmldoc.GetReadyState().ToLower() == "complete") { htmldoc.CreateStyleSheet(sFileName, 0); } } public bool Copy() { return this.execCommand(commandids.IDM_COPY, null, false, true); } public bool Paste() { BeforePasteArgs e = new BeforePasteArgs(); this.OnBeforePaste(e); if (e.Cancel) { return false; } else return this.execCommand(commandids.IDM_PASTE, null, false, true); } public bool SaveAs(string DefaultPath) { return this.execCommand(commandids.IDM_SAVEAS, DefaultPath, true, true); } public bool Undo() { return this.execCommand(commandids.IDM_UNDO, null, false, true); } public bool Redo() { return this.execCommand(commandids.IDM_REDO, null, false, true); } public bool Cut() { return this.execCommand(commandids.IDM_CUT, null, false, true); } public bool SetSelectionBold() { return this.execCommand(commandids.IDM_BOLD, null, false, true); } public bool SetSelectionItalic() { return this.execCommand(commandids.IDM_ITALIC, null, false, true); } public bool ClearSelectionFormatting() { return this.execCommand(commandids.IDM_REMOVEFORMAT, null, false, true); } [Browsable(false)] public Color SelectionBackColor { get { if (Document == null) return Color.Empty; else { object Result = HtmlDocument2.QueryCommandValue("BackColor"); try { return System.Drawing.ColorTranslator.FromHtml(Result.ToString()); } catch { return Color.Empty; } } } set { //need to send a COLORREF value this.execCommand(commandids.IDM_BACKCOLOR, utils.GetCsColor(value), false, true); } } /// /// Gets/Sets the color for the selected text. /// [Description("Gets/Sets the color of the selected text."), Browsable(false)] public Color SelectionForeColor { get { if (Document == null) return Color.Empty; else { object Result = HtmlDocument2.QueryCommandValue("ForeColor"); try { return System.Drawing.ColorTranslator.FromHtml(Result.ToString()); } catch { return Color.Empty; } } } set { //need to send a COLORREF value this.execCommand(commandids.IDM_FORECOLOR, utils.GetCsColor(value), false, true); } } [Description("Gets/sets the font for the selected text"), Browsable(false)] public Font SelectionFont { get { try { //This is here because of hosting issues on a UserControl if (this.Document == null) return null; System.Drawing.FontStyle fs = new FontStyle(); int FontSize = 8; if (Convert.ToBoolean(HtmlDocument2.QueryCommandValue("bold"))) fs |= FontStyle.Bold; if (Convert.ToBoolean(HtmlDocument2.QueryCommandValue("italic"))) fs |= FontStyle.Italic; if (Convert.ToBoolean(HtmlDocument2.QueryCommandValue("underline"))) fs |= FontStyle.Underline; switch (Convert.ToInt32(HtmlDocument2.QueryCommandValue("FontSize"))) { case 1: FontSize = 8; break; case 2: FontSize = 10; break; case 3: FontSize = 12; break; case 4: FontSize = 18; break; case 5: FontSize = 24; break; case 6: FontSize = 36; break; case 7: FontSize = 48; break; } string FontName = HtmlDocument2.QueryCommandValue("FontName").ToString(); return new Font(FontName, FontSize, fs); } catch { return null; } } set { try { //This is here because of hosting issues on a UserControl if ((Document == null) | (value == null)) return; HtmlDocument2.ExecCommand("FontName", false, value.Name); if (value.SizeInPoints <= 8) { HtmlDocument2.ExecCommand("FontSize", false, 1); } else if (value.SizeInPoints <= 10) { HtmlDocument2.ExecCommand("FontSize", false, 2); } else if (value.SizeInPoints <= 12) { HtmlDocument2.ExecCommand("FontSize", false, 3); } else if (value.SizeInPoints <= 18) { HtmlDocument2.ExecCommand("FontSize", false, 4); } else if (value.SizeInPoints <= 24) { HtmlDocument2.ExecCommand("FontSize", false, 5); } else if (value.SizeInPoints <= 36) { HtmlDocument2.ExecCommand("FontSize", false, 6); } else { HtmlDocument2.ExecCommand("FontSize", false, 7); } if (Convert.ToBoolean(HtmlDocument2.QueryCommandValue("bold")) != Font.Bold) HtmlDocument2.ExecCommand("bold", false, null); if (Convert.ToBoolean(HtmlDocument2.QueryCommandValue("italic")) != Font.Bold) HtmlDocument2.ExecCommand("italic", false, null); if (Convert.ToBoolean(HtmlDocument2.QueryCommandValue("underline")) != Font.Bold) HtmlDocument2.ExecCommand("underline", false, null); } catch { } } } /// /// Gets/Sets the alignment of the selected text. /// [Description("Gets/Sets the alignment of the selected text."), Browsable(false)] public System.Windows.Forms.HorizontalAlignment SelectionAlignment { get { if (Document == null) return HorizontalAlignment.Left; else { if (Convert.ToBoolean(HtmlDocument2.QueryCommandValue("JustifyRight"))) { return HorizontalAlignment.Right; } else if (Convert.ToBoolean(HtmlDocument2.QueryCommandValue("JustifyCenter"))) { return HorizontalAlignment.Center; } else { return HorizontalAlignment.Left; } } } set { if (Document == null) return; switch (value) { case HorizontalAlignment.Left: HtmlDocument2.ExecCommand("JustifyLeft", false, null); break; case HorizontalAlignment.Center: HtmlDocument2.ExecCommand("JustifyCenter", false, null); break; case HorizontalAlignment.Right: HtmlDocument2.ExecCommand("JustifyRight", false, null); break; } } } /// /// Get/Sets if numbering is on for the selected text. /// [Description("Get/Sets if numbering is on for the selected text."), Browsable(false)] public bool SelectionNumbering { get { if (Document == null) return false; else return Convert.ToBoolean(HtmlDocument2.QueryCommandValue("InsertOrderedList")); } set { if (Document != null && Convert.ToBoolean(HtmlDocument2.QueryCommandValue("InsertOrderedList")) != value) HtmlDocument2.ExecCommand("InsertOrderedList", false, null); } } /// /// Gets/Sets if bullets are on or off for the selected text. /// [Description("Gets/Sets if bullets are on or off for the selected text."), Browsable(false)] public bool SelectionBullets { get { if (Document == null) return false; else return Convert.ToBoolean(HtmlDocument2.QueryCommandValue("InsertUnorderedList")); } set { if (Document != null && Convert.ToBoolean(HtmlDocument2.QueryCommandValue("InsertUnorderedList")) != value) HtmlDocument2.ExecCommand("InsertUnorderedList", false, null); } } public bool SelectAll() { return this.execCommand(commandids.IDM_SELECTALL, null, false, true); } public void ClearSelection() { this.HtmlDocument2.GetSelection().Empty(); } public void DeleteSelection() { this.execCommand(commandids.IDM_DELETE, null, false, true); } #region Load and save documents public void LoadDocument(String documentVal) { if ((documentVal != string.Empty) | (!this.bLoadDocumentWhenReady)) { //if doc is waiting to load, it is already in string variable this.bLoadDocumentWhenReady = false; sDocument = documentVal; } else { this.bLoadDocumentWhenReady = false; this.iLoadAttempts += 1; } if (!isCreated) { if (iLoadAttempts < 2) { this.bLoadDocumentWhenReady = true; return; } else { throw new HtmlEditorException("Document not created"); } } if ((this.HtmlDocument2.GetReadyState().ToLower() != "complete") & (this.HtmlDocument2.GetReadyState().ToLower() != "interactive")) //try to load on interactive as well as complete { if (iLoadAttempts < 2) { this.bLoadDocumentWhenReady = true; return; } else { throw new HtmlEditorException("Document not ready"); } } //this is a fix for exception raised in UpdateUI theSite.mFullyActive = false; Encoding theDefaultPreamble = getEncodingFromEncodingType(this.mDefaultPreamble); utils.LoadDocument(ref this.mHtmlDoc, sDocument, this.mAlwaysLoadAnsi, false, theDefaultPreamble); this.iLoadAttempts = 0; } public void LoadUrl(String url) { this.bLoadUrlWhenReady = false; this.url = url; if (!isCreated) { Debug.WriteLine("Doc not created" + iLoadAttempts.ToString()); if (iLoadAttempts < 2) { this.bLoadUrlWhenReady = true; return; } else { throw new HtmlEditorException("Document not created"); } } if (!isCreated) return; //this is a workaround for a problem calling Caret.SetLocation before it //is ready, in UpdateUI theSite.mFullyActive = false; //don't ask for downloadOnly since clientsite is already set utils.LoadUrl(ref mHtmlDoc, url, false); iLoadAttempts = 0; } public String GetDocumentSource() { if (!isCreated) return null; HTMLDocument thedoc = this.Document; if (thedoc == null) return null; return utils.GetDocumentSource(ref thedoc, this.mDocumentEncoding); } #endregion public void ShowFindDialog() { this.execCommand(commandids.IDM_FIND, null, true, true); } public void Print(bool bPreview) { this.Print(bPreview, string.Empty, true); } public void Print(bool bPreview, String sTemplatePath) { this.Print(bPreview, sTemplatePath, true); } public void Print(bool bPreview, String sTemplatePath, bool bPromptUser) { Object pvaIn; if (sTemplatePath == string.Empty) { pvaIn = null; } else if (!File.Exists(sTemplatePath)) { pvaIn = null; } else { pvaIn = sTemplatePath; } if (bPreview) { this.execCommand(commandids.IDM_PRINTPREVIEW, pvaIn, bPromptUser, true); } else { this.execCommand(commandids.IDM_PRINT, pvaIn, bPromptUser, true); } } public bool Stop() { return this.execCommand(commandids.IDM_STOP, null, false, false); } #endregion #region private properties and methods Boolean isCreated { get { return (theSite != null) && (theSite.Document != null); } } internal bool execCommand(int iCommand, object argument, bool bPromptUser, bool checkReadystate) { if (this.mHtmlDoc == null) { return false; } //Check readystate if (checkReadystate) { if (this.ReadyState != "complete") { //throw new HtmlEditorException("Document is not ready"); return false; } } //get the command target IOleCommandTarget ct = (IOleCommandTarget)this.mHtmlDoc; if (ct == null) { throw new HtmlEditorException("Cannot get COM command target"); } //exec the command System.Guid pguidCmdGroup = new Guid("DE4BA900-59CA-11CF-9592-444553540000"); object[] pvaOut = null; object[] args = new object[] {argument}; int iRetval; int iPromptUser; if (bPromptUser) { iPromptUser = (int)OLECMDEXECOPT.OLECMDEXECOPT_PROMPTUSER; } else { iPromptUser = (int)OLECMDEXECOPT.OLECMDEXECOPT_DONTPROMPTUSER; } iRetval = ct.Exec(ref pguidCmdGroup, iCommand, iPromptUser, args, pvaOut); return (iRetval == 0); } /// /// Queries the status of the specified command /// private CommandStatus GetCommandInfo(int command) { //get the command target IOleCommandTarget ct = (IOleCommandTarget)this.mHtmlDoc; if (ct == null) { throw new HtmlEditorException("Cannot get COM command target"); } System.Guid pguidCmdGroup = new Guid("DE4BA900-59CA-11CF-9592-444553540000"); //Query the command target for the command status OLECMD oleCommand = new OLECMD(); oleCommand.cmdID = command; onlyconnect.OLECMDTEXT ot = new onlyconnect.OLECMDTEXT(); int hr = ct.QueryStatus(ref pguidCmdGroup, 1, oleCommand, ref ot); if (hr != HRESULT.S_OK) { return CommandStatus.Unknown; } if ((oleCommand.cmdf | (int)OLECMDF.OLECMDF_LATCHED) == (int)OLECMDF.OLECMDF_LATCHED) { return CommandStatus.EnabledAndToggledOn; } else if ((oleCommand.cmdf | (int)OLECMDF.OLECMDF_ENABLED) == (int)OLECMDF.OLECMDF_ENABLED) { return CommandStatus.Enabled; } else if ((oleCommand.cmdf | (int)OLECMDF.OLECMDF_SUPPORTED) == (int)OLECMDF.OLECMDF_SUPPORTED) { return CommandStatus.Disabled; } else { return CommandStatus.Unsupported; } } private bool isCommandEnabled(int command) { CommandStatus cs = this.GetCommandInfo(command); if ((cs == CommandStatus.Enabled) | (cs == CommandStatus.EnabledAndToggledOn)) { return true; } else { return false; } } /// /// set focus to the hosted document, not just to the winform control /// private void setFocusToMshtml() { if (this.mNativeDocWindow != null) { win32.SetFocus(this.mNativeDocWindow.Handle); } } internal bool setDefaultFont() { if (!this.mDesignMode) return false; if (this.HtmlDocument2.GetReadyState() != "complete") { this.bSetComposeSettingsWhenReady = true; return false; } this.bSetComposeSettingsWhenReady = false; object o = mComposeSettings.CommandString; if (this.execCommand(commandids.IDM_HTMLEDITMODE, true, false, false)) { return this.execCommand(commandids.IDM_COMPOSESETTINGS, o, false, false); } else { return false; } } Encoding getEncodingFromEncodingType(EncodingType enc) { switch (enc) { case EncodingType.ASCII: return Encoding.ASCII; case EncodingType.Auto: return Encoding.Default; case EncodingType.Unicode: return Encoding.Unicode; case EncodingType.UTF7: return Encoding.UTF7; case EncodingType.UTF8: return Encoding.UTF8; case EncodingType.WindowsCurrent: return Encoding.Default; default: return Encoding.Default; } } #endregion } class HandleWndProc : NativeWindow { internal HtmlEditor thecontrol; protected override void WndProc(ref Message message) { bool bPassToControl = false; //The idea is only pass the key presses and mouse clicks (and not the right clicks) to the base form to process the events correctly. if ((message.Msg >= 0x100 && message.Msg <= 0x0108) || (message.Msg >= 0x200 && message.Msg <= 0x020A && message.Msg != win32.WM_LBUTTONDOWN && message.Msg != win32.WM_RBUTTONDOWN && message.Msg != win32.WM_LBUTTONDBLCLK && message.Msg != win32.WM_MBUTTONDBLCLK && message.Msg != win32.WM_RBUTTONDBLCLK)) { bPassToControl = true; } else { //These are separate because if you pass it to the base control then the right mouse clicks etc. don't fire. switch (message.Msg) { case win32.WM_LBUTTONDOWN: thecontrol.InvokeOnMouseDown(new MouseEventArgs(MouseButtons.Left, 1, message.LParam.ToInt32() & 0xffff, Convert.ToInt32((message.LParam.ToInt32() & 0xffff0000) >> 16), 0)); break; case win32.WM_RBUTTONDOWN: thecontrol.InvokeOnMouseDown(new MouseEventArgs(MouseButtons.Right, 1, message.LParam.ToInt32() & 0xffff, Convert.ToInt32((message.LParam.ToInt32() & 0xffff0000) >> 16), 0)); break; case win32.WM_LBUTTONDBLCLK: case win32.WM_MBUTTONDBLCLK: case win32.WM_RBUTTONDBLCLK: thecontrol.InvokeOnDoubleClick(); break; } } if (message.Msg == win32.WM_KEYDOWN) { Keys k = (Keys)message.WParam.ToInt32(); if (k == Keys.Back) { bPassToControl = false; } if ((k == Keys.Right) || (k == Keys.Left) || (k == Keys.Up) || (k == Keys.Down)) { bPassToControl = false; } } if (bPassToControl) { thecontrol.InvokeWndProc(ref message); } base.WndProc(ref message); } } public enum EncodingType { UTF7, UTF8, Unicode, ASCII, WindowsCurrent, Auto } public enum CommandStatus : byte { Unsupported, Disabled, Enabled, EnabledAndToggledOn, Unknown } }