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


Автор: Mapa3M 29.8.2008, 00:50
сабж.   Хочу нажать на кнопку и перетащить ее в другую часть окна. Но, когда зажимаю левую кнопку мыши и перетаскиваю, окну приходят только WM_CTLCOLORBTN. А нужное мне WM_MOUSEMOVE, как показывает Spy++ приходят кнопке (тоесть когда ставлю хук на кнопку - ловлю, а когда на окно - нет)

Я буквально только-что начал знакомится с Win API, не судите строго. Расскажите пожалуйста, как поймать WM_MOUSEMOVE?  Возможно ли создать, если да то как, отдельную CALLBACK для кнопки ?

Автор: BorisVorontsov 29.8.2008, 20:01
ReleaseCapture?

Автор: Mapa3M 29.8.2008, 23:28
Цитата(BorisVorontsov @  29.8.2008,  20:01 Найти цитируемый пост)
ReleaseCapture


спасибо за вариант, сейчас попробую.  
Но всетаки хотелось бы разобраться теории. Как программой должно отслеживаться сообщение посылаемое кнопке?

Автор: Mapa3M 30.8.2008, 04:18
Кстати, чегото неполучается поставить SetCapture по сообщению WM_PARENTNOTIFY. 
По другим сообщениям получается, здесь чота никак не срабатывает.  Сообщение приходит что каптча поставлена на кнопку, хотя я ставлю на окно. Как так?

Автор: 586 30.8.2008, 04:41
Код
long OldBtnProc;

LRESULT CALLBACK MyButtonProc(HWND hWnd, UINT uMsg, WPARAM wParam, LPARAM lParam)
{
    switch(uMsg)
    {
     case WM_LBUTTONDOWN:
        ReleaseCapture();  // нужно для сообщения 0xF012
        SendMessage(hWnd, WM_SYSCOMMAND, 0xF012, 0);
        return 0;

    }
    return CallWindowProc((WNDPROC)OldBtnProc, hWnd, uMsg, wParam, lParam);
}


OldBtnProc = SetWindowLong(hWnd, GWL_WNDPROC, (long)MyButtonProc);

Автор: Mapa3M 30.8.2008, 04:55
586,   Спасиб, то что надо! Буду пробовать...

Автор: Mapa3M 30.8.2008, 14:13
Цитата(586 @  30.8.2008,  04:41 Найти цитируемый пост)
        ReleaseCapture();  // нужно для сообщения 0xF012
        SendMessage(hWnd, WM_SYSCOMMAND, 0xF012, 0);


вот это просто феерично!  Я как настоящий герой, непонимая этой части кода, сналача закоментировал его. Пытался двигать контрол вместе с мышкой. Убивался сложностью получаения относительных координат, тормознутостью прерисовки.  А тут вот как значит...

Кстати, откуда 0xF012? В MSDNе ниче не нашел.

Автор: 586 30.8.2008, 15:45
Цитата(Mapa3M @  30.8.2008,  15:13 Найти цитируемый пост)
Кстати, откуда 0xF012? В MSDNе ниче не нашел. 

Это народный метод. Работает только для левой кнопки. Написал здесь просто для примера - твой вопрос же был, как перехватить оконную процедуру.
Если хорошенько подумать, то можно и с координатами:
Код
#include <windowsx.h>  // GET_X_LPARAM and GET_Y_LPARAM

long OldBtnProc;
LRESULT CALLBACK MyButtonProc(HWND hWnd, UINT uMsg, WPARAM wParam, LPARAM lParam)
{
    static POINT ptOld;
    static int x, y;        // позиция указателя на кнопке
    static bool bMove = false;
    POINT pt;


    switch(uMsg)
    {
     case WM_RBUTTONDOWN:
        SetCapture(hWnd);
        x = GET_X_LPARAM(lParam);
        y = GET_Y_LPARAM(lParam);
        GetCursorPos(&ptOld);
        ScreenToClient(GetParent(hWnd), &ptOld);
        bMove = true;
        return 0;

     case WM_MOUSEMOVE:
        if(bMove)
        {
            GetCursorPos(&pt);
            ScreenToClient(GetParent(hWnd), &pt);

            SetWindowPos(hWnd, 0, ptOld.x - x, ptOld.y - y,
                0, 0, SWP_NOSIZE);
            ptOld = pt;
        }
        return 0;

     case WM_RBUTTONUP:
     case WM_CAPTURECHANGED:
        ReleaseCapture();
        bMove = false;
        return 0;
    }
    return CallWindowProc((WNDPROC)OldBtnProc, hWnd, uMsg, wParam, lParam);
}

Код
#include <windowsx.h>

long OldBtnProc;
LRESULT CALLBACK MyButtonProc(HWND hWnd, UINT uMsg, WPARAM wParam, LPARAM lParam)
{
    static int x, y;                     // клиентские коорд. места, где находится курсор
    static RECT wndrect;          // коорд. кнопки относительно родителя
    static bool bMove = false;

    switch(uMsg)
    {
     case WM_RBUTTONDOWN:
        SetCapture(hWnd);
        x = GET_X_LPARAM(lParam);
        y = GET_Y_LPARAM(lParam);
        GetWindowRect(hWnd, &wndrect);
        wndrect.right -= wndrect.left;
        wndrect.bottom -= wndrect.top;
        ScreenToClient(GetParent(hWnd), (LPPOINT)&wndrect);
        bMove = true;
        return 0;

     case WM_MOUSEMOVE:
        if(bMove)
        {
            wndrect.left += GET_X_LPARAM(lParam) - x;
            wndrect.top += GET_Y_LPARAM(lParam) - y; 

            /*wndrect.left += (short)LOWORD(lParam) - x;
            wndrect.top += (short)HIWORD(lParam) - y; */

            SetWindowPos(hWnd, 0, wndrect.left, wndrect.top,
                0, 0, SWP_NOSIZE);
        }
        return 0;

     case WM_RBUTTONUP:
     case WM_CAPTURECHANGED:
        ReleaseCapture();
        bMove = false;
        return 0;
    }
    return CallWindowProc((WNDPROC)OldBtnProc, hWnd, uMsg, wParam, lParam);
}

Автор: GremlinProg 30.8.2008, 16:26
мда уж, не думал, что тема до такого марадерства дойдет...

Mapa3M, сообщение мыши приходят в окно, над которым она находится, т.е., если ты кликаешь на кнопку, то WM_LBUTTONDOWN и придет в кнопку, т.е. в процедуре диалога оно само не объявится.

чтобы перехватить процедуру окна любого контрола, необходимо воспользоваться специально предназначенного для этого API, вот пример такого перехвата (корректная версия с поддержкой w64):
Код

OldWndProc = (WNDPROC)SetWindowLongPtr (hButton,GWLP_WNDPROC, (LONG_PTR)NewWndProc);

версия с включенной поддержкой w64, это 32-битная версия, (стандартная ошибка локализации, на x64 работать не будет!):
Код

OldWndProc = (WNDPROC)SetWindowLong(hButton,GWL_WNDPROC, (LONG)(LONG_PTR)NewWndProc);

версия с выключенной поддержкой w64 (без ошибок и без x64):
Код

OldWndProc = (WNDPROC)SetWindowLong(hButton,GWL_WNDPROC, (LONG)NewWndProc);

NewWndProc - это процедура кнопки, которая после такого будет вызываться вместо стандартной (OldWndProc)
стратегия NewWndProc должна составлять хотя бы такой минимум:
Код

LRESULT CALLBACK NewWndProc(HWND hButton,UINT message,WPARAM wParam,LPARAM lParam){
  return::CallWindowProc(OldWndProc,hButton,message,wParam,lParam);
}

такой перехват нужно делать сразу после инициализации диалога, например в WM_INITDIALOG

если нужен перехват WM_LBUTTONDOWN, то это можно сделать так:
Код

LRESULT CALLBACK NewWndProc(HWND hButton,UINT message,WPARAM wParam,LPARAM lParam){
  switch(message){
    case WM_LBUTTONDOWN:{
      // код сообщения WM_LBUTTONDOWN, которое произошло над кнопкой
      break;
    }
  }
  return::CallWindowProc(OldWndProc,hButton,message,wParam,lParam);
}

вариант#1:
перемещение кнопки нужно сопрягать по крайней мере с тремя сообщениями мыши:

1. WM_LBUTTONDOWN
2. WM_MOUSEMOVE
3. WM_LBUTTONUP

в первом нужно запомнить позицию мыши относительно левого-верхнего угла кнопки (sx,sy) = (x - left,y - top) и включить флаг переноса
во втором нужно тестировать флаг переноса, и если он включен, переместить кнопку в позицию (x - sx,y - sy)
в третьем нужно сбросить флаг переноса

флаг переноса - просто любая глобальная переменная, изначально проинициализированная нулем

вариант#2:
корректное перемещение включает еще дополнительное сообщение:
4. WM_CAPTURECHANGED
при этом, на WM_LBUTTONDOWN нужно еще вызвать SetCapture, а на WM_LBUTTONUP вызвать ReleaseCapture, флаг переноса сбрасывать уже не на WM_LBUTTONUP, а в сообщении WM_CAPTURECHANGED, остальное - так же как в первом варианте.

попробуй оба варианта, разницу сам увидишь

тебе как нужно кнопку двигать? зажатую или не зажатую, или это не важно?
если не зажатую, то во время перехвата WM_LBUTTONDOWN, не выполняй стандартную обработку окна, т.е. вместо break вызови return 0 этим ты предотвратишь стандартную инициализацию механизма нажатия (в том числе и SetCapture), но тогда уже и нажиматься кнопка не будет.

Автор: Mapa3M 30.8.2008, 18:47
586, О, гуру, открой мне источник своей мудрости! (Не хочу по два дня ковырять ивзращенный код того, что делается в две строчки еще и лучше)  =))
З.Ы. извиняюсь, подскажите книжку...

Цитата(586 @  30.8.2008,  15:45 Найти цитируемый пост)

Это народный метод. Работает только для левой кнопки. 

хм, учту.  Метод понравился, спасибо. 


Цитата(586 @  30.8.2008,  15:45 Найти цитируемый пост)
Если хорошенько подумать, то можно и с координатами:

вот примерно так я и делал. Только с координатами помучался, ибо GetParent функции не знал, и без SetCapture. Зачем он в таком коде? Чтоб с другими обектами конфуз не вышел?


GremlinProg, спасибо за подробный разбор полетов. 

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