Commentor Blog

When Quality Matters

Commentor A/S

When Quality Matters

Contact usSend mail

Recent comments

Disclaimer

The opinions expressed herein are my own personal opinions and do not represent my employer's view in anyway.

© Copyright 2012

How to display a Notification Bubble in Windows Mobile using .NETCF

[Reposted from Christian Helle's Blog]

Yesterday, I found myself using an old piece of code that I wrote ages ago. It's something I've used every now and then for past few years. Since I myself find it useful, I might as well share it. All the code does is display a Notification Bubble in Windows Mobile. To do this you use the Notification class in the Microsoft.WindowsCE.Forms namespace. Even though the Notification class is very straight forward and easy to use, I created a helper class so that I only need to write one line of code for displaying a notification bubble: NotificationBubble.Show(2, "Caption", "Text");

/// <summary>
/// Used for displaying a notification bubble
/// </summary>
public static class NotificationBubble
{
    /// <summary>
    /// Displays a notification bubble
    /// </summary>
    /// <param name="duration">Duration in which the notification bubble is shown (in seconds)</param>
    /// <param name="caption">Caption</param>
    /// <param name="text">Body</param>
    public static void Show(int duration, string caption, string text)
    {
        var bubble = new Notification
        {
            InitialDuration = duration,
            Caption = caption,
            Text = text
        };
 
        bubble.BalloonChanged += OnBalloonChanged;
        bubble.Visible = true;
    }
 
    private static void OnBalloonChanged(object sender, BalloonChangedEventArgs e)
    {
        if (!e.Visible)
            ((Notification)sender).Dispose();
    }
}


Hope you found this helpful.

Be the first to rate this post

  • Currently 0/5 Stars.
  • 1
  • 2
  • 3
  • 4
  • 5

Posted by christian.resma.helle on Thursday, February 10, 2011 9:16 AM
Permalink | Comments (0) | Post RSSRSS comment feed

Multi-platform Mobile Development - Sending SMS

[Reposted from Christian Helle's Blog]

This is a task that pretty much every mobile device can do, or at least any mobile phone can do. In this short post on multi-platform mobile development I would like to demonstrate how to send an SMS or launch the SMS compose window in a mobile application.

I'm going to demonstrate how to use the messaging API's of the following platforms:

  • Android
  • Windows Phone 7
  • Windows Mobile 5.0 (and higher) using .NET Compact Framework
  • Windows Mobile using the Platform SDK (Native code)

Android

There are 2 ways of sending SMS from an Android application: Launching the Compose SMS window; Through the SmsManager API. I figured that since this article is supposed to demonstrate as many ways as possible for sending SMS that I create a helper class containing methods that I think would be useful or at least convenient to have.

Here's a SMS helper class for Android that I hope you would find useful.

package com.christianhelle.android.samples;

import android.app.Activity;
import android.app.PendingIntent;
import android.content.BroadcastReceiver;
import android.content.Context;
import android.content.Intent;
import android.content.IntentFilter;
import android.net.Uri;
import android.telephony.SmsManager;
import android.widget.Toast;

/**
* Helper class for sending SMS messages
*
*
@author Christian Resma Helle
*/
public class Sms {
   
private final static String SENT_ACTION = "SENT";
   
private final static String DELIVERED_ACTION = "DELIVERED";
   
private Context context;
   
private PendingIntent sentIntent;
   
private PendingIntent deliveredIntent;

   
/**
     * Creates an instance of the SMS class
     *
     *
@param context     Context that owns displayed notifications
     */
   
public Sms(Context context) {
       
this.context = context;
        registerForNotification
();
   
}

   
private void registerForNotification() {
       
sentIntent = PendingIntent.getBroadcast(context, 0, new Intent(SENT_ACTION), 0);
        deliveredIntent = PendingIntent.getBroadcast
(context, 0, new Intent(DELIVERED_ACTION), 0);

        context.registerReceiver
(messageSentReceiver, new IntentFilter(SENT_ACTION));
        context.registerReceiver
(messageDeliveredReceiver, new IntentFilter(DELIVERED_ACTION));
   
}
    
   
protected void finalize() throws Throwable {
       
context.unregisterReceiver(messageSentReceiver);
        context.unregisterReceiver
(messageDeliveredReceiver);
   
}

   
/**
     * Opens the Compose SMS application with the recipient phone number displayed
     *
     *
@param phoneNumber     recipient phone number of the SMS
     */
   
public void composeMessage(String phoneNumber) {
       
Intent intent = new Intent(Intent.ACTION_VIEW, Uri.parse("sms:" + phoneNumber));
        context.startActivity
(intent);
   
}

   
/**
     * Opens the Compose SMS application with the recipient phone number and message displayed
     *
     *
@param phoneNumber     recipient phone number of the SMS
     *
@param text             message body
     */
   
public void composeMessage(String phoneNumber, String text) {
       
Intent intent = new Intent(Intent.ACTION_VIEW);
        intent.putExtra
("sms_body", text);
        intent.putExtra
("address", phoneNumber);
        intent.setType
("vnd.android-dir/mms-sms");
        context.startActivity
(intent);
   
}

   
/**
     * Opens the Compose SMS application with the multiple recipient phone numbers and the message displayed
     *
     *
@param phoneNumber     recipient phone numbers of the SMS
     *
@param text             message body
     */
   
public void composeMessage(String[] phoneNumbers, String text) {
       
StringBuilder sb = new StringBuilder();
       
for (String string : phoneNumbers) {
           
sb.append(string);
            sb.append
(";");
       
}
       
composeMessage(sb.toString(), text);
   
}

   
/**
     * Send an SMS to the specified number
     *
     *
@param phoneNumber     recipient phone number of the SMS
     *
@param text             message body
     */
   
public void sendMessage(String phoneNumber, String text) {
       
sendMessage(phoneNumber, text, false);
   
}

   
/**
     * Send an SMS to the specified number and display a notification on the message status
     * if the notifyStatus parameter is set to
<b>true</b>
    
*
     *
@param phoneNumber     recipient phone number of the SMS
     *
@param text             message body
     *
@param notifyStatus     set to <b>true</b> to display a notification on the screen
     *                         if the message was sent and delivered properly, otherwise
<b>false</b>
    
*/
   
public void sendMessage(String phoneNumber, String text, boolean notifyStatus) {
       
SmsManager sms = SmsManager.getDefault();
       
if (notifyStatus) {
           
sms.sendTextMessage(phoneNumber, null, text, sentIntent, deliveredIntent);   
       
} else {
           
sms.sendTextMessage(phoneNumber, null, text, null, null);
       
}   
    }

   
/**
     * Send an SMS to multiple recipients and display
     *
     *
@param phoneNumber     recipient phone number of the SMS
     *
@param text             message body
     */
   
public void sendMessage(String[] phoneNumbers, String text) {
       
sendMessage(phoneNumbers, text, false);
   
}

   
/**
     * Send an SMS to multiple recipients and display a notification
     * on the message status if notifyStatus is set to
<b>true</b>
    
*
     *
@param phoneNumber     recipient phone number of the SMS
     *
@param text             message body
     *
@param notifyStatus     set to <b>true</b> to display a notification on the screen
     *                         if the message was sent and delivered properly, otherwise
<b>false</b>
    
*/
   
public void sendMessage(String[] phoneNumbers, String text, boolean notifyStatus) {
       
StringBuilder sb = new StringBuilder();
       
for (String string : phoneNumbers) {
           
sb.append(string);
            sb.append
(";");
       
}
       
sendMessage(sb.toString(), text, notifyStatus);
   
}

   
private BroadcastReceiver messageSentReceiver = new BroadcastReceiver() {
       
@Override
       
public void onReceive(Context context, Intent intent) {
           
switch (getResultCode()) {
           
case Activity.RESULT_OK:
                Toast.makeText
(context, "SMS sent", Toast.LENGTH_SHORT).show();
               
break;
           
case SmsManager.RESULT_ERROR_GENERIC_FAILURE:
                Toast.makeText
(context, "Generic failure", Toast.LENGTH_SHORT).show();
               
break;
           
case SmsManager.RESULT_ERROR_NO_SERVICE:
                Toast.makeText
(context, "No service", Toast.LENGTH_SHORT).show();
               
break;
           
case SmsManager.RESULT_ERROR_NULL_PDU:
                Toast.makeText
(context, "Null PDU", Toast.LENGTH_SHORT).show();
               
break;
           
case SmsManager.RESULT_ERROR_RADIO_OFF:
                Toast.makeText
(context, "Radio off", Toast.LENGTH_SHORT).show();
               
break;
           
}
        }
    }
;

   
private BroadcastReceiver messageDeliveredReceiver = new BroadcastReceiver() {
       
@Override
       
public void onReceive(Context context, Intent intent) {
           
switch (getResultCode()) {
           
case Activity.RESULT_OK:
                Toast.makeText
(context, "Message delivered", Toast.LENGTH_SHORT).show();
               
break;
           
case Activity.RESULT_CANCELED:
                Toast.makeText
(context, "Message not delivered", Toast.LENGTH_SHORT).show();
               
break;
           
}
        }
    }
;
}


Before your Android application can send SMS it needs the right permissions for it. Add the SEND_SMS permission your AndroidManifest.xml

<uses-permission android:name="android.permission.SEND_SMS"></uses-permission>


Here are some examples on how to use the SMS helper class defined above from within an Activity class:

Sms sms = new Sms(getApplicationContext());

// Send an SMS to the specified number
sms.sendMessage("+4512345678", "Multi-platform Mobile Development");

// Send an SMS to the specified number and display a notification on the message status
sms.sendMessage("+4512345678", "Multi-platform Mobile Development", true);

// Send an SMS to multiple recipients
sms.sendMessage(new String[] { "+4512345678", "+4598765432" }, "Multi-platform Mobile Development");       

// Send an SMS to multiple recipients and display a notification on the message status
sms.sendMessage(new String[] { "+4512345678", "+4598765432" }, "Multi-platform Mobile Development", true);

// Opens the Compose SMS application with the recipient phone number displayed
sms.composeMessage("+4512345678");

// Opens the Compose SMS application with the recipient phone number and message displayed
sms.composeMessage("+4512345678", "Multi-platform Mobile Development");

// Opens the Compose SMS application with the multiple recipient phone numbers and the message displayed
sms.composeMessage(new String[] { "+4512345678", "+4598765432" }, "Multi-platform Mobile Development");


Windows Phone 7

This platform unfortunately doesn't provide as vast a API collection compared to Android and Windows Mobile. To send an SMS in Windows Phone 7, you will have to use the SMS Compose page in the built-in messaging application. To launch this we call the Show() method in SmsComposeTask.

Here's how to use SmsComposeTask

SmsComposeTask launcher = new SmsComposeTask();
launcher.To = "+45 12 34 56 78";
launcher.Body = "Multi-platform Mobile Development";
launcher.Show();


Windows Mobile 5.0 (and higher) using .NET Compact Framework

Sending an SMS in this platform is just as easy as doing so in Windows Phone 7. Windows Mobile provides native API's for the Short Messaging System, these methods are exposed as C type methods in a DLL called sms.dll. Aside from the SMS API, the platform also offers another API called the Messaging API (CE MAPI) for sending SMS, MMS, and Emails. Microsoft has provided managed wrappers for these and many other API's to make the life of the managed code developer a lot easier.

To send an SMS in Windows Mobile 5.0 (and higher) we use the SmsMessage object. There are 2 ways of accomplishing this: Using the Send() method of the SmsMessage class; Sending the SMS using the Compose SMS application

Here's a snippet on how to send SMS using the Send() method

SmsMessage sms = new SmsMessage("+45 12 34 56 78", "Multi-platform Mobile Development");
sms.Send();


Here's a snippet on how to send SMS using the Compose SMS application

SmsMessage sms = new SmsMessage("+45 12 34 56 78", "Multi-platform Mobile Development");
MessagingApplication.DisplayComposeForm(sms);


The code above depends on 2 assemblies that must be referenced to the project:
  • Microsoft.WindowsMobile.dll
  • Microsoft.WindowsMobile.PocketOutlook.dll


It is also possible to P/Invoke the SMS API through sms.dll, but this requires a slightly more complicated solution. In the next section, I will demonstrate how use the SMS API in native code. This should give you an idea on how to use the SMS API if you would like to go try the P/Invoke approach.


Windows Mobile using the Platform SDK (Native code)

Probably not very relevant for most modern day managed code developers but just to demonstrate as many ways to send SMS in as many platforms as possible I'd like to show how to send SMS in native code using the Windows CE Short Message Service (SMS) API.

Here's a sample C++ helper class for sending SMS using the Platform SDK

#include "stdafx.h"
#include "sms.h"
#include <string>
 
class SmsMessage 
{
private:
    std::wstring recipient;
    std::wstring message;
 
public:
    SmsMessage(const wchar_t* phoneNumber, const wchar_t* text) 
    {
        recipient = phoneNumber;
        message = text;
    }
 
    void Send() 
    {
        SMS_HANDLE smshHandle;
        HRESULT hr = SmsOpen(SMS_MSGTYPE_TEXT, SMS_MODE_SEND, &smshHandle, NULL);
        if (hr != S_OK)
            return;
 
        SMS_ADDRESS smsaDestination;
        memset (&smsaDestination, 0, sizeof (smsaDestination));
        smsaDestination.smsatAddressType = SMSAT_INTERNATIONAL;
        lstrcpy(smsaDestination.ptsAddress, recipient.c_str());
 
        TEXT_PROVIDER_SPECIFIC_DATA tpsd;
        tpsd.dwMessageOptions = PS_MESSAGE_OPTION_NONE;
        tpsd.psMessageClass = PS_MESSAGE_CLASS1;
        tpsd.psReplaceOption = PSRO_NONE;
 
        SMS_MESSAGE_ID smsmidMessageID = 0;
        hr = SmsSendMessage(smshHandle, 
                            NULL, 
                            &smsaDestination, 
                            NULL,
                            (PBYTE) message.c_str(), 
                            (message.length() + 1) * sizeof(wchar_t), 
                            (PBYTE) &tpsd, 
                            sizeof(TEXT_PROVIDER_SPECIFIC_DATA), 
                            SMSDE_OPTIMAL, 
                            SMS_OPTION_DELIVERY_NONE, 
                            &smsmidMessageID);
 
        SmsClose (smshHandle);
    }
};


The code above requires the that the project is linked with sms.lib, otherwise you won't be able to build.

Here's a snippet of how to use the SMS helper class defined above:

SmsMessage *sms = new SmsMessage(L"+14250010001", L"Multi-platform Mobile Development");
sms->Send();
delete sms;


For those who don't know what +14250010001 is, this is the phone number of the Windows Mobile emulator. For testing SMS functionality on the emulator, you can use this phone number.


That's it for now. I hope you found this article interesting.

Be the first to rate this post

  • Currently 0/5 Stars.
  • 1
  • 2
  • 3
  • 4
  • 5

Posted by christian.resma.helle on Friday, January 21, 2011 4:40 AM
Permalink | Comments (0) | Post RSSRSS comment feed

Multi-platform Mobile Development - Creating a List Based UI

[Reposted from Christian Helle's Blog] 

Here's the first installment on my series on multi-platform mobile development articles. A common practice on displaying information to a mobile device user is a list. A list is one of the best ways to display a group of information allowing the user to easily select which specific details he/she wishes to display.

A good example of decent list implementations is the Inbox on pretty much all mobile devices. Most Inbox list implementations display sender, title, date, size, and a preview of the message body. A good list is not only bound to textual information but also visual. Most Inbox implementation displays the information using bold fonts if the message is unread

In this article I would like to demonstrate how to implement customized list based UI's on the following platforms:

  1. Windows Phone 7
  2. Windows Mobile using .NET Compact Framework
  3. Android

Let's get started...


Windows Phone 7

This is definitely the easiest platform to target, in fact this is by far the easiest platform I've ever worked with. Development times on this platform are a lot shorter than any other platform I've worked with. I've been working with Windows CE based phones for the last 7 or so and I definitely think that this is the best Windows CE based OS ever. There unfortunately a few down sides like lack of a native code API and limited platform integration, but considering the performance and development ease, it is for most cases worth it. The best part with designing UI's for Windows Phone 7 is that I don't have to care about the design very much, I just ask my designer / graphic artist to shine up my XAML file and I can concentrate on the code.

A Visual Studio 2010 project template is actually provided by default for creating a list based UI makes things easier. This project template is called a Windows Phone Databound Applicaton, the description goes "A project for creating Windows Phone applications using List and Navigation controls". This project creates 2 pages, one for displaying the list, and the other for displaying details of this list.

The code examples for Windows Phone 7 uses the Model-View-ViewModel. This pattern is heavily used and I guess one can say an accepted standard in developing Windows Phone 7 applications. I'm not gonna go deep into the pattern in this article, so I assume that you do a bit of home work on MVVM.

To display a list Windows Phone 7 we use the ListBox control in XAML. This will represent the View.

<ListBox ItemsSource="{Binding Items}">
  <ListBox.ItemTemplate>
    <DataTemplate>
      <StackPanel>
        <TextBlock Text="{Binding LineOne}" TextWrapping="Wrap"/>
        <TextBlock Text="{Binding LineTwo}" TextWrapping="Wrap"/>
      </StackPanel>
    </DataTemplate>
  </ListBox.ItemTemplate>
</ListBox>


Our ViewModel is implemented in code. A ViewModel class should implement the INotifyPropertyChanged interface for the View to be able to respond to changes in the ViewModel.

public class ViewModelBase : INotifyPropertyChanged
{
    public event PropertyChangedEventHandler PropertyChanged;
 
    protected void NotifyPropertyChanged(String propertyName)
    {
        PropertyChangedEventHandler handler = PropertyChanged;
        if (null != handler)
        {
            handler(this, new PropertyChangedEventArgs(propertyName));
        }
    }
}
 
public class ItemViewModel : ViewModelBase
{
    private string _lineOne;
    public string LineOne
    {
        get { return _lineOne; }
        set
        {
            if (value != _lineOne)
            {
                _lineOne = value;
                NotifyPropertyChanged("LineOne");
            }
        }
    }
 
    private string _lineTwo;
    public string LineTwo
    {
        get { return _lineTwo; }
        set
        {
            if (value != _lineTwo)
            {
                _lineTwo = value;
                NotifyPropertyChanged("LineTwo");
            }
        }
    }
}
 
public class MainViewModel : ViewModelBase
{
    private MainModel model;
 
    public MainViewModel()
    {
        model = new MainModel();
        Items = model.GetData();
    }
 
    public ObservableCollection<ItemViewModel> Items { get; private set; }
}


The ViewModel code above contains an instance of the Model. The Model in this naive example just returns a populated collection of ItemViewModel.

public class MainModel
{
    public ObservableCollection<ItemViewModel> GetData()
    {
        return new ObservableCollection<ItemViewModel> 
        {
            new ItemViewModel() { LineOne = "runtime one", LineTwo = "Maecenas praesent accumsan bibendum" },
            new ItemViewModel() { LineOne = "runtime two", LineTwo = "Dictumst eleifend facilisi faucibus" },
            new ItemViewModel() { LineOne = "runtime three", LineTwo = "Habitant inceptos interdum lobortis" },
            new ItemViewModel() { LineOne = "runtime four", LineTwo = "Nascetur pharetra placerat pulvinar" }
        };
    }
}



Here's how the application looks like: 




Windows Mobile

This is actually a pretty decent platform and offers a huge selection of low level API's for platform integration. The OS also offers full multi tasking and the ability to create applications that run behind scenes. The down side of course to all that fun stuff is that you have to do a lot of things the hard way. Implementing a decent list based UI in this platforms can be done in 2 ways: Using the Windows CE custom drawing service; Creating an Owner Drawn List Control. Both require writing a few hundred lines of code.

For this example we create an Owner Drawn List. For those who are not familiar what that means, we draw the entire control from scratch, manually. We create a class that inherits from System.Windows.Forms.Control (the base class of all UI components) and override the drawing, resizing, and input methods. It's a bit tedious, but most of the code in owner drawn controls can be re-used as base classes for other owner drawn controls.

Let's start off with creating an owner drawn list base class.

abstract class OwnerDrawnListBase<T> : Control
{
    int selectedIndex;
    int visibleItemsPortrait;
    int visibleItemsLandscape;
    VScrollBar scrollBar;
 
    protected OwnerDrawnListBase()
        : this(7, 4)
    {
    }
 
    protected OwnerDrawnListBase(int visibleItemsPortrait, int visibleItemsLandscape)
    {
        this.visibleItemsPortrait = visibleItemsPortrait;
        this.visibleItemsLandscape = visibleItemsLandscape;
 
        Items = new List<T>();
 
        scrollBar = new VScrollBar { Parent = this, Visible = false, SmallChange = 1 };
        scrollBar.ValueChanged += (sender, e) => Invalidate();
    }
 
    public List<T> Items { get; private set; }
 
    public int SelectedIndex
    {
        get { return selectedIndex; }
        set
        {
            selectedIndex = value;
            if (SelectedIndexChanged != null)
                SelectedIndexChanged(this, EventArgs.Empty);
            Invalidate();
        }
    }
 
    public event EventHandler SelectedIndexChanged;
 
    protected virtual void OnSelectedIndexChanged(EventArgs e)
    {
        if (SelectedIndexChanged != null)
            SelectedIndexChanged(this, e);
    }
 
    public T SelectedItem
    {
        get
        {
            if (selectedIndex >= 0 && selectedIndex < Items.Count)
                return Items[selectedIndex];
            else
                return null;
        }
    }
 
    protected Bitmap OffScreen { get; private set; }
 
    protected int VisibleItems
    {
        get
        {
            if (Screen.PrimaryScreen.Bounds.Height > Screen.PrimaryScreen.Bounds.Width)
                return visibleItemsPortrait;
            else
                return visibleItemsLandscape;
        }
    }
 
    protected int ItemHeight
    {
        get { return Height / VisibleItems; }
    }
 
    protected int ScrollPosition
    {
        get { return scrollBar.Value; }
    }
 
    protected bool ScrollBarVisible
    {
        get { return scrollBar.Visible; }
    }
 
    protected int ScrollBarWidth
    {
        get { return scrollBar.Width; }
    }
 
    protected int DrawCount
    {
        get
        {
            if (ScrollPosition + scrollBar.LargeChange > scrollBar.Maximum)
                return scrollBar.Maximum - ScrollPosition + 1;
            else
                return scrollBar.LargeChange;
        }
    }
 
    #region Overrides
 
    protected override void OnResize(EventArgs e)
    {
        scrollBar.Bounds = new Rectangle(
            ClientSize.Width - scrollBar.Width,
            0,
            scrollBar.Width,
            ClientSize.Height);
 
        Dispose(OffScreen);
 
        if (Items.Count > VisibleItems)
        {
            scrollBar.Visible = true;
            scrollBar.LargeChange = VisibleItems;
            OffScreen = new Bitmap(ClientSize.Width - scrollBar.Width, ClientSize.Height);
        }
        else
        {
            scrollBar.Visible = false;
            scrollBar.LargeChange = Items.Count;
            OffScreen = new Bitmap(ClientSize.Width, ClientSize.Height);
        }
        DrawBorder();
 
        scrollBar.Maximum = Items.Count - 1;
    }
 
    private void DrawBorder()
    {
        using (var gfx = Graphics.FromImage(OffScreen))
        using (var pen = new Pen(SystemColors.ControlText))
            gfx.DrawRectangle(pen, new Rectangle(0, 0, OffScreen.Width - 1, OffScreen.Height - 1));
    }
 
    protected override void OnMouseDown(MouseEventArgs e)
    {
        // Update the selected index based on where the user clicks
        SelectedIndex = scrollBar.Value + (e.Y / ItemHeight);
        if (SelectedIndex > Items.Count - 1)
            SelectedIndex = -1;
 
        if (!Focused)
            Focus();
 
        base.OnMouseUp(e);
    }
 
    protected override void OnPaintBackground(PaintEventArgs e)
    {
        // To avoid flickering, do all drawing in OnPaint
    }
 
    protected override void Dispose(bool disposing)
    {
        if (disposing)
            Dispose(OffScreen);
        base.Dispose(disposing);
    }
 
    #endregion
 
    protected static void Dispose(IDisposable obj)
    {
        if (obj != null)
        {
            obj.Dispose();
            obj = null;
        }
    }
}


The class above implements the basic functionality of an owner drawn list. It hands resizing the off screen bitmap that serves as a double buffer, handles the scroll bar visibility, and handles updating the selected index. One can implement responding to keyboard input or gestures from here as well.

Next we create a class where we define how the control is drawn. This class inherits from our owner drawn list base class.

class CustomListViewItem
{
    public string LineOne { get; set; }
    public string LineTwo { get; set; }
}
 
class CustomListView : OwnerDrawnListBase<CustomListViewItem>
{
    const int topleft = 3;
 
    StringFormat noWrap;
    Pen pen;
    SolidBrush backgroundBrush;
    SolidBrush selectedBrush;
    SolidBrush selectedTextBrush;
    SolidBrush textBrush;
    Font headerFont;
 
    public override Font Font
    {
        get { return base.Font; }
        set
        {
            base.Font = value;
            Dispose(headerFont);
            headerFont = new Font(value.Name, value.Size, FontStyle.Bold);
        }
    }
 
    public CustomListView()
    {
        pen = new Pen(ForeColor);
        textBrush = new SolidBrush(ForeColor);
        backgroundBrush = new SolidBrush(BackColor);
        selectedTextBrush = new SolidBrush(SystemColors.HighlightText);
        selectedBrush = new SolidBrush(SystemColors.Highlight);
        noWrap = new StringFormat(StringFormatFlags.NoWrap);
        headerFont = new Font(base.Font.Name, base.Font.Size, FontStyle.Bold);
    }
 
    protected override void OnPaint(PaintEventArgs e)
    {
        using (var gfx = Graphics.FromImage(OffScreen))
        {
            gfx.FillRectangle(backgroundBrush, 1, 1, Width - 2, Height - 2);
 
            int top = 1;
            bool lastItem = false;
            bool itemSelected = false; ;
 
            for (var i = ScrollPosition; i < ScrollPosition + DrawCount; i++)
            {
                if (top > 1)
                    lastItem = Height - 1 < top;
 
                // Fill the rectangle if the item is selected
                itemSelected = i == SelectedIndex;
                if (itemSelected)
                {
                    if (!lastItem)
                    {
                        gfx.FillRectangle(
                            selectedBrush,
                            1,
                            (i == ScrollPosition) ? top : top + 1,
                            ClientSize.Width - (ScrollBarVisible ? ScrollBarWidth : 2),
                            (i == ScrollPosition) ? ItemHeight : ItemHeight - 1);
                    }
                    else
                    {
                        gfx.FillRectangle(
                            selectedBrush,
                            1,
                            top + 1,
                            ClientSize.Width - (ScrollBarVisible ? ScrollBarWidth : 1),
                            ItemHeight);
                    }
                }
 
                // Draw seperator lines after each item unless the item is the last item in the list
                if (!lastItem)
                {
                    gfx.DrawLine(
                        pen,
                        1,
                        top + ItemHeight,
                        ClientSize.Width - (ScrollBarVisible ? ScrollBarWidth : 2),
                        top + ItemHeight);
                }
 
                // Get the dimensions for creating the drawing areas
                var item = Items[i];
                var size = gfx.MeasureString(item.LineOne, Font);
                var rectheight = ItemHeight - (int)size.Height - 6;
                var rectwidth = ClientSize.Width - (ScrollBarVisible ? ScrollBarWidth : 5);
 
                // Draw line one with an offset of 3 pixels from the top of the rectangle 
                // using a bold font (no text wrapping)
                gfx.DrawString(
                    item.LineOne,
                    headerFont,
                    (i == SelectedIndex) ? selectedTextBrush : textBrush,
                    new RectangleF(topleft, top + 3, rectwidth, rectheight),
                    noWrap);
 
                // Draw line two with an offset of 3 pixels from the bottom of line one 
                // (no text wrapping)
                gfx.DrawString(
                    item.LineTwo,
                    Font,
                    (i == SelectedIndex) ? selectedTextBrush : textBrush,
                    new RectangleF(topleft, top + size.Height + 6, rectwidth, rectheight),
                    noWrap);
 
                // Set the top for the next item
                top += ItemHeight;
            }
 
            e.Graphics.DrawImage(OffScreen, 0, 0);
        }
    }
 
    protected override void Dispose(bool disposing)
    {
        if (disposing)
        {
            Dispose(headerFont);
            Dispose(backgroundBrush);
            Dispose(textBrush);
            Dispose(selectedTextBrush);
            Dispose(selectedBrush);
            Dispose(pen);
        }
 
        base.Dispose(disposing);
    }
}


Once that is in place you can just drag it in from the toolbox or dynamically add it to the Form in runtime.

var lv = new CustomListView();
lv.Dock = DockStyle.Fill;
Controls.Add(lv);
 
lv.Items.AddRange(new List<CustomListViewItem>
{
    new CustomListViewItem { LineOne = "runtime one", LineTwo = "Maecenas praesent accumsan bibendum" },
    new CustomListViewItem { LineOne = "runtime two", LineTwo = "Dictumst eleifend facilisi faucibus" },
    new CustomListViewItem { LineOne = "runtime three", LineTwo="Habitant inceptos interdum lobortis" },
    new CustomListViewItem { LineOne = "runtime four", LineTwo="Nascetur pharetra placerat pulvinar" },
    new CustomListViewItem { LineOne = "runtime five", LineTwo = "Maecenas praesent accumsan bibendum" },
    new CustomListViewItem { LineOne = "runtime six", LineTwo = "Dictumst eleifend facilisi faucibus" },
    new CustomListViewItem { LineOne = "runtime seven", LineTwo="Habitant inceptos interdum lobortis" },
    new CustomListViewItem { LineOne = "runtime eight", LineTwo="Nascetur pharetra placerat pulvinar" }
});


Here's how the custom list view looks like in a Windows Mobile 6.5.3 emulator


You can grab the source for Windows Mobile application above here.


Android

Creating decent list based UI's is also pretty easy. The designer experience is unfortunately not as elegant as what Windows Phone 7 has to offer, but shares the same idea. The user interface layout of Android applications are described in XML files and are parsed during runtime. In some occasions it seems easier to create the UI layout in runtime through code instead of struggling with the UI designer. This is probably because of my lack of patience with the tool or because of my lack of experience using it. Either way, I think it could have been done in a much smarter way.

To create a list based UI in Android we can create a class that extends from the ListActivity class. The ListActivity base class contains a List control set to fill the parent, it comes in handy if you want a UI with nothing but a list control. In android development, you usually setup the UI and do other initialization methods in the onCreate() method, our example will do the same. We set the data source of our list control by calling setListAdapter().

To have a more flexible and customizable we use the ArrayAdapter for presenting our data source to the screen. To optimize performance, we use an object called convertView that ArrayAdapter exposes, we can store a single instance of a class containing UI text components and just update the text for that instance. This is done by overriding the ArrayAdapter getView method.

Here's the code for implementing the ListActivity and ArrayAdapter

import java.util.ArrayList;
import java.util.List;

import android.app.Activity;
import android.app.ListActivity;
import android.os.Bundle;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import android.widget.ArrayAdapter;
import android.widget.TextView;

public class MainActivity extends ListActivity {
   
@Override
   
public void onCreate(Bundle savedInstanceState) {
       
super.onCreate(savedInstanceState);
        setListAdapter
(new ListItemActivity(this, R.layout.list_item, createList()));
   
}

   
private List<ListItem> createList() {
       
List<ListItem> list = new ArrayList<ListItem>();
        list.add
(new ListItem("runtime one", "Maecenas praesent accumsan bibendum"));
        list.add
(new ListItem("runtime two", "Dictumst eleifend facilisi faucibus"));
        list.add
(new ListItem("runtime three", "Habitant inceptos interdum lobortis"));
        list.add
(new ListItem("runtime four", "Nascetur pharetra placerat pulvinar"));
       
return list;
   
}

   
class ListItem {
       
public String lineOne;
       
public String lineTwo;

       
public ListItem(String lineOne, String lineTwo) {
           
this.lineOne = lineOne;
           
this.lineTwo = lineTwo;
       
}
    }

   
class ListItemActivity extends ArrayAdapter<ListItem> {
       
private Activity context;
       
private List<ListItem> items;

       
public ListItemActivity(Activity context, int textViewResourceId, List<ListItem> items) {
           
super(context, textViewResourceId, items);
           
this.context = context;
           
this.items = items;
       
}

       
@Override
       
public View getView(int position, View convertView, ViewGroup parent) {
           
ViewHolder holder;
           
if (convertView == null) {
               
LayoutInflater inflater = context.getLayoutInflater();
                convertView = inflater.inflate
(R.layout.list_item, parent, false);
                holder =
new ViewHolder();
                holder.lineOne =
(TextView) convertView.findViewById(R.id.lineOne);
                holder.lineTwo =
(TextView) convertView.findViewById(R.id.lineTwo);
                convertView.setTag
(holder);
           
} else {
               
holder = (ViewHolder) convertView.getTag();
           
}

           
ListItem item = items.get(position);
            holder.lineOne.setText
(item.lineOne);
            holder.lineTwo.setText
(item.lineTwo);
           
return convertView;
       
}
    }

   
static class ViewHolder {
       
TextView lineOne;
        TextView lineTwo;
   
}
}


Here's how the XML layout file is for the list item (list_item.xml)

<?xml version="1.0" encoding="utf-8"?>
 
<LinearLayout
  xmlns:android="http://schemas.android.com/apk/res/android"
  android:layout_width="fill_parent"
  android:layout_height="fill_parent"
  android:orientation="vertical">
 
  <TextView
    android:id="@+id/lineOne"
    android:layout_width="fill_parent"
    android:layout_height="wrap_content"
    android:paddingTop="12dp"
    android:paddingLeft="12dp"
    android:textSize="18sp"
    android:textStyle="bold"
  />
 
  <TextView
    android:id="@+id/lineTwo"
    android:layout_width="fill_parent"
    android:layout_height="wrap_content"
    android:paddingLeft="12dp"
    android:paddingBottom="12dp"
  />
 
</LinearLayout>
 


Here's how the applications looks like on an Android 2.3 emulator


You can grab the source for Android application above here.


So this basically all I have for now. I plan to go into detail by breaking down each part of the code samples I provided for all 3 platforms, or perhaps add another platform as well. I hope you found this useful.

Be the first to rate this post

  • Currently 0/5 Stars.
  • 1
  • 2
  • 3
  • 4
  • 5

Posted by christian.resma.helle on Wednesday, January 19, 2011 10:10 AM
Permalink | Comments (0) | Post RSSRSS comment feed

Widcomm Bluetooth Pairing Prompt

[Reposted from Christian Helle's Blog

Not so long ago I had a project that involved Bluetooth communication from a windows mobile device to some custom designed hardware. Everything went smoothly with only a few bumps during the scope of the project. When we had the applications on the field, certain users where running on devices that used the Widcomm Bluetooth Stack and that gave us a few headaches. For one, our system doesn't use any security features in bluetooth hence it doesn't not require pairing. Our security architecture is service based and checks for certain keys being passed back and forth to an online service. Very standard stuff. The problem we had with the devices running on a Widcomm Bluetooth stack was that the user was always prompted to pair even though the device did not require pairing. The application was not designed to handle user input aside from logging in/out and some diagnostic features. The application was designed to just run quietly in the background with the device tucked in the users pocket.

Since quite a few devices use the Widcomm Bluetooth Stack I needed a quick fix/hack to avoid the user having to pick up the device and click "Yes" on the security prompt to communicate with the device. The fix had to be as simple as possible and had to run without disrupting the user. My not so clean solution was to create a small application that does nothing but check if the Widcomm security pairing prompt app was running and if so send a keystroke event to simulate the user clicking on "Yes".

The Widcomm security prompt app main window uses the class name Broadcom_BTWizard with the window name Bluetooth. I check if this window exists and send the F1 keyboard event to simulate clicking on the left hardware button on the device

Here's a code snippet in C#

[DllImport("coredll.dll")]
static extern IntPtr FindWindow(string lpClassName, string lpWindowName);
 
[DllImport("coredll")]
static extern void keybd_event(byte bVk, byte bScan, int dwFlags, int dwExtraInfo);
 
const int KEYEVENTF_KEYUP = 0x02;
const int KEYEVENTF_KEYDOWN = 0x00;
static bool running = true;
 
static void CloseBroadcomWindowWorker()
{
    while (running)
    {
        var hwnd = FindWindow("Broadcom_BTWizard", "Bluetooth");
        if (hwnd != IntPtr.Zero)
        {
            keybd_event((byte)Keys.F1, 0, KEYEVENTF_KEYDOWN, 0);
            keybd_event((byte)Keys.F1, 0, KEYEVENTF_KEYUP, 0);
        }
 
        Thread.Sleep(5000);
    }
}


This is of course not the best solution but if you have a similar problem then this might save you some time if your only requirement is to get it to work as soon as possible. Otherwise, I would suggest avoiding the Widcomm Bluetooth Stack and just go for devices that use the Microsoft Bluetooth Stack

Be the first to rate this post

  • Currently 0/5 Stars.
  • 1
  • 2
  • 3
  • 4
  • 5

Posted by christian.resma.helle on Wednesday, October 20, 2010 7:22 AM
Permalink | Comments (0) | Post RSSRSS comment feed

How to retrieve a list of installed applications using .NETCF

In this short post I'd like to demonstrate how to retrieve a list of installed applications on a Windows Mobile device using OMA Client Provisioning. First you need to add a reference to the Microsoft.WindowsMobile.Configuration assembly. To retrieve the list we need to process a specific configuration (UnInstall Configuration Service Provider) using the ConfigurationManager.ProcessConfiguration method. The configuration is described in an XML and the response to this will also be described in XML

To query the device we process this configuration:

<wap-provisioningdoc>
  <characteristic-query type="UnInstall"/>
</wap-provisioningdoc>


The query above will only return installed application that can be uninstalled. The device would then respond with something like this:

<wap-provisioningdoc>
  <characteristic type="UnInstall">
    <characteristic type="Microsoft Application#2">
      <parm name="uninstall" value="0"/>
    </characteristic>
    <characteristic type="Microsoft Application#1">
      <parm name="uninstall" value="0"/>
    </characteristic>
    <characteristic type="Demo Home Screen">
      <parm name="uninstall" value="0"/>
    </characteristic>
  </characteristic>
</wap-provisioningdoc>


And here's how to accomplish this task using .NETCF and C#

var doc = new XmlDocument();
doc.LoadXml(@"<wap-provisioningdoc><characteristic-query type=""UnInstall""/></wap-provisioningdoc>");
doc = ConfigurationManager.ProcessConfiguration(doc, true);
 
var nodes = doc.SelectNodes("wap-provisioningdoc/characteristic[@type='UnInstall']/characteristic/@type");
foreach (var node in nodes.Cast<XmlNode>())
{
    Trace.WriteLine(node.InnerText);
}


I hope you found this useful.

Be the first to rate this post

  • Currently 0/5 Stars.
  • 1
  • 2
  • 3
  • 4
  • 5

Posted by christian.resma.helle on Wednesday, October 06, 2010 8:46 AM
Permalink | Comments (0) | Post RSSRSS comment feed

Enumerating Bluetooth Devices from .NETCF

I recently had a project where I needed to send data to Bluetooth devices. The client applications where to run in several platforms, currently only J2ME (Nokia phones) and Windows Mobile phones. Windows Mobile actually offers a pretty decent Bluetooth stack but not all devices use this. One of the devices I needed to use used the Widcomm stack. Luckily, there is an open source project called 32feet.NET which came in very handy for providing a layer over the 2 different stacks I use. The 32feet.NET library was also incredibly easy and fun to use.

In this article I'd like to demonstrate how to enumerate Bluetooth devices using .NETCF and the 32feet.NET library. The following code will work on both Microsoft and Widcomm Bluetooth stacks:

using System.Diagnostics;
using InTheHand.Net.Sockets;
 
namespace BluetoothSample
{
    static class Program
    {
        private static void Main()
        {
            BluetoothDeviceInfo[] devices;
            using (BluetoothClient sdp = new BluetoothClient())
                devices = sdp.DiscoverDevices();
 
            foreach (BluetoothDeviceInfo deviceInfo in devices)
            {
                Debug.WriteLine(string.Format("{0} ({1})",deviceInfo.DeviceName, deviceInfo.DeviceAddress));
            }
        }
    }
}

An interesting thing I had to consider for this project was the CPU architecture or endianness of the device I'm running on and the device I'm sending data to. I needed to reverse the byte order of the numeric data I sent and received.

Be the first to rate this post

  • Currently 0/5 Stars.
  • 1
  • 2
  • 3
  • 4
  • 5

Posted by christian.resma.helle on Thursday, July 29, 2010 2:25 AM
Permalink | Comments (0) | Post RSSRSS comment feed

Improve .NETCF Build Performance in Visual Studio

A lot of .NETCF developers are surprisingly not aware of the Platform Verification Task in Visual Studio. Disabling this in the build process will speed up the build of .NETCF projects. To make things quick and short, here's what you need to do:

1) Open the file C:\WINDOWS\Microsoft.NET\Framework\v3.5\Microsoft.CompactFramework.Common.targets for editing.

2) Change the following:

   99   <Target
  100     Name="PlatformVerificationTask">
  101     <PlatformVerificationTask
  102       PlatformFamilyName="$(PlatformFamilyName)"
  103       PlatformID="$(PlatformID)"
  104       SourceAssembly="@(IntermediateAssembly)"
  105       ReferencePath="@(ReferencePath)"
  106       TreatWarningsAsErrors="$(TreatWarningsAsErrors)"
  107       PlatformVersion="$(TargetFrameworkVersion)"/>
  108   </Target> 

to:

   99   <Target
  100     Name="PlatformVerificationTask">
  101     <PlatformVerificationTask
  102       Condition="'$(DoPlatformVerificationTask)'=='true'"
  103       PlatformFamilyName="$(PlatformFamilyName)"
  104       PlatformID="$(PlatformID)"
  105       SourceAssembly="@(IntermediateAssembly)"
  106       ReferencePath="@(ReferencePath)"
  107       TreatWarningsAsErrors="$(TreatWarningsAsErrors)"
  108       PlatformVersion="$(TargetFrameworkVersion)"/>
  109   </Target>
 
The following configuration above was an excert from an article called Platform Verification Task leading to slow builds on compact framework projects

Be the first to rate this post

  • Currently 0/5 Stars.
  • 1
  • 2
  • 3
  • 4
  • 5

Posted by christian.resma.helle on Wednesday, July 28, 2010 4:51 AM
Permalink | Comments (1) | Post RSSRSS comment feed

ListView Custom Drawing in .NETCF

In this article I would like to demonstrate how to do custom drawing in the ListView control that the .NET Compact Framework provides. I'll be extending the code I published previously in the article entitled ListView Extended Styles in .NETCF

This is normally a very tedious and frustrating task to do and to accomplish this task we'll have to take advantage of the custom drawing service Windows CE provides for certain controls. A very good reference for custom drawing is an MSDN article called Customizing a Control's Appearance using Custom Draw. Before going any further, I may have to warn you about the extensive interop code involved in this task.

We'll have to handle the ListView windows messages ourselves, and we accomplish this by subclassing this ListView. Subclassing a window means that we assign a new window procedure for messages that are meant for the ListView. This can be done through the SetWindowLong() method with the GWL_WNDPROC parameter. When subclassing, the developer is responsible for choosing which messages they want to handle, which to ignore, and which they let operating system handle. To have the operating system handle the message, a call to CallWindowProc() is done using a pointer to original window procedure.

Before setting the new window procedure its important to get a pointer to the original one in case the developer wishes to let the operating system handle the message. This is done through GetWindowLong()Let's get started...First we need to define the interop structures for custom drawing

 

    struct RECT
    {
        public int left;
        public int top;
        public int right;
        public int bottom;
    }
 
    struct NMHDR
    {
        public IntPtr hwndFrom;
        public IntPtr idFrom;
        public int code;
    }
 
    struct NMCUSTOMDRAW
    {
        public NMHDR nmcd;
        public int dwDrawStage;
        public IntPtr hdc;
        public RECT rc;
        public int dwItemSpec;
        public int uItemState;
        public IntPtr lItemlParam;
    }
 
    struct NMLVCUSTOMDRAW
    {
        public NMCUSTOMDRAW nmcd;
        public int clrText;
        public int clrTextBk;
        public int iSubItem;
        public int dwItemType;
        public int clrFace;
        public int iIconEffect;
        public int iIconPhase;
        public int iPartId;
        public int iStateId;
        public RECT rcText;
        public uint uAlign;
    }
 

Note: In C# (and VB and C++), the StructLayout is Sequencial by default, hence I didn't state itThe P/Invoke declarations we need are the following: 

 

    [DllImport("coredll.dll")]
    static extern IntPtr CallWindowProc(IntPtr lpPrevWndFunc, IntPtr hWnd, uint Msg, IntPtr wParam, IntPtr lParam);
 
    [DllImport("coredll")]
    static extern int SendMessage(IntPtr hWnd, int Msg, int wParam, ref RECT lParam);
 
    [DllImport("coredll.dll")]
    static extern uint SendMessage(IntPtr hwnd, uint msg, uint wparam, uint lparam);
 
    [DllImport("coredll.dll", SetLastError = true)]
    static extern int SetWindowLong(IntPtr hWnd, int nIndex, WndProcDelegate newProc);
 
    [DllImport("coredll.dll", SetLastError = true)]
    static extern IntPtr GetWindowLong(IntPtr hWnd, int nIndex);
 

And to make life a bit easier, I created some extension methods to the RECT struct we just defined.

 

     static class RectangleExtensions

    {
        public static Rectangle ToRectangle(this RECT rectangle)
        {
            return Rectangle.FromLTRB(rectangle.left, rectangle.top, rectangle.right, rectangle.bottom);
        }
 
        public static RectangleF ToRectangleF(this RECT rectangle)
        {
            return new RectangleF(rectangle.left, rectangle.top, rectangle.right, rectangle.bottom);
        }
    }
 
 

We'll need the following constants defined in the Platform SDK 

 

    const int GWL_WNDPROC = -4;
    const int WM_NOTIFY = 0x4E;
    const int NM_CUSTOMDRAW = (-12);
    const int CDRF_NOTIFYITEMDRAW = 0x00000020;
    const int CDRF_NOTIFYSUBITEMDRAW = CDRF_NOTIFYITEMDRAW;
    const int CDRF_NOTIFYPOSTPAINT = 0x00000010;
    const int CDRF_SKIPDEFAULT = 0x00000004;
    const int CDRF_DODEFAULT = 0x00000000;
    const int CDDS_PREPAINT = 0x00000001;
    const int CDDS_POSTPAINT = 0x00000002;
    const int CDDS_ITEM = 0x00010000;
    const int CDDS_ITEMPREPAINT = (CDDS_ITEM | CDDS_PREPAINT);
    const int CDDS_SUBITEM = 0x00020000;
    const int CDIS_SELECTED = 0x0001;
    const int LVM_GETSUBITEMRECT = (0x1000 + 56);
 
 

Custom drawing in the ListView will only work in the Details view mode. To ensure this, I set the View to View.Details in the constructor method. Since I'm extending my old ListViewEx (Enables ListView Extended Styles) I'm gonna enable Double buffering, Grid lines, and the Gradient background. I'm gonna enable subclassing on the ListView only when the parent is changed, this is because I need to receive messages sent to the parent control of the ListView. We also need a delegate for the new window procedure and a pointer to the original window procedure. And last but not the least we need the actual window procedure method. 

 

    delegate IntPtr WndProcDelegate(IntPtr hWnd, uint msg, IntPtr wParam, IntPtr lParam);
    IntPtr lpPrevWndFunc;
 
    public ListViewCustomDraw()
    {
        View = View.Details;
        DoubleBuffering = true;
        GridLines = true;
        Gradient = true;
 
        ParentChanged += delegate
        {
            lpPrevWndFunc = GetWindowLong(Parent.Handle, GWL_WNDPROC);
            SetWindowLong(Parent.Handle, GWL_WNDPROC, WndProc);
        };
    }
 
    private IntPtr WndProc(IntPtr hWnd, uint msg, IntPtr wParam, IntPtr lParam)
    {
        if (msg == WM_NOTIFY)
        {
            var nmhdr = (NMHDR)Marshal.PtrToStructure(lParam, typeof(NMHDR));
            if (nmhdr.hwndFrom == Handle && nmhdr.code == NM_CUSTOMDRAW)
                return CustomDraw(hWnd, msg, wParam, lParam);
 
        }
 
        return CallWindowProc(lpPrevWndFunc, hWnd, msg, wParam, lParam);
    }
 
 

In the new window procedure, we are only really interested in the WM_NOTIFY message, because this is what the NM_CUSTOMDRAW message is sent through. The LPARAM parameter of the message will contain the NMHDR which then contains the NM_CUSTOMDRAW message. The LPARAM also contains the NMLVCUSTOMDRAW which provide state and information about the ListView.

The trickiest part in performing custom drawing in the ListView is handling the drawing stage. We create a method called CustomDraw to handle the different drawing stages of the ListView 

 

    private IntPtr CustomDraw(IntPtr hWnd, uint msg, IntPtr wParam, IntPtr lParam)
    {
        int result;
        var nmlvcd = (NMLVCUSTOMDRAW)Marshal.PtrToStructure(lParam, typeof(NMLVCUSTOMDRAW));
        switch (nmlvcd.nmcd.dwDrawStage)
        {
            case CDDS_PREPAINT:
                result = CDRF_NOTIFYITEMDRAW;
                break;
 
            case CDDS_ITEMPREPAINT:
                var itemBounds = nmlvcd.nmcd.rc.ToRectangle();
                if ((nmlvcd.nmcd.uItemState & CDIS_SELECTED) != 0)
                {
                    using (var brush = new SolidBrush(SystemColors.Highlight))
                    using (var graphics = Graphics.FromHdc(nmlvcd.nmcd.hdc))
                        graphics.FillRectangle(brush, itemBounds);
                }
 
                result = CDRF_NOTIFYSUBITEMDRAW;
                break;
 
            case CDDS_SUBITEM | CDDS_ITEMPREPAINT:
                var index = nmlvcd.nmcd.dwItemSpec;
                var rect = new RECT();
                rect.top = nmlvcd.iSubItem;
                SendMessage(Handle, LVM_GETSUBITEMRECT, index, ref rect);
                rect.left += 2;
 
                Color textColor;
                if ((nmlvcd.nmcd.uItemState & CDIS_SELECTED) != 0)
                    textColor = SystemColors.HighlightText;
                else
                    textColor = SystemColors.ControlText;
 
                using (var brush = new SolidBrush(textColor))
                using (var graphics = Graphics.FromHdc(nmlvcd.nmcd.hdc))
                    graphics.DrawString(Items[index].SubItems[nmlvcd.iSubItem].Text,
                                        Font,
                                        brush,
                                        rect.ToRectangleF());
 
                result = CDRF_SKIPDEFAULT | CDRF_NOTIFYSUBITEMDRAW;
                break;
 
            default:
                result = CDRF_DODEFAULT;
                break;
        }
 
        return (IntPtr)result;
    }
 
 

In the first stage we handle is the CDDS_PREPAINT. Here we return CDRF_NOTIFYITEMDRAW to tell that we want to handle drawing of the row ourselves. After this we receive the CDDS_ITEMPREPAINT where we can draw the entire row.

We check if the row is selected through the uItemState field of NMCUSTOMDRAW, if this field has the CDIS_SELECTED flag then it means the item is selected, hence we draw a fill rectangle. After handling the CDDS_ITEMPREPAINT, we return CDRF_NOTIFYSUBITEMDRAW to tell that we want to draw the sub items ourselves.

For drawing the sub items we need to handle CDDS_SUBITEM | CDDS_ITEMPREPAINT. We can get the position index of the item through the dwItemSpec field of NMCUSTOMDRAW. To get the bounds of the current sub item we send the LVM_GETSUBITEMRECT message to the ListView and pass a pointer to RECT as the LPARAM. Before sending this message, set the "top" field of the RECT to the index of the sub item (retrieved from iSubItem field of NMLVCUSTOMDRAW. After drawing the sub item we return CDRF_SKIPDEFAULT | CDRF_NOTIFYSUBITEMDRAW to tell that we only care about handling the next sub item.

Well I hope you guys find this interesting and helpful. To keep things simple, I only demonstrated displaying plan text and a plain rectangle for the selection.

If you're interested in the full source code then you can grab it here.

Be the first to rate this post

  • Currently 0/5 Stars.
  • 1
  • 2
  • 3
  • 4
  • 5

Posted by christian.resma.helle on Tuesday, July 27, 2010 2:54 AM
Permalink | Comments (1) | Post RSSRSS comment feed

ListView Background Image in .NETCF

In this short entry I'd like to demonstrate how to display a background image in the ListView control. For this we will send the LVM_SETBKIMAGE or the LVM_GETBKIMAGE message to the ListView control with the LVBKIMAGE struct as the LPARAM. Unfortunately, the Windows CE version of LVBKIMAGE does not support LVBKIF_SOURCE_URL flag which allows using an image file on the file system for the background image of the ListView.

The layout of the background image can be either tiled or specified by an offset percentage. The background image is not affected by custom drawing, unless of course you decide to fill each sub item rectangle. For setting the background image we use the LVBKIF_SOURCE_HBITMAP flag together with the layout which is either LVBKIF_STYLE_TILE or LVBKIF_STYLE_NORMAL. If we set the layout to LVBKIF_STYLE_NORMAL, then we have the option of setting where the image will be drawn by setting the value of xOffsetPercentage and yOffsetPercentage.

In this example I'd like to make use of extension methods to add the SetBackgroundImage() and GetBackgroundImage() methods to ListView. This can of course be easily used to in a property to an inherited ListView. 

public static class ListViewExtensions
{
    [DllImport("coredll")]
    static extern int SendMessage(IntPtr hWnd, int Msg, int wParam, ref LVBKIMAGE lParam);
 
    const int LVM_FIRST = 0x1000;
    const int LVM_SETBKIMAGE = (LVM_FIRST + 138);
    const int LVM_GETBKIMAGE = (LVM_FIRST + 139);
    const int LVBKIF_SOURCE_NONE = 0x00000000;
    const int LVBKIF_SOURCE_HBITMAP = 0x00000001;
    const int LVBKIF_STYLE_TILE = 0x00000010;
    const int LVBKIF_STYLE_NORMAL = 0x00000000;
 
    struct LVBKIMAGE
    {
        public int ulFlags;
        public IntPtr hbm;
        public IntPtr pszImage; // not supported
        public int cchImageMax;
        public int xOffsetPercent;
        public int yOffsetPercent;
    }
 
    public static void SetBackgroundImage(this ListView listView, Bitmap bitmap)
    {
        SetBackgroundImage(listView, bitmap, false);
    }
 
    public static void SetBackgroundImage(this ListView listView, Bitmap bitmap, bool tileLayout)
    {
        SetBackgroundImage(listView, bitmap, tileLayout, 0, 0);
    }
 
    public static void SetBackgroundImage(
        this ListView listView,
        Bitmap bitmap,
        bool tileLayout,
        int xOffsetPercent,
        int yOffsetPercent)
    {
        LVBKIMAGE lvBkImage = new LVBKIMAGE();
        if (bitmap == null)
            lvBkImage.ulFlags = LVBKIF_SOURCE_NONE;
        else
        {
            lvBkImage.ulFlags = LVBKIF_SOURCE_HBITMAP | (tileLayout ? LVBKIF_STYLE_TILE : LVBKIF_STYLE_NORMAL);
            lvBkImage.hbm = bitmap.GetHbitmap();
            lvBkImage.xOffsetPercent = xOffsetPercent;
            lvBkImage.yOffsetPercent = yOffsetPercent;
        }
 
        SendMessage(listView.Handle, LVM_SETBKIMAGE, 0, ref lvBkImage);
    }
 
    public static Bitmap GetBackgroundImage(this ListView listView)
    {
        LVBKIMAGE lvBkImage = new LVBKIMAGE();
        lvBkImage.ulFlags = LVBKIF_SOURCE_HBITMAP;
 
        SendMessage(listView.Handle, LVM_GETBKIMAGE, 0, ref lvBkImage);
 
        if (lvBkImage.hbm == IntPtr.Zero)
            return null;
        else
            return Bitmap.FromHbitmap(lvBkImage.hbm);
    }
}
 
Here's an example of exposing the background image as a property in an inherited ListView by using the extension methods above.
 
class ListViewEx : ListView
{
    public Bitmap BackgroundImage
    {
        get { return this.GetBackgroundImage(); }
        set { this.SetBackgroundImage(value, BackgroundLayout == BackgroundImageLayout.Tile); }
    }
 
    public BackgroundImageLayout BackgroundLayout { get; set; }
 
    public enum BackgroundImageLayout
    {
        Tile,
        Center
    }
}
 
A small catch with the ListView background image is that it is only supported in Windows CE 5.0 and later. Hope you found this information useful.

Be the first to rate this post

  • Currently 0/5 Stars.
  • 1
  • 2
  • 3
  • 4
  • 5

Posted by christian.resma.helle on Tuesday, July 27, 2010 2:47 AM
Permalink | Comments (1) | Post RSSRSS comment feed

Unit Testing for Smart Devices Webcast

On the 28th of February 2008, Microsoft Denmark will have the first and largest online launch for Windows Server 2008, SQL Server 2008, and Visual Studio 2008. We made a few webcasts related to the products and technologies to be released. Here's one that I made entitled "Unit Testing for Smart Devices"

http://blogs.commentor.dk/downloads/smart_device_unit_testing.wmv

Be the first to rate this post

  • Currently 0/5 Stars.
  • 1
  • 2
  • 3
  • 4
  • 5

Posted by christian.resma.helle on Thursday, February 21, 2008 2:44 AM
Permalink | Comments (2) | Post RSSRSS comment feed