wxScrolled communication between windows 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.
Post Reply
raananb
Super wx Problem Solver
Super wx Problem Solver
Posts: 488
Joined: Fri Oct 27, 2006 4:35 pm
Location: Paris, France
Contact:

wxScrolled communication between windows

Post by raananb »

Referring to the scrolled sample, example showing scrolling only part of the window (starting line 196), I would like to be able execute a function in SubCanvas window following a click in the SubRowLabels window. Both windows are created in SubScrolledWindow.

What would be the best way to implement this (if at all possible) ?
User avatar
doublemax
Moderator
Moderator
Posts: 19160
Joined: Fri Apr 21, 2006 8:03 pm
Location: $FCE2

Re: wxScrolled communication between windows

Post by doublemax »

SubCanvas and SubRowLabels are sibling, i.e. children of the same parent. There are several ways do to this:

1) SubRowLabels calls a method in the parent and this parent will delegate the call to SubCanvas

2) SubRowLabels sends an event to the parent and this parent will delegate the call to SubCanvas
This is a little "cleaner" than 1), but more to type and effectively the same.

3) A signalling system. wxWidgets' event system is not suitable for broadcast messages with arbitrary receivers, so you'll need an external component / library for this. I've used SigSlot ( http://sigslot.sourceforge.net/ ) a couple of times. It's header-only and pretty simple to use. However, it's very old and there are probably better solutions out there (Boost?).
Use the source, Luke!
raananb
Super wx Problem Solver
Super wx Problem Solver
Posts: 488
Joined: Fri Oct 27, 2006 4:35 pm
Location: Paris, France
Contact:

Re: wxScrolled communication between windows

Post by raananb »

A problem I did not manage to solve: how to invoke the method 'ProcessSubRowLabelsClick' in SubScrolledWindow from SubRowLabels. The compiler issues an error message for the second line of OnLeftUp in SubRowLabels:

1>d:\projetsdb\accountsmanager2\report.cpp(364): error C2027: utilisation du type non défini 'SubScrolledWindow' (use of undefied type 'SubScrolledWindow')
1>d:\projetsdb\accountsmanager2\report.cpp(139): note: voir la déclaration de 'SubScrolledWindow' (See the declaration of 'SubScrolled Window')
1>d:\projetsdb\accountsmanager2\report.cpp(364): error C2227: la partie gauche de '->ProcessSubRowLabelsClick' doit pointer vers un type class/struct/union/générique (the left part of -> ... should point at a type class/struct/union/Generic)

(the application works if the line at 364 is commented out, evidently without the functionality I am trying to implement)

Code: Select all

line 139 class SubScrolledWindow;
line 140 SubScrolledWindow* subScrolledWindow;
...
class SubRowLabels : public wxWindow
{
public:
...
  void OnLeftUp(wxMouseEvent& event)
  { // info sent to parent
  line 363    int row = event.GetY() / (rowHeight + 1);
  line 364    subScrolledWindow->ProcessSubRowLabelsClick(categoriesInDisplay.Item(row));
  }
...
};

class SubCanvas : public wxPanel
{
public:
...
  void ProcessCategory(wxString Category)
  { // processing of info received from parent
  }
...
};  
...

class SubScrolledWindow : public wxScrolled<wxWindow>
{
public:
...
  void ProcessSubRowLabelsClick(wxString categorie)
  { // info received from SubRowLabels sent to SubCanvas
    m_canvas->ProcessCategory(category);
  }
...
};

... 
 subScrolledWindow = new SubScrolledWindow(this);
...
User avatar
doublemax
Moderator
Moderator
Posts: 19160
Joined: Fri Apr 21, 2006 8:03 pm
Location: $FCE2

Re: wxScrolled communication between windows

Post by doublemax »

Code: Select all

line 139 class SubScrolledWindow;
line 140 SubScrolledWindow* subScrolledWindow;
...
class SubRowLabels : public wxWindow
{
public:
...
  void OnLeftUp(wxMouseEvent& event)
  { // info sent to parent
  line 363    int row = event.GetY() / (rowHeight + 1);
  line 364    subScrolledWindow->ProcessSubRowLabelsClick(categoriesInDisplay.Item(row));
  }
Is this all in a header file? If you use a forward declaration, the compiler only knows that the class exists, but it doesn't know anything about its methods or structure. You need to include the header file for subScrolledWindow. If that's not possible in a header file because of cyclic dependence, you need to move the code to the .cpp file.
Use the source, Luke!
raananb
Super wx Problem Solver
Super wx Problem Solver
Posts: 488
Joined: Fri Oct 27, 2006 4:35 pm
Location: Paris, France
Contact:

Re: wxScrolled communication between windows

Post by raananb »

Actually this is a two-part file cpp file : at the top are the classes comprising the wxScrolled, followed by the application class (Report) which generates the data and then creates the wxScrolled Window. Preceding the wxScrolled set are data elements used by wxScrolled and set by Report.

The header file only pertains to Report class, and was created by DialogBlocks along with report.cpp

The forward declarations were there all the time, and posed no problems. However, since the inter-windows communication code was not present, no reference issues were encountered. The problem arose when the code in my latest post was introduced.
User avatar
doublemax
Moderator
Moderator
Posts: 19160
Joined: Fri Apr 21, 2006 8:03 pm
Location: $FCE2

Re: wxScrolled communication between windows

Post by doublemax »

Hard to tell without seeing everything in context. But the error seems clear: When you're using subScrolledWindow, the compiler does not know enough about the class SubScrolledWindow. Did you include its header file in report.cpp?
Use the source, Luke!
raananb
Super wx Problem Solver
Super wx Problem Solver
Posts: 488
Joined: Fri Oct 27, 2006 4:35 pm
Location: Paris, France
Contact:

Re: wxScrolled communication between windows

Post by raananb »

Your question is very pertinent: since the code for wxScrolled was lifted directly from the scroll sample, it has no header, since the sample has no header...

Back to the design board.
User avatar
doublemax
Moderator
Moderator
Posts: 19160
Joined: Fri Apr 21, 2006 8:03 pm
Location: $FCE2

Re: wxScrolled communication between windows

Post by doublemax »

If it's still only one file, can it post it completely or attach the file?
Use the source, Luke!
raananb
Super wx Problem Solver
Super wx Problem Solver
Posts: 488
Joined: Fri Oct 27, 2006 4:35 pm
Location: Paris, France
Contact:

Re: wxScrolled communication between windows

Post by raananb »

I dont see where to attach the file, but it is available for downloading (for 48 hours) at

http://www.pixname.com/Forum/report.cpp

Thanks for looking into it.
User avatar
doublemax
Moderator
Moderator
Posts: 19160
Joined: Fri Apr 21, 2006 8:03 pm
Location: $FCE2

Re: wxScrolled communication between windows

Post by doublemax »

Oh dear, i was hoping for something that would actually compile...

I will pick one problem as an example:

Code: Select all

class SubRowLabels : public wxWindow
{
public:
	SubRowLabels(wxScrolled<wxWindow> *parent) : wxWindow(parent, wxID_ANY, wxDefaultPosition, wxSize(100, 600))
	{
		m_owner = parent;

		Connect(wxEVT_PAINT,		wxPaintEventHandler(SubRowLabels::OnPaint));
		Connect(wxEVT_LEFT_UP,	wxMouseEventHandler(SubRowLabels::OnLeftUp));		
	}

	void OnLeftUp(wxMouseEvent& event)
	{
		int row = event.GetY() / (rowHeight + 1);

//		m_owner->ProcessSubRowLabelsClick(categoriesInDisplay.Item(row));
	}
m_owner is of type wxScrolled<wxWindow>. But this class has no method "ProcessSubRowLabelsClick".
You should change m_owner to be of type ProcessSubRowLabelsClick and adjust the constructor, so that the "parent" parameter is a ProcessSubRowLabelsClick* too.
Use the source, Luke!
raananb
Super wx Problem Solver
Super wx Problem Solver
Posts: 488
Joined: Fri Oct 27, 2006 4:35 pm
Location: Paris, France
Contact:

Re: wxScrolled communication between windows

Post by raananb »

I can assure you that this compiles, provided that the line m_owner->ProcessSubRowLabelsClick is commented out.

I already tried to change the constructor to transmit two addresses, but could not determine how to transmit the address of the function ProcessSubRowLabelsClick.
raananb
Super wx Problem Solver
Super wx Problem Solver
Posts: 488
Joined: Fri Oct 27, 2006 4:35 pm
Location: Paris, France
Contact:

Re: wxScrolled communication between windows

Post by raananb »

I transposed the tentative solution into scroll sample; below is the part pertaining to my problem, which can be compiled if inserted into the sample.

I added the forward declaration of MySubScrolledWindow and the pointer mySubScrolledWindow (which gets its value upon creation of MySybScrolledWindow in MySubFrame)

I also added
OnRowLeftUp() in MySubRowLabels,
ReactToClick() in MyCanvas
ProcessRowLabelsClick() in MySubScrolledWindow

The compiler complains about line 'm_funcp->ProcessRowLabelsClick(wxString::Format("row clicked =%", row));' in MySubRowLabels using undefined type 'MySubScrolledWindow', referring to the forward declaration.

Code: Select all

// ----------------------------------------------------------------------------
// example showing scrolling only part of the window
// ----------------------------------------------------------------------------

// this window consists of an empty space in its corner, column labels window
// along its top, row labels window along its left hand side and a canvas in
// the remaining space

class MySubScrolledWindow;

MySubScrolledWindow* mySubScrolledWindow;

class MySubColLabels : public wxWindow
{
public:
    MySubColLabels(wxScrolled<wxWindow> *parent)
        : wxWindow(parent, wxID_ANY)
    {
        m_owner = parent;

        Connect(wxEVT_PAINT, wxPaintEventHandler(MySubColLabels::OnPaint));
    }

private:
    void OnPaint(wxPaintEvent& WXUNUSED(event))
    {
        wxPaintDC dc(this);

        // This is wrong..  it will translate both x and y if the
        // window is scrolled, the label windows are active in one
        // direction only.  Do the action below instead -- RL.
        //m_owner->PrepareDC( dc );

        int xScrollUnits, xOrigin;

        m_owner->GetViewStart( &xOrigin, 0 );
        m_owner->GetScrollPixelsPerUnit( &xScrollUnits, 0 );
        dc.SetDeviceOrigin( -xOrigin * xScrollUnits, 0 );

        dc.DrawText("Column 1", 5, 5);
        dc.DrawText("Column 2", 105, 5);
        dc.DrawText("Column 3", 205, 5);
    }

    wxScrolled<wxWindow> *m_owner;
};


class MySubRowLabels : public wxWindow
{
public:
    MySubRowLabels(wxScrolled<wxWindow> *parent, MySubScrolledWindow* funcp)
        : wxWindow(parent, wxID_ANY)
    {
        m_owner = parent;
        m_funcp = funcp;

        Connect(wxEVT_PAINT, wxPaintEventHandler(MySubRowLabels::OnPaint));
        Connect(wxEVT_LEFT_UP, wxMouseEventHandler(MySubRowLabels::OnRowLeftUp));
    }

    void OnRowLeftUp(wxMouseEvent& event)
    {
        int row = event.GetY();
        m_funcp->ProcessRowLabelsClick(wxString::Format("row clicked =%", row));
    }
private:
    void OnPaint(wxPaintEvent& WXUNUSED(event))
    {

        wxPaintDC dc(this);

        // This is wrong..  it will translate both x and y if the
        // window is scrolled, the label windows are active in one
        // direction only.  Do the action below instead -- RL.
        //m_owner->PrepareDC( dc );

        int yScrollUnits, yOrigin;

        m_owner->GetViewStart( 0, &yOrigin );
        m_owner->GetScrollPixelsPerUnit( 0, &yScrollUnits );
        dc.SetDeviceOrigin( 0, -yOrigin * yScrollUnits );

        dc.DrawText("Row 1", 5, 5);
        dc.DrawText("Row 2", 5, 30);
        dc.DrawText("Row 3", 5, 55);
        dc.DrawText("Row 4", 5, 80);
        dc.DrawText("Row 5", 5, 105);
        dc.DrawText("Row 6", 5, 130);

    }

    wxScrolled<wxWindow> *m_owner;
    MySubScrolledWindow* m_funcp;
};

class MySubCanvas : public wxPanel
{
public:
    MySubCanvas(wxScrolled<wxWindow> *parent, wxWindow *cols, wxWindow *rows)
        : wxPanel(parent, wxID_ANY)
    {
        m_owner = parent;
        m_colLabels = cols;
        m_rowLabels = rows;

        (void)new wxButton(this, wxID_ANY, "Hallo I",
                           wxPoint(0,50), wxSize(100,25) );
        (void)new wxButton(this, wxID_ANY, "Hallo II",
                           wxPoint(200,50), wxSize(100,25) );

        (void)new wxTextCtrl(this, wxID_ANY, "Text I",
                             wxPoint(0,100), wxSize(100,25) );
        (void)new wxTextCtrl(this, wxID_ANY, "Text II",
                             wxPoint(200,100), wxSize(100,25) );

        (void)new wxComboBox(this, wxID_ANY, "ComboBox I",
                             wxPoint(0,150), wxSize(100,25));
        (void)new wxComboBox(this, wxID_ANY, "ComboBox II",
                             wxPoint(200,150), wxSize(100,25));


        SetBackgroundColour("WHEAT");

        Connect(wxEVT_PAINT, wxPaintEventHandler(MySubCanvas::OnPaint));
    }

    // override the base class function so that when this window is scrolled,
    // the labels are scrolled in sync
    virtual void ScrollWindow(int dx, int dy, const wxRect *rect) wxOVERRIDE
    {
        wxPanel::ScrollWindow( dx, dy, rect );
        m_colLabels->ScrollWindow( dx, 0, rect );
        m_rowLabels->ScrollWindow( 0, dy, rect );
    }

    void ReactToClick(wxString text)
    {
        wxMessageBox(text);
    }
private:
    void OnPaint(wxPaintEvent& WXUNUSED(event))
    {
        wxPaintDC dc( this );
        m_owner->PrepareDC( dc );

        dc.SetPen( *wxBLACK_PEN );

        // OK, let's assume we are a grid control and we have two
        // grid cells. Here in OnPaint we want to know which cell
        // to redraw so that we prevent redrawing cells that don't
        // need to get redrawn. We have one cell at (0,0) and one
        // more at (200,0), both having a size of (100,25).

        // We can query how much the window has been scrolled
        // by calling CalcUnscrolledPosition()

        int scroll_x = 0;
        int scroll_y = 0;
        m_owner->CalcUnscrolledPosition( scroll_x, scroll_y, &scroll_x, &scroll_y );

        // We also need to know the size of the window to see which
        // cells are completely hidden and not get redrawn

        int size_x = 0;
        int size_y = 0;
        GetClientSize( &size_x, &size_y );

        // First cell: (0,0)(100,25)
        // It it on screen?
        if ((0+100-scroll_x > 0) && (0+25-scroll_y > 0) &&
            (0-scroll_x < size_x) && (0-scroll_y < size_y))
        {
            // Has the region on screen been exposed?
            if (IsExposed(0,0,100,25))
            {
                dc.DrawRectangle( 0, 0, 100, 25 );
                dc.DrawText("First Cell", 5, 5);
            }
        }


        // Second cell: (200,0)(100,25)
        // It it on screen?
        if ((200+100-scroll_x > 0) && (0+25-scroll_y > 0) &&
            (200-scroll_x < size_x) && (0-scroll_y < size_y))
        {
            // Has the region on screen been exposed?
            if (IsExposed(200,0,100,25))
            {
                dc.DrawRectangle( 200, 0, 100, 25 );
                dc.DrawText("Second Cell", 205, 5);
            }
        }
    }

    wxScrolled<wxWindow> *m_owner;
    wxWindow *m_colLabels,
             *m_rowLabels;
};

class MySubScrolledWindow : public wxScrolled<wxWindow>
{
public:
    enum
    {
        CORNER_WIDTH = 60,
        CORNER_HEIGHT = 25
    };

    MySubScrolledWindow(wxWindow *parent)
        : wxScrolled<wxWindow>(parent, wxID_ANY)
    {
        // create the children
        MySubColLabels *cols = new MySubColLabels(this);
        MySubRowLabels *rows = new MySubRowLabels(this, mySubScrolledWindow);

        m_canvas = new MySubCanvas(this, cols, rows);

        // lay them out
        wxFlexGridSizer *sizer = new wxFlexGridSizer(2, 2, 10, 10);
        sizer->Add(CORNER_WIDTH, CORNER_HEIGHT); // just a spacer
        sizer->Add(cols, wxSizerFlags().Expand());
        sizer->Add(rows, wxSizerFlags().Expand());
        sizer->Add(m_canvas, wxSizerFlags().Expand());
        sizer->AddGrowableRow(1);
        sizer->AddGrowableCol(1);
        SetSizer(sizer);

        // this is the key call: it means that only m_canvas will be scrolled
        // and not this window itself
        SetTargetWindow(m_canvas);

        SetScrollbars(10, 10, 50, 50);

        Connect(wxEVT_SIZE, wxSizeEventHandler(MySubScrolledWindow::OnSize));
    }

    void ProcessRowLabelsClick(wxString rowText)
    {
        wxMessageBox(rowText);
        m_canvas->ReactToClick(rowText);
    }
protected:
    // scrolled windows which use scroll target different from the window
    // itself must override this virtual method
    virtual wxSize GetSizeAvailableForScrollTarget(const wxSize& size) wxOVERRIDE
    {
        // decrease the total size by the size of the non-scrollable parts
        // above/to the left of the canvas
        wxSize sizeCanvas(size);
        sizeCanvas.x -= 60;
        sizeCanvas.y -= 25;
        return sizeCanvas;
    }

private:
    void OnSize(wxSizeEvent& WXUNUSED(event))
    {
        // We need to override OnSize so that our scrolled
        // window a) does call Layout() to use sizers for
        // positioning the controls but b) does not query
        // the sizer for their size and use that for setting
        // the scrollable area as set that ourselves by
        // calling SetScrollbar() further down.

        Layout();

        AdjustScrollbars();
    }

    MySubCanvas *m_canvas;
};

class MySubFrame : public wxFrame
{
public:
    MySubFrame(wxWindow *parent)
        : wxFrame(parent, wxID_ANY, "MySubScrolledWindow")
    {
        mySubScrolledWindow = new MySubScrolledWindow(this);

        Show();
    }
};
User avatar
doublemax
Moderator
Moderator
Posts: 19160
Joined: Fri Apr 21, 2006 8:03 pm
Location: $FCE2

Re: wxScrolled communication between windows

Post by doublemax »

This cyclic dependence can't be resolved in a single file. In order to get "m_funcp->ProcessRowLabelsClick" to work, MySubScrolledWindow would have to be moved above the MySubRowLabels definition. But then you would get an error inside MySubScrolledWindow .

You have to split the classes into header and implementation files.
Use the source, Luke!
raananb
Super wx Problem Solver
Super wx Problem Solver
Posts: 488
Joined: Fri Oct 27, 2006 4:35 pm
Location: Paris, France
Contact:

Re: wxScrolled communication between windows

Post by raananb »

I already came across this problem, and thought that the sample would have been more instructive if it were constructed with a header file to begin with.

To avoid the trouble of splitting the code, I defined a global variable (selectedCategory) where SubRowLabels stores the row value when a row was selected. A timer (in the application) is launched to check whether the variable was set. The value is sent from the timer handler to SubScrolledWindow, which sends it to SubCanvas which proccesses the information. This is quite clean and works fine.

The code used:

Code: Select all

At global level:

wxString selectedCategory;

wxTimer*  pTimerRowLabelClick;

In the application (Report):

pTimerRowLabelClick = &timerRowLabelClick;
timerRowLabelClick.SetOwner(this, ID_TimerRowLabelClick);
timerRowLabelClick.Start(300);

void Report::OnTimerRowLabelClick(wxTimerEvent& event)
{
    if (selectedCategorie.IsEmpty())
    {
	return;
    }

    timerRowLabelClick.Stop();

    subScrolledWindow->ProcessSubRowLabelsClick(selectedCategory);
}

In SubRowLabels:

void OnLeftUp(wxMouseEvent& event)
{
    int row = event.GetY() / (rowHeight + 1);
    selectedCategory = categoriesInDisplay.Item(row);
}

In SubScrolledWindow:

void ProcessSubRowLabelsClick(wxString categorie)
{ // info received from SubRowLabels sent to SubCanvas
    m_canvas->ProcessCategorie(categorie);
}

In SubCanvas:

void ProcessCategorie(wxString Categorie)
{
    selectedCategory.Clear();

    pTimerRowLabelClick->Start(300);
}

Post Reply