Это уже достаточно старая задача, лет семь (если не отказала память) живет в моих тестах на профпригодность, выдаваемых кандидатам при собеседовании.
В отличии от прошлой задачи, здесь не требуется знаний о памяти процесса включающую работу стека и прочее, в ней тестировалось знание VCL, как она реагирует при взаимодействии с прямыми API вызовами, где сидит засада в общем виде и углубленка по GDI.
Итак, на скорую руку ваяем приложение в котором есть форма, кнопка и TMemo.
Дано 4 варианта реализации кода обработчика кнопки:
Суть данного кода проста - берем текст, который содержится в Memo и тупо выводим его на канву формы.
Вариант того, что отрисовка должна быть в OnPaint, не рассматриваем, он тут лишний.
В результате на форме будет либо выведен текст, либо он будет не выведен, либо уплывет бэкграунд (ХР и ниже), но текст все равно отобразится.
Задача выглядит следующим образом:
1. Определить какой из вариантов кода правилен.
2. Объяснить причину такого поведения.
3. Если выполнили первые два пункта - объяснить причину отрисовки в неправильных вариантах (ХР и ниже).
Дам пояснения:
Тот кто даст ответ на первый вопрос - хорошист. Время решения - 10 минут (без использования компьютера).
Тот кто разъяснит второй вопрос - отличник, как минимум. Время решения - час. (Отладчик в руки и вперед - пока не найдете).
А вот с третьим вопросом сложнее, такое поведение можно воспроизвести только под ХР и ниже, да и до кучи ответ на данный вопрос, как минимум требует очень хорошего знания книжек Фень Юаня, откуда можно вынести понимание данного поведения (ну этот ответ конечно на 5 с плюсом, но таких подробностей на собеседованиях я никогда не требовал, ибо дай то бог решить второй пункт :)
Удачи :)
UPD:
Для тех кто все-же против того что рисуется не в OnPaint, вот такой вариант кода:
В отличии от прошлой задачи, здесь не требуется знаний о памяти процесса включающую работу стека и прочее, в ней тестировалось знание VCL, как она реагирует при взаимодействии с прямыми API вызовами, где сидит засада в общем виде и углубленка по GDI.
Итак, на скорую руку ваяем приложение в котором есть форма, кнопка и TMemo.
Дано 4 варианта реализации кода обработчика кнопки:
var ACanvas: THandle; AText: string; begin ACanvas := Canvas.Handle; AText := Memo1.Lines[0]; TextOut(ACanvas, Button1.Left, 20, PChar(AText), Length(AText)); end; ... begin ACanvas := Canvas.Handle; AText := Memo1.Text; TextOut(ACanvas, Button1.Left, 20, PChar(AText), Length(AText)); end; ... begin AText := Memo1.Lines[0]; ACanvas := Canvas.Handle; TextOut(ACanvas, Button1.Left, 20, PChar(AText), Length(AText)); end; ... begin AText := Memo1.Text; ACanvas := Canvas.Handle; TextOut(ACanvas, Button1.Left, 20, PChar(AText), Length(AText)); end;
Суть данного кода проста - берем текст, который содержится в Memo и тупо выводим его на канву формы.
Вариант того, что отрисовка должна быть в OnPaint, не рассматриваем, он тут лишний.
В результате на форме будет либо выведен текст, либо он будет не выведен, либо уплывет бэкграунд (ХР и ниже), но текст все равно отобразится.
Задача выглядит следующим образом:
1. Определить какой из вариантов кода правилен.
2. Объяснить причину такого поведения.
3. Если выполнили первые два пункта - объяснить причину отрисовки в неправильных вариантах (ХР и ниже).
Дам пояснения:
Тот кто даст ответ на первый вопрос - хорошист. Время решения - 10 минут (без использования компьютера).
Тот кто разъяснит второй вопрос - отличник, как минимум. Время решения - час. (Отладчик в руки и вперед - пока не найдете).
А вот с третьим вопросом сложнее, такое поведение можно воспроизвести только под ХР и ниже, да и до кучи ответ на данный вопрос, как минимум требует очень хорошего знания книжек Фень Юаня, откуда можно вынести понимание данного поведения (ну этот ответ конечно на 5 с плюсом, но таких подробностей на собеседованиях я никогда не требовал, ибо дай то бог решить второй пункт :)
Удачи :)
UPD:
Для тех кто все-же против того что рисуется не в OnPaint, вот такой вариант кода:
type TForm1 = class(TForm) Button1: TButton; Memo1: TMemo; procedure FormPaint(Sender: TObject); procedure Button1Click(Sender: TObject); private ACanvas: THandle; AText: string; end; var Form1: TForm1; implementation {$R *.dfm} procedure TForm1.Button1Click(Sender: TObject); begin ACanvas := Canvas.Handle; AText := Memo1.Lines[0]; Repaint; end; procedure TForm1.FormPaint(Sender: TObject); begin if ACanvas <> 0 then TextOut(ACanvas, Button1.Left, 20, PChar(AText), Length(AText)); end;
Думаю, что aCanvas.Handle надо "дёргать" в первую очередь
ОтветитьУдалитьА потом уже Memo1.Text.
ОтветитьУдалитьПотому что он посылает WM_GetText.
А Memo1.Lines[0] лишь "надстройка".
Т.е. правильный вариант - ВТОРОЙ. Мне так кажется...
Хотя... ПОСЛЕДНИЙ наверное...
ОтветитьУдалитьСначала GetText, а потом Handle.
Почему?
Потому что иначе GetText может "дёрнуть" Handle контрола, а он - пересоздать окно.
А версия Delphi какая?
ОтветитьУдалитьХм, нюанс...
УдалитьТакое поведение гарантированно воспроизводится начиная с Delphi7 по ХЕ4 включительно (остальных версий у меня нет в наличии), но думаю и на остальных будет такая же петрушка.
Признаюсь, ответа не знаю и поиск по исходникам мне не помог.
ОтветитьУдалитьЭто даже не ответ на поставленный вопрос, но 1-й вариант не отрисовывает совсем ничего.
1) Сразу выбрал варианты 3 и 4 т.к. создание hdc лучше делать непосредственно перед "использованием", ну или если через tcanvas - использовать методы lock/unlock
ОтветитьУдалить2) На этом этапе к выбранным 3 и 4 добавляется вполне корректно работающий вариант 2. Значит вся "соль" в разности работы 1 и 2. Более грамотного объяснения чем следующее дать не смогу:
Первый вариант не работает т.к. происходит смена контекста вывода при обработке сообщения EM_GETLINE, отправка которого осуществляется при запросе строки - AText := Memo1.Lines[0]. При работе второго варианта смена контекста не выполняется.:)
3) Могу только предположить, что на вывод (корректность вывода) в ОС XP и ниже могут оказать различные параметры выравнивания текста.
Нет правильного варианта. 1. Неправильное место (button click), 2. Канвас не подготовлен для вывода текста
ОтветитьУдалитьВ VCL всё рисование всегда заключается в Lock/Unlock (Paint, PaintTo, OnPaint и т.п.), либо производится на локально выделенном DC. Соответственно, если этот код вызывать в OnPaint, то работать он будет. Иначе нет гарантий, что канва не будет пересоздана. По этому соображению методы 3 и 4 "более правильные", т.к. канва берётся позднее и нет промежуточных операций.
ОтветитьУдалитьДалее, есть подозрение, что разница между методами в том, что один из них дёргает SendMessage, а второй - Perform. Соответственно, SendMessage приводит к полноценному циклу обработки, в котором, кажется, вызывается FreeDeviceContexts.
В целом, я бы заключил код в Lock/Unlock, либо выполнял бы его из OnPaint.
Интересно... почему до сих пор никто не увидел самого простого в этих вариантах..
ОтветитьУдалитьВ задаче явно не оговорено содержимое Memo, а значит Memo1.Lines[0] не может быть правильным, т.к. вернёт только первую строку многострочного текста (в отличии от Memo1.Text).
Почему-то я думаю, что именно это и есть ответ на 4ку :)
Хотя, если предположить, что текст заранее определён однострочным (неточность в условии), то тут есть что подумать (насчёт уплывания фона и прочих неприятностей).
ОтветитьУдалитьУ меня мысли такие.
OnClick - это событие, которое приходит из очереди сообщений. Следом за OnClick пойдёт перерисовка кнопки в отпущенном состоянии (т.к. перед OnClick она была нарисована нажатой), перерисовку "дёрнет" сообщение типа WM_PAINT, а перед ним, обычно, приходит ERASEBACKGROUND. Эти сообщения на перерисовку инициируется вызовом Invalidate (или InvalidateRect). Таких вызовов в общем случае может быть много, поэтому ОС их запоминает, а ERASEBACKGROUND и PAINT получаются отложенными до определённого момента.
Соответственно вызовы Memo.Lines[0], Memo.Text, Canvas.Handle - могут как-то повлиять на очередь WM_PAINT.
Это я пишу по памяти, т.к. ковырял VCL, читал Фень Юаня (замечательнейшая книга!) и писал компоненты.
Поэтому, если взять в руки отладчик, то на что обращать внимание - для меня понятно. Но я подожду ответа уважаемого (без сарказма) автора. Спасибо
>> 1. Определить какой из вариантов кода правилен.
ОтветитьУдалитьЯ тоже считаю, что правильного варианта здесь нет. Рисовать нужно в OnPaint.
Для тех кто против отрисовки вне OnPaint, добавил и такой вариант. :)
ОтветитьУдалитьRouse_, а текст-то всё же однострочный или нет?
ОтветитьУдалитьВ данном случае роли не играет, допустим, пусть будет однострочный.
УдалитьЯ один, что ли тут на := Memo1.Lines[0]; смотрю с большим опасением? У Lines, что ли не бывает Count = 0? Я посмотрю, конечно. Но, даже если так, то сам такие вещи делать бы всё равно не стал - Index out of bounds - плохая штука.
ОтветитьУдалитьTCanvas в данном случае является TControlCanvas, для которых Device context'ы хранятся в глобальном "пуле" ограниченного размера. При создании нового контекста, VCL приходится освобождать место в пуле, освобождать ранее созданные контексты.
ОтветитьУдалить