ScrollBar z symulowanym naciśnięciem Shifta.

0

Cześć.

Moj plugin dla TotalCommandera do odtwarzania modułów muzycznych w sumie przepisany na WinAPI, ale męczę się z jedną rzeczą. Chciałbym aby pasek przewijania do ustalania pozycji odtwarzanego modułu po kliknięciu w pole po lewej lub po prawej od scrolowanego kwadraru ustawiał się od razu na tej pozycji. Taki efekt można osiągnąc z tego co wiem, tylko ze wciśniętym Shiftem. A ja chciałbym to zrobić bez jego wciśkania.

Poniższy kod ma te wadę, ze w obsłudze okna rodzica komunikatu WM_HSCROLL po ustawieniu pozycji ScrollBara zakodowałem SetFocus na ostatnie aktywne okno, co w większości przypadków powoduje przywrócenie sterowania na listę plików w jednym z okien z plikami i katalogami, w którym nie jest otwarty moj plugin.

Niestety jeżeli myszkę pozostawiliśmy nadal nad scrollbarem to Shift ciągle będzie wciśnęty i zaczniemy zaznaczać pliki lub przy wciskaniu klawiszy efekt będzie taki jakbyśmy to robili nadal ze wciśniętym Shiftem. Prosil bym o przykład jak to lepiej zakodować i ogarnąć. Próbowałem "puszczać" klawisz symulując to w WM_KILLFOCUS, ale wtedy tylko pierwsze kliknięcie na ScrollBar działa jak należy, później już nie. Natomiast symulowania wciskania Shifta w komunikacie WM_LBUTTONDOWN następuje za późno, ponieważ efekt jest taki, jakby Shift nie był wciśnięty. Z gory dziękuję za wszelkie kody i porady, bo sam raczej nie przeskoczę tego i albo z tego zrezygnuje albo rozwiązanie będzie jak teraz niedopracowane czego bym nie chciał.

//...
function _TrackMouseEvent; external comctl32 name '_TrackMouseEvent';

procedure KeyDownUp(KeyToSend : Byte; KeyDown : boolean);
const
  DownUp_Flags_Arr : array[boolean] of DWORD = (0, KEYEVENTF_KEYUP);
  Extended_Flags_Arr : array[boolean] of DWORD = (0, KEYEVENTF_EXTENDEDKEY);
var
  Input : TagINPUT;
  KeyExtended : boolean;
begin
  Input.Itype := INPUT_KEYBOARD;
  Input.ki.wVK := KeyToSend;
  Input.ki.wScan := MapVirtualKey(KeyToSend, 0);
  KeyExtended := KeyToSend in
    [VK_CONTROL, VK_LCONTROL, VK_RCONTROL,
    VK_UP, VK_DOWN, VK_LEFT, VK_RIGHT,
    VK_HOME, VK_END, VK_PRIOR, VK_NEXT,
    VK_INSERT, VK_DELETE, VK_MENU];
  Input.ki.dwFlags := DownUp_Flags_Arr[not KeyDown] or Extended_Flags_Arr[KeyExtended];
  Input.ki.time := 0;
  SendInput(1, Input, SizeOf(Input));
end;

procedure DownUpShiftKeyThreadProc(Param : Pointer); stdcall;
begin
  KeyDownUp(VK_LSHIFT, Boolean(Param));
end;

function NewScrollBarProc(AHWnd : HWND; Msg : UINT; AWParam : WParam; ALParam : LParam) : LResult; stdcall;
var
  ThreadId : Cardinal;
  TME : TTrackMouseEvent;
begin
  case Msg of
    wM_SETCURSOR :
      begin
        SetCursor(LoadCursor(0, IDC_HAND));
        Result := 0;
        Exit;
      end;
    WM_SETFOCUS :
      begin
        FocusedHandle := GetForegroundWindow;
      end;
    WM_MOUSEMOVE :
      begin
        if IsWindowEnabled(AHwnd) then
        begin
          if not SBTrackingMouse then
          begin
            TME.cbSize := SizeOf(TME);
            TME.dwFlags := TME_LEAVE;
            TME.hwndTrack := AHWnd;
            _TrackMouseEvent(@TME);
            SBTrackingMouse := True;
            if DownUpShiftThrHandle = 0 then
            begin
              DownUpShiftThrHandle := CreateThread(nil, 0, @DownUpShiftKeyThreadProc, Pointer(True), 0, ThreadId);
            end;
          end;
        end;
      end;
    WM_MOUSELEAVE :
      begin
        SBTrackingMouse := False;
        if DownUpShiftThrHandle > 0 then
        begin
          CreateThread(nil, 0, @DownUpShiftKeyThreadProc, Pointer(False), 0, ThreadId);
          CloseHandle(DownUpShiftThrHandle);
          DownUpShiftThrHandle := 0;
        end;
      end;
  end;
  Result := CallWindowProc(POldScrollBarProc, AHWnd, Msg, AWParam, ALParam);
end;

procedure CreateModulePositionSB;
var
  R : TRect;
begin
  Windows.GetClientRect(TimeLabelHandle, R);
  TimeLabelHeight := R.Bottom;
  ModulePositionSBHandle := CreateWindow('ScrollBar', '', WS_TABSTOP or WS_VISIBLE or WS_CHILD,
    (GetControlWidth(ControlsGBHandle) - GetControlWidth(TitleEditHandle)) div 2,
    GetControlTop(ShowKindGBHandle) + GetControlHeight(ShowKindGBHandle) + TimeLabelHeight + Controls_Vert_Distance,
    GetcontrolWidth(TitleEditHandle), 18,
    ControlsGBHandle, IDC_MODULEPOSITIONSB, HInstance, nil);
  POldScrollBarProc := Pointer(SetWindowLong(ModulePositionSBHandle, GWL_WNDPROC, Longint(@NewScrollBarProc)));
  SetSBMin(ModulePositionSBHandle, 0);
  SetSBMax(ModulePositionSBHandle, 100);
  SetSBPosition(ModulePositionSBHandle, 0);
end;
0

Nie wnikałem w obsługę winapiowego scrollbara, ale jak już go subclassujesz, to może złap mousedown, sprawdź shifta i jeśli jest, oblicz pozycję i ją ustaw.
tldr: zrób to „normalnie” ;-)

0

@Azarien: tylko, że ja chcę aby ScrollBar reagował tak, jakby zawsze był wciśnięty Shift. Nawet kiedy nie jest. Natomiast pobieżnie wertując dokumentację MSDN i opis typu ScrolInfo, nie doszukałem się informacji, w jaki sposób pobrać klikniętą pozycję i ją obliczyć tak żeby od razu przesunąć w te miejsce. Prosił bym o jakiś przykładowy kod. Z góry dziękuję :)

0

pobrać pozycję myszy możesz w WM_LBUTTONDOWN, a wyliczyć to już samemu...

0

Czyli jeśli chcę reagować tylko na Page Left i Right to powinienem raczej w obsłudze WM_HSCROLL pobrać pozycję X kursora, zrobić ClientToScreen i przeliczyc wynik współrzednej X według Left i Width ScrollBara do jego Max. Dobrze kombinuję? Tylko z przeliczeniem będę musiał się nagłowić, bo jak wiadomo z matmy jestem noga :/

0

A próbowałeś SetKeyboardState na czas wykonywania obsługi ScrollBara?

0

Tego nie próbowałem, bo mogę się mylić, ale o ile GetKeyboardState z krótkim Sleepem rzędu 100 ms może robić za globalny czytnik klawiszy w całym systemie. O tyle zdawało mi się, że wspomniana przez Ciebie funkcja trwale załącza tylko te klawisze, które mają diody na klawiaturze. Mylę się? Poza tym nie wiem czy ScrollBar się da tak "oszukać". Pokombinuje jutro kiedy będę w domu. Póki co wydedukowałem taki wzór na ustalanie pozycji. Chyba jest ok?

A = Szerokość'obszaru scrolla bez strzałek div Kliknięta współrzedna X
Pozycja do ustawienia = Max scrollbara div A

0

Podejrzewam, że Scrollbar robi sobie GetGeyState (w końcu to dzieło MS). W takim układzie powinno zadziałać ;)

2

Pod VCL z SubClasingiem ScrollBara uzyskałem (chyba czyli o ile dobrze zrozumiałem o co chodzi) taki efekt poprzez pseudo (czyli niezbyt dokładne ale powinno być zadowalające) wyliczenie i zmianę pozycji po kliknięciu. Wyliczam pozycję w taki sposób (kod dla pionowego ScrollBara ale łatwo można dostosować):

procedure TForm1.ScrollBarProc(var Message: TMessage);
var
  R: TRect;
  yArrowBtnSize, yPos, minPos, maxPos: Integer;
begin
  if Message.Msg = WM_LBUTTONDOWN then
  begin
    Windows.GetClientRect(ScrollBar1.Handle, R);//wielkość scrollbara
    GetScrollRange(ScrollBar1.Handle, SB_CTL, minPos, maxPos); //min i max scrollbara
    yArrowBtnSize:= GetSystemMetrics(SM_CXVSCROLL); //wielkość V strzałki
    yPos:= HIWORD(Message.lParam); //pozycja Y gdzie kliknięto
    yPos:= yPos - yArrowBtnSize; //pozycja kliknięcia scroolbara minus wielkość Y przycisku strzałki
    R.Bottom:= R.Bottom - 2 * yArrowBtnSize; //wysokość scrollbara bez strzałek
    yPos:= Round(yPos / R.Bottom * (maxPos - minPos)); //wyliczenie pozycji (oczywiście nie super dokładne)
    if (yPos >= minPos) and (yPos <= maxPos) then //czy pozycja mieści się w zakresie (jezeli nie to kliknięto strzałkę)
    begin
      SetScrollPos(ScrollBar1.Handle, SB_CTL, yPos, True); //zmiana pozycji scollbara
      Message.Result:= 0;
      exit;
    end;
  end;

  Message.Result:= CallWindowProc(pOldSBProc, ScrollBar1.Handle,
      Message.Msg, Message.WParam, Message.LParam);
end;

EDIT// aby było dokładnie trzeba by jeszcze wziąc pod uwagę border (i może coś jeszcze) wszędzie tam gdzie wielkość strzałek.

0

Bardzo dziękuję @kAzek. Powinienem sobie poradzić z dostosowaniem tego do poziomego scrollBara, który używam w swojej aplikacji. I nie musi być super dokładnie tam gdzie to zastosuje. Ostateczne rozwiązamie podam jutro.

EDIT: Zatwierdzam rozwiązanie, które podal @kAzek. Ponieważ w swoim kodzie chyba źle obliczałem nową pozycję. Dlatego skorzystałem ze sprawdzonego rozwiązania. Dołączam do tego posta kod. Przy okazji dwa moduły WinAPI, które mogą się Wam przydać, kiedy będziecie pisać poza VCL. A i ScrollBar dolny, czyli "według kAzka", nie reaguje na zmiany Page i strzałek, ponieważ jest zrobiony tylko na szybko aby sprawdzić Jego kod. Za to ten pierwszy od góry działa idealnie tak, jak chciałem.

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