Использование True DBgrid при создании приложений для
управления базами данных на Visual Basic 6.0 (Часть2)
Введение (обновленное и дополненное)
Как только я собрался писать вторую часть статьи,
посвященной программированию на VB6, так Microsoft объявила об окончании
поддержки Visual Basic 6.0. Окончательным сроком было объявлено 31 марта 2005
года. В Интернете на http://www.classicvb.org/
развернули сбор подписей в поддержку VB6 . В эхе fido7.ru.visual.basic развернулась весьма бурная дискуссия по этому поводу.
Все это, а также мое собственное осознание того, что я, увы, отстаю от
прогресса, поставило под угрозу выход второй части. Я долго колебался между
необходимостью продолжить труд и переходом на VB.NET. Лихорадочно перечитывал сообщения в эхе, читал в
Интернете статьи многих уважаемых программистов. И Постепенно пришел к выводу,
что статью все же закончить надо, а VB6 проживет еще
достаточно долго. И тому есть примеры: FoxPro для DOS.
Итак, в первой части мы рассмотрели:
- сам TrueDbGrid;
- промежуточные компоненты: AdoDc,
TrueDbGridDown;
- использование TDataLite;
- различные режимы фильтрации;
- и, наконец, перехват ошибок.
Статья, как это ни странно, вызвала определенное количество
откликов. Часть вопросов, которые мы дальше рассмотрим, были навеяны перепиской
с читателями. Сегодня мы обязательно остановимся на сохранении параметров TrueDbGgrid и восстановлении их при следующем запуске
программы, на сортировке данных в TrueDbGgrid и
отображении информативной стрелки направления сортировки. Мы посмотрим, как
можно выполнить экспорт и импорт данных средствами TrueDbGgrid
и ADO. Попробуем избавиться от компонента Adodc. А также обязательно остановимся на печати табличных
данных при помощи самого TrueDbGgrid и при помощи
компоненты VSView.
Отказываемся от AdoDc
AdoDc очень капризный контрол,
сжирающий огромное количество ресурсов. Каждый AdoDc
формирует новое подключение к БД и если их много, то производительность будет
падать с каждым новым подключением. Поэтому есть смысл отказаться от его
использования.
Примечание. Компания ComponetOne, прекрасно
понимая ущербность AdoDC, написала TData
(не путайте с TDataLite). Это своего рода симбиоз AdoDC и TDataLite
с некоторыми дополнительными возможностями. В частности в эту компоненту
заложены функции автоматического обновления Recodset,
что позволяет отказаться от ручного обновления и, соответственно, от таймеров.
Итак, какова же методика отказа от AdoDc?
Для начала создадим глобальную переменную Connect,
которая будет глобальна для всего проекта.
Public connection As New ADODB.connection
Понято, что ссылка на ADO в вашем проекте уже должна стоять! (Смотри часть первую)
Далее подключение к БД нужно открыть. Это все очень похоже
на AdoDc.
connection.ConnectionString = "PROVIDER=SQLOLEDB"
& _
";SERVER=" & SerName & _
";UID=" & UserName & _
";PWD=" & pss & _
";DATABASE=CDR_SI"
connection.Open
Переменные SerName, UserName, pss можно
заменить непосредственно на нужные значения, а можно эти переменные определить
и присвоить значения. Эта строка предназначена для подключения к SQL Server. В
случае с MDB это будет
выглядеть так:
connection.ConnectionString = “Provider=Microsoft.Jet.OLEDB.4.0;Data
“ & _ “Source=C:\kross\krossav.mdb;Persist Security Info=False”
connection.Open
Все это мы уже рассматривали в первой части. Ничего нового
пока нет.
После выполнения строки
connection.Open
через объект connection будет открыто подключение к БД, и оно будет открыто до тех
пор, пока вы явно его не закроете:
connection.Close
или
Set connection=Nothing
Примечание. Завершение программы без явного закрытия подключения,
скорое всего, не закроет открытого подключения. При этом в случае с MDB файлами оно обычно закрывается, а в случае с SQL Server не
закрывается никогда! Учитывая тот факт, что количество подключений к SQL Server
ограничено, такая ситуация будет пустым расточительством ресурсов. Расскажите
своему системному администратору о том, что для отключения пользователей от SQL Server
достаточно его перезапустить, а сам сервер перезагружать не нужно.
Для получения данных из БД и передачи их для отображения в Grid нам необходимо создать объект Recordset.
Сначала опишем переменную:
Dim rsa As New
ADODB.Recordset
Обычно объекты Recordset формируются также часто, как и закрываются, на время
выполнения той или иной процедуры. Вы можете открывать сколь угодно много Recordset и использовать их как глобально для всей программы,
так и в зоне действия процедуры (функции), не забывая их закрывать по мере
того, как они становятся не нужны.
Чтобы открыть Recordset используется
следующий код:
strSql = "SELECT Id, DateDay " &
_
"From dbo.CDR_DAY "
& _
"Where (Id = 1)"
rsa.Open strSql, connection, adOpenKeyset, adLockOptimistic
Понятно, что вместо переменной strSql
можно непосредственно написать текст SQL запроса. Вы
также можете указать тип курсора и тип блокировки данных.
Вот собственно говоря и все. Осталось передать открытый Recordset в Grid, делается это просто:
Set
Me.TDBGrid1.DataSource = rsa
Для закрытия объекта Recordset используем конструкции:
rsa.Close
или
Set rsa=Nothing.
Как видите, отказаться от использования AdoDc
очень легко. А удобство и производительность программы возрастает.
Экспорт и импорт данных в TrueDbGrid и ADO
Как бы программист не старался создать самодостаточную
программу, все равно у него ничего не получится. Пользователю обязательно
захочется самому проанализировать полученные данные из БД или банально передать
их кому-то. Передача только на бумаге – это прошлый век. Сегодня все чаще мы
обмениваемся информацией в электронном виде.
Все это привело к тому, что солидная программа должна
обладать средствами экспорта и импорта данных. Задача экспорта намного проще
импорта, так как БД используется только для чтения. Импорт, возможно, потребует
более детального рассмотрения проблемы, формирование дополнительных таблиц и
преобразования данных.
ADO
Экспорт данных средствами ADO
выполняется при помощи метода Save объекта Recordset. Имеется возможность сохранить данные Recordset в формате XML и в формате ADTG. Последний – это специфический бинарный
формат, предназначенный для обмена данными между различными Recordset.
Формат XML сегодня становится практически основным
обменным форматом. Однако, несмотря на то, что формат XML,
строго говоря, стандартизирован, он имеет много различных версий и реализаций.
Поэтому прочитать ваш экспортный файл сторонним программным обеспечением не
проблема, но вот считать в Recordset, файл XML, созданным другой программой, весьма затруднительно.
Для сохранения данных Recodset в файле используется следующий код:
rsa.Save
"sss.xml",adPersistXML
Метод Save делает копию Recodset на диске, но не закрывает его. Имя файла и путь к
нему может быль любым подходящим. Чтобы сохранить файл в формате ADTG, второй параметр метода можно не указывать. Так как
данные по умолчанию сохраняются в этом формате. Но лучше параметр все же
указывать, потом код программы будет легче читать.
rsa.Save
"sss.dat", adPersistADTG
Чтобы открыть ранее сохраненный файл-Recordset
используется метод Open.
rsa.Open "sss.dat",
"Provider=MSPersist;", adOpenKeyset, _
dLockOptimistic,
adCmdFile
Все параметры нам уже известны, кроме одного "Provider=MSPersist;". Это
провайдер OLE DB
позволяющий работать с отсоединенными наборами строк. Собственного говоря все,
что рассказано выше имеет свое специальное предназначения в ADO.
Все это средства работы с отсоединенными объектами Recordset,
которые позволяют продолжить работу с набором данных даже после того, как
объект будет отсоединен от источника данных. Эта методика применяется для
обеспечения мобильности пользователям, работающим при непостоянном соединении.
Отсоединенный Recordset можно изменять всеми доступными
через ADO средствами, а затем синхронизировать с
основной БД.
Чтобы выполнить синхронизацию БД и отсоединенного набора
строк нужно выполнить следующее:
rsa.ActiveConnection=connection
rsa.UpdateBatch
Примечание. Мне трудно сказать как будут работать отсоединенные
объекты от файловой БД (MDB), насколько корректно они
будут синхронизироваться с основной БД и так далее? Я никогда этого в случае с MDB не делал. В случае с SQL Server все
работает. Однако обработку коллизий программист должен взять на себя, так как
все изменения, вносимые в отсоединенный Recordset будут
применены к оригинальным данным в БД.
DBGrid
Экспорт данных в True DBGrid представлен более обширными
форматами и средствами, по сравнению с ADO. Кроме
обычных средств имеются средства для экспорта конкретной строки или части
строк.
Чтобы выполнить экспорт в формате HTML использует метод ExportToFile, например, так:
Me.TDBGrid1.ExportToFile
"ssss.html", False
У метода ExportToFile следующий синтаксис:
TDBGrid.ExportToFile
pathname, append, [selector], [tablewidth]
Где параметр pathname
– это имя файла с полным путем к нему. Второй параметр append
может принимать значение False или
True – указывает, следует ли дописывать строки к
существующим в файле или нет. Параметр Selector
предназначен для указания какие строки должны быть экспортированы: 0 – dbgAllRows – все строки, 1 – dbgSelectedRows
– только выбранные строки, 2
– dbgCurrentRow – только текущую строку. Параметр tablewidth используется при генерации тэга <table>
в формате HTML.
Вы можете применять фильтр к Grid,
а после этого вызывать экспорт, чтобы экспортировать только отфильтрованные
записи. Параметр Selector не
так уж и удобен, как может показаться. Проще фильтровать записи средствами ADO (пусть даже и через Grid), чем
пользоваться средствами SelBookmarks. Раз уж мы начали этот разговор давайте
рассмотрим как можно пользоваться средства SelBookmarks. В комплекте с TrueDBGrid поставляет пример (Tutoral 5), который рассматривает работу с SelBookmarks
подробно.
Dim SelBks As TrueOleDBGrid70.SelBookmarks
Set SelBks = TDBGrid1.SelBookmarks
Применять SelBookmarks рекомендуется на клоне Recordset.
Dim dclone As
ADODB.Recordset
Set dclone = rsa.Clone
Далее начинаем выделять строки:
SelBks.Add dclone.Bookmark
Напомню, что свойство Bookmark по-умолчанию соответствует
текущей строке в Recordset. Понятно, что выполнять эту
строку можно по условию, в процессе перебора строк, как это сделано в примере
№5.
Чтобы снять выделение следует выполнить такой код:
While SelBks.Count
SelBks.Remove 0
Wend
Пользователь может выделять строки щелкая их мышью,
совместно с клавишей CTRL. При этом свойство AllowRowSelect должно быть установлено как True.
Обратите внимание, что изучая средства экспорта, мы изучили
еще одно важное средство TDBGrid – SelBookmarks. На
практике такое применение SelBookmarks используется редко, но умение
определения записей, которые пользователь предварительно выделил может выручить
вас на просторах программирования – ситуации бывают разные.
Давайте перейдем к экспорту в формате CSV.
CSV – это обыкновенный текстовый файл, использующий в
качестве разделителя полей специальный символ, который нигде внутри полня не
повторяется. Обычно это запятая, но может использоваться и любой другой,
подходящий символ.
TDBGrid1.ExportToDelimitedFile pathname, [delim], [selector], _
[prefix],
[suffix]
Параметр delim
– это и есть разделитель полей. Параметр selector – рассмотрен выше. Параметры prefix и
suffix используются для ограничения поля сначала и с конца значения.
Часто для этого используют кавычки.
Примечание. В справке, которая описывает этот метод,
по крайней мере того пакета, который стоит у меня, содержится ошибка в описании
префикса и суффикса. Префикс – это до значения поля, а суффикс – это после.
Работает все так – как должно.
Импорт данных
Строго говоря импорт никакого отношения к Grid
не имеет. Так это применительно только к БД. Однако тема эта весьма важная и
умение применять средства VB для импорта пригодится вам
в дальнейшем. Как правило импорт выполняется из текстовых файлов и из файлов CSV. Остается только гадать, почему Microsoft не встроила средства импорта CSV в ADO? На практике, возможно, потребуется импортировать данные
из Excel. Это отдельный вопрос, требующий более
детального рассмотрения. Excel мощная
программа, импорт из нее можно выполнить разными способами. В некоторых случаях
файлы *.XLS могут представляться как файлы БД и
позволяют обращаться к ним средствами ADO. Другими
словами, на этом мы останавливаться не будем. Это тема отдельной публикации.
Остановимся на импорте текстовых файлов.
Текстовые файлы могут быть с жестко разделенными полями, а
могут быть с идентификаторами. Если поля жестко разделены, то работать с таким
файлом очень просто: всегда заранее известно положение поля в строке и его
размер. Пример такого файла приведен ниже:
18.04.20057294321822559498
11:01:07000302001
18.04.2005729409189202162913
11:01:30000102001
18.04.2005729407821456
10:44:03010670018
18.04.20057294049822368421
11:02:36000472001
18.04.2005729409393689 11:04:17000020001
18.04.2005729411493511
11:04:20000070001
В лучшем случае к файлу должно прилагаться описание полей,
хотя в нашем случае и так все понятно. Чтобы открыть и посчитать размер и
позицию полей откройте этот файл в редакторе FAR.
Примечание. Обратите внимание, что ни Word, ни Total Commander не помощники в этом вопросе. Посчитать в Word пробелы и символы крайне затруднительно. И здесь самый
лучший вариант использовать DOS-программы, или псевдо-DOS, которой и является FAR.
В нашем примере можно ничего не считать, так как описание на
формат есть:
10
<Date>
Дата в формате <DD.MM.YYYY>
|
7
<Subscriber A>
Номер абонента A
|
18
<Subscriber B>
Номер вызываемого абонента
|
8
<Time>
Время начала разговора в формате <HH.MM.SS>
|
5
<TalkTime Sec>
Время разговора в секундах
|
1
<TarifType>
0 - местный / 2 - междугородний вызов
|
3
<TalkTime Min>
Поминутный тарификатор
|
Где цифры в таблице означают размер поля, далее идет его
описание. Импорт такого файла не представляется сложным.
Для начала заведем переменную типа (запись)
Type SaveConnect
AbonentA As Long
AbonentB As String
ProdW As Long
DateW As String
End Type
Обратите внимание, что мы будем записывать из файла только
те значения, которые нам нужны.
Опишем переменную:
Public SConnect As SaveConnect
Далее код открытия файла:
FileNum = FreeFile
Close FileNum
Open “c:\tarif.log” For Input As #FileNum
Line Input #FileNum, ReadString
While Not EOF(FileNum)
SConnect.DateW = Replace(Format(DateValue(Mid(ReadString,
1, 10)), _
"dd/MM/yyyy"),
".", "/") & " " & _
Format(Mid(ReadString,
36, 8), "HH:NN:SS")
SConnect.ProdW = Val(Mid(ReadString, 44, 5))
SConnect.AbonentB = Trim(Mid(ReadString, 18, 18))
SConnect.AbonentA = Val(Mid(ReadString, 11, 7))
‘ сюда добавить код записи в БД
Wend
Остается только записать полученные значения в БД. Это
отдельный вопрос и здесь мы его рассматривать не будем.
Давайте рассмотрим приведенный код более подробно.
SConnect.DateW = Replace(Format(DateValue(Mid(ReadString,
1, 10)), _
"dd/MM/yyyy"),
".", "/") & " " & _
Format(Mid(ReadString,
36, 8), "HH:NN:SS")
Здесь мы читаем из строки ReadString подстроку с символа 1
позиции до символа 10 позиции, затем полученная дата преобразуется к
специальному формату, который необходим для корректной записи в БД. Кстати,
применение функции Format здесь является избыточным.
Это предусмотрено специально, если вдруг формат даты в исходном файле
изменится. Впрочем это бывает редко. Замена точки на слеш – это обязательная
процедура – так как ADO в качестве разделителя в дате
обязательно использует слеш. Далее мы читаем время из строки ReadString с
символа 36 позиции до символа 44 позиции. Также применяется специальный формат,
что в данном случае также излишество.
Замечание. Излишнее применение функции Format может показаться некорректным программированием. Но практика
использования программ на разных ОС (русифицированных и чисто англоязычных, с
неверно установленными регионами и так далее) показала, что такое
преобразование гарантирует правильное представление дат. Рекомендую переменную DateW описать как String, чтобы
исключить ее преобразование средствами VB. На практике
вы очень быстро поймете, насколько неожиданным может оказаться результат при
работе с датами в БД. Есть еще одно замечание: при добавлении записей в БД
средствами ADO (метод AddNew)
дату можно записывать в формате dd/mm/yyyy. А вот в случае в SQL
инструкцией Insert, дата должна быть в формате mm/dd/yyyy. Кстати,
чтобы раз и навсегда исключить разночтения дат лучше месяц представлять словом,
а не цифрой.
Переменная SConnect.AbonentB имеет тип String.
Записывать незначащие пробелы ни к чему. Поэтому используется функция Trim, удаляющая незначащие пробелы. Переменная
SConnect.AbonentA, напротив, имеет тип Long, потому
следует текст преобразовать в число, для это и используется функция Val, которая автоматически отбрасывает незначащие пробелы. И,
наконец, везде используется функция Mid, которая
возвращает часть строки с определенной позиции и определенной длины. Ничего
сложно в таком импорте нет.
Перейдем к файлам с идентификаторами. Смысл идентификатора в
том, чтобы как-то указать, что данная информация является принадлежностью
определенного поля, но его длина не фиксирована. Ниже предлагаю примере такого
файла:
25.03.2003(Tu) 18:35:41
1 148s AB N= 25453 A=7294059.1 IC=128 EC=192
25.03.2003(Tu) 18:38:50
1 27s AB N= 25598 A=7294059.1 IC=128 EC=192
25.03.2003(Tu) 18:38:59
1 54s AB N=89038513388 A=7294094.1 IC=128 EC=192
25.03.2003(Tu) 18:41:40
1 2s AB N= 22149 A=7294016.1 IC=128 EC=192
Как видите, вначале каждая строка имеет фиксированный формат
и размер полей, а вот далее, поле N= может менять свою
длину и соответственно все поля далее сдвигаются.
Таким образом, вначале можно обработать файл обычным
способом и получить значения даты и времени вызова, а также его
продолжительность:
SConnect.DateW = Replace(Format(DateValue(Mid(ReadString,
1, 10)), _
"dd/MM/yyyy"), ".",
"/") & " " & Format(Mid(ReadString, 16, 8),
"HH:NN:SS")
SConnect.ProdW = Val(Mid(ReadString, 28, 6))
Здесь все просто и аналогично предыдущему примеру. А вот
далее начинаются сложности. Переменная SConnect.AbonentB. Позиция
идентификатора N= всегда постоянно, а вот позиция
идентификатора A= может меняться. Чтобы получить размер
поля, достаточно получить разницу из позиций за минусом 3, так как нужно
выбросить из результата A= и незначащий пробел перед
ним. Функция InStr возвращает позицию найденного контекста. Надо сказать, что и
начальную позицию идентификатора N= мы получаем через
функцию InStr.
SConnect.AbonentB = Trim(Mid(strZvon, InStr(1, ReadString,
"N=") + 2, _
InStr(1,
strZvon, "A=") - InStr(1, ReadString, "N=") - 3))
Аналогична ситуация и с переменной SConnect.AbonentA.
SConnect.AbonentA = Val(Trim(Mid(ReadString,
InStr(1, ReadString, "A=") _
+ 2, InStr(1, ReadString,
"A=") - InStr(1, ReadString, ".") - 1)))
Вот и вся методика. Определить размер поля по
идентификаторам очень просто. Программа сильно не усложняется, а выполнив это
один раз вы будете это повторять с легкостью.
Перейдем к файлам CSV. В файлах CSV жестко закреплен лишь порядок полей и их разделитель.
Другими словами, разделитель не должен встречаться в теле поля. Хотя и такой
вариант возможен, тогда значения всех полей заключаются, например, в кавычки. Такой
вариант для импорта самый сложный. Поэтому на практике стараются, чтобы
разделители не встречались в теле полей. Вот пример файла с которым мы будем
работать:
401698;2925087;30.08.04
10:12:16;0734021683;09;;23400
401699;2925088;30.08.04
10:12:18;0734021492;22759;;22080
401700;2925089;30.08.04
10:10:26;9056505313;21956;;134350
401701;2925090;30.08.04
10:12:32;0734022453;21529;;9990
Для импорта файлов CSV воспользуемся
функцией Split. Учитывая, что количество полей всегда
заранее известно, можно описать массив. В нашем случае полей 7 и в качестве
разделитя используется точка с запятой. Обратите внимание, что предпоследнее
поле не имеет значения и поэтому там появились две точки с запятой. Первые два
поля для нас могут быть не информативными (это уникальный номер и идентификатор
процесса в оборудовании), поле 6 пустое, а вот все остальные это то, что нужно.
Однако проще определить массив размером 7, а потом просто выбрать из него
только те значения, которые необходимы. Не забываем, что по умолчанию нижняя
граница массива 0.
Dim stroka(6) As String
stroka=Split(ReadString, “;”)
SConnect.DateW = Trim(stroka(2))
SConnect.ProdW = Val(stroka(6))
SConnect.AbonentB = Trim(stroka(4))
SConnect.AbonentA =
Trim(stroka(3))
Вы можете не определять размер массива заранее. Функция Split все сделает сама. Ну вот и все. Конечно, задачи импорта
этим не заканчиваются. Часто они бывают очень сложными. На моей практике
встречалась задача, когда одна запись, в текстовом файле была представлена
двумя строками и при том не всегда. Ее также удалось решить чтением файла с
конца. Но это уже совсем другая история…
Сортировка списков в True DbGrid
Речь пойдет, собственно, не о самой сортировке, а о том, как
это «красиво» представить средствами интерфейса Grid.
Обычно сортировка списков выполняется при нажатии мышью на заголовок столбца,
при этом на заголовке столбца появляется информационная стрелка, указывающая
направление сортировки. True DBGrid позволяет это сделать весьма эффективно. Для начала
определимся: мы будем это делать использую стили оформления. В TrueDbGrid имеется ряд предопределенных стилей, но мы
создадим свои. Предварительно вы должны подготовить два файла в формате BMP с изображением стрелки вверх и вниз. Размер рисунка можно
подобрать самому, по-моему 10х10 очень неплохо смотрится. В комплекте с True DbGrid поставляются специальные картинки, в том числе и стрелки.
Единственный недостаток формата BMP заключается в том,
что он не поддерживает прозрачность. Возможно в более поздних версиях Grid эта проблема решена.
Чтобы заголовок поля списка Grid вел
себя как кнопка необходимо свойство ButtonHeader установить True для каждой колонки,
где это нужно. Обычно так делают для всех колонок.
Теперь перейдем к Style Factory. Чтобы облегчить себе жизнь
создадим два новых стиля на основе стиля Heading. Назовем их, соответственно «Up» и «Down». Отличаться эти два
стиля от стиля Heading будут лишь двумя свойствами: ForegroundPicture и ForegroundPicturePosition.
Свойство ForegroundPicture хранит картинку,
обозначающую направление сортировки. Для стиля Up – это
стрелка вверх, а для стиля Down – стрелка вниз.
Свойство ForegroundPicturePosition устанавливается в 1-Right для новых стилей, это означает, что картинка всегда
будет показываться справа, независимо от ширины колонки.
Работы с интерфейсом закончены, осталось запрограммировать
корректное поведение Grid. При нажатии мышью на
заголовок колонки генерируется событие TDBGrid1_HeadClick, а в качестве
параметра возвращается номер колонки, по которой осуществилось нажатие. Код
данного события будет выглядеть так:
Private Sub TDBGrid1_HeadClick(ByVal ColIndex As
Integer)
SetSortHen TDBGrid1, rsa, ColIndex
End Sub
Понятно, что процедура SetSortHen должна быть создана
программистом. Чтобы унифицировать ее, она должна принять в качестве параметров
имя Grid, имя Recordset,
который на самом деле придется сортировать, а также номер колонки.
Public Sub SetSortHen(ByRef tdg As TDBGrid, ByRef
rs As Recordset, _
ByVal ColIndex As
Integer)
Dim i As Integer
Dim book As Variant
On Error Resume Next
Screen.MousePointer = vbHourglass ‘показали часики
DoEvents
For i = 0 To tdg.Columns.Count – 1
‘ пробежали все колонки и применили к ним стандартный стиль
оформления
tdg.Columns(i).HeadingStyle =
tdg.Styles("Heading")
Next i
If rs.Sort = "" Then
'сортировка туда (по возрастанию), первая сортировка
‘ передали в Recordset имя поля, по
которому следует сортировать
rs.Sort = tdg.Columns(ColIndex).DataField
‘ применили стиль Down, стрелочка
появилась
tdg.Columns(ColIndex).HeadingStyle =
tdg.Styles("Down")
ElseIf rs.Sort <> "" And
rs.Sort <> tdg.Columns(ColIndex).DataField Then
‘ снова туда
‘ передали в Recordset
имя поля, по которому следует сортировать
rs.Sort =
tdg.Columns(ColIndex).DataField
‘ применили стиль Down, стрелочка
появилась
tdg.Columns(ColIndex).HeadingStyle =
tdg.Styles("Down")
ElseIf rs.Sort <> "" And
InStr(rs.Sort, "DESC") > 0 Then
' снова туда, если была применена обратная сортировка
rs.Sort = tdg.Columns(ColIndex).DataField
tdg.Columns(ColIndex).HeadingStyle =
tdg.Styles("Down")
Else
' обратно
rs.Sort =
tdg.Columns(ColIndex).DataField & " DESC"
‘применили стиль Up, стрелочка появилась
tdg.Columns(ColIndex).HeadingStyle =
tdg.Styles("Up")
End If
Screen.MousePointer
= vbDefault ‘вернули курсор мыши на нормальный
End Sub
Некоторые пояснения. Задача процедуры определить первая ли
это сортировка или последующая, и если последующая по одному и тому же столбцу,
то необходимо выполнить переключение направления сортировки. То есть процедура
работает как переключатель. Результат работы впечатляет и приложение
приобретает профессиональный вид.
Сохранение параметров TrueDbGgrid
Под сохранением параметров здесь понимаются параметры
внешнего вида Grid: ширина колонки и столбца. Средства TrueDbGgrid
позволяют сохранять все его параметры в файл, а затем восстанавливать их в
нужный момент. Понятно, что параметры сортировки придется сохранять вручную,
например, в реестре Windows и применять их при открытии
приложения. Есть много и других подобных параметров, которые, строго говоря, к Grid не имеют отношения, а относятся к самому приложению.
Более того, сохранить, например, установленную пользователем ширину столбцов не
проблема.
Public Sub SaveWidhColumn(tdg As TDBGrid)
Dim i As Long
On Error Resume Next
For i = 0 To tdg.Columns.Count - 1
SaveSetting "Avtokross",
"SettingsCol", tdg.Name & i, tdg.Columns(i).Width
Next i
End Sub
А затем восстановить из при запуске приложения:
Public Sub SetWidhColumn(tdg As TDBGrid)
Dim i As Long
For i = 0 To tdg.Columns.Count - 1
tdg.Columns(i).Width =
GetSetting("Avtokross", "SettingsCol", tdg.Name & i,
1500)
Next i
End Sub
В качестве параметра tdg передается Grid. Раздел реестра (в примере Avtokross)
можно указать по своему желанию. Работает это также хорошо, как и
рассматриваемый ниже способ, но мы то с вами учимся использовать все
возможности Grid, поэтому будем программировать в таком
направлении.
Запись параметров Grid в файл можно
осуществлять после вносимых пользователем изменений в интерфейс, а можно и по
окончании работы приложения. Это проще, но нужно не забывать, что при аварийном
завершении приложения, внесенные пользователем изменения в интерфейс будут
потеряны.
Чтобы произвести запись изменений ширины столбцов
воспользуемся соответствующим событием:
Private Sub TDBGrid1_ColResize(ByVal ColIndex As
Integer, Cancel As Integer)
On Error Resume Next
TDBGrid1.LayoutFileName = App.Path &
"\cdrview.grx"
TDBGrid1.Layouts.Add "MainLayout"
End Sub
Дословно Layout можно перевести как
«расположение». В нашем случае переведем как «свойства интерфейса», так более
понятно. Из кода, приведенного выше, можно сделать вывод, что «расположений» в
одном файле может быть много, и загружать их можно по мере необходимости
(например, для каждого пользователя свой). На основе этого можно реализовать
различные цветовые схемы. Пользователь настраивает внешний вид Grid по своему желанию, в рамках тех средств настройки,
которые вы, как программист, ему предоставили, создает несколько, удобных для
него, цветовых схем и переключается между ними, по мере необходимости.
Замечание. Насколько я понял из документации в файле Layout сохраняются все (!) параметры Grid.
Понятно, что имя файла и путь к нему может быть любым,
удобным для вас.
Для восстановления сохраненных значений используется
следующий код:
TDBGrid1.LayoutName = "MainLayout"
TDBGrid1.LayoutFileName = App.Path &
"\cdrview.grx"
TDBGrid1.LoadLayout
И последнее. Будьте внимательны. При изменении параметров Grid в режиме Design
их нужно сохранить в файле Layout, например, путем специального вызова той
части программы, которая вызывает запись параметров. Если этого не сделать,
после чтения данных из файла Layout, они будут восстановлены во время
выполнения программы, но при этом (!) в режиме Design
они не изменятся. Вот и получается, что вы меняете свойства, а они как будто,
не применяются. Однажды я долго менял свойства, пока не додумался временно
отключить приведенные выше три строки кода из события Form_Load.
Печать табличных данных средствами TrueDBGrid
TrueDBGrid обладает минимальными
средствами для печати табличных данных. Впрочем, они не такие уж и простые, но
пользоваться имя можно только из программного кода, а ведь гораздо проще
визуально создать макет отчета, а затем подстегнуть шаблон к приложению. Чтобы
создавать более сложные отчеты потребуется специальный контрол VSView, но об этом чуть позже.
Ниже приведен простой код, который выведен на предварительный
просмотр содержимое Grid.
Private Sub Command1_Click()
With TDBGrid1.PrintInfo
' устанавливаем шрифт и текст заголовка
.PageHeaderFont.Italic = True
.PageHeader = "Пробная таблица"
' повторяем заголовки столбцов на каждой странице
.RepeatColumnHeaders = True
' показать номер страницы по центру
.PageFooter = "\tPage: \p"
' вывести на предварительный
просмотр
.PrintPreview
End With
End Sub
При выполнении кода получится приблизительно следующее:
Красиво, но не очень удобно. Все средства управления: меню,
кнопки, всплывающие подсказки – по-английски.
Многие параметры предварительного просмотра можно установить
вручную в процессе разработки программы на вкладке PrintInfo
свойства Custom самого Grid.
Еще больше параметров можно поменять в процессе выполнения
программы. Ну, например, добавим строку
.SetMenuText dbgpMenuFile,
"Файл"
перед строкой
.PrintPreview
И, о чудо, меню File стало называться «Файл».
В принципе, все проблемы внешнего вида данного окна можно
решить при помощи свойства SetMenuText. А все остальные мелочи, если они вам
понадобятся, можно прочитать в обширном разделе справочной системы
Но ведь отчеты табличных данных могут быть представлены не
только в виде таблицы, но в виде привычных текстовых отчетов, чего TrueDbGrid предоставить не может. Для этого обратимся к
замечательному компоненту VSView.
Замечание. Далее в примерах используется VSView
версии 7.0 фирмы ComponentOne. Для корректной работы
проекта требуется на панель объектов интерфейса добавить компоненту VideSoft VSPrinter.
А в ссылках References, если они не добавятся
автоматически, установить галочки ComponentOne VSReport 7.0 Control и VideSoft VSPrinter 7.0. Далее в статье я рассматриваю только малую
часть средств этой компоненты.
Печать табличных данных средствами VsView
Разработка средств печати в приложении при помощи компоненты
VsView можно разделить на два этапа. Первый – это
собственно программирование на VB, а второй – это
создание макета отчета в конструкторе, очень похожем на конструктор отчетов в Access. Начнем с программирования. Создаем новую форму. Ее
размер подбирайте сами. Расположим на ней объект VSPrinter
и назовем его «vp». Также можно на форму добавить меню
Файл, для экспорта, вызова окна параметров страницы и всего остального чего
пожелаете. Также следует добавить на форму объект Label,
обычно выше VSPrinter и где-то в удобном месте кнопку Command1 и измените свойство Caption
на «Отмена». По умолчанию эта кнопка Visible=False. Пример на рисунке ниже:
Обратите внимание, что кнопки перебора страниц, кнопка с
лупой и принтером появятся автоматически. Настроим объект VSPrinter.
Для этого вызовем свойство Custom. Появится диалоговое
окно. Нас интересуют только две вкладки: Navigation и Settings. Первая вкладка
предназначена для настройки панели навигации (переход по страницам).
Можно выбрать место расположения панели навигации.
Максимальный и минимальный Zoom, а также шаг его изменения.
На вкладке Setings можно
по-русски указать что будет написано на маленьком окошке, которое будет
появляться при отправке документа на принтер. На нем будет также кнопка для
отмены печати.
В поле NavBarMenuText можно
по-русски указать стандартные значения Zoom. В качестве
разделителя используется символ «|».
Собственно, настройка VSPrinter закончена. Удобно, что и ни говори.
При изменении размеров диалогового окна, хотелось бы, чтобы
и объект VSPinter также изменял свои размеры. Сделать
это легко:
Private Sub Form_Resize()
On Error Resume Next
vp.Move vp.Left, vp.Top, ScaleWidth - 2 *
vp.Left, _
ScaleHeight
- vp.Left - vp.Top
Me.Label1.Width = Me.Width - 50
End Sub
Пришло время создать шаблон отчета. Для этого воспользуемся
приложением vsrpt7.exe – VSReport Desiner. Создадим новый файл: File-New. Затем следует нажать на кнопку New Report. Приложение запросит строку
подключения к данным. Все это совпадает с тем, к чему подключен сам Grid или компонента AdoDc, если вы ее
все еще используете. Пример на рисунке ниже:
Не пугайтесь. В примере я взял рабочий вариант для
подключения к SQL серверу и достаточно сложный запрос к
данным. Вы можете подключаться непосредственно к таблице, а можете использовать
SQL запрос к данным, что предпочтительнее. Потом,
программно вы сможете переключать источник данных.
Дальнейшая методика работы с Designer
ничем не отличается от конструктора отчетов в Access. С
небольшим отличиями. Свойства объектов все по-английски, это одно из самых
главных неудобств и они достаточно сильно отличаются от Access,
но постепенно привыкаешь.
Основным рабочим объектом отчета является DataField.
Вы можете их располагать как в теле отчета, так и в колонтитулах листов.
Привязка к данным осуществляется через свойство Text! Если
к источнику данных вы подключились правильно, то при создании отчета
запускается мастер, напоминающий аналогичный мастер в Access.
Там просто нужно выбрать поля и стиль оформления. Потом можно легко подправить,
но так проще создавать все поля данных. Как видно из рисунка – набор свойств у
объекта весьма обширен. Большинство из них нам известны по VB
и Access. Некоторые, например RTF,
никогда не встречались. Описывать их все здесь я не имею возможности. Это явно
выходит за рамки статьи. Но методика создания отчета понятна. В моем случае
получился отчет, который я назвал «rpCDR». Шаблон
отчета сохраняется в файле *.xml. В одном xml файле может быть несколько отчетов с уникальными именами.
Часто бывает проще импортировать похожий отчет из Access. Возможно вам будет первое время проще создавать
отчеты в Access, а затем импортировать их. Чтобы
выполнить импорт следует нажать на кнопку Import. На
кнопке нарисован ключ – стандартная эмблема Access.
Работает это средство на ура! Очень удобно. Шаблон отчета создан.
Его можно посмотреть, если щелкнуть кнопку Preview.
Чтобы кнопка стала активной щелкните предварительно название отчета в подокне
слева.
Для предварительно просмотра отчет будет подключен к данным
и в соответствии с запросом SQL (или таблицей, если она
является источником) покажет их.
При работе с шаблоном из приложения VB
на vпотребуется подключить данный файл (*.xml) к программе, указать какой отчет показывать и подключить
его уже к тем данным, с которыми у нас работает пользователь. Сделать это легко
и ниже я покажу как это сделать. Report Designer можно
закрыть.
Замечание. Report Designer зарубежная программа. Файл xml сохраняется с кодировкой Win 1252. Нам нужно Win-1251. Придется
каждый раз вручную менять тэг кодировки. Сделать это легко при помощи Far manager. Не
забывайте этого делать. Периодически делайте резервные копии xml
файла для сложных отчетов. Были случаи потери русскоязычного оформления
безвозвратно.
Теперь подключим отчет к Vb-проекту.
Для начала добавим на форму объект VSReport.
После этого в процедуру, которая должна будет у вас в
программе передать данные на печать добавьте следующий код:
‘аналогия DoVents в
Бейсике
VSR.DoEvents = True
‘указали путь к файлу xml и имя
рабочего шаблона
VSR.Load App.Path
& "\templates.xml", "rpCDR"
‘ подключили к шаблону нужный нам Recordset
VSR.DataSource.Recordset = rsa
‘ программно изменили текст в одном
из полей в заголовке отчета
VSR.Fields("F_sostoan").Text
= “По состоянию на 2005 год”
‘ передали отчет на предварительный просмотр
VSR.Render vp
Все! У меня этот код находится в процедуре Form_Activate. Так как предварительный просмотр открывается в
новом окне.
Вы можете воспользоваться событиями, чтобы сделать интерфейс
еще более удобным. Например, если текст отчета очень большой можно показать
пользователю, что идет формирование отчета.
Private Sub VP_StartDoc()
‘запретили доступ к меню на время формирования отчета
mnuFile.Enabled = False
DoEvents
‘ показали надпись на форме
Me.Label1.Caption = "Формирование отчета..."
DoEvents
‘ показали кнопку Отмена
Me.Command1.Visible = True
End Sub
Чтобы кнопка Отмена действовала нужно ее запрограммировать.
Private Sub Command1_Click()
If VSR.IsBusy Then
VSR.Cancel = True
End If
Unload Me
End Sub
Формирование отчета прерывается мгновенно!
Учитывая, что пользователь может попытаться закрыть окно
отчета раньше, чем он сформируется, закрытие окна нужно выполнить корректным
образом:
Private Sub Form_Unload(Cancel As Integer)
On Error Resume Next
If VSR.IsBusy Then
VSR.Cancel = True
End If
End Sub
Чтобы после окончания формирования отчета пропали надпись
«Формирование отчета» и кнопка «Отмена» следует запрограммировать другое
событие:
Private Sub VP_EndDoc()
DoEvents
mnuFile.Enabled = True
DoEvents
Me.Label1.Caption =
""
DoEvents
Me.Command1.Visible =
False
End Sub
Экспорт из VSReport
VSReport обладает встроенными
средствами экспорта. Ниже предлагаю готовую процедуру экспорта:
Public Sub exExportPrint(ByVal VSR As VSReport,
ByVal hw As Long, _
ByVal
fr As Form)
Dim strFilename As String
On Error GoTo err
‘здесь нужно сделать запрос имени файла.
‘ я предположил, что файл уже известен
strFilename = “export.pdf”
If strFilename = "" Then Exit Sub
If Dir(strFilename) <> "" Then
If MsgBox("Файл
" & strFilename & " уже существует. Переписать?", vbInformation + vbYesNo, "SQL Error") = vbYes
Then
Kill strFilename
Else
Exit Sub
End If
End If
MousePointer = vbHourglass
DoEvents
Me.Label1.Caption = "Экспорт..."
Me.Command1.Visible = True
mnuFile.Enabled = False
‘ подключаем данные, так как после
‘ VSR.Render vp
‘ данные из VSR
пропадают
VSR.DataSource.Recordset = rsa
DoEvents
MousePointer = vbHourglass
‘собственно экспорт
If InStr(1, strFilename, ".txt") > 0
Then
VSR.RenderToFile strFilename, vsrText
ElseIf InStr(1, strFilename, ".html")
> 0 Then
VSR.RenderToFile strFilename, vsrHTML
ElseIf InStr(1, strFilename, ".pdf")
> 0 Then
VSR.RenderToFile strFilename, vsrPDF
End If
Me.Label1.Caption = ""
Me.Command1.Visible = False
mnuFile.Enabled = True
MousePointer = vbDefault
DoEvents
Exit Sub
err:
MousePointer = vbDefault
Me.Label1.Caption = ""
Me.Command1.Visible = False
End Sub
Как видно из кода я предлагаю выполнять экспорт в трех
форматах: TXT, HTML, PDF. Сразу предупреждаю, что на любом этапе экспорта данных
может возникнуть проблема с кодировкой символов.
Резюме
Все ли мы теперь рассмотрели? Боюсь, что все равно нет!
Очень многие вопросы остались без внимания. Я предполагаю, что по мере работы и
накопления опыта вы все свои проблемы решите сами, а данная статься является
только определенным толчком на курс в нужном направлении.
Воронеж, июнь 2005 года