The blog of Eric Sibly; focusing on mountain biking, .NET development for the Desktop, Smartphone and PocketPC.

Sunday, February 20, 2005

Die stupid browser based apps...

For the life of me I still can not figure out why people still try to build business applications within the browser; “hey, it's like a browser man - it for browsing not for entering, maintaining and analysing business critical data”. So with the release of Windows Forms 2.0 and Web Services (see Smart Clients) we are going to be able to create some killer applications. So repeat after me, “like death to the stupid browser apps man”!

An AWD Motorhome...

This is fantastic! For those outdoors folks that have always laughed at the motorhome crowd, well they might just be laughing back at you now ;-) An AWD Motorhome - looks like it might even allow you to survive a nuclear explosion!

Miniture PacMan...

Everyone's old favourite PacMan is available in miniture style.

Tuesday, February 15, 2005

I finally put down the deposit...

On the weekend I finally did it; put a deposit down on my new bike - the Yeti 575. It has been a while coming, I first blogged about it back in December. So the boys and (girls?) at Cycle Evolution (Brisbane) should be ordering and assembling my new toy as we speak. Awesome :-)

The suicide note in PowerPoint...

The story of a man who committed suicide and left his final thoughts in PowerPoint. A very entertaining read.

Wednesday, February 02, 2005

.NET CF PopupManager...

This is where I actually post some real code for the first time :-) Basically I have created a PopupManager that will take a control and display it over a selected form on the PDA stopping stylus input to the underlying form. For the Popup you can choose to display it centered, display it with an optional border, and have it automatically hide if the user clicks outside of the popup control. The code is as-is where-is, use at your own risk, royalty free; if you have any comments etc. then please let me know. Note: you will need to be using OpenNETCF for this to work, see the class documentation within the code below for more information.

And finally, here is the code (excuse the line breaks) - enjoy!

using System;
using System.Drawing;
using System.Windows.Forms;
using OpenNETCF.Windows.Forms;
using Microsoft.WindowsCE.Forms;
using System.Runtime.InteropServices;
using System.ComponentModel;

namespace Chullybun.NetCF
{
  /// <summary>
  /// A class for managing a <see cref="Popup"/> control within the context of an <see cref="Owner"/> form.
  /// </summary>
  /// <remarks>For this funtionality to work it needs to hook into the standard Windows messages through the use of message
  /// filters (<see cref="IMessageFilter"/>). Unfortunately, within the standard .NET Compact Framework this is not implemented,
  /// but all is not lost - the fantastic folks over at OpenNETCF (http://www.opennetcf.org) have provided the <see cref="ApplicationEx"/>
  /// class that provides this functionality. So to use the <see cref="PopupManager"/> the <see cref="ApplicationEx.Run"/> method
  /// should be used to run the applications main form.
  /// </remarks>
  public sealed class PopupManager
  {
    private Form owner;
    private Control popup;
    private Panel panel;
    private PopupMessageFilter filter;
    private bool centered = true;
    private bool bordered = false;
    private bool autoHide = false;

    /// <summary>
    /// Initializes a new instance of the <see cref="PopupManager"/> class.
    /// </summary>
    /// <param name="owner"><see cref="Owner"/> form.</param>
    /// <param name="popup"><see cref="Popup"/> control.</param>
    public PopupManager(Form owner, Control popup)
    {
      if (owner == null)
        throw new ArgumentNullException("owner");

      if (popup == null)
        throw new ArgumentNullException("popup");

      this.owner = owner;
      this.popup = popup;
    }

    #region Properties

    /// <summary>
    /// Gets the <see cref="Form"/> that owns the <see cref="Popup"/>.
    /// </summary>
    public Form Owner
    {
      get { return owner; }
    }

    /// <summary>
    /// Gets the <see cref="Popup"/> that will be displayed.
    /// </summary>
    public Control Popup
    {
      get { return popup; }
    }

    /// <summary>
    /// Indicates whether the <see cref="Popup"/> should be centered over the <see cref="Owner"/> form.
    /// </summary>
    /// <remarks>The default value is <b>true</b>.</remarks>
    public bool Centered
    {
      get { return centered; }
      set { centered = value; }
    }

    /// <summary>
    /// Indicates whether a border line will be displayed around <see cref="Popup"/>.
    /// </summary>
    /// <remarks>The default value is <b>false</b>.</remarks>
    public bool Bordered
    {
      get { return bordered; }
      set { bordered = value; }
    }

    /// <summary>
    /// Indicates whether the <see cref="Popup"/> should automatically <see cref="Hide"/> when a mouse event occurs outside of the popup region.
    /// </summary>
    public bool AutoHide
    {
      get { return autoHide; }
      set { autoHide = value; }
    }

    #endregion

    #region Show/Hide

    /// <summary>
    /// Show the <see cref="Popup"/>.
    /// </summary>
    public void Show()
    {
      // Make sure the popup hasn't already been associated with another form.
      if (popup.Parent != null)
        throw new InvalidOperationException("Control has already been parented to another control or is already displayed.");

      // Center the popup where requested to do so.
      if (centered)
      {
        popup.Left = (owner.ClientRectangle.Width - popup.Width) / 2;
        popup.Top = (owner.ClientRectangle.Height - popup.Height) / 2;
      }

      // Add the filter so we can filter mouse messages from the rest of the form.
      if (filter == null)
        filter = new PopupMessageFilter(this);

      ApplicationEx.AddMessageFilter(filter);

      // Add a bordered panel if requested.
      if (bordered)
      {
        panel = new Panel();
        panel.Top = popup.Top - 1;
        panel.Left = popup.Left - 1;
        panel.Width = popup.Width + 2;
        panel.Height = popup.Height + 2;
        panel.BackColor = Color.Black;

        owner.Controls.Add(panel);
        owner.Controls.SetChildIndex(panel, 0);
      }

      // Add the popup to the owning form, make it the foremost control and set its focus.
      owner.Controls.Add(popup);
      owner.Controls.SetChildIndex(popup, 0);
      popup.Focus();
    }

    /// <summary>
    /// Hides the <see cref="Popup"/>.
    /// </summary>
    public void Hide()
    {
      // Make sure the popup is assigned as expected.
      if (popup.Parent == null popup.Parent != owner)
        return;

      // Remove the message filter and control from owner form.
      ApplicationEx.RemoveMessageFilter(filter);
      owner.Controls.Remove(popup);

      // Remove the bordered panel where previously added.
      if (panel != null)
      {
        owner.Controls.Remove(panel);
        panel.Dispose();
        panel = null;
      }
    }

    /// <summary>
    /// Action the automatic hide of the popup.
    /// </summary>
    private void ActionAutoHide()
    {
      CancelEventArgs e = new CancelEventArgs(false);
      if (PopupAutoHiding != null)
        PopupAutoHiding(this, e);

      if (!e.Cancel)
        Hide();
    }

    #endregion

    #region Events

    /// <summary>
    /// Occurs when the <see cref="Owner"/> receives a close message.
    /// </summary>
    public event CancelEventHandler OwnerClosing;

    /// <summary>
    /// Occurs when the <see cref="Popup"/> is about to automatically hide.
    /// </summary>
    public event CancelEventHandler PopupAutoHiding;

    #endregion

    #region PopupMessageFilter

    /// <summary>
    /// Class for filtering windows messages.
    /// </summary>
    private class PopupMessageFilter : IMessageFilter
    {
      private const int WM_CLOSE = 0x0010;
      private const int WM_LBUTTONDOWN = 0x0201;
      private const int WM_LBUTTONUP = 0x0202;
      private const int WM_LBUTTONDBLCLK = 0x0203;

      [DllImport("coredll.dll")]
      private static extern bool GetCursorPos(out POINT lpPoint);

      [StructLayout(LayoutKind.Sequential)]
      struct POINT
      {
        public int X;
        public int Y;
      }

      private PopupManager pm;
      private bool hidePopupNextMessage = false;

      /// <summary>
      /// Initializes a new instance of the class.
      /// </summary>
      public PopupMessageFilter(PopupManager pm)
      {
        this.pm = pm;
      }

      /// <summary>
      /// Filters messages before they are sent to the form proper.
      /// </summary>
      public bool PreFilterMessage(ref Message m)
      {
        // Hide the popup
        if (hidePopupNextMessage)
        {
          hidePopupNextMessage = false;
          pm.ActionAutoHide();
        }

        switch (m.Msg)
        {
          case WM_LBUTTONDOWN:
          case WM_LBUTTONUP:
          case WM_LBUTTONDBLCLK:
            // Swallows all the messages relating to the left mouse button, i.e. the stylus.
            POINT pt;
            if (GetCursorPos(out pt))
            {
              bool inPopup = pm.Popup.RectangleToScreen(pm.Popup.ClientRectangle).Contains(pt.X, pt.Y);

              // Where selected to auto hide do so on next message as current needs to be swallowed before we attempt to hide.
              if (pm.autoHide && !inPopup)
                hidePopupNextMessage = true;

              return !inPopup;
            }

            break;

          case WM_CLOSE:
            // Provides a means for the consumer to stop the form close.
            CancelEventArgs e = new CancelEventArgs(false);
            if (pm.OwnerClosing != null)
              pm.OwnerClosing(pm, e);

            if (e.Cancel)
              return true;

            break;
        }

        return false;
      }
    }
    #endregion
  }
}

Tuesday, February 01, 2005

Crazy Customer Expectations

I don’t know why, I should know better, but I am still constantly amazed by the lack of understanding of a customer and their expectations of effort to complete some tasks. I am working on a RFT at the moment for a Pilot Pocket PC application as follows.

The PDA application is to duplicate the functionality of their existing Smart Client (Windows Forms) application and refactor the UI design for the PDA consuming the existing Web Services using the wireless capability of their GPRS enabled Pocket PC phones. They would also like this application to be self deploying/updateable, provide a duplication of their reference data services, encrypt all locally persisted data, provide a means to reprocess updates where the updates fail, and finally integrate into their existing security services.

I have no problem with all of that, it is all technically feasible, and would be a lot of fun to develop. Then I turn to the page in the RFT with the section on timing and there lies the following innocent sentence, “A three week delivery timeframe will be imposed once the successful tender has been notified of their selection”.

Are you mad, are you crazy – three weeks! What planet are you from that you would expect that the above could be completed in this timeframe.? Surely you asked yourself, how long did it take to develop the application previously, including the additional requirements – it would have been over a period of months, certainly not weeks.

Come on, give me a break. Is it April 1, because this is a complete and utter joke right?!?