MOUSE_WHEELED



  • Hallo,
    Ich habe Schwierigkeiten mit der Auswertung des Mausrades in der Konsole. Zwar erkenne ich wheel_up und wheel_down,
    aber wenn sich das Rad dreht, dreht es sich. Ich möchte aber, das erkannt wird, wenn das Mausrad aufhört zu drehen.

    Das hier ist meine MouseInput-Methode, in diesem Fall wird das Mausrad bei einem Mausklick auf 0 gesetzt. Ich möchte aber, das das Mausrad auf 0 gesetzt wird, wenn es nicht mehr bedient wird.

    // variablen //...
    namespace console
    {
            //...
            constexpr int32_t mouseBufferSize = 32;
            constexpr int32_t mouseKeyBufferSize = 5;
            //...
    }
    
    struct KeyState
    {
    	bool pressed = false;
    	bool released = false;
    	bool held = false;
    };
    
    struct MouseState
    {
    	console::Point pos;
    	int32_t wheelState = 0;
    	int32_t wheelData = 0;
    };
    
    MouseState mouse;
    std::array<INPUT_RECORD, console::mouseBufferSize> mouseBuffer = {};
    std::array<KeyState, console::mouseKeyBufferSize> mouseKeys;
    std::array<int32_t, console::mouseKeyBufferSize> newMouseState = { 0 };
    std::array<int32_t, console::mouseKeyBufferSize> oldMouseState = { 0 };
    
    //...
    
    
    bool Console::handleMouseInput()
    {
    	// check for window events
    	DWORD events = 0;
    	if (!GetNumberOfConsoleInputEvents(inHandle, &events))
    		return error("GetNumberOfConsoleInputEvents");
    	if (events > 0)
    	{
    		if (!ReadConsoleInput(inHandle, mouseBuffer.data(), console::mouseBufferSize, &events))
    			return error("ReadConsoleInput");
    	}
    
    	// handle events 
    	for (DWORD i = 0; i < events; ++i)
    	{
    		switch (mouseBuffer[i].EventType)
    		{
    		case FOCUS_EVENT:
    		{
    			consl.inFocus = mouseBuffer[i].Event.FocusEvent.bSetFocus;
    		}
    		break;
    
    		case MOUSE_EVENT:
    		{
    			switch (mouseBuffer[i].Event.MouseEvent.dwEventFlags)
    			{
    			case MOUSE_MOVED:
    			{
    				mouse.pos.x = mouseBuffer[i].Event.MouseEvent.dwMousePosition.X;
    				mouse.pos.y = mouseBuffer[i].Event.MouseEvent.dwMousePosition.Y;
    			}
    			break;
    
    			case MOUSE_WHEELED:
    			{
    				int32_t wheel = mouseBuffer[i].Event.MouseEvent.dwButtonState;
    
    				if (wheel > 0)
    				{
    					mouse.wheelState = 1; // wheel up
    					mouse.wheelData++;
    				}
    				else if (wheel < 0)
    				{
    					mouse.wheelState = 2; // wheel down
    					mouse.wheelData--;
    				}
    			}
    			break;
    
    			case 0: // button click
    			{
    				// reset mouse wheeled
    				mouse.wheelState = 0;
    				mouse.wheelData = 0;
    
    				for (auto j = 0; j < console::mouseKeyBufferSize; ++j)
    					newMouseState[j] = (mouseBuffer[i].Event.MouseEvent.dwButtonState & (1 << j)) > 0;
    			}
    			break;
    
    			default:
    				break;
    			}
    		}
    		break;
    
    		default:
    			break; // don't care just at the moment
    		}
    	}
    
    	for (auto i = 0; i < console::mouseKeyBufferSize; ++i)
    	{
    		mouseKeys[i].pressed = false;
    		mouseKeys[i].released = false;
    
    		if (newMouseState[i] != oldMouseState[i])
    		{
    			if (newMouseState[i])
    			{
    				mouseKeys[i].pressed = true;
    				mouseKeys[i].held = true;
    			}
    			else
    			{
    				mouseKeys[i].released = true;
    				mouseKeys[i].held = false;
    			}
    		}
    
    		oldMouseState[i] = newMouseState[i];
    	}
    
    	return true;
    }
    
    

    Ich bin jetzt etwas ratlos, weiß auch gar nicht, wo ich ansetzen sollte. Wie bekomme ich mouse.wheelState auf 0, wenn sich das Rad nicht mehr dreht?



  • Wie wärs mit einem MRE??



  • Ja, ich will da nicht gleich so kompliziert einsteigen.



  • PS:
    mouse.wheelData zählt ordentlich. Nur wenn sich das Rad dreht, wird hoch- oder runtergezählt.
    Kann man das nicht verknüpfen mit mouse.wheelState?



  • @zeropage sagte in MOUSE_WHEELED:

    Ja, ich will da nicht gleich so kompliziert einsteigen.

    Du sollst a minimales kompilierbares beispiel liefern. Wie lange bist Du jetzt hier im Forum??



  • Oh sorry! Auch wegen der Verspätung.

    Ich dachte, man kann sich in den Code so rein denken. Aber ich schau mal, was ich da machen kann. Immerhin müsste ich ja eine ganz neue Funktion schreiben, weil so wie die Methode oben steht, geht ja nur mit zugehöriger Klasse. Aber ich schau mal.



  • @Swordfish

    Bist Du sauer auf mich, weil wir vor 150 Jahren mal aneinander geraten sind?



  • @zeropage sagte in MOUSE_WHEELED:

    Oh sorry! Auch wegen der Verspätung.

    Ich dachte, man kann sich in den Code so rein denken. Aber ich schau mal, was ich da machen kann. Immerhin müsste ich ja eine ganz neue Funktion schreiben, weil so wie die Methode oben steht, geht ja nur mit zugehöriger Klasse. Aber ich schau mal.

    Es geht auch darum, das einfach in live mal sehen zu können und vlt selbst mal zwei, drei Sachen probieren zu können ohne selbst all zu viel Arbeit da rein zu investieren.

    Verschachtelte switch case if for Konstrukte machen es auch nicht wirklich "einfach" sich irgendwo rein zu denken.

    Ich möchte aber, das das Mausrad auf 0 gesetzt wird, wenn es nicht mehr bedient wird.

    Was heißt denn nicht mehr bedient? 0.1 Sekunden nicht weiter gedreht, oder 0.2 s nicht weiter gedreht? Mein Mausrad hier hat so Zwischenstufen, wenn ich langsam drehe bleibe ich da immer "kurz" stehen. Ist das dann "nicht mehr bedient"? Vielleicht gibt es, für das, was du vorhast, ein besseres Kriterium?



  • In meiner Variable mouse.wheelData wird tatsächlich nur hoch und runtergezählt, wenn man das Mausrad auch dreht. Wenn es still steht wird auch nicht gezählt, während mouse.wheelState immer und ständig != 0 ist. (bzw in diesem Fall erst bei Mausklick auf 0 gestzte wird)

    Dieses switch_case Ding wird von der API so vorgegeben. zB hier:

    Reading Input Buffer Events
    Und da steht ausdrücklich

    MOUSE_WHEELED 0x0004 The vertical mouse wheel was moved.

    If the high word of the dwButtonState member contains a positive value, the wheel was rotated forward, away from the user.
    Otherwise, the wheel was rotated backward, toward the user.

    Ich musste dieses Beispiel etwas umschreiben, damit es bei mir (VS22, c++20) kompiliert:

    #include <windows.h>
    #include <iostream>
    #include <string>
    
    HANDLE hStdin;
    DWORD fdwSaveOldMode;
    
    void ErrorExit(const std::string& lpszMessage)
    {
        std::cout << stderr << lpszMessage;
    
        // Restore input mode on exit.
        SetConsoleMode(hStdin, fdwSaveOldMode);
        ExitProcess(0);
    }
    
    void KeyEventProc(KEY_EVENT_RECORD ker)
    {
        std::cout << "Key event: ";
    
        if (ker.bKeyDown)
            std::cout << "key pressed\n";
        else std::cout << "key released\n";
    }
    
    void MouseEventProc(MOUSE_EVENT_RECORD mer)
    {
        std::cout << "Mouse event: ";
    
        switch (mer.dwEventFlags)
        {
        case 0:
    
            if (mer.dwButtonState == FROM_LEFT_1ST_BUTTON_PRESSED)
            {
                std::cout << "left button press \n";
            }
            else if (mer.dwButtonState == RIGHTMOST_BUTTON_PRESSED)
            {
                std::cout << "right button press \n";
            }
            else
            {
                std::cout << "button press\n";
            }
            break;
        case DOUBLE_CLICK:
            std::cout << "double click\n";
            break;
        case MOUSE_MOVED:
            std::cout << "mouse moved\n";
            break;
        case MOUSE_WHEELED:
            if (HIWORD(mer.dwButtonState) > 0)
            {
                std::cout << "mouse wheel up\n";
            }
            else 
            {
                std::cout << "mouse wheel down\n";
            }
            break;
        default:
            std::cout << "unknown\n";
            break;
        }
    }
    
    void ResizeEventProc(WINDOW_BUFFER_SIZE_RECORD wbsr)
    {
        std::cout << "Resize event\n";
        std::cout << "Console screen buffer is " << wbsr.dwSize.X << " columns by " << wbsr.dwSize.Y << " rows\n";
    }
    
    int main()
    {
        DWORD cNumRead, fdwMode;
        
    
        // Get the standard input handle.
        hStdin = GetStdHandle(STD_INPUT_HANDLE);
        if (hStdin == INVALID_HANDLE_VALUE)
            ErrorExit("GetStdHandle");
    
        // Save the current input mode, to be restored on exit.
        if (!GetConsoleMode(hStdin, &fdwSaveOldMode))
            ErrorExit("GetConsoleMode");
    
        // Enable the window and mouse input events.
        fdwMode = (ENABLE_EXTENDED_FLAGS | ENABLE_WINDOW_INPUT | ENABLE_MOUSE_INPUT);
        if (!SetConsoleMode(hStdin, fdwMode))
            ErrorExit("SetConsoleMode");
    
        INPUT_RECORD irInBuf[128];
    
        // Loop to read and handle the next 100 input events.
        int counter = 0;
        while (counter++ <= 100)
        {
            // Wait for the events.
            if (!ReadConsoleInput(
                hStdin,      // input buffer handle
                irInBuf,     // buffer to read into
                128,         // size of read buffer
                &cNumRead)) // number of records read
                ErrorExit("ReadConsoleInput");
    
            // Dispatch the events to the appropriate handler.
            for (DWORD i = 0; i < cNumRead; i++)
            {
                switch (irInBuf[i].EventType)
                {
                case KEY_EVENT: // keyboard input
                    KeyEventProc(irInBuf[i].Event.KeyEvent);
                    break;
    
                case MOUSE_EVENT: // mouse input
                    MouseEventProc(irInBuf[i].Event.MouseEvent);
                    break;
    
                case WINDOW_BUFFER_SIZE_EVENT: // scrn buf. resizing
                    ResizeEventProc(irInBuf[i].Event.WindowBufferSizeEvent);
                    break;
    
                case FOCUS_EVENT:  // disregard focus events
                    break;
    
                case MENU_EVENT:   // disregard menu events
                    break;
    
                default:
                    ErrorExit("Unknown event type");
                }
            }
        }
    
        // Restore input mode on exit.
        SetConsoleMode(hStdin, fdwSaveOldMode);
    
        return 0;
    }
    
    
    

    Aber dort funktioniert es nicht richtig, ich bekomme nur 'wheel_up' als Rückmeldung, egal wie ich drehe.



  • @zeropage

    Erstmal zu wheel_up :

    Das HIWORD Makro verschiebt das high word nach rechts setzt die obersten 16 Bit auf 0. Wenn das als Integer interpretiert wird, ist das immer positiv.

    case MOUSE_WHEELED:
      {
          
        short h = HIWORD(mer.dwButtonState);
        if (h > 0)
        {
          std::cout << "mouse wheel up\n";
        }
        else
        {
          std::cout << "mouse wheel down\n";
        }
        break;
      }
    


  • @zeropage
    Das Drehen des Mouse-Wheel wird nicht als anhaltender Zustand zum PC gemeldet, sondern in "Klicks". (Bei vielen Mäusen sind diese "Klicks" auch deutlich spürbar - aber nicht bei allen.)

    Also wenn du das Rad z.B. zu dir hin drehst, dann bekommst du PC seitig einfach hin und wieder einen Event mit negativem HIWORD(dwButtonState) gemeldet - also pro "Klick" kommt ein solcher Event. Dazwischen kommt nix. Und wenn du aufhörst zu drehen, dann kommt auch nix. Im Prinzip das selbe wie wenn du mehrfach eine Maustaste drückst. Da meldet die Maus ja auch nur jeden Klick einzeln, und es gibt keinen Event wenn du dann irgendwann mal mit Klicken aufhörst. D.h. wenn du mitbekommen willst dass der Benutzer aufgehört hat das Rad zu drehen, dann kannst du nur selbst willkürlich ein Timeout festlegen, nach dessen Ablauf du einfach annimmst dass der Benutzer eben aufgehört hat das Mausrad zu drehen. Einfach experimentell ermitteln was sich deiner Meinung nach passend anfühlt.

    Umsetzen kannst du das z.B. mittels SetTimer. Du kannst bei jedem Mausrad-Event einen Timer starten. Wenn du dabei eine von dir vordefinierte Timer-ID verwendest, dann ersetzt SetTimer automatisch den alten Timer mit der selben ID und startet das Timeout neu. D.h. wenn der Timer Event kommt, dann weisst du dass X Millisekunden seit dem letzten Mausrad-Event vergangen sind. Dann kannst du deinen Mausrad-Zustand auf "steht" setzen und den Timer löschen.



  • Er. Ich sehe gerade du arbeitest mit ReadConsoleInput. D.h. du hast vermutlich kein Fenster. In dem Fall wird SetTimer wohl nicht funktionieren. D.h. die Timeout-Sache wirst du irgendwie anders lösen müssen. Falls du schon einen "polling loop" mit ausreichender Frequenz hast kannst du z.B. einfach mitzählen.



  • @hustbaer

    Doch, SetTimer funktioniert auch ohne Fenster. In diesem Fall muss man nullptr für das Fensterhandle und einen Funktionszeiger als letzten Parameter übergeben, dann wird keine WM_TIMER Nachricht verschickt, sondern direkt die Funktion aufgerufen. Ob man dann die Windows Message Pump per GetMessage manuell behandeln muss weiss ich allerdings nicht.



  • @DocShoe
    Ah, OK.

    Ob man dann die Windows Message Pump per GetMessage manuell behandeln muss weiss ich allerdings nicht.

    Ja, muss man:

    When you specify a TimerProc callback function, the default window procedure calls the callback function when it processes WM_TIMER.
    Therefore, you need to dispatch messages in the calling thread, even when you use TimerProc instead of processing WM_TIMER.
    

    Würde mich wundern wenn das ohne Fenster anders wäre. In dem Fall wird man die WM_TIMER vermutlich einfach als "thread message" bekommen. Also als WM_TIMER die nicht an ein Fenster gerichtet ist. Und: WM_TIMER Messages werden auch nicht magisch, asynchron von irgendwem in die Queue gesteckt, sondern ad-hoc von GetMessage/PeekMessage generiert wenn es abgelaufene Timer gibt und nix wichtigeres ansteht.



  • Wobei ich nicht weiss wie man die Kombination aus ReadConsoleInput und Message-Loop am besten macht. Es gibt zwar für beide "peek" Funktionen, aber ich kenne keine Funktion mittels der man warten könnte bis es bei einem der beiden etwas zu holen gibt. D.h. man wird wohl trotzdem pollen müssen.

    Vermutlich könnte man es auch mehr oder weniger elegant mit Threads lösen. Fühlt sich aber irgendwie fast nach Overkill an.



  • Vielen Dank für den Input 🙂 Muss ich erstmal sortieren...


Anmelden zum Antworten