Проблема таймеров / жестов касания и удержания в.NET CF
Я столкнулся с некоторым странным поведением в моем приложении.NET CF 2.0, работающем в Windows CE 5.0.
У меня есть таймер, периодически обновляющий элемент управления, который также может получать жесты касания и удержания от пользователя (в обработчике мыши). Я обнаружил, что когда TAH начинается (но до его выхода), может начинаться обработка события таймера, которое опережает обработчик нажатия мыши в середине выполнения.
Насколько мне показало мое исследование, это не нормальное поведение, я просто неправильно понимаю таймеры / события? Может ли быть так, что SHRecognizeGesture вызывает эквивалент Application.DoEvents?
В любом случае, есть ли у кого-нибудь "хороший" способ исправить этот пример, чтобы, когда приложение проверяет TAH, делегат таймера не "тикал".
Ниже приведен пример программы, которая иллюстрирует эту проблему (нажмите и удерживайте пустое место под списком, чтобы сгенерировать сообщения журнала).
Заранее спасибо.
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;
}
}
}
Я не могу связать изображения встроенными, поэтому вот ссылка на скриншот, иллюстрирующий поведение
Изменить: в моем реальном приложении, отметка таймера обновляет элемент управления. Так что я ограничен работой в одном потоке. (Я не могу сделать то, что мне нужно, с помощью обработчиков событий).
4 ответа
Почему бы не установить для свойства Timer Enabled значение false в начале вашего обработчика и вернуться к концу в конце?
Поскольку Timer является таймером WinForms, он работает в том же контексте потока, что и пользовательский интерфейс и все его обработчики (включая обработчик нажатия мыши). Это означает, что в данный момент времени может работать только один. Проблема, которую вы видите, заключается в том, что ваш обработчик мыши выполняет и выполняет другие задачи, которые вы меняете при выполнении.
Вы можете предотвратить это, используя критическую секцию (Монитор). Вы можете установить блокировку вокруг всего обработчика mousedown (или входа монитора в начале и выхода монитора в конце), а затем один в таймере, который блокирует один и тот же объект. Это будет означать, что весь обработчик mousedown должен будет выполняться до того, как запустится процесс таймера, и "тики" таймера фактически встанут в очередь (если вы не использовали Monitor. TryEnter, чтобы специально этого избежать).
Возможным недостатком является то, что обратное также будет правдой - ваш обработчик mousedown никогда не сможет работать, пока не будет завершен какой-либо ожидающий процесс таймера. Независимо от того, является ли это проблемой, будет зависеть от вашего варианта использования, и вы всегда можете смягчить ее в таймере, ища событие или флаг для раннего выхода.
Тем не менее, еще один ответ, который, вероятно, предотвратил бы такое поведение (см. Мой другой ответ для объяснения того, почему вы его видите), состоит в том, чтобы перейти от использования таймера форм к таймеру потоков, чтобы процесс потока находился в отдельном потоке. Если таймер не влияет на пользовательский интерфейс, это должно работать хорошо. Если вы все еще видите конфликт (мы не знаем, что на самом деле делает ваш таймер), изменение приоритета потока в начале таймера на что-то вроде BelowNormal предотвратит зависание процессора в интерфейсе пользователя.
Обойти эту проблему можно, установив логический флаг в общедоступной статической переменной класса (возможно, в стиле singleton). Назовите ее, например, IgnoreTick.
Установите для IgnoreTick значение true в обработчике мыши. В вашем тик-обработчике проверьте значение IgnoreTick. Если это правда, вернись, если не делай то, что делаешь. Вам, конечно, придется добавить обработчик мыши, в котором вы вернете IgnoreTick значение false.