Page 1 of 1

Making Background of Custom Widget Transparent

Posted: Sun Jul 21, 2019 3:12 pm
by sam_des
Hi,

I have written a simple Custom Widget for LED using wxPanel. It includes nothing more than a circle with user selected On-Color & Off-Color.

Everything works fine, when background of parent widget is white.

As soon as background of parent widget turns dark, a white(or any other color which I set) rectangle appears around the circle.

Is there any way to make my LED widget background transparent or wxPanel non-rectangular?
TIA,
sam

Re: Making Background of Custom Widget Transparent

Posted: Mon Jul 22, 2019 3:51 pm
by doublemax
Achieving "real" transparency that e.g. would even work on a parent with a background image, is hard.

For simple cases, just set the background color of the custom widget to the background color of its parent.

Re: Making Background of Custom Widget Transparent

Posted: Mon Jul 22, 2019 5:07 pm
by sam_des
Hi,
Thanks for info.
Background of the LED widget will be a bitmap/png/jpeg file, actually a map, loaded by the user on the fly.
So background of LED widget may have multiple colors simultaneously.

With circular LED shape, while background may be tolerable, but there can be Triangular shaped LED and its terribly annoying.

Is there a workaround this?
sam

Re: Making Background of Custom Widget Transparent

Posted: Mon Jul 22, 2019 7:21 pm
by PB
What exactly is the issue?

This naive code

Code: Select all

#include <wx/wx.h>

class BitmapPanel : public wxPanel
{
public:
    BitmapPanel(wxWindow* parent, const wxBitmap& bitmap = wxNullBitmap)
        : wxPanel(parent, wxID_ANY, wxDefaultPosition, wxDefaultSize, wxFULL_REPAINT_ON_RESIZE),
          m_bitmap(bitmap)
    {                
        SetBackgroundStyle(wxBG_STYLE_PAINT);
        Bind(wxEVT_PAINT, &BitmapPanel::OnPaint, this);                        
    }

    void SetBitmap(const wxBitmap& bitmap)
    {
        m_bitmap = bitmap;     
        Update(); Refresh();
    }

private:
    wxBitmap m_bitmap;
    
    void OnPaint(wxPaintEvent&) 
    { 
        wxPaintDC dc(this);
        
        dc.SetBackground(*wxBLACK_BRUSH);
        dc.Clear();

        if ( !m_bitmap.IsOk() )
            return; 
        
        wxMemoryDC mdc(m_bitmap);

        dc.StretchBlit(wxPoint(0, 0), dc.GetSize(), &mdc, wxPoint(0,0), mdc.GetSize(), wxCOPY, true);
    }       
};

class wxLEDCtrl : public wxWindow
{
public:
    enum Shape { Circle, Triangle };
    enum State { Off = 0, On = 1 };
    
    wxLEDCtrl(wxWindow* parent, Shape shape = Circle, State state = On, 
             const wxColour& colourOn = *wxRED, const wxColour& colourOff = *wxLIGHT_GREY)
        : wxWindow(parent, wxID_ANY, wxDefaultPosition, wxDefaultSize, wxTRANSPARENT_WINDOW),
          m_shape(shape), m_state(state), m_colourOn(colourOn), m_colourOff(colourOff)
    {               
        SetBackgroundStyle(wxBG_STYLE_PAINT);
        Bind(wxEVT_PAINT, &wxLEDCtrl::OnPaint, this);    
    }

    bool HasTransparentBackground() override { return true; }

protected:        
    wxSize DoGetBestClientSize() const override
    {
        const int size = FromDIP(32);

        return wxSize(size, size);        
    }

private:   
    Shape m_shape;
    State m_state;
    wxColour m_colourOn, m_colourOff;
    
    void OnPaint(wxPaintEvent&)
    {
        const wxColour* clr = m_state == On ? &m_colourOn : &m_colourOff;
        wxPaintDC dc(this);
        wxDCPenChanger penChanger(dc, *clr);
        wxDCBrushChanger brushChanger(dc, *clr);
        const wxRect r = wxRect(dc.GetSize());

        if ( m_shape == Circle )
        {
            dc.DrawCircle(r.GetLeft() + r.GetWidth() / 2, r.GetTop() + r.GetHeight() / 2, 
                r.GetWidth() / 2 );
        }
        else 
        if ( m_shape == Triangle )
        {
            const int pointCount = 3;
            wxPoint points[pointCount];

            points[0] = r.GetBottomLeft();
            points[1] = r.GetBottomRight();
            points[2].x = r.GetLeft() + r.GetWidth() / 2;
            points[2].y = r.GetTop();

            dc.DrawPolygon(pointCount, points);
        }
        else
            wxFAIL_MSG("Invalid LED shape");        
    }    
};


class MyFrame : public wxFrame
{
public:
    MyFrame() : wxFrame(nullptr, wxID_ANY, "Test", wxDefaultPosition, wxSize(640, 640))
    {          
        wxBoxSizer* mainSizer = new wxBoxSizer(wxVERTICAL);

        wxButton* button = new wxButton(this, wxID_ANY, "Load image file for background of the third panel...");
        mainSizer->Add(button, wxSizerFlags().Expand().Border());
        button->Bind(wxEVT_COMMAND_BUTTON_CLICKED, &MyFrame::OnLoadImageFile, this);

        wxPanel* panel1 = new wxPanel(this, wxID_ANY, wxDefaultPosition, wxDefaultSize, wxFULL_REPAINT_ON_RESIZE);        
        CreateLEDs(panel1);
        mainSizer->Add(panel1, wxSizerFlags().Border());

        wxPanel* panel2 = new wxPanel(this, wxID_ANY, wxDefaultPosition, wxDefaultSize, wxFULL_REPAINT_ON_RESIZE);
        panel2->SetBackgroundColour(*wxYELLOW);        
        CreateLEDs(panel2);
        mainSizer->Add(panel2, wxSizerFlags().Proportion(1).Expand().Border());

        m_bitmapPanel = new BitmapPanel(this);
        CreateLEDs(m_bitmapPanel);
        mainSizer->Add(m_bitmapPanel, wxSizerFlags().Proportion(1).Expand().Border());
      
        SetSizer(mainSizer);
    }   
protected:       
    BitmapPanel* m_bitmapPanel;

    void CreateLEDs(wxPanel* parent)
    {    
        wxGridSizer* sizer = new wxGridSizer(3);
        
        wxLEDCtrl* LEDCtrl;

        LEDCtrl = new wxLEDCtrl(parent);
        sizer->Add(LEDCtrl, wxSizerFlags().DoubleBorder());

        LEDCtrl = new wxLEDCtrl(parent, wxLEDCtrl::Circle, wxLEDCtrl::On, *wxGREEN, *wxLIGHT_GREY);
        sizer->Add(LEDCtrl, wxSizerFlags().DoubleBorder());

        LEDCtrl = new wxLEDCtrl(parent, wxLEDCtrl::Circle, wxLEDCtrl::Off);
        sizer->Add(LEDCtrl, wxSizerFlags().DoubleBorder());

        LEDCtrl = new wxLEDCtrl(parent, wxLEDCtrl::Triangle, wxLEDCtrl::On, *wxRED, *wxLIGHT_GREY);
        sizer->Add(LEDCtrl, wxSizerFlags().DoubleBorder());

        LEDCtrl = new wxLEDCtrl(parent, wxLEDCtrl::Triangle, wxLEDCtrl::On, *wxGREEN, *wxLIGHT_GREY);
        sizer->Add(LEDCtrl, wxSizerFlags().DoubleBorder());
        
        LEDCtrl = new wxLEDCtrl(parent, wxLEDCtrl::Triangle, wxLEDCtrl::Off);
        sizer->Add(LEDCtrl, wxSizerFlags().DoubleBorder());

        parent->SetSizer(sizer);        
    }
    
    void OnLoadImageFile(wxCommandEvent&)
    {
        static wxString fileName;
        wxImage image;

        fileName = wxFileSelector("Choose Image to Load", "", fileName, "",
            "Image files (*.jpg;*.png;*.tga;*.bmp;*.tiff;*.gif;*.ico;*.pcx)| *.jpg;*.png;*.tga;*.bmp;*.tiff;*.gif;*.ico;*.pcx",
            wxFD_OPEN | wxFD_FILE_MUST_EXIST, this);

        if ( !fileName.empty() && image.LoadFile(fileName) )            
            m_bitmapPanel->SetBitmap(wxBitmap(image));                    
    }
    
};

class MyApp : public wxApp
{
public:   
    bool OnInit() override
    {
        wxInitAllImageHandlers();
        (new MyFrame)->Show();
        return true;
    }
}; wxIMPLEMENT_APP(MyApp);
results on Windows 10 (if you're not on MSW, perhaps SetBackgroundStyle(wxBG_STYLE_TRANSPARENT) could work) in this
leds.png
leds.png (39.38 KiB) Viewed 788 times
Did you get drawing artifacts when resizing etc. with just simple code like above?

BTW, the bitmap panel is just for testing, the image resizing should be done e.g. similarly to viewtopic.php?f=1&t=45099#p186759

EDIT
There were drawing artifacts when resizing a regular panel but that could be fixed by using wxFULL_REPAINT_ON_RESIZE in the panel'style.