Sunday, December 30, 2007

Wind of changes and happiness at the end of this year - Welcome Santiago!

I'm very sorry because I know, there were several days without any post on this blog, but I think you'll understand the reasons.

First of all, I have left Q4Tech after almost 8 years of great experiences and lot of friends there. It's just time to go ahead and continue my way. The selected destination was Clarius Consulting where I've already started working as Dev Lead. I've previously worked with Daniel Cazzulino (Kzu) on the development of the Patterns & Practices Mobile Client Software Factory, and some Microsoft Live Labs projects, so this is a familiar place to work for me, and I'm really happy to start this new stage on my professional life with such hi-level team.

But there is another big, huge reason to be happy at this end of 2007, my first son was born on December 24th, What a Christmas present!! The Best one!

Well, he is just 6 days old (or young), and I'm so proud of let you know him, Santiago Gallardo is here:

SantiGallardo

He's the reason because I don't remember what is to sleep 3 hours in a row ;)

Happy New Year for all of you! I'll continue posting about mobility on 2008... see you then!

Tuesday, December 11, 2007

Adaptative UI sample using our multiline MeasureString implementation

I've decided to post a sample application showing how to use the multiline MeasureString code provided on my previous post, to build a dynamic/adaptative UI. It also support screen rotation.

Here you have some screenshots:

image

After changing the text and pressing "Relayout" you can get something like this:

image

And rotating the screen:

image

Finally, this is the source code, enjoy it!

Wednesday, December 5, 2007

Multi-line Graphics.MeasureString implementation on .Net CF

If you have ever tried to build a dynamic UI for a .Net Compact Framework application, probably you've had to build adjustable multi-line labels or text-boxes. It's hard to solve because the only supported overload for Graphics.MeasureString on .Net CF is:

public SizeF MeasureString ( string text, Font font )

When you need to resize or position the controls dynamically in runtime, it's very important to know what should be the size, particularly the height of the multi-line label or multi-line text-box. It's the same problem if you're building a new custom control with a complex layout and you need to measure a potential multi-line string.


Having only this overload on .Net CF, we cannot get a multi-line string size because it calculates just the size of a single-line string. If the string is longer than the string, it gets a big SizeF result but as a single-line text.


Solving the problem


The only solution here is to implement our own multi-line MeasureString method.


To solve the problem, we'll use the native API DrawText. It will calculate the size of the text according with the uFormat parameter and using the graphics (device context) selected font.

[DllImport("coredll.dll")]
static extern int DrawText(IntPtr hdc, string lpStr, int nCount, ref Rect lpRect, int wFormat);

Additionally, if the control if a text-box, we should use the DT_EDITCONTROL flag and add extra 6 pixels (3 pixels at top and 3 pixels at bottom) to the calculated size.


Remember, if you have an empty string, you'll probably need to force one-line size for your controls.


Here's the CFMeasureString sample class source code:

using System;
using System.Text;
using System.Runtime.InteropServices;
using System.Drawing;

namespace MeasureStringSample
{
public class CFMeasureString
{
private struct Rect
{
public int Left, Top, Right, Bottom;
public Rect(Rectangle r)
{
this.Left = r.Left;
this.Top = r.Top;
this.Bottom = r.Bottom;
this.Right = r.Right;
}
}

[DllImport("coredll.dll")]
static extern int DrawText(IntPtr hdc, string lpStr, int nCount, ref Rect lpRect, int wFormat);
private const int DT_CALCRECT = 0x00000400;
private const int DT_WORDBREAK = 0x00000010;
private const int DT_EDITCONTROL = 0x00002000;

static public Size MeasureString(Graphics gr, string text, Rectangle rect, bool textboxControl)
{
Rect bounds = new Rect(rect);
IntPtr hdc = gr.GetHdc();
int flags = DT_CALCRECT|DT_WORDBREAK;
if (textboxControl) flags |= DT_EDITCONTROL;
DrawText(hdc, text, text.Length, ref bounds, flags);
gr.ReleaseHdc(hdc);
return new Size(bounds.Right - bounds.Left, bounds.Bottom - bounds.Top + (textboxControl? 6 : 0));
}
}
}

And you can use it to resize a label in the following way:

label1.Height = CFMeasureString.MeasureString(CreateGraphics(), label1.Text, label1.ClientRectangle, false).Height;

Now we can start using it and build a dynamic UI on .Net CF.


Update: To support different font sizes, please take a look at this post which shows a good improvement (a.k.a. fix ;)).

Sunday, December 2, 2007

Working with forms in Windows Mobile

As we've already seen, a mobile application UI is naturally modal. Does it mean every form should be open using Form.ShowDialog()?

Of course it doesn't. There are several business requirements (like complex navigation schemas), design and architectural constrains or even basic requirements like the need of implement a wizard that require a different approach. Here's where we need to face new design challenges to provide a rich user experience in our application.

How to show a form in Windows Mobile

It depends on the use case, but you should take care of the form title, the Running Programs window and how it interacts with the multitasking environment.

PocketPC and the Running Programs list

You can show a .Net CF form using Form.Show() or Form.ShowDialog(). In both you'll get in the Running Programs list one instance for each open form, as they were different applications, and you can navigate to any form from that list directly (it will appear disabled if using Form.ShowDialog()).

The first method to prevent this, is every time you show a form, you should change the current form title to an empty string (String.Empty), in the Running Programs List you'll see only the new form title. When you come back, you should change the title (Form.Text property) again to the original title and it will be the one on the Running Programs List. Here you have a sample code:

private void menuItem1_Click(object sender, EventArgs e)
{
Form2 form = new Form2();
this.Text = String.Empty;
form.Show();
}
private void Form1_Activated(object sender, EventArgs e)
{
this.Text = "Form1";
}

The second method is using Form.Owner (valid only for .Net CF 2.0+). In this case you should set the Form.Owner property of the new form to the current form, and the title will remain the current form's title. Due to this, we need to change the current title to the new form title and set it back when we come back to the form.

It makes sense if you're using Form.ShowDialog() or if you have a controller form and you're showing kind of a wizard opening different forms and all of them have the same "controller form" as the owner (which is where you start and where you finish the wizard).

Here is the sample code for method 2:

private void menuItem2_Click(object sender, EventArgs e)
{
Form3 form = new Form3();
form.Owner = this;
this.Text = form.Text;
form.Show();
}
private void Form2_Activated(object sender, EventArgs e)
{
this.Text = "Form2";
}

Handling Form_Activate and Form_Deactivate events

Form_Activate is not only useful to set the form title back when the user returns to the form. As we've seen before, Windows Mobile is a multitasking environment and we should deal with it. Here's when Form_Activate and Form_Deactivate are fully helpful.

You can stop unnecessary expensive processes if the application is in background on Form_Deactivate, and start them again when Form_Activate in order to preserve processor, memory and fundamentally battery.

Keep it in mind when you develop more mobile applications.