wxGrid and wxBitmap Topic is solved

If you are using the main C++ distribution of wxWidgets, Feel free to ask any question related to wxWidgets development here. This means questions regarding to C++ and wxWidgets, not compile problems.
Melandr
Experienced Solver
Experienced Solver
Posts: 53
Joined: Thu Oct 28, 2021 6:07 am

wxGrid and wxBitmap

Post by Melandr »

Good day! I'm trying to display an image obtained from binary data in a wxGrid cell. It worked out in StaticBitmap. In principle, I found similar topics on the forum, you need to create a new wxBmpGridCellRenderer class based on the wxGridCellRenderer class, overriding the Draw method in it. But since I'm still not very familiar with the concept of object-oriented programming, the question arose of how to properly pass an image to the wxGridCellRenderer class.
Let me explain in more detail:
- method wxImage* wxMainFrame::LoadImageFromBlob(const unsigned char *data, int size) returns an image in wxImage format.
- next I override my own cell renderer from wxGridCellRenderer - class wxBmpGridCellRenderer
- further, when rendering the wxGrid grid, I assign a new renderer

Code: Select all

SetCellRenderer(row, col, new wxBmpCellRenderer(bmp));
- a bmp object, wxBitmap type, is passed to the wxBmpCellRenderer constructor.
The question is, which class would it be correct to make this image a member of? Maybe it would be more correct to include the LoadImageFromBlob method in the wxBmpGridCellRenderer class? Sorry for possibly stupid questions, I'm just learning.
Below is the syntax for overriding the Draw method

Code: Select all

void wxBmpGridCellRenderer::Draw(wxGrid& grid, wxGridCellAttr& attr, wxDC& dc, const wxRect& rect, int row, int col, bool isSelected)
{
    wxGridCellRenderer::Draw(grid, attr, dc, rect, row, col, isSelected);
    dc.SetClippingRegion(rect);
    dc.DrawBitmap(*m_foto ,rect.x,rect.y);
    dc.DestroyClippingRegion();
}
the m_foto variable is a member of the wxMainFrame application's main form class. How to correctly pass the image received from the database to the wxGrid table cell renderer?
Below is the implementation of the wxMainFrame::LoadImageFromBlob method, it returns the wxImage image

Code: Select all

wxImage* testWxSmithFrame::LoadImageFromBlob(const unsigned char *data, int size)
{
  if( data != NULL )
  {
    wxMemoryInputStream mi(data, size);
    wxImage *img = new wxImage(mi, wxBITMAP_TYPE_ANY);
    if( img != NULL && img->IsOk() ) return img;
    // wxLogDebug( wxT("DB::LoadImageFromBlob error: data=%p size=%d"), data, size);
    // caller is responsible for deleting the pointer
    delete img;
  }
  return NULL;
}
In this thread viewtopic.php?f=1&t=35320&start=15 I found the implementation of the renderer, but it displays the image received in the renderer itself. But how to display an image that is a member of another class, such as the main form?
The following code outputs an image from memory to a StaticBitmap while scaling the image.

Code: Select all

wxMemoryBufferBufferOut;
void *pBlob = pResults->GetResultBlob(_("FIMG"),BufferOut);
const unsigned char *pRetrievedBuffer = (const unsigned char*)pBlob;

intlen;
len = (int)BufferOut.GetDataLen();

m_foto = LoadImageFromBlob(pRetrievedBuffer,len);
StaticBitmap1->SetBitmap(wxBitmap(m_foto->Rescale(wxSize(218,290).GetWidth(),wxSize(218,290).GetHeight())));
What is the correct way to pass the image returned in the m_foto variable to the wxGrid cell renderer?
User avatar
doublemax
Moderator
Moderator
Posts: 19115
Joined: Fri Apr 21, 2006 8:03 pm
Location: $FCE2

Re: wxGrid and wxBitmap

Post by doublemax »

There is no easy and straightforward way to deal with this.

The best way might be to use wxDataViewListCtrl instead of wxGrid.

I assume you're using a wxGrid with the default wxGridTableBase? wxGridTableBase can only store strings. If you have only few and small bitmaps, you could store image data base64 encoded in the table. But that would be a quick and dirty solution, waste performance and memory.

Responding to your code samples:

Ideally, there should be only only one wxBmpCellRenderer for the whole column and it should get the bitmap data using the row and col parameters passed to the render method.

As to how the renderer gets access to the bitmap, there are several options again:
- A singleton class that reads the data from the database
- Through the wxGrid reference passed to the render method. If you subclass wxGrid, you can add every method and/or pointer you need to it.

Dirtier alternatives (use at your own risk and don't mention my name):
- From wxGrid you could use GetParent() (eventually multiple times) to get a pointer to the main frame
- If you have only one toplevel window, use wxApp::GetMainTopWindow() to get a pointer to it
Use the source, Luke!
Melandr
Experienced Solver
Experienced Solver
Posts: 53
Joined: Thu Oct 28, 2021 6:07 am

Re: wxGrid and wxBitmap

Post by Melandr »

Yes indeed. I also had a thought, where is the image saved when passing it to wxGrid ?
Unfortunately, I did not find normal examples for wxDataViewListCtrl. Using this class will completely replace wxGrid ? Or should it be used in conjunction with wxGrid ?
Those solutions that I found on the forum, using the wxGridCellRenderer class override, are not correct?
But it's still not clear to me, if you override the wxGrid class, how to properly save images in table cells?
If using your advice by passing image to wxGrid
by reference to the rendering class, then in principle it is clear. But where to save the images received upon request? Or in any case, you should use new on each pass of the loop

Code: Select all

        if (pResults)
        {
            while (pResults->Next())
            {
                wxMemoryBufferBufferOut;
                void *pBlob = pResults->GetResultBlob(_("FIMG"),BufferOut);
                const unsigned char *pRetrievedBuffer = (const unsigned char*)pBlob;

                int len;
                len = (int)BufferOut.GetDataLen();

                foto = LoadImageFromBlob(pRetrievedBuffer,len);
                StaticBitmap1->SetBitmap(wxBitmap(foto->Rescale(wxSize(218,290).GetWidth(),wxSize(218,290).GetHeight())));

                //Grid1->SetCellRenderer(countOfRows,0,pResults->GetResultBlob("FPHOTO"));
                Grid1->SetCellValue(countOfRows,1,wxString::Format(_("%i"),pResults->GetResultLong(_("FID"))));
                Grid1->SetCellValue(countOfRows,2,wxString::Format(_("%s"),pResults->GetResultString(_("FLAST"))));
                Grid1->SetCellValue(countOfRows,3,wxString::Format(_("%s"),pResults->GetResultString(_("FFIRST"))));
                Grid1->SetCellValue(countOfRows,4,wxString::Format(_("%s"),pResults->GetResultString(_("FMIDDLE"))));
                Grid1->SetCellValue(countOfRows,5,wxString::Format(_("%s"),pResults->GetResultString(_("FDEPT"))));
                Grid1->SetCellValue(countOfRows,6,wxString::Format(_("%s"),pResults->GetResultString(_("FTITLE"))));
                countOfRows++;
                if(countOfRows >= Grid1->GetNumberRows())
                    Grid1->AppendRows();
            }
            m_Database->CloseResultSet(pResults);
        }
m_Database->Close();
saving the image dynamically, and accordingly deleting objects on a new request or closing the application?
ONEEYEMAN
Part Of The Furniture
Part Of The Furniture
Posts: 7459
Joined: Sat Apr 16, 2005 7:22 am
Location: USA, Ukraine

Re: wxGrid and wxBitmap

Post by ONEEYEMAN »

Hi,
Yes, wxDataViewListCtrl will be created instead of wxGrid.
And the best is to buid and run the dataview sample that comes with the library.

And why do youi need to save the image? It will just stay in memory and the renderer will just displays it.
Look at the grid sample.

Thank you.
Melandr
Experienced Solver
Experienced Solver
Posts: 53
Joined: Thu Oct 28, 2021 6:07 am

Re: wxGrid and wxBitmap

Post by Melandr »

Good day! I don't need to store the image in memory. The task is what - when performing different queries to the database, the sql server response is returned with various data that needs to be displayed in the wxGrid table. The data is strings and an image. The image is displayed in the first column of the wxGrid . After reading the forum threads, I realized that it is necessary to override the grid cell renderer class for the column that displays the image. In the response from the sql server, either one image or several tens, or a hundred, depending on the request, can be returned. The image itself has a size of up to 500 kB, but it can be displayed in a table cell in a reduced form. I did Rescale, but I suspect that this is not the right approach, since the image size is not reduced.
If I understand correctly, I need to add a member of the wxImage class to the renderer class and use the dynamic creation of a variable to add a photo on each pass of the loop when reading the response from the sql server.
User avatar
doublemax
Moderator
Moderator
Posts: 19115
Joined: Fri Apr 21, 2006 8:03 pm
Location: $FCE2

Re: wxGrid and wxBitmap

Post by doublemax »

If I understand correctly, I need to add a member of the wxImage class to the renderer class and use the dynamic creation of a variable to add a photo on each pass of the loop when reading the response from the sql server.
I'm not a big fan of using a separate renderer instance for all rows, but you can do it that way.

In order to avoid rescaling the image on every redraw, add two different member variables to the renderer, one wxImage and one wxBitmap. The bitmap is the one you use for rendering and will contain a scaled down version of the wxImage. And you re-create the bitmap only when the desired size changes (e.g. if the row height changes).

Additionally you should still take a look at wxDataViewListCtrl. As you said you couldn't find a simple sample on how to use it, here's some code:

Code: Select all

wxPanel *panel = new wxPanel(this, wxID_ANY);
wxBoxSizer *mainSizer = new wxBoxSizer(wxVERTICAL);

  wxDataViewListCtrl *dvlc = new wxDataViewListCtrl(panel, wxID_ANY, wxDefaultPosition, wxDefaultSize, wxDV_ROW_LINES );
  dvlc->AppendTextColumn("header col 1", wxDATAVIEW_CELL_INERT, -1, wxALIGN_CENTER_HORIZONTAL);
  dvlc->AppendBitmapColumn("header col 2", 1, wxDATAVIEW_CELL_INERT, -1, wxALIGN_CENTER_HORIZONTAL);

  // create dummy bitmaps for testing only
  const int bmpSize = 16;
  wxBitmap bmpRed(bmpSize, bmpSize);
  {
      wxMemoryDC mdc(bmpRed);
      mdc.SetBrush(*wxRED);
      mdc.SetPen(*wxBLACK);
      mdc.DrawRectangle(0, 0, bmpSize, bmpSize);
  }
  
  wxBitmap bmpGreen(bmpSize, bmpSize);
  {
      wxMemoryDC mdc(bmpGreen);
      mdc.SetBrush(*wxGREEN);
      mdc.SetPen(*wxBLACK);
      mdc.DrawRectangle(0, 0, bmpSize, bmpSize);
  }

  // this vector will contain the data for one complete row
  wxVector<wxVariant> data;
  data.push_back("line1");
  data.push_back( wxVariant(bmpRed) );
  dvlc->AppendItem(data);

  data.clear();
  data.push_back("line2");
  data.push_back( wxVariant(bmpGreen) );
  dvlc->AppendItem(data);

mainSizer->Add(dvlc, 1, wxEXPAND | wxALL, 4);
   
panel->SetSizer(mainSizer);
Use the source, Luke!
Melandr
Experienced Solver
Experienced Solver
Posts: 53
Joined: Thu Oct 28, 2021 6:07 am

Re: wxGrid and wxBitmap

Post by Melandr »

Good day! Thanks for your help, it turned out to display images in wxGrid cells, but there are several negative points that I would like to eliminate, but I don’t know how to do it right. I will be glad for any hints. Below is the code that displays images in the cells of the wxGrid grid.
Overridden renderer class

Code: Select all

class wxBmpGridCellRenderer : public wxGridCellRenderer
{
public:
    wxBmpGridCellRenderer(const wxImage& image) : wxGridCellRenderer(), m_img(image) {}

    void Draw(wxGrid& grid, wxGridCellAttr& attr, wxDC& dc,
                 const wxRect& rect, int row, int col, bool isSelected);

    wxSize GetBestSize(wxGrid& grid, wxGridCellAttr& attr, wxDC& dc,
                       int row, int col)
    {
        return wxSize(m_img.GetWidth(), m_img.GetHeight());
    }
    wxGridCellRenderer *Clone() const
    {
        return new wxBmpGridCellRenderer(m_img);
    }
private:
    wxImage m_img;
};
Implementing the Draw Method

Code: Select all

void wxBmpGridCellRenderer::Draw(wxGrid& grid, wxGridCellAttr& attr, wxDC& dc, const wxRect& rect, int row, int col, bool isSelected)
{
    wxGridCellRenderer::Draw(grid, attr, dc, rect, row, col, isSelected);
    dc.SetClippingRegion(rect);
    m_img.Rescale(wxSize(109,145).GetWidth(),wxSize(109,145).GetHeight());
    wxBitmap cellBitmap(m_img);
    dc.DrawBitmap(cellBitmap ,rect.x,rect.y);
    dc.DestroyClippingRegion();
}
creation of wxBmpGridCellRenderer class instances in wxGrid table cell filling loop

Code: Select all

pFoto = LoadImageFromBlob(pRetrievedBuffer,len);
Grid1->SetCellRenderer(countOfRows,0,new wxBmpGridCellRenderer(*pFoto));
In principle, the program works as expected, but now about the negative or incomprehensible points:
1. After executing the query, the wxGrid table is populated with string data and images in the first column. On the next request, the string data is removed by calling the method

Code: Select all

Grid1->ClearGrid();
But the images stay in the first column. How to remove them when clearing string data?
2. If the request contains a large number of lines in the response, the program crashes (closes). If the answer is one line or ten, then everything is fine. How to determine what is causing the program to crash?
3. I create dynamic wxBmpGridCellRenderer objects in the following line

Code: Select all

Grid1->SetCellRenderer(countOfRows,0,new wxBmpGridCellRenderer(*pFoto));
using the new operator. What is the correct way to release memory using the delete operator? Where in the program should I call it?
User avatar
doublemax
Moderator
Moderator
Posts: 19115
Joined: Fri Apr 21, 2006 8:03 pm
Location: $FCE2

Re: wxGrid and wxBitmap

Post by doublemax »

But the images stay in the first column. How to remove them when clearing string data?
Clear all cell renderers with SetCellRenderer(row, col, NULL);
2. If the request contains a large number of lines in the response, the program crashes (closes). If the answer is one line or ten, then everything is fine. How to determine what is causing the program to crash?
Use a debug build and run it under a debugger.
What is the correct way to release memory using the delete operator? Where in the program should I call it?
You don't have to delete the renderers. But you may have to delete the pFoto you get from LoadImageFromBlob()
Use the source, Luke!
Melandr
Experienced Solver
Experienced Solver
Posts: 53
Joined: Thu Oct 28, 2021 6:07 am

Re: wxGrid and wxBitmap

Post by Melandr »

Another question arose. And is it possible to hide or show the contents of the cell displayed using the renderer? The meaning is this: I added a checkbox to the form, and I want to control the display of the photo in the first column, can you tell me which way to look in order to implement such functionality?
I also found what is causing the crash in the program. When responding to the sql query, the "FIMG" field does not contain an image and the field contains a NULL value. I tried to check with the following code when adding an image to a cell by the renderer

Code: Select all

                    if(pFoto->IsOk())
                    {
                        Grid1->SetCellRenderer(countOfRows,0,new wxBmpGridCellRenderer(*pFoto));
                    }
but it doesn't work. What is the correct way to use this method if pFoto is a pointer to wxImage ?
At the same time, when I make a request without displaying an image in a cell, the program works fine. I understand that you need to add code that checks that the function

Code: Select all

LoadImageFromBlob(pRetrievedBuffer,len);
does return a pointer to the correct image.
2022-01-21_124320.jpg
2022-01-21_124320.jpg (58.42 KiB) Viewed 642 times
maybe it's worth checking what gets buffered in this line

Code: Select all

wxMemoryBufferBufferOut;
void *pBlob = pResults->GetResultBlob(_("FIMG"),BufferOut);
but I don't know how to check that the GetResultBlob function returns an image and not something else.
PS:After experimenting with pFoto->Ok() and pBlob != NULL, none of them worked, but it turned out to check for the buffer size

Code: Select all

wxMemoryBufferBufferOut;
...
intlen;
len = (int)BufferOut.GetDataLen();

                     if(len > 0)
                     {
                         Grid1->SetCellRenderer(countOfRows,0,new wxBmpGridCellRenderer(*pFoto));
                     }
User avatar
doublemax
Moderator
Moderator
Posts: 19115
Joined: Fri Apr 21, 2006 8:03 pm
Location: $FCE2

Re: wxGrid and wxBitmap

Post by doublemax »

LoadImageFromBlob should just return a NULL pointer if there is no image data.
Use the source, Luke!
Melandr
Experienced Solver
Experienced Solver
Posts: 53
Joined: Thu Oct 28, 2021 6:07 am

Re: wxGrid and wxBitmap

Post by Melandr »

Indeed, the expression

Code: Select all

if(pPhoto != NULL)
working.
Can you tell me how to hide, display uploaded images in wxGrid cells?
User avatar
doublemax
Moderator
Moderator
Posts: 19115
Joined: Fri Apr 21, 2006 8:03 pm
Location: $FCE2

Re: wxGrid and wxBitmap

Post by doublemax »

Melandr wrote: Fri Jan 21, 2022 1:29 pm Can you tell me how to hide, display uploaded images in wxGrid cells?
Either remove the renderer from the cell, clear the image assigned to the renderer, or add a flag to the renderer that controls whether the bitmap gets drawn or not.
Use the source, Luke!
Melandr
Experienced Solver
Experienced Solver
Posts: 53
Joined: Thu Oct 28, 2021 6:07 am

Re: wxGrid and wxBitmap

Post by Melandr »

Good evening!
Do not tell me how to turn off the display correctly? Should this be viewed in the direction of the device contest?
I tried to pass NULL to the renderer, the picture was erased, but the memory was not released.
User avatar
doublemax
Moderator
Moderator
Posts: 19115
Joined: Fri Apr 21, 2006 8:03 pm
Location: $FCE2

Re: wxGrid and wxBitmap

Post by doublemax »

Melandr wrote: Sun Jan 23, 2022 4:48 pm I tried to pass NULL to the renderer, the picture was erased, but the memory was not released.
Which memory?
Use the source, Luke!
Melandr
Experienced Solver
Experienced Solver
Posts: 53
Joined: Thu Oct 28, 2021 6:07 am

Re: wxGrid and wxBitmap

Post by Melandr »

Operational. I do request to base with a large number of lines. The program starts downloading information from the sql server. In the task manager, you can see how the network is loaded and the memory allocated to the application is increasing.
The next moment confuses me. I create a dynamic renderer object in each cell of the first column if there is an image in the input buffer. But then I don't clear the memory anywhere, after I make a new request and get new data
2022-01-24_082318.jpg
Post Reply