Fucking Date and Time Picker Controls or how to close DateTimePicker programmatically
Только что закончил фикс милейшего дефекта. DateTimePicker вываливал
окно календаря и при щелчках мышью по заголовку родительского окна или в другие
области оно оставалось висеть пока не выбрана дата.
Происходило это потому, что элемент управления DateTimePicker был
создан в потоке отличном от потока окна верхнего уровня, в котором он находится.
Да, знаю, многозадачный UI - это зло, но имеем то, что имеем.
В результате исследования выяснилось, что начиная с Windows Vista этой проблемы больше нет - окно закрывается в любом случае (маленькая поправочка - при использовании 6-й версии библиотеки comctl32). На Windows XP проблема имеет место быть в не зависимости от версии comctl32.
Помимо внедрения механизма, который позволяет перехватывать по запросу асинхронные сообщения со всех UI потоков, возникла еще одна задачка - програмно закрыть окно календаря.
Сначала показалось, что задачка из простых - было найдено сообщение
DTM_CLOSEMONTHCAL,
которое выполняет нужную функцию. После попытки разобраться почему вариант не рабочий
выяснилось, что Minimum supported client - Windows Vista
. А нам для Windows
XP. Блядь-блядь-блядь!!!
Хрен с ним, решение все равно есть. Достаточно лишь понять как работает
DateTimePicker. А он закрывает month calendar при нажатии клавиши Escape,
а также при некоторых манипуляциях мышью. Также следует учесть, что элемент управления
не теряет фокус во время отображения окна month calendar, а само выпадающее окно
никогда не активируется.
Первое, что пришло в голову, - эмулировать нажатие Escape когда month calendar
нужно закрыть програмно - выполнить SendKeys.SendWait("{ESC}");. Вариант
оказался не рабочий. Причина весьма коварна - вызов этого метода запускает вложенный
цикл сообщений и крутит его до тех пор, пока внедренные сообщения о нажатии клавиш
не будут обработаны. А еще DateTimePicker запускает свой цикл сообщений,
когда показывает окно календаря:
user32.dll!_NtUserGetMessage@16() + 0xc bytes comctl32.dll!_DPLBD_MonthCal@8() + 0x244 bytes comctl32.dll!_DPLButtonDown@12() + 0x79 bytes comctl32.dll!_DatePickWndProc@16() + 0x56b bytes user32.dll!_InternalCallWinProc@20() + 0x28 bytes user32.dll!_UserCallWinProcCheckWow@32() + 0xb7 bytes user32.dll!_CallWindowProcAorW@24() + 0x51 bytes user32.dll!_CallWindowProcW@20() + 0x1b bytes System.Windows.Forms.dll!System.Windows.Forms.NativeWindow.DefWndProc(ref System.Windows.Forms.Message m) Line 810 + 0x31 bytes C# System.Windows.Forms.dll!System.Windows.Forms.Control.DefWndProc(ref System.Windows.Forms.Message m) Line 5729 + 0xa bytes C# System.Windows.Forms.dll!System.Windows.Forms.Control.WmMouseDown(ref System.Windows.Forms.Message m, System.Windows.Forms.MouseButtons button, int clicks) Line 12909 + 0xc bytes C# System.Windows.Forms.dll!System.Windows.Forms.Control.WndProc(ref System.Windows.Forms.Message m) Line 13761 C# System.Windows.Forms.dll!System.Windows.Forms.DateTimePicker.WndProc(ref System.Windows.Forms.Message m) Line 1686 C#
В этом цикле содержится логика закрытия окна календаря. Одно из ее составляющих
- ожидание сообщения WM_KEYDOWN с VK_ESC. Сообщение при
этом глотается, до окна оно не доходит. Именно поэтому первая попытка провалилась.
Выход - использовать SendKeys.Send("{ESC}");, неблокирующий вызов,
который не запускает вложенный цикл обработки сообщений, просто вставляет в очередь
необходимые сообщения, которые в последствии обрабатываются циклом сообщений внутри
DPLBD_MonthCal. Мне он тоже не подошел т.к. активировалось другое окно
и оно же получало нажатие Escape. В результате я просто делаю Win32.PostMessage(Handle,
Win32.WM_KEYDOWN, Win32.VK_ESC, IntPtr.Zero); и все пучком. Код стероидов:
/// <summary>
/// Month calendar visibility.
/// </summary>
public bool IsMonthCalendarShown
{
get
{
return (IsHandleCreated
&& Win32.SendMessage(Handle, Win32.DTM_GETMONTHCAL, IntPtr.Zero, IntPtr.Zero) != IntPtr.Zero);
}
}
/// <summary>
/// Closes month calendar if visible.
/// </summary>
/// <remarks>
/// This is guaranteed to work on Windows Vista and above. Under Windows XP this method uses a hack.
/// </remarks>
public void CloseMonthCalendar()
{
if (!IsMonthCalendarShown)
{
return;
}
if (Win32.HasVistaAPI)
{
Win32.SendMessage(Handle, Win32.DTM_CLOSEMONTHCAL, IntPtr.Zero, IntPtr.Zero);
}
else
{
// NOTE: SendKeys.Send[Wait] can't be used here!
// comctl32 runs its own message loop and awaits for WM_KEYDOWN with VK_ESC. It doesn't dispatch that however,
// rather closes the month calendar instead. SendKeys.SendWait calls Application.DoEvents() which
// runs message loop and does dispatch WM_KEY**** messages. So the month calendar is not closed.
// SendKeys.Send might send input to wrong window (one being activated by mouse).
//
// HACK: So the only option here is to post WM_KEYDOWN with VK_ESC.
//
Win32.PostMessage(Handle, Win32.WM_KEYDOWN, Win32.VK_ESC, IntPtr.Zero);
}
}
1 коммент.:
It's difficult to find experienced people for this topic, however, you sound like you know what you're talking about!
Thanks
http://scoaladeit.ro/forums/topic/budget-allowed-nip-relating-to-situation-institutions-will-mean-in-all-likelihood-tuition-singapore-outdoor-hikes/
http://123a1.com/groups/good-things-about-acquiring-a-math-concepts-singapore-tuition-for-a-baby/
my weblog - hand balloons
Отправить комментарий