РАССЫЛКА ЯВЛЯЕТСЯ ЧАСТЬЮ ПРОЕКТА RSDN , НА САЙТЕ КОТОРОГО ВСЕГДА МОЖНО НАЙТИ ВСЮ НЕОБХОДИМУЮ РАЗРАБОТЧИКУ ИНФОРМАЦИЮ, СТАТЬИ, ФОРУМЫ, РЕСУРСЫ, ПОЛНЫЙ АРХИВ ПРЕДЫДУЩИХ ВЫПУСКОВ РАССЫЛКИ И МНОГОЕ ДРУГОЕ. |
Приветствую вас!
/ / / / СТАТЬЯ / / / / / / / / / / / / / / / / / / / / / /
Отчёты Crystal Reports для Visual C++ 6
Недавно я начал писать один небольшой проект на VC с отчетом Crystal Reports 8 и столкнулся со следующей проблемой: я не знал, как написать отчет. После поиска материалов на эту тему в интернете, у меня сложилось впечатление, что перед разработчиками на VC не стоит проблема создания отчетов. На CodeGuru в разделе Databases я не нашел ни одного материала на эту тему. Пришлось копать эту тему самому. К сожалению, у меня оказался только один пример, в котором довольно сложный отчет полностью создаётся в run-time без использования редактора отчетов. Это автоматически означало, что мне нужно будет изучить несколько десятков, а то и сотен килобайт текста, прежде чем я выдам первый отчет. Времени на это у меня не было. Поэтому для создания отчета я воспользовался следующей технологией, которая и описывается ниже.
Для выполнения этого проекта необходимо:
Приступим.
Для начала, создадим наш отчет. Запускаем Crystal Report Designer. Создаем blank report. Добавляем ODBC connection, указывающее, на пример, на БД pubs на вашем SQL сервере, или на какую-нибудь таблицу в mdb-файле. Выбираем таблицу pubs.dbo.authors, давим add кнопку, закрываем окно. В появившемся окне дизайнера отчетов перетаскиваем в область Details нужные поля: au_id, au_fname, au_lname. Сохраняем отчёт.
Создаём простой Dialog-based проект со всеми настройками по умолчанию. В меню Projects->Add to project->Components and controls добавляем Crystal Report Viewer Control. В окне Confirm classes давим OK. Закрываем окно Components and controls. Добавляем Crystal Report Viewer Control на диалог. В окне ClassWizard для диалога добавляем обработчик WM_SHOWWINDOW. At the Member variables tab добавляем переменную m_CRView1. В начало файла SampRepDlg.cpp добавляем строки
#import <craxdrt.tlb> no_namespace #import <msado15.dll> rename ("EOF", "adoEOF")
(подразумевается, что файл craxdrt.tlb находится в одной из стандартных папок для include. Изначально он находится в каталоге C:\Program Files\Seagate Software\Crystal Reports\Developer Files\include\)
так же добавляем следующие строки в начале файла RepSampDlg.cpp
const CLSID CLSID_Application = {0xb4741fd0,0x45a6,0x11d1,{0xab,0xec,0x00,0xa0,0xc9,0x27,0x4b,0x91}}; const IID IID_IApplication = {0x0bac5cf2,0x44c9,0x11d1,{0xab,0xec,0x00,0xa0,0xc9,0x27,0x4b,0x91}};
const CLSID CLSID_ReportObjects = {0xb4741e60,0x45a6,0x11d1,{0xab,0xec,0x00,0xa0,0xc9,0x27,0x4b,0x91}}; const IID IID_IReportObjects = {0x0bac59b2,0x44c9,0x11d1,{0xab,0xec,0x00,0xa0,0xc9,0x27,0x4b,0x91}};
Переходим к обработчику CRepSampDlg::OnShowWindow. Я обычно создаю стандартное окружение для работы с COM-объектами:
try{ } catch(const _com_error& e) { _bstr_t bstrSource(e.Source()); _bstr_t bstrDescription(e.Description()); CString strError; strError.Format("_com_error catched at CRepSampDlg::OnShowWindow\n" "Source : %s\nDescription : %s", (LPCSTR)bstrSource,(LPCSTR)bstrDescription); AfxMessageBox(strError); }
В try-блоке присоединяем наш файл отчета:
HRESULT hr=S_OK; IApplicationPtr pApp; IReportPtr pRep; hr=CoCreateInstance(CLSID_Application, NULL, CLSCTX_INPROC_SERVER , IID_IApplication, (void **) &pApp); if(FAILED(hr)) _com_issue_error(hr); pRep = pApp->OpenReport(_bstr_t("d:\\projects\\RepSamp\\Report1.rpt")); m_CRView1.SetReportSource(pRep); m_CRView1.ViewReport();
Собираем проект, и запускаем. Появится отчет, который в качестве источника данных использует свои настройки по умолчанию. Теперь давайте подставим ему в качестве источника данных необходимый нам Recordset. Я предпочитаю ADO. Следующий код я добавил сразу после строки "HRESULT hr=S_OK;" :
ADODB::_ConnectionPtr pConn; pConn.CreateInstance(__uuidof(ADODB::Connection)); if(FAILED(hr)) _com_issue_error(hr); CString sConnStr("Provider=SQLOLEDB.1;" "Integrated Security=SSPI;Persist Security Info= False;" "Initial Catalog= pubs;Data Source= DATACENTER"); hr= pConn->Open(_bstr_t(sConnStr),_bstr_t(L""), _bstr_t(L""), ADODB::adConnectUnspecified); if(FAILED(hr)) _com_issue_error(hr); ADODB::_RecordsetPtr pRs; pRs.CreateInstance(__uuidof(ADODB::Recordset)); CString sSQL("SELECT * FROM authors"); pRs->Open(_bstr_t(sSQL), pConn.GetInterfacePtr(), ADODB::adOpenDynamic, ADODB::adLockOptimistic,ADODB::adCmdText); if(FAILED(hr)) _com_issue_error(hr);
теперь запихиваем наш recordset в отчет:
IApplicationPtr pApp; IReportPtr pRep; hr=CoCreateInstance(CLSID_Application, NULL, CLSCTX_INPROC_SERVER , IID_IApplication, (void **) &pApp); if(FAILED(hr)) _com_issue_error(hr); pRep=pApp->OpenReport(_bstr_t("d:\\proj\\SampRep\\Report1.rpt")); m_CRView1.SetReportSource(pRep); IDatabasePtr pDatabase = 0; IDatabaseTablesPtr pTables = 0; IDatabaseTablePtr pTable = 0; pRep->get_Database((IDatabase**) &pDatabase); pDatabase->get_Tables((IDatabaseTables**) &pTables); VARIANT var, var2; VariantInit(&var); VariantInit(&var2); var.vt = VT_DISPATCH; var.pdispVal = (IDispatch*)pConn; var2.vt = VT_DISPATCH; var2.pdispVal = (IDispatch*)pRs->GetActiveCommand(); hr = pDatabase->AddADOCommand(var, var2); ASSERT(SUCCEEDED(hr));
собираем проект. Всё готово.
/ / ВОПРОС-ОТВЕТ/ / / / / / / / / / / / / / / / / /
Как сделать нестандартную кнопку на основе битмапа (без MFC, только
WinAPI)?
Кнопка не обязательно должна иметь стандартный внешний вид (хотя лично я не нахожу внешний вид стандартной кнопки скучным или "простецким"). Однако для многих разработчиков и пользователей кнопки, имеющие нестандартный вид, выглядят более привлекательными. Поэтому для придания некоего стиля интерфейсу собственных программ можно использовать кнопки, отображающие некий битмап (bitmap - растровое изображение).
Кроме эффектов изображения можно использовать еще и эффекты формы - к примеру, круглая или овальная кнопка также достаточно оригинальны внешне, - но данная статья не рассматривает технику создания кнопок, имеющих форму, отличную от прямоугольной.
Windows имеет встроенные механизмы и API, поддерживающие создание кнопок (а также и других контролов), имеющих нестандартный внешний вид. Способ отрисовки внешнего вида контрола зависит от его стиля. В данном случае, стиль, нужный нам - это BS_OWNERDRAW. Из его названия видно, что отрисовку вида кнопки выполняет код пользователя, помещенный в оконную (диалоговую) функцию окна-владельца контрола.
Рассмотрим основные этапы отрисовки контрола, имеющего стиль xx_OWNERDRAW.
Поскольку мы реализуем, хотя и самостоятельно отрисовываемую, но все же кнопку, то было бы неплохо, если бы она имела поведение обычной кнопки - края кнопки в нормальном состоянии должны имитировать выпуклый контрол, при нажатом состоянии - вдавленный, при установленном фокусе кнопка должна иметь на себе прямоугольник, выполненный пунктирной линией, и в неактивном состоянии кнопка должна резко отличаться по цвету (либо фона, либо надписи, либо и того, и другого).
Выполняя указанные требования, мы можем подготовить четыре битмапа, реализующие внешний вид каждого из состояний кнопки, и отрисовывать в нужный момент (вот где появляется необходимость знать текущее состояние кнопки) одно из них. В этом случае мы сами полностью контролируем внешний вид кнопки в каждом из состояний. Впечатление, которое вы произведете на пользователя, будет целиком зависеть от вашего вкуса и умения создавать растровые изображения.
Что касается кода, реализующего необходимую логику работы, то его реализация может быть следующей:
BOOL CALLBACK DlgProc(HWND hDlg, UINT msg, WPARAM wParam, LPARAM lParam) { static HBITMAP hBm[BM_COUNT] = {NULL, NULL, NULL, NULL}; . . . case WM_DRAWITEM: return DrawFreeStyleBtn( (LPDRAWITEMSTRUCT) lParam, hBm ); . . . } BOOL DrawFreeStyleBtn( LPDRAWITEMSTRUCT pis, HBITMAP* phBm ) { if( IDC_BMPBTN == pis->CtlID ) { HBITMAP hOld = NULL; HBITMAP hbm = phBm[BM_UP]; switch(pis->itemAction) { case ODA_DRAWENTIRE: case ODA_SELECT: if( pis->itemState & ODS_DISABLED ) hbm = phBm[BM_DISABLE]; else if( pis->itemState & ODS_SELECTED ) hbm = phBm[BM_DOWN]; break; case ODA_FOCUS: if( pis->hwndItem == GetFocus() ) hbm = phBm[BM_FOCUS]; break; } HDC hCompDC = CreateCompatibleDC( pis->hDC ); hOld = (HBITMAP) SelectObject( hCompDC, hbm ); BitBlt( pis->hDC, pis->rcItem.left, pis->rcItem.top, pis->rcItem.right - pis->rcItem.left, pis->rcItem.bottom - pis->rcItem.top, hCompDC, 0, 0, SRCCOPY ); SelectObject( pis->hDC, hOld ); DeleteDC( hCompDC ); return TRUE; } return FALSE; }
Как видим, ничего сложного. Код распадается на две части: в первой на основе сведений о выполняемых действиях (itemAction) и текущем состоянии кнопки (itemState) производится выбор необходимого битмапа, во второй части происходит вывод выбранного битмапа в контекст кнопки. Код обрамляется проверкой на необходимый идентификатор контрола, поскольку в рабочей программе подобных контролов может быть несколько.
Внимательный читатель готов задать вопрос о том, что в самом начале упоминались не только механизмы (реализованные, как мы выяснили, через сообщения WM_MEASUREITEM и WM_DRAWITEM), но и API?
Действительно, имеется несколько функций, облегчающих придание стандартного вида OWNERDRAW-контролам. Разработчик готовит только основной битмап для кнопки, а для отрисовки границ и состояний кнопки (неактивное и в фокусе) пользуется функциями WinAPI - DrawEdge() (границы контрола - "выпуклый/вдавленный"), DrawState() (состояние "активный/неактивный") и DrawFocusRect() (состояние "в фокусе"). В таком случае вышеприведенный код примет вид:
BOOL CALLBACK DlgProc(HWND hDlg, UINT msg, WPARAM wParam, LPARAM lParam) { static HBITMAP hBm = NULL; . . . case WM_DRAWITEM: return DrawClassicStyleBtn( (LPDRAWITEMSTRUCT) lParam, hBm ); . . . } void DrawClassicStyleBtn(LPDRAWITEMSTRUCT pis, HBITMAP hBm, int deflate = 4) { UINT uState = DSS_NORMAL; UINT uEdge = EDGE_RAISED; int x = 0, y = 0; BOOL bFocus = FALSE; RECT rFocus; if(IDC_BMPBTN == pis->CtlID) { switch(pis->itemAction) { case ODA_DRAWENTIRE: case ODA_SELECT: if(pis->itemState & ODS_DISABLED) { uState = DSS_DISABLED; } else if(pis->itemState & ODS_SELECTED) { x += 1; // сдвиг всего рисунка вправо-вниз подчеркивает y += 1; // визуальный эффект нажатия кнопки uEdge = EDGE_SUNKEN; } break; case ODA_FOCUS: if(pis->hwndItem == GetFocus()) { memcpy(&rFocus, &pis->rcItem, sizeof(RECT)); rFocus.left += deflate; rFocus.top += deflate; rFocus.right -= deflate; rFocus.bottom -= deflate; bFocus = TRUE; } break; } DrawState( pis->hDC, NULL, NULL, (LONG)hBm, 0, x, y, 0, 0, DST_BITMAP | uState); DrawEdge(pis->hDC, &pis->rcItem, uEdge, BF_RECT); if(bFocus) DrawFocusRect(pis->hDC, &rFocus); } }
Выигрыш подобного подхода состоит в меньшем использовании самостоятельно подготавливаемых ресурсов и меньшем их потреблении при работе программы. К недостаткам (и весьма заметным, на мой взгляд) можно отнести то, что происходит потеря контроля над внешним видом кнопки в различных ее состояниях. Впрочем, работа этих упомянутых функций ориентирована на поддержание стандартного внешнего вида контролов, поэтому и результат не очень выразителен. На мой взгляд, данная техника больше подходит к выполнению кнопок, имеющих в основном стандартный внешний вид, но снабженных небольшими изображениями по соседству с текстом кнопки.
Следует заметить, что при необходимости можно (а иногда и нужно) пользоваться комбинацией приведенных методик: предположим, использовать для отрисовки чертыре битмапа, но границу рисовать функцией DrawEdge().
При подготовке данного материала мною использован код, опубликованный в одном из сообщений эхоконференции SU.WIN32.PROG (FidoNet). Автор кода - Dmitry Timoshkov <dmitry@sloboda.ru> - вполне может и не узнать его, поскольку код был мною довольно сильно переработан и дополнен :-))).
/ / / / / / / / / / / / / / / / / / / / / / / / / / / / / / / / / /
На сегодня все. До встречи!
Алекс Jenter
jenter@rsdn.ru
Красноярск, 2001. Рассылка является частью проекта RSDN.