Привет Артем. Несколько дней назад получил письмо следующего содержания:
From mpv@police.a-teleport.com Thu Feb 24 12:41:17 2000 From: "Pavel Mojseenko V." <mpv@police.a-teleport.com> To: <lerick@mail.ru> Subject: =Пару вопросов по MFC, VC и базам данных Как можно реализовать в VC c помощью MFC (или без нее) при работе с реляционной базой данных форму ввода по подобию Access, где в одной форме реализован ввод информации сразу в 2 и более таблиц, связанные между собой отношением 1 ко многим. Во всех учебниках и у Вас на сайте рассматривается подключение с помощью AppWizard-a только к не реляционной модели БД (т.е. представленных одной таблицей). Если есть возможность опишите это в одном из шагов.
Я конечно не гуру в VC++, но у меня созрел вариант как это можно реализовать средствами VC++. Кстати, я во многом основывался на Ваших шагах + немножко эксперимента. Не думаю, что он идеален. Просто как вариант, где используются только Мастер и немножко SQL.
Создаем БД в MS Access с именем TestDB.mdb. Моя подопытная БД состоит из двух таблиц: Customer и Product. Не буду объяснять как подобную БД можно создать с помощью среды Access. Просто создайте эту БД и запомните ее местонахождение.
Структура таблицы Customer:
ID - идентификационный номер клиента (тип: счетчик)
CustName - имя клиента (тип: текст)
Структура таблицы Product:
ID - идентификационный номер клиента (тип: число)
Product - название товара (тип: текст)
Смысл БД достаточно прост: в одной таблице находится список клиентов(Customer), а в другой список товаров(Product), которые покупает клиент. Они имеют связь по полю ID соответственно (один-ко-многим).
Допустим, нам надо добавить товар купленный клиентом. Как это можно сделать с помощью одной формы ?
Создаем с помощью AppWizard(exe) новый проект с именем DB.
Step 1 - Single document
Step 2 - Database view without file support
Жмем кнопку Data source, указываем как показано на рисунке:
Каждый раз нас будут спрашивать: где искать БД ? Впрочем, можно и описать путь к БД, кому как удобнее (см. "Шаг 110 - Готовим данные для записной книжки"). Жмем OK. Затем спросят путь к БД и какие таблицы нас интересуют:
Выбираем обе таблицы (держим Shift и кликаем по искомым таблицам)
Жмем на OK, а потом уже можно и на Finish. Итак, мы создали проект с использованием БД.
В ресурсах найдите диалоговое окно ID_DB_FORM (или что-то в этом роде) и приведите его примерно к такому же виду, как у меня. Впрочем, принципиально здесь только наличие элементов Edit. У меня получилось: IDC_EDIT1 (ID), IDC_EDIT2 (Customer), IDC_EDIT3 (Product name), IDC_EDIT4 (New Product)
Слева у меня получились поля из таблицы Customer, а справа - из таблицы Product. Свяжите первые три элемента Edit с соответствующими полями таблиц (m_ID, m_CustName, m_ProdName соответственно). Как это сделать Артем уже достаточно подробно описывал в Шаге 111 (MFC). Скажите ему спасибо. С четвертым же элементом Edit (IDC_EDIT4) я связал две переменные: m_Add(Category: Value; Variable type: CString) и m_AddCtl(Category: Control; Variable type: CEdit). Пояснения как это можно сделать найдете в "Шаг 30 - Первый проект с AppWizard", "Шаг 34 - Понимание DDX". Надеюсь, что все пока получается.
Теперь обратимся к тому, что нам Мастер сделал. Он, как умный парень, считает, что нам может понадобиться только одна таблица. И результирующей у нас будет только одна таблица. Поэтому при запуске проекта Вы увидите в панели инструментов стрелочки для передвижения по записям только для одной таблицы. Можете поиграться с тем, что нам сотворил Мастер, но все равно это не то, что нужно. Я же, как человек достаточно ленивый, не захотел особо что-то переделывать, напрягаться, и решил применить свои скромные знания языка SQL. Я просто сделаю из двух таблиц одну с помощью запроса SQL.
Мастер при создании проекта, помимо всего прочего "классового добра", сгенерировал нам два класса CDBSet(родитель - CRecordset) и CDBView(родитель - CRecordView). С ними и поработаем.
Класс CDBView отвечает за то, что мы увидим на экране. В нем описана переменная m_pSet, которая является указателем на объект класса CDBSet. При конструировании объекта класса CDBView
CDBView::CDBView() : CRecordView(CDBView::IDD) { //{{AFX_DATA_INIT(CDBView) m_pSet = NULL; m_Add = _T(""); //}}AFX_DATA_INIT }я особо хочу отметить строку
m_pSet = NULL;
Дело в том, что, как можно заметить, класс CDBSet наследован из класса CRecordset и работает абсолютно также, как и родительский класс. При открытии таблицы с помощью CRecordset используется обычный SQL запрос. При инициализации объекта так, как это показано в конструкторе CDBView::CDBView(), т.е. когда указателю присваивается значение NULL, соединение и запрос формируются с помощью функций CDBSet::GetDefaultConnect() и CDBSet::GetDefaultSQL(). Нас интересует функция CDBSet::GetDefaultSQL(). Я ее подправил так, чтобы она выглядела следующим образом:
CString CDBSet::GetDefaultSQL() { return _T("[Customer] Left Outer Join [Product] ON Product].ID=[Customer].ID"); }
Это только часть SQL запроса, которая следует после служебного слова FROM запроса. С помощью этого запроса я связываю наши таблицы по полю ID. Я бы Вам объяснил как это работает, но это наверное намного лучше объяснено в любом описании языка SQL. Смотрите раздел про Outer Join. Лучше взгляните на результат. С помощью стрелочек перехода с записи на запись можно последовательно просматривать все товары, записанные на каждого клиента.
Наконец-то добрался до самого важного момента. У нас остался один незатронутый элемент управления - кнопка "Add New". Создайте для нее реакцию на событие OnClick. Артем много раз показывал как это можно сделать. При нажатии на эту кнопку произойдет добавление нового товара(название берется из введенного в элемент управления IDC_EDIT4) текущему клиенту.
У меня получилась следующая функция:
void CDBView::OnButton1() { UpdateData(); if (m_Add!="") { m_AddCtl.SetSel(-1,0); // удаляем любое возможное выделение в элементе IDC_EDIT4 m_AddCtl.SetSel(0,-1); // выделяем все символы в поле элемента IDC_EDIT4 m_AddCtl.Clear(); // очищаем char buffer[4]; CString str; _itoa(m_pSet->m_ID,buffer,10); str=buffer; m_pSet->m_pDatabase->ExecuteSQL("insert into [Product] (ID,ProdName) Values ("+str+",'"+m_Add+"')"); UpdateData(); m_pSet->Requery(); m_pSet->MoveFirst(); UpdateData(false); } else AfxMessageBox("Поле ввода не должно быть пустым !!!"); }
Сначала обновляем значения таких переменных, как m_Add, m_AddCtl и всех остальных, которые описаны в функции CDBView::DoDataExchange(CDataExchange* pDX). Если переменная m_Add содержит не пустую строку, то производим операцию добавления. Затем производим очищение поля элемента IDC_EDIT4. Однако отмечу, что само очищение произойдет немножко позже, когда мы вызовем функцию UpdateData(). Преобразуем с помощью стандартной функции _itoa значение идентификатора ID типа long в символьный вид, но нам нужна переменная типа CString, чтобы применить полученное значение при формировании запроса SQL.
В переменной m_pDatabase хранится ссылка на БД(класс CDatabase), с которой мы работаем. В свою очередь, в классе CDatabase есть функция ExecuteSQL, с помощью которой можно выполнить любой запрос к БД(но без возвращения каких-либо значений). Этим мы и воспользовались и вставили с помощью запроса SQL новую запись в таблицу Product.
Затем обновляем(перечитываем) наш набор данных с помощью функции Requery, перемещаем указатель на первую запись с помощью функции MoveFirst и обновляем отображение с помощью UpdateData(false).
Вроде все, надеюсь, что ничего не забыл. Да, прежде чем пробовать проект, создайте хотя бы несколько записей в таблице клиентов. Я же, все-таки, не все предусмотрел. Это только один из примеров реализации подобной задачи. Дерзайте и у Вас все получится.
Прислал Valeri Khromov.