Программирование на Visual C++. Архив рассылки - Алекс Jenter
Шрифт:
Интервал:
Закладка:
CComPtr<IXMLDOMNode> spXMLChildNode;
hr = spXMLDOM->createNode(CComVariant(NODE_ELEMENT), CComBSTR("xmlchildnode"), NULL, &spXMLChildNode);
if (FAILED(hr)) throw "Unable to create 'xmlchildnode' XML node";
if (spXMLChildNode.p == NULL) throw "Unable to create 'xmlchildnode' XML node";
Если парсеру удалось создать новый узел, следующий шаг – разместить его в дереве XML. Метод IXMLDOMNode::appendChild() – как раз то, что нам нужно.
CComPtr<IXMLDOMNode> spInsertedNode;
hr = spXMLNode->appendChild(spXMLChildNode, &spInsertedNode);
if (FAILED(hr)) throw "Unable to move 'xmlchildnode' XML node";
if (spInsertedNode.p == NULL) throw "Unable to move 'xmlchildnode' XML node";
Если родительский узел принял только что созданный узел в качестве дочернего, он вернёт вам ещё один экземпляр IXMLDOMNode, который представляет новый узел. На самом деле, этот новый узел и узел, который вы передали в appendChild(), в точности совпадают. Тем не менее, проверка указателя на добавленный дочерний узел может быть полезной, так как в случае ошибки он примет значение NULL.
Итак, мы уже нашли требуемый узел и добавили к нему дочерний узел; теперь посмотрим, как работать с атрибутами. Представьте себе, что вам нужно добавить к новому дочернему узлу атрибут:
xml="fun"
Сделать это не сложно, но вам придётся переключиться с IXMLDOMNode на IXMLDOMElement, чтобы поработать с узлом как с элементом. На практике это означает, что вам придётся запросить у интерфейса IXMLDOMNode связанный с ним интерфейс IXMLDOMElement, а потом, получив его, вызвать IXMLDOMElement::setAttribute():
CComQIPtr<IXMLDOMElement> spXMLChildElement;
spXMLChildElement = spInsertedNode;
if (spXMLChildElement.p == NULL)
throw "Unable to query for 'xmlchildnode' XML element interface";
hr = spXMLChildElement->setAttribute(CComBSTR(L"xml"), CComVariant(L"fun"));
if (FAILED(hr)) throw "Unable to insert new attribute";
Ну вот, мы модифицировали исходное XML-дерево, как нам этого хотелось. Приложение уже может сохранить документ на диск, но может сделать и что-нибудь ещё. Например, разыскать ещё один узел и отобразить на экране содержащийся в нём текст. Поскольку искать узлы мы уже умеем, перейдём прямо к извлечению данных.
Для извлечение данных предназначен метод IXMLDOMNode::get_nodeTypedValue(). Данные, которые содержит узел, можно задавать с использованием схемы типов фирмы Microsoft, поэтому вы без труда можете сохранять числа с плавающей точкой, целые числа, строки или любые другие поддерживаемые схемой данные. Тип данных задаётся с использованием атрибута dt:type, например:
<model dt:type="string">SL-2</model>
<year dt:type="int">1992</year>
Если некоторый узел содержит данные заданного типа, вы сможете извлечь их в нужном формате, используя get_nodeTypedValue(). Если тип не задан, по умолчанию он считается текстовым, и парсер вернёт вам VARIANT с содержащимся в нём BSTR. В нашем случае этого достаточно, поскольку узел, который мы ищем, является текстовым и действительно содержит строку. Если нужно, мы всегда сможем отконвертировать её в другое представление, используя средства типа atoi(). А пока просто извлечём строку и отобразим её.
CComVariant varValue(VT_EMPTY);
hr = spXMLNode->get_nodeTypedValue(&varValue);
if (FAILED(hr)) throw "Unable to retrieve 'xmltext' text";
if (varValue.vt == VT_BSTR) {
// Display the results... since we're not using the
// wide version of the STL, we need to convert the
// BSTR to ANSI text for display...
USES_CONVERSION;
LPTSTR lpstrMsg = W2T(varValue.bstrVal);
std::cout << lpstrMsg << std::endl;
} else {
// Some error
throw "Unable to retrieve 'xmltext' text";
}
Если нам удалось извлечь значение, связанное с узлом, и если оно оказалось именно того типа, который мы ожидаем (BSTR), мы выводим текст на экран. В противном случае просто выводится сообщение об ошибке. Но вы, в зависимости от ситуации, можете предпринять и другие действия.
Наша последняя задача – сохранить обновлённое XML-дерево на диск, что мы и делаем, используя IXMLDOMDocument::save():
hr = spXMLDOM->save(CComVariant("updatedxml.xml"));
if (FAILED(hr)) throw "Unable to save updated XML document";
Сохранив документ, программа выдаёт на экран короткое сообщение и завершается.
Эта демонстрационная программа вряд ли поразит ваше воображение. Вы могли бы сделать ещё очень много, но я надеюсь, что этот простой пример показал вам, как использовать MSXML в программах на языке C++. Сам по себе парсер – сложный продукт, и я настоятельно рекомендую вам использовать MSDN как справочное руководство по нему. Парсер предоставляет множество интерфейсов, каждый из которых обычно содержит большое количество методов. Несмотря на это, я широко использую парсер в своих проектах и теперь, поработав и поэкспериментировав с ним, нахожу его простым и удобным в использовании. Я надеюсь, что и вы найдёте ему, а также XML в целом, множество применений.
ВОПРОС-ОТВЕТ
Как разрешить перетаскивание окна за любую точку?
Автор: Алексей Кирюшкин
Демонстрационное приложение DragWin
Пример – приложение DragWin (диалоговое окошко, MFC) иллюстрирует два способа осуществить перемещение окна с захватом его не только за заголовок, но и за любую точку на клиентской области. Идея первого способа проста – при получении сообщения о перемещении мыши передвигаем наше окно в соответствии с новыми координатами. Второй способ поизящнее, и заключается в некотором "обмане" Windows, после которого она считает, что мышь находится над заголовоком окна, даже если реально это уже клиентсткая часть.
Способ 1Реализован для главного окна приложения. Заключается в написании собственных обработчиков нажатия (WM_LBUTTONDOWN), перемещения (WM_MOUSEMOVE) и отпускания (WM_LBUTTONUP) левой кнопки мыши. Обработчики на данные события устанавливаются стандартным образом – через MFC ClassWizard.
void CDragWinDlg::OnLButtonDown(UINT nFlags, CPoint point) {
// выставим флажок – пошло перетаскивание
m_bMoveWindow = TRUE;
// все сообщения от мыши - к нашему окну, независимо от координат
// чтобы мышь не улетала с окна при быстром движении
SetCapture();
// сохраняем координаты окна
GetWindowRect(m_RectDlg);
// сохраняем положение мышки внутри окна программы
ClientToScreen(&point);
m_MouseInDlg = point - m_RectDlg.TopLeft();
// меняем курсор, чтоб веселее было тащить
m_hCursor = m_hCursorDown;
::SetCursor(m_hCursor);
// вызываем обработчик по умолчанию
CDialog::OnLButtonDown(nFlags, point);
}
void CDragWinDlg::OnMouseMove(UINT nFlags, CPoint point) {
if (m_bMoveWindow) // надо тащить
{
// преобразуем координаты мыши в экранные
// именно они нужны будут для SetWindowPos()
ClientToScreen(&point);
// двигаем окно в соответствии с новыми координатами мыши
SetWindowPos(&wndTop, point.x - m_MouseInDlg.x, point.y - m_MouseInDlg.y,
m_RectDlg.right - m_RectDlg.left, m_RectDlg.bottom - m_RectDlg.top,
SWP_SHOWWINDOW);
// поскольку обработчик по умолчанию все равно будет использовать
// первоначальные параметры сообщения
// обратное преобразование ScreenToClient(&point);
// можно не вызывать
}
// вызываем обработчик по умолчанию
CDialog::OnMouseMove(nFlags, point);
}
void CDragWinDlg::OnLButtonUp(UINT nFlags, CPoint point) {
// перетаскивание закончилось
m_bMoveWindow = FALSE;
// "отпускаем" мышку
ReleaseCapture();
// меняем курсор на исходный
m_hCursor = m_hCursorUp;
// вызываем обработчик по умолчанию
CDialog::OnLButtonUp(nFlags, point);
}
BOOL CDragWinDlg::OnSetCursor(CWnd* pWnd, UINT nHitTest, UINT message) {
// заменяем курсор на свой
::SetCursor(m_hCursor);
return TRUE; // !!! было return CDialog::OnSetCursor(pWnd, nHitTest, message);
}
Замена курсора естесственно не является критичной для собственно перетаскивания, а добавлена исключительно для визуализации процесса захвата окошка.
Способ 2Реализован для окна About этого же приложения. Заключается в замене обработчика события WM_NCHITTEST, которое информирует об области, над которой в данный момент находится мышка. Обработчик этого сообщения также можно добавить через MFC ClassWizard. Предварительно на закладке ClassInfo для класса CAboutDlg нужно установить для Message Filter значение Window.
Переписываем функцию – обработчик следующим образом:
UINT CAboutDlg::OnNcHitTest(CPoint point) {
UINT ret = CDialog::OnNcHitTest(point);
// если обработчик по умолчанию говорит нам что мышка
// над клиентской областью окна, заменяем возвращаемое
// значение на HTCAPTION – мышка над заголовком окна,
// а за заголовок перемещать окно можно!
if (ret == HTCLIENT) return HTCAPTION;
return ret;
}
Второй способ проще, первый потенциально гибче, используйте тот, что лучше подходит к вашему конкретному случаю.