Главная страница
    Top.Mail.Ru    Яндекс.Метрика
Форум: "Media";
Текущий архив: 2005.01.02;
Скачать: [xml.tar.bz2];

Вниз

Артефакты при отрисовке "резиновой нити"   Найти похожие ветки 

 
Albert ©   (2004-09-16 11:25) [0]

Необходимо сделать оболочку, работающую с графическими файлами. Попробовал начать с тестовой задачи, отображающей содержимое директории с JPEG-файлами (аналог ACDSee). Неожиданно обнаружились серьезные проблемы с реализацией выделения картинок с помощью "резиновой нити".
 Сначала использовал компонент TDrawGrid, но при движении "резиновой рамки" и выделении ячеек (компонент сам выполняет обновление с помощью Invalidate) на форме оставались незатертые участки "резиновой рамки" (артефакты). Кроме того, не понравилось, что нельзя плавно скроллировать вверх-вниз таблицу с шагом, меньшим шага сетки.
 Переделал пример, использовав в качестве фона для вывода образов (Thumbnail) JPEG-файлов объект TImage. Пока реализовал только выделение ячеек "тыканьем" в картинку и "резиновой нитью" (при прижатой левой кнопке мыши тащишь и захватываешь). Пробовал рисовать выделение синим образов картинок прямо в ходе движения (до события OnMouseUp), но началось сильное мерцание компонента. Оставил лишь снятие выделения при начале растягивания рамки, но опять появляются артефакты. Пытался использовать методы выбора контекстов устройств HDC, описанные в HELP по Windows SDK (сделал рисование рамки напрямую на контексте экрана, а не на Canvas объекта TImage), но это не помогло - я не могу управлять обновлением компонента TImage, сделает ли это система до отрисовки очередного участка рамки или после. Артефакт пропадает, если прокрутить TImage вверх-вниз, но от этого не легче.
 При всем этом ACDSee выделяет рамкой Thumbnails влет, без всякого мерцания и артефактов. Неужели средствами Delphi эту проблему не решить? Если для такой вполне второстепенной задачи требуется освоение С++  - дело дрянь.
 Прилагаю часть исходника, касающуюся работы с мышью. Привел бы сразу все (чтобы запустить), но не знаю, как прицепить архивированный файл. Вышлю на Ваш E-Mail по первому требованию (если ответ будет по делу - готов и доплатить).

.
.
.
var
 Form1: TForm1;
 slBitMap: TStringlist;
 Search_Rec: TSearchRec;
 ScrollBox1 : TMyScrollbox;
 Image1, Image3 : TImage;
 Image2, Image4 : TPicture;
 N_Thumb, Number_1Cell, Flag_Thread1, Flag_Thread2: Integer;
 A_Rect : array of TRect;
 Drawing, Flag_1Cell, Flag_CTRL, Flag_All: Boolean;
 A_Select, A_RubberZone, A_Draw : array of Boolean;
 Thread1 :TMyThread;
 Thread2 :TMyThread1;
 Origin, MovePt, P1, P2, P3, P4 : TPoint;
 P : array [0..4] of TPoint;

 const
 Path = "D:\AAA\";   // Здесь укажите директорию с большим >100)
                     // количеством JPEG-файлов
 Pic_Width = 64;
 Pic_Height = 48;
 Grid_Width = 80;   // Ширина ячейки сетки
 Grid_Height = 70;  // Высота ячейки сетки
 Text_Height = 14;
 N_Col = 5;         // Число колонок таблицы

.
.
.
procedure TMyScrollbox.FormMouseDown(Sender: TObject; Button: TMouseButton;
 Shift: TShiftState; X, Y: Integer);
var I, A_Col, A_Row, N_Select : Integer;
begin
 Drawing := True;
 Origin := self.ClientToScreen(Point(X, Y));
 Origin.Y := Origin.Y + Image1.Top;
 MovePt := Origin;
 P1 := Origin;
 P2 := Origin;
 P3 := Origin;
 P4 := Origin;
// Определение номера указанной картинки
 A_Col := X div Grid_Width;
 A_Row := Y div Grid_Height;
 N_Select := A_Row * N_Col + A_Col;
// Определяем, выделять или нет ячейки
 for I := 0 to slBitmap.Count -1 do
   if I <> N_Select then
      begin
       if (not Flag_CTRL) then
         if A_Select[I] <> False then
           begin
             A_Select[I] := False;
// Определяем номера строки и столбца
             A_Row := I div N_Col;
             A_Col :=  I mod N_Col;
             DrawSelect(A_Col, A_Row, A_Select[I]);
           end
         else
      end
   else
     begin
       if A_Select[N_Select]  then
          begin
             if (not Flag_1Cell) then
                begin
                   A_Select[N_Select] := False;
                   A_Row := I div N_Col;
                   A_Col :=  I mod N_Col;
                   DrawSelect(A_Col, A_Row, A_Select[N_Select]);
                end
           end
       else
         begin
           A_Select[N_Select] := True;
           Flag_1Cell := True;
           Number_1Cell := N_Select;
           A_Row := I div N_Col;
           A_Col :=  I mod N_Col;
           DrawSelect(A_Col, A_Row, A_Select[N_Select]);
         end;
     end;
end;

procedure TMyScrollbox.FormMouseUp(Sender: TObject; Button: TMouseButton;
 Shift: TShiftState; X, Y: Integer);
var  Handle_Pen :HPEN;
    HC : HDC;
    I, A_Col, A_Row : Integer;
 begin
 if Drawing then
 begin
// Затирание старого прямоугольника
   Handle_Pen := CreatePen(PS_DOT, 0,  RGB(0,0,0) );
   HC := GetDC(0);
   SetBKMode(HC, Transparent);
   SelectObject(HC, Handle_Pen);
   SetROP2(HC, R2_NOT);
   P[0] := P1;
   P[1] := P2;
   P[2] := P3;
   P[3] := P4;
   P[4] := P1;
   Polyline(HC, P, 5);
   Drawing := False;
// Отрисовка выделенных рамкой
   for I := 0 to slBitmap.Count -1 do
     begin
// Обновление списка выделенных ячеек с учетом выборки резиновой нитью
     if A_RubberZone[I] then
       begin
         if A_Select[I] then A_Select[I] := False
         else A_Select[I] := True;
       A_Col := I mod N_Col;
       A_Row := I div N_Col;
       ScrollBox1.DrawSelect(A_Col, A_Row, A_Select[I]);
         A_RubberZone[I] := False;
       end;

     end;
     Flag_1Cell := False;
 end;
end;

procedure TMyScrollbox.FormMouseMove(Sender: TObject; Shift: TShiftState;
                                     X, Y: Integer);
var Xmin, Xmax, Ymin, Ymax: Integer;
  {Flag: Boolean;}
var  Handle_Pen :HPEN;
    HC : HDC;
    K, A_Col, A_Row, X0, Y0 : Integer;
begin
 if Drawing then
 begin
   Handle_Pen := CreatePen(PS_DOT, 0,  RGB(0,0,0) );
   HC := GetDC(0);
   SetBKMode(HC, Transparent);
   SelectObject(HC, Handle_Pen);
   SetROP2(HC, R2_NOT);
   P[0] := P1;
   P[1] := P2;
   P[2] := P3;
   P[3] := P4;
   P[4] := P1;
   Polyline(HC, P, 5);
// Погасить 1-ю выделенную ячейку при начале использования
// резиновой нити
   XMin := Min(ScreenToClient(Origin).X, X);
   XMax := Max(ScreenToClient(Origin).X, X);
   YMin := Min(ScreenToClient(Origin ).Y- Image1.Top, Y);
   YMax := Max(ScreenToClient(Origin).Y- Image1.Top, Y);
.
.
.
   P2.X := MovePt.X;
   P3   := MovePt;
   P4.Y := MovePt.Y;
// Выделение резиновой рамкой
   P[0] := P1;
   P[1] := P2;
   P[2] := P3;
   P[3] := P4;
   P[4] := P1;
   Polyline(HC, P, 5);
// Определение попадания ячеек в зону рамки
   for K := 0 to slBitmap.Count - 1 do
     begin
       A_Col := K mod N_Col;
       A_Row := K div N_Col;
       X0 := A_Col * Grid_Width ;
       Y0 := A_Row * Grid_Height;
       if ((X0 + (Grid_Width div 3)  - XMin) > 0) and
          ((X0 + ((2 * Grid_Width)  div 3) - XMax) < 0) and
          ((Y0 + (Grid_Height div 3) - YMin) > 0) and
          ((Y0 + ((2 * Grid_Height) div 3) - YMax) < 0) then
                                    A_RubberZone[K] := True
       else A_RubberZone[K] := False;
     end;
  end;
end;


 
wicked ©   (2004-09-16 14:35) [1]

вообще то лучше использовать TListView...


 
Mihey_temporary ©   (2004-09-16 15:56) [2]

Пришли ка мне на мыло (mihey77@hotmail.com) если там размер скажем меньше 1 мегабайта.


 
Рыба ©   (2004-09-16 18:14) [3]

Может попробовать рисовать не на рабочем столе

 HC := GetDC(0);

а на самом компоненте?

 HC := GetDC(Handle);

ЗЫ: Delphi ни в чем не виновата. Если не устраивает какой-то компонент надо писать свой.


 
Рыба ©   (2004-09-16 18:33) [4]

>Кроме того, не понравилось, что нельзя плавно скроллировать вверх-вниз таблицу с шагом, меньшим шага сетки.

Насколько я понимаю, если шаг скроллинга будет меньше шага сетки то придется отрисовывать неполные ячейки. Но может это и не проблема, а сделано по иной причине..

Если TMyScrollbox потомок TScrollingWinControl, то шаг можно изменить
VertScrollBar.Increment := y;
HorzScrollBar.Increment := x;


 
Albert ©   (2004-09-20 14:46) [5]

Рисование на компоненте, если не ошибаюсь, то же, что рисовать на Canvas объекта TImage. Приводило к сильному мерцанию при перетаскивании рамки. Поетому и перешел на рабочий стол.


 
Рыба ©   (2004-09-21 17:29) [6]

>Albert ©  (20.09.04 14:46) [5]
>Рисование на компоненте, если не ошибаюсь, то же, что рисовать на Canvas объекта TImage. Приводило к сильному мерцанию при перетаскивании рамки. Поетому и перешел на рабочий стол.

Мой вам совет - TImage нигде не использовать! Это плохой компонент. При малейшем изменении канвы он полность перерисовывается. Поэтому и мерцает.

В общем, попробуйте сделать следущее.
1. В FormMouseDown рисовать резиновую нить после перерисовки ячейки (выделенной).
2. В FormMouseMove стирать повторной прорисовкой резиновую нить перед перерисовкой ячеек, выделенных рамкой, а рисовать с новыми координатами уже после этого, в конце процедуры.
3. В FormMouseUp стирать рамку повторной прорисовкой в самом начале.
4. Рисовать на самом компоненте.
5. Вместо вот этого кода:

 Handle_Pen := CreatePen(PS_DOT, 0,  RGB(0,0,0) );
 HC := GetDC(0);
 SetBKMode(HC, Transparent);
 SelectObject(HC, Handle_Pen);
 SetROP2(HC, R2_NOT);
 P[0] := P1;
 P[1] := P2;
 P[2] := P3;
 P[3] := P4;
 P[4] := P1;
 Polyline(HC, P, 5);


использовать следующий:

 DrawFocusRect(GetDC(Handle), FRect);

где в FRect записаны координаты (не экранные) нужного прямоугольника.
То есть:

procedure TMyScrollbox.FormMouseDown(Sender: TObject; Button: TMouseButton; Shift: TShiftState; X, Y: Integer);
...
FRect := Rect(x, y, x, y);
...
procedure TMyScrollbox.FormMouseMove(Sender: TObject; Shift: TShiftState;  X, Y: Integer);
...
FRect := Rect(FRect.x, FRect.y, x, y);


----
А TImage выкинуть!!!


 
wicked ©   (2004-09-21 19:55) [7]

немного поправлю уважаемого Рыба [6]:
> 1. В FormMouseDown рисовать резиновую нить после перерисовки
> ячейки (выделенной).
> 2. В FormMouseMove стирать повторной прорисовкой резиновую
> нить перед перерисовкой ячеек, выделенных рамкой, а рисовать
> с новыми координатами уже после этого, в конце процедуры.
> 3. В FormMouseUp стирать рамку повторной прорисовкой в самом
> начале.
> 4. Рисовать на самом компоненте.
нить рисовать нужно только в обработчике сообщения для рисования... поскольку стандартные компоненты (окромя TForm) не позволяют перехватить это сообщение, то придется работать со свойством WindowProc данного компонента - написать свою оконную процедуру и обрабатывать там WM_PAINT: отдавать сообщение компоненту, затем рисовать "резиновую нить"... так же лучше поступать и с самой формой для того, чтобы всегда иметь контроль над тем, какую область необходимо перерисовать....
в остальных обработчиках необходимо только менять состояние флагов (тащим/изменяем размеры нити), сохранять во временном TRect"е информацию о том, где была нарисована нить и делать форме/компоненту InvalidateRect для области, занимаемой нитью....


 
wicked ©   (2004-09-21 19:57) [8]

а лучше таки, чтоб без гемороя, использовать TListView - там уже всё есть....


 
Рыба ©   (2004-09-21 20:33) [9]

>wicked ©  (21.09.04 19:55) [7]

Конечно, так грамотнее :))


 
Рыба ©   (2004-09-21 21:09) [10]

>wicked ©  (21.09.04 19:55) [7]
>поскольку стандартные компоненты (окромя TForm) не позволяют перехватить это сообщение, то придется работать со свойством WindowProc данного компонента - написать свою оконную процедуру и обрабатывать там WM_PAINT: отдавать сообщение компоненту, затем рисовать "резиновую нить"... так же лучше поступать и с самой формой для того, чтобы всегда иметь контроль над тем, какую область необходимо перерисовать....

Не понял. Можно по-подробней?
По-моему всё делается стандартными средствами. Ничего не надо перехватывать.

А грамотнее - это я имел в виду работу с флагами.


 
wicked ©   (2004-09-21 22:40) [11]

> Рыба [10]

> Не понял. Можно по-подробней?
> По-моему всё делается стандартными средствами. Ничего не
> надо перехватывать.

проблема в том, чтобы рисовать нить (1)только тогда, когда надо и (2)только ту часть, которую надо.... и если второе мы можем проигнорировать, поскольку затраты на то, чтоб нарисовать прямоугольник, ничтожны, то первое мы не можем проигнорировать никак... средства VCL (TCanvas) как раз дадут нам безболезненно сделать второе (через св-во UpdateRect), но не первое (панель не сообщит, когда надо рисовать)...
почему панель?... просто обобщаем на случай любого контрола - я обычно использую TWinControl...
поэтому самым легким и безболезненным способом будет написать кусочек оконной процедуры, которая должна будет обрабатывать WM_PAINT после того, как контрол отрисует своё содержимое....
например, так:
procedure TForm1.PanelWndProc(var msg: TMessage);
var dc: HDC;
begin
   case msg.Msg of
       WM_PAINT: begin
           // отдаем отрисовку контролу, например, так:
           OldPanelWndProc(msg);
           // затем рисуем нить сами
           // здесь: код рисования нити в dc
       end;
       else // отдаем контролу всё остальное
           OldPanelWndProc(msg);
   end;
end;


соответственно, заставить это работать можно так:
// переменная-член класса TForm1
OldPanelWndProc: TWndMethod;
// инициализация (например, в OnCreate)
OldPanelProc := Panel.WindowProc;
Panel.WindowProc := PanelWndProc;
// деинициализация (например, в OnDestroy) - не обязательна
Panel.WindowProc := OldPanelProc;


таким образом, мы бесплатно получим и первое (когда рисовать) и второе (где рисовать - GetUpdateRect)...
а самое главное - чистый и ясный код:
- в мышиных обработчиках - вычисления(что и где рисовать) и InvalidateRect
- в обработчике отрисовки - собственно, отрисовка (любая, а не только, когда нам приспичит)


 
Рыба ©   (2004-09-22 21:46) [12]

>wicked ©  (21.09.04 22:40) [11]

Похоже ты перемудрил... :))
Итак по порядку.

1. Как обычно реализуется резиновая нить.
 -- Когда происходит событие MouseDown ставится какой-то флаг и запоминаются координаты статичного угла прямоугольника.
 -- При MouseMove стирается старая нить и рисуется новая с обновленными координатами.
 -- При MouseUp окончательно стирается резиновая нить.
 -- Стирается резиновая нить повторным вызовом процедуры её прорисовки. Т.е. не надо перерисовывать весь компонент. Надо лишь заново вызвать DrawFocusRect с прежними координатами. Цитата из справки: Because DrawFocusRect is an XOR function, calling it a second time with the same rectangle removes the rectangle from the screen. This function draws a rectangle that cannot be scrolled. To scroll an area containing a rectangle drawn by this function, call DrawFocusRect to remove the rectangle from the screen, scroll the area, and then call DrawFocusRect again to draw the rectangle in the new position.

--- --- ---
(Дальше уже не относится конкретно к теме.)

2. Часть, которую надо перерисовывать. Это актуально только при сообщении операционной системы -  WM_PAINT - т.е. когда перерисовка "ненасильственная". Получается вызовом GetClipBox или через свойство Canvas.ClipRect, что одно и то же.

3. Перехватывать WndProc нет никакого смысла, т.к. можно просто переопределить обработчик WM_PAINT или метод Paint (для наследников TCustomControl и TGraphicControl). Пример:

TMyPanel = class(TWinControl)
private
 procedure WMPaint(var Message: TWMPaint); message WM_PAINT;
end;

procedure TMyPanel.WMPaint(var Message: TWMPaint);
begin
 inherited; // вызов прорисовки предка
 // А здесь уже рисуем самостоятельно
end;


Либо

TMyPanel = class(TPanel)
protected
   procedure Paint; override; // переопределяем метод
end;

procedure TMyPanal.Paint;
begin
 inherited; // вызов прорисовки предка
 // Рисуем, что хотим. Можно уже на Canvas.
end;


Именно этот метод реализован в VCL.
Панель все-таки сама нам сообщит, когда и что надо перерисовать :))

>- в обработчике отрисовки - собственно, отрисовка (любая, а не только, когда нам приспичит)

Почему бы не рисовать резиновую нить когда нам приспичит не дергаясь, а когда потребует винда, рисовать то, что она требует и нужный нам прямоугольничик? Это совсем несложно. Даже не изгаляясь ;-))


 
Albert ©   (2004-09-29 17:06) [13]

Спасибо за обсуждение, изучаю предложения.

 Относительно совета не использовать TImage ( Рыба (21.09.04 17:29)  [6] ) и рисовать непосредственно на компоненте - тут трудности. TScrollBox и TPanel не имеют свойства Canvas, на них нельзя вывести картинку-Thumbnail напрямую (методы копирования картинки вроде CopyRect - под вопросом, поскольку при этом скроллинг невозможен).
 Опробовал работу с DrawFocusRect - рамка стала похожей на стандартный инструмент Windows, упростил текст обработчиков событий мыши.
 Относительно последовательности отрисовки:
> 1. В FormMouseDown рисовать резиновую нить после перерисовки ячейки (выделенной).
> 2. В FormMouseMove стирать повторной прорисовкой резиновую нить перед перерисовкой ячеек, выделенных рамкой, > а рисовать с новыми координатами уже после этого, в конце процедуры.
>3. В FormMouseUp стирать рамку повторной прорисовкой в самом начале.

 В FormMouseDown нить НЕ РИСУЕТСЯ. Здесь мы только запоминаем начальную точку Origin и присваиваем флаги (Drawing).
 В FormMouseMove все сделано именно так, но это не помогает. А все потому (по моему мнению), что и отрисовка нити, и рисование выделенной ячейки-Thumbnail - это процессы неизвестной продолжительности, запускаемые Windows практически параллельно. Они могут продолжаться вплоть до следующего цикла выполнения обработчика FormMouseMove, могут и пересекаться. В процедурном программировании все работало бы нормально, поскольку выполнение шло бы в соотвествии с порядком вызова в теле программы (пока не кончится одно - не начнется другое). У Windows - не так. Хорошо бы запускать перерисовку ячейки ТОЛЬКО ПОСЛЕ затирания нити, но для этого нужно 1) получить сообщение, 2) задержать начало до этого сообщения. Короче, привести все к процедурному типу?
 Относительно подмены обработчика WM_PAINT - мысль очень интересная, но для чайника (извините за тупость) изложено несколько абстрактно. Если я после Inherited заколочу отрисовку нити, тогда эта нить и будет отрисовываться при каждом чихе? В использовании механизма сообщений я пока слабоват.


 
Рыба ©   (2004-09-29 20:17) [14]

>Albert ©  (29.09.04 17:06) [13]
>TScrollBox и TPanel не имеют свойства Canvas

TScrollBox может и не имеет, но TPanel точно знаю что имеет, только скрытым. Можно сделать так:

type TXPanel = class(TPanel);
...
with TXPanel(Panel1).Canvas do
begin
 //здесь рисуем на канве панели
end;


Но вместо CopyRect можно использовать BitBlt. Для него Canvas не нужен. И вывод картинок лучше делать напрямую через BitBlt.

>А все потому (по моему мнению), что и отрисовка нити, и рисование выделенной ячейки-Thumbnail - это процессы неизвестной продолжительности, запускаемые Windows практически параллельно.

Насколько мне известно все визуальные компоненты работают в одном потоке. А значит и отрисовка идет последовательно.
Если в FormMouseMove действия:
- Стираем старую нить
- Рисуем необходимые ячейки
- Рисуем нить с новыми кординатами
именно в этой последовательности, то должно работать.

>Если я после Inherited заколочу отрисовку нити, тогда эта нить и будет отрисовываться при каждом чихе?

ДА.

Может выложите где-нибудь исходники? Или пришлите на filinpro@list.ru
Интересно, где все-таки собака зарыта. :))


 
Albert ©   (2004-09-30 14:50) [15]

Ураа!!! Заработала!
 Вставил собственную процедуру WMPaint:

procedure TMyScrollBox.WMPaint(var Message: TWMPaint);
begin
// Если резиновая рамка уже нарисована - затираем
   if Drawing then  DrawFocusRect(GetDC(Handle), FRect);
   inherited;
// Восстановление рамки
   if Drawing then  DrawFocusRect(GetDC(Handle), FRect);
end;


 После этого наконец-то убрались незатертые остатки рамки, появляющиеся при выделении/снятии выделения ячеек.
 Для меня это решение было не очевидным, поскольку казалось, что достаточно отрисовки в обработчике FormMouseMove. Изложенная ранее последовательность таки не работала!
 Отправил на E-Mail <Рыба> архивированный проект. Для критики там большой простор, тем более что попробовал загрузку картинок в два потока.
 Должен констатировать, что в учебниках (напр., Фаронов) идеология решения подобных конфликтов не приводится, а понять механизм обновления компонент и использования сообщений не так-то просто.



Страницы: 1 вся ветка

Форум: "Media";
Текущий архив: 2005.01.02;
Скачать: [xml.tar.bz2];

Наверх





Память: 0.55 MB
Время: 0.048 c
14-1103197373
Девушка
2004-12-16 14:42
2005.01.02
Максимальное количество адресатов


3-1101370985
zom
2004-11-25 11:23
2005.01.02
SHRINKDATABASE - плюсы и минусы сего действия?


1-1103377379
DimaK
2004-12-18 16:42
2005.01.02
компанент типа TDateTimePicker


14-1102260533
Урфин Джюс
2004-12-05 18:28
2005.01.02
Подводим итоги года: 2004


3-1102101932
lapatoc
2004-12-03 22:25
2005.01.02
OLE объекты в БД





Afrikaans Albanian Arabic Armenian Azerbaijani Basque Belarusian Bulgarian Catalan Chinese (Simplified) Chinese (Traditional) Croatian Czech Danish Dutch English Estonian Filipino Finnish French
Galician Georgian German Greek Haitian Creole Hebrew Hindi Hungarian Icelandic Indonesian Irish Italian Japanese Korean Latvian Lithuanian Macedonian Malay Maltese Norwegian
Persian Polish Portuguese Romanian Russian Serbian Slovak Slovenian Spanish Swahili Swedish Thai Turkish Ukrainian Urdu Vietnamese Welsh Yiddish Bengali Bosnian
Cebuano Esperanto Gujarati Hausa Hmong Igbo Javanese Kannada Khmer Lao Latin Maori Marathi Mongolian Nepali Punjabi Somali Tamil Telugu Yoruba
Zulu
Английский Французский Немецкий Итальянский Португальский Русский Испанский