Сохраняем 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 или хотя бы сформулирует и закинет в трекер, пишите:)


Если пост полезен для вас вы можете подписаться на RSS или мы можем доставлять вам новые посты прямо в ваш почтовый ящик.


wxWidgets определение размера экрана

June 22nd, 2010 Begemot

В wxWidgets есть wxSystemSettings::GetMetric() который, кроме прочего, позволяет узнать разрешение экрана, но к сожалению не правильно 🙁

Проблемы появляются на мультимониторных конфигурациях. Вызов wxSystemSettings::GetMetric(wxSYS_SCREEN_X); дает нам ширину только первого монитора под Windows (сам проверял) и суммарную ширину обоих мониторов под Линуксом (сведенья из инета). Подозреваю что это баг, но разбиратся, формулировать и спорить некогда, поэтому просто написал воркараунд для себя

#ifdef __WIN32__  
    // wxSystemSettings::GetMetric does not support multy-monitor configuration at Windows, and return parameters only for first monitor, but it seems wots fine under Linux
    const int maxWidth = ::GetSystemMetrics(SM_CXVIRTUALSCREEN);
    const int maxHeight = ::GetSystemMetrics(SM_CYVIRTUALSCREEN);
#else
    const int maxWidth = wxSystemSettings::GetMetric(wxSYS_SCREEN_X);
    const int maxHeight = wxSystemSettings::GetMetric(wxSYS_SCREEN_Y);
#endif

Updated:
Эхх стало стыдно за плохой стиль:(

// wxSystemSettings::GetMetric does not support multy-monitor configuration at Windows and return parameters only for first monitor, but it seems works well under Linux
wxSize BegUtils::GetVirtualDisplayResolution()
{
#ifdef __WIN32__  
    return wxSize(::GetSystemMetrics(SM_CXVIRTUALSCREEN), ::GetSystemMetrics(SM_CYVIRTUALSCREEN));
#else
    return wxSize(wxSystemSettings::GetMetricwxSYS_SCREEN_X), wxSystemSettings::GetMetricwxSYS_SCREEN_Y));
#endif
}


...

    const wxSize resolution = BegUtils::GetVirtualDisplayResolution();

Если пост полезен для вас вы можете подписаться на RSS или мы можем доставлять вам новые посты прямо в ваш почтовый ящик.


Удаление всех выбранных строк в 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--);

Если пост полезен для вас вы можете подписаться на RSS или мы можем доставлять вам новые посты прямо в ваш почтовый ящик.


Drag and Drop at wxTreeCtrl

September 26th, 2009 Begemot

Добавлял тут drag and drop к дереву, для своего легкого менеджера заметок, порядком намучался. А все оказывается не так и сложно, надо просто делать так как доктор прописал.

Задача была такая – есть дерево, хочу что бы перетаскивать можно было:

  1. Ветви внутри дерева
  2. Данные (текст) из дерева наружу
  3. Текст снаружи в дерево.

Read the rest of this entry »


Если пост полезен для вас вы можете подписаться на RSS или мы можем доставлять вам новые посты прямо в ваш почтовый ящик.


Передача Unidode пути к файлу в ANSI библиотеку

July 28th, 2009 Begemot

Если вас вдруг  угораздило работать с какой-нибудь старой библиотекой которая не хочет понимать пути в юникоде… то ключ к решению wxFileName::GetShortPath(), а все правильное решение такое:

wxString path = ….

wxFileName fn(path, _T(“name”), _T(“ext”));

fn.Normalize();

xz.Open((const char*)fn.GetShortPath().ToAscii());

wxString path = ….

wxFileName fn(path, _T(“name”), _T(“ext”));

fn.Normalize();

xz.Open((const char*)fn.GetShortPath().ToAscii());


Если пост полезен для вас вы можете подписаться на RSS или мы можем доставлять вам новые посты прямо в ваш почтовый ящик.


Используем png для иконок и храним ресурсы в архиве

December 24th, 2008 Begemot

Со временем я пришел к выводу что иконки лучше хранить в PNG, чем в XPM. Несомненый плюс это наличие альфа-канала. К тому же хранение иконок вне exe файла позволяет пользователю заменить используемые иконки на свои, что дает возможность правильно использовать для вашего приложения графику под LGPL лицензией (Где брать бесплатные иконки).

Что бы всякие дополнительные файлы, необходимые программе не загромождали папку, можно хранить их все в одном зип файле, благо wxWidgets предоставляет все что для этого необходимо.

Я написал пару врапперов для удобной работы:

// don't forget call wxFileSystem::AddHandler(new wxZipFSHandler); in MyApp::OnInit() 
wxImage BegUtils::GetImageFromZipResource(const wxString & path, long type/*=wxBITMAP_TYPE_ANY*/)
{
	scoped_ptr<wxFileSystem> fs(new wxFileSystem);
	scoped_ptr<wxFSFile> file(fs->OpenFile(path));
	if (!file) return wxNullImage;
    
	wxInputStream* stream = file->GetStream();
	wxImage image(*stream, type);

	return image;
}


// Load a text resource from zip file
// don't forget call wxFileSystem::AddHandler(new wxZipFSHandler); in MyApp::OnInit()
wxString BegUtils::GetUTF8TextFromZipResource(const wxString& path)
{
    scoped_ptr<wxFileSystem> fs(new wxFileSystem);
    scoped_ptr<wxFSFile> file(fs->OpenFile(path));
    if (!file) wxEmptyString;
    
    wxInputStream* stream = file->GetStream();
    size_t sz = stream->GetSize();
    scoped_array<char> buf(new char[sz]);
    stream->Read((void*) buf.get(), sz);

    wxString text = wxString::FromUTF8(buf.get(), sz);
    if (text[0]==0xFEFF) text.erase(0, 1); // BOM
    return text;
}

 

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

 

wxString resPath=exePath+wxT("resource.bin#zip:");
...
#define MMB(x) BegUtils::GetImageFromZipResource(resPath+wxT("icons\\")+x+wxT(".png"), wxBITMAP_TYPE_PNG)

  AppendMenuItemWithImage(itemMenu, wxID_HELP, _T("Help\tF1"), _T(""), MMB(wxT("help")));
  AppendMenuItemWithImage(itemMenu, wxID_ABOUT, _T("About\tAlt+F1"), _T(""), MMB(wxT("information")));
....
 m_ToolBar->AddTool(wxID_EXIT, _T(""), MMB(wxT("exit")), wxNullBitmap, wxITEM_NORMAL, _T("Terminate Application (Alt+X)"), wxEmptyString);
....
#undef MMB

AppendMenuItemWithImage можно взять в wxMenu с иконками и обход выделенных атемов в wxListCtrl

Пользуйтесь на здоровье.


Если пост полезен для вас вы можете подписаться на RSS или мы можем доставлять вам новые посты прямо в ваш почтовый ящик.


hex2bin and bin2hex c++ functions

December 11th, 2008 Begemot

Написал тут себе пару функций для перевода массива байт в hex строку и наоборот.  Особого отношения к wxWidgets не имеют, да и сами фунции конечно тривиальные, но может кому съэкономит час работы (а может и два!) работы, а это в наше нелегкое кризисное время, не мало 🙂

#include <assert.h>
#include <vector>
using namespace std;

std::vector<unsigned char> hex2bin(const TCHAR * hexstring)
{
	std::vector<unsigned char> v;
	int ind=-1; 
	bool first=true;
	TCHAR c, b;
	while(hexstring[++ind])
	{
		c=hexstring[ind];
		if( (c>47) &amp;amp;&amp;amp; (c<58)) c-=48; 
		else  if ( (c>64) &amp;amp;&amp;amp; (c<71))  c-=65-10;
		else  if ( (c>96) &amp;amp;&amp;amp; (c<103)) c-=97-10;
		else continue;
		
		if (first) b=c<<4; else v.push_back(static_cast<unsigned char>(b+c));

		first=!first;
	}
	return v;
}

//bufferlen should be at least len*2+1 or len*3 if separator is set
int bin2hex(unsigned char *data, int len, TCHAR *buffer, int bufferlen, TCHAR separator)
{
	assert( ((separator==0) &amp;amp;&amp;amp; (bufferlen>len*2)) || (separator &amp;amp;&amp;amp; (bufferlen>len*3-1)));
	if (((separator==0) &amp;amp;&amp;amp; (bufferlen<len*2+1)) || (separator &amp;amp;&amp;amp; (bufferlen<len*3))) 
	{
		if (bufferlen>0) buffer[0]=0;
		return 0;
	}
	   
	const TCHAR hex[]={_T('0'), _T('1'), _T('2'), _T('3'), _T('4'), _T('5'), _T('6'),
 _T('7'), _T('8'), _T('9'), _T('a'), _T('b'), _T('c'), _T('d'), _T('e'), _T('f')}; 

    int ind=-1;
    while(len>0) 
    {
		buffer[++ind] = hex[*data >> 4];
		buffer[++ind] = hex[*data &amp;amp; 0x0F];
		if (separator!=0) buffer[++ind] = separator;
		++data; --len;
    }
	if (separator!=0) --ind;
	buffer[++ind]=0;
	return ind; // count TCHAR without end \0
}

Если пост полезен для вас вы можете подписаться на RSS или мы можем доставлять вам новые посты прямо в ваш почтовый ящик.


Исключаем контрол из tab order’a

November 12th, 2008 Begemot

Минус в том что контрол надо субкласить, плюс в том что это работает:)

h:
	bool SetAllowSetFocusFromKbd(bool allow=true) 
	{
		bool t=AllowSetFocusFromKbd; 
		AllowSetFocusFromKbd=allow; 
		return t;
	};
	
        bool GetAllowSetFocusFromKbd() const
            {return AllowSetFocusFromKbd;};

	virtual void SetFocusFromKbd();

private:
	bool AllowSetFocusFromKbd;


cpp:
void CMyClass::SetFocusFromKbd()
{
	if (AllowSetFocusFromKbd) 
		BaseClass::SetFocusFromKbd();
	else 
		Navigate();
}

 

Спасибо мудрецам c форума шадонета за наводку


Если пост полезен для вас вы можете подписаться на RSS или мы можем доставлять вам новые посты прямо в ваш почтовый ящик.


Учимся вводить только цифры в wxTextCtrl

September 8th, 2008 Begemot

Поразительная фигня, казалось бы простая задача  и должно быть готовое решение. И правда есть, только поразительно кривое:(

Итак, задача – необходимо дать возможность пользователю указать число, например в настройках, количество записей  для отображения где-нибудь… Первое что приходит в голову – wxSlider, ну знаете такой контрол со стрелочками. Так как у меня диапазон от нуля до 10 000, а возможность задать шаг инкрементации для стрелочек я не нашел, и равен он 1 – то этот вариант, оказывается издевательством над юзером.

Думаем дальше и вспоминаем, про wxTextCtrl и валидаторы. Радуемся и пишем код, что-то типа

m_Count = new wxTextCtrl( this, ID_MAXCLIPTEXT, _T(""), wxDefaultPosition, wxSize(50, -1), 0, wxTextValidator(wxFILTER_NUMERIC, &count));

Я ожидал что контрол будет давать вводить в себя только цифры. Я ошибся, он также пропускал точку и запятую, что еще хоть как-то можно понять, хотя его и не просили. Но еще оказались разрешенными: ‘+’ и ‘–’. Также он спокойно пропускал все буквы кирилицы. Похоже что контрол отфильтровывал только пробел, спецсимволы и латиницу. Хотя даже это можно ввести туда пользуясь драг анд дропом или простой вставкой. Вообщем я разочаровался.

Попробывал задать более конкретно wxFILTER_INCLUDE_CHAR_LIST и разрешил только цифры. “голосуй, не голосуй, все равно … “ (с). Теже траблы со вставкой текста из буфера и …. с кирилицей. Прямо наваждение какое-то.

Решил писать сам, вот что получилось

 
h:
	void OnMaxClipTextUpdated( wxCommandEvent& event );

	wxTextCtrl* m_Count;
	
	unsigned long maxCount;
	long maxInsertionPoint;
	wxString maxCountStr;
	bool maxSetManually;

cpp:
	EVT_TEXT( ID_MAXCLIPTEXT, COPDatabase::OnMaxClipTextUpdated )

	m_Count = new wxTextCtrl( this, ID_MAXCLIPTEXT, _T(""), wxDefaultPosition, wxSize(50, -1), 0);
	m_Count->SetMaxLength(4);


void COPDatabase::OnMaxClipTextUpdated( wxCommandEvent& event )
{
	if (maxSetManually) return;
	wxString str=m_Count->GetValue();
	
	if (str.empty() || (str==wxT(" "))) // вторая проверка нужна для случая когда юзер выделяет все в контроле и жмет пробел
	{
		maxCount=0;
		str=wxT("0");
		maxInsertionPoint=1;
		// or if you want to let empty string - replace last 2 string to these - maxCountStr=wxEmptyString; maxInsertionPoint=0;
	}
		
	if (str.ToULong(&maxCount))
	{ // successfully converted to ulong, check range now
		if (maxCount>10000) maxCount=10000; 
		else if (maxCount<0) maxCount=0;

		maxInsertionPoint=m_Count->GetInsertionPoint();
		maxCountStr=wxString::Format(wxT("%i"), maxCount); // we should do like this! иначе траблы с пробелами, нулями..
	}

	maxSetManually=true;
	m_Count->SetValue(maxCountStr);
	maxSetManually=false;
	m_Count->SetInsertionPoint(maxInsertionPoint);
}

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

Поведение (юзабилити) не идеально, но твердую 4-ку я бы поставил. Хотя в случае если диапазон снизу ограничен не нулем, тогда поведение хреново будет, но у меня такого случая пока не было. Как выход можно исключить динамическую проверку диапазона, и перенести ее в код сохраняющий значение.

Еще совет, лучше поставить ограничение на длину вводимых символов, типа m_Count->SetMaxLength(4); для верхней границы 10000 и т.д.

Пользуйтесь. Высказывайтесь.


Если пост полезен для вас вы можете подписаться на RSS или мы можем доставлять вам новые посты прямо в ваш почтовый ящик.