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

Motorola Dual Bluetooth Stack Support

[Reposted from Christian Helle's Blog

Apparently most Motorola devices support 2 Bluetooth Stacks: Microsoft and StoneStreet. To switch which stack to use you would have to make some changes in the registry and restart the device.

[HKEY_LOCAL_MACHINE\SOFTWARE\SymbolBluetooth]

"SSStack"=DWORD:1

0 = Microsoft Stack 
1 = StoneStreet One Stack

The StoneStreet One Stack is supported in all devices except the ES400 and MC65. For the Microsoft stack, any device running Windows Mobile 6.1, Windows Mobile 6.5.x and Windows CE 6.0

Motorola recommendeds using the Microsoft stack for new development and I strongly agree with Motorola on this!

Currently rated 1.0 by 1 people

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

Categories: Windows Mobile
Posted by christian.resma.helle on Thursday, October 21, 2010 5:58 PM
Permalink | Comments (0) | Post RSSRSS comment feed

Copying files from the Desktop to the Device using .NET

I've been frequently asked on how to copy files from the desktop to a Windows Mobile based device. The answer is the Remote API. here's how to do it:

 

First, we'll need our P/Invokes to rapi.dll 

 

[DllImport("rapi.dll")]
static extern int CeRapiInit();
 
[DllImport("rapi.dll")]
static extern int CeRapiUninit();
 
[DllImport("rapi.dll", CharSet = CharSet.Unicode)]
static extern IntPtr CeCreateFile(
  string lpFileName,
  uint dwDesiredAccess,
  int dwShareMode,
  int lpSecurityAttributes,
  int dwCreationDisposition,
  int dwFlagsAndAttributes,
  int hTemplateFile);
 
[DllImport("rapi.dll", CharSet = CharSet.Unicode)]
internal static extern int CeWriteFile(
    IntPtr hFile,
    byte[] lpBuffer,
    int nNumberOfbytesToWrite,
    ref int lpNumberOfbytesWritten,
    int lpOverlapped);
 
[DllImport("rapi.dll", CharSet = CharSet.Unicode)]
internal static extern int CeSetFileTime(
    IntPtr hFile,
    ref long lpCreationTime,
    ref long lpLastAccessTime,
    ref long lpLastWriteTime);
 
const int BUFFER_SIZE = 1024 * 5; // 5k transfer buffer
const int CREATE_ALWAYS = 2;
const int ERROR_SUCCESS = 0;
const int FILE_ATTRIBUTE_NORMAL = 0x80;
const uint GENERIC_WRITE = 0x40000000;
const int INVALID_HANDLE_VALUE = -1;

 

Now let's wrap all those in a method called CopyToDevice(string localFile, string remoteFile). The localFile is the file located on the desktop and the remoteFile is the destination filename on the device.

 

void CopyToDevice(string localFile, string remoteFile)
{
    var rapi = CeRapiInit() == ERROR_SUCCESS;
    if (!rapi)
        return;
 
    try
    {
        var filePtr = CeCreateFile(remoteFile, GENERIC_WRITE, 0, 0, CREATE_ALWAYS, FILE_ATTRIBUTE_NORMAL, 0);
        if (filePtr == new IntPtr(INVALID_HANDLE_VALUE))
            return;
 
        using (var localFileStream = new FileStream(localFile, FileMode.Open, FileAccess.Read))
        {
            var byteswritten = 0;
            var position = 0;
            var buffer = new byte[BUFFER_SIZE];
 
            var bytesread = localFileStream.Read(buffer, position, BUFFER_SIZE);
            while (bytesread > 0)
            {
                position += bytesread;
                if (CeWriteFile(filePtr, buffer, bytesread, ref byteswritten, 0) == ERROR_SUCCESS)
                    return;
 
                try
                {
                    bytesread = localFileStream.Read(buffer, 0, BUFFER_SIZE);
                }
                catch
                {
                    bytesread = 0;
                }
            }
        }
    }
    finally
    {
        CeRapiUninit();
    }
}

 

To use the code above you will have to know the full path of the file on the desktop. I hope you found this useful.

Be the first to rate this post

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

Categories: Windows Mobile | RAPI
Posted by christian.resma.helle on Monday, August 23, 2010 9:12 AM
Permalink | Comments (0) | 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

Transparent Controls in .NETCF

I work a lot with a graphic artist for developing solutions. The better the graphic artist you work with is, the harder it is to implement their designs to your application. One thing that my solutions have in common is that they all require transparent controls. My graphic artist loves having a image buttons on top of fancy background.

Here's some screen shots of what I've made with my graphic artist:

 

 

 

 

 

 

 

 



 

 

 

 

 

 

 

 

 

In these screen shots I heavily use a transparent label control over a Form that has a background image. I normally set my controls to be designer visible so I can drag and drop while designing. Visual Studio 2005 and 2008 will automatically load all Custom Controls and UserControls

Implementing Transparent Controls

For creating transparent controls, I need the following:

1) IControlBackground interface - contains BackgroundImage { get; }
2) TransparentControlBase - draws the BackgroundImage of an IControlBackground form
3) Transparent Control - Inherits from TransparentControlBase
4) FormBase Form - implements IControlBackground and draws the background image to the form

Let's start off with the IControlBackground interface. Like I mentioned above, it only contains a property called BackgroundImage.

public interface IControlBackground
{
  Image BackgroundImage { get; }
}

Next we will need to create the TransparentControlBase. Let's create a class that inherits from Control. We then need to override the OnPaintBackground() event to draw the IControlBackground.BackgroundImage of the Parent control. To do this, we create an instance of IControlBackground from the Parent. Once we have the BackgroundImage, we draw part of the BackgroundImage where the transparent control is lying on.

We also override the OnTextChanged() and OnParentChanged() events to force a re-draw whenever the text or parent of the control is changed.

public class TransparentControlBase : Control
{
  protected bool HasBackground = false;

  protected override void OnPaintBackground(PaintEventArgs e)
  {
    IControlBackground form = Parent as IControlBackground;
    if (form == null) {
      base.OnPaintBackground(e);
      return;
    } else {
      HasBackground = true;
    }

    e.Graphics.DrawImage(
      form.BackgroundImage,
      0,
      0,
      Bounds,
      GraphicsUnit.Pixel);
  }

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

  protected override void OnParentChanged(EventArgs e)
  {
    base.OnParentChanged(e);
    Invalidate();
  }
}

Now we need to create a control that inherits from TransparentControlBase. I'll create a simple TransparentLabel control for this example. The control will have the same behavior as the standard Label control, except that it can be transparent when used over a form or control that implements IControlBackground.

public class TransparentLabel : TransparentControlBase
{
  ContentAlignment alignment = ContentAlignment.TopLeft;
  StringFormat format = null;
  Bitmap off_screen = null;

  public TransparentLabel()
  {
    format = new StringFormat();
  }

  public ContentAlignment TextAlign
  {
    get { return alignment; }
    set
    {
      alignment = value;
      switch (alignment) {
        case ContentAlignment.TopCenter:
          format.Alignment = StringAlignment.Center;
          format.LineAlignment = StringAlignment.Center;
          break;
        case ContentAlignment.TopLeft:
          format.Alignment = StringAlignment.Near;
          format.LineAlignment = StringAlignment.Near;
          break;
        case ContentAlignment.TopRight:
          format.Alignment = StringAlignment.Far;
          format.LineAlignment = StringAlignment.Far;
          break;
      }
    }
  }

  protected override void OnPaint(PaintEventArgs e)
  {
    if (!base.HasBackground) {
      if (off_screen == null) {
        off_screen = new Bitmap(ClientSize.Width, ClientSize.Height);
      }
      using (Graphics g = Graphics.FromImage(off_screen)) {
        using (SolidBrush brush = new SolidBrush(Parent.BackColor)) {
          g.Clear(BackColor);
          g.FillRectangle(brush, ClientRectangle);
        }
      }
    } else {
      using (SolidBrush brush = new SolidBrush(ForeColor)) {
        e.Graphics.DrawString(
          Text,
          Font,
          brush,
          new Rectangle(0, 0, Width, Height),
          format);
      }
    }
  }
}

Now that we have our transparent controls, we need to create a Form that will contain these controls. First we need to create a base class that will implement IControlBackground and inherit from Form.

In this example, I added a background image to the solution and as an embedded resource. My default namespace is called TransparentSample and my background image is located at the root folder with the filename background.jpg

public class FormBase : Form, IControlBackground
{
  Bitmap background;

  public FormBase()
  {
    background = new Bitmap(
      Assembly.GetExecutingAssembly().GetManifestResourceStream(
      "TransparentSample.background.jpg"));
  }

  protected override void OnPaint(PaintEventArgs e)
  {
    e.Graphics.DrawImage(background, 0, 0);
  }

  public Image BackgroundImage
  {
    get { return background; }
  }
}

For the last step, we need to create a Form that will contain these transparent controls. To start, let's add a new Form to our project and let it inherit from FormBase instead of Form.

Now we can add our transparent controls to the main form.

public class MainForm : FormBase
{
  TransparentLabel label;

  public MainForm()
  {
    label = new TransparentLabel();
    label.Font = new Font("Arial", 16f, FontStyle.Bold);
    label.ForeColor = Color.White;
    label.Text = "Transparent Label";
    label.Bounds = new Rectangle(20, 60, 200, 50);
    Controls.Add(label);
  }
}

That wasn't very complicated, was it? Having a nice and intuitive UI offers a very good user experience. Being creative, imaginative, and learning to work with a graphic artist can really pay off.

Currently rated 4.4 by 7 people

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

Posted by christian.resma.helle on Sunday, January 13, 2008 9:30 AM
Permalink | Comments (14) | Post RSSRSS comment feed

Generic Multiple CAB File Installer for the Desktop

In this article I would like to share a nice and simple multiple CAB file installer application for the desktop that I have been using for a few years now. The tool can install up to 10 CAB files and is written in C and has less than a 100 lines of code.

Before we get started with installing multiple CAB files with a generic installer, let's try to go through the process of installing a CAB file from the desktop.

Installing a CAB file from the desktop to the mobile device is pretty simple. All you need to do is create a .INI file that defines the CAB files you wish to install and launch the Application Manager (CeAppMgr.exe) passing the .INI file (full path) as the arguments. Application Manager is included when installing ActiveSync or the Windows Mobile Device Center (Vista)

An Application Manager .INI file contains information that registers an application with the Application Manager. The .INI file uses the following format:

[CEAppManager]
Version = 1.0
Component = component_name

[component_name]
Description = descriptive_name
CabFiles = cab_filename [,cab_filename]

Here's an MSDN link for more details on Creating an .ini File for the Application Manager

Before launching the Application Manager, we should first make sure that it's installed. Once we know that then we get the location of the file. The easiest way to do this programmatically is to look into the registry key SOFTWARE\Microsoft\Windows\CurrentVersion\App Paths\CEAppMgr.exe. If this key doesn't exist, then the Application Manager is not installed.

The next step is quite interesting yet very simple. In this installer application, I use a configuration file called Setup.ini which defines which CAB files I want to install. Setup.ini will look like this:

[CabFiles]
CABFILE1 = MyApp1.ini
CABFILE2 = SQLCE.ini
CABFILE3 = NETCF.ini
CABFILE4 =
CABFILE5 =
CABFILE6 =
CABFILE7 =
CABFILE8 =
CABFILE9 =
CABFILE10 =

You can easily modifiy the code to install more than 10 CAB files if you think it's appropriate. I used GetPrivateProfileString() to read the values from my configuration file.

Setup.ini, the .INI files, and the actual CAB files are required to be in the same directory as the generic installer.

Ok, now we have Application Manager command-line arguments ready, we now just have to launch it. I used CreateProcess() to launch the application manager and used WaitForSingleObject() to wait for the process to end.

Here's the full source code:

[C CODE]

#include "windows.h"
#include "tchar.h"

#define CE_APP_MGR TEXT("SOFTWARE\\Microsoft\\Windows\\CurrentVersion\\App Paths\\CEAppMgr.exe")

LPTSTR GetParameters()
{
  TCHAR szParams[2048];
  szParams[0] = TCHAR(0);

  TCHAR szCurrDir[MAX_PATH];
  GetCurrentDirectory(MAX_PATH, szCurrDir);

  for (int i = 1; i < 11; i++) {
    TCHAR buffer[16];
    TCHAR szKey[16];

    strcpy(szKey, TEXT("CABFILE"));
    itoa(i, buffer, 16);
    strcat(szKey, buffer);

    TCHAR szSetupIni[MAX_PATH];
    strcpy(szSetupIni, szCurrDir);
    strcat(szSetupIni, TEXT("\\"));
    strcat(szSetupIni, TEXT("Setup.ini"));

    TCHAR szCabFile[MAX_PATH];
    ::GetPrivateProfileString(TEXT("CabFiles"), szKey,
      (TCHAR*)"", szCabFile, sizeof(szCabFile), szSetupIni);

    if (0 != strcmp(szCabFile, (TCHAR*)"")) {
      strcat(szParams, TEXT(" \""));
      strcat(szParams, szCurrDir);
      strcat(szParams, TEXT("\\"));
      strcat(szParams, szCabFile);
      strcat(szParams, TEXT(" \""));
    }
  }

  return szParams;
}

int APIENTRY WinMain(HINSTANCE hInstance, HINSTANCE hPrevInstance, LPSTR lpCmdLine, int nCmdShow)
{
  HKEY hkey;
  DWORD dwDataSize = MAX_PATH;
  DWORD dwType = REG_SZ;
  TCHAR szCEAppMgrPath[MAX_PATH];

  if(ERROR_SUCCESS == RegOpenKeyEx(HKEY_LOCAL_MACHINE, CE_APP_MGR, 0, KEY_READ, &hkey)) {
    if(ERROR_SUCCESS != RegQueryValueEx(hkey, NULL, NULL,
      &dwType, (PBYTE)szCEAppMgrPath, &dwDataSize))
    {
      MessageBox(NULL, TEXT("Unable to find Application Manager for Pocket PC Applications"),
        TEXT("Error"), MB_ICONEXCLAMATION | MB_OK);
      return 1;
    }
  } else {
    MessageBox(NULL, TEXT("Unable to find Application Manager for Pocket PC Applications"),
      TEXT("Error"), MB_ICONEXCLAMATION | MB_OK);
    return 1;
  }


  RegCloseKey(hkey);

  STARTUPINFO startup_info = {0};
  PROCESS_INFORMATION pi = {0};
  startup_info.cb = sizeof(startup_info);

  if (CreateProcess(szCEAppMgrPath, GetParameters(), NULL,
    NULL, FALSE, 0, NULL, NULL, &startup_info, &pi))
  {
    WaitForSingleObject(pi.hProcess, INFINITE);
  } else {
    MessageBox(NULL, TEXT("Unable to Launch Application Manager for Pocket PC Applications"),
      TEXT("Error"), MB_ICONEXCLAMATION | MB_OK);
    return 2;
  }

  return 0;
}


When deplying my applications in this manner, I like packaging them in a self-extracting zip file that is configured for software installation. I'm still holding to an old version of Winzip Self-Extractor to accomplish this task.

Another reason I use WaitForSingleObject is because a self-extracting zip installer file deletes the all the temporary files it has extracted once the installer process ends. This means that your .INI and CAB files will be deleted even before they get copied to the device.

Be the first to rate this post

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

Categories: Windows Mobile
Posted by christian.resma.helle on Monday, July 16, 2007 9:26 AM
Permalink | Comments (0) | Post RSSRSS comment feed