Wykrywanie pojawienia się i zniknięcia kursora z regionu

Wykrywanie pojawienia się i zniknięcia kursora z regionu
gswidwa
  • Rejestracja: dni
  • Ostatnio: dni
  • Postów: 839
0

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

Kopiuj
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.

_13th_Dragon
  • Rejestracja: dni
  • Ostatnio: dni
0
  1. To:
Kopiuj
    if( cur.x < 0 || cur.x >= WindowWidth() || cur.y < 0 || cur.y >= WindowHeight() )
     return false;
    else
     return true;
}

lepiej zapisać jako:

Kopiuj
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();

Kopiuj
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.
gswidwa
  • Rejestracja: dni
  • Ostatnio: dni
  • Postów: 839
0
  1. Te pierwsze uproszczenie rozumiem, ale kompletnie nie rozumiem jak działa:
Kopiuj
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 :(

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

    return (bool) PtInRegion(hRegion, cur.x, cur.y);
}
Kopiuj
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:
Kopiuj
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?

_13th_Dragon
  • Rejestracja: dni
  • Ostatnio: dni
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.

gswidwa
  • Rejestracja: dni
  • Ostatnio: dni
  • Postów: 839
0
Kopiuj
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:

Kopiuj
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);
    ///...
    }
 
Kopiuj
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;
}
 
gswidwa
  • Rejestracja: dni
  • Ostatnio: dni
  • Postów: 839
0

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

Kopiuj
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;)

Kopiuj
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

gswidwa
  • Rejestracja: dni
  • Ostatnio: dni
  • Postów: 839
0

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

Kopiuj
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

Kopiuj
#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
Kopiuj
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
Kopiuj
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:

Kopiuj
 
    if(wsk->hRegion != NULL)
     hRegion = wsk->hRegion;
    else
     GetWindowRgn(wsk->hWindow, hRegion);
gswidwa
  • Rejestracja: dni
  • Ostatnio: dni
  • Postów: 839
0

Kurde 10ty raz to czytam i nie rozumiem.

Kopiuj
 
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

gswidwa
  • Rejestracja: dni
  • Ostatnio: dni
  • Postów: 839
0

W takim razie to jest dobrze?

Kopiuj
 
    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:

Kopiuj
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.

gswidwa
  • Rejestracja: dni
  • Ostatnio: dni
  • Postów: 839
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ę:

Kopiuj
 
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?

_13th_Dragon
  • Rejestracja: dni
  • Ostatnio: dni
1

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

gswidwa
  • Rejestracja: dni
  • Ostatnio: dni
  • Postów: 839
0

W funkcji CreateButton()"

Kopiuj
    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:

Kopiuj
 
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:

Kopiuj
 
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ą:

Kopiuj
 
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 :)

gswidwa
  • Rejestracja: dni
  • Ostatnio: dni
  • Postów: 839
0

Kurde.

Kopiuj
 
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?

gswidwa
  • Rejestracja: dni
  • Ostatnio: dni
  • Postów: 839
0

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

Kopiuj
wsk->hregion = hRegion 
gswidwa
  • Rejestracja: dni
  • Ostatnio: dni
  • Postów: 839
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.

_13th_Dragon
  • Rejestracja: dni
  • Ostatnio: dni
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ą.

gswidwa
  • Rejestracja: dni
  • Ostatnio: dni
  • Postów: 839
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.

Zarejestruj się i dołącz do największej społeczności programistów w Polsce.

Otrzymaj wsparcie, dziel się wiedzą i rozwijaj swoje umiejętności z najlepszymi.