Custom widget (slider + progress bar)

84 views
Skip to first unread message

Richard Závodný

unread,
Mar 31, 2021, 6:24:05 AMMar 31
to wx-users
Hi, I wanna create custom widget with functions of both slider and progress bar. I have some input data that I want to show and also want to let user set something interactively with slider like behaviour. See the photo.

P75a.png

How can I achieve this? I'm still pretty new to the wxWidgets so sorry for this "stupid" question.

Thank you!

BTW, if there is anybody who would possibly do it for me, I would be happy to send you some $$ for a beer or two. :D  

New Pagodi

unread,
Mar 31, 2021, 12:32:40 PMMar 31
to wx-users
Whenever you want to make a custom control, the 2 main things you need to do are
1) draw the control using wxDC or wxGraphicsContext in the controls paint handler.
2) respond to mouse, keyboard, and system events to make the control behave as you want.

Optionally, you might also want to have your control generate some events of its own to allow it to notify your application.

Here's a incomplete sample of a custom slider I have that shows an example of how to do these things.  It uses a double between 0 and 1 to store the slider's value, but the code could be modified to use 0 and 255 or an arbitrary max and min instead. Unfortunately, it's completely undocumented, but I don't have time to add comments right now:

header:

class CustomSliderEvent;
wxDECLARE_EVENT(csSLIDER,CustomSliderEvent);

class CustomSliderEvent:public wxCommandEvent
{
public:
    CustomSliderEvent(wxEventType commandEventType=wxEVT_NULL, int id=0);
    void SetDouble(double);
    double GetDouble() const;
private:
    double m_double;
};

class CustomSlider:public wxWindow
{
public:
    CustomSlider(wxWindow*, wxWindowID, const wxPoint& pos = wxDefaultPosition,
                 const wxSize& size = wxDefaultSize, long  style = 0,
                 const wxString& name = "CustomSlider");

    void SetValue(double);
    double GetValue() const;
private:

    void OnPaint(wxPaintEvent&);
    void OnLeftUp(wxMouseEvent&);
    void OnLeftDown(wxMouseEvent&);
    void OnMouseMotion(wxMouseEvent&);
    void OnMouseCaptureLost(wxMouseCaptureLostEvent&);
    void OnSize(wxSizeEvent&);

    void FinishDrag();
    void ProcessMotion(int);

    int ValueToPosition(double);
    double PostionToValue(int);
    void SetClampedPosition(int);

    int m_sliderPosition;

    bool m_dragging;
    int m_thumbWidth;
    int m_minPsn;
    int m_maxPsn;

    double m_value;
};

body:

#include <wx/dcbuffer.h>

#include "customslider.h"

wxDEFINE_EVENT(csSLIDER, CustomSliderEvent);

CustomSliderEvent::CustomSliderEvent(wxEventType commandEventType, int id)
                  :wxCommandEvent(commandEventType,id)
{
    m_double = 0;
}

void CustomSliderEvent::SetDouble(double d)
{
    m_double = d;
}

double CustomSliderEvent::GetDouble() const
{
    return m_double;
}


CustomSlider::CustomSlider(wxWindow* par, wxWindowID id, const wxPoint& pos,
                           const wxSize& size, long  style, const wxString& name)
             :wxWindow(par,id,pos, size , style|wxFULL_REPAINT_ON_RESIZE, name)
{
    wxSize emSize = GetTextExtent("M");

    int emWidth = emSize.GetWidth();
    if ( emWidth % 2 == 0 )
    {
        ++emWidth;
    }

    SetMinSize(wxSize(-1,emSize.GetHeight()));


    SetBackgroundStyle(wxBG_STYLE_PAINT);
    Bind(wxEVT_PAINT, &CustomSlider::OnPaint, this);
    Bind(wxEVT_LEFT_UP, &CustomSlider::OnLeftUp, this);
    Bind(wxEVT_LEFT_DOWN, &CustomSlider::OnLeftDown, this);
    Bind(wxEVT_MOUSE_CAPTURE_LOST, &CustomSlider::OnMouseCaptureLost, this);
    Bind(wxEVT_SIZE,&CustomSlider::OnSize,this);

    m_sliderPosition=0;
    m_dragging = false;
    m_thumbWidth = emWidth;
    m_minPsn=0;
    m_maxPsn=0;

    m_value = 0.0;
}

void CustomSlider::SetValue(double d)
{
    m_value = d;
    m_sliderPosition = ValueToPosition(m_value);
    Refresh();
}

double CustomSlider::GetValue() const
{
    return m_value;
}

static int RoundDoubleToInt(double d)
{
    return static_cast<int>(d+.5);
}

int CustomSlider::ValueToPosition(double d)
{
    return RoundDoubleToInt((m_maxPsn-m_minPsn)*d)+m_minPsn;
}

double CustomSlider::PostionToValue(int i)
{
    return static_cast<double>(i-m_minPsn) /
               static_cast<double>(m_maxPsn-m_minPsn);
}

void CustomSlider::SetClampedPosition(int psn)
{
    if ( psn < m_minPsn )
    {
        psn = m_minPsn;
    }

    if ( psn > m_maxPsn )
    {
        psn = m_maxPsn;
    }

    m_sliderPosition = psn;
}

void CustomSlider::OnSize(wxSizeEvent& event)
{
    wxSize newSize = event.GetSize();
    int width = newSize.GetWidth();

    int thumbHalf = m_thumbWidth/2;
    m_minPsn = thumbHalf;
    m_maxPsn = width-1-thumbHalf;

    m_sliderPosition = ValueToPosition(m_value);
    event.Skip();
}

void CustomSlider::OnLeftDown(wxMouseEvent& event)
{
    m_dragging = true;
    Bind(wxEVT_MOTION,&CustomSlider::OnMouseMotion,this);
    ProcessMotion(event.GetX());
    CaptureMouse();
}

void CustomSlider::OnMouseMotion(wxMouseEvent& event)
{
    ProcessMotion(event.GetX());
}

void CustomSlider::OnMouseCaptureLost(wxMouseCaptureLostEvent&)
{
    FinishDrag();
    Refresh();
}

void CustomSlider::FinishDrag()
{
    m_dragging = false;
    Unbind(wxEVT_MOTION,&CustomSlider::OnMouseMotion,this);

    if ( HasCapture() )
    {
        ReleaseMouse();
    }
}

void CustomSlider::ProcessMotion(int x)
{
    double oldValue = m_value;

    SetClampedPosition(x);

    m_value = PostionToValue(m_sliderPosition);
    Refresh();

    if ( fabs(m_value-oldValue) > DBL_EPSILON )
    {
        CustomSliderEvent event(csSLIDER, GetId());
        event.SetDouble(m_value);
        ProcessWindowEvent(event);
    }
}

void CustomSlider::OnLeftUp(wxMouseEvent& event)
{
    if ( m_dragging )
    {
        FinishDrag();
    }

    ProcessMotion(event.GetX());
}

void CustomSlider::OnPaint(wxPaintEvent&)
{
    wxAutoBufferedPaintDC dc(this);
    wxRect clientRect = GetClientRect();

    dc.Clear();
    dc.SetPen(*wxWHITE_PEN);
    dc.SetBrush(*wxWHITE_BRUSH);
    dc.DrawRectangle(clientRect);

    dc.SetPen(*wxBLACK_PEN);
    dc.SetBrush(*wxBLACK_BRUSH);

    wxRect rect(wxPoint(m_sliderPosition-m_thumbWidth/2,0),
                wxSize(m_thumbWidth,clientRect.GetHeight()));
    dc.DrawRectangle(rect);
}

As I said this a very basic and incomplete control. On windows it looks something like this
Untitled.png

But hopefully it gives an example of how to do the drawing and event handling needed.

Richard Závodný

unread,
Mar 31, 2021, 12:46:25 PMMar 31
to wx-users
Thank you for the example, I'll take a better look at that soon. However Igor, I'll like to update the slider every time when I call something like SetValue method with the value between 0 and 255. Also I need methods to set the two slider values independently. 

Igor Korot

unread,
Mar 31, 2021, 2:47:46 PMMar 31
to wx-u...@googlegroups.com
Hi,

On Wed, Mar 31, 2021 at 11:32 AM New Pagodi <m0...@thaleswell.com> wrote:
Whenever you want to make a custom control, the 2 main things you need to do are
1) draw the control using wxDC or wxGraphicsContext in the controls paint handler.

There is no need to do that in this particular case - he can just create a panel and put
the native controls in there. So it will not be the custom control "per se", but a custom control
for the purpose of his app.

2) respond to mouse, keyboard, and system events to make the control behave as you want.

Yes - this part is essential - you need to properly respond to all those events and possibly generate an event that can
send data outside.

Thank you.

--
Please read http://www.wxwidgets.org/support/mlhowto.htm before posting.
 
To unsubscribe, send email to wx-users+u...@googlegroups.com
or visit http://groups.google.com/group/wx-users
---
You received this message because you are subscribed to the Google Groups "wx-users" group.
To unsubscribe from this group and stop receiving emails from it, send an email to wx-users+u...@googlegroups.com.
To view this discussion on the web visit https://groups.google.com/d/msgid/wx-users/5d0229e1-0383-4f88-bae0-9948c030bab2n%40googlegroups.com.

Gunter Königsmann

unread,
Mar 31, 2021, 3:24:26 PMMar 31
to Richard Závodný, wx-users
I Guess You could combine a wxSlider with an wxOverlayDC and could take the colours you use from wxSystemSettings...
--
Sent from my Android device with K-9 Mail. Please excuse my brevity.

Richard Závodný

unread,
Apr 5, 2021, 7:23:15 AMApr 5
to wx-users
I looked at the code, tried it and it works! However, could you pinpoint me to the point where I have two indpendently sliding rectangles? I don't actually understand how it is moving. In fact, the only part I get is the part of the OnPaint function where you paint the slider. :D

On Wednesday, March 31, 2021 at 6:32:40 PM UTC+2 New Pagodi wrote:

Richard Závodný

unread,
Apr 7, 2021, 6:44:21 AMApr 7
to wx-users
Anybody? 
Reply all
Reply to author
Forward
0 new messages