|
Модераторы: Poseidon, Snowy, bems, MetalFan |
|
THandle |
|
|||
Хранитель Клуба Награды: 1 Профиль Группа: Админ Сообщений: 3639 Регистрация: 31.7.2007 Где: Moscow, Dubai Репутация: 65 Всего: 372 |
Введение.
Всем привет. Хочу в этой небольшой статейке-обзоре описать два очень удобных класса - TWriter и TReader. Даже с первого взгляда видно, что они что-то куда-то пишут и читают. Но что? Куда? Откуда? Помню, любил я в детстве эти вопросы задавать... Эх, были времена... Молодость... Ну, это мы что-то отвлеклись от темы. А работают эти классы с потоками (наследниками класса TStream). TWriter пишет в ассоциированный с ним поток данные, а TReader соответственно читает. Но зачем это нужно? Какие-то отдельные классы... Ведь в TStream'ах есть же методы Write/Read. В TStream'ах все пишется одним методом. И часто возникают вопросы, например, по записи строки. Человек подсовывает в Write сам указатель на строку, а не данные, которые начинаются с String[1], и получается, что строка в файл у него не пишется. Таких тем на форумах очень много и возникают они довольно часто. К тому же некоторые методы TStream'ов используют TWriter/TReader в своих целях - например, для записи компонентов. В рассматриваемых же классах есть готовые функции для записи/чтения любых данных. Можно записывать данные секциями. В общем, намного удобнее, чем вручную все вгонять в поток. Но, к сожалению многие просто не знают о существовании этих классов, и мучаются с вопросами, пример которых я описал выше. Я тоже когда-то таким занимался Сейчас же хочу поделиться со всеми, тем, что существует более удобный способ записи и чтения данных. Статья будет состоять из трех частей. В первой я расскажу о классе TWriter, о записи с его помощью данных в поток. Во второй - о классе TReader, и, соответственно, о чтении данных из потока. В третьей части мы рассмотрим дополнительные возможности этих классов, а так же рассмотрим, как Delphi сохраняет свои формы в файлы dfm. |
|||
|
||||
THandle |
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
Хранитель Клуба Награды: 1 Профиль Группа: Админ Сообщений: 3639 Регистрация: 31.7.2007 Где: Moscow, Dubai Репутация: 65 Всего: 372 |
Часть 1. TWriter.
Итак. Хватит просто так болтать, примемся за дело. Сначала я опишу TWriter. Ведь откуда читать, если файла/куска памяти нет? Неоткуда :( Поэтому прежде чем заняться чтением - займемся записью. Оба класса - и TWriter, и TReader имеют одного предка - TFiler. В него заложены основные методы взаимодействия с потоком. Конструктор TWriter получил от TFiler'а:
Первый параметр - ссылка на существующий экземпляр любого наследника класса TStream. Второй - любое положительное ( BufSize > 0 ) число. Отвечает за размер буфера для записи данных. Что это за буфер такой? Дело в том, что TWriter пишет не сразу в ассоциированный с ним поток данных, а в буфер, находящийся внутри класса. Объявлен он как указатель (Pointer), в конструкторе под него выделяется соответствующий кусок памяти размером BufSize. Первый вопрос, который возник лично у меня, когда я это узнал: что же то получается, с помощью одного экземпляра рассматриваемого нами класса, можно записать в поток только кусок данных размером с BufSize? На самом деле, этот буфер перезаписываемый. Допустим, указали Вы размер буфера 16 байт, записали сначала строку в 10 символов. В самом потоке этих данных еще нет, их туда никто не записывал. Теперь записываете еще 10 байт информации. А TWriter смотрит, в буфере мало места осталось, он пишет кусок данных, размером с оставшееся свободное в буфере место, после чего записывает все данные из буфера в поток и устанавливает значение полю FBufPos в 0. Это поле отвечает за позицию в буфере, с которой начинается свободная его часть, то есть FBufSize-FBufPos и есть то количество байт, которое свободно в буфере для записи данных. Еще есть такое свойство:
Оно содержит в себе размер уже всех записанных данных. И тех, которые были записаны в сам поток, и тех которые пока еще находятся в буфере. В деструкторе, конечно же, все данные из буфера записываются в поток. Чтобы принудительно записать все данные из буфера в поток и очистить буфер следует вызвать метод:
Теперь несколько слов о том, в каком виде пишутся данные. У TWriter'а есть свой формат записи данных. Все типы данных перечислены в типе:
На данном этапе разбирать каждое значение, думаю, не стоит. Займемся этим при обсуждении конкретных методов записи данных. А теперь, как раз, рассмотрим эти самые методы:
К этому методу сводится, в принципе, вся запись (Все прочие методы в конечном счете вызывают Write). Записывает кусок данных в буфер.
Понятно, что данный метод записывает в буфер переменную типа Boolean. Вроде бы все ясно и просто, НО, как мы помним, TWriter пишет данные в своем формате(TValueType). Посмотрим, что он делает с Boolean:
То есть записывает явно не значения 1 или 0(true or false), а пишет значения соответствующих элементов перечисляемого типа TValueType. В итоге получается что данные, записанные с помощью TWriter можно, не создавая себе лишних проблем, прочитать только с помощью TReader'а. Итак, WriteBoolean записывает вместо true - vaTrue, вместо false - vaFalse. Едем дальше.
Первая процедура предназначена для записи типов Byte, ShortInt, SmallInt, Integer - то есть всех чисел с диапазоном значений <= Integer. Проверяет значение и пишет соответственно vaInt8, vaInt16, vaInt32. Посмотреть подробнее как проверяет можете в Classes.pas - тут код приводить не буду, нет нужды Вторая процедура записывает целое значение, превосходящее Integer, то есть Int64. Если переданное значение меньше Integer, то вызывается первый вариант WriteInteger. Иначе записывает значение типа vaInt64. Во как по-умному сделано Теперь рассмотрим запись строк. Для этого предназначены следующие методы:
Первый(WriteStr) во всех версиях Delphi(включая 2009) записывает Ansi ShortString строку. То есть строку длиной не более 255 символов. В параметре он принимает «нормальную» (не ограниченную 255 символами) строку, и если её длинна больше только что приведенного магического числа, то строка записывается не полностью, а пишутся только первые 255 символов. Второй метод, точно так же как и предыдущий, записывает первые 255 символов UTF8 строки. Третий метод записывает строку, как есть, без всяких обрезаний. Только в версиях Delphi ниже 2009 записывает не Unicode, а ANSI строку. Соответственно, объявлена она там немного по-другому (с другим типом параметра). Четвертая процедурка из этого списка в 2009 играет ту же роль что и WriteString. Точнее все её действия сводятся к вызову этого метода. В Delphi более ранних версий записывает Unicode строку. Различным строкам из набора TValueType соответственно соответствуют: vaString - строка длиной менее 255 символов. vaLString - ANSI строка неограниченной длины. vaWString - Unicode строка. vaUTF8String - строка в кодировке UTF8. Теперь поговорим о записи символов:
Оба метода записывают символ в соответствующей кодировке. В Delphi 2009 они идентичны. WriteWideChar появился только в 2007 версии (возможно и раньше (например в 2006), но проверить, к сожалению, не могу, это уж сами. Но в Delphi 7 о таком методе еще не слыхали) . Теперь о записи действительных чисел. За это отвечают четыре следующих метода:
Ничего сверхъестественного они не делают - только пишут то, что они должны писать: значения соответствующего типа из набора TValueType. Типы для этих функций таковы: vaExtended, vaSingle, vaDouble и vaCurrency соответственно. То есть, сначала пишется тип данных, берущийся из TValueType, а потом пишутся сами данные. И так во всех случаях записи. Дата и время записываются с помощью метода:
Тут тоже ничего интересного. Из набора TValueType данным этого типа соответствует значение vaDate. Данные вариантного (Variant) типа записываются с помощью:
Переданный параметр анализируется на значения типа varXXX и записывается с помощью одного из рассмотренных уже нами методов. Значению типа Variant varEmpty соответствует из набора TValueType значение vaNil. А значению varNull - vaNull соответственно. Единственное что мне в этом методе не понятно, так это то, зачем там нужен вот такой код:
Почему бы не вызвать просто метод WriteBoolean, и не копировать код, как для других типов. Например:
Для меня осталось это загадкой - почему так сделали. Но, не важно, мелочь. В данном методе возможно возбуждение исключения, поэтому, пожалуйста, не забываете заключать свой код в блок try..finally..end. Теперь рассмотрим такой метод:
Метод этот записывает четыре идентификатора либо же просто UTF8 строчку, обозначая её как vaIdent. Это можно использовать для записи каких-то что-то идентифицирующих строк. Лично мне этот метода никогда не пригождался. Рассмотрим четыре значения параметра Ident, которые данный метод пишет не просто как строку, а как нечто неизведанное... Но нам уже знакомое Итак, это строки: 'False', 'True', 'Null', 'Nil' - все равно, в каком регистре написанные. Записываются они не как строка, а как значение типов: vaFalse, vaTrue, vaNull, vaNil соответственно. Может, кому и пригодится И перед тем как начать разбор записи самых "вкусных" элементов - компонентов, взглянем одним глазом на метод:
Этот метод пишет сигнатуру TPF0. Вот так. Просто 4 байта. Можете использовать это как Вам захочется, а то как эту сигнатуру использует Delphi, куда пишет её - узнаем чуть позже, при рассмотрении метода WriteDescendent. Ну а теперь... TWriter позволяет записывать в поток компоненты, всех их свойства, даже целые формы! На самом деле Delphi использует этот класс для формирования dfm файла формы, но об этом поговорим в самом конце, в третьей части статьи, уже, после того как разберемся со всеми нюансами записи компонентов. Для начала разберем метод записи компонента:
Этот метод записывает указанный в параметре компонент в поток. То есть записывает все данные содержащиеся в компоненте. Если свойство Root:
установлено, то компонент на которое оно указывает будет считаться корневым. Что это значит? Рассмотрим несколько примеров. Для всех примеров на форме должно быть две панели(TPanel), ну и какие либо компоненты на эти панельки накиданные. Например, сделаем так. На первой будет TEdit, TCheckBox. А на второй TListBox. 1 пример.
Что этот код делает? Он записывает в файловый поток компонент Panel1, без его дочерних компонентов. Без. Root не установлен, корня нет, идти неоткуда - вот и пишется только сам Panel1 и его свойства. Разберем второй пример. 2.
Вроде бы Root установлен. Почему не пишутся дочерние компоненты, расположенные на панели? Вроде же все правильно... А дело тут вот в чем. Да, Panel1 является Parent'ом для лежащих на ней компонентов, но, в Root надо указывать не Parent, а Owner, владельца. Для всех компонентов созданных в Design-time(во время батонокидательства) владельцем является сама форма, на которой эти компоненты расположились. Соответственно в Root надо указывать именно Owner'а, то есть форму. НО!!! Не стоит забывать, что если Вы компоненты создаете динамически, то Owner'ом может быть тот, кого установите Вы. В таких случаях Вам придется ставить в Root уже соответствующий указатель на владельца. Не забывайте об этом. Теперь два примера иллюстрирующие только что написанный текст: 1.
Таким образом отлично сохраняется сама Panel1 и все лежащие на ней компоненты с Owner = Root. То есть, если вы создадите динамически какой-то компонент, с Owner'ом отличающимся от Root'а, но с Parent'ом компонента, который входит в состав записываемых, то такой динамически созданный компонент в поток записан не будет. Но зачем говорить, рассмотрим еще один пример: 2.
Как видите динамически созданный компонент типа TPanel с именем PanelXXX, Parent'ом равным записываемому в поток компоненту(Panel1) и с отличным от Root владельцем никуда не записывается, чего и следовало ожидать. Теперь кратенько опишу еще одно свойство:
Это read-only свойство используется при записи TWriter'ом вложенных фреймов. При записи компонентов, находящихся на фрейме указывает на этот фрейм, как на владельца. То же самое касается и TReader'а, то есть чтица данных. Используется точно так же. Рассматривать никакого примера мы не будем, так как в данной статье мы все-таки обсуждаем не фреймы. Теперь еще одно важное свойство:
Давайте представим следующую ситуацию. У нас есть компонент, с огромным количеством свойств. А установлено(отличается от дефолтного) только одно(образно - значения таких свойств как длина, ширина и им подобные, конечно же, всегда при визуальном проектировании отличаются от заданных изначально(хотя при динамическом создании можно ничего этого и не трогать)) - вот теперь вопрос: зачем спрашивается нам писать, тратить драгоценные байты на жестком диске, все эти не установленные свойства? Мне тоже не очень понятно, зачем это делать. Так вот. Свойство Ancestor используется для сравнения записываемого компонента с неким прототипом. То есть записываются только те свойства, которые в записываемом компоненте не равны свойствам в прототипе. Использовать свойство Ancestor напрямую - не рекомендуется. Ведь если Вы, допустим, укажете в нем некую панель(TPanel), а записывать будете различные компоненты, то получится не известно что при сравнении свойств, и записано будет уже явно не то, что Вам нужно. Если уж очень надо записать компонент, сравнивая его с каким то прототипом, то используйте WriteDescendent, о котором речь пойдет чуть позже. Вернемся к нашему свойству. Ancestor устанавливается в значение отличное от nil, только если это форма разработанная в визуальном дизайнере. Теперь хочу сказать пару слов о записи свойств компонентов. Стандартно записываются только свойства определенные в секции published - то есть те, которые доступны в Object Inspector. При объявлении свойств в секции Published мы заставляем компилятор добавлять дополнительную RTTI информацию о компоненте, с которой, в том числе, позже работает всеми нами любимый Object Inspector. По этой причине в данной секции класса не могут быть объявлены некоторые типы данных. Разрешены стандартные типы, строки, массивы, классы и указатели на методы. Попробуйте объявить свойство в виде указателя(Pointer) - ничего у Вас не выйдет. Теперь еще одно свойство:
Оно используется внутри TWriter'а для сравнения корневого компонента(Root'а) с неким прототипом. В принципе, действует аналогично Root. Так же у рассматриваемого нами класса есть еще одно интересное свойство:
Если это свойство установлено, то при записи компонента не будут записаны принадлежащие ему компоненты. Рассмотрим для большей наглядности два примерчика: 1.
Как видите в получившемся файле записана только форма. Никакие компоненты на ней расположенные записаны не были. 2. Этот пример стандартный, уже приводился:
Записываются все компоненты с Owner'ом равным Form1. Как Вы уже поняли, при IgnoreChildren = true, пишется только сам компонент, а при false еще и все ему принадлежащие. По умолчанию это свойство установлено в false. Теперь еще один метод записи компонента в поток.
Это в принципе тот же WriteComponent, только с небольшими отличиями. 1. Сначала записывает сигнатуру, а потом уже компонент и все ему принадлежащее. 2. Устанавливает значение свойств Ancestor и RootAncestor в значение переданное в параметре AAncestor, а свойства Root и LookupRoot в значение первого параметра(Root). Ну а после всего этого, собственно говоря, вызывается сам WriteComponent. Теперь еще один метод записи:
Приведу лишь его код. Разбирайтесь сами ;)
Ну, и последий метод записи:
Как ясно из названия метода - он записывает в поток коллекцию. Что это за шутка такая - TCollection - можно почтить по следующей ссылке: http://www.delphikingdom.com/asp/viewitem.asp?catalogid=215 Теперь же о том как проходит запись. Сначала, независимо от того передана ли в параметре живая коллекция или же nil, записывается значение из набора TValueType(да, давненько мы о нем не говорили). А именно vaCollection. А дальше записываются все свойства каждого элемента коллекции. На этом записывающие методы TWriter'а мы рассматривать заканчиваем, так как они у него уже закончились Ах, да. Есть еще две такие процедурки, методы TWriter'а:
Что они делают? Это что-то наподобие begin..end'а в самом Delphi. Они ничего не делают, кроме того, что записывают в буфер значения из TValueType - vaList при вызове WriteListBegin и vaNull при WriteListEnd. Это позволяет разбивать записываемые данные на разделы, что бывает довольно удобно. Только никогда не забывайте закрывать открытый WriteListBegin'ом раздел с помощью вызова WriteListEnd, ну или если вам так хочется, просто записью vaNull. Больше сказать об этих методах нечего. На этом рассмотрение класса TWriter мы заканчиваем, и 1 часть статьи соответственно тоже заканчивается. Дальше мы рассмотрим класс TReader. Это сообщение отредактировал(а) THandle - 8.3.2009, 15:20 |
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
THandle |
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
Хранитель Клуба Награды: 1 Профиль Группа: Админ Сообщений: 3639 Регистрация: 31.7.2007 Где: Moscow, Dubai Репутация: 65 Всего: 372 |
Часть 2. TReader.
Итак, переходим ко второй части статьи. Тут мы рассмотрим класс TReader, который используется для чтения данных из потока. Эта часть статьи будет немного короче, так как все мы уже рассмотрели. В TReader'е просто соответствующие методы для чтения определенного типа данных, и я думаю Вам понятно, что если Вы, допустим, записали данные с помощью TWriter.WriteInteger, то читать их нужно с помощью TReader.ReadInteger. Эти методы мы рассматривать не будем, лишь кратенько пробежимся по их списку, а разберем только наиболее интересное и что-то новенькое. Итак. Рассмотрим методы, которые особого интереса для нас не представляют. То есть те, работа которых понятна по аналогии с методами записи TWriter'а.
Этот метод читает в Buf данные размером в Count байт. К этому методу, аналогично TWriter.Write сводится все чтение данных.
Читает из потока данные в переменную типа Boolean. Следует обратить внимание на то, что сам метод выглядит таким образом:
То есть, вызывается метод ReadValue, которое возвращает значение типа TValueType, а результат возвращается простым сравнения этого значения с vaTrue. Следовательно, все равно, что прочитает ReadValue. Пусть там будет хоть vaString или что-то еще - в любом случае будет возвращен результат в виде false, и ошибку того, что Вы прочитали не те данные, получить будет невозможно. Поэтому, будьте аккуратнее с чтением. Читайте только данные только в том порядке и тех типов, как они были записаны.
Читают значения типа Char. Осторожно, возможно возникновения исключение, если прочитается строка, а не символ. Еще раз говорю о том, что бы Вы следили за порядком чтения данных и не забывать ставить try..finally..end.
Читают строки типа vaString, vaLString.
Читает vaUTF8String, vaWString.
Читают значения целочисленных типов. С типами все как в TWriter.
Тут, думаю, тоже все ясно. Только не забывайте читать данные именно того типа, который записывали.
Читает значение типа Variant. Проверяет, какого типа является следующее значение и вызывает метод чтения для соответствующего типа. Если тип данных определить не удалось(записаны какие-то левые данные), то будет возбужденно исключение. Аккуратнее с этим.
Читает дату/время. Тут все ясно.
Читает сигнатуру TPF0. Если читается что-то другое, то возбуждается исключение.
Как уже говорилось - данный метод считывает из потока SizeOf(TValueType) байт и возвращает тип данных, идущих за этой записью. Таким методом можно считывать вручную данные, своими методами. Хотя конечно не рекомендую этого делать, ведь есть уже готовые методы ;)
Считывает из потока коллекцию. Ссылка на статью про коллекции была в первой части данного обзора. Считывание происходит аналогично записи. Считываются все элементы коллекции и их свойства. Теперь поговорим о считывании компонентов. Тут тоже ничего сложного нет, если не углубляться в основы того, как что читается. Статья у нас обзорная, углубляться не будем. Если интересно - посмотрите сами реализацию этих методов. Начнем.
Эта функция считывает компонент. Но тут не все так просто как с записью. Прежде чем читать, следует установить несколько свойств. Посмотрим, что же это за свойства.
Свойство Root перешло к TReader еще от своего предка - TFiler'а. Но это не столь важно. Используется оно так же, как и при записи. Указывает на владельца читаемого компонента. Обязательно надо устанавливать(Если конечно не используется ReadRootComponent, о которой чуть позже). Следующее свойство:
Отвечает за родителя читаемого компонента. Актуально только для визуальных компонентов(наследников TControl). Для них обязательно следует устанавливать это свойство в валидное значения. А, например, для TOpneDialog - не нужно. И третье свойство, которое, впрочем, устанавливать не обязательно:
Если оно не установлено, то владельцем прочитанного из потока компонента станет Root. Если же установлено, то, соответственно, владельцем компонента будет уже, то значение, в которое Вы установите свойство Owner. Смотрите, не намудрите с этим ;) Итак, все эти свойства мы рассмотрели. Теперь идем дальше.
Вот. Есть такие три метода. Что они делают? Эти методы гарантируют, что до того момента, пока все компоненты не будут загружены, ссылки на них нигде использоваться не будет. В BeginReferences создается TList, в который добавляются все считываемые компоненты. При считывании, в свойству ComponentState, являющиеся множеством, добавляется значение csLoading. BeginReferences должен вызываться до считывания компонентов. В FixupReferences у всех, уже прочитанных компонентов, из множества ComponentState убирается значение csLoading и компоненты становится можно использовать. В EndReferences просто освобождается список, в котором были ссылки на прочитанные компоненты. Если Вы вызываете BeginReferences - Вы обязаны: 1. Вызывать FixupReferences и EndReferences. 2. Обязательно заключать чтение компонентов в блок try..finally, таким образом:
Обязательно!!! Далее.
Свойство Root устанавливается в значение единственного параметра этого метода. Предыдущие замечания про XXXReferences касаются и этого метода. Теперь, я хотел бы немного отойти от скучного рассмотрения методов, и разобрать пару небольших примеров. В Интернете для записи некоего компонента нам предлагают написать процедуру следующего типа(не "дословно", но смысл тот же):
Вот такой код. Сохраняет компонент(и его дочерние компоненты), переданный во втором параметре процедуры, в файл, имя которого передается, соответственно, в первом Все отлично сохраняется. Никаких проблем нет. Каким кодом нам предлагают все это безобразие читать? А вот таким:
Все работает просто замечательно. Радостный программист пишет код дальше не задумываясь, что тут такого происходит. А что тут, собственно, не так? Давайте разберемся. Эта процедура считывает вместо компонента, передаваемого во втором параметре, какой-то компонент из потока данных, в данном случае представленного файлом. Но что же происходит? Старый компонент уничтожается полностью, а на его место читается новый. Это может быть вообще что угодно. Но не будем представлять ситуацию, когда вместо суслика мы получаем крокодила, а взглянем на то, что получится, если считывать тот же компонент. Представим. На форме панель(TPanel). На панели TEdit. Сохраняем сие чудо в файл, с помощью вызова:
Сохранили. Все прекрасно. Дальше. Считываем аналогичным образом.
Все данные считались. Все, на первый взгляд, тоже замечательно. Но. Вот прочитали мы. Наша программа работает дальше. И где-то, мимолетно, мы обращаемся к Form1. Программа начинает ругаться матом на пользователя. Он пугается, падает со стула, ломает, допустим, руку, и Вас отдают под суд. Почему возникает ошибка? Передали мы в процедуру ReadComponentFromFile нашу панельку, взяли у нее, то, что нам надо(владельца и родителя), да и уничтожили её совсем, вызвав FreeAndNil. Panel1 = Nil. Все. При любом обращении, при любой попытке вызова метода Panel1 у нас получится AV. Но ведь панель же у нас на форме есть??? Это же не призрак? Есть. Найти её можно с помощью того же FindComponent. Имя, и другие свойства, у неё не изменились. Но ссылка стала не действительна. И именно вот такой код встречается в большинстве статей/ответов в Интернете. Как исправить, особо ничего не переделывая? Не сложно. К примеру, так:
Ну, и вызывать эту функцию соответствующим образом:
Далее. Есть такой метод:
Этот метод считывает компоненты из потока данных. Каким образом он их считывает? Компоненты из потока читаются, пока не будет обнаружено значение равное vaNull. Это проверяется функцией:
Она считывает из буфера(да, да - вспоминаем, что у нас TReader/TWriter работают с потоком через буфер) один байт и возвращает true, если прочитанное значение равно vaNull и false, если не равно. Так вот, перед тем как начать считывать компоненты свойства Root и Owner устанавливаются в значение, переданное в параметре AOwner, а свойства Parent, соответственнно, устанавливается в значение параметра AParent. Дальше вызывается метод BeginReferences(то есть, нам его теперь вызывать не требуется). И запускается цикл по считыванию компонентов, в котором, после прочтение компонента, проверяется, существует ли некая процедура Proc(третий параметр этого метода), и если существует, то она вызывается. Это удобно для выполнения каких-то своих действий при считывании компонентов. Тип процедуры имеет следующий вид:
Единственный параметр, который в нее передается из ReadComponents -только что считанный компонент. После прочтения всех компонентов и обнаружения значения vaNull, вызываются методы FixupReferences и EndReferences. Все то, что я сейчас сказал, Вы могли свободно узнать, посмотрев на код этого метода:
Ну, думаю с этим методом все ясно. Попробуйте реализовать какую-нибудь свою процедурку обработки считываемых компонентов. Осталось нам в TReader'е рассмотреть совсем чуть-чуть. Всего несколько методов. Итак, отвлеклись на секунду, и продолжим.
Этот метод очень просто - он лишь "очищает"(ставит в ноль свойство Position) буфер.
Этот метод пропускает следующее значение, которое обнаруживает в потоке данных. Он просто считывает соответствующее количество байт идущих после идентификатора типа этих данных(TValueType, о котором мы говорили в первой части). Следующий метод:
Он копирует считываемое значение в некий TWriter, переданный в единственном параметре этого метода. Все просто. Проверяется тип считываемого значения, и вызывается соответствующий метод записи TWriter'а, который это значение пишет.
Этот метод проверяет, является ли считываемое значение из буфера значением типа Value(единственный параметр этого метода). Если тип тот, который должен быть - то спокойно программа выполняется дальше. Если же нет, то, "о, ужас!!!", возбуждается исключение, сообщающее нам об этой прискорбной ошибке. Последние два метода, которые мы расмотрим, соответствуют методам WriteListBegin и WriteListEnd класса TWriter.
Что в них происходит - говорить не буду. Все ясно из кода этих методов:
На этом мы завершаем рассмотрение класса TReader. Поздравляю, теперь Вы знаете достаточно, чтобы использовать эти классы по назначению. Если интересно узнать еще кое-что, то читайте третью часть статьи, под кодовым названием "Немного в дополнение". |
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
THandle |
|
||||||||||||
Хранитель Клуба Награды: 1 Профиль Группа: Админ Сообщений: 3639 Регистрация: 31.7.2007 Где: Moscow, Dubai Репутация: 65 Всего: 372 |
Часть 3. Немного в дополнение.
Итак, приветствую всех в третьей части этой статьи. В этой части мы рассмотрим некоторые вещи, которые одновременно и относятся к TReader/TWriter, но и не являются рассмотрением возможностей чтения/записи этих классов. Это часть - дополнительный материал. Если Вы уже узнали все, что хотели - можете эту часть не читать. Хотя тут даже и читать то нечего. Пара примеров Итак. Начнем. 3.1. Запись и считывание форм в Delphi. Всем известно, что Delphi сохраняет автоматически свои формы в файлы dfm. Давайте рассмотрим, как это делается. Имеется у нас форма. IDE Delphi хочет её сохранить. Вызывается процедура:
Первый параметр - имя файла, в который нужно сохранять, второй - сохраняемый компонент. Все просто. Рассмотрим, что же происходит дальше. Вот код WriteComponentResFile:
Как Вы видите, тут создается файловый поток данных, и вызывается его метод WriteComponentRes. В этом методе после еще нескольких "перенаправлений" все сводится к вызову метода TStream - WriteDescendent:
В нем Вам, если Вы читали предыдущие части, все должно быть ясно. В итоге имеем то, что все сводится к работе TWriter'а. Компонент записывается в файл и все. Но это не файл dfm. Нет. Это бинарный файл ресурсов. Но как же создается dfm? Бинарные данные преобразовать в текстовые очень просто - процедурой ObjectBinaryToText:
Её параметры - два потока данных. Первый - поток с бинарными данными. Во второй мы получаем преобразованные в текст данные из первого. (Первый - Input, второй - Output, если вдруг кто запутался). Аналогичным образом можно преобразовать текстовое представление компонента в бинарный вид. Это делает процедура ObjectTextToBinary. Все это касается не только dfm(VCL), но и xfm(CLX) файлов. А теперь напишем небольшую программку-пример. Что этот пример будет уметь делать? 1. Открывать dfm файл формы нашего примера, считывать из него форму и показывать её. 2. При закрытии считываемой формы программа будет спрашивать: Не хотите ли Вы сохранить эту форму со всеми внесенными изменениями? Пример идет в аттаче к этому посту. Думаю, в его коде все Вам понятно. 3.2. Сохранение/чтение не Published свойств. Как Вы уже знаете, стандартные методы записи свойств компонентов из класса TReader, и соответствующие им методы чтения из TReader, работают только с теми свойствами компонента, которые объявлены в секции Published. Но что делать нам, если вдруг потребуется сохранить, допустим, Public свойство компонента? В этой заключительной части статьи мы как раз рассмотрим такой метод. Посмотрим на 2 метода, общие и для TReader и для TWriter. Эти свойства идут еще от TFiler'а.
Разберемся с первым. Этот метод определяет свойство, которое должно быть сохранено наряду с published свойствами. Параметр WriteData - указатель на процедуру, которая будет производить запись данных. В TReader этот параметр игнорируется. Параметр ReadData - указатель на процедуру по считыванию данных. В TWriter этот параметр игнорируется. Естественно, все эти процедуры мы должны написать сами Последний параметр - HasData, используется только при записи, для определения того, действительно ли нужно записывать это свойство. DefineBinaryProperty отличается от DefineProperty тем, что данные свойства сразу идут в поток/из него, пропуская буферы наследников TFiler'а. Для того чтобы опередилить не Published свойство, нужно перекрыть метод:
И определить для его параметра Filer нужные свойства через DefineProperty. После этого написать процедуры записи/чтения данных и, в принципе, все. Сначала я хотел привести пример в этой статье, того как писать такие компоненты. Но, все что приходило в голову слишком надумано и никогда не сможет применяться в реальной жизни. Лучше посмотрите на реальные компоненты, которые используют этот метод. Например, одна из первых ссылок в Google: http://www.delphisources.ru/pages/faq/base...mbers_list.html Вполне реальный, и между тем не большой компонентик, в котором не трудно будет разобраться. Сохраняются несколько свойств... Но как это делается, я сказал. Поищите и изучите сами. Заключение. На этом я заканчиваю. Возможно, позже, я дополню третью часть еще чем-то. Но сейчас я не вижу ничего такого, что можно было бы еще написать. Надеюсь, этот небольшой обзор был Вам полезным. С уважением ко всем читателям, THandle. Благодарности. Rrader - за замечания по статье. Ну как всегда в общем. Огромное спасибо. Kbl4AH - за помощь в оценке статьи со стороны человека изучающего Delphi. THandle - за идею. Присоединённый файл ( Кол-во скачиваний: 74 ) Example1.rar 17,87 Kb |
||||||||||||
|
|||||||||||||
mr.Anderson |
|
|||
iOS Lead Developer Профиль Группа: Участник Клуба Сообщений: 3374 Регистрация: 20.12.2004 Где: далеко Репутация: 3 Всего: 128 |
Хорошая статья ) Лови +
|
|||
|
||||
SneG0K |
|
|||
Max Mara Профиль Группа: Завсегдатай Сообщений: 1887 Регистрация: 1.12.2007 Где: Wis Dells Репутация: 1 Всего: 54 |
Молодец, отлично все расписал.
|
|||
|
||||
Kbl4AH |
|
|||
Опытный Профиль Группа: Участник Сообщений: 741 Регистрация: 1.4.2008 Где: Вятка Репутация: 1 Всего: 15 |
Афтар, зачот Пишы ищо
ЗЫ. Примером смог открыть только форму примера Придется, видимо, RAD2009 установить. |
|||
|
||||
THandle |
|
|||
Хранитель Клуба Награды: 1 Профиль Группа: Админ Сообщений: 3639 Регистрация: 31.7.2007 Где: Moscow, Dubai Репутация: 65 Всего: 372 |
Kbl4AH, Пример нормальный. Только что открыл его 7, 2006, 2007, 2009 версией Delphi.
Если у кого выскакивает ошибка на
То эту строку просто удалите. Kbl4AH, или какая ошибка и в каком месте у тебя возникает? |
|||
|
||||
Kbl4AH |
|
|||
Опытный Профиль Группа: Участник Сообщений: 741 Регистрация: 1.4.2008 Где: Вятка Репутация: 1 Всего: 15 |
||||
|
||||
GN1 |
|
|||
Новичок Профиль Группа: Участник Сообщений: 10 Регистрация: 23.4.2008 Где: Казахстан Репутация: нет Всего: нет |
Спасибо за статью.
Жаль что на http://delphiplus.org не размещают ссылки на статьи с винграда :( За всем не уследишь. Хотел поставить "+", постов не хватает |
|||
|
||||
bems |
|
|||
Эксперт Профиль Группа: Комодератор Сообщений: 3400 Регистрация: 5.1.2006 Репутация: 31 Всего: 88 |
GN1, не вопрос, поможем
-------------------- Обижено школьников: 8 |
|||
|
||||
UniBomb |
|
|||
Новичок Награды: 1 Профиль Группа: Участник Клуба Сообщений: 1754 Регистрация: 24.10.2006 Где: Санкт-Петербург Репутация: нет Всего: 97 |
GN1, и я поставлю от нас двоих
|
|||
|
||||
execoma |
|
|||
Новичок Профиль Группа: Участник Сообщений: 16 Регистрация: 8.3.2010 Репутация: нет Всего: -2 |
Автору респект!
|
|||
|
||||
MegaVolt |
|
|||
Новичок Профиль Группа: Участник Сообщений: 27 Регистрация: 20.10.2006 Репутация: нет Всего: нет |
Я бы добавил сюда дополнение из статьи про сериализацию объектов
|
|||
|
||||
Иванqwe |
|
|||
Новичок Профиль Группа: Участник Сообщений: 1 Регистрация: 18.1.2013 Репутация: нет Всего: нет |
подскажите, что написано на гроуп боксе и на кнопке?
потому что у меня знаки вопроса выскакивают |
|||
|
||||
Правила форума "Delphi: Общие вопросы" | |
|
Запрещается! 1. Публиковать ссылки на вскрытые компоненты 2. Обсуждать взлом компонентов и делиться вскрытыми компонентами
Если Вам понравилась атмосфера форума, заходите к нам чаще! С уважением, Snowy, MetalFan, bems, Poseidon, Rrader. |
0 Пользователей читают эту тему (0 Гостей и 0 Скрытых Пользователей) | |
0 Пользователей: | |
« Предыдущая тема | Delphi: Общие вопросы | Следующая тема » |
|
По вопросам размещения рекламы пишите на vladimir(sobaka)vingrad.ru
Отказ от ответственности Powered by Invision Power Board(R) 1.3 © 2003 IPS, Inc. |