Craig Dahlinger

mshtml – the ongoing adventure Apr 14

     I am currently working on a project where we are using the .net web browser control in designer mode.  This allows you to use the control as an html editor.  There are numerous posts out there on this control. Tim Anderson has a great post on using the html editor control and utilizing allot of its built in features.  One feature the control did not have out of the box was the ability to click on an anchor tag when using the editor in design mode.  This behavior is in outlook when creating an email in html.  You have the ability when hovering your mouse over an anchor tag, you can use ctrl + click to the link follow link.  It even has a nice popup dialog.  I wanted to introduce this behavior into the mshtml control when in design mode.

     I started digging around with reflector and I found the IHTMLPopup interface.I decided to approach it with my own interface definition instead of using the generated interop.

[ComVisible(true), ComImport()]
[TypeLibType(TypeLibTypeFlags.FDispatchable)]
[InterfaceTypeAttribute(ComInterfaceType.InterfaceIsIDispatch)]
[Guid("3050f666-98b5-11cf-bb82-00aa00bdce0b")]
public interface IHTMLPopup
{
   [DispId(DispIDs.DISPID_IHTMLPOPUP_SHOW)]
    void show(
        int x,
        int y,
        int w,
        int h,
        object pElement
        );
 
    [DispId(DispIDs.DISPID_IHTMLPOPUP_HIDE)]
    void hide();
 
    [DispId(DispIDs.DISPID_IHTMLPOPUP_DOCUMENT)]
    IHTMLDocument document { [return: MarshalAs(UnmanagedType.Interface)] get; }
 
    [DispId(DispIDs.DISPID_IHTMLPOPUP_ISOPEN)]
    bool isOpen { [return: MarshalAs(UnmanagedType.VariantBool)] get; }
 
}

     Once I had the interface set up, I implemented the interface in a HTMLPopup class like so.

public static class HtmlPopup
    {
        private static IHTMLPopup _CurrentPopup;
 
        /// <summary>
        /// Shows the popup.
        /// </summary>
        /// <Remarks>
        /// Background color is set to Aqua and the border is set to 1px solid skyblue. Font is tahoma
        /// with a size of 12. A style filter is used to provided the gradient color in the background.
        /// Starting with skyblue with an end color of white.
        /// </Remarks>
        /// <param name="domDocument">The DOM document.</param>
        /// <param name="displayHtml">The display HTML.</param>
        /// <param name="location">The location.</param>
        public static void ShowPopup(object domDocument,string displayHtml,PopupLocation location)
        {
            
            IHTMLDocument2 doc2 = domDocument as IHTMLDocument2;
 
            if (doc2 != null)
            {
                IHTMLWindow4 window4 = doc2.parentWindow as IHTMLWindow4;
 
                if (window4 != null)
                {
                    object nullObject = null;
 
                    IHTMLPopup popup = (Interfaces.IHTMLPopup)window4.createPopup(ref nullObject);
 
                    if (popup != null)
                    {
                        IHTMLDocument2 popupdoc = popup.document as IHTMLDocument2;
 
                        if (popupdoc != null && popupdoc.body != null)
                        {
                            popupdoc.body.style.backgroundColor = "Aqua";
                            popupdoc.body.style.border = "1px solid skyblue";
                            popupdoc.body.style.fontFamily = "tahoma";
                            popupdoc.body.style.fontSize = "12";
                            popupdoc.body.style.filter =
                                "progid:DXImageTransform.Microsoft.Gradient(GradientType=1, StartColorStr='skyblue', EndColorStr='white')";
                            popupdoc.body.innerHTML = displayHtml;
                        }
 
                        //x-coordinate, y-coordinate, width, height, element
                        popup.show(location.ClientX + 10, location.ClientY + 20, location.Width, location.Height, doc2.body);
                        _CurrentPopup = popup;
                    }
                }
 
            }
        }
 
 
        /// <summary>
        /// Hides the popup.
        /// </summary>
        public static void HidePopup()
        {
            if(_CurrentPopup != null)
            {
                _CurrentPopup.hide();
            }
        }
 
    }

    Now using the class, I  first had to set up an event handler for handling the on mouse over, on mouse out and click event on an anchor tag.  When the document was completely loaded I set up the event handler like so:

IHTMLDocument2 doc2 = htmlEditorCtrl.Document.DomDocument as IHTMLDocument2;
 
if (doc2 != null)
{               
    mshtml.HTMLDocumentEvents2_Event iEvent = doc2 as HTMLDocumentEvents2_Event;
 
    if (iEvent != null)
    {
        iEvent.onmouseover += new HTMLDocumentEvents2_onmouseoverEventHandler(MouseOverEventHandler);
        iEvent.onmouseout += new HTMLDocumentEvents2_onmouseoutEventHandler(iEvent_onmouseout);
        iEvent.onclick += new HTMLDocumentEvents2_onclickEventHandler(iEvent_onclick);
    }                  
 
}

     One of the issues I ran into was trying to change the cursor when the mouse would go over the element. The control by default overrides the cursor change, you have to tell the control, “Control, I am going to change the cursor, so don’t change it back, ok?!?You accomplish this by executing the IDM_OVERRIDE_CURSOR command.  You would execute the command and then change the cursor to what you desire.  Then to change cursor back, you do the opposite, change the cursor, then execute the command.  So in my case, when the mouse is over the  anchor tag I want to change the cursor to a hand and show the popup, and when the mouse moves away from the anchor tag I want to return the cursor to normal and hide the popup.

     Handling the mouse over event.  In the mouse over event is where we use the HTMLPopup class.

private void MouseOverEventHandler(mshtml.IHTMLEventObj evntObject)
{
   //handling an anchor tag hover, I know there is probably better way to handle this.
   if (evntObject.srcElement.tagName.ToLowerInvariant() == "a")
   {
       if (htmlEditorCtrl.Document != null)
       {
           //need to override the cursor within the html control, so we are telling it to not override our
           //request to change.
           InternalExecCommand.Execute(htmlEditorCtrl.Document.DomDocument, commandids.IDM_OVERRIDE_CURSOR,
                                       Convert.ToUInt32(false), null, null);
           this.Cursor = Cursors.Hand;
       }
 
           string href = evntObject.srcElement.getAttribute("href", 0) as string;
 
           if (href != null && href.Length >= 20)
               href = href.Substring(0, 20);
 
           PopupLocation location;
           location.ClientX = evntObject.clientX;
           location.ClientY = evntObject.clientY;
           location.Width = 170;
           location.Height = 30;
 
           string innerHtml =
               string.Format(
                   "<P style=\"left:0; width:100%; height:100%; overflow: auto; word-wrap: break-word;\" >&nbsp{0}...<BR>&nbsp<B>{1}</B></P>",
                   href,Resources.CLICK_TO_FOLLOW);
 
           HtmlPopup.ShowPopup(htmlEditorCtrl.Document.DomDocument,
                               innerHtml, location);
          }
}

     Handling the mouse out event:

void iEvent_onmouseout(IHTMLEventObj evntObject)
{
    //are we on an anchor tag.
    if (evntObject.srcElement.tagName.ToLowerInvariant() == "a")
    {
        if (htmlEditorCtrl.Document != null)
        {
            this.Cursor = Cursors.Arrow;
 
            InternalExecCommand.Execute(htmlEditorCtrl.Document.DomDocument, commandids.IDM_OVERRIDE_CURSOR,
                                        Convert.ToUInt32(false), null, null);
        }
 
        HtmlPopup.HidePopup();
    }
}

     Handling the anchor ctrl+click event:

bool iEvent_onclick(IHTMLEventObj pEvtObj)
{
    bool handled = false;
 
    if (Control.ModifierKeys == Keys.Control)
    {
        if (pEvtObj.srcElement.tagName.ToLowerInvariant() == "a")
        {
            IHTMLAnchorElement anchor = pEvtObj.srcElement as IHTMLAnchorElement;
 
            if (anchor != null)
            {
                handled = true;
                System.Diagnostics.Process.Start(anchor.href);
            }
        }
    }
 
    return handled;
}

 

 

     Now the screen shot of it in action.

image 

     Thanks for reading and I hope you find this post useful.

 

Thanks

CD

windows mobile meets weight watchers : MVP style Jan 11

     Ok, so I know it has been a long time since a post, but it has been really busy with work and family.  I have been busy coding and learning lots of new stuff.  I work with a great bunch of developers and my current team lead is a great mentor. 

    Well for the new year the wife and I decided to get back into shape.  I started hitting the gym and so did she but she is also doing weight watchers with a friend.  One of the things they do is they have to calculate points on a daily basis.  These points are comprised of calories, fat and fiber.  There is a formula for these three which in turn results in the number of points a particular item is.  A few months ago I convinced the wife to get a windows mobile device (woo hoo!) and she is a good power user.  So one night she asks me, “Is there a way I can just enter in the calories, fat and fiber on my phone and it tell me how many points something is?”.  I did some searching and there are numerous online versions of the calculator but no native ones for windows mobile.  I found the formula here, and started to get to work.

   I wanted to approach this application using the MVP design pattern.  I know it may be overkill for this simple of an application but I thought it would be good practice.

I started with the interface for the data model, in this case it would be the main caloric properties of food.

namespace WWPC.Common.Interfaces
{
    public interface IFoodModel        
    {
        int Fiber { get; set; }
        int Calories { get; set; }
        float Fat { get; set; }
        int Points { get; set; }
        int CalculatePoints();
        
    }
}

I then wrote up the interface for the view for the model.

namespace WWPC.Common.Interfaces
{
    public interface IFoodCalcView
    {
        int Calories { get; }
        int Fiber { get; }
        float Fat { get; }
        int Points { set; }
 
        event EventHandler DataChanged;
    }
}

Next, came the interface for the presenter.

 public interface IFoodCalcPresenter
 {
        void OnCalculatePoints();
 }

 

Ok, now that I got my main interfaces in place, time to code up the implementation.  I started with the model first since this was the class that would provide the implementation for calculating the caloric points.  Using the formula mentioned above, the CalculatePoints() method came out like so:

public int CalculatePoints()
{
        var calories = Convert.ToDecimal(Calories);
 
        var cal = calories / 50;
 
        var totalFat = Convert.ToDecimal(Fat);
 
        var fat = totalFat / 12;
 
        var fiber = Convert.ToDecimal(Fiber);
 
        return Points = Convert.ToInt32(Math.Round(cal + fat - (fiber/5), 0));            
 }
 

With the model  complete, I then moved to the presenter.  The presenter would be responsible for binding the model to the view responding to the data changes in the view and rebinding those changes to the model. I made the presenter with an overloaded constructor to take a view and a model.  The presenter then binds to the data changed event on the view which enables the presenter to update the model from the view.  The OnCalculatePoints() method will update the view with the points value after using the model for calculation.

 
namespace WWPC.Common
{
    public class FoodPresenter : IFoodCalcPresenter
    {
        private readonly IFoodCalcView _View;
        private readonly IFoodModel _Model;
 
        public FoodPresenter(IFoodCalcView view, IFoodModel model)
        {
            _View = view;
            _View.DataChanged += new EventHandler(_View_DataChanged);
 
            _Model = model;
            
        }
 
        void _View_DataChanged(object sender, EventArgs e)
        {
            SetModelFromView();
        }
 
        private void SetModelFromView()
        {
            _Model.Calories = _View.Calories;
            _Model.Fat = _View.Fat;
            _Model.Fiber = _View.Fiber;
        }
 
        #region IFoodCalcPresenter Members
 
        public void OnCalculatePoints()
        {
            _View.Points = _Model.CalculatePoints();
        }
 
        #endregion
    }
}
 

With the presenter done it was time to implement the view.  I wanted a simple mobile form where you can enter in data quickly and then calculate the results.  I initially tried using a label to display the result, but did not like it.  I then tried a mobile gauge control, but that took up too much space on the small screen.  Finally I decided to use the notification class for windows mobile.  I did not use the managed wrapper version, I used the the version created by Christopher Fairbairn, found here.  This version has an awesome implementation which exposes many features of the notification class.  I wanted to give the user the  ability to dismiss the notification when they were done reading the results.  Also using the notification class the UI was able show the needed text boxes for entry and the SIP panel along with the results without needing to scroll the screen.  Here is a screen shot of the main form.

                            MainScreeen

Now with the controls in place on the form, I can implement the view.  The form creates a new presenter and passed into it a new model during construction.  When the calculate menu option is clicked the main form raises the data changed event then calls the OnCalculateMethod on the presenter.  When the presenter binds the model to the view, during the set of the points value, the notification is shown to the user via the ShowNotification method.

 

namespace WWPC.Calc
{
    public partial class WWPCalculator : Form, IFoodCalcView
    {
        private readonly FoodPresenter _Presenter;
 
        private NotificationWithSoftKeys _Notification;
        
 
        public WWPCalculator()
        {
            InitializeComponent();
            
            _Presenter = new FoodPresenter(this,new FoodModel());
            
        }
 
 
        public int Calories
        {
            get { return (string.IsNullOrEmpty(txtCalories.Text)) ? 0 : 
                Int32.Parse(txtCalories.Text); }
        }
 
        public int Fiber
        {
            get { return (cmbFiber.Text == "4 or more") ? 4 :
                (string.IsNullOrEmpty(cmbFiber.Text)) ? 0 :Int32.Parse(cmbFiber.Text); }
        }
 
        public float Fat
        {
            get { return (string.IsNullOrEmpty(txtFat.Text)) ? 0 : float.Parse(txtFat.Text); }
        }
 
        public int Points
        {
            set
            {
                ShowPointsNotification(value);
            }
        }
 
        public event EventHandler DataChanged;
 
        private void mnuExit_Click(object sender, EventArgs e)
        {
            this.Close();
        }
 
        private void mnuCalculate_Click(object sender, EventArgs e)
        {
            if (DataChanged != null)
                this.DataChanged(sender, e);
 
            _Presenter.OnCalculatePoints();
        }
 
        private void mnuClear_Click(object sender, EventArgs e)
        {
            txtCalories.Text = string.Empty;
            txtFat.Text = string.Empty;
            cmbFiber.Text = "0";
        }
 
        private void ShowPointsNotification(int points)
        {
            _Notification = new NotificationWithSoftKeys
            {
                Text = String.Format("Total Points:{0}", points),
                Caption = "Weight Watchers Point Calculator",
                RightSoftKey = new NotificationSoftKey(SoftKeyType.Dismiss, "Dismiss"),
            };
 
            _Notification.RightSoftKeyClick+=new EventHandler(_Notification_RightSoftKeyClick);
 
            _Notification.Visible = true;
            
        }
 
        void _Notification_RightSoftKeyClick(object sender, EventArgs e)
        {
            if (_Notification == null) return;
            _Notification.Visible = false;
            _Notification = null;
        }
 
    }
}

 

Now, when it is all put together, it looks like so.

                           results

 

Below is a link to the source code.  The project was done using Visual Studio 2008 against the windows mobile 5 sdk.  It will also work against windows mobile 6 sdk, I just chose version 5 since that is the common sdk.  Thanks for reading!!

How do you handle events? Apr 28

 

     At work in order for us to submit code against certain code bases, we go through code reviews. When my code was getting reviewed a fellow developer pointed out how I was raising events compared to how he was raising events. He was using Event handler, and I was going through the events invocation list. Which one is correct? I think it all depends on how you want to control your events. The one issue I thought of, was if I use Event Handler and one of the callers registering for the event throws an exception, will the rest of the event handlers get notified or will it stop at the event handler where the exception occurred. Also, I was thinking do I want to continue with the invocation list, or stop. So I decided to write a little test app to show how events are handled and I wanted to share my findings for those out there not sure what happens when an exception is thrown during the use of Event Handler.

First I made my own EventArgs class, just a simple class to pass data around when raising the event:

public class CustomEventArgs : EventArgs
   {
       private string m_notes = string.Empty;

       public string Notes
       {
           get { return m_notes; }
           set { m_notes = value; }
       }

       public CustomEventArgs(string pNotes)
       {
           if (!(string.IsNullOrEmpty(pNotes)))
               m_notes = pNotes;
       }

       public CustomEventArgs() { }
   } 

 

Then I created an event dispatcher class.  This class would be responsible for raising events two different ways.  The first way would be by using event and a delegate and going through the invocation list of the event, and the other way would be using EventHandler.

 

public class EventDispatcher
   {

       public delegate void CustomEventDelegate(object sender, CustomEventArgs pArgs);
       public event CustomEventDelegate OnCustomEvent;


       public EventHandler<CustomEventArgs> OnCustomEventHandler;


       public void RaiseEvents(eMode pMode,CustomEventArgs pArgs)
       {
           switch (pMode)
           {
               case eMode.EVENT_HANDLER:
                   RaiseEventHandlerStyle(pArgs);
                   break;

               case eMode.INVOCATION_STYLE:
                   RaiseEventInvocationStyle(pArgs);
                   break;
           }
       }

       public enum eMode
       {
           EVENT_HANDLER,
           INVOCATION_STYLE,
       }

       private void RaiseEventHandlerStyle(CustomEventArgs pArgs)
       {
           try
           {
               EventHandler<CustomEventArgs> eh = OnCustomEventHandler;

               if (null != eh)
               {
                   eh(this, pArgs);
               }
           }
           catch { }
       }

       private void RaiseEventInvocationStyle(CustomEventArgs pArgs)
       {
           if (OnCustomEvent != null)
           {
               foreach (CustomEventDelegate pDel in OnCustomEvent.GetInvocationList())
               {
                   try
                   {
                       pDel.Invoke(this,pArgs);
                   }
                   catch { }
               }
           }
       }
   }
 
 
Now that I got the event args in place and the event dispatcher set up to handle raising event two different ways, my next goal was to use both ways to raise 3 events, but the callers handling the second event would throw an exception.  What I wanted to find out was, would the Event handler go through the list of event handlers or stop at the second one after the exception was thrown?
 
Below is the code for registering for events from the dispatcher class using the EventHandler.
 
private void RaiseEventsEventHandlerStyle(bool bThrowException)
{
    try
    {
        EventDispatcher disp = new EventDispatcher();
        m_bThrowExceptionEvent2 = bThrowException;
        disp.OnCustomEventHandler += 
                new EventHandler<CustomEventArgs>(this.dispatcher_OnCustomEvent1);
        disp.OnCustomEventHandler += 
                new EventHandler<CustomEventArgs>(this.dispatcher_OnCustomEvent2);
        disp.OnCustomEventHandler += 
                new EventHandler<CustomEventArgs>(this.dispatcher_OnCustomEvent3);
        this.lstStatus.Items.Clear();
        
        disp.RaiseEvents(EventDispatcher.eMode.EVENT_HANDLER, 
                            new CustomEventArgs("Running Event Handler"));
    }
    catch (Exception ex)
    {
        UpdateStatus(ex.Message);
        m_bThrowExceptionEvent2 = false;
    }
}
 
 
Below is the code for registering for events from the dispatcher class using invocation style.
 
private void RaiseEventsInvocationStyle(bool bThrowException)
{
   try
   {
       EventDispatcher dispatcher = new EventDispatcher();
       m_bThrowExceptionEvent2 = bThrowException;
       dispatcher.OnCustomEvent += 
                new EventDispatcher.CustomEventDelegate(dispatcher_OnCustomEvent1);
       dispatcher.OnCustomEvent += 
                new EventDispatcher.CustomEventDelegate(dispatcher_OnCustomEvent2);
       dispatcher.OnCustomEvent += 
                new EventDispatcher.CustomEventDelegate(dispatcher_OnCustomEvent3);

       this.lstStatus.Items.Clear();

       dispatcher.RaiseEvents(EventDispatcher.eMode.INVOCATION_STYLE, 
                                new CustomEventArgs("Running Invocation Style"));
   }
   catch (Exception ex)
   {
       UpdateStatus(ex.Message);
       m_bThrowExceptionEvent2 = false;
   }
}
 
 
 
With the code in place I made a little test gui to raise events 4 different ways.
  1. Raise events using invocation style.
  2. Raise events using the EventHandler delegate.
  3. Raise events using invocation style, throwing an exception after handling the second event.
  4. Raise events using the EventHandler delegate, throwing an exception after handling the second event.

 

image
 
The first test, I did was to click on buttons one and two to make sure all events would properly raise using the two different methods.
 
Event handler style.
 
image
 
 
Invocation list style.
 
image
 
 
Now the juicy part, next I click on button 3, which will handle the first event but throw an exception on handling the 2nd event, what happens with the 3rd registered event handler? Notice the 3rd event is not raised nor handled.
 
 
 image
 
 
Now I click on button 4, which also throws an exception handling the 2nd event, but here we see that the 3rd event is raised and handled.
 
image
 
 
 
Going through this little exercise helped clear up how events are handled, it also shed some light on how I can stream line my code for handling events.  Please keep in mind that  I am working within the constraints of the compact framework so I am limited to certain features of the .NET framework, but I was able to code up a centralized way of handling events using generics and the EventHandler delegate.  Below is the code snippet.
 
 
public class SafeRaiseEventArgs<T> : EventArgs
    {
        private T m_objectValue;

        public SafeRaiseEventArgs(T value)
        {
            m_objectValue = value;
        }

        public T ObjectValue
        {
            get { return m_objectValue; }
        }
    }
private void SafeRaise<T>(EventHandler<SafeRaiseEventArgs<T>> evtHandler, 
                            object sender, SafeRaiseEventArgs<T> args) where T : class
{            
   if (evtHandler != null)
   {
       if (args != null)
       {
           foreach (EventHandler<SafeRaiseEventArgs<T>> del in evtHandler.GetInvocationList())
           {
               try
               {
                   del.Invoke(sender, args);
               }
               catch (Exception ex)
               {
                    //TODO:Trace out exeption to log
               }
           }
       }
   }
}
 
 
 

VS 2005 Project Sample download here.

 
This was my first blog entry, so please excuse typos, and I do hope to get better as I post more. ;-)