views:

346

answers:

4

Hey,

Ive encountered some strange behaviour in my .NET CF 2.0 application running on Windows CE 5.0.

I have a timer updating a control periodically, that control can also receive Tap and Hold gestures from the user (in the mouse down handler). What I am finding is that when a TAH begins (but before it exits) a timer event can begin processing which is pre-empting the mouse down handler halfway through execution.

As far as my research has told me, this isn't normal behaviour, am I simply misunderstanding timers / events? Could it just be that SHRecognizeGesture is calling an equivalent to Application.DoEvents?

In any event, does anyone have a "nice" way of fixing this example so that when the app is checking for TAH, the timer delegate doesn't "tick".

See below for a sample program which illustrates this problem (Tap and hold in the empty space below the listbox to generate the log messages).

Thanks in advance.

using System;
using System.Collections.Generic;
using System.Text;
using System.Windows.Forms;
using System.Runtime.InteropServices;

namespace DeviceApplication1
{
    public class BugExample : Control
    {
        [Flags]
        internal enum SHRGFLags
        {
            SHRG_RETURNCMD = 0x00000001,
            SHRG_NOTIFYPARENT = 0x00000002,
            SHRG_LONGDELAY = 0x00000008,
            SHRG_NOANIMATION = 0x00000010,
        }

        [DllImport("aygshell.dll")]
        private extern static int SHRecognizeGesture(ref SHRGINFO shrg);

        private struct SHRGINFO
        {
            public int cbSize;
            public IntPtr hwndClient;
            public int ptDownx;
            public int ptDowny;
            public int dwFlags;
        }

        public bool TapAndHold(int x, int y)
        {
            SHRGINFO shrgi;

            shrgi.cbSize = 20;
            shrgi.hwndClient = this.Handle;
            shrgi.dwFlags = (int)(SHRGFLags.SHRG_RETURNCMD );
            shrgi.ptDownx = x;
            shrgi.ptDowny = y;

            return (SHRecognizeGesture(ref shrgi) > 0);

        }

        protected override void OnMouseDown(MouseEventArgs e)
        {
            base.OnMouseDown(e);

            BugExampleForm parent = (BugExampleForm)this.Parent;

            //The problem is that the parent tick event will fire whilst TapAndHold is running
            //Does TapAndHold perform an equivelant to Application.DoEvents?
            parent.AddLog("Tap Hold - Enter");
            parent.AddLog(String.Format("Tap Hold - Exit - {0}", TapAndHold(e.X, e.Y)));

        }
    }

    public class BugExampleForm : Form
    {
        Timer _timer;
        BugExample _example;
        ListBox _logBox;

        public BugExampleForm()
        {
            _example = new BugExample();
            _example.Dock = DockStyle.Fill;

            _logBox = new ListBox();
            _logBox.Dock = DockStyle.Top;

            _timer = new Timer();
            _timer.Interval = 1000;
            _timer.Enabled = true;
            _timer.Tick += new EventHandler(_timer_Tick);

            this.SuspendLayout();
            this.Text = "Example";
            this.Size = new System.Drawing.Size(200, 300);

            this.Controls.Add(_example);
            this.Controls.Add(_logBox);
            this.ResumeLayout();
        }

        void _timer_Tick(object sender, EventArgs e)
        {
            AddLog("Tick");
        }

        public void AddLog(string s)
        {
            _logBox.Items.Add(s);
            _logBox.SelectedIndex = _logBox.Items.Count - 1;
        }
    }
}

I can't link images inline, so here is a link to a screenshot illustrating the behaviour

Edit: In my actual application, the timer tick is updating the control. So I'm limited to working within the one thread. (I can't really accomplish what I need with event handlers either).

A: 

A workaround to this issue could be to set a boolean flag in a public, static class variable (singleton style perhaps.) Call it IgnoreTick for example.

Set IgnoreTick to true in your mouse down handler. In your tick handler check the value of IgnoreTick. If it's true, return, if not do what you do. You'll of course have to add a mouse up handler within which you'll set IgnoreTick back to false.

Paul Sasik
This is more or less my current workaround. Unfortunately it results in tightly coupling the control to the form and big all caps comments saying "CHECK THIS VALUE IN THESE CIRCUMSTANCES" :)
Josh LOL
A: 

Why not set the Timer's Enabled property to false at the beginning of your handler and back to ture at the end?

ctacke
I assume you mean disabling the timer during the control's mousedown event?This would probably work unless the timer has an event lined up before the mouse handler code runs (although I'm not 100% sure if that can occur). Could someone with a bit more knowledge than I please elaborate?
Josh LOL
There can't be one "lined up" before hand. The Timer you're using is a Forms timer, so it runs it hte context of the UI thread - the same as your mousedown handler. Ony one or the other can run, so if you've entered your mouse handler, the Timer hasn't yet fired. This actually gives me a thought on another answer.
ctacke
A: 

Since the Timer is a WinForms timer it runs in the same thread context as the UI and all UI handlers (including your mouse down handler). This means that only one can ever be running at a given time. The problem you're seeing is because your mouse handler is taking and doing other tasks that you're swapping execution.

You can prevent this from happening using a critical section (Monitor). You can put a lock around the entire mousedown handler (or Monitor enter at the start and monitor exit at the end) and then one in the timer proc that locks on the same object. This would mean that the entire mousedown handler would have to execute before the timer proc could run and the timer "ticks" would actually queue up (unless you used Monitor.TryEnter to specifically avoid that).

The possible down side is that the reverse would also be true - your mousedown handler could never run until any pending timer proc had completed fully. Whether or not this is a problem would be based on your use case, and you could always mitigate it in the timer proc by looking for an event or flag to exit out early.

ctacke
This doesn't quite work (I just tested it), isn't the same thread is being used for the locking in both handlers? Monitor.Enter is re-entrant, so the code just continues execution as per normal. Unless I am misunderstanding what you are saying?
Josh LOL
A: 

Yet anohter answer that would probably prevent this behavior (see my other answer for the explanation as to why you see it) would be to change from using a Forms timer to a Threading timer so the thread proc is on a separate thread. If the timer proc isn't affecting the UI this should work well. If you still see contention (we have no idea what your timer proc really is doing) changing the thread priority at the start of the timer proc to something like BelowNormal would prevent hogging CPU quantum from the UI.

ctacke
In my actual application, the timer tick is updating the control (Ill update the OP to clarify), I understand event handlers would work a little better but I don't really have that luxury for this example. Therefore if I use the Threading.Timer I would have to invoke back to the main thread and we are back at square 1.
Josh LOL