Wykrywanie pojawienia się i zniknięcia kursora z regionu

0

Witam. Do tej pory sprawdzałem, czy kursor wchodzi na kontrolkę i z niej schodzi funkcją:

bool DLL_EXPORT sButtonClass::KursorWewnatrz(){
    POINT cur;
    GetCursorPos( & cur );
    ScreenToClient( hWindow, & cur );
    if( cur.x < 0 || cur.x >= WindowWidth() || cur.y < 0 || cur.y >= WindowHeight() )
     return false;
    else
     return true;
}

Jednak od czasu jak dodałem flagę, aby kontrolka mogła być okrągła lub miała kształt zdefiniowany przez uzytkownika funkcja straciła swoją ... funkcjonalność. Istnieje jakaś funkcja, która mówi mi, czy kursor znajduje się nad oknem bądź regionem?

Opis sytuacji:
Mam przycisk. Okrągły przycisk. Po najechaniu na niego kursorem zmienia się bitmapa i jak zjedziemy kursorem wraca do poprzedniego stanu. Ale ale. Mając okrągły przycisk i klikając w pixel (0, 0) klikamy w obszar nie należący do kontrolki.

Próbowałem zrobić to za pomocą wzoru na długość odcinka w układzie współrzędnych, ale wiadomo, że to zadziała tylko w przypadku okręgu, a kontrolka może mieć dowolny kształt.

0
  1. To:
    if( cur.x < 0 || cur.x >= WindowWidth() || cur.y < 0 || cur.y >= WindowHeight() )
     return false;
    else
     return true;
}

lepiej zapisać jako:

return !( cur.x < 0 || cur.x >= WindowWidth() || cur.y < 0 || cur.y >= WindowHeight() );

lub jeszcze lepiej tak: return 0<=cur.x && cur.x<WindowWidth() && 0<=cur.y && cur.y<WindowHeight();

2. zapoznaj się z `PtInRect()`
3. zawsze możesz sprawdzić `if((cur.x<=R)&&(cur.y<=R)&&(hypot(R-cur.x,R-cur.y)>R) // jesteśmy poza obszarem zaokrąglenia o promieniu R w lewym górnym` oraz jeszcze trzy takich sprawdzenia.
0
  1. Te pierwsze uproszczenie rozumiem, ale kompletnie nie rozumiem jak działa:
return  0<=cur.x && cur.x<WindowWidth() && 0<=cur.y && cur.y<WindowHeight(); 
  1. PtInRect() działa tylko na czworokącie / trójkącie a mi zależy na wielokątach (dowolny region)
  2. Jezeli chodzi o okrąg to też tak nie do końca bo mogę mieć kontrolkę o rozmiarach 100:20 i wtedy okrąg staje się owalny czy jakoś tak ;P
    A do okręgu po prostu sprawdzam czy dlugosc odcinka środek-kursor jest mniejsza od R i po sprawie ;P Problem tkwi w regionach. Najgorsze jest to, że kiedys miałem styczność chyba z taką funkcją. Zaczynała sie na is(+1 wyraz)(); i nie moge jej znaleźć.

EDIT:

Wyskrobałem funkcję PtInRgn();
Teraz mam z nią problem :(

bool DLL_EXPORT sButtonClass::KursorWewnatrz(){
    POINT cur;
    GetCursorPos( & cur );
    ScreenToClient( hWindow, & cur );

    return (bool) PtInRegion(hRegion, cur.x, cur.y);
}
case WM_MOUSEMOVE:{
            class sButtonClass *wsk = (class sButtonClass *)GetWindowLong(hwnd, 0);
            if(!wsk || wsk->GetStan() == STAN_NIEAKTYWNY)
             break;
            POINT cur;
            GetCursorPos( & cur );
            ScreenToClient( hwnd, & cur );

            if(!wsk->GetCapture()){
                SetCapture(hwnd);
                wsk->SetCapture(true);
                wsk->SetStan(STAN_NAJECHANY);
                wsk->OdswiezOkno();
            }
            if(!wsk->KursorWewnatrz()){
                ReleaseCapture();
                wsk->SetStan(STAN_AKTYWNY);
                wsk->SetCapture(false);
                wsk->OdswiezOkno();
            }
            break;
        } 

Działanie:

Są 4 bitmapy przycisku:
a) aktywny przycisk
b) przycisk na który najechaliśmy
c) przycisk wciśnięty
d) nieaktywny

  1. Najeżdżam na kontrolkę i wywołuje się komunikat WM_MOUSEMOVE.
    a) Funkcją wsk->GetCapture() sprawdzam, czy SetCapture() (nie wsk->SetCapture() !!!!) jest ustawione na mojej kontrolce.
    b) Jeżeli wsk->GetCapture() zwraca false to:
  • ustawiam capture na true;
  • kieruje wszystkie komunikaty myszy od tej pory do tej kontrolki
  • zmieniam bitmapę przycisku na B (najechany).
  1. Gdy sobie jeżdżę po kontrolce myszą za każdym razem jest sprawdzany warunek:
if(!wsk->KursorWewnatrz()){
                ReleaseCapture();
                wsk->SetStan(STAN_AKTYWNY);
                wsk->SetCapture(false);
                wsk->OdswiezOkno();
            } 

Więc jeżeli kursor nie będzie wewnątrz zwalniamy capture i wszystko wraca do stanu początkowego.

Problem w tym, że jak wjeżdżam na kontrolkę nie ma żadnej reakcji. Wychodzi na to, że wsk->KursorWewnatrz() zawsze zwraca mi wartość FALSE i wywołują się 2 if-y zaraz po sobie. jakieś pomysły?

0

Ad 1. https://pl.wikipedia.org/wiki/Prawa_De_Morgana
Ad 2. https://msdn.microsoft.com/en-us/library/windows/desktop/dd162883%28v=vs.85%29.aspx ale musisz zrobić region, tyle że w przypadku kształtów geometrycznych prościej to zrobić za pomocą PtInRect plus to co podałem w pkt 3.
Ad 3. Nie znasz równania na elipse? No właśnie dla okręgu podałem równanie. Nie musisz wymyślać kola na nowo.

0
case WM_CREATE:{
            CREATESTRUCT *lpCreateStruct = (CREATESTRUCT *)lParam;
            struct sButtonClassInfo *Info = (struct sButtonClassInfo *)lpCreateStruct->lpCreateParams;
            Info->hWindow = hwnd;
            class sButtonClass *wsk = new sButtonClass(Info);

            SetWindowLong(hwnd, 0, (LONG)wsk);
            break;
        } 

i w konstruktorze klasy:

DLL_EXPORT sButtonClass::sButtonClass(struct sButtonClassInfo *wsk){
    if(!wsk || !wsk->hInstance || !wsk->hWindow || !wsk->hBitmap)
     return;

    hInstance = wsk->hInstance;
    hWindow = wsk->hWindow;
    hParent = GetParent(hWindow);
    if(wsk->hRegion != NULL)
     hRegion = wsk->hRegion;
    else
     GetWindowRgn(wsk->hWindow, hRegion);
    SetWindowRgn(hWindow, hRegion, true);
    ///...
    }
 
HWND DLL_EXPORT f_CreateButton(HINSTANCE hInstance, HWND hParent, char *sciezka, COLORREF cTransparent, int x, int y, int width, int height, int ID, DWORD flags, HRGN hRegion, void *dane){
    struct sButtonClassInfo Info;
    Info.cTransparent = cTransparent;
    Info.flagi = flags;
    Info.hBitmap = ( HBITMAP ) LoadImage( NULL, sciezka, IMAGE_BITMAP, 0, 0, LR_LOADFROMFILE );
    Info.hInstance = hInstance;
    Info.dane = dane;

    Info.hRegion = NULL;
    if(Info.flagi & STYLE_MyRegion)
     Info.hRegion = hRegion;
    if(Info.flagi & STYLE_RegionCircle)
     Info.hRegion = CreateEllipticRgn(x, y, width, height);

    HWND hNew = CreateWindowEx(WS_EX_COMPOSITED, "WH_RBUTTON", NULL, WS_CHILD | WS_VISIBLE, x, y, width, height, hParent, (HMENU)ID, hInstance, (LPVOID)&Info);
    return hNew;
}
 
0

Nie wierzę... Chciałem sprawdzić czy chodzi o warunek i zaczęło działać. Ale nie rozumiem dlaczego i to jest złe.

if(!wsk->GetCapture()){
                SetCapture(hwnd);
                wsk->SetCapture(true);
                wsk->SetStan(STAN_NAJECHANY);
                wsk->OdswiezOkno();
            }
            if(!wsk->KursorWewnatrz()){
                ReleaseCapture();
                wsk->SetStan(STAN_AKTYWNY);
                wsk->SetCapture(false);
                wsk->OdswiezOkno();
            } 

zamieniłem na: (dodałem tylko break;)

if(!wsk->GetCapture()){
                SetCapture(hwnd);
                wsk->SetCapture(true);
                wsk->SetStan(STAN_NAJECHANY);
                wsk->OdswiezOkno();
                break;
            }
            if(!wsk->KursorWewnatrz()){
                ReleaseCapture();
                wsk->SetStan(STAN_AKTYWNY);
                wsk->SetCapture(false);
                wsk->OdswiezOkno();
            } 

Dlaczego mi to działa teraz? Przecież skoro pierwszy raz się wywołał komunikat WM_MOUSEMOVE to kursor musi znajdować się w regionie, więc drugi warunek powinien być FALSE, a wszystko wskazuje, że jest TRUE

0

Nie nie, w tym miejscu jest obsługa złego wywołania CreateButton();

HWND DLL_EXPORT f_CreateButton(HINSTANCE hInstance, HWND hParent, char *sciezka, COLORREF cTransparent, int x, int y, int width, int height, int ID, DWORD flags, HRGN hRegion, void *dane); 

jeżeli we fladze podasz

#define STYLE_MyRegion              4

to wtedy w argumencie hRegion trzeba podać region. Ale w razie jakby użytkownik dał tę flagę i hregion ustawił NULL to kontrolka pozostałaby bez regionu? Dlatego w razie czegoś takiego pobieram region z prostokąta okna.

Czyli:

  1. poprawne
f_CreateButton(hInstance, hParent, "przycisk.bmp", RGB(0,0,0), 0,0, 30, 20, 100, STYLE_MyRegion, hRegion, NULL);
  1. niepoprawne, bo nie podaję regionu i muszę sam go stworzyć, wskaźnik hRegion w konstruktorze jest równy NULL
f_CreateButton(hInstance, hParent, "przycisk.bmp", RGB(0,0,0), 0,0, 30, 20, 100, STYLE_MyRegion, NULL, NULL);

I właśnie za to odpowiada ten warunek:

 
    if(wsk->hRegion != NULL)
     hRegion = wsk->hRegion;
    else
     GetWindowRgn(wsk->hWindow, hRegion);
0

Kurde 10ty raz to czytam i nie rozumiem.

 
HRGN hrgn = CreateRectRgn(0,0,0,0); //tworzmy kwadrat o zerowych wymiarach?
int regionType = GetWindowRgn(hwnd, hrgn); //ustalamy wymiary regionu dopasowane do okna hwnd? :O
if (regionType != ERROR) 
{ 
/* hrgn contains window region */ 
}
DeleteObject(hrgn); /* finished with region */

O to chodzi? Muszę już mieć jakiś region, żeby go edytować?
EDIT:
Dopiero odczytałem, że zmienna hRegion zostanie nietknięta. To co robi ta funkcja w takim razie xd

0

W takim razie to jest dobrze?

 
    Info.hRegion = NULL;
    if(Info.flagi & STYLE_MyRegion)
     Info.hRegion = hRegion;
    if(Info.flagi & STYLE_RegionCircle)
     Info.hRegion = CreateEllipticRgn(0, 0, width, height);
    if(!Info.hRegion)
     Info.hRegion = CreateRectRgn(0, 0, width, height);

oraz w konstruktorze:

if(!wsk || !wsk->hInstance || !wsk->hWindow || !wsk->hBitmap)
     return;

    hInstance = wsk->hInstance;
    hWindow = wsk->hWindow;
    hParent = GetParent(hWindow);
    hRegion = wsk->hRegion;
    SetWindowRgn(hWindow, hRegion, true);
    cTransparent = wsk->cTransparent;

I potem oczywiście usuwam ten region w destruktorze.

0

teraz to wszystko wygląda tak, że tworzę region i ustawiam taki sam za pomocą SetWindowRgn() dla kontrolki.

Mam zapisany region z uchwytem hRegion.
mam kontrolkę o szerokości WindowWidth() i wysokości WindowHeight().
mam pozycję kursora w strukturze POINT względem kontrolki. (sprawdziłem, pozycja kursora odczytuje się prawidłowo).

Teraz mam funkcję:

 
bool DLL_EXPORT sButtonClass::KursorWewnatrz(){
    POINT cur;
    GetCursorPos( & cur );
    ScreenToClient( hWindow, & cur );
    return PtInRegion(hRegion, cur.x, cur.y);
}

Dlaczego funkcja zawsze zwraca false, nawet jak kursor znajduje się w obszarze buttonu?

1

Ponieważ nie przekazujesz tego regionu, od kilku dni cię mówię że GetWindowRgn() nic sensownego nie zwraca.

0

W funkcji CreateButton()"

    Info.hRegion = NULL;
    if(Info.flagi & STYLE_MyRegion)
     Info.hRegion = hRegion;
    if(Info.flagi & STYLE_RegionCircle)
     Info.hRegion = CreateEllipticRgn(0, 0, width, height);
    if(!Info.hRegion)
     Info.hRegion = CreateRectRgn(0, 0, width, height); 

oraz w konstruktorze:

 
if(!wsk || !wsk->hInstance || !wsk->hWindow || !wsk->hBitmap)
     return;
 
    hInstance = wsk->hInstance;
    hWindow = wsk->hWindow;
    hParent = GetParent(hWindow);
    hRegion = wsk->hRegion;
    SetWindowRgn(hWindow, hRegion, true);
    cTransparent = wsk->cTransparent;

Nastepnie przy WM_MOUSEMOVE sprawdzam warunek uzywając funkcji:

 
bool DLL_EXPORT sButtonClass::KursorWewnatrz(){
    POINT cur;
    GetCursorPos( & cur );
    ScreenToClient( hWindow, & cur );
    return PtInRegion(hRegion, cur.x, cur.y);
}

na 100% jest coś nie tak z ta funkcją, ponieważ jak sprawdzę funkcją:

 
ool DLL_EXPORT sButtonClass::KursorWewnatrz(){
    POINT cur;
    GetCursorPos( & cur );
    ScreenToClient( hWindow, & cur );

    if(cur.x < 0 || cur.y < 0 || cur.x >= WindowWidth() || cur.y >= WindowHeight())
     return false;
    else
     return true;
}

cały przycisk działa jak należy, ale tylko dla prostokąta oczywiście :)

0

Kurde.

 
POINT cur;
    GetCursorPos( & cur );
    ScreenToClient( hWindow, & cur );

    //if(cur.x < 0 || cur.y < 0 || cur.x >= WindowWidth() || cur.y >= WindowHeight())
     //return false;
    //else
     //return true;

    HRGN region = CreateEllipticRgn(0, 0, WindowWidth(), WindowHeight());
    bool result = PtInRegion(region, cur.x, cur.y);
    DeleteObject(region);
    return result;

Dlaczego ten kod mi działa jak należy? Skoro ustawiałem kontrolkę funkcją SetWindowRegion(hRegion) i przycisk był kółkiem to znaczy, że prawidłowo utworzyło region. A warunek chyba go nie wyłapywał. teraz stworzyłem nowy region i działa. Może mi ktoś podpowiedzieć, czemu tak jest abym w przyszłości nie zadawał (być może) głupiego pytania?

0

A jak prawidłowo przekazuje się region? Bo ja myślałem, że po prostu:

wsk->hregion = hRegion 
0

Hm, może ma to coś wspólnego z tym? Bo w konstruktorze ustawiam region kontrolki za pomocą SetWindowRgn() uzywając właśnie tego uchwytu. A jeśli tak, to jak zrobić "kopię regionu" przed tym?

https://msdn.microsoft.com/en-us/library/windows/desktop/dd145102%28v=vs.85%29.aspx

After a successful call to SetWindowRgn, the system owns the region specified by the region handle hRgn. The system does not make a copy of the region. Thus, you should not make any further function calls with this region handle. In particular, do not delete this region handle. The system deletes the region handle when it no longer needed.

To obtain the window region of a window, call the GetWindowRgn function.

1

No to masz znalezione, spróbuj po SetWindowRgn() zrobić jeszcze raz CreateEllipticRgn którego wsadź w ten wsk->hRegion; aby nie tworzyć regionu po każdym ruszeniu myszką.

0

Jeszcze jedno, jest może jakaś funkcja co "kopiuje" region? Bo jak będe zmieniał wielkość przycisku w trakcie trwania programu musiałbym kombinować "jak wyglądał mój region". A tak wezmę go to hRegion a używając SetWindowrgn() będę po prostu używał na kopii.

1 użytkowników online, w tym zalogowanych: 0, gości: 1