Sunday, September 30, 2007

ButtonEx - Owner Drawn Button Control

Wow 2 posts in 1 day. I must have a lot of spare time today! Actually I'm down with a cold and I just feel sorry for myself when I stay in bed.

In this article I'd like to share another piece of code that I use quite often. Its a simple owner drawn button control that can have a nice 3D shadow. Drawing the 3D shadow effect was by complete accident though. I was trying to just draw a simple control that acts like a button. When I was drawing the borders the first time I drawn the rectangle a pixel bigger hence the control had a 1 pixel line of just black on the right and bottom side. Anyway, my graphic artist loved it and decided that we keep it.

The control has the following properties added:
  1) PushedColor (Color) - The color the control will be filled with once the MouseDown is fired
  2) DrawShadow (Boolean) - A flag whether to draw the 3D border

The default settings of the control is best on a form with a blue-ish background.

And here's the code:

public class ButtonEx : Control
{
  private Bitmap off_screen;
  private SolidBrush back_brush;
  private SolidBrush selected_brush;
  private SolidBrush text_brush;
  private Pen border_pen;
  private bool pushed;
  private bool shadow;

  public ButtonEx()
  {
    BackColor = Color.FromArgb(2, 32, 154);
    ForeColor = Color.White;

    back_brush = new SolidBrush(Color.FromArgb(48, 88, 198));
    selected_brush = new SolidBrush(Color.FromArgb(15, 51, 190));
    border_pen = new Pen(Color.White);
    text_brush = new SolidBrush(Color.White);
  }

  public override Color BackColor
  {
    get { return base.BackColor; }
    set
    {
      base.BackColor = value;
      back_brush = new SolidBrush(value);
    }
  }

  public override Color ForeColor
  {
    get { return base.ForeColor; }
    set
    {
      base.ForeColor = value;
      border_pen = new Pen(value);
      text_brush = new SolidBrush(value);
    }
  }

  public Color PushedColor
  {
    get { return selected_brush.Color; }
    set { selected_brush = new SolidBrush(value); }
    }

  public bool DrawShadow
  {
    get { return shadow; }
    set { shadow = value; }
  }

  protected override void OnPaint(PaintEventArgs e)
  {
    if (off_screen == null) {
      off_screen = new Bitmap(ClientSize.Width, ClientSize.Height);
    }

    using (Graphics gxOff = Graphics.FromImage(off_screen)) {
      gxOff.DrawRectangle(
        border_pen,
        0,
        0,
        ClientSize.Width - (shadow ? 0 : 1),
        ClientSize.Height - (shadow ? 0 : 1));

      Rectangle rect = new Rectangle(1, 1,
        ClientRectangle.Width - 2, ClientRectangle.Height - 2);

      gxOff.FillRectangle(
        pushed ? back_brush : selected_brush, rect);

      if (!string.IsNullOrEmpty(Text)) {
        SizeF size = gxOff.MeasureString(Text, Font);
        gxOff.DrawString(
          Text,
          Font,
          text_brush,
          (ClientSize.Width - size.Width) / 2,
          (ClientSize.Height - size.Height) / 2);
      }
    }

    e.Graphics.DrawImage(off_screen, 0, 0);
  }

  protected override void OnPaintBackground(PaintEventArgs e) { }

  protected override void OnMouseDown(MouseEventArgs e)
  {
    base.OnMouseDown(e);

    pushed = true;
    Invalidate();
  }

  protected override void OnMouseUp(MouseEventArgs e)
  {
    base.OnMouseUp(e);

    pushed = false;
    Invalidate();
  }

  protected override void OnTextChanged(EventArgs e)
  {
    base.OnTextChanged(e);

    Invalidate();
  }
}

Attaching Photos to a .NETCF Application

Sorry for not posting any articles lately. I've been quite busy and didn't have time to follow up on blogs.

Anyway, lately I've been working on a Mobile Inspection Log application for a customer. The application is rather simple and works like a small check list. The back-end server provides the mobile client with a list of Observation types where the User can set each observation to "OK", "Questionable", "Fail", and "N/A". If an Observation is questionable or failed then the user can attach a comment and a photo. The photo is then saved to a local database which can be synchronized with an access database on the users desktop computer or be sent to a web service.

The user has 2 options for attaching photos to their observation: 1) Opening an existing photo on the file system; 2) taking a photo themselves using the built-in camera. Since the one of the requirements for the application is Windows Mobile 6.0 Professional, my job got a lot easier with the Microsoft.WindowsMobile.Forms.CameraCaptureDialog and the Microsoft.WindowsMobile.Forms.SelectPictreDialog forms

Here's a small sample of how to use the CameraCaptureDialog:

string image_filename = null;
using (CameraCaptureDialog camera = new CameraCaptureDialog()) {
  camera.Owner = this;
  camera.Title = base.Text;
  camera.Mode = CameraCaptureMode.Still;
  camera.StillQuality = CameraCaptureStillQuality.High;

  if (camera.ShowDialog() == DialogResult.OK) {
    image_filename = camera.FileName;
  }
}

And for the SelectPictureDialog:

string image_filename = null;
using (SelectPictureDialog open = new SelectPictureDialog()) {
  open.Filter = "Pictures (*.BMP;*.JPG;*.GIF)|*.BMP;*.JPG;*.GIF";

  if (open.ShowDialog() == DialogResult.OK) {
    image_filename = open.FileName;
  }
}

For saving to the local database I simply convert an Image to byte[]. I do this by:

public static byte[] ImageToByteArray(Image image, ImageFormat format) {
  MemoryStream stream = new MemoryStream();
  image.Save(stream, format);

  byte[] buffer = stream.GetBuffer();

  stream.Close();
  stream = null;

  return buffer;
}

or

public static byte[] ImageToByteArray(string image_file) {
  FileStream stream = new FileStream(image_file, FileMode.Open);
  int size = (int)stream.Length;
  byte[] buffer = new byte[size];
  stream.Read(buffer, 0, size);

  return buffer;
}

For loading image from the database to the application I simply convert the byte[] to an Image. I do that by:

public static Image ByteArrayToImage(byte[] raw_data) {
  MemoryStream stream = new MemoryStream(raw_data.Length);
  stream.Write(raw_data, 0, raw_data.Length - 1);

  Bitmap image = new Bitmap(stream);

  stream.Close();
  stream = null;

  return image;
}

Since each inspection report will contain around 40-50 observation, it can also take around 40-50 images. For synchronizing the application via web services, I had to split up the inspection report in parts, 1 part will contain all the textual information, and all the other parts will be for the photos. The Mobile Client Software Factory made life easier for having this architecture run on an occasionally connected environment.

Other things that are keeping me busy is integrating mobile applications with popular out-of-the-box ERP systems, such as Navision, Axapta, and Visma. I'll post more info and how-to's once I get more familiar with these systems.