Программирование

Готовь сани летом

September 4th, 2010 Begemot

Стукнуло мне что-то тут в голову изучить вопрос глобальной обработки ошибок – создания минидампа и т.д. Что бы если где-то что-то там у них того, можно было попросить информацию.

Про то что в wxWidgets есть родные средства  я знал, но хотелось полностью готового и универсального (я еще пишу на Qt и поддерживаю MFC) решения. Начал искать.

Сначала прочел все четыре части XCrashReport, поимел общее представление и даже нашел как прикрутить это дело к wxWidgets (пришлось заходить сзади). Но оказалось что ихний exe для отсылки информации, надо пересобирать, а сделать это в 2008 студии не представляется возможным с разумными усилиями. Автор уже давно в принципе сам все поправил и значительно улучшил, но теперь хочет за современную версию $80.

Решил выкинуть их ехе который занимается отправкой, и просто генерить файлы. Потом решил, что я вообще могу выкинуть почти весь их код и использовать стандартную MiniDumpWriteDump. И вот тут я наконец решил посмотреть что есть готового в wxWidgets.

Оказалось есть, и причем неплохое – wxDebugReport. Генерит минидамп, xml со вспомогательной информацией, позволяет включить другие файлы, показывает диалог и даже может вроде их куда-то аплоадить (не тестил), жаль только по е-майл не может послать, что плохо.

image

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

Но за все надо платить, в данном случае цена – 300кб, для меня многовато, но фиг с ним.

Учтите что нужно добавить в проект либы wxmsw29u_qa.lib, wxbase29u_xml.lib и wxexpat.lib. Еще нужно включить генерацию map и pdb файлов для релизной версии программы. Эти файлы нужно хранить для каждого релиза, вы же конечно используете version contorol system?


Сохраняем CF_DIB\CF_DIBV5 в файл

August 27th, 2010 Begemot

Казалось бы простое дело, а убил большую часть дня на это. И то сделал только благодаря помощи одного человека, автора программы для работы с битмапами, иконками и т.д. Приятно разговаривать с человеком который хорошо разбирается в теме:) В итоге узнал очень много сокровенных знаний, которым и буду делится.

Итак у нас есть данные в памяти в форматах CF_DIB/CF_DIBV5 (да я знаю что это форматы буфера обмена!) ну или другими словами у нас есть BITMAPINFO. А мы хотим получить wxBitmap.

Сначала нам нужно получить HBITMAP, который потом уже можно конвертить в wxBitmap используя wxBitmap::SetHBITMAP(да в документации ее нету).

Много писать мне уже сегодня лень, поэтому просто покажу код, там в комментариях описаны все проблемы и решения.

 

        if (BITMAPINFO * bi = static_cast<BITMAPINFO*>(p->data.GetData()))
        {
              /*   там с клипбоардом вообще мрак, MS сам свои спецификации не соблюдает, я тебе уже жаловался, когда в классы всё это барахло заворачивал
                    когда в biCompression указано BI_BITFIELDS за структурой следуют 12 байт маски цветовых каналов, надо на 12 байт смещать начало пиксельного массива
                    1) 12 байт пропускать надо? только если biCompression==BI_BITFIELDS
                    2) по-хорошему, их надо не пропускать, а анализировать и использовать
                    3) работает ли твой код в случае с 8-битным битмапом (т.е. таким, который включает палитру)?
   
                    в третьем с конца - нужно пропустить кол-во элементов палитры
                    так, а как узнать количество элементов палитры?
                    biClrUsed или (1<<biBitCount), если biClrUsed=0  и это делать только если biBitCount <= 8
            */

                int offset = 0;
                if (bi->bmiHeader.biBitCount <= 8)
                {
                    offset = bi->bmiHeader.biClrUsed ? bi->bmiHeader.biClrUsed : (1 << bi->bmiHeader.biBitCount);
                }
                else
                {
                    if (bi->bmiHeader.biCompression == BI_BITFIELDS) offset = 3;
                }

/*
  Правильно использовать independing bitmap - но похоже тут у wxBitmap проблемы, мы получаем правильный hBitmap (видно на экране), но в файле пробелмы - на определенной
  цветности\форматах (тестировалось только для DIB)
  поэтому использую DDB - тут теряется цветность, итоговая цветность не равна оригинальной, а зависит от текущих настроек экрана... но зато работает.
  // (RGBQUAD*)((byte*)bi + bi->bmiHeader.biSize) + offset  instead of &bi->bmiColors + shift - for support both versions DIB\DIBV5
*/

#if 0
             // using independent bitmap
             char * pBits;
             HBITMAP hBitmap = CreateDIBSection(CreateCompatibleDC(NULL), bi, DIB_RGB_COLORS, (void**)&pBits, NULL, 0);
             memcpy(pBits, (RGBQUAD*)((byte*)bi + bi->bmiHeader.biSize) + offset, abs(bi->bmiHeader.biHeight) * (((bi->bmiHeader.biWidth * bi->bmiHeader.biBitCount + 31) & ~31) / 8));
#else         // use DDB (dependent) - we lost information about color depth - bad way
             
             HBITMAP hBitmap = CreateDIBitmap(GetDC(NULL), &bi->bmiHeader, CBM_INIT, (RGBQUAD*)((byte*)bi + bi->bmiHeader.biSize) + offset, bi, DIB_RGB_COLORS);
#endif
//           //------------------------------------------------------------------------------------------------------------------
//              { // write to screen to test it
//                  HDC screen = GetDC(NULL);
//                  HDC bitmap = CreateCompatibleDC(screen);
//                  HBITMAP old = (HBITMAP)SelectObject(bitmap, hBitmap);
//                  int w = 0, h = 0, d = 0;
//                  w = bi->bmiHeader.biWidth; h = bi->bmiHeader.biHeight; d = bi->bmiHeader.biBitCount;
//                  BitBlt(screen, 100, 100, w, h, bitmap, 0, 0, SRCCOPY);
//                  SelectObject(bitmap, old);
//                  DeleteDC(bitmap);
//                  ReleaseDC(NULL, screen);
//              }
//             //------------------------------------------------------------------------------------------------------------------

                wxBitmap bitmap;
                bitmap.SetHBITMAP(hBitmap);
                if (bitmap.IsOk())
                {
                    bool res = bitmap.SaveFile(file, BegUtils::DetermineImageType(file));
                    wxASSERT(res);
                }
                else wxASSERT(0);
            }

 

Да p->data.GetData() может содержать либо просто DIB либо 5ю версию.

Если кто знает другое решение, которое позволит обойти проблему и сохранять данные корректно, или кто пофиксит баг в wxBitmap или хотя бы сформулирует и закинет в трекер, пишите:)


Удаление всех выбранных строк в wxListCtrl

March 24th, 2010 Begemot

На первый взгляд это кажется банальной процедурой, но у меня заработало только с третье или четвертой попытки, ибо есть особенности. Так что – code snippet :

void ClassName::OnDeleteClick(wxCommandEvent& event)
{
    long item = -1;
    while(true)
    {
        item = m_list->GetNextItem(item, wxLIST_NEXT_ALL, wxLIST_STATE_SELECTED);
        if (item == wxNOT_FOUND) break;
        m_list->DeleteItem(item--);
    }
}

Updated: более лаконичная версия по мотивам комментариев

    long item = -1;
    while((item = m_list->GetNextItem(item, wxLIST_NEXT_ALL, wxLIST_STATE_SELECTED)) != wxNOT_FOUND)
        m_list->DeleteItem(item--);

Иногда вещи не такие как ты думаешь

December 8th, 2009 Begemot

Как думаете что будет в str?

wxConfigBase * config = wxConfigBase::Get();

config->Write(_T("/Options/teeestg"), _T("-\\&-"));

wxString str = config->Read(_T("/Options/teeestg"), str);

неправильно, будет “-&-”. Тоже самое и при использовании % вместо &. Проблема оказывается в том что при чтении из конфига автоматически раскрываются переменные окружения.

if you have the following in your config file:

  # config file for my program
  UserData = $HOME/data

  # the following syntax is valud only under Windows
  UserData = %windir%\\data.dat
 

the call to config->Read("UserData") will return something like "/home/zeitlin/data" if you’re lucky enough to run a Linux system 😉

Although this feature is very useful, it may be annoying if you read a value which containts ‘$’ or ‘%’ symbols (% is used for environment variables expansion under Windows) which are not used for environment variable expansion. In this situation you may call SetExpandEnvVars(false) just before reading this value and SetExpandEnvVars(true) just after. Another solution would be to prefix the offending symbols with a backslash.

Чтобы это отключить, необходимо вызвать config->SetExpandEnvVars(false);

С одной стороны вроде все описано и логично, но с другой я все таки не понимаю почему в сочетании \$, код библиотеки удаляет первый слеш, вот сижу думаю это баг и надо его запостить в трекер или все-таки нет.

Думаете надуманная ситуация? Ничего подобного, баг нашел юзер у которого имя пользователя в винде начинается с $ и это вызвано корпоративными стандартами, в этом случае путь к файлам пользователя получается “C:\Documents and

Settings\$xxx\Application Data\xxx\” ну и сразу после установки, программа не может открыть базу данных…

Posted in Программирование | Tags:
Comments Off on Иногда вещи не такие как ты думаешь


Проблемы с потерей конфиг файла

November 3rd, 2009 Begemot

Хочу пожаловаться, может кто поможет советом. Есть программы на написанные на wxWidgets и MFC, стартуют вместе с виндой и завершаются с ней же. При старте, как и рекомендует хелп делаю

wxFileConfig * config = new wxFileConfig(wxGetApp().GetAppName(), wxGetApp().GetVendorName(), dataPath + wxT("clipdiary.cfg")); // will be deleted by wxWidgets automatically

wxConfigBase::Set(config);

Потом периодически читаю\пишу, иногда, но не всегда делаю Flush(true). При выходе из программы и\или при получении сообщения EVT_QUERY_END_SESSION пишу кое-какие данные в цфг, делаю Flush(true). В самом конце делаю делаю вот такое

int MyApp::OnExit()
{
    // Баг, столкнулся с тем что почему-то не пишет в файл надо или flush или удалять указатель (при удалении он сам все пишет)
    // по идее библиотека должна сама удалять объек. но не удаляет. так что будем вручную.
    delete wxConfigBase::Set(NULL);       // Flush and delete config

    return wxApp::OnExit();
}

А проблема в том, что периодически (редко) при запуске программы – конфиг файл оказывается пустой, в смысле или совсем пустой или отсутствующий, не могу точно сказать. Программа при загрузке выдает сообщение ошибку wxWidgets, что-то типа “ошибка конвертации в юникод” точно не помню. Скорее всего проблема происходит при выключении компьютера, наверняка при каком-то “плохом” завершении работы. Раньше об этом писали пользователи, жаловались на то что пропадают настройки… Недавно поймал такое у себя – висели три мои программы на mfc, wxWidgets trunk и  2.8.10 – две последние потеряли все настройки, мфц’шная нет. Вот думаю куда копать и что делать, уже думал делать дублирование настроек во втором файле, но какой-то это странный путь 🙂 Может кто знает как сделать правильно?

 

Кстати, вот инструкция как реализовать ручную загрузку функций из DLL или как в программе использовать функции из Vista API?


Когда все пишут плохой код…

October 21st, 2009 Begemot

…получается неприятная хрень и приходиться долго отлаживать.

Наткнулся на интересную багу, как раз перед выпуском новой версии – программа падает при выходе. После утра с дебагером, я конечно это победил, но случай интересный. Как я понимаю проблема в совокупности – плохого кода в databaselayer’e, плохого архитектурного решения и небезопасного подхода у меня и ошибки в самой wxWidgets. (disclamer : все выпады против чужого кода, основываются на сугубо субъективных догадках). По отдельности все это в принципе не критично, ну разве кроме ошибки в вх, но раз ее никто еще не обнаружил, видимо нужна редкая комбинация факторов что бы она проявилась, но сойдясь в одном проекте – приводило к падению. Итак

Мои проблемы

Две ошибки – я использовал глобальную переменную объект своего класса, для взаимодействия с базой данных. Все просто открываем, владеем и работаем с базой данных, деструктор разумеется ее закрывает. Раз деструктор все закрывает, то зачем закрывать руками при выходе? Думал я так раньше.

databaselayer

Есть там такой, имхо не очень красивый код

void DatabaseErrorReporter::ResetErrorCodes()
{
   m_strErrorMessage = _("");

   m_nErrorCode = DATABASE_LAYER_OK;
}

и вызывается он и их процедуры закрытия. Вот если бы там было написано грамотно,

   m_strErrorMessage = wxEmptyString;  
  //   m_strErrorMessage = _wxT("");  или так

то проблем не было бы.

 

wxWidgets trunk

Ну и похоже есть проблема в самой wx, в логике в e:\Const\wxWidgets-trunk\src\common\strconv.cpp. Там две процедуры забавно вызывают друг друга в цикле

    Flashnote.exe!wxMBConv::ToWChar(wchar_t * dst=0x00000000, unsigned int dstLen=0, const char * src=0x00a1414b, unsigned int srcLen=4294967295)  Строка 161 + 0x14 байт C++
    Flashnote.exe!wxMBConv::MB2WC(wchar_t * outBuff=0x00000000, const char * inBuff=0x00a1414b, unsigned int outLen=0)  Строка 357 + 0x1c байт    C++
    Flashnote.exe!wxMBConv::ToWChar(wchar_t * dst=0x00000000, unsigned int dstLen=0, const char * src=0x00a1414b, unsigned int srcLen=4294967295)  Строка 230 + 0x17 байт C++
    Flashnote.exe!wxMBConv::MB2WC(wchar_t * outBuff=0x00000000, const char * inBuff=0x00a1414b, unsigned int outLen=0)  Строка 357 + 0x1c байт    C++
    Flashnote.exe!wxMBConv::ToWChar(wchar_t * dst=0x00000000, unsigned int dstLen=0, const char * src=0x00a1414b, unsigned int srcLen=4294967295)  Строка 230 + 0x17 байт C++
    Flashnote.exe!wxMBConv::MB2WC(wchar_t * outBuff=0x00000000, const char * inBuff=0x00a1414b, unsigned int outLen=0)  Строка 357 + 0x1c байт    C++
    Flashnote.exe!wxMBConv::ToWChar(wchar_t * dst=0x00000000, unsigned int dstLen=0, const char * src=0x00a1414b, unsigned int srcLen=4294967295)  Строка 230 + 0x17 байт C++

Хорошо все-таки использовать  open source библиотеки, всегда можно пойти и посмотреть в чем затык.  И теперь когда я победил эту проблему, я могу спокойно релизить свой notes manager повышая свой внутренний статус wxWidgets 3.0 до использования в продакшене:)

 

p.s. надо не забыть в багтрекер закинуть описание бага.


GetViewStart() vs GetScrollPos()

September 9th, 2009 Begemot

Столкнулся только что с багом – есть наследник от wxScrolledControl’a, контента мало – линеек прокрутки нет, то есть client size = virtual size. Крутим над ним колесиком мыши – прокручивая “вверх”, после этого GetViewStart() говорит нам что у нас позиция по вертикали минус сколько-то…, GetScrollPos() возвращает правильный ноль. Точно грешить на wxWidgets не могу – так как и приложение там сложное, у окна есть еще 1 или даже два базовых класса, которые возможно обрабатывают скролл мышкой (Скорее всего конечно нет, но совсем не удивлюсь если так:)), да и версия либы там весьма древняя. Но тем не менее следует иметь ввиду, что что возможно есть такая особенность, beware 🙂

Posted in Программирование | Tags:
Comments Off on GetViewStart() vs GetScrollPos()


Салянка из wxWidgets, wxCombobox и MAC OS

June 25th, 2009 Begemot

Сборная.

Кажется на маке с комбобоксами плохо, весьма плохо. Как то уж очень странно они выглядят (такое впечетление что родных нет, и вх эмулирует их сам, причем весьма посредственно)

Picture 6

Picture 8

Правда страшненько?

А вот это такое стремненькое белоей с каемочкой это стандартные color picker(!).

Причем тут комбобокс еще немного прилизанный, так как по умолчанию у него размер раза в 2 – 2.5 шире реально необходимо – почему ? хз. Пришлось руками уменьшать размер после создания

#ifdef __WXMAC__
{  // default combobox size under macos is too big
int w = 0, h = 0;
m_OverlayFontSize->GetSize(&w, &h);
m_OverlayFontSize->SetMinSize(wxSize(w / 2, h));
}
// combobox does not receive wxEVT_KILL_FOCUS event under MAC OS – http://trac.wxwidgets.org/ticket/9862
// so we use text update event, inseted of text_enter + kill_focus
#else

#ifdef __WXMAC__
{  // default combobox size under macos is too big
int w = 0, h = 0;
m_OverlayFontSize->GetSize(&w, &h);
m_OverlayFontSize->SetMinSize(wxSize(w / 2, h));
}

А еще они не получает события установки\потери фокуса. Пришлось жертвовать красотой работы и делать отдельную ветку логики под мак:(

Вот такой вот винегрет.


wxListCtrl и джедайские приемы

June 19th, 2009 Begemot

Знаете как создать wxListCtrl с одной колонкой, ширина которой равна ширине контрола? не уже – что бы выделение было красивым и удобным для пользователя, не шире что бы не появлялась линейка прокрутки вообщем сделать так как должно быть в нормально продукте. Я думал просто, я был удивлен, нормального пути нет или я его не нашел.

Поскольку контрол в сайзере ширина его точно не известна, я смог сделать только так :
[sourcecode language=”cpp”]
m_TransitionList = new wxListCtrl(….
m_TransitionList->Connect(wxEVT_SIZE, wxSizeEventHandler(MovieMaker::OnTransitionListSize)); // for correct resizing width of column
[/sourcecode]

Ну и сам обработчик : 

[sourcecode language=”cpp”]
void MovieMaker::OnTransitionListSize(wxSizeEvent& event)
{
if (wxListCtrl * list = ((MovieMaker*)GetParent()->GetParent())->m_TransitionList)
list->SetColumnWidth(0, list->GetSize().GetWidth() – wxSystemSettings::GetMetric(wxSYS_VSCROLL_X) – 6);
}
[/sourcecode]

Учтите что обработчик принадлежит класу создающему wxListCtrl, а не класу контрола, поэтому this у него не “правильный”,  берем родителя (в моем случае дедушку), кастим к нужному класу  и устанавливаем ширину. Почему то пришлось вычитать еще 6 пикселов (?!), число 6 подобранно эксперементально.

Вот теперь у нас нормальный лист контрол который не стыдно отдать юзверю, а вы как решаете эту проблему?

Update: Новая более правильная версия, спасибо Алексею за облагораживание кода:

[sourcecode language=”cpp”]
void MovieMaker::OnTransitionListSize(wxSizeEvent& event)
{
if (wxListCtrl * list = dynamic_cast<wxListCtrl*>(event.GetEventObject()))
list->SetColumnWidth(0, list->GetSize().GetWidth() – wxSystemSettings::GetMetric(wxSYS_VSCROLL_X) – 6);
}
[/sourcecode]


Scroll wxTreeCtrl – как скроллить ручками

May 21st, 2009 Begemot

Появилась задача, есть wxTreeCtrl с горизонтальной линейкой прокрутки, необходимо переодически скроллить ее в начало начал. В wxWidgets есть SetScrollPos(wxHORIZONTAL, 0); , но она делает только то что сдвигает скроллер у окна, но никак не его содержимое, в принципе об этом честно написано в документации

This function does not directly affect the contents of the window: it is up to the application to take note of scrollbar attributes and redraw contents accordingly.

Хотя есть люди которые утверждают, что у них работает именно так как хочется, а не так как в документации говорится:) Я даже обновился до 2.8.10, но не помогло. Зато помог самый просто способ,

::SendMessage((HWND)m_Tree->GetHWND(), WM_HSCROLL, SB_LEFT, 0);

Дойдем до альтернативных OS, будем думать.

А посмотрел код для скроллинга в самой бибилиотеке… толи что я чего-то не понимаю, то ли там хрень какая-то 🙁