Code Repo    |     RSS
MD's Technical Sharing



Friday, December 6, 2013

Showing an input box on Windows Phone 7

In a Windows Phone 7 application, how can you quickly show a dialog asking the user to enter an input string without creating a new XAML page? The easiest method would be to call Guide.BeginShowKeyboardInput from the Microsoft.Xna.Framework.GamerServices namespace, which offers a similar user experience to the .NET framework's InputBox method, available in the .NET framework as part of the Microsoft.VisualBasic namespace.

First you will need to add references to Microsoft.Xna.Framework and Microsoft.Xna.Framework.GamerServices to your project. After that, use the following code:

Guide.BeginShowKeyboardInput(Microsoft.Xna.Framework.PlayerIndex.One, "Title goes here", "Description goes here", "default text", new AsyncCallback(Value_Entered), null);

The prompt will be shown:


Handle the response by:

void Value_Entered(IAsyncResult res)
{
     string value = Guide.EndShowKeyboardInput(res);
}

Notice that the returned value will be an empty string when the CANCEL button is clicked or when the user does not enter any value and simply clicks the OK button. Another restriction is that the dialog will automatically be closed if the application enters the background (e.g. when the Windows key is pressed) and will not be redisplayed when the application is subsequently activated. This makes the use of BeginShowKeyboardInput unsuitable for scenarios when the user needs to leave the application temporarily before the code can be entered, for example when the dialog asks for a verification code sent to the user's phone number as part of the mobile number verification process and the user needs to leave the application to open the Messaging application to check for new messages.

In those scenarios, a more suitable approach is to use the InputPrompt class available is the coding4fun controls toolkit for Windows Phone. To install this, from Visual Studio Project menu, select Manage NuGet Packages and install the Coding4fun Toolkit - Controls package:


After the installation, restart Visual Studio and you will be able to use the InputPrompt class. If you encounter the error "You are trying to install this package into a project that targets ... but the package does not contain any assembly references that are compatible with that framework.", please reinstall the latest version of NuGet using the Tools>Extensions and Updates menu in Visual Studio.

You can show the input message by using the following code:

InputPrompt input = new InputPrompt();
input.InputScope = new InputScope { Names = { new InputScopeName() { NameValue = InputScopeNameValue.Number} } };
input.Completed += input_Completed;
input.Title = "Basic Input";
input.Message = "I'm a basic input prompt"; 
input.Value = "";  
input.MessageTextWrapping = TextWrapping.Wrap;
input.IsCancelVisible = true;
input.Show();

The dialog will be shown:


Handle the response by:

void input_Completed(object sender, PopUpEventArgs<string, PopUpResult> e)
{
    string value = e.Result;
} 

With this approach, the dialog will still remain open when the user leaves the application and comes back, which is better than using the BeginShowKeyboardInput method. Take note that this will only work if the user returns to the application by holding the BACK key and selecting the application icon. If the user clicks on the application icon in the Start menu, due to a restriction in the Windows Phone 7 SDK, the application will be restarted from the beginning and the input prompt will no longer be shown. On Windows Phone 8, the user can also return to the input prompt using the Start menu icon if the following modification is added to the task entry in the WMAppManifest.xml file to enable fast app resume: 

<DefaultTask Name="_default" NavigationPage="MainPage.xaml" ActivationPolicy="Resume"/>

For more information on the customizations that can be done on the InputPrompt class, refer to this.
Read More »

Saturday, July 24, 2010

An RTF Editor Control for .NET Compact Framework

As you may have known, there is no RichTextBox control in .NET Compact Framework. This creates a lot of headaches for developers who want to display RTF-formatted text in their program. The only way is to use the RichInk control. However, there is no good free managed wrapper for it, even the OpenNetCF's InkX wrapper is still untidy and requires a lot of P/Invoke calls to make it work properly. IntelliProg used to provide a working control but the company has seemingly disappeared (their domain is parked). Until 2011, DSRTech also sold an expensive commercial solution but the page for this control has since been removed from their website.

AgileNotesTouch with RichInk control

After some research, I found a freeware called AgileNotes Touch which allows user to write notes and save it into TXT, PWI or RTF. Since the source code is not available, I decided to go ahead decompiling the executable using Reflector. This was an easy task - the decompiled source code, with only some minor modifications, compiled and ran as if it were the original. It turned out AgilesNotes Touch also uses the RickInk control, with some nice managed .NET wrappers which can be re-used easily.

Creating RTF programmatically: NRtfTree

Since I need to create the RTF document programmatically, I decided to use the open source project NRtfTree. Some basic formatting worked well until I tried to create an RTF document with images such as this. The document was created properly but to my disappointment the image was not displayed when the document was open with my program and with Pocket Word. On the other hand, Wordpad, and Office Word, displayed the image just fine. It took me a long time before I figured out the cause - the RichInk control (and the RichEd50W control used by Pocket Word) only supports binary images, and not hex images. For example the following picture will be discarded:

{\pict\pngblip\picw10449\pich3280\picwgoal5924\pichgoal1860 hex data}

but the following will work:

{\pict\pngblip\picw10449\pich3280\picwgoal5924\pichgoal1860\bin binary data}

And since NRtfTree only supports hex image, I had to modify the library to support binary image. What a hassle, but in the end everything worked well and images are displayed properly.

From RichInk to RichEdit50W

Next, I noticed that my control could not display images on the same line as text and seem to drop tables, while Pocket Word displayed them just fine. This is a hint that Pocket supports higher RTF versions then RichInk, so I use Remote Spy to find out which control Pocket is using:


PocketWord never used RichInk, but RichEd50W, similiar to what RichTextBox is using. I changed my code to use this window class (it stays in RichEd20.dll):

WindowsAPIs.LoadLibrary("RichEd20.dll");
this.m_RichInk = new WindowHost("RICHEDIT50W", 0, 0);

and images are displayed as intended. Surprisingly that's all I need - my program is now as good as Pocket Word.

Making the document read-only

My main purpose was to display documents - and not allowing user to modify them so I applied ES_READONLY to the window style:

this.m_RichInk = new WindowHost("RICHEDIT50W", 0, ES_READONLY);

There is still a minor problem: the window display may be distorted or incomplete after some extensive usage. I have yet to figure out the cause of the problem or how to fix it.

Source code

The sample source code is here. It includes the modified NRtfTree library, the decompiled source code of AgilesNotes Touch, and a sample form showing you how to read and write RTF files. NRtfTree was modified to insert binary images, instead of hex, and to support unicode characters. I have commented clearly where I modified the code.

I hope this will help other developers facing the same problem.
Read More »

Saturday, July 17, 2010

Beware when using DES/TripleDES for data encryption

I recently worked on a secure device application which requires a PIN code to be entered as startup.The application database also needs to be encrypted. My first approach was to use a DESCryptoServiceProvider, which is among the few supported encryption methods in the .NET Compact Framework,  to encrypt the database using the PIN code itself as the secret key.

My full code can be found here.

Soon after, I discovered the problem. The encrypted database can be decrypted if the secret key provided is slightly wrong. For example, if the secret key I used when encrypting is 12345678, the decryption will work with both 12345678 and 12345778 (wrong value). Thinking that this was almost certainly a bug, I went ahead to file a report at Microsoft Connect.

I did not have to wait until Microsoft responsed me (which was 3 days later) for an answer. Soon after I read the Wikipedia article on DES, I immediate realized my mistake. The key ostensibly consists of 64 bits; however, only 56 of these are actually used by the algorithm. Eight bits are used solely for checking parity, and are thereafter discarded. This explains why the wrong secret key can be used to decrypt the data as described above.

I changed to TripleDES only to observe the same behaviour. As AES is not supported on the compact framework, I end up encrypting the database using an MD5 hash of the PIN code. Although you may argue that MD5 has some chance of collision, the above hash + encryption combination will ensure, at least with ultimate possibility, that there is no way for the database to be decrypted without the correct password.
Read More »

Thursday, July 15, 2010

Intercept incoming SMS message on HTC Phones with HTC Sense

In my previous post I have explained the problem with MessageInterceptor on new HTC Phones and how to temporary fix it it by installing the patch. Although this allows existing applications to work without any code changes, user will have to use Pocket Outlook, which has an inferior user interface compared to HTC Messages, to send SMS. MMS are not supported until the Arcsoft MMS client is installed. HTC Sense will not be able to show new incoming text messages, which is a major limitation.

Luckily there is a way to intercept incoming text messages without installing any patch. However, you will have to make some code changes to your application. The idea is that, although the .NET MessageInterceptor class does not work, the SDK MapiRule sample (C:\Program Files\Windows Mobile 6.5.3 DTK\Samples\Common\CPP\Win32\MapiRule) works just fine. This post shows you how to modify the MAPIRule sample code to integrate it with your .NET project

Modifying MAPIRule.dll

You will not have to touch most of the MAPIRule code, except for modifying the GUID (important!) and the ProcessMessage() function (the sample just displays the incoming text message received in a MessageBox). Although you can parse the incoming message and process it directly inside ProcessMessage(), I decided on the following to make things clearer:
  1. When MapiRule.dll received a new SMS message in ProcessMessage(), it simply send a Win32 message via SendMessage to the host application.
  2. Upon received the specific Win32 message, host application, which is a .NET application, will decide on whether or not to intercept the new SMS message (it will not appear in phone Inbox) or let the default messaging application handle it.
  3. Based on the result of SendMessage(), MapiRule.dll will act accordingly.
This interprocess communication can be easily done by using the Win32 API SendMessage() and the .NET Compact Framework's MessageWindow class. Although you can specify your own message ID, I have chosen WM_COPYDATA since MapiRule.dll needs to send the SMS text and sender number to the host application, otherwise string pointers to message text and sender numbers cannot be shared. The sample code is below. Notice that newMsgWin is the handle to the host application's window. The sender number and message text is concatenated into a single string to be sent out.

HRESULT CMailRuleClient::ProcessMessage(IMsgStore *pMsgStore, ULONG cbMsg, LPENTRYID lpMsg, ULONG cbDestFolder, LPENTRYID lpDestFolder, ULONG *pulEventType, MRCHANDLED *pHandled)
{
............
        TCHAR msgInfo[500];
        COPYDATASTRUCT cds;

        // we concatenate the sender number and the message text, to be sent to the host application
        memset(msgInfo, 0, sizeof(msgInfo));
        wcscpy(msgInfo, pspvEmail->Value.lpszW);       
        wcscat(msgInfo, L"\n");
        wcscat(msgInfo, pspvSubject->Value.lpszW);
              
        //length for the recipient to know
        cds.dwData = wcslen(pspvEmail->Value.lpszW) + wcslen(pspvSubject->Value.lpszW) + 1;

        cds.cbData = sizeof(msgInfo);          //msg size in bytes
        cds.lpData = &msgInfo;            //pointer to the information to be sent

        // tell the main app about the text message
        if (SendMessage(newMsgWin, WM_COPYDATA, 0, (LPARAM) &cds) == (LRESULT) 1)
        {
            // a LRESULT of 1 means that the message was processed by parent application
            // so we delete the message and mark it as handled so it won't show up in Inbox
            hr = DeleteMessage(pMsgStore, pMsg, cbMsg, lpMsg, cbDestFolder, lpDestFolder, pulEventType, pHandled);
        }
        else
        {
            // other LRESULT means message not handled by main app, pass it on
            *pHandled = MRC_NOT_HANDLED;               
        }
............
}

Everything is straightforward, except that the lpData member of COPYDATASTRUCT structure has to be part of the structure memory area. This explains why an array of TCHAR, an not LPWSTR is used. If this is not followed, application may work or crash randomly without any indication why.

UPDATE (26 May 2012): As suggested by a reader, the above code may have problem with Unicode messages since sizeof(wchar_t) is 2 on Windows, resulting in incorrect value for the data length, dwData. If you have problems with truncated messages, try this:

cds.dwData = 2*wcslen(pspvEmail->Value.lpszW) + 2*wcslen(pspvSubject->Value.lpszW) + 1;

The updated MAPI DLL with the fix can be downloaded from here

The .NET code is as follows:

    [StructLayout(LayoutKind.Sequential)]
    private struct CopyDataStruct
    {
        public int IntData;
        public int Length;

        [MarshalAs(UnmanagedType.LPWStr)]
        public string Data;
    }

public class NewMsgWindow : MessageWindow
{
..........
    protected override void WndProc(ref Message m)
    {
        if (m.Msg == WM_COPYDATA) //we received a message telling us that there is an incoming SMS
        {
            CopyDataStruct cs = (CopyDataStruct)Marshal.PtrToStructure(m.LParam, typeof(CopyDataStruct));

            if (cs.Data.Contains(delim))
            {
                string sender = cs.Data.Split(delim)[0];
                string messageText = cs.Data.Split(delim)[1];

                if (true)
                {
                    // a LRESULT of 1 marks message as processed.
                    // and native msg app will not show msg in Inbox
                    m.Result = new IntPtr(1);
                }
                else
                    // Other LRESULT indicates we did not intercept the message
                    // and the message will be passed on to native messaging application.
                    m.Result = new IntPtr(0);
            }
        }
.......
}

The challenge is to receive the WM_COPYDATA message and marshal it back to a string. Most sample codes use GetLParam(), but as this is not supported by .NET CF, we have to use Marshal.PtrToStructure(). As commented, the .NET code will response with a result of either 1 or 0.

Source code

The sample code is here. Some part of the code was taken from the Remote Tracker open source project which also intercepts incoming text messages. Remote Tracker source code, however, parses the text message directly inside MapiRule.dll.

Setting up

Some registry keys need to be modified for MapiRule.dll to be used. This can either be done via a CAB file, or via the CreateInterceptorMethod2() method in the code. A reboot is needed for changes to take effect. If you need to change MapiRule.dll, you will have to remove the registry keys via RemoveInterceptorMethod2(), reboot the device, update the dll, call CreateInterceptorMethod2() and reboot again! Terminating poutlook.exe and tmail.exe may also help to reduce the number of restarts on some devices. This makes the development process very time consuming. Debugging MapiRule.dll is possible by attaching the debugger to tmail.exe.

Issues

There are still some minor issues yet to be solved. On HTC HD2, for some reasons, all text messages received will have the string " - GSM" appended at the end (refer to this discussion). Also, the total length of the sender name and the message text cannot exceed 500 since the TCHAR array length is hard coded. If you need to receive longer text message, the interprocess communication algorithm has to be modified to send and receive the text message part by part. You cannot simply increase the array length, as it will cause a stack fault.

Last but not least, the approach described here works on all devices, including HTC Devices. So it can be used as a replacement for the MessageInterceptor class. In a sense, it's better as you can decide, at the point of receiving, which message to be processed, instead of pre-creating a set of MessageInterceptor conditions and re-creating them should the interception conditions change.
Read More »

Wednesday, July 7, 2010

Common problems with .NET CF WebBrowser controls

Although most .NET developers will immediately think of the WebBrowser control when it comes to displaying HTML-formatted document, that seems to be more true for the full .NET framework rather than the compact framework. In fact, if your smart device application has to support a wide variety of devices, below are some reasons why the use of the WebBrowser control is discouraged:

1. Inconsistent HTML support across different devices. Although simple formatting like bold, italic, underline and tables tend to work well, more complicated constructs (css, javascripts) will be dropped or simplified by many devices. There is no way to know for sure except for testing.

2. WebBrowser.Navigate() opens a new window. On many devices, especially on devices where Opera is the default web browser, calling WebBrowser.Navigate() on a local file will simply open a new Opera browser window and leave the application. This will prevent many applications relying on the WebBrowser control from working.

A workaround is to set back IE as the default web browser by modifying registry key, see this. Another workaround is not to use the extension ".htm" or ".html" when calling .Navigate(). For example, instead of

WebBrowser1.Navigate("\\test.htm");

use:

WebBrowser1.Navigate("\\test.tmp");

The document contents will still be rendered properly despite the ".tmp" extension. This trick, however, does not help if you want to the web browser to navigate to an external URL, e.g. http://www.google.com.

Also, on many devices, the WebBrowser.Navigating event never fires, which I could not find a workaround for it.

3. Local images not rendered when DocumentText is set. This may seem a trivial problem with image path or format, but it's actually not. The problem is particular to .NET Compact Framework 3.5 running on Windows Mobile 6.5. The web browser control will refuse to render any local images and only display placeholder if the HTML is set via the DocumentText properly. Strangely it will display remote images from a web server. Images are also displayed correctly if the DocumentText is written to a file and loaded by calling Navigate(). The problem never appears on older .NET Compact Framework or Windows Mobile versions.

All the above seemingly trivial problems are just making life harder for the Windows Mobile developers.
Read More »

Tuesday, June 29, 2010

.NET CF MessageInterceptor class fails to intercept incoming messages

If you're writing an SMS application that intercepts incoming text messages using the MessageInterceptor class and observe that your application does not work on a particular phone, below is a list of things to check:

1. Most new HTC Phones will choose HTC Messages (part of HTC Sense), and not Pocket Outlook, as the default messaging spplication. HTC Message does not implement the MessageInterceptor class. To overcome this problem, you'll need to set Pocket Outlook as the default messaging application by using this patch.

Note: You may not be able to send/receive MMS after installing the patch. To use MMS, please install the Arcsoft MMS client.

Update (19 July 2010): I have found a way to intercept incoming messages on affected HTC phones without installing any patch by using MAPI Rule. For more information, see this post.

2. Some phones have a specific registry key to manually enable MessageInterceptor. Check HKEY_LOCAL_MACHINE\Software\Microsoft\Inbox\Svc\SMS\Rules and look for a key branch that looks like a GUID, e.g. {1000BC1C-F4A3-4210-B197-4AEBF2CEE6F5} Open that key, set the default value to 0 and reboot the phone to allow message interception.

3. You may have conflicting message interceptor rules left-over from other applications. To fix this, go to HKey_Local_Machine\Software\Microsoft\Inbox\Rules\, delete all sub-entries and reboot the phone. To avoid this from happening in the future, always call Dispose() on a MessageInterceptor when you finish using it or when your application closes.

Reference:

1. http://social.msdn.microsoft.com/Forums/en/vssmartdevicesvbcs/thread/e56f6db2-70d6-43a9-b3f2-7d476aabfa7e

2. http://stackoverflow.com/questions/2138650/messageinterceptor-is-not-called-on-htc-hd2
Read More »

Saturday, June 19, 2010

SerializableDictionary and System.InvalidOperationException

I recently attempted to use Paul Welter's SerializableDictionary in one of my Windows Mobile projects, Everything seems to work well with no code motifications, until when I need to serialize a class containing 2 dictionaries:

public class AppConfig
{       
      public SerializableDictionary Dictionary1;
      public SerializableDictionary Dictionary2;    
}

Attempt to serialize the above class will fail with a System.InvalidOperationException: Two mapping for dictionary. Searching the web for the exact error message returns no useful result, except for a posting which described a similiar problem (Two mappings for SourceTree.DTO.InventoryItemCollection.) but did not provide a solution. The posting also explicitly mentioned that the error occurred only on .NET Compact Framework, and not on the full framework.

The root cause of the error is at the first few lines of the SerializableDictionary source code:

[XmlRoot("dictionary")]
public class SerializableDictionary
    : Dictionary, IXmlSerializable

With this declaration, when 2 SerializableDictionary stay in the same class (which will be serialized), there will perhaps be a conflict somewhere as both of them are set up to use the same XmlRoot element ("dictionary").

A workaround is not to define XmlRoot inside SerializableDictionary class, but to create another class inheriting from SerializableDictionary and define XmlRoot in this class:

//parent class: SerializableDictionary
public class SerializableDictionary
    : Dictionary, IXmlSerializable { .... }

//dictionary 1
[XmlRoot("SerializableDictionary1")]
public class SerializableDictionary1 : SerializableDictionary { }

//dictionary 2
[XmlRoot("SerializableDictionary2")]
public class SerializableDictionary2 : SerializableDictionary { }

//the class to be serialized
public class AppConfigNew
{       
      public SerializableDictionary1 Dictionary1;
      public SerializableDictionary2 Dictionary2;    
}

This requires a few more lines of code, but is the cleanest workaround I have found. Also, I have yet to figure out why the full framework does not have such a problem.
Read More »

Sunday, January 17, 2010

ArgumentOutOfRangeException when calling DateTime.ToString()

Recently I encountered a strange problem with one of my .NET applications when running on phones having the language set to Chinese. If started by clicking the executable file, the program would just crash without warning at startup. If started via Visual Studio debugger, the debugging session will be terminated shortly with the error message "The remote connection to the device has been lost" and the program crashes with no further error messages.

For the device to show a more detailed error message, I uninstalled the .NET Compact Framework version 2.0 that comes bundled with the device and let Visual Studio automatically install the framework before debugging in started. With this change, the exact error message pops up, showing an ArgumentOutOfRangeException when the program tries to call DateTime.ToString().  With some further investigation, I realized this seems to be an issue with the .NET Compact Framework when running on phones where the user has changed the default language in Regional Settings to Chinese. In all my test devices, the problem seems to only happen on HTC phones with the language set to Chinese (Singapore) and strangely does not occur when a different Chinese dialect (e.g. Chinese (Taiwan)) is used.

To use Chinese (Singapore) as the phone's default localization, a workaround is to open Region Settings, in the Date tab, under Calendar Type, select another type of calendar such as 西历, instead of Gregorian Calendar.


The problem may have to do with the fact that HTC phones allow user to switch between 西历 and Gregorian Calendar. On other phones that do not have this problem the option Calendar Type is grayed out, default to 西历, and thus cannot be changed if the localization is set to Chinese.

Interestingly, when this problem happens, a Try/Catch around the related code section does not always prevent the crash. If it does, further execution of the program may have erratic behaviour such as throwing  mysterious ArgumentOutOfRangeException or returning wrong results when calculating date/time values.

Upgrading to .NET Compact 3.5 does not fix the problem.
Read More »

Monday, June 8, 2009

Animated gifs in .NET Compact Framework

By default the .Net Compact framework does not support displaying GIF based animations on a Windows Form. It is possible to code up a custom animator that will essentially do the same. Here's a link to a sample class for displaying a GIF on the compact framework.

http://msdn.microsoft.com/en-us/library/aa446483.aspx

This does not work with all GIF files, however. A better approach would be to use a WebBroswer control to display the animated picture.

The full .NET framework, however, supports animated GIF natively.

Read More »

Monday, May 25, 2009

Accessing wifi hardware status via Microsoft.WindowsMobile.Status.SystemState

The State and Notification Broker in WM6 supports the following new properties (not present in WM5 SDK)

* BluetoothStateA2DPConnected
* BluetoothStateDiscoverable
* BluetoothStateHandsFreeAudio
* BluetoothStateHandsFreeControl
* BluetoothStateHardwarePresent
* BluetoothStatePowerOn

and

* WiFiStateConnected
* WiFiStateConnecting
* WiFiStateHardwarePresent
* WiFiStateNetworksAvailable
* WiFiStatePowerOn

To use these properties, you must add a reference to Microsoft.WindowsMobile.Status found in C:\Program Files\Windows Mobile 6 SDK\Managed Libraries\Microsoft.WindowsMobile.Status.dll (that is, the WM6 SDK), and not another DLL having the same name found in C:\Program Files\Windows CE Tools\wce500\Windows Mobile 5.0 Pocket PC SDK\DesignTimeReferences\Microsoft.WindowsMobile.PocketOutlook.dll (which is for WM5 SDK). If you mistakenly add the WM5 SDK's DLL, you won't be able to access these properties.

Reference:

What's New For Managed Developers In Windows Mobile 6
Read More »

Monday, April 13, 2009

Bring an application's active window to foreground

1. On Windows CE/Windows Mobile only 1 instance of any executable file is allowed to run at a time. The following will start the application if it's not running, OR bring its active window to the foreground if it's already running


Process.Start(Assembly.GetExecutingAssembly().GetModules(0).FullyQualifiedName, "")


The code can be called by the application itself, or by a different application. It will not work if the application is busy at the time the code is executed.


2. On both Windows CE and Windows XP, we can do so by finding the active window handle and call SetForegroundWindow


This makes use of the MainWindowHandle property of the .NET's Process class to retrieve the handle to the active window of the current process.


SetForegroundWindow(Process.GetCurrentProcess().MainWindowHandle)


3. In Win32 C++


Making a window the foreground window requires more than just calling the SetForegroundWindow API. You must first determine the foreground thread and attach it to your window, using AttachThreadInput, then call SetForegroundWindow. That way they can share input states.


First I call GetForegroundWindow to get the handle of the current foreground window. Then a few calls to GetWindowThreadProcessId retrieve the threads associated with the current foreground window and the window I want to bring to the foreground. If these threads are the same a simple call to SetForegroundWindow is all that is necessary. Otherwise, the foreground thread is attached to the window that I am bringing to the front and detached from what was the current foreground window. The AttachThreadInput API handles this.


Once this is accomplished, the state of the window is determined. If IsIconic says that the window is minimized, I restore it by issuing ShowWindow with the SW_RESTORE flag. Otherwise ShowWindow is called with the SW_SHOW flag.


Reference:


Simulate Window's Alt-Tab List and
Bring a Window to the Foreground, http://www.thescarms.com/vbasic/alttab.aspx

Read More »

Monday, March 9, 2009

Disable full-screen edit mode in smartphone's multiline textbox

The multi-line textbox created by .NET CF on a smartphone does not allow inline editing. As soon as user presses ENTER, the textbox enters full-screen edit mode, and editing must be done there.


To understand what is going on here, we need to understand into native code. The textbox control in .NET CF is a wrapper around the native edit control, however the specific wrapping which occurs changes depending upon the value of the multiline property.


As the "Expandable Edit Controls" article available on MSDN documents (available at http://msdn2.microsoft.com/en-us/library/ms911985.aspx), to get an expandable edit control in native Win32 code requires two controls to be constructed. A normal edit control, followed by a up down spinner control. It is the spinner control that is drawing the arrow to the right hand edge of the textbox control and introducing the behaviour which enables bringing up a fullscreen edit window. The .NET Compact Framework is hiding this additional control/complexity by automatically creating the required controls whenever the multiline property is changed.


There doesn't appear to be a property or method exposed via the Compact Framework which will enable the user to decide how multiline text box controls should be handled.


Knowing this and by using a tool such as Remote Win Spy++ to see what is going on at the OS level, the following hack attempts to remove the spinner control and disable the ENTER key to prevent the textbox entering into full-screen edit mode


using System.Runtime.InteropServices;


[DllImport("coredll.dll")]

private static extern IntPtr GetWindow(IntPtr hWnd, int uCmd);


private const int GW_HWNDNEXT = 2;


[DllImport("coredll.dll")]

private static extern IntPtr GetParent(IntPtr hWnd);


[DllImport("coredll.dll")]

private static extern int DestroyWindow(IntPtr hWnd);


[DllImport("coredll.dll")]

private static extern int SetWindowPos(IntPtr hWnd, IntPtr hWndInsertAfter,

int X, int Y, int cx, int cy, int uFlags);


private void menuItem1_Click(object sender, EventArgs e)

{

// Find the spinner control associated with the textbox

IntPtr spin = GetWindow(textBox1.Handle, GW_HWNDNEXT);

// Destroy the spinner

DestroyWindow(spin);

// Move the textbox control to take up the entire

// client space

IntPtr container = GetParent(textBox1.Handle);

SetWindowPos(textBox1.Handle, container, 0, 0,

textBox1.Width - 1, textBox1.Height, 0);

}


private void TextBox1_KeyDown(object sender, KeyEventArgs e)

{

// disable the ENTER key

if (e.KeyCode == Keys.ENTER) e.Handled = true;

}


Reference:

http://social.msdn.microsoft.com/Forums/en-US/netfxcompact/thread/8272ec73-920f-4c04-ad38-1bca35598317/

Read More »

Add Cut/Copy/Paste functionality to WM Textboxes

Allow user to display a context menu with cut/copy/paste functionalities to textboxes by tap & hold, y using Windows CE's SIPPREF control to automatically implement default input panel behavior for a dialog. It provides the following features:

  • The Input Panel is automatically shown/hidden as controls gain and loose focus.
  • Edit controls have an automatic context menu with Cut, Copy, Paste type options.
  • The SIP state is remembered if the user switches to another application and later returns to this form

In order to use the SIPPREF control we must first request the operating system to register the SIPPREF window class. We do this by calling the SHInitExtraControls function. This step only needs to be done once, so is typically done during your application’s start up code. It is very easy to call, as the following example demonstrates:


#include
SHInitExtraControls
();


Once we have registered the SIPPREF window class, we simply create a SIPPREF control as a child of our dialog. When the SIPPREF control is created it will enumerate all sibling controls and subclass them in order to provide the default SIP handling behaviour. The SIPPREF control must be the last control added to your dialog, as any controls added after the SIPPREF control will not be present when the SIPPREF control enumerates its siblings, and hence will not be subclassed to provide the proper SIP handling.


If dynamically creating the SIPPREF control, a good place to do this is within the WM_CREATE or WM_INITDIALOG message handler, as the following code sample demonstrates:


case WM_INITDIALOG:
// Create a SIPPREF control to handle the SIP. This
// assumes 'hDlg' is the HWND of the dialog.
CreateWindow(WC_SIPPREF, L"", WS_CHILD,
0, 0, 0, 0, hDlg, NULL, NULL, NULL);


.NET CF Sample Code via P/invoke


Note: if a panel is used, hWndParent passed to CreateWindowEx should be the handle of the panel, otherwise there is no effect!


[DllImport("aygshell.dll")]
private static extern int SHInitExtraControls();

[DllImport("coredll.dll")]
private static extern IntPtr CreateWindowEx(
uint dwExStyle,
string lpClassName,
string lpWindowName,
uint dwStyle,
int x,
int y,
int nWidth,
int nHeight,
IntPtr hWndParent,
IntPtr hMenu,
IntPtr hInstance,
IntPtr lpParam);

private static readonly string WC_SIPPREF = "SIPPREF";
private static readonly uint WS_CHILD = 0x40000000;


protected override void OnLoad()
{
// Initialise the extra controls library
SHInitExtraControls();

// Create our SIPPREF control which will enumerate all existing
// controls created by the InitializeControl() call.
IntPtr hWnd = CreateWindowEx(0, WC_SIPPREF, "", WS_CHILD,
0, 0, 0, 0, this.Handle, IntPtr.Zero, IntPtr.Zero, IntPtr.Zero);
}


Reference: http://www.christec.co.nz/blog/archives/146

Read More »

Monday, February 16, 2009

Multi-line Graphics.MeasureString implementation on .Net CF

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

public SizeF MeasureString ( string text, Font font )

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

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

The only solution here is to implement our own multi-line MeasureString method.
To solve the problem, we'll use the native API DrawText. It will calculate the size of the text according with the uFormat parameter and using the graphics (device context) selected font.

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

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

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

Read More »

Monday, December 29, 2008

Attached the debugger to a running .NET Compact Framework process

To attach the debugger to a running process on the device:
  • On the Debug menu, click Attach to Process.
  • In the Transport box, select Smart Device.
  • To populate the Qualifier box, click Browse.
  • In the Connect to Device dialog box, select the correct device type, and then click Connect.
  • To populate the Attach to box, click Select.
  • In the Available Processes box, select the process name, and then click Attach.
  • You are now attached with the native debugger.
By default, devices, including emulators, do not allow the managed debugger to attach to processes that are already running. Attaching the managed debugger to an already running process is a situation that you typically encounter in device solutions that include both managed and native code.

To enable the managed debugger to attach to a running process:
  • Open the Registry Editor for the target device, expand Windows Mobile 5.0 Pocket PC Emulator, and then create the following key:
  • HKEY_LOCAL_MACHINE\SOFTWARE\Microsoft\.NETCompactFramework\Managed Debugger.
  • Create a DWORD named AttachEnabled.
  • Set the Name as AttachEnabled, and the Value as 1.
  • You may need to restart Visual Studio, or at least close the running application on the device which you want to attach to.
Without this registry modification, there will be an error:

"Unable to attach to the process. Attach is not enabled for this process with this debug type."

Reference: http://msdn.microsoft.com/en-us/library/ms228818.aspx
Read More »

Thursday, November 6, 2008

Floating popup menu on a Smartphone

As there is no touchscreen on a smartphone and therefore no such thing as "tap and hold", the .NET compact framework for WM6 standard doesn't support the ContextMenu property for textbox controls. Simulate this behaviour by displaying a popup menu whenever the user presses ENTER when the textbox is having focus. This is possible in C++ by using TrackPopupMenu API.


Method 1: Retrieve all the sub-menus under the left soft key menu of the application (or in general, associated with the MainMenu property of the application) and display as pop-up menu on the top left corner of the form:


IntPtr parentWindow = GetActiveWindow()
IntPtr hMenuWnd
= SHFindMenuBar(parentWindow);
IntPtr hMainMenu
= (IntPtr)SendMessage(hMenuWnd, SHCMBM_GETMENU, 0, 0);
IntPtr hMenu
= GetSubMenu(hMainMenu, 0);
TrackPopupMenu(hMenu, TPM_LEFTALIGN, 0, 0, parentWindow, IntPtr.Zero);

[DllImport("coredll.dll", EntryPoint = "TrackPopupMenuEx", SetLastError = true)]
private static extern int TrackPopupMenu(IntPtr hMenu, int wFlags, int x, int y, IntPtr hwnd, IntPtr lprc);

[DllImport("coredll.dll", SetLastError = true)]
public static extern IntPtr GetActiveWindow();

[DllImport("aygshell.dll", SetLastError=true)]
public static extern IntPtr SHFindMenuBar(IntPtr hWindow);

[DllImport("coredll.dll", SetLastError = true)]
public static extern int SendMessage(IntPtr hWnd, int msg, int wParam, int lParam);

[DllImport("coredll.dll", SetLastError = true)]
public static extern IntPtr GetSubMenu(IntPtr hMenu, int nPos);


The OnClick event of the menu items can be declared the usual way:


this.menuItem3.Click += new System.EventHandler(this.menuItem3_Click);
private void menuItem3_Click(object sender, EventArgs e)
{
}


and menuItem3_Click will be called when the associated menu item is clicked, regardless of whether it comes from the pop-up menu or from the main menu.


This approach has some limitations:


(1) It can only display a sub-menu (via GetSubMenu) but cannot display the entire menu. Removing the GetSubMenu line and passing hMainMenu to TrackPopupMenu immediately causes TrackPopupMenu to return 0 and GetLastError returns ERROR_INVALID_HANDLE. How to display the entire menu?


(2) The menu can only be displayed as pop-up if it is associated with the MainMenu property of the form (otherwise SHFindMenuBar would not work). How to display the pop-up menu without first associating it with the form? One approach is to use reflection to retrieve the private properties of the MainMenu data type, which hopefully contains the IntPtr handle.


Method 2: Create our own menu and pass the handle to TrackPopupMenu:


IntPtr hMenu = CreatePopupMenu();
int index = 100;
AppendMenu(hMenu, MF_STRING, index, "Item1");index++;
AppendMenu(hMenu, MF_STRING, index, "Item2");index++;
AppendMenu(hMenu, MF_STRING, index, "Item3");index++;


It works this way (the popup menu is displayed) but handling the click event is difficult. To trace which item is clicked we must use:


TrackPopupMenu(hMenu, TPM_LEFTALIGN | TPM_RETURNCMD, 0, 0, parentWindow, IntPtr.Zero);


which make the call to TrackPopupMenu a blocking call and will return the index of the selected menu item.


Reference:

http://forums.microsoft.com/MSDN/ShowPost.aspx?PostID=3556499&SiteID=1

Read More »