Форум: "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.035 c