Sunday, July 31, 2005

Remove mMode Icon from Home Screen

Here's how you can remove the mMode Icon from your SMT5600 SmartPhone home screen.

1. Download a SmartPhone registry editor, I suggest this one.
2. Open the registry editor and look for the path:
\HKEY_LOCAL_MACHINE\SOFTWARE\AT&T\PluginFavorites
3. Double click on "Order" and remove the mMode.lnk, then click OK.

4. Now we need to find a .lnk to replace mMode.lnk. Open up \HKEY_CURRENT_USER\Software\Microsoft\Shell\StartMenu to see a list of .lnk replacements. In this case, I will choose "Call History.lnk"
5. Go back to \HKEY_LOCAL_MACHINE\SOFTWARE\AT&T\PluginFavorites and add this new .lnk

6. Restart your phone.

Friday, July 29, 2005

C# Set Cursor Position in Textbox

How does one go about setting the cursor position in a .NET CF TextBox?

You use a combination of the TextBox.SelectionStart and TextBox.SelectionLength.

For example,

private void textBox1_GotFocus(object sender, System.EventArgs e)
{
textBox1.SelectionStart = 5;
textBox1.SelectionLength = 0;
}


from groups.google.

C# String Formatting Resources

Here are two incredibly useful string formatting resources, including how to do variable-width string alignment in C#.

1. http://www.stevex.org/CS/blogs/dottext/articles/158.aspx
2. http://www.codeproject.com/books/0735616485.asp

Sunday, July 24, 2005

Thread Safe Singleton

Typical WinForms applications are typically running in the UI thread, so the simple singleton will provide you with the functionality that you're looking for. However, if you are creating a multi-threaded application that needs to access a singleton across all of its threads, then you will need to create a thread-safe singleton class instead. What you gain in functionality comes at the cost of some performance, so you shouldn't use this form of the class unless you actually intend to use the class from multiple threads.

Thread Safe Singleton Code

Saturday, July 23, 2005

No Thread.Abort() in .NET CF 1.0

You cannot call Thread.Abort() in .NET CF 1.0, so how do you close/stop a thread?

See this HOWTO on msdn for a workaround.

Friday, July 22, 2005

Obtaining Memory Status w/C#

6.5. How do I determine how much memory a device has available?

You can P/Invoke the GetSystemMemoryDivision and GlobalMemorySystem functions to determine how the memory is divided and allocated between program and storage. Definitions of the parameters can be found in the API reference documentation.

from our friendly Smart Client Developer FAQ on msdn.

Control.Invoke System.ArgumentException

Even though I'd read this before, I'd forgotten about it and it caused me a slight headache today. If you call Control.Invoke on a custom event delegate which is not EventHandler, you will receive a System.ArgumentException.

From msdn,

Platform Note: In .NET Compact Framework applications, the delegate must be an instance of EventHandler. For an example, see How to: Use a Custom Delegate.

Efficiently Counting Lines in a File

Fast absolute, and statistical line counting algorithms for use with progress notification by Justin Rogers.

Creating a multi-form app on SmartPhone

Creating a Multiple Form Application Framework for the Microsoft .NET Compact Framework by Chris Tacke on msdn.

Thursday, July 21, 2005

Where art thou Java iterators?

So the C# equivalent to the Java iterator is the "Enumerator" class. As much as I like the foreach block and simple iteration in C#, I miss the ability to delete items from my collections while I traverse them like I can do in Java.

MSDN has an article (and slight fix/kludge) about this,

When this code is run, it generates an exception because you can't modify a collection while you're enumerating over it. That's a pretty good idea since you usually don't want collections to change underneath you, but it's sometimes annoying.

This got me thinking, and I realized that what was needed was a class that would isolate the enumeration from the collection. This class would do a full enumeration over the collection and store the data away to expose its own enumerable object. Changing the collection while using that object would be okay.

Logging on the SMT5600

I'm currently exploring ways to integrate logging into my application. I noticed that .NET Framework 2.0 has a class called Microsoft.Build.Utilities.Logger however this isn't available for .NET CF 2.0 much less .NET 1.0. Through a somewhat brief google driven investigation, I've determined that log4net appears to be the standard and, as the name implies, isn't much different from log4j and like that java library, is also an Apache OSS project. There are also other, less dramatic options.

For example, Neil Cowburn talks about ultra-simple application logging, though it does require P/Invoke.

This is a really cool technique that you can use to add simple logging/tracing functionality to your .NET Compact Framework apps. In Windows CE, there is a Win32 API which goes by the moniker of SetStdioPathW. By calling this API, you can redirect standard input, output and error output. The cool thing about this is that you can actually redirect the I/O to a text file! By redirecting standard output like this, you can scatter Console.WriteLine()'s all throughout your code and hey presto! Instant instrumentation. (link)

There is also nspring written by Jeff Varszegi, which seems to be a fairly complete logger he wrote by himself in ~1 month. You can read about it here.

.NET CF 2.0 Performance

Very interesting read here by the Microsoft .NET Compact Framework Team on .NET CF 2.0 performance. Not sure how relevant this is to those of us still developing in 1.0 -- but interesting nonetheless.

Wednesday, July 20, 2005

No Linked Lists in C# 1.0, 1.1

Argh, no linked lists in C#. They do have this in .NET 2.0 however, in the form of Generic Linked Lists.

Monday, July 18, 2005

My Classes Are Sealed

Continuing the theme tonight of talking about new C# keywords and language implementations (and comparing them to Java), I would be remiss if I didn't mention the keyword "sealed."

By marking a class "sealed" you prevent other classes from deriving from it. A sealed class, then, is sort of a one off. In contrast, you could also mark a class abstract (just like in Java -- behaves the same way too), which means that the class is made to be derived and cannot be instantiated itself.

readonly vs. const

I came across the "readonly" keyword haphazardly when I attempted to mark a static member variable as const. This is not possible in C#. Instead, use "readonly."

Please, Let Me Keep My Unassigned Variables

If you compile code in C# that contains uninitialized variables, you will receive a compilation error in VS. C# imposes definite assignment, which requires that all variables be assigned before they are used (from Programming C#). I typically resolve this by assigning my object vars to null or, in the case of primitives, something like int.MaxValue or -1.

Enumerations

When I first learned programming it was difficult for me to perceive the importance of enumerations (i.e. enum in C++). Then I moved from C++ to Java and I missed them. Now, with C#, enumerations are back... and the sweet thing about them is you can print them or add them to a string and their declaration name prints out instead of their value.

For instance,

enum Choice
{
Yes = 1,
No = 2
}


String strChoice = "Does Ian Smith actually like Debbie Gibson? " + Choice.Yes;

actually prints out, "Does Ian Smith actually like Debbig Gibson? Yes"

cool, no?

(PS, you c++ types should note that you don't have to follow the closed bracket with a semi-colon -- ahhh, feel the Java influence)

C# Entry Point

One thing that I found useful in Java was the ability to declare multiple static void main(string [] args) functions in different classes. This made it easy to setup simple test code. It's technically possible to do the same thing in C#; however, in this case you must use the /main command line switch to tell C# which class contains the Main method you'd like to execute.

Properties

I like Properties in C#, I think it begins to better define that space of functionality within a class that is not really a method but shouldn't just be a public member. In fact, according to MSDN protected instance fields and public instance fields should be used sparingly in C#.

A property is preferable to using a protected or public instance field.

From here.

I think this decoupling is good. It provides another layer of abstraction and allows the class designer to be a bit clever about what they expose and how they expose it.

Interestingly, I just found out that in addition to placing an access modifier on the Property itself, you can also apply one individually to the get/set. So, you could expose the "get" publically but make the set protected. For example,

public Size Size
{
get{ return _size; }
protected set { _size = value; }
}

I should mention that in my short experience with using interfaces in C#, I don't believe that it is possible to declare an access modifier on a Property (or its accessors) in an interface.

Saturday, July 16, 2005

Creating a Splash Screen

Two links from msdn:

1. Creating a Splash Screen Form on a Separate Thread
2. Crafting Smartphone User Interfaces Using .NET Compact Framework

Overview of .NET Compact Framework Garbage Collection

What are the ramifications of calling GC.Collect()?

You’ve probably heard the old wisdom “Yes, there is an API you can use to force a garbage collection, but don’t ever call it – the garbage collector can do a better job on its own”. Here’s the thinking behind that statement:
Garbage collection is a relatively expensive operation. As we’ve seen, a collection involves “suspending” threads, traversing object graphs, moving blocks of memory around and so on. Each time you force a collection, you’re forcing the Compact CLR to, at a minimum, get all threads to a state in which a collection can start, and scan the object graph looking for unused objects. Clearly, calling GC.Collect() repeatedly will have a seriously negative performance impact. The Compact Framework’s collector will kick in automatically after 750KB of objects have been created. As such, collections do happen occasionally - they don’t only happen when memory is exhausted. Furthermore, when a call to GC.Collect() returns, you can’t be guaranteed that all object finalizers have finished running. As a result, you cannot force a collection in hopes of deterministically finalizing a particular object (use the Dispose pattern to deterministically free resources associated with an object). That said, it is possible that you may have a scenario in which you’ll benefit by initiating collections yourself. If you believe you have such a scenario, feel free to try it, but be aware of the implications and measure performance carefully to make sure you’re not doing more harm than good.


from Steven Pratschner's .Net CF WebLog - link to above article can be found here.

Friday, July 15, 2005

MenuItem.MenuItems.Clear NotSupportedException

When I call myMenuItem.MenuItems.Clear() during runtime, I get a

"An unhandled exception of type 'System.NotSupportedException' occurred in System.Windows.Forms.dll

Additional information: NotSupportedException"

This same error occurs if I call myMenuItem.MenuItems.Remove(menu_i) in a for loop at the point when myMenuItem.MenuItems.Count is down to 1. I can remove all menu items up until that point successfully.

I posted to forums.microsoft.com here.

Sunday, July 10, 2005

Native Interoperability

To do really cool stuff on the SMT5600, sometimes it's necessary to use native code and the P/Invoke functionality. MSDN has some pretty good articles on native interoperability with the .NET Compact Framework.

Here are a select few that interest me:

For a good introduction into this world, download the sample P/Invoke Library solution and source code for C# and VB. Note however that this code is for Pocket PC 2003, rather than SmartPhone.

Friday, July 08, 2005

OpenNET CF SDF 1.3 Installation Failure

I just downloaded OpenNETCF Smart Device Framework 1.3 and was unpleasantly surprised by the following installer error ("Object reference not set to an instance of an object").


Others are apparently having this problem as well (here, here and here). I also attempted installation on my laptop, which resulted in the same error. I was looking forward to playing around with the various OpenNET CF offerings -- it's unfortunate that their installer is plagued with bugs.

Platform Invoke List!

Daniel Moth points to various P/Invoke listings on the web:

Every .NET developer is familiar with platform invocation services (PInvoke), the ability to call native functions declared in dlls via the DllImport attribute. In the early .NET days it was common to see queries about specific API declarations, but 4 years down the road you would have thought people would stop asking and instead use the ready-made ones easily found all over the web. This blog entry is proof of the opposite and it will serve as my pointer next time someone requests a particular Win32 DllImport declaration.

(link)

More Useful Blogs

Added Peter Foot's blog to the list of blogs on the sidebar. He is a .NET Compact Framework Microsoft MVP, opennetcf.org developer, and overall .NET CF guru.

Also added the Windows Mobile Team Blog to the sidebar.

As well as Daniel Moth's blog -- another .NET Compact Framework Microsoft MVP.

Numeric Textbox

No easy way to make a "numbers" only TextBox with .NET CF on the SmartPhones. There are two easy ways of adding this, one simply traps keypresses on a custom TextBox and the other uses Platform Invoke.

(1) from http://forums.microsoft.com/msdn/ShowPost.aspx?PostID=47854

// in InitializeComponents, add this event handler.
this.textBox1.KeyPress += new System.Windows.Forms.KeyPressEventHandler(this.NumbericTextValidator);

// now this is the event handler's method.
private void NumbericTextValidator(
object sender,
System.Windows.Forms.KeyPressEventArgs e)
{
if ("0123456789".IndexOf(e.KeyChar) > -1)
e.Handled = false;
else
e.Handled = true;
}

(2) http://www.devx.com/wireless/Article/21291/1763

This solution uses Platform Invoke, which has the advantage of actually changing the text input mode icon in the upper right hand corner of the SMT5600 (e.g. T9, abc, 123, etc.).

(3) Something similar over at opennetcf.org.

Update (April 24th, 2006): In .NET CF 2.0 this is easy.
For CF 2.0 (Windows Mobile 5.0 Smartphone):
using Microsoft.WindowsCE.Forms; //at the top
InputModeEditor.SetInputMode(textBox1, InputMode.Numeric); //somewhere in your code

(from link)

Play An Alert Sound w/.NET CF

Unfortunately it looks like there is no easy way to play an audible beep or alert in .NET CF --Console.Beep() is unsupported:( To be fair, this is not necessarily an issue with the CompactFramework, but infact, affects all of .NET 1.0. From the level of posts on newsgroups/forums, it has been duly noted by Microsoft and supposedly .NET 2.0 has a sound player control. Of course, this doesn't help those of us stuck with .NET CF 1.

Here is what information I gathered in a preliminary investigation:

1. A .NET example from codeproject that plays .Wav files (http://www.thecodeproject.com/cs/media/PlaySounds1.asp)
2. No simple playsound or beep function (http://forums.microsoft.com/msdn/ShowPost.aspx?PostID=26126)

Force Form to Top Level Focus

My .NET Compact Framework application requires that, from time to time, the user gets audibly alerted and a messagebox or form is displayed (using the System.Threading.Timer). Unfortunately, I have been unable to get my Form to display over the home screen. I have tried:

this.Focus();
this.Visible = true;
this.Show();

All in various combinations. I've found a few solutions about this on the web (I haven't tried either yet).

(1)
[DllImport("coredll.dll")]
public static extern bool SetForegroundWindow(IntPtr hWnd );
[DllImport("coredll")]
public static extern IntPtr FindWindow(string className, string wndName);
this.Show();
IntPtr hwnd = FindWindow(null, this.Text);
SetForegroundWindow(hwnd );

A full example can be found here.

(2)
If you need to fix your form at the top of the z-order, then rather than continually pulling the window to the front, use SetWindowPos (you'll need to P/Invoke) with the HWND_TOPMOST flag. This will keep your form at the top even if it loses focus. See the code for OpenNETCF.Win32.Win32Window for P/Invoke declaration:-http://vault.netcf.tv/VaultService/VaultWeb/GetFile.aspx?repid=2&path=%24%2fSDF%2fOpenNETCF.Windows.Forms%2fWin32%2fWin32Window.cs&version=2(username guest, password guest)

Thursday, July 07, 2005

Controls.Add ArgumentException

When executing this rather innocuous line of code within a System.Windows.Forms.Form:

this.Controls.Add(new Label());

I get this cruel, indescript error:


Note that I get the error no matter what type of Control I try to add (e.g. TextBox, ListView, etc.). Unfortunately, this is the worst type of error because the Exception can't be easily searched via Google.

My hunch at the moment is that this Exception is indicative of something more wrong than an ArgumentException (particularly because I KNOW that the argument is correct). The method that contains this Control.Add function is called by an event triggered by a Timer thread, I suspect that this could be the root of the problem. I found a post in the .NET newsgroups stating that manipulating a Windows Form from another thread is highly hazardous.

Update July 8th, 2005 12:29AM: I found out more information about the underlying problem here. From Code Project,

.NET allows you to call System.Windows.Forms.Control functions only from the thread in which the control was created. To run them from another thread we need to use the Control.Invoke (synchronous call) or Control.BeginInvoke (asynchronous call) functions.

Aha, now we're on the right track. However, note that .NET Compact Framework 1.0 does not support Control.BeginInvoke (although .NET CF 2.0 does). That's fine though. I created my own delegate and tried this on my own -- something really simple like public delegate void MyDelegate(); But then what do you know, I get the exact same System.ArgumentException as before but now when I call MyForm.Invoke(MyDelegate). It took me forever to track down why because it's in very small print in the MSDN API for the Control.Invoke method (and I missed it quite a few times):

Platform Note: In .NET Compact Framework applications, the delegate must be an instance of EventHandler. For an example, see How to: Use a Custom Delegate.

So, you can't use your own delegate, it has to be an instance of EventHandler. I haven't actually tried this yet because it took me roughly two hours to figure this out. I originally found out about this delegrate constraint here:

7.10. Can I create a custom delegate to pass to invoke?

No, in the .NET Compact Framework, only EventHandler methods can be invoked. The proper method for doing this is described here:

public void HandleMe(object o, EventArgs e) {...}
form.Invoke(new EventHandler(form.HandleMe));

Although the following will compile, it will not work properly:

public delegate void MyHandler();
public void HandleMeBadly() {...}
form.Invoke(new MyHandler(form.HandleMeBadly));

In fact, the HandleMeBadly code above does compile, which makes it even more confusing. If you try to run that code, you will get an ArgumentException error. Given what I know now, however, that Exception actually makes sense, as opposed to when I called this.Controls.Add(new Label()) in the non-creation thread.

Naming Conventions

People at my work are used to the Java naming conventions; C# is fairly different. For example, the guideline states that the first letter of methods and package names should be capitalized. Go here for more.

Wednesday, July 06, 2005

Update: Tab Order

Previously, I posted about tab order problems with custom controls.

I found more information about it in the .NET Compact Framework Developer's Guide (here), which says,

To tab out of the custom control to the previous control, call this.Parent.Controls(this.Parent.GetChildIndex(customcontrol) - 1).Focus() in the KeyDown event handler when a Keys.Up key is detected.

I also found this exact phrasing in the Smart Client Developer Center Home: Frequently Asked Questions (link).

5.41. How do I tab out of a custom control to the previous control?
Call this.Parent.Controls(this.Parent.GetChildIndex(customcontrol) - 1).Focus() in the KeyDown event handler when a Keys.Up key is detected.

There are multiple glaring errors with this documentation, which I find completely unsatisfactory. The code they meant to write was something more along the lines of:

this.Parent.Controls[this.Parent.Controls.GetChildIndex(customcontrol) - 1].Focus() ;

1. this.Parent.Controls returns a ControlCollection which is accessed with brackets, not parenthesis
2. this.Parent.GetChildIndex does not exist, they meant to write this.Parent.Controls.GetChildIndex

Even given these two corrections, this code is still not entirely accurate. We really don't want to try and give focus to just any Control -- in fact, some controls can't be focused (e.g. panels, labels, etc.) so using their code, in some cases, we end up with nothing being focused at all. To fix this, we could change this code to something more like this:

int index = this.Parent.Controls.GetChildIndex(customcontrol);
do{
if(index == 0)
index = this.Parent.Controls.Count; //roll over
} while(!this.Parent.Controls[--index].CanFocus());
this.Parent.Controls[index].Focus();

However, CanFocus() is not supported by the .NET Compact Framework, so instead we have to do something like this:

int index = this.Parent.Controls.GetChildIndex(customcontrol);
do{
if(index == 0)
index = this.Parent.Controls.Count; //roll over
} while(!this.Parent.Controls[--index].Focus());

In the above code, we rely on the fact that the Control.Focus method returns false if the focus request was unsuccessful to find a Control that is focusable.

Update July 6th 2005, 2:00PM: Hmm, actually, as it turns out the Control.Focus() method returns true for Controls like Label -- which is somewhat contradictory to API documentation. So, I had to change this slightly. Here is some OnKeyDown code that I wrote for the SmartPhone that provides tab control for custom controls (sorry about the formatting, if I keep writing code on this blog, I will have to get a tool that simplifies posting code and formats it nicely as well).

protected override void OnKeyDown(KeyEventArgs e){
switch (e.KeyCode)
{
case Keys.Down:
int index = this.Parent.Controls.GetChildIndex(this);
do{
if ((index + 1) == this.Parent.Controls.Count)
index = -1; //roll over
index++;
} while (!isFocusable(this.Parent.Controls[index]));
break;

case Keys.Up:
index = this.Parent.Controls.GetChildIndex(this);
do{
if (index == 0)
index = this.Parent.Controls.Count; //roll over
index--;
} while(!isFocusable(this.Parent.Controls[index]));
break;
}
base.OnKeyDown(e);
}

public bool isFocusable(Control control)
{
if(control is Label)
return false;
else
return true;
}

Tuesday, July 05, 2005

No Transformation (or any GDI+) Support

I wouldn't think so, but Google confirmed it by returning this article, .NET Compact Framework Graphics, by David Durant and Paul Yao (an excerpt from their book .NET Compact Framework Programming with C#).

This brings up another difference between the desktop .NET Framework and the .NET Compact Framework: available coordinate transformations. The desktop provides a rich set of coordinate transformations—scrolling, scaling, and rotating—through the Matrix class and the 3 × 3 geometric transform provided in the System.Drawing.Drawing2D namespace. The .NET Compact Framework, by contrast, supports no coordinate mapping. That means that, on handheld devices, application software that wants to scale, scroll, or rotate must handle the arithmetic itself because neither the .NET Compact Framework nor the underlying operating system provides any coordinate transformation helpers. What the .NET Compact Framework provides, as far as coordinates go, is actually the same thing that the underlying Windows CE system provides: pixels, more pixels, and only pixels.

SmartPhone Screen Resolution

Pocket PC:

  • Portrait / Landscape QVGA (240x320, 96 dpi)
  • Portrait / Landscape VGA (480x640, 192 dpi)
  • Square screen (240x240, 96 dpi)
  • Square screen VGA (480x480, 192 dpi)

Smartphone:

  • Portrait (176x220, 96 dpi) <-SMT5600
  • Portrait QVGA (240x320, 131 dpi)

The Audiovox SMT5600 features a TFT (Thin Film Transistor) screen, capable of displaying 65K colors. Measuring 2.2-inch-diagonally, it has a resolution of 176 x 220 px.

(from MSDN)

Monday, July 04, 2005

Reading in Files on SmartPhone Emulator

The other day I made a post on http://forums.microsoft.com about trying to load input files on the SmartPhone SE Emulator, which you can find here. The problem, of course, is trying to figure out how the SmartPhone Emulator maps a portion of the desktop file system to its file system. The response I got was,

The easiest way is to use folder sharing. Under File\Configure\General Tab set the folder sharing directory to any directory on your desktop. Put your text files into that directory and they will be accessible from "\Storage Card\" inside the Windows Mobile OS.

Though these instructions were either for VS2003 or inaccurate -- I got the point. In VS2005 you can do something similar by going to the File Menu -> Tools -> Options -> Device Tools -> Devices -> Smartphone 2003 SE Emulator -> Properties -> Emulator Options. The Emulator Options button opens a Emulator Properties dialog box. In the General tab of this dialog, you can input a "Shared folder" path, which I did (Incidentally, if you hit F1 on this tab for Help, you will note that the only function not explained in the Help is the "Shared folder"! Hmm, maybe it's supposed to be self-explanatory).

However, we're not done yet. We still have to figure out a way to refer to this "Shared folder" in your code -- This is something I have yet to figure out. I tried one thing this morning, which was to create a file programtically on the SmartPhone Emulator and see if I could find it on the desktop. I ran this simple test code:

string FILE_NAME = "MyFile.txt";
if (File.Exists(FILE_NAME))
{
Console.WriteLine("{0} already exists.", FILE_NAME);
}
StreamWriter sw = File.CreateText(FILE_NAME);
sw.WriteLine("This is my file.");
sw.Close();


After the code completed, I could not find the file anywhere on my desktop's filesystem. I also tried "\\Storage\\MyFile.txt" and "\\Storage Card\\MyFile.txt" for FILE_NAME but no dice. If you create FileInfo or DirectoryInfo objects to look at the file, it appears to be using the SmartPhone-type filesystem (e.g. root dir is \ instead of, say, c:\).

Update (July 5th 2005, 3:35PM): As Vladimir's comment suggests, he followed up on his original reply to me on the message boards here. A copy of the post follows:

The directions were for VS2005 DE for configuration of the running instance of the emulator. There are three emulator configurations that exist - global, local saved and instance. The Global configuration is what you set under Tools\Options\Device Tools\Pick Platform\Properties\Emulator Options. The global configuration will be applied to an emulator if there is no local saved state. If you have created a local saved state - the global configuration is ignored until you clear the saved state. The instance configuration is accessed after the emulator is started via File\Configure (on the emulator itself) and is only applied to the instance that is running.

So if you start the emulator and click on the emulator File menu, select Configure option and then select General Tab - you should see the "Shared Folder" textbox.

The folder is accessed as "\Storage Card\". My guess is that you changed your global configuration but it was not applied because you had a local saved state. You can verify that the shared folder works correctly by craddling your emulator with Device Emulator Manager and using ActiveSync to browse the filesystem.

I tried this and it works perfectly.

Sunday, July 03, 2005

By Pen You Mean Color

The Pen object in .NET CF 1.0 is incredibly limited. It has no methods and only one property, which is Color. So basically the Pen object is a glorified Color object. The .NET CF 2.0 fixes this a bit by including the Width attribute.

I found this in the MSDN .NET CF 1.0 FAQ:

Setting the pen width is not available in the .NET Compact Framework. Some alternate solutions include:

  • Drawing filled rectangles with the Graphics.FillRectangle method
  • Drawing multiple lines next to each other
  • Writing a custom graphics routine with GAPI

Let the Designers Choose

I can understand why Microsoft has endeavored to make clear the interaction/ui differences between a desktop and a SmartPhone or even a PocketPC and SmartPhone to developers. Obviously, the lack of a mouse or touchscreen introduces a new set of HCI issues. I will say, though, that I am fundamentally opposed to how Microsoft has chosen to ensure SmartPhone developers don't veer off a fairly rigid idea about how SmartPhone interaction should take place -- which is somewhat ironic given that the developers in my research group find the SmartPhone OS UI to be overly complicated. The focus seemed to be create an OS interface that follows the most prominent UI features/models of Windows rather than creating an OS interface that is actually good as a phone UI (e.g. syncing to the Outlook contact list probably seemed like a good idea until you test it with actual users, where you would find that, in fact, much of an Outlook contact list is not made up of phone numbers but rather e-mail addresses! This is great if I want to use e-mail on my phone but not when I just want to make a phone call. My Outlook contact list is populated with 100s of contacts whom I rarely talk to and would never call (because, for one, I don't even have their numbers). But I digress...

Back to my original topic which is: the way Microsoft has structured the .NET CF API to enforce its limited vision of HCI on the phones.

Case in point, radio buttons. Yes, radio buttons. Microsoft disallows Radio Buttons on the SmartPhones. They made that choice for me. They think radiobuttons are a bad idea on an interface that small with limited input mechanisms. Instead, they suggest I use a ListView where only one item can be selected at a time (ah, how clever, that's just like a radiobutton but not!). However, in my application, it is much more relevant and intuitive to usea radiobutton. But somehow this choice isn't mine, it's Microsofts.

You know, fundamentally, I think the .NET CF GUI API should really just expose everything. Let the designers choose what's right or wrong. I know Microsoft is trying to provide some baseline standard, fine, but don't restrict my creative freedom and design talents.

Saturday, July 02, 2005

How Dare You Take My OwnerDraw Away!

So, I thought the ListView class might be the answer to all of my Key Event problems. I need an interface on the phone with multiple controls that all respond to "hot key" presses. The ListView Control, which is probably the most used layout control on the phone, basically gives you this functionality if your two controls are "labels" and "checkboxes." In my case, I need one more control ala "radiobuttons" -- how hard could it be to draw a radiobutton instead of a checkbox in a custom ListView class?

So, I derived the ListView class and overrode the OnPaint and OnBackgroundPaint methods and launched a test on the emulator. Hmm, it looked like my OnPaint methods were not being called. WTF? Some searching found, "The ListView control is drawn by the operating system or is owner drawn, so the Paint event is never raised. For more information about owner-drawing, see the OwnerDraw property." Stop right there!

Yes things still sound salvagable until you note that the .NET CF 1.0 ListView control does NOT have an OwnerDraw property. Try it yourself if you like -- create a simple class MyListView : ListView and then override OnPaint and OnBackgroundPaint. Note how those methods are never called. So, unless you are using .NET 2.0 you're screwed.

Jon goes back to the drawing board now (which will probably involve something like the custom list view found here, which is really not even a custom list view but rather a completely new custom control). Oh the woes of programming UI on the SmartPhones. Which bears the question, why build a huge set of widgets and then restrict their usage so much that they become useless?