Smartphone: ListView + Back Key = No KeyPress?

J

John T

I'm writing a Smartphone app where I need to control the movement between
forms which means I need to know when the user clicks the "Back" key. I can
trap the KeyPress event and handle the Escape key code with no problem, but
there's a catch: It appears this event is not fired when the form has a
ListView and
the ListView has focus when the user clicks Back.

You can see the issue with a simple one-form app. Create a Smartphone
project (I'm using VS2005, C#, .NET 2.0), add one form (with or without menu
doesn't seem to matter). Add a handler for the form's KeyPress event and set
a breakpoint or message box in the handler. Run the app, press the Back key,
notice the handler is called.

Now add a ListView on the form (don't bother adding columns or items). Run
the app again, press the Back key, notice the handler is *not* called. Now
add a KeyPress handler for the ListView control. Run the app, press the Back
key, observe neither handler is called.

Has anybody found a workaround for this? Or am I just missing something?
:)

Thanks.
 
J

John T

John T said:
I'm writing a Smartphone app where I need to control the movement
between forms which means I need to know when the user clicks the
"Back" key. I can trap the KeyPress event and handle the Escape key
code with no problem, but there's a catch: It appears this event is
not fired when the form has a ListView and
the ListView has focus when the user clicks Back.

I've found a workaround (or perhaps the intended way?) for the handling of
the Back key when the ListView has focus. Thanks to Sergey Bogdanov for
posting his WindowHook class. In case anybody else is having my problem,
here's my solution (without much effort put into polishing it for showroom
quality):

// WindowHook class originally posted by
// Sergey Bogdanov sergey.bogdanov at gmail.com
// http://www.sergeybogdanov.com
// Modified by John Tabor to add GetHiWord() and GetLoWord() methods.
using System;
using System.Collections.Generic;
using System.Text;
using System.Windows.Forms;
using System.Runtime.InteropServices;
using System.ComponentModel;

namespace MyNamespace
{
internal class WindowHook
{
IntPtr _oldWndProc;
IntPtr _hwnd;
WndProcDelegate _newWndProc;

public WindowHook()
{
_newWndProc = new WndProcDelegate(WndProc);
}

public void Attach(Control c)
{
Attach(c.Handle);
}

public void Attach(IntPtr hwnd)
{
_hwnd = hwnd;
_oldWndProc = GetWindowLong(hwnd, GWL_WNDPROC);
if (_oldWndProc == IntPtr.Zero) throw new Win32Exception();
int r = SetWindowLong(hwnd, GWL_WNDPROC, _newWndProc);
if (r == 0) throw new Win32Exception();
}

public void Detach()
{
int r = SetWindowLong(_hwnd, GWL_WNDPROC, _oldWndProc);
if (r == 0) throw new Win32Exception();
}

protected virtual int WndProc(IntPtr hWnd, uint msg,
IntPtr wparam, IntPtr lparam)
{
return CallWindowProc(_oldWndProc, hWnd, msg, wparam, lparam);
}

/// <summary>
/// Method: Lets the HiWord from an integer value, good for
/// interpreting LParam, WParam etc
/// </summary>
/// <param name="aValue"></param>
/// <returns></returns>
public static int GetHiWord(int value)
{
return (short)(((uint)value & 0xFFFF0000U) >> 16);
}

/// <summary>
/// Method: Lets the LoWord from an integer value, good for
/// interpreting LParam, WParam etc
/// </summary>
/// <param name="aValue"></param>
/// <returns></returns>
public static int GetLoWord(int value)
{
//return aValue & 0xffff;
return (short)((uint)value & 0x0000FFFFU);
}

internal delegate int WndProcDelegate(IntPtr hWnd, uint Msg,
IntPtr wParam, IntPtr lParam);

#region P/Invoke declaration

[DllImport("coredll.dll", SetLastError = true)]
static extern IntPtr GetWindowLong(IntPtr hWnd, int nIndex);
[DllImport("coredll.dll", SetLastError = true)]
static extern int SetWindowLong(IntPtr hWnd, int nIndex,
WndProcDelegate newProc);
[DllImport("coredll.dll", SetLastError = true)]
static extern int SetWindowLong(IntPtr hWnd, int nIndex,
IntPtr newProc);

[DllImport("coredll.dll", SetLastError = true)]
static extern int CallWindowProc(IntPtr lpPrevWndFunc,
IntPtr hWnd, uint Msg, IntPtr wParam, IntPtr lParam);

[DllImport("coredll.dll", SetLastError = true)]
static extern IntPtr GetWindow(IntPtr hWnd, uint uCmd);
#endregion

const int GWL_WNDPROC = -4;
}
}


// Base form for use in the application.
using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Data;
using System.Drawing;
using System.Text;
using System.Windows.Forms;

namespace MyNamespace
{
public partial class BaseForm : Form
{
#region Constructors
public BaseForm()
{
InitializeComponent();
}

#endregion Constructors

#region Property: WindowHook
/// <summary>
/// Private back for the WindowHook property.
/// </summary>
private WindowHook m_WindowHook;
/// <summary>
/// Gets/sets the window hook for the form.
/// </summary>
internal WindowHook WindowHook
{
get
{
return m_WindowHook;
}
set
{
m_WindowHook = value;
}
}
#endregion Property: WindowHook

protected override void OnHandleCreated(EventArgs e)
{
WindowHook = new WindowHookEx(this);
(WindowHook as WindowHookEx).BackKeyPress += new
KeyPressEventHandler(BackKeyPress);
base.OnHandleCreated(e);
}

/// <summary>
/// Handler called by the window hook for the
/// back key being pressed.
/// </summary>
/// <param name="sender"></param>
/// <param name="e"></param>
public virtual void BackKeyPress(object sender,
KeyPressEventArgs e)
{
}

private class WindowHookEx : WindowHook
{
#region Event: BackKeyPress
/// <summary>
/// Event raised when a key is pressed.
/// </summary>
public event KeyPressEventHandler BackKeyPress;
/// <summary>
/// Raises the KeyPress event.
/// </summary>
/// <param name="key"></param>
protected virtual void OnBackKeyPress(object sender,
KeyPressEventArgs e)
{
KeyPressEventHandler handler = BackKeyPress;
if (handler != null)
{
BackKeyPress(sender, e);
}
}
#endregion Event: BackKeyPress

#region Private fields
const int VK_ESCAPE = 0x1B;
const int WM_HOTKEY = 0x0312;
const int VK_TBACK = VK_ESCAPE;
const int MOD_KEYUP = 0x1000;
#endregion Private fields

#region Constructors
public WindowHookEx(Form form)
{
Form = form;
Attach(Form);
}
#endregion Constructors

#region Property: Form
/// <summary>
/// Private back for the Form property.
/// </summary>
private Form m_Form;
/// <summary>
/// Gets/sets the form to hook.
/// </summary>
public Form Form
{
get
{
return m_Form;
}
set
{
m_Form = value;
}
}
#endregion Property: Form

#region WndProc()
/// <summary>
/// Handles specified windows messages.
/// </summary>
/// <param name="hWnd"></param>
/// <param name="msg"></param>
/// <param name="wparam"></param>
/// <param name="lparam"></param>
/// <returns></returns>
protected override int WndProc(IntPtr hWnd, uint msg,
IntPtr wparam, IntPtr lparam)
{
int returnValue = 0;
bool ignore = false;
switch (msg)
{
case WM_HOTKEY:
int hiWord = GetHiWord(lparam.ToInt32());
int loWord = GetLoWord(lparam.ToInt32());
if (hiWord == VK_TBACK && ((loWord & MOD_KEYUP) != 0))
{
KeyPressEventArgs e = new
KeyPressEventArgs((char)hiWord);
OnBackKeyPress(Form, e);
ignore = e.Handled;
}
break;
default:
break;
}
if (!ignore)
{
returnValue = base.WndProc(hWnd, msg, wparam, lparam);
}
return returnValue;
}
#endregion WndProc()
}
}
}


// Example derived form.
using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Data;
using System.Drawing;
using System.Text;
using System.Windows.Forms;

namespace MyNamespace
{
internal partial class DerivedForm : BaseForm
{

// I typically have a menu with a Cancel option and
// treat the Back key the same as the user clicking
// the Cancel menu item.

#region CancelForm()
/// <summary>
/// Cancel the current form.
/// </summary>
private void CancelForm()
{
DialogResult result = MessageBox.Show("Close?", String.Empty,
MessageBoxButtons.YesNo, MessageBoxIcon.Question,
MessageBoxDefaultButton.Button1);
if (result == DialogResult.Yes)
{
DialogResult = DialogResult.Cancel;
}
}
#endregion CancelForm()

public override void BackKeyPress(object sender,
KeyPressEventArgs e)
{
// Treat "Back" as Cancel.
CancelForm();
e.Handled = true;
base.BackKeyPress(sender, e);
}
}
}
 

Ask a Question

Want to reply to this thread or ask your own question?

You'll need to choose a username for the site, which only take a couple of moments. After that, you can post your question and our members will help you out.

Ask a Question

Top