Tree Node Checked behavior on a TreeView in Compact Framework 3.5 running on Windows Mobile 6.5
-
18-09-2019 - |
Question
I have been upgrading an existing .NET Windows Mobile application to use the 3.5 version of the compact framework and to run on Windows Mobile 6.5. I have a form with a TreeView. The TreeView.Checkboxes property is set to true so that each node has a check box. This gives no trouble in all previous versions of Windows Mobile.
However, in version 6.5 when you click on a check box it appears to check and then uncheck instantaneously. But it only raises the AfterCheck event once. The only way I can get a check to stick is by double clicking it (which is the wrong behavior).
Has anyone seen this behavior? Does anyone know of a workaround for it?
I have included a simple test form. Dump this form into a Visual Studio 2008 Smart Device application targeted at Windows Mobile 6 to see what I mean.
Public Class frmTree
Inherits System.Windows.Forms.Form
#Region " Windows Form Designer generated code "
Public Sub New()
MyBase.new()
' This call is required by the Windows Form Designer.
InitializeComponent()
' Add any initialization after the InitializeComponent() call.
End Sub
'Form overrides dispose to clean up the component list.
<System.Diagnostics.DebuggerNonUserCode()> _
Protected Overrides Sub Dispose(ByVal disposing As Boolean)
If disposing AndAlso components IsNot Nothing Then
components.Dispose()
End If
MyBase.Dispose(disposing)
End Sub
'Required by the Windows Form Designer
Private components As System.ComponentModel.IContainer
Friend WithEvents TreeView1 As System.Windows.Forms.TreeView
Private mainMenu1 As System.Windows.Forms.MainMenu
'NOTE: The following procedure is required by the Windows Form Designer
'It can be modified using the Windows Form Designer.
'Do not modify it using the code editor.
<System.Diagnostics.DebuggerStepThrough()> _
Private Sub InitializeComponent()
Dim TreeNode1 As System.Windows.Forms.TreeNode = New System.Windows.Forms.TreeNode("Node0")
Dim TreeNode2 As System.Windows.Forms.TreeNode = New System.Windows.Forms.TreeNode("Node2")
Dim TreeNode3 As System.Windows.Forms.TreeNode = New System.Windows.Forms.TreeNode("Node3")
Dim TreeNode4 As System.Windows.Forms.TreeNode = New System.Windows.Forms.TreeNode("Node4")
Dim TreeNode5 As System.Windows.Forms.TreeNode = New System.Windows.Forms.TreeNode("Node1")
Dim TreeNode6 As System.Windows.Forms.TreeNode = New System.Windows.Forms.TreeNode("Node5")
Dim TreeNode7 As System.Windows.Forms.TreeNode = New System.Windows.Forms.TreeNode("Node6")
Dim TreeNode8 As System.Windows.Forms.TreeNode = New System.Windows.Forms.TreeNode("Node7")
Me.mainMenu1 = New System.Windows.Forms.MainMenu
Me.TreeView1 = New System.Windows.Forms.TreeView
Me.SuspendLayout()
'
'TreeView1
'
Me.TreeView1.CheckBoxes = True
Me.TreeView1.Location = New System.Drawing.Point(37, 41)
Me.TreeView1.Name = "TreeView1"
TreeNode2.Text = "Node2"
TreeNode3.Text = "Node3"
TreeNode4.Text = "Node4"
TreeNode1.Nodes.AddRange(New System.Windows.Forms.TreeNode() {TreeNode2, TreeNode3, TreeNode4})
TreeNode1.Text = "Node0"
TreeNode6.Text = "Node5"
TreeNode7.Text = "Node6"
TreeNode8.Text = "Node7"
TreeNode5.Nodes.AddRange(New System.Windows.Forms.TreeNode() {TreeNode6, TreeNode7, TreeNode8})
TreeNode5.Text = "Node1"
Me.TreeView1.Nodes.AddRange(New System.Windows.Forms.TreeNode() {TreeNode1, TreeNode5})
Me.TreeView1.Size = New System.Drawing.Size(171, 179)
Me.TreeView1.TabIndex = 0
'
'frmTree
'
Me.AutoScaleDimensions = New System.Drawing.SizeF(96.0!, 96.0!)
Me.AutoScaleMode = System.Windows.Forms.AutoScaleMode.Dpi
Me.AutoScroll = True
Me.ClientSize = New System.Drawing.Size(240, 268)
Me.Controls.Add(Me.TreeView1)
Me.Menu = Me.mainMenu1
Me.Name = "frmTree"
Me.Text = "frmTree"
Me.ResumeLayout(False)
End Sub
#End Region
End Class
Solution 2
I was able to create a workaround for this, but had to go to extreme measures to do so. It appears that the behavior we are seeing occurs because the Click event is being fired both from the MouseDown and the MouseUp events (instead of just the mouse up as it would in Windows or previous versions).
To show this, you can start by tapping on the checkbox, leaving your finger on the screen and dragging off the check box. It will become checked from the MouseDown event and will stay checked because the MouseUp event is not fired when you lift your finger from a different position. The same works for tapping off the checkbox and dragging on.
In order to prevent the double click behavior you must suppress one of the MouseDown or MouseUp events. I ended up creating a control that inherited the TreeView and used WndProcHooker to hook the OnMouseDown method and mark it as handled so that the MouseDown Event never actually gets fired. I figured that made the most sense (you must have your finger over the checkbox when you lift it).
Here is a link to the MSDN article about the WndProcHooker. Below is my code for my TreeViewInherit class. While this works, I am still astonished that these are the lengths I have to go to to get this working. Additionally, I am not looking forward to the day MS fixes this and thus breaks my workaround in the process.
Imports System.Windows.Forms
Imports Microsoft.WindowsCE.Forms
Public Class TreeViewInherit
Inherits System.Windows.Forms.TreeView
#Region " Variables "
Private mBlnHandleMouseDown As Boolean
#End Region
#Region " Methods "
Public Sub New()
MyBase.New()
'Set the Handle Mouse Down based on the OS. if 6.5 and up, then handle it.
mBlnHandleMouseDown = (System.Environment.OSVersion.Version.Major >= 5 AndAlso System.Environment.OSVersion.Version.Minor >= 2 AndAlso System.Environment.OSVersion.Version.Build >= 21234)
If mBlnHandleMouseDown Then
WndProcHooker.HookWndProc(Me, New WndProcHooker.WndProcCallback(AddressOf Me.WM_LButtonDown_Handler), Win32.WM_LBUTTONDOWN)
End If
End Sub
Protected Overrides Sub OnMouseDown(ByVal e As System.Windows.Forms.MouseEventArgs)
'Don't Call the Base to prevent the extra event from firing
If Not mBlnHandleMouseDown Then
MyBase.OnMouseDown(e)
End If
End Sub
#End Region
#Region " Events "
Private Function WM_LButtonDown_Handler(ByVal hwnd As IntPtr, ByVal msg As Integer, ByVal wParam As Integer, ByVal lParam As Integer, ByRef handled As Boolean) As Integer
Try
Me.Capture = False
Dim lastCursorCoordinates As Win32.POINT = Win32.LParamToPoint(lParam)
If Me.ClientRectangle.Contains(lastCursorCoordinates.X, lastCursorCoordinates.Y) Then
OnMouseDown(New MouseEventArgs(MouseButtons.Left, 1, lastCursorCoordinates.X, lastCursorCoordinates.Y, 0))
End If
handled = True
Return 0
Catch ex As Exception
Throw
End Try
End Function
#End Region
End Class
Good Luck!
OTHER TIPS
Hydroslide: To answer your first question, yes, I'm seeing this behavior. To your second question: No, I've yet to find a solution.
I've developed an app in VS2K8 targeting CF 3.5 SP1. Like your app, mine is used on several generations of devices with no problems. However, I've run into this TreeView issue on Windows Mobile 6.5.
On all my WM 6.5 phones (the HTC Pure (ST6356), HTC Tilt 2, and HTC Imagio) -- as well as in the WM 6.5 emulator -- the TreeView's checkbox functionality fails. Clicking on a checkbox nearly always causes a checkmark to be set only to be cleared milliseconds later (and vice-versa). The only way I've found to reliably force a checkmark to "stick" is to double-tap the checkbox. Sound familiar, Hydroslide?
In addition to this bizarre behavior, the appearance of these TreeViews is altered on the newer HTC phones to include increased white space between nodes, presumably to allow for easier manipulation via a finger or thumb. Compare: @http://ftp.agconnections.com/treeviews.png. (Remove the @ preceeding the link. Necessary because StackOverflow first encourages me to post questions that are "detailed and specific," then prevents me from creating a post that includes more than one hyperlink. Nice.) Interestingly, the WM 6.5 emulator displays the treeview without any additional white space, but still exhibits the check/uncheck problem.
I've created a barebones project containing only a standard treeview and a few nodes, and its behavior is identical to that of my production project: http://ftp.agconnections.com/TreeViewTest.zip. I've set a breakpoint in the AfterCheck event and found -- just as Hydroslide did -- that it's only fired once when single-tapping.
I'm astonished that no one outside of the two of us has complained of this behavior.
The customers waiting for a fix to this problem are starting to pile up, and a few of them are somewhat-less-than-understanding. Any suggestions are greatly appreciated.
Jason Purcell
You may wish to use this alternative: http://blogs.southworks.net/mconverti/2009/06/22/optional-checkable-tree-view-control-for-net-compact-framework-35/
To get over the problem of the AfterCheck event not firing I found that I could get the node clicked on and then use that to call AfterCheck, that works but I then found AfterCheck was called before the state of the checkbox had been changed so instead raised my own event and handled it appropriately.
Imports System.Windows.Forms
Imports Microsoft.WindowsCE.Forms
Public Class TreeViewInherit
Inherits System.Windows.Forms.TreeView
'Occurs when the user clicks a TreeNode with the mouse.
Public Event MouseDownOveride(ByVal node As TreeNode)
#Region " Variables "
Private mBlnHandleMouseDown As Boolean
#End Region
#Region " Methods "
Public Sub New()
MyBase.New()
'Set the Handle Mouse Down based on the OS. if 6.5 and up, then handle it.
mBlnHandleMouseDown = (System.Environment.OSVersion.Version.Major >= 5 AndAlso System.Environment.OSVersion.Version.Minor >= 2 AndAlso System.Environment.OSVersion.Version.Build >= 21234)
If mBlnHandleMouseDown Then
WndProcHooker.HookWndProc(Me, New WndProcHooker.WndProcCallback(AddressOf Me.WM_LButtonDown_Handler), Win32.WM_LBUTTONDOWN)
End If
End Sub
Protected Overrides Sub OnMouseDown(ByVal e As System.Windows.Forms.MouseEventArgs)
'Don't Call the Base to prevent the extra event from firing
If Not mBlnHandleMouseDown Then
MyBase.OnMouseDown(e)
End If
End Sub
Private Function FindTreeNodeFromHandle(ByVal tnc As TreeNodeCollection, ByVal handle As IntPtr) As TreeNode
For Each tn As TreeNode In tnc
If tn.Handle = handle Then
Return tn
End If
' we couldn't have clicked on a child of this node if this node
' is not expanded!
If tn.IsExpanded Then
Dim tn2 As TreeNode = FindTreeNodeFromHandle(tn.Nodes, handle)
If tn2 IsNot Nothing Then
Return tn2
End If
End If
Next
Return Nothing
End Function
#End Region
#Region " Events "
Private Function WM_LButtonDown_Handler(ByVal hwnd As IntPtr, ByVal msg As UInteger, ByVal wParam As UInteger, ByVal lParam As Integer, ByRef handled As Boolean) As Integer
Try
Me.Capture = False
Dim lastCursorCoordinates As Win32.POINT = Win32.LParamToWin32POINT(lParam)
If Me.ClientRectangle.Contains(lastCursorCoordinates.X, lastCursorCoordinates.Y) Then
OnMouseDown(New MouseEventArgs(MouseButtons.Left, 1, lastCursorCoordinates.X, lastCursorCoordinates.Y, 0))
End If
handled = True
Dim msgPos As Point = Win32.LParamToPoint(CInt(Win32.GetMessagePos()))
msgPos = Me.PointToClient(msgPos)
' check to see if the click was on an item
Dim hti As New Win32.TVHITTESTINFO()
hti.pt.X = msgPos.X
hti.pt.Y = msgPos.Y
Dim hitem As Integer = Win32.SendMessage(Me.Handle, Win32.TVM_HITTEST, 0, hti)
Dim htMask As UInteger = (Win32.TVHT_ONITEMICON Or Win32.TVHT_ONITEMLABEL Or Win32.TVHT_ONITEMINDENT Or Win32.TVHT_ONITEMBUTTON Or Win32.TVHT_ONITEMRIGHT Or Win32.TVHT_ONITEMSTATEICON)
If hti.flags = Win32.TVHT_ONITEMSTATEICON Then
RaiseEvent MouseDownOveride(FindTreeNodeFromHandle(Me.Nodes, hti.hItem))
End If
Return 0
Catch ex As Exception
Throw
End Try
End Function
#End Region
End Class
Works ok.
We asked MS for an answer and they gave us a workaround which involves capturing the click and checking or un-checking the checkbox as desired. Have not tired it yet but if it does the job I will post that as well.