开发者

Timers / Tap and Hold gesture problem in .NET CF

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 SHRecognizeGes开发者_高级运维ture(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 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.


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


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.


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.

0

上一篇:

下一篇:

精彩评论

暂无评论...
验证码 换一张
取 消

最新问答

问答排行榜