Добавил:
Upload Опубликованный материал нарушает ваши авторские права? Сообщите нам.
Вуз: Предмет: Файл:
Лекции ОТИ - копия.doc
Скачиваний:
5
Добавлен:
01.07.2025
Размер:
8.91 Mб
Скачать

Занятие №28

Классы MFC для поддержки ODBC. Класс CRecordset.

Класс CRecordset

Recordset - это источник строк. Этот обьект можно рассматривать как оболочку для строк или колонок таблицы. То есть этот обьект может включать, как всю таблицу, так и часть её на основе SQL запроса. В файле базы данных может храниться не одна таблица. Базы данных для этого и предусмотрены. Только старые версии персональных баз данных типа FoxPro хранят каждую таблицу в отдельном файле.

Итак для создания обьекта нам необходимо обьявить переменную. Обьект создается на основе конструктора. Вот описание конструктора.

CRecordset( CDatabase* pDatabase = NULL);

Как видите в конструктор можно передать указатель на объект типа CDatabase, который отвечает за доступ к базе данных. Конструктор предусматривает и простое создание без передачи обьекта CDatabase. Действительно манипулируя доступными дланными есть возможность в последствии прикрепить класс CDatаbase.

Будем cчитать, что мы создали и открыли этот обьект.

Использование конструктора.

if (cdbMyDB.IsOpen())

{

CRecordset cr(&cdbMyDB);

}

Или вот так например:

if (cdbMyDB.IsOpen())

{

CRecordset cr;

cr.m_pDatabase=&cdbMyDB;

}

В плане использования два предыдущих конструктора идентичны, и в плане функциональности тоже. Переменная m_pDatabase, в которой хранится указатель на базу данных в виде указателя на класс CDatabase, общедоступная и может быть использована в любой момент. Вот её объявление:

CDatabase* m_pDatabase;

Имея созданный объект Crecordset, Вы можете теперь открыть таблицу на основе SQL запроса.

cr.Open(CRecordset::forwardOnly,"SELECT * FROM TABLE1;",CRecordset::readOnly);

Подразумевается, что в Вашей базе данных присутствует таблица Table1. Вызов данной функции может вызвать исключительные ситуации следующих типов:

CDBException

CMemoryException;

Расшифровка этих исключительных ситуаций позволит Вам получить информацию о произошедшей ошибке. Только не забудьте при подобных испытаниях с исключительными ситуациями переключаться на тип проекта Release, дабы не нарываться на злобный ASSERT. Смотрите пример ниже. Таблицы TABLE21 у меня нет.

CRecordset cr;

cr.m_pDatabase=&cdbMyDB;

try

{

cr.Open(CRecordset::forwardOnly,"SELECT * FROM TABLE21;",

CRecordset::readOnly);

cr.Close();

}

catch(CDBException cdb)

{

AfxMessageBox(cdb.m_strStateNativeOrigin);

}

Внизу показано собщение в результате работы данного кода.

Класс CRecordset имеет функцию IsOpen она вам знакома из класса CDatabase:

if (cr.IsOpen()) ......;

Обзор SQL

Для построения объекта CRecordset необходим параметр в виде SQL запроса к БД.

SQL - Structured Query Language. По русски это язык построения запросов. Его появление вызвано переходом от настольных СУБД типа FoxPro, Paradox и т.д. к системам управления базами данных. Эти системы сформировались в понятие сервера баз данных. Язык SQL является стандартом для реляционных баз данных и его поддерживают практически все систему управления данными. Но к спецификации SQL очень часто производители конкретной БД добавляют свои расширения.

Если кратко, то системы управления данными стремятся к модели клиент - сервер. Сервер - это мощное программное обеспечение работающее обычно на неменее можной аппаратной части, а клиент это программное обеспечение, которое мы с вами разрабатываем. При этом подходы могут быть разные. Например каждому клиенту сервер, или один сервер (часто мультипроцессорный) для всех. Но самое интересное, что нас это не очень волнует. Использование ODBC и SQL позволяют создать приложения не особенно зависимые от сервера баз данных, где хранятся наши таблицы, запросы и т.д. Конечно иcпользуя только стандарты, наше приложение будет работать на многих платормах, но в замен мы не сможем получить максимум производительности. Так вот мое мнение такое. Чем оптимизировать программу, дешевле увеличить оперативную память в два раза на сервере и получить совместимость.

Важной особенностью SQL является то, что этот язык описывает только результат, процесс получения на нем описать нельзя. То есть я могу сказать выбери мне данные, но механизм не пишу. Кроме того следует отметить разницу программирования на основе SQL от программирования на других языках. При получении результатов нет необходимости пробегать по всем полям. Запрос работает сразу со всей базой данных. Это очень серьезное отличие от языка скажем FoxPro.

Запрос SQL состоит из одного или нескольких операторов SQL разделенных точкой с запятой. Самые важные операторы - описание стандартов ANSI/ISO SQL.

В прошлом шаге мы использовали оператор SQL для создания обьекта CRecordset.

cr.Open(CRecordset::forwardOnly,"SELECT * FROM TABLE1;",CRecordset::readOnly);

Мы использовали оператор Select. Этот оператор позволяет получить данные. Ниже приведена расшифровка.

SELECT // выбери

* // все колонки таблицы

FROM // для таблицы

TABLE1; // имя таблицы

Данный оператор выберет всю таблицу. Но мы с Вами можем этим оператором ограничить диапазон выбора. Например:

SELECT TABLE1.Family FROM TABLE1;

Выбирает только столбец Family в таблице TABLE1.

Если необходимо выбрать несколько столбцов, то они должны быть разделены запятой:

SELECT TABLE1.Family, TABLE1.ID FROM TABLE1;

Два столбца Family и ID.

Используя WHERE и логические операции AND,OR можно составить довольно сложное условие.

SELECT TABLE1.Family, TABLE1.ID FROM TABLE1 WHERE (((TABLE1.Family)="Petrov") AND ((TABLE1.ID)=1));

Также можно указать, что производить выбор из двух или более таблиц перечислив их после FROM через запятую

SELECT TABLE1.Family, TABLE1.ID, TABLE2.ID_PROF FROM TABLE1, TABLE2;

Пришло время выводов. Для создания обьекта CRecordse на основе запроса SQL только те данные, с которыми вы будете работать. Тем самым перенеся тяжесть обработки данных на сервер и съэкономив системные ресурсы на клиенте.

Продолжаем изучать класс Recordset.

Вот полное описание функции Open:

virtual BOOL Open( UINT nOpenType = AFX_DB_USE_DEFAULT_TYPE,

LPCTSTR lpszSQL = NULL, DWORD dwOptions = none );

throw( CDBException, CMemoryException );

Как видите при открытии набора записей есть несколько типов:

CRecordset::dynaset

CRecordset::snapshot

CRecordset::dynamic

CRecordset::forwardOnly

snapshot - набор записей типа моментального снимка (Snapshot-type Recordset). Набор записей статического типа или, другими словами, типа моментального снимка (Snapshot-type Recordset) содержит копию данных, которую нельзя изменять. Этот тип набора записей удобно использовать для поиска записи, удовлетворяющей какому-либо критерию, или при генерации отчетов. Следует помнить, что при использовании этого набора записей содержимое всех полей заносится в память, что может потребовать значительных ресурсов памяти.

Смотрим как это сделать:

void CDatebaseDlg::OnOpen()

{

CDatabase cdbMyDB;

cdbMyDB.OpenEx("DSN=123");

if (cdbMyDB.IsOpen())

{

CRecordset cr(&cdbMyDB);

try

{

cr.Open(CRecordset::snapshot,

"SELECT Family FROM TABLE1", CRecordset::readOnly );

if (cr.IsOpen()) AfxMessageBox("Open");

cr.Close();

}

catch(CDBException cdb)

{

AfxMessageBox(cdb.m_strStateNativeOrigin);

}

}

else AfxMessageBox("Not Open");

cdbMyDB.Close();

}

Для связи с базой данных мы применили новую функцию OpenEx. Она сразу открывает и инициализирует связь с базой данных и рекомендована к использованию. В ней я указал драйвер в формате DNS=name.

И выделенное жирным открытие набора строк. Этот набор строк можно проверить открытием небезызвестной IsOpеn. Этот код защищен от ошибок. Например при ошибочном SQL операторе типа SELECT Family FROM TABLEw1 вы получите сообщение:

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

Этот код можно сократить и он будет работать.

void CDatebaseDlg::OnOpen()

{

CRecordset cr(NULL);

try

{

cr.Open(CRecordset::snapshot,

"SELECT Family FROM TABLE1", CRecordset::readOnly );

if (cr.IsOpen()) AfxMessageBox("Open");

cr.Close();

}

catch(CDBException cdb)

{

AfxMessageBox(cdb.m_strStateNativeOrigin);

}

}

Как видите, в указание драйвера мы указали NULL. Что будет? ODBC спросит Вас какой.

Итак, мы можем получить обьект CRecordset, например, как ниже. Теперь пора изучить его свойства. Открытие сильно упрощено.

void CDatebaseDlg::OnOpen()

{

CRecordset cr(NULL);

try

{

cr.Open(CRecordset::snapshot,

"SELECT Family,Count FROM TABLE1", CRecordset::readOnly );

//........

cr.Close();

}

catch(CDBException cdb)

{

AfxMessageBox(cdb.m_strStateNativeOrigin);

}

}

Нам может понадобиться SQL, на основе которого создан данный источник строк:

// Описание const CString& GetSQL( ) const;

//......

cr.Open(CRecordset::snapshot,"SELECT Family,Count FROM TABLE1",CRecordset::readOnly);

AfxMessageBox(cr.GetSQL());

//......

Теперь надо проанализировать сколько столбцов нам вернулось. Вы можете сказать: "ведь мы знаем, что два из запроса SQL", но запрос можно построить и так, что это будет не факт. Смотрите SELECT. Получаем количество столбцов:

// Описание short GetODBCFieldCount( ) const;

short nFields = cr.GetODBCFieldCount();

Используя количество столбцов можно получить о них информацию с помощью GetODBCFieldInfo():

// Описание void GetODBCFieldInfo( short nIndex,

// CODBCFieldInfo& fieldinfo ); throw( CDBException );

short nFields = cr.GetODBCFieldCount();

for (short x=0;x < nFields;x++)

{

CODBCFieldInfo fieldinfo;

short pos=x;

cr.GetODBCFieldInfo(pos,fieldinfo );

AfxMessageBox(fieldinfo.m_strName);

}

Информация о типе полей находится в струтуре CODBCFieldInfo():

struct CODBCFieldInfo

{

CString m_strName;

SWORD m_nSQLType;

UDWORD m_nPrecision;

SWORD m_nScale;

SWORD m_nNullability;

};

Мы воспользовались данными из структуры m_strName. В этом поле структуры находится имя столбца запроса.

Второе поле m_nSQLType говорит нам о типе данных в данной колонке. Вот описание некоторых типов (например, нет OLE).

#define SQL_UNKNOWN_TYPE 0

#define SQL_CHAR 1

#define SQL_NUMERIC 2

#define SQL_DECIMAL 3

#define SQL_INTEGER 4

#define SQL_SMALLINT 5

#define SQL_FLOAT 6

#define SQL_REAL 7

#define SQL_DOUBLE 8

#define SQL_DATETIME 9

#define SQL_VARCHAR 12

Пример ниже показывает как этим можно воспользоваться. Он просматривает все колонки в поисках поля типа Integer и при нахождении выдает о нем информацию:

for (short x=0;x < nFields;x++)

{

CODBCFieldInfo fieldinfo;

short pos=x;

cr.GetODBCFieldInfo(pos,fieldinfo );

if (fieldinfo.m_nSQLType==SQL_INTEGER)

AfxMessageBox("integer " + fieldinfo.m_strName);

}

Поле структуры m_nPrecision соотвествует в ACCESS ширине поля.

Поле Scale говорит о том, сколько знаков после запятой у числового поля, а m_nNullability отвечает за то, может ли это поле принимать NULL. И для этого данное поле необходимо сравнить с SQL_NULLABLE или с SQL_NO_NULLS.

Записи в CRecordset

Итак, с информацией о столбцах мы разобрались. Теперь неплохо бы получить само содержание столбца и текущей строки, а лучше всего вместе. Для реализации этой функции есть переменная типа GetFieldValue, в которую передается два параметра. Номер столбца и переменная типа CDBVariant для помещения значений. Вот код, который пробегает по строкам первого столбца и показывает содержимое:

void CDatebaseDlg::OnOpen()

{

CRecordset cr(NULL);

try

{

cr.Open(CRecordset::snapshot, "SELECT Family,Count FROM TABLE1",

CRecordset::readOnly );

CDBVariant var;

short index=0;

cr.Move(0);

while (!cr.IsEOF())

{

cr.GetFieldValue(index,var);

AfxMessageBox(*var.m_pstring);

cr.MoveNext();

}

cr.Close();

}

catch(CDBException cdb)

{

AfxMessageBox(cdb.m_strStateNativeOrigin);

}

}

Переменная типа CDBVariant принимает значение поля и может принять любое значение. Описание этой переменной. Только это класс, а не переменная :-)

// Указатели на данные в этом классе

m_dwType

m_boolVal

m_chVal

m_iVal

m_lVal

m_fltVal

m_dblVal

m_pdate

m_pstring

m_pbinary

Конструктор этого класса не имеет параметров, и поэтому в коде мы спокойно объявили объект класса. Команда Move перемещает указатель на заданную позицию. Мы ставим 0 и перемещаем в первую позицию. После этого задаём цикл, проверяя с помощью IsEOF достижение конца выборки (источника строк).

Функция GetFieldValue данные из указанного столбца в переменную. Эта функция перегруженная и может иметь несколько возможных параметров. Мы применили этот вариант.

void GetFieldValue( short nIndex, CDBVariant& varValue,

short nFieldType = DEFAULT_FIELD_TYPE );

throw( CDBException, CMemoryException );

Зная, что в этом столбце находится строка, мы взяли соотвествующий ей указатель m_pstring. Проверить это можно запустив в режиме отладки до строки AfxMessageBox.

То есть, если Вы даже не предпологаете тип данных, применив данный прием, вы можете попробовать извлечь выводы о типе данных, а самое главное реально прочитанное из данного столбца. Наверно, не трудно догадаться, что если столбец будет иметь тип Date, то и указатель нужен соотвествующий.

Для того, чтобы показать все записи используем функцию MoveNext, которая перемещает нас на следующую запись, и так до достижения окончания записей, которые тестируются функцией IsEOF.

При использовании обьекта CRecordset возникает необходимость подсчитать сумму в поле. Возникает желание пробежаться по всем полям с помощью MoveNext и произвести подсчет. Это можно. На всякий случай задумайтесь о другом варианте. В примере ниже подсчитаем сумму на основе оператора SQL SELECT:

void int_AfxMessageBox(int i)

{

char t[10];

itoa(i,t,10);

AfxMessageBox(t);

}

void CDatebaseDlg::OnOpen()

{

CRecordset cr(NULL);

try

{

// Внимание тестировалось на ACCESS 97

cr.Open(CRecordset::snapshot,

"SELECT Sum(TABLE1.Count) AS SumCount FROM TABLE1",

CRecordset::readOnly );

cr.Move(0);

CDBVariant var;

short index=0;

cr.GetFieldValue(index,var);

double count=var.m_dblVal;

int_AfxMessageBox((int)count);

cr.Close();

}

catch(CDBException cdb)

{

AfxMessageBox(cdb.m_strStateNativeOrigin);

}

}

Здесь построен SQL оператор, который считает сумму в колонке.

Занятие №29

Использование классов MFC для поддержки ODBC. Обновление приложений.

Фильтры, сортировка CRecordset

Если тщательно разобраться с SQL оператором SELECT, то с помощью него можно получить практически любые виды от исходной таблицы. Но этот способ не единственный. Даже если с помощью данного оператора Вы отсортировали записи, всегда возникает необходимость допустим их отсортировать по полю или наложить фильтр. Это можно сделать и на основе доступных переменных класса CRecordset.

Для накладывания фильтра в классе CRecordset есть переменная m_strFilter. В эту переменную можно поместить фильтр и вызвать функцию обновления источника строк.

void CDatebaseDlg::OnOpen()

{

CRecordset cr(NULL);

try

{

cr.Open(CRecordset::dynaset, "SELECT * FROM TABLE1");

cr.m_strFilter ="Count<100";

cr.Requery();

CDBVariant var;

short index=1;

cr.Move(0);

cr.GetFieldValue(index,var);

AfxMessageBox(*var.m_pstring);

cr.Close();

}

catch(CDBException cdb)

{

AfxMessageBox(cdb.m_strStateNativeOrigin);

}

}

Обратите внимание, что мы обьявили таблицу как dynaset, что позволяет проводить подобные операции, в будущем эта опция разрешит добавлять, редактировать и удалять записи.

В фильтр мы поместили строку, которая указывает, что поле должно быть меньше 100. Если вы запустите, то увидите фамилию Vasilev, так как он единственный, который имеет это поле меньше 100. После задания фильтра мы вызвали функцию Requery, которая обновила источник строк на основе фильтра. Не забывайте её использовать.

Для сортировки тоже есть переменная и она носит имя m_strSort, в ней молжно указать поле ,по которому будет произведена сортировка.

void CDatebaseDlg::OnOpen()

{

CRecordset cr(NULL);

try

{

cr.Open(CRecordset::dynaset, "SELECT * FROM TABLE1");

cr.m_strSort="Family";

cr.Requery();

CDBVariant var;

short index=1;

cr.Move(0);

cr.GetFieldValue(index,var);

AfxMessageBox(*var.m_pstring);

cr.Close();

}

catch(CDBException cdb)

{

AfxMessageBox(cdb.m_strStateNativeOrigin);

}

}

При работе этого кода первым будет Artem, хотя в исходной таблице он последний. Логика точно такая же, как и выше в примере. Установка поля сортировки и обновление.

Рассмотрение RFX

RFX - Record Field Exchange. Это механизм обмена данными между классом потомком от CRecordset и самой базой данных. Работа этого механизма по смыслу аналогична работе DDX. Данный механизм применяет ClassWizard при автоматическом создании класса CRecordset.

Для работы с RFX нам необходимо создать сына от класса CRecordset на основе известной структуры базы данных. Мы создали в файле ACCESS новую таблицу TABLE3 с одним текстовых полем, и поместили туда две записи. Итак, нам известна структура - одна текстовая колонка (не являющаяся ключевым полем). Эта текстовая колонка.

class MyCrec:public CRecordset

{

public:

MyCrec( CDatabase* pDatabase = NULL);

virtual void DoFieldExchange(CFieldExchange* pFX);

CString m_Fam;

};

Как видите ничего необычного нет. Простое наследование, перегрузка конструктора, обьявление функции DoFieldExchange для организации механизма обмена и переменной типа CString для соответствия формату поля колонки из таблицы базы данных. Мы не сделали эту переменную private, но можно это делать спокойно.

Реализация конструктора.

MyCrec::MyCrec( CDatabase* pDatabase)

:CRecordset(pDatabase)

{

}

Функция обмена:

void MyCrec::DoFieldExchange(CFieldExchange* pFX)

{

pFX->SetFieldType(CFieldExchange::outputColumn);

RFX_Text(pFX,_T("Famili"),m_Fam);

}

В простом приближении правило простое. Перед функциями обмена надо вызвать SetFieldType.

Эта функция позволяет Вам произвести обмен между переменной типа CString и текущей строкой в конкретной колонке базы данных. У меня колонка называется Famili. Обратите внимание на то, что строка помещена в конструкцию _T(...), это сделано для гарантии создания объекта типа CString. А вот ниже полное описание этой функции.

void RFX_Text( CFieldExchange* pFX, const char* szName,

CString& value, int nMaxLength = 255, int nColumnType = SQL_VARCHAR,

short nScale = 0 );

После создания данного класса мы можем воспользоваться им, например, для добавления новой записи в Базу данных.

void CDatebaseDlg::OnOpen()

{

MyCrec cr(NULL);

try

{

cr.m_nFields=1;

cr.Open(CRecordset::dynaset, "SELECT * FROM TABLE3");

if(cr.CanAppend())

{

cr.AddNew();

}

cr.m_Fam="Kaev";

cr.Update();

cr.Close();

}

catch(CDBException cdb)

{

AfxMessageBox(cdb.m_strStateNativeOrigin);

}

}

Итак, объявляем объект от нашего класса. Устанавливаем количество колонок в m_nFields. Открываем набор записей, как динамический (т.е. в который можно вносить измения) - dynaset. CanAppend проверяет возможность добавления записей. Запись добавляется пустая. Вводим в переменную содержание и заносим данные непосредственно в базу данных Update.

Редактирование записей

Добалять записи мы научились. Но их необходимо еще и редактировать.

Смотрим код:

void CDatebaseDlg::OnOpen()

{

MyCrec cr(NULL);

try

{

cr.m_nFields=1;

cr.Open(CRecordset::dynaset, "SELECT * FROM TABLE3");

cr.Move(0);

cr.Edit();

AfxMessageBox(cr.m_Fam);

cr.m_Fam="Yaci";

cr.Update();

cr.Close();

}

catch(CDBException cdb)

{

AfxMessageBox(cdb.m_strStateNativeOrigin);

}

}

Первой камандой мы перемещаемся к первой записи (0).

Функцией Edit мы разрешаем начать редактировать запись. Эта функция вызовет механизм RFX дабы уведомить о том, что мы будет редактировать. Соответственно мы можем увидеть, что изменим. Для этого и используется функция AfxMessageBox.

Теперь мы присваиваем новое значение переменной и вызываем Update, дабы закрепить изменение.

Получилось. Теперь удаление.

void CDatebaseDlg::OnOpen()

{

MyCrec cr(NULL);

try

{

cr.m_nFields=1;

cr.Open(CRecordset::dynaset, "SELECT * FROM TABLE3");

cr.Move(1);

cr.Delete();

cr.Close();

}

catch(CDBException cdb)

{

AfxMessageBox(cdb.m_strStateNativeOrigin);

}

}

Код понятен. Открываем CRecordset, переходим на запись, удаляем, закрываем.

Класс CRecordView

Класс CRecordView является видом представления, специально предназначенного для отображения данных из набора записей. Как и всякий класс, произведённый от CFormView, этот класс основан на диалоговом ресурсе. В частности, это позволяет использовать обмен данными с управляющими элементами диалога для отображения данных из набора записей. Кроме того, CRecordView обеспечивает поддержку навигации по набору записей. В классе определены два конструктора:

CRecordView(LPCSTR lpszTemplateName);

CRecordView(UINT nIDTemplate);

Параметр lpszTemplateName содержит указатель на строку с именем шаблона диалогового ресурса, а nIDTemplate – числовой идентификатор шаблона диалогового ресурса. Когда вы создаёте свой объект, производного от CRecordView типа, вызовите любую форму конструктора базового класса. Унаследованный этим объектом метод OnInitialUpdate() вызывает метод UpdateData(), который, в свою очередь, содержит вызов метода DoDataExchange(). Этот вызов приводит к соединению управляющих элементов формы с членами-данными, содержащими поля набора записей, созданными с помощью ClassWizard. Эти члены-данные нельзя использовать до вызова метода OnInitialUpdate() базового класса. Метод OnGetRecordset(), имеющий прототип:

Virtual Crecordset*OnGetRecordset()=0;

возвращает указатель на объект, произведённый от класса CRecordset. Вы должны переопределить этот метод в своём производном классе. Если вы определяете свой класс представления записей с помощью ClassWizard, последний создаёт преопределённый метод сам. Реализация метода, создаваемая ClassWizard по умолчанию, возвращает указатель на набор записей, хранящийся в объекте представления записей, если он существует. Если такой указатель не определён, ClassWizard создаёт объект набора записей заданного вами типа и вызывает его метод Open(), затем возвращает указатель на открытый объект. Если объект создать не удалось, метод возвращает NULL.

В классе определены два метода, которые позволяют определить, не является ли текущая запись первой или последней. Метод IsOnFirstRecord():

BOOL IsOnFirstRecord();

возвращает TRUE, если текущая запись является первой, а метод IsOnLastRecord():

BOOL IsOnLastRecord();

возвращает TRUE, если она является последней. Заметим, что последний метод надёжно определяет последнюю запись набора только после того, как пользователь переместился за последнюю запись, а затем перемещается на последнюю запись или любую предыдущую.

Последний из определённых в классе методов – метод OnMove() – обновляет поля в источнике данных, если текущая запись изменена, и осуществляет перемещение к заданной записи. Он имеет простой прототип:

Virtual BOOL OnMove(Uint nIDMoveCommand); throw(CDBException);

Метод возвращает TRUE, если перечисленные действия выполнены успешно. Единственный параметр метода может принимать одно из значений:

ID_RECORD_FIRST – переместиться к первой записи набора записей;

ID_RECORD_LAST– переместиться к последней записи набора записей;

ID_RECORD_NEXT– переместиться к следующей записи набора записей;

ID_RECORD_PREV– переместиться к предыдущей записи набора записей.

Заметим, что AppWizard создаёт ресурс меню, содержащий элементы для навигации по набору записей, а если вы выбрали создание швартуемой панели инструментов, то в ней будут созданы кнопки, отвечающие командам навигации по набору записей При перемещении за последнюю запись набора в форме продолжает отображаться эта запись; точно также при перемещении перед первой записью набора продолжает отображаться первая запись. Метод выбрасывает исключение, если набор несодержит записей.