Technorati Profile

For quite a while, my approach to debugging sidebar gadgets has been; add a debug label to the HTML and write debug data to it. It would be ideal to be able to single step through the sidebar code, but Visual Studio 2005 is not really set up for that – maybe 2007…

Being able to display diagnostic messages is better than nothing, but adding a label to the gadget and then just displaying one message at a time is a bit limited. So I spent a while writing a diagnostic monitoring component. It’s been a while since I’ve written ActiveX components (they are sooo 20th century), but this only took me half a day. I decided that I’d use MSMQ for my messages. This means the monitoring program doesn’t have to be running all the time; you could run the gadget then when you hit a problem, start the monitor program and you’d have the history that led up to the problem.

The code for the ActiveX components is quite short. There are four functions (and I started to gold-plate so it’s more involved than necessary). I allowed messages to be written to MSMQ or to a text file – in case MSMQ was not installed, since it’s an optional extra in Vista.

The code for the component (in C#) is:

using System;
using System.Runtime.InteropServices;
using System.Text;
using System.IO;
using System.Messaging;
namespace Monitor
{
    public interface iRecorder
    {
        string SetFile(string file);
        string WriteEvent(string message);
        string QueueEvent(string message);
        string DequeueEvent();
    }

    [Guid("613054E2-5BB0-4137-809C-DFC64FEF4F77")]
    [ClassInterface(ClassInterfaceType.AutoDual)]
    [ComVisible(true)]
    [ProgId("Monitor.Recorder")]
    public class Recorder : iRecorder
    {
        private string m_File;
        private string m_Queue;

        public Recorder()
        {
            string dir = Environment.GetFolderPath(Environment.SpecialFolder.CommonApplicationData);
            string path = Path.Combine(dir, "Monitor.txt");
            m_File = path;
            if (!File.Exists(path)) File.Create(path);

            m_Queue = ".\\private$\\monitorLog";
        }

        public string SetFile(string path)
        {
            m_File = path;
            if (!File.Exists (path)) File.Create (path);
            return "OK";
        }

        public string WriteEvent(string message)
        {
            StreamWriter sw = null;
            try
            {
                sw = new StreamWriter(m_File, true);
                sw.WriteLine(message);
                return "OK";
            }
            catch (Exception ex)
            {
                return "BAD";
            }
            finally
            {
                if (sw != null) sw.Close();
            }
        }

        public string QueueEvent(string message)
        {
            MessageQueue messageQueue = null;

            try
            {
                messageQueue = new MessageQueue(m_Queue, QueueAccessMode.Send);
                messageQueue.Send(new Message(message, new XmlMessageFormatter(new string[] { "System.String,mscorlib" })), "Monitor");
                return "OK";

            }
            catch (Exception ex)
            {
                this.WriteEvent("Failed to write to message queue: '" + ex.Message + "'");
                return "BAD";

            }
            finally
            {
                if (messageQueue != null) messageQueue.Close();
            }
        }

        public string DequeueEvent()
        {
            MessageQueue messageQueue = null;

            try
            {
                messageQueue = new MessageQueue(m_Queue, QueueAccessMode.Receive);
                Message message = messageQueue.Receive(new TimeSpan(10000));
                if (message == null) return "BAD";
                message.Formatter = new XmlMessageFormatter(new string[] { "System.String,mscorlib" });
                return message.Body.ToString();
            }
            catch (MessageQueueException mex)
            {
                return null;
            }
            catch (Exception ex)
            {
                this.WriteEvent("Failed to read from message queue: (" + ex.ToString() + ") '" + ex.Message + "'");
                return "BAD";

            }
            finally
            {
                if (messageQueue != null) messageQueue.Close();
            }
        }
    }
}

One point worth noting: In, DequeueEvent, the Read call has a TimeSpan parameter. This is set to an arbitrary value 10,000. This is the time in ticks the Read function will wait before timing out. Leave the parameter off and the function will never return until it’s popped something off the queue. I found that 10,000 was a reasonable value to use. Any smaller and, for some reason, even if there was a message in the queue, it wouldn’t be popped. I never looked any further into that.

Just compile this up to a DLL. This DLL will be a .Net assembly and that’s not going to be callable from COM, so it has to be registered as a COM component. The meta code at the head of the assembly is for COM regsitration (it has a GUID, a COM ProgId and a declaration that COM dual interfaces are to be created). Register the assembly using REGASM, which means typing the following into a command window:

c:\WINNT\Microsoft.NET\Framework\v2.0.50727\RegAsm Monitor.dll /tlb:MonitorCOM.dll /codebase

In my case, I’m using the v2.0 version of REGASM since I’ve compiled this with VS2005, which has build a .Net Framework 2 assembly.

Next I added a “ShowDiag” function to my javascript:

function showDiag (message)
{
    try
    {
        var monitor = new ActiveXObject("Monitor.Recorder");
        var d = new Date();
        monitor.QueueEvent("Portfolio - " + formatTime(d) +" : " + message);
    }
    catch (badError)
    {
        try
        {
            document.getElementById('error').innerHTML = "" + message + "";
        }
        catch (worseError)
        {
            // Pretty much game over.
        }
    }
}

and I add diagnostic message calls to my code using lines like:

    ShowDiag ("Hello World");

The final thing is to have some way of displaying the diagnostics. I lashed up a simple VB application, with one text box. I could have got all fancy, and even a gadget mighht be a neat approach, however for now this does the job:

The VB app has a timer whcih ticks every two seconds and checks for messages:

Private Sub Timer1_Timer()
    Dim oMonitor As Object
    Dim text As String

    On Error GoTo failed

    Set o = CreateObject("Monitor.Recorder")
    text = o.DequeueEvent
    While text <> ""
        Text1.text = Text1.text + text + vbCr + vbLf
        text = o.DequeueEvent
    Wend
    Text1.SelStart = Len(Text1.text)
    Set o = Nothing
    Exit Sub

failed:
    MsgBox Err.Description, vbCritical + vbOKOnly, "Monitor Test"
    Set o = Nothing
End Sub

Again, nothing startling. Note that since I only poll every 2 seconds there may have been more than 1 diagnostic since the last time I looked so I loop to get all the messages from the queue. The Text1.SelStart line positions the text box at the end of thebuffer, so new messages are always scrolled into view.

This is not a true debugger; that would require proper integration with Visual Studio and some debuggable version of the sidebar. It’s just a way of logging messages from a gadget (or any COM-compatible program). As I said, it’s better than nothing and means you don’t have to compromise your HTML by adding debug labels.

I wanted to have a completely non-standard window; no border and not rectangular. I looked on the Net for how-to’s on a few WPF issues I was having problems with:

Windows without traditional borders. One reference (http://learnwpf.com/Posts/Post.aspx?postId=e9cb689c-e6af-407a-b28c-d38f2f2f555c) had a good description of this, but one point was omitted that caused me problems until I found it elsewhere. Moving windows that have no borders was something I had to figure out myself.

To remove the traditional Windows border from a window, you must set the WindowStyle to None and the ResizeMode to NoResize within the Window tag.

However if you have an irregularly shaped window (for example if you have rounded corners), you also have to set AllowsTransparency to True and set the Background to Transparent (I had to do this because the background for my dialog is a PhotoShopped image – a rectangle with rounder corners).

So the whole Window tag looks like:

<Window
xmlns=http://schemas.microsoft.com/winfx/2006/xaml/presentation

xmlns:x=http://schemas.microsoft.com/winfx/2006/xaml

WindowStyle=None ResizeMode=NoResize
AllowsTransparency=“True Background=“Transparent>

I have a Settings window, and wanted to do that all in XAML I found that the AllowsTranspency setting has to be set to true in the code-behind, not in the XAML. This may be because I don’t have a background image, just XAML. So my Window tag looks like:

<Window
xmlns=http://schemas.microsoft.com/winfx/2006/xaml/presentation
xmlns:x=http://schemas.microsoft.com/winfx/2006/xaml
Background=Transparent
WindowStyle=None ResizeMode=NoResize
Height=232 Width=240>

And in the C# code-behind, I have: 

 Settings settings = new Settings();
settings.Owner =
this;
settings.AllowsTransparency =
true;
settings.Show();

In both these samples, I have removed things like the class name and, because I created the XAML using Expression Blend, the extra name spaces that involves.How to make such a window movable? I wanted to be able to drag this window to a new place. With no border, Windows does not help you, you have to do that all yourself. I captured two events to achieve this and again in the Window tag I had the line

In both these samples, I have removed things like the class name and, because I created the XAML using Expression Blend, the extra name spaces that involves.

How to make such a window movable? I wanted to be able to drag this window to a new place. With no border, Windows does not help you, you have to do that all youself. I captured two events to achieve this and again in the Window tag I had the line: MouseMove=onMouseMove MouseDown=onMouseDownThen in the code-behind I would move the window in the MouseMove handler. In the MouseDown handler, I would capture the place the mouse had been pressed. I found that this would disrupt behaviour of the text boxes and list boxes in the window - basically you can’t actually select text in a text box since that moves the window! so I use the DirectlyOver property of the mouse event to only move the window if I click over background (imageBackground is the image object for my background).private Point m_LastPoint;

private void onMouseDown(object sender, EventArgs e)
{
MouseEventArgs args = (MouseEventArgs)e;
if (args.LeftButton == MouseButtonState.Pressed &&
args.MouseDevice.DirectlyOver == imageBackground)
{
m_lastPoint = args.GetPosition(
this);
}
}

private void onMouseMove(object sender, EventArgs e)
{
MouseEventArgs args = (MouseEventArgs)e;
if (args.LeftButton == MouseButtonState.Pressed &&
args.MouseDevice.DirectlyOver == imageBackground)
{
if (m_lastPoint.X >= 0 && m_lastPoint.Y >= 0)
{
this.Top += args.GetPosition(this).Y – m_lastPoint.Y;
this.Left += args.GetPosition(this).X – m_lastPoint.X;
}
}
}

This works fairly well, but even now I have problems if I move the move too quickly. I think it’s because I’ve moved the mouse outside the bounds of the window whis being redrawn slightly lower than I can drag it. For neatness I should tidy that up. Probably it’s got something to do with capturing the mouse, which I remember from my old Win32 days.

© 2010 Derek Knight's Blog Suffusion WordPress theme by Sayontan Sinha