Версия для печати темы
Нажмите сюда для просмотра этой темы в оригинальном формате
Форум программистов > C/C++: Системное программирование и WinAPI > Как перехватить нажатие Enter?


Автор: 0x07L 10.6.2006, 22:43
Почему-то в ListView не перехватывается нажатие Enter. Я смотрел нотификационное сообшение LVN_KEYDOWN, использовал subclassing и перехватывал посылаемые ListView сообщения WM_SYSKEYDOWN, WM_KEYDOWN, WM_CHAR, но нажатие Enter нигде обнаружено не было. Интересно, что нажатие Enter в Edit'е успешно перехватывается в WM_KEYDOWN. Может, кто подскажет, что с этим делать? 

Автор: Pulse69 11.6.2006, 06:05
Наш любимый МСДН:

NM_RETURN (list view) Notification

--------------------------------------------------------------------------------

Notifies a list-view control's parent window that the control has the input focus and that the user has pressed the ENTER key. NM_RETURN is sent in the form of a WM_NOTIFY message. 

 

Автор: 0x07L 11.6.2006, 12:14
Это очень странно, но NM_RETURN тоже не перехватывается...

Добавлено @ 12:20 
Вот моя тестовая программка

Код

#include <windows.h>
#include <tchar.h>
#include <commctrl.h>
#include "resource.h"

#pragma comment (lib, "ComCtl32.Lib")

int APIENTRY _tWinMain (HINSTANCE hInstance, HINSTANCE hPrevInstance, LPTSTR strCmdLine, int CmdShow);
LRESULT CALLBACK DlgProc (HWND hDlg, UINT MessageCode, WPARAM wParam, LPARAM lParam);
LRESULT CALLBACK ListViewSubclassWndProc (HWND hListView, UINT MessageCode, WPARAM wParam, LPARAM lParam);

WNDPROC OriginalListViewWndProc;

int APIENTRY _tWinMain (HINSTANCE hInstance, HINSTANCE hPrevInstance, LPTSTR strCmdLine, int CmdShow)
{
    INITCOMMONCONTROLSEX InitComCtl;
    InitComCtl.dwSize = sizeof InitComCtl;
    InitComCtl.dwICC = ICC_LISTVIEW_CLASSES;
    InitCommonControlsEx (&InitComCtl);

    return static_cast<int>(DialogBox(
        hInstance,
        MAKEINTRESOURCE(IDD_DIALOG1),
        0, reinterpret_cast<DLGPROC>(DlgProc)));
}

LRESULT CALLBACK DlgProc (HWND hDlg, UINT MessageCode, WPARAM wParam, LPARAM lParam)
{
    const int LinesInListView = 20;
    const int ColumnsInListView = 15;
    const int ColumnWidth = 35;
    
    const int cchBuffer = 40;
    TCHAR strBuffer [cchBuffer];

    static HWND hListView;
    
    switch (MessageCode)
    {
    case WM_INITDIALOG:
        {

        RECT rcClient;
        GetClientRect (hDlg, &rcClient);
            
        hListView = CreateWindowW (
            WC_LISTVIEW, 0, WS_CHILD | WS_VISIBLE | LVS_REPORT,
            0, 0, rcClient.right, rcClient.bottom,
            hDlg, 0, 0, 0);

        ListView_SetExtendedListViewStyle (hListView, LVS_EX_GRIDLINES);

        LVITEM Item;
        Item.iItem = LinesInListView;
        Item.iSubItem = 0;
        Item.mask = 0;
        
        for (int i=0; i<LinesInListView; i++)
        {
            ListView_InsertItem (hListView, &Item);
        }

        LVCOLUMN Column;
        Column.mask = LVCF_WIDTH;
        Column.cx = ColumnWidth;

        for (int i=0; i<ColumnsInListView; i++)
        {
            ListView_InsertColumn (hListView, ColumnsInListView, &Column);
        }

        ::OriginalListViewWndProc = reinterpret_cast<WNDPROC>(SetWindowLongPtr (
            hListView, GWLP_WNDPROC, reinterpret_cast<LONG_PTR>(ListViewSubclassWndProc)));
        
        return true;

        } // case WM_INITDIALOG

    case WM_NOTIFY:
        {

        LPNMHDR pHeader = reinterpret_cast<LPNMHDR>(lParam);

        if (pHeader->hwndFrom == hListView)
        {
            switch (pHeader->code)
            {
            case LVN_KEYDOWN:
                {

                LPNMLVKEYDOWN pKeyDown = reinterpret_cast<LPNMLVKEYDOWN>(lParam);

                if (pKeyDown->wVKey == VK_RETURN)
                {
                    MessageBox (hDlg, _TEXT("Поймано нажатие ENTER"), _TEXT("LVN_KEYDOWN"), MB_OK | MB_ICONINFORMATION);
                }
                else
                {
                    _stprintf_s (
                        strBuffer, cchBuffer,
                        _TEXT("Нажата клавиша %X"),
                        pKeyDown->wVKey);

                    MessageBox (hDlg, strBuffer, _TEXT("LVN_KEYDOWN"), MB_OK | MB_ICONINFORMATION);
                }

                return true;

                } // case LVN_KEYDOWN

            case NM_RETURN:

                MessageBox (hDlg, _TEXT("Поймано нажатие ENTER"), _TEXT("NM_RETURN"), MB_OK | MB_ICONINFORMATION);
                return true;

            } // switch (pHeader->code)

        } // if (... == hListView)

        return false;

        } // case WM_NOTIFY
    
    case WM_SIZE:

        MoveWindow (hListView, 0, 0, LOWORD(lParam), HIWORD(lParam), true);
        return true;

    case WM_CLOSE:

        DestroyWindow (hDlg);
        return true;

    case WM_DESTROY:

        EndDialog (hDlg, 0);
        return true;

    } // switch (MessageCode

    return false;
}

LRESULT CALLBACK ListViewSubclassWndProc (HWND hListView, UINT MessageCode, WPARAM wParam, LPARAM lParam)
{
    const int cchBuffer = 40;
    TCHAR strBuffer [cchBuffer];
    
    switch (MessageCode)
    {
    case WM_SYSKEYDOWN:

        if (wParam == VK_RETURN)
        {
            if ((lParam >> 28) << (sizeof(lParam)*8-1) == 0)
                // 29ый бит установлен в 0 - Alt не нажат
                // (см. описание WM_SYSKEYDOWN в MSDN)
            {
                MessageBox (hListView, _TEXT("Поймано нажатие ENTER"), _TEXT("WM_SYSKEYDOWN"), MB_OK | MB_ICONINFORMATION);
                return true;
            }
        }
        else
        {
            _stprintf_s (strBuffer, cchBuffer, _TEXT("Нажата клавиша %X"), wParam);

            MessageBox (hListView, strBuffer, _TEXT("WM_SYSKEYDOWN"), MB_OK | MB_ICONINFORMATION);
        }
        break;

    case WM_KEYDOWN:

        if (wParam == VK_RETURN)
        {
            MessageBox (hListView, _TEXT("Поймано нажатие ENTER"), _TEXT("WM_KEYDOWN"), MB_OK | MB_ICONINFORMATION);
            return true;
        }
        else
        {
            _stprintf_s (strBuffer, cchBuffer, _TEXT("Нажата клавиша %X"), wParam);

            MessageBox (hListView, strBuffer, _TEXT("WM_KEYDOWN"), MB_OK | MB_ICONINFORMATION);
        }
        break;
    
    case WM_CHAR:

        if (wParam == _T('\n'))
        {
            MessageBox (hListView, _TEXT("Поймано нажатие ENTER"), _TEXT("WM_CHAR"), MB_OK | MB_ICONINFORMATION);
            return true;
        }
        else
        {
            _stprintf_s (strBuffer, cchBuffer, _TEXT("Нажата клавиша '%C'"), wParam);

            MessageBox (hListView, strBuffer, _TEXT("WM_CHAR"), MB_OK | MB_ICONINFORMATION);
        }
        break;
    }

    return ::OriginalListViewWndProc (hListView, MessageCode, wParam, lParam);
}
 

Автор: Pulse69 11.6.2006, 15:21
У тебя List-View в диалоге, вся проблема именно в этом. Я с таким как-то сталкивался. В MSDN Magazine есть статья примерно на такую тему. Если у тебя VS2005, посмотри эту ссылку:

ms-help://MS.MSDNQTR.v80.en/MS.MSDN.v80/dnmag00/html/c0700.htm

Суть в том, что мне пришлось создавать таблицу акселераторов с VK_RETURN, а затем обрабатывать сообщение от акселератора. По-другому я не знаю как.

PS: Xотя нет, знаю. Не пиши для диалога =) 

Автор: 0x07L 11.6.2006, 15:53
Спасибо большое! 

Автор: 0x07L 15.6.2006, 16:46
Кстати, в MFC, оказывается, перехватить нажатие Enter еще сложнее, потому что диалоговое окно при его нажатии закрывается  smile 
Даже, если нет кнопки по кмолчанию... 

Автор: Earnest 15.6.2006, 18:39
В MFC как раз проще - там есть волшебная функция PreTranslateMessage. 

Автор: 0x07L 15.6.2006, 19:13
Буду знать 

Автор: 0x07L 7.7.2006, 19:22
Есть решение проблемы! Оказывается, существует сообщение WM_GETDLGCODE. Оно посылается функции окна элемента управления. Возвращенное из функции окна значение говорит, какие клавиши ты хочешь обрабатывать сам. Windows при нажатии этих клавиш не будет сам ничего делать (кроме как ставить очередное сообщение в очередь). С моим ListView это проходит! 

Автор: 0x07L 7.7.2006, 19:40
Обнаружилась странная вещь
Код


// ===============================================
//
// (добавлено 11.07.2006)
//
// Код смотреть не рекомендуется, поскольку ошибка
// Дополнительную инфу смотри в обработчике WM_GETDLGCODE
//
// ===============================================

#include <windows.h>
#include <tchar.h>

#include "resource.h"

int APIENTRY _tWinMain(HINSTANCE, HINSTANCE, LPTSTR, int);
LRESULT CALLBACK DialogProc(HWND, UINT, WPARAM, LPARAM);

int APIENTRY _tWinMain(HINSTANCE hInstance, HINSTANCE hPrevInstance, LPTSTR CmdLine, int CmdShow)
{
    return static_cast<int>(DialogBox(hInstance, MAKEINTRESOURCE(IDD_DIALOG), 0, reinterpret_cast<DLGPROC>(DialogProc)));
}

LRESULT CALLBACK DialogProc(HWND hDialog, UINT Message, WPARAM wParam, LPARAM lParam)
{
    switch(Message)
    {
    case WM_INITDIALOG:

        return TRUE;

    case WM_GETDLGCODE:

// ===============================================
//
// (добавлено 11.07.2006)
//
// Ошибка: ну ка скажите мне,
// как возвращать значение
// из функции диалогового окна :-)
//
// ===============================================

        return DLGC_WANTALLKEYS;

    case WM_KEYDOWN:

        if (wParam /* virtual key code */ == VK_RETURN)
        {
            Beep(1500, 50);
        }
        return TRUE;

    case WM_CLOSE:

        DestroyWindow(hDialog);
        return TRUE;

    case WM_DESTROY:

        EndDialog(hDialog, 0);
        return TRUE;
    }
    return FALSE;
}


Функции диалогового окна, несмотря на то что она не есть "the window procedure associated with a control" (MSDN), WM_GETDLGCODE все равно приходит. Правда, толку от него мало: Enter в диалоговом окне не ловится smile  

Автор: Earnest 10.7.2006, 17:36
Ты немного не так понял. Существует стандартная обработка некоторых клавиш в диалоге
Цитата

the system interprets certain types of keyboard input as dialog box navigation keys

Но если в оконной функции контрола в ответ на WM_GETDLGCODE возвратить определенные значения, то это стандартное поведение можно изменить. 

А ENTER попробуй ловить как WM_CHAR с соответствующим ASCII-кодом, а не WM_KEYDOWN. 

Автор: 0x07L 10.7.2006, 22:25
Глупый вопрос: а как сделать так, чтобы в функцию диалога приходил WM_CHAR? 

Автор: DeadSoul 10.7.2006, 22:29
Цитата(0x07L @  10.7.2006,  22:25 Найти цитируемый пост)
Глупый вопрос: а как сделать так, чтобы в функцию диалога приходил WM_CHAR?  

Создать обработчик этого сообщения? 

Автор: 0x07L 10.7.2006, 22:32
Обработчик-то есть:
Код


// ===============================================
//
// (добавлено 11.07.2006)
//
// Код смотреть не рекомендуется, поскольку ошибка
// Дополнительную инфу смотри во вставке кода
// в моем предыдущем посте
//
// ===============================================

#include <windows.h>
#include <tchar.h>

#include "resource.h"

int APIENTRY _tWinMain(HINSTANCE, HINSTANCE, LPTSTR, int);
LRESULT CALLBACK DialogProc(HWND, UINT, WPARAM, LPARAM);

int APIENTRY _tWinMain(HINSTANCE hInstance, HINSTANCE hPrevInstance, LPTSTR CmdLine, int CmdShow)
{
    return static_cast<int>(DialogBox(hInstance, MAKEINTRESOURCE(IDD_DIALOG), 0, reinterpret_cast<DLGPROC>(DialogProc)));
}

LRESULT CALLBACK DialogProc(HWND hDialog, UINT Message, WPARAM wParam, LPARAM lParam)
{
    switch(Message)
    {
    case WM_INITDIALOG:

        return TRUE;

    case WM_GETDLGCODE:

        return DLGC_WANTALLKEYS;

    case WM_KEYDOWN:

        if (wParam /* virtual key code */ == VK_RETURN)
        {
            Beep(1500, 50);
        }
        return TRUE;

    case WM_CHAR:

        if (wParam /* ASCII code */ == _T('\n'))
        {
            Beep(500, 50);
        }
        return TRUE;

    case WM_CLOSE:

        DestroyWindow(hDialog);
        return TRUE;

    case WM_DESTROY:

        EndDialog(hDialog, 0);
        return TRUE;
    }
    return FALSE;
}
  

Автор: DeadSoul 10.7.2006, 22:40
А WM_KEYDOWN тоже не ловится? Spy++ смотрел какие сообщения приходят? 

Автор: 0x07L 10.7.2006, 22:47
Что за чушь? В Spy есть WM_KEYDOWN, WM_CHAR, WM_KEYUP. Причем нажатие Enter там тоже видно  smile

Добавлено @ 22:49 
А, кстати, при нажатии на Enter не приходит WM_CHAR  smile

Добавлено @ 22:57 
Вот как (засунуть это в обработчик WM_GETDLGCODE):
Код

LPMSG pMsg = reinterpret_cast<LPMSG>(lParam);
if (pMsg && pMsg->message == WM_KEYDOWN && pMsg->wParam == VK_RETURN)
{
    Beep(2000, 50);
}
[S]return DLGC_WANTALLKEYS | DLGC_WANTCHARS | DLGC_WANTMESSAGE[/S];
  

Автор: 0x07L 11.7.2006, 00:10
Дописал я тут свою программку
Код

#include <windows.h>
#include <tchar.h>

#include "resource.h"

int APIENTRY _tWinMain(HINSTANCE, HINSTANCE, LPTSTR, int);
LRESULT CALLBACK DialogProc(HWND, UINT, WPARAM, LPARAM);

int APIENTRY _tWinMain(HINSTANCE hInstance, HINSTANCE hPrevInstance, LPTSTR CmdLine, int CmdShow)
{
    return static_cast<int>(DialogBox(hInstance, MAKEINTRESOURCE(IDD_DIALOG), 0, reinterpret_cast<DLGPROC>(DialogProc)));
}

LRESULT CALLBACK DialogProc(HWND hDialog, UINT Message, WPARAM wParam, LPARAM lParam)
{
    const int BufferSize = 120;
    TCHAR Buffer [BufferSize];
    
    switch(Message)
    {
    case WM_INITDIALOG:

        return TRUE;

    case WM_GETDLGCODE:
        {
            LPMSG pMsg = reinterpret_cast<LPMSG>(lParam);
            
            if (pMsg)
            {
                _stprintf_s(Buffer, BufferSize, _TEXT(
                    "Received:\tWM_GETDLGCODE\nMessage:\t\t%d (dec)\nwParam:\t\t%p (hex)"),
                    pMsg->message, pMsg->wParam);

                MessageBox(hDialog, Buffer, _TEXT("Catch ENTER in dialog"), MB_OK | MB_ICONINFORMATION);
            }
            else
            {
                Beep(1500, 50);
            }
        }
        SetWindowLongPtr(hDialog, DWLP_MSGRESULT, DLGC_WANTALLKEYS);
        return TRUE;

    case WM_KEYDOWN:

        _stprintf_s(Buffer, BufferSize, _TEXT(
            "Received:\tWM_KEYDOWN\nwParam:\t\t%p (hex)"),
            wParam);

        MessageBox(hDialog, Buffer, _TEXT("Catch ENTER in dialog"), MB_OK | MB_ICONINFORMATION);

        return TRUE;

    case WM_CHAR:

        _stprintf_s(Buffer, BufferSize, _TEXT(
            "Received:\tWM_CHAR\nwParam:\t\t%p (hex)"),
            wParam);

        MessageBox(hDialog, Buffer, _TEXT("Catch ENTER in dialog"), MB_OK | MB_ICONINFORMATION);

        return TRUE;

    case WM_KEYUP:

        _stprintf_s(Buffer, BufferSize, _TEXT(
            "Received:\tWM_KEYUP\nwParam:\t\t%p (hex)"),
            wParam);

        MessageBox(hDialog, Buffer, _TEXT("Catch ENTER in dialog"), MB_OK | MB_ICONINFORMATION);

        return TRUE;

    case WM_CLOSE:

        DestroyWindow(hDialog);
        return TRUE;

    case WM_DESTROY:

        EndDialog(hDialog, 0);
        return TRUE;
    }
    return FALSE;
}


Теперь все работает. Рзве что почему-то не приходит WM_KEYUP  smile 

Кстати, WM_GETDLGCODE, сообщающий о нажатии Enter, приходит всегда (независимо от того, что возвращено в обработчике). Так что буду юзать WM_GETDLGCODE.

PS Прошу извинить меня за предыдущий пост. Так торопился, что забыл, что должна возвращать функция диалогового окна.   

Автор: 0x07L 11.7.2006, 00:28
Никогда не думал, что Windows тоже может посылать сообщения с таймаутом. Он как раз и делает так с WM_KEYUP. Из-за моих MessageBox'ов (да и из-за отладчика) таймаут просто уже проходит.  smile  

Автор: Earnest 11.7.2006, 08:12
Не из-за тайм-аутов, а из-за переключения окна: сообщения ввода (клавиатуры и мыши) всегда приходят активному окну. Т.е. WM_KEYDOWN приходит твоему диалогу, а WM_KEYUP - уже MessageBox. Или отладчику. Тоже самое будет с WM_BUTTONDOWN \ UP.  

Так все-таки ENTER в WM_KEYDOWN приходит или нет? Или ты получаешь его только через lParam
от WM_GETDLGCODE? Я с чистым API давно не работала, так что интересно... Похоже, его функция TranslateMessage во что-то преобразует...
 

Автор: 0x07L 11.7.2006, 09:26
Ответы на предыдущие посты

Enter приходит и с WM_GETDLGCODE, и с WM_KEYDOWN, и с WM_CHAR, но, как я уже говорил, без WM_KEYUP.

Насчет переключения окна. Исправил свою программку, добавив перед каждым MessageBox'ом вызов Sleep(500):

Код

#include <windows.h>
#include <tchar.h>

#include "resource.h"

int APIENTRY _tWinMain(HINSTANCE, HINSTANCE, LPTSTR, int);
LRESULT CALLBACK DialogProc(HWND, UINT, WPARAM, LPARAM);

int APIENTRY _tWinMain(HINSTANCE hInstance, HINSTANCE hPrevInstance, LPTSTR CmdLine, int CmdShow)
{
    return static_cast<int>(DialogBox(hInstance, MAKEINTRESOURCE(IDD_DIALOG), 0, reinterpret_cast<DLGPROC>(DialogProc)));
}

LRESULT CALLBACK DialogProc(HWND hDialog, UINT Message, WPARAM wParam, LPARAM lParam)
{
    const int BufferSize = 120;
    TCHAR Buffer [BufferSize];
    
    switch(Message)
    {
    case WM_INITDIALOG:

        return TRUE;

    case WM_GETDLGCODE:
        {
            LPMSG pMsg = reinterpret_cast<LPMSG>(lParam);
            
            if (pMsg)
            {
                Sleep(500);
                
                _stprintf_s(Buffer, BufferSize, _TEXT(
                    "Received:\tWM_GETDLGCODE\nMessage:\t\t%d (dec)\nwParam:\t\t%p (hex)"),
                    pMsg->message, pMsg->wParam);

                MessageBox(hDialog, Buffer, _TEXT("Catch ENTER in dialog"), MB_OK | MB_ICONINFORMATION);
            }
            else
            {
                Beep(1500, 50);
            }
        }
        SetWindowLongPtr(hDialog, DWLP_MSGRESULT, DLGC_WANTALLKEYS | DLGC_WANTCHARS);
        return TRUE;

    case WM_CHAR:

        Sleep(500);
        
        _stprintf_s(Buffer, BufferSize, _TEXT(
            "Received:\tWM_CHAR\nwParam:\t\t%p (hex)"),
            wParam);

        MessageBox(hDialog, Buffer, _TEXT("Catch ENTER in dialog"), MB_OK | MB_ICONINFORMATION);

        return TRUE;

    case WM_KEYDOWN:

        Sleep(500);
        
        _stprintf_s(Buffer, BufferSize, _TEXT(
            "Received:\tWM_KEYDOWN\nwParam:\t\t%p (hex)"),
            wParam);

        MessageBox(hDialog, Buffer, _TEXT("Catch ENTER in dialog"), MB_OK | MB_ICONINFORMATION);

        return TRUE;

    case WM_KEYUP:

        Sleep(500);

        _stprintf_s(Buffer, BufferSize, _TEXT(
            "Received:\tWM_KEYUP\nwParam:\t\t%p (hex)"),
            wParam);

        MessageBox(hDialog, Buffer, _TEXT("Catch ENTER in dialog"), MB_OK | MB_ICONINFORMATION);

        return TRUE;

    case WM_CLOSE:

        DestroyWindow(hDialog);
        return TRUE;

    case WM_DESTROY:

        EndDialog(hDialog, 0);
        return TRUE;
    }
    return FALSE;
}

Таким образом добиваемся того, что клавиша отжимается до того, как показывается MessageBox. Между тем сообщения WM_KEYUP как не было, так и нет (и в Spy++ его тоже не видно).

Новые наблюдения

1)

Опытным путем установлено, что если закомментировать Sleep и MessageBox в обработчике WM_KEYDOWN, не приходит не только WM_KEYUP, но и WM_CHAR (в Spy++ последний, однако, виден):

Код

#include <windows.h>
#include <tchar.h>

#include "resource.h"

int APIENTRY _tWinMain(HINSTANCE, HINSTANCE, LPTSTR, int);
LRESULT CALLBACK DialogProc(HWND, UINT, WPARAM, LPARAM);

int APIENTRY _tWinMain(HINSTANCE hInstance, HINSTANCE hPrevInstance, LPTSTR CmdLine, int CmdShow)
{
    return static_cast<int>(DialogBox(hInstance, MAKEINTRESOURCE(IDD_DIALOG), 0, reinterpret_cast<DLGPROC>(DialogProc)));
}

LRESULT CALLBACK DialogProc(HWND hDialog, UINT Message, WPARAM wParam, LPARAM lParam)
{
    const int BufferSize = 120;
    TCHAR Buffer [BufferSize];
    
    switch(Message)
    {
    case WM_INITDIALOG:

        return TRUE;

    case WM_GETDLGCODE:
        {
            LPMSG pMsg = reinterpret_cast<LPMSG>(lParam);
            
            if (pMsg)
            {
                Sleep(500);
                
                _stprintf_s(Buffer, BufferSize, _TEXT(
                    "Received:\tWM_GETDLGCODE\nMessage:\t\t%d (dec)\nwParam:\t\t%p (hex)"),
                    pMsg->message, pMsg->wParam);

                MessageBox(hDialog, Buffer, _TEXT("Catch ENTER in dialog"), MB_OK | MB_ICONINFORMATION);
            }
            else
            {
                Beep(1500, 50);
            }
        }
        SetWindowLongPtr(hDialog, DWLP_MSGRESULT, DLGC_WANTALLKEYS | DLGC_WANTCHARS);
        return TRUE;

    case WM_CHAR:

        Sleep(500);
        
        _stprintf_s(Buffer, BufferSize, _TEXT(
            "Received:\tWM_CHAR\nwParam:\t\t%p (hex)"),
            wParam);

        MessageBox(hDialog, Buffer, _TEXT("Catch ENTER in dialog"), MB_OK | MB_ICONINFORMATION);

        return TRUE;

    case WM_KEYDOWN:

        /*Sleep(500);
        
        _stprintf_s(Buffer, BufferSize, _TEXT(
            "Received:\tWM_KEYDOWN\nwParam:\t\t%p (hex)"),
            wParam);

        MessageBox(hDialog, Buffer, _TEXT("Catch ENTER in dialog"), MB_OK | MB_ICONINFORMATION);*/

        return TRUE;

    case WM_KEYUP:

        Sleep(500);

        _stprintf_s(Buffer, BufferSize, _TEXT(
            "Received:\tWM_KEYUP\nwParam:\t\t%p (hex)"),
            wParam);

        MessageBox(hDialog, Buffer, _TEXT("Catch ENTER in dialog"), MB_OK | MB_ICONINFORMATION);

        return TRUE;

    case WM_CLOSE:

        DestroyWindow(hDialog);
        return TRUE;

    case WM_DESTROY:

        EndDialog(hDialog, 0);
        return TRUE;
    }
    return FALSE;
}

Замечу, что WM_KEYDOWN приходит (ставил точку останова)

2)

Если же вдобавок к этому закомментировать WM_GETDLGCODE, то посылается только WM_KEYUP:

Код

#include <windows.h>
#include <tchar.h>

#include "resource.h"

int APIENTRY _tWinMain(HINSTANCE, HINSTANCE, LPTSTR, int);
LRESULT CALLBACK DialogProc(HWND, UINT, WPARAM, LPARAM);

int APIENTRY _tWinMain(HINSTANCE hInstance, HINSTANCE hPrevInstance, LPTSTR CmdLine, int CmdShow)
{
    return static_cast<int>(DialogBox(hInstance, MAKEINTRESOURCE(IDD_DIALOG), 0, reinterpret_cast<DLGPROC>(DialogProc)));
}

LRESULT CALLBACK DialogProc(HWND hDialog, UINT Message, WPARAM wParam, LPARAM lParam)
{
    const int BufferSize = 120;
    TCHAR Buffer [BufferSize];
    
    switch(Message)
    {
    case WM_INITDIALOG:

        return TRUE;

    case WM_GETDLGCODE:
        {
            LPMSG pMsg = reinterpret_cast<LPMSG>(lParam);
            
            if (pMsg)
            {
                /*Sleep(500);
                
                _stprintf_s(Buffer, BufferSize, _TEXT(
                    "Received:\tWM_GETDLGCODE\nMessage:\t\t%d (dec)\nwParam:\t\t%p (hex)"),
                    pMsg->message, pMsg->wParam);

                MessageBox(hDialog, Buffer, _TEXT("Catch ENTER in dialog"), MB_OK | MB_ICONINFORMATION);*/
            }
            else
            {
                Beep(1500, 50);
            }
        }
        SetWindowLongPtr(hDialog, DWLP_MSGRESULT, DLGC_WANTALLKEYS | DLGC_WANTCHARS);
        return TRUE;

    case WM_CHAR:

        Sleep(500);
        
        _stprintf_s(Buffer, BufferSize, _TEXT(
            "Received:\tWM_CHAR\nwParam:\t\t%p (hex)"),
            wParam);

        MessageBox(hDialog, Buffer, _TEXT("Catch ENTER in dialog"), MB_OK | MB_ICONINFORMATION);

        return TRUE;

    case WM_KEYDOWN:

        /*Sleep(500);
        
        _stprintf_s(Buffer, BufferSize, _TEXT(
            "Received:\tWM_KEYDOWN\nwParam:\t\t%p (hex)"),
            wParam);

        MessageBox(hDialog, Buffer, _TEXT("Catch ENTER in dialog"), MB_OK | MB_ICONINFORMATION);*/

        return TRUE;

    case WM_KEYUP:

        Sleep(500);

        _stprintf_s(Buffer, BufferSize, _TEXT(
            "Received:\tWM_KEYUP\nwParam:\t\t%p (hex)"),
            wParam);

        MessageBox(hDialog, Buffer, _TEXT("Catch ENTER in dialog"), MB_OK | MB_ICONINFORMATION);

        return TRUE;

    case WM_CLOSE:

        DestroyWindow(hDialog);
        return TRUE;

    case WM_DESTROY:

        EndDialog(hDialog, 0);
        return TRUE;
    }
    return FALSE;
}

Замечу, что WM_GETDLGCODE и WM_KEYDOWN здесь приходят (ставил точку останова). Что неприятно, с включенной точкой останова WM_KEYUP не посылается (таймаут или переключение окна?). В Spy++ видны WM_KEYDOWN, WM_CHAR (что же он до меня-то не доходит?) и, соответственно, WM_KEYUP.

3)

Если закомментировать WM_GETDLGCODE, а WM_KEYDOWN оставить, то все проходит, как будто комментариев нет вообще.
В смысле, как в изначальном варианте (в том, который приведен в первой вставке в данном посте)

Примечание: результаты приводятся для символьной клавиши (если говорить точнее, клавиши I)

Выводы из наблюдений и вопросы

Не слишком ясная картина, не так ли. Особенно возмутил меня пункт (1). Каким образом комментарии в WM_KEYDOWN влияют на WM_CHAR? Возникает нахальная мысль: может это проделки Visual Studio?

Если кто-нибудь поймет, в чем дело, напишите. Мне это хоть и надоело, но интерес еще остался.

Искренне надеюсь, что не напортачил очередной раз.

А, чуть не забыл. Может кто-нибудь откомпилить мой код, дабы проверить результаты? 

Автор: Earnest 11.7.2006, 15:55
Компилировать и проверять, честно говоря, в лом, да и некогда, но вот еще пара вопросов:
1) Что будет, если возвращать в WM_KEYDOWN FALSE?
2) Сделать "чистый тест" поступления сообщений, без MessageBox и точек останова, а просто выводить диагностику с помощью OutputDebugString.

Нужно добавить, что картина не совсем стандартная, но и цикл обработки сообщений у тебя непонятно какой: стандартного цикла GetMessage - TranslateMessage - DispatchMessage нет, а что там Windows делает внутри DialogBox - хто знает.
Самое интересное, конечно, это исчезновение WM_KEYUP... Просто удивительно... 

Автор: 0x07L 11.7.2006, 18:13
1) Пробовал возвращать FALSE. Ничего от этого не меняется.

2) Чистый тест поступления сообщений

Новая тестовая программка:
Код

// ============================================================================

//#define _ENABLE_SLEEP_
//#define _ENABLE_MESSAGE_BOX_
#define _ENABLE_OUTPUT_DEBUG_STRING_

// ============================================================================

#include <windows.h>
#include <tchar.h>

#include "resource.h"

#ifdef _ENABLE_SLEEP_
#define _SLEEP_IF_DEFINED_ Sleep(500);
#else
#define _SLEEP_IF_DEFINED_ ;
#endif

#if defined (_ENABLE_MESSAGE_BOX_) || defined (_ENABLE_OUTPUT_DEBUG_STRING_)
#define _FILL_BUFFER_IF_DEFINED_
#endif

#ifdef _ENABLE_MESSAGE_BOX_
#define _SHOW_MESSAGE_BOX_IF_DEFINED_ MessageBox(hDialog, Buffer, _TEXT("Catch ENTER in dialog"), MB_OK | MB_ICONINFORMATION);
#else
#define _SHOW_MESSAGE_BOX_IF_DEFINED_ ;
#endif

#ifdef _ENABLE_OUTPUT_DEBUG_STRING_
#define _OUTPUT_DEBUG_STRING_IF_DEFINED_ OutputDebugString(Buffer);
#else
#define _OUTPUT_DEBUG_STRING_IF_DEFINED_ ;
#endif

int APIENTRY _tWinMain(HINSTANCE, HINSTANCE, LPTSTR, int);
LRESULT CALLBACK DialogProc(HWND, UINT, WPARAM, LPARAM);

int APIENTRY _tWinMain(HINSTANCE hInstance, HINSTANCE hPrevInstance, LPTSTR CmdLine, int CmdShow)
{
    return static_cast<int>(DialogBox(hInstance, MAKEINTRESOURCE(IDD_DIALOG), 0, reinterpret_cast<DLGPROC>(DialogProc)));
}

LRESULT CALLBACK DialogProc(HWND hDialog, UINT Message, WPARAM wParam, LPARAM lParam)
{
#ifdef _FILL_BUFFER_IF_DEFINED_

    const int BufferSize = 120;
    TCHAR Buffer [BufferSize];

#endif
    
    switch(Message)
    {

    case WM_INITDIALOG:

        return TRUE;

    case WM_GETDLGCODE:
        {
            LPMSG pMsg = reinterpret_cast<LPMSG>(lParam);
            
            if (pMsg)
            {

                _SLEEP_IF_DEFINED_
                
#ifdef _FILL_BUFFER_IF_DEFINED_

                _stprintf_s(Buffer, BufferSize, _TEXT(
                    "Received:     WM_GETDLGCODE\nMessage:      %d (dec)\nwParam:       %p (hex)\n\n"),
                    pMsg->message, pMsg->wParam);
#endif
                _SHOW_MESSAGE_BOX_IF_DEFINED_
                _OUTPUT_DEBUG_STRING_IF_DEFINED_
            }
            else
            {
                Beep(1500, 50);
            }
        }
        SetWindowLongPtr(hDialog, DWLP_MSGRESULT, DLGC_WANTALLKEYS | DLGC_WANTCHARS);
        return TRUE;

    case WM_CHAR:

        _SLEEP_IF_DEFINED_
                
#ifdef _FILL_BUFFER_IF_DEFINED_

        _stprintf_s(Buffer, BufferSize, _TEXT(
            "Received:     WM_CHAR\nwParam:       %p (hex)\n\n"),
            wParam);
#endif
        _SHOW_MESSAGE_BOX_IF_DEFINED_
        _OUTPUT_DEBUG_STRING_IF_DEFINED_
        return TRUE;

    case WM_KEYDOWN:

        _SLEEP_IF_DEFINED_

#ifdef _FILL_BUFFER_IF_DEFINED_
        
        _stprintf_s(Buffer, BufferSize, _TEXT(
            "Received:     WM_KEYDOWN\nwParam:       %p (hex)\n\n"),
            wParam);
#endif
        _SHOW_MESSAGE_BOX_IF_DEFINED_
        _OUTPUT_DEBUG_STRING_IF_DEFINED_
        return TRUE;

    case WM_KEYUP:

        _SLEEP_IF_DEFINED_

#ifdef _FILL_BUFFER_IF_DEFINED_

        _stprintf_s(Buffer, BufferSize, _TEXT(
            "Received:     WM_KEYUP\nwParam:       %p (hex)\n\n"),
            wParam);
#endif

        _SHOW_MESSAGE_BOX_IF_DEFINED_
        _OUTPUT_DEBUG_STRING_IF_DEFINED_
        return TRUE;

    case WM_CLOSE:

        DestroyWindow(hDialog);
        return TRUE;

    case WM_DESTROY:

        EndDialog(hDialog, 0);
        return TRUE;
    }
    return FALSE;
}

Прошу извинить на низкую читабельность кода.

В этот раз все просто. Комментируй WM_KEYDOWN или нет, получаем следующее:

В окошке Output:
Код

Received:     WM_GETDLGCODE
Message:      256 (dec)
wParam:       00000049 (hex)

Received:     WM_KEYDOWN
wParam:       00000049 (hex)

Received:     WM_KEYUP
wParam:       00000049 (hex)

В Spy++:
Код

WM_KEYDOWN
WM_CHAR
WM_KEYUP

Что тут сказать? Опять пропал WM_CHAR smile 

Ради эксперимента вновь включил MessageBox'ы. Получил следующее

В окошке Output и в MessageBox'ах
Код

Received:     WM_GETDLGCODE
Message:      256 (dec)
wParam:       00000049 (hex)

Received:     WM_CHAR
wParam:       00000069 (hex)

Received:     WM_KEYDOWN
wParam:       00000049 (hex)

В Spy++
Код

WM_KEYDOWN
WM_CHAR

Как я понял, WM_CHAR и WM_KEYUP не могут существовать вместе smile (разве что в случае, описанном несколько постов назад)

3) 
Цитата(Earnest @  11.7.2006,  16:55 Найти цитируемый пост)
 но и цикл обработки сообщений у тебя непонятно какой: стандартного цикла GetMessage - TranslateMessage - DispatchMessage нет, а что там Windows делает внутри DialogBox - хто знает

Мы в данном топике рассматриваем особенности диалоговых окон. В обычном окне все должно быть OK, поскольку там нет этой самой обработки клавиш по умолчанию. С немодальном диалоговом окном я попробовал. Если применять IsDislogMessage, ничего не меняется. Если обходится без IsDialogMessage, окно функционирует как обычное, не диалоговое (приходят, как и должно быть, WM_KEYDOWN, WM_CHAR, WM_KEYUP). WM_GETDLGCODE не приходит.

Если включить MessageBox'ы, то и в окошке Output, и в MessageBox'ах, и в Spy++:
Код

Received:     WM_CHAR
wParam:       00000069 (hex)

Received:     WM_KEYDOWN
wParam:       00000049 (hex)

Есть что-то общее с диалоговыми окнами, не правда ли?

4) Общие вопросы и предложения

Earnest, спасибо за OutputDebugString. Про эту функцию я тоже не знал.

Кто знает, почему Spy++ не поддерживает помещение текста в буфер обмена? Такая полезная возможность была бы.

Вопрос. Как отлаживать код, подобный моему (с макросами)?     

Автор: Earnest 11.7.2006, 19:06
0x07L, молодец, вот уж не поленился!

Цитата(0x07L @  11.7.2006,  19:13 Найти цитируемый пост)
Вопрос. Как отлаживать код, подобный моему (с макросами)?  

А в чем, собственно, вопрос? Я вижу только одну проблему: отладочные макросы сильно замусоривают код... Лучше, конечно, делать их более незаметными.

Во-первых, нет смысла выводить отладочную информацию в MessageBox - это всегда нарушает нормальный ход выполнения (переключение активных окон и т.д.). От использования Sleep - тоже польза сомнительная (разве что с разными потоками работаешь). Т.е. для вывода достаточно трассировочных сообщений. Приходим к следующему:

Код

// чтобы отключить отладочный вывод, нужно эту строчку закомментировать
#define DUMP_ENABLED
#ifdef DUMP_ENABLED
   #define DEBUG_DUMP   TRACE
#else
   #define DEBUG_DUMP __noop
#endif

// Здесь я использую MFC-шный макрос TRACE, который в конце концов превращается 
// в OutputDebugString; если не использовать MFC, несложно написать аналог, чтобы сразу иметь 
// форматированный вывод, без всяких буферов

// далее, использование

 ...
 case WM_KEYDOWN:
     DEBUG_DUMP (_TEXT("Received:     WM_KEYDOWN\nwParam:       %p (hex)\n\n"),  wParam);
     // делаем что-то полезное
     ...
     return TRUE;


При желании все же иметь MessageBox, можно эту возможность включить в функцию, которая выполняет DEBUG_DUMP. Если отладкой управляют несколько макросов (разными опциями) их реализацию нужно выносить в вспомогательную функцию:

Код

void DoDebugDump (LPCTSTR psFmt, ...)
{
   va_list args; 
   va_start (args, psFmt);

   CString buf;
   buf.FormatV (psFmt, args);
   va_end (args);

   #ifdef USE_MESSAGE_BOX
      MessaegBox(...buf ...);
   #endif

   OutputDebugString (buf); 

   // возможно, что-то еще,... вывод в файл, например
}

#define DEBUG_DUMP DoDebugDump

Коротко, смысл в следущем. 
Никаких #ifdef\#endif внутри полезного кода.
Никаких временных переменных для отладки. Если в них все же возникает необходимость, написать вспомогательные функции, классы и т.д., за пределами полезного кода.
Отладочный макрос должен записываться одним предложением, желательно покороче.
И т.д. 
Тогда полезный код сохраняет прозрачность, отладка включается \ отключается легко и очевидно. И проблем с отладкой не возникает. 

 

Автор: 0x07L 11.7.2006, 19:21
Здорово!

А я тут заметил еще момент. Флажок DLGC_WANTCHARS (один из тех, которые мы возвращаем из обработчика WM_GETDLGCODE) вообще не играет роли. Если его не указывать, все остается так, как было. Интересно, почему в MSDN об этом не упоминается. 

Автор: Earnest 11.7.2006, 19:51
Да? А если вообще GetDlgCode не обрабатывать, тоже? По идее, вроде, должно.
Т.е. меня изначально удивило, что в диалоге что-то меняется от наличия\отсутствия обработчика GETDLGCODE. А в MSDN это как раз написано, но не прямо: GETDLGCODE стандартно имеет смысл для контролов, диалог по-умолчанию вроде должен получать весь ввод (который контролы схватить не успели). 
Только почему-то у тебя в самом начале вроде не получалось получить ENTER. Я ранний код не очень внимательно смотрела (большой  smile ), так в чем там разница? 

Автор: 0x07L 11.7.2006, 20:01
Если не использовать в обработчике WM_GETDLGCODE SetWindowLong(...MSGRESULT...WANTALLKEYS...), то Enter будет приходить только через lParam этого самого WM_GETDLGCODE и больше его не будет видно нигде. 

Автор: Earnest 11.7.2006, 20:22
Понятно, т.е. WANTALLKEYS нужно, а WANTCHARS нет...

Интересно, откуда все же разница между выводом Spy и твоей DialogProc? По идее, Spy подключает хук к процессу и выводит все проходящие сообщения. Остается только предположить, что внутри цикла DialogBox происходит какая-то трансляция сообщений. Т.е. Spy, видимо, подключается непосредственно к очереди, а в диалоговую процедуру попадают уже процеженные сообщения ... 

Автор: 0x07L 11.7.2006, 20:36
Причем сообщения могут не только процеживаться, но еще и добавляться (WM_GETDLGCODE). Без SendMessage и PostMessage, кстати.

Что мы имеем:
1) WM_KEYUP не посылается из-за переключения окна или истечения времени ожидания.
2) Что происходит с WM_CHAR, непонятно. Ясно только то, что с ним что-то делает цикл обработки сообщений диалогового окна 

Автор: diverkaz 26.11.2007, 15:19
У меня тоже проблема с Enter  и ListView, почитал эту тему и попробова... И в результате он у меня в WM_GETDLGCODE ВОБЩЕ не заходит.. подскажите в чем проблемма... может у диалога стиль кокойнить особый нужен... 
Пробовал даже копиваровать ваш исходник... но все равно не заходит ни в WM_GETDLGCODE ни в WM_KEYDOWN?

Автор: AndreyK 26.11.2007, 16:33
У меня была та-же проблема, но с другими элементами - они сначала сами обрабатывают свои сообщения, а потом только соизволяют передать (ненужное), обработчику.

Я эту проблему решил, при помощи перехвата всех сообщений в программе (SetWindowsHook).
При старте диалога я включаю режим фильтрации и сравниваю хендл окна, если совпадает - обрабатываю.

Код

int CALLBACK FilterFunc(int nCode,WORD wParam,DWORD lParam)
{
    LPMSG lpmsg = (LPMSG)lParam;

    if (lpmsg->message == WM_COMMAND||lpmsg->message == WM_KEYDOWN||lpmsg->message == WM_SYSKEYDOWN ) { //&& lpmsg->wParam == VK_F1
      if (dlgHWnd!=0) {
            if (OnMyKeydown(dlgZakazHWnd, lpmsg->message,lpmsg->wParam,lpmsg->lParam)) {
             //это функция обработки события
          return true;
        };
      };
    }

    DefHookProc(nCode, wParam, lParam, &lpFilterFunc);

    return 0;
}

//это начальный перехват (при запуске программы)
bool RunStopHook(bool State, HINSTANCE hInstance) {
 
  if (State) {
    lpProcInstance = (HOOKPROC) MakeProcInstance(FilterFunc, hInstance);
    if (lpProcInstance == NULL)
      return FALSE;
    lpFilterFunc = SetWindowsHook(WH_MSGFILTER, lpProcInstance);
  } else {
    UnhookWindowsHookEx(lpFilterFunc);
  };
 return true;
};

//а так я в диалоге включаю обработку перехвата события
...
  case WM_INITDIALOG:
      dlgHWnd = hDlg;
...
//а так выключаю
     dlgHWnd = 0;
     EndDialog(hDlg, LOWORD(wParam));
...


Powered by Invision Power Board (http://www.invisionboard.com)
© Invision Power Services (http://www.invisionpower.com)