Płynne przesuwanie Image

0

Witam. Mam taki kod jak poniżej, no i on działa, ale nie do końca w poprawny spsoób.
Mianowicie chce uzyskać taki efekt jak przy powiększonym obrazku podglądanym w
chociażby AcdSee. Czyli że moge po kliknięciu na niego przesuwać go myszką w doł,
gótę, lewo lub prawo. Jednak w AcdSee można przy klikniętym przycisku myrzki, po
przesunięciu na przykład w dół zmienić kierunek przesuwania w gótę. Kombinowałem
już, ale nic mi nie wychodzi. Teraz przesuwając w doł mogę dopiero przesunąc - na
przykład - w górę dopiero jak puszcze przycisk myszki i klikne na nowo. Kody, ktore
znalazłem w sieci przesuwają obrazek po formatce przy użyciu merody Drag&Drop i
powodują, że owszem obrazek na inne miejsce można przesunąć ale tylko jeżeli owo
docelowe miejsce jest puste i nie należy do borazka. Jakby ktoś miał jakieś pomysły
lub fragmenty kodów (może kiedyś też dla siebie pisaliście prostą przeglądarkę grafiki)
to proszę piszcie Z góry dziękuję. Oto mój dotychzasowy kod, który działa tak sobie:

var
  PreviewForm : TPreviewForm;
  Punkt : TPoint;
  Kliknieto : boolean;

implementation

{$R *.dfm}

procedure TPreviewForm.Image1MouseDown(Sender: TObject; Button: TMouseButton;
Shift: TShiftState; X, Y: Integer);
begin
  Kliknieto := True;
  Punkt := Point(X, Y);
  Image1.BeginDrag(True);
end;

procedure TPreviewForm.Image1MouseUp(Sender: TObject; Button: TMouseButton;
  Shift: TShiftState; X, Y: Integer);
begin
  Kliknieto := False;
end;

procedure TPreviewForm.Image1DragOver(Sender, Source: TObject; X,
Y: Integer; State: TDragState; var Accept: Boolean);
const
  IlePixSkok = 10;
begin
  if Y < Punkt.Y then
  begin
    if Image1.Top <> 0 then
      Image1.Top := Image1.Top + IlePixSkok;
  end;

  if Y > Punkt.Y then
  begin
    if Image1.Top + Image1.Height > ClientHeight then
      Image1.Top := Image1.Top - IlePixSkok;
  end;

  if X < Punkt.X then
  begin
    if Image1.Left <> 0 then
      Image1.Left := Image1.Left + IlePixSkok;
  end;

  if X > Punkt.X then
  begin
    if Image1.Left + Image1.Width > ClientWidth then
      Image1.Left := Image1.Left - IlePixSkok;
  end;
end;
0
type
  TForm1 = class(TForm)
    Image1: TImage;
    procedure Image1MouseDown(Sender: TObject; Button: TMouseButton;
      Shift: TShiftState; X, Y: Integer);
    procedure FormMouseMove(Sender: TObject; Shift: TShiftState; X, Y: Integer);
    procedure FormMouseUp(Sender: TObject; Button: TMouseButton;
      Shift: TShiftState; X, Y: Integer);
    procedure FormCreate(Sender: TObject);
  private
    FDragOffset: TPoint;
  public
    { Public declarations }
  end;

var
  Form1: TForm1;

implementation

{$R *.dfm}

procedure TForm1.FormCreate(Sender: TObject);
begin
  FDragOffset.X := -1;
end;

procedure TForm1.FormMouseMove(Sender: TObject; Shift: TShiftState; X,
  Y: Integer);
begin
  if FDragOffset.X >= 0 then
  begin
    Image1.Left := Max(0, Min(ClientWidth - Image1.Width, X - FDragOffset.X));
    Image1.Top := Max(0, Min(ClientHeight - Image1.Height, Y - FDragOffset.Y));
  end;
end;

procedure TForm1.FormMouseUp(Sender: TObject; Button: TMouseButton;
  Shift: TShiftState; X, Y: Integer);
begin
  MouseCapture := False;
  FDragOffset.X := -1;
end;

procedure TForm1.Image1MouseDown(Sender: TObject; Button: TMouseButton;
  Shift: TShiftState; X, Y: Integer);
begin
  FDragOffset.X := X;
  FDragOffset.Y := Y;
  MouseCapture := True;
end;
0

Dzieki adf, o to mi chodziło - tylko dosowałem sobie jedno zdarzenie tak aby działało jak chce i teraz mam:

procedure TPreviewForm.FormMouseMove(Sender: TObject; Shift: TShiftState; X,
  Y: Integer);
begin
  if FDragOffset.X >= 0 then
  begin
    Image1.Left := Max(-1 * (Image1.Width - ClientWidth), Min(0, X - FDragOffset.X));
    Image1.Top := Max(-1 * (Image1.Height - ClientHeight), Min(0, Y - FDragOffset.Y));
  end;
end;

Jeszcze raz dzięki :)

0

-1 * (Image1.Width - ClientWidth)
nie lepiej

ClientWidth - Image1.Width

:>

Poza tym coś namieszałeś. Chodzi ci o to aby można było wysunąć obrazek za krawędź tak ?
Więc powinno być:

const MIN = 5; //minimalna ilość pikseli widocznych jeśli schowamy część obrazka za krawędź (bo jeśli schowamy cały to nie będzie go jak złapać później)
//wszystkie krawędzie
    Image1.Left := Max(MIN - Image1.Width, Min(ClientWidth - MIN, X - FDragOffset.X));
    Image1.Top := Max(MIN - Image1.Height, Min(ClientHeight - MIN, Y - FDragOffset.Y));
//lub tylko prawa i dolna
    Image1.Left := Max(0, Min(ClientWidth - MIN, X - FDragOffset.X));
    Image1.Top := Max(0, Min(ClientHeight - MIN, Y - FDragOffset.Y));

Ważne jest też aby Max było na zewnątrz a Min wewnątrz Max, wtedy obrazek niemieszczący się na formie będzie wyrównany do lewej-górnej krawędzi, a nie do prawej-dolnej.

0

Dzięki za wskazówki. Zawsze byłem słaby z matmy i nie pomyślałem żeby odwrócić działanie bez minus 1:

procedure TPreviewForm.FormMouseMove(Sender: TObject; Shift: TShiftState; X,
  Y: Integer);
begin
  if FDragOffset.X >= 0 then
  begin
    Image1.Left := Max(ClientWidth - Image1.Width , Min(0, X - FDragOffset.X));
    Image1.Top := Max(ClientHeight - Image1.Height, Min(0, Y - FDragOffset.Y));
  end;
end;

Teraz mam tak i działa ok. Ogólnie to na razie robiłem testy na osobnej formatce. Docelowo ów Image ma
być utworzony dynamicznie i jako Parenta mieć ScrollBox, a cała operacja przesuwania ma się odbywać
na powiększonym obrazku, w taki sposób aby użytkownik mógl zobaczyć go scrollując myszką w całości.

0

Ale dalej masz źle. Dlaczego zamieniłeś miejscami "0" z "ClientWidth - Image1.Width" ? To nie działa dobrze. Nie kłam :p

0

Po prostu tam gdzie miałem wcześniej mnożone przez -1 wstawiłem poprawioną wersję i z póki co to z
moich testów wynika że wszystko jest w porządku. Mam formatkę z borderStyle na bsNone oraz na niej
Image który ma AutoSize ustawione na True i jest większy niż widoczna formatka i przesuwą się tak jak
chciałem. A z moich testów wynika, że gdybym pozostawił zero na początku nie mogłbym w ogole poza
formatkę przesunąć obrazka w doł lun w prawo. Tak aby zobaczyć co jest niżej albo po prawej stronie.
A jak według Ciebie powinno być ,tak jak napisałeś wcześniej? Jak chcesz to pobierz sobie plik z tego
adresu; http://www.speedyshare.com/650740610.html i zobacz jaki jest efekt i dokladnie o to chodzi.

0

Aha, o to chodzi. Dobra, jest ok, myślałem o zupełnie innym efekcie. Ja ci podałem przepis na obrazki mniejsze od formy, ty na obrazki większe. Więc teraz trzeba coś zrobić z obrazkami, które są mniejsze od formy. Albo zabronić w ogóle przesuwania, albo przesuwać zgodnie z zasadą jaką podałem w pierwszym poście - wtedy będzie się dało je przesunąć w dowolne miejsce formy ale nie będzie się dało wysunąć (choćby częściowo) poza krawędź. Jeśli byś pozostał przy takim kodzie jaki masz teraz to obrazek trzymałby się dolnego-prawego rogu.

0

Dzięki jeszcze raz za pomoc, ale jednak moje wprawki na osobnym formularzu mimo że działały ok - to
w docelowej aplikacji już nie działąło to jak należy. Ostatecznie w docelowej aplikacji mam taki oto kod:

procedure TPreviewForm.Img_MouseMove(Sender: TObject; Shift: TShiftState; X,
Y: Integer);
begin
  if Kliknieto = True then
  begin
    {Co tutaj dodać aby obraz nie wychodził prawie poza ekran}
    ScrollBox.DoubleBuffered := True;
    Img.Left := Img.Left + X - Punkt.X;
    Img.top := Img.top + Y - Punkt.Y;
  end;
end;

I teraz prośba do Ciebie adf albo do kogoś kto ma chwilę pokombinować. Pod poniższym adresem jest
źródło mojego prostego programiku, który pisałem tak dla testów. Jakbyś mógl Ty adf albo ktoś inny w
module preview.pas (TPreViewForm) poprawić kod tak aby przesuwany obrazek nie "wychodził" poza
ekran całkowicie. Decydujące miejsce to {Co tutaj dodać aby obraz nie wychodził prawie poza ekran},
tym komentarzem oznaczyłem je w kodzie. Wcześniej kombinowałem na różne sposoby ale tutaj Img to
tworzony dynamicznie komponent do tego osadzony na rozszerzającym się ScrollBoxie. I sobie z tym
sam nie moge poradzić. Link: http://www.speedyshare.com/161362345.html i z góry przepraszam, bo
źródło może mimo że sformatowane raczej ok, to nie wiem czy każdemy odpowiadać będzie mój styl w
jakim koduje, ale ja do tej pory większość swoich aplikacji pisałem dla siebie "do szuflady" i mam pewne
nawyki jak na przykład mieszanie nazw zmiennych czy procedur raz piszę po polsku, a raz in english.
Z gory dzięki za wszelkie wskazówki. Co do przeglądarki to jednak komponent Image jeżeli chodzi o to
jak damy w nim DoubleBuffer na True to przy pewnych powiększeniach to już się np. nie scroluje ok ;/
Także chodzi o dopracowanie przesuwania Image na powiększeniu (klawisz plus na numerucznej lub
z popupmenu na obrazku), no a przesuwanie jest dostępne wtedy kiedy kursor zmieni się na "rączkę".

0

Główny problem jest spowodowany tym, że użyłeś scrollbox'a. Przy powiększeniu obrazka tak, że się nie mieści w scrollboxie (współrzedne ujemne), obrazek zmienia współrzędne na (0, 0) a scrolbox przewija się tak, aby widoczny był ten sam fragment. Albo pozbądź się scrollbox'a (na razie na kij ci on potrzebny), albo zamiast przesuwać obrazek (w OnMouseMove) scrolluj samego scrolbox'a.

Jeszcze kilka uwag:

  1. Zabiję za te magiczne liczby w FormKeyDown :-[ Pół dnia ( ;) ) spędziłem na szukaniu jakie to klawisze.
    Jako że mam laptopa to nie mam klawiatury numerycznej i nie pomyślałem że takowej użyjesz. Najpierw napieprzałem we wszystkie klawisze jakie się da, a później musiałem zamieniać liczby na hex i lookać w vk table. Zamieniłem je na odpowiednie stałe oraz dodałem obsługę klawiszy
    '0' - zoom 100%
    '-' - zoom out
    '=' - zoom in
  if ((Key = VK_ADD) or (Key = VK_OEM_PLUS)) and (ZoomIn1.Enabled = True) then
    ZoomIn1.Click;
  if ((Key = VK_SUBTRACT) or (Key = VK_OEM_MINUS)) and (ZoomOut1.Enabled = True) then
    ZoomOut1.Click;
  if ((Key = VK_MULTIPLY) or (Key = Word('0'))) and (Default1.Enabled = True) then
    Default1.Click;

Poza tym skok powiększenia x2 to za dużo. Wypadało by też dodać klawisz robiący "Dopasuj do ekranu".

  1. Dużo masz kodu który się wykonuje wielokrotnie, a wystarczyłoby raz. Np. większość FormActivate możesz przenieść do FormCreate, czy też włączenie DoubleBuffered. W kilku miejscach tak masz.

  2. Czy na formie będą jeszcze jakieś kontrolki (jakiś panel kontrolny, "X" do zamykania) widoczne podczas przesuwania obrazka ? Jeśli tak, to trzeba jeszcze przechwycić mysz po kliknięciu (MouseCapture), żeby zdarzenie OnMouseMove docierało do obrazka nawet jeśli kursorem wjedziesz na jedną z kontrolek.

  3. Opcja "Dopasuj do ekranu" powinna być jednorazową akcją która jedynie zmienia zoom (czyli nie checkbox tylko zwykła pozycja). Teraz zachowuje się to dziwnie, przy zaznaczonym "Dopasuj do ekranu" i tak można zmieniać zoom i w dodatku zmiana jest nie taka jaką można by oczekiwać, np. po kliknięciu "-" obrazek nie zmniejsza się dwukrotnie tylko zoom zmienia się na 50%. Może być to checkbox, tylko odznaczaj go

  4. Koniecznie dodaj obsługę mouse scrolla

  5. Masz np. obsługę kliknięcia popup item'a "zoom in":

procedure TPreviewForm.ZoomIn1Click(Sender: TObject);
...

Która zmienia zoom.
I masz obsługę klawisza "+" (podaje już poprawioną):

  if ((Key = VK_ADD) or (Key = VK_OEM_PLUS)) and (ZoomIn1.Enabled = True) then
    ZoomIn1.Click; //<------------

To jest nie ładne rozwiązanie. Uzależnia popup item'a od buttona. To jest nieelastyczne i trochę nielogiczne. Polecam popracować na ActionList. Komponencik jest super. Mały opis jakbyś go nie znał - tworzysz sobie listę akcji czyli procedur. Każdej akcji można przypisać Caption, Checked, Enabled, ImageIndex, Hint, ShortCut, Visible ... czyli wszystkie te rzeczy które się ustawia w różnych przyciskach, itemach, toolbarach. Później kładziesz kontrolkę np. button, podpinasz ją pod akcję (własność Action) i już sama się ona dostosowuje do tego co ustawiłeś przy danej akcji. Albo dajmy na to do jednej akcji masz podpięte 2 kontrolki (np. menuitem i toolbarbutton). Po zmianie Enabled tej akcji w obydwu kontrolkach automatycznie zmienia się też Enabled na taki jaki ustawiłeś przy akcji.

  1. Problem z dużym powiększeniem jest taki, że TImage i tak jest cały rysowany niezależnie od widocznego fragmentu i zajmuje to za dużo czasu. Rozwiązaniem byłoby pozbycie się TImage i rysowanie widocznego fragmentu samemu w OnPaint. Dzięki takiemu rozwiązaniu możesz również zrobić sobie interpolacje żeby nie było takiej pikselozy. Kwadratowa wystarczy, może być też linowa (naprawdę banalna, bierzesz średnią ważoną z najbliższych czterech pikseli - wagi zgodne z oddaleniem od środka danego piksela w metryce miejskiej, czyli suma oddaleń w kierunku x i y) ale efekt nie będzie już taki zadowalający.
0

Dziekuję za porady zastosuje się. To znaczy co do ActionList to jeszcze zobaczę, bo jakoś do tej pory
nie za bardzo umiałem je obsługiwać. Zostawiłem ją tylko, bo działała już gotowa w programie, ktory na
tym serwisie można znaleźć "Kartoteka", który nieco przerobiłem w ramach ćwiczeń sam dla siebie, a
co do tego programu to faktycznie ScrollBox wywaliłem - myślałem że będzie potrzebny, bo jak obraz
znajdzie się poza ekranem to pojawią się paski przewijania na formatce, a tak się nie stanie, bo ma ona
ustawioną BorderStyle na bsNone. Po zrezygnowaniu ze ScrollBoxa, dzięki poniższemy kodowi jest taki
efekt o jaki mi chodziło. Obraz przesuwa się prawidłowo w obrębie formatki, tak jak tego chciałem. Kod:

procedure TPreviewForm.Img_MouseMove(Sender: TObject; Shift: TShiftState; X,
Y: Integer);
var
  Img_Left, Img_Top, Img_Height, Img_Width : integer;
begin
  Img_Left := Img.Left;
  Img_Top := Img.Top;
  Img_Height := Img.Height;
  Img_Width := Img.Width;
  Application.ProcessMessages;
  if Kliknieto = True then
  begin
    if Y < Punkt.Y then
    begin
      // Do góry
      if Img_Top + Img_Height < ClientHeight then
      begin
        Kliknieto := False;
        Exit;
      end;
    end;

    if Y > Punkt.Y then
    begin
      // Na dół
      if Img.Top > 0 then
      begin
        Kliknieto := False;
        Exit;
      end;
    end;

    if X < Punkt.X then
    begin
      // W lewo
      if Img_Left + Img_Width < ClientWidth then
      begin
        Kliknieto := False;
        Exit;
      end;
    end;

    if X > Punkt.X then
    begin
      //W prawo
      if Img_Left > 0 then
      begin
        Kliknieto := False;
        Exit;
      end;
    end;
    Img.Left := Img.Left + X - Punkt.X;
    Img.top := Img.top + Y - Punkt.Y;
  end;
end;

Co do obsługi klawiszy - to tak podejrzewałem, że możesz mieć Ty lub inny użytkownik laptopa i w ogóle
nie mieć dodatkowej klawiatury, czyli brak dostępu do klawisza. Już to poprawiłem. Tylko że znowu tutaj w
zdarzeniu użyłem kodów klawiszy, ponieważ dla klawiszy "-" oraz "=" w Delphi 7 nie ma taki stałych jak je
podałeś. A spis klawiszy miałem z: http://delphi.about.com/od/objectpascalide/l/blvkc.htm i nie widziałem
tam wszysktich tych klawiszy, dlatego użyłem kodów liczbowych klawiszy, ale mogłem dodać stosowny
komentarz w kodzie. A nie dało się wywołąć klawiszy "-" oraz "=" poprzez Word('-') i Word('='), A co do
przeniesienia niektórych metod do OnCreate formatki też się zastosowałem. Dzięki za poświęceni czasu.

  if ((Key = VK_ADD) or (Key = 187)) and (ZoomIn1.Enabled = True) then
    ZoomIn1.Click;
  if ((Key = VK_SUBTRACT) or (Key = 189)) and (ZoomOut1.Enabled = True) then
    ZoomOut1.Click;
  if ((Key = VK_MULTIPLY) or (Key = Word('0'))) and (Default1.Enabled = True) then
    Default1.Click;

Jakby ktoś chciał to poprawione źródło wrzuciłem na http://www.speedyshare.com/462951860.html i
nie jest to może idealny program, ale jako prosta przeglądarka jpegów chyba może być, wiadomo że ja
mało zaawansowany programista, nie dorównam twórcą takich kombajnow - jak chociażby AcdSee :)

EDIT: jednak dodałem stałe zgodne z wykazem kodów klawiszy na msdnie, a pod powyższym linkiem
jsą póki co ostateczne źródła wersji mojej prostej przeglądarki jpgów, oznaczonej symbolem 0.9 BETA.

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