From 1d91d71a8ce433857bfda68125e68232a4ffdcd9 Mon Sep 17 00:00:00 2001 From: Bill Su Date: Tue, 17 Mar 2026 00:32:58 -0400 Subject: [PATCH] dnd: improve autoscroll example Using wxScrolled<> on the wxFrame meant that the status bar moved when scrolling, so use give the frame a wxScrolledCanvas child. Also, wxDC::FloodFill() doesn't seem to work well with a scrolling window. (Maybe because the center point isn't necessarily visible?) Using DrawRectangle(), etc works better. --- samples/dnd/d_and_d.txt | 9 +- samples/dnd/dnd.cpp | 200 +++++++++++++++++++++------------------- 2 files changed, 110 insertions(+), 99 deletions(-) diff --git a/samples/dnd/d_and_d.txt b/samples/dnd/d_and_d.txt index f7dd7a5f47..b4d44ce505 100644 --- a/samples/dnd/d_and_d.txt +++ b/samples/dnd/d_and_d.txt @@ -84,10 +84,14 @@ must have been registered) and their values. drop source will delete the corresponding data - which would lead to data loss if you didn't paste it properly. + If wxScrolled<>::EnableAutoscrollWithoutCapture() was called previously, + wxScrolled<>::DisableAutoscrollWithoutCapture() should be called here. + c) OnEnter() called when the mouse enters the window: you might use this function to - give some additional visual feedback. + give some additional visual feedback and/or call + wxScrolled<>::EnableAutoscrollWithoutCapture(). d) OnLeave() @@ -140,7 +144,7 @@ blocking function which enters into its own message loop and may return after an arbitrarily long time interval. During it, the QueryContinueDrag() is called whenever the mouse or keyboard state changes. The default behaviour is quite reasonable for 99% of cases: the drag operation is cancelled if the key -is preessed and the drop is initiated if the mouse button is released. +is pressed and the drop is initiated if the mouse button is released. b) After the end of d&d @@ -160,6 +164,5 @@ wxDropSource::Move code. @@@@ TODO: support tymed != TYMED_HGLOBAL; better support of CF_BMP, CF_METAFILE - scrolling support!! (how?) sample demonstrating use of user-defined formats sample which really does something useful diff --git a/samples/dnd/dnd.cpp b/samples/dnd/dnd.cpp index 0350e85ede..251b89dc0c 100644 --- a/samples/dnd/dnd.cpp +++ b/samples/dnd/dnd.cpp @@ -32,6 +32,7 @@ #endif #if wxUSE_DRAG_AND_DROP +class DnDShapeCanvas; // ---------------------------------------------------------------------------- // Derive two simple classes which just put in the listbox the strings (text or @@ -381,17 +382,14 @@ public: // well, it's a bit difficult to describe a triangle by position and // size, but we're not doing geometry here, do we? ;-) - wxPoint p1(m_pos); - wxPoint p2(m_pos.x + m_size.x, m_pos.y); - wxPoint p3(m_pos.x, m_pos.y + m_size.y); + wxPoint p[3] = { + { m_pos }, + { m_pos.x + m_size.x, m_pos.y }, + { m_pos.x, m_pos.y + m_size.y }, + }; - dc.DrawLine(p1, p2); - dc.DrawLine(p2, p3); - dc.DrawLine(p3, p1); - - //works in multicolor modes; on GTK (at least) will fail in 16-bit color dc.SetBrush(wxBrush(m_col)); - dc.FloodFill(GetCentre(), m_col, wxFLOOD_BORDER); + dc.DrawPolygon(3, p); } }; @@ -416,18 +414,8 @@ public: { DnDShape::Draw(dc); - wxPoint p1(m_pos); - wxPoint p2(p1.x + m_size.x, p1.y); - wxPoint p3(p2.x, p2.y + m_size.y); - wxPoint p4(p1.x, p3.y); - - dc.DrawLine(p1, p2); - dc.DrawLine(p2, p3); - dc.DrawLine(p3, p4); - dc.DrawLine(p4, p1); - dc.SetBrush(wxBrush(m_col)); - dc.FloodFill(GetCentre(), m_col, wxFLOOD_BORDER); + dc.DrawRectangle(m_pos, m_size); } }; @@ -452,10 +440,8 @@ public: { DnDShape::Draw(dc); - dc.DrawEllipse(m_pos, m_size); - dc.SetBrush(wxBrush(m_col)); - dc.FloodFill(GetCentre(), m_col, wxFLOOD_BORDER); + dc.DrawEllipse(m_pos, m_size); } }; @@ -669,7 +655,7 @@ private: class DnDShapeDialog : public wxDialog { public: - DnDShapeDialog(wxFrame *parent, DnDShape *shape); + DnDShapeDialog(wxWindow *parent, DnDShape *shape); DnDShape *GetShape() const; @@ -702,17 +688,30 @@ private: // A frame for the shapes which can be drag-and-dropped between frames // ---------------------------------------------------------------------------- -class DnDShapeFrame : public wxScrolled +class DnDShapeFrame : public wxFrame { public: DnDShapeFrame(wxFrame *parent); - ~DnDShapeFrame(); + +protected: + bool TryBefore(wxEvent& event) override; + +private: + DnDShapeCanvas *m_canvas = nullptr; +}; + +// ---------------------------------------------------------------------------- +// A canvas for the shapes which can be drag-and-dropped between frames +// ---------------------------------------------------------------------------- + +class DnDShapeCanvas : public wxScrolledCanvas +{ +public: + DnDShapeCanvas(DnDShapeFrame *parent); + + DnDShapeFrame *GetParent(); void SetShape(DnDShape *shape); - virtual bool SetShape(const wxRegion ®ion) - { - return wxFrame::SetShape( region ); - } // callbacks void OnNewShape(wxCommandEvent& event); @@ -730,9 +729,9 @@ public: void OnDrop(wxCoord x, wxCoord y, DnDShape *shape); private: - DnDShape *m_shape; + std::unique_ptr m_shape; - static DnDShapeFrame *ms_lastDropTarget; + static DnDShapeCanvas *ms_lastDropTarget; wxDECLARE_EVENT_TABLE(); }; @@ -744,31 +743,31 @@ private: class DnDShapeDropTarget : public wxDropTarget { public: - DnDShapeDropTarget(DnDShapeFrame *frame) + DnDShapeDropTarget(DnDShapeCanvas *canvas) : wxDropTarget(new DnDShapeDataObject) { - m_frame = frame; + m_canvas = canvas; } // override base class (pure) virtuals virtual wxDragResult OnEnter(wxCoord x, wxCoord y, wxDragResult def) override { #if wxUSE_STATUSBAR - m_frame->SetStatusText("Mouse entered the frame"); + m_canvas->GetParent()->SetStatusText("Mouse entered the frame"); #endif // wxUSE_STATUSBAR - m_frame->EnableAutoscrollWithoutCapture(); + m_canvas->EnableAutoscrollWithoutCapture(); return OnDragOver(x, y, def); } virtual void OnLeave() override { #if wxUSE_STATUSBAR - m_frame->SetStatusText("Mouse left the frame"); + m_canvas->GetParent()->SetStatusText("Mouse left the frame"); #endif // wxUSE_STATUSBAR - m_frame->DisableAutoscrollWithoutCapture(); + m_canvas->DisableAutoscrollWithoutCapture(); } virtual bool OnDrop(wxCoord x, wxCoord y) override { - m_frame->DisableAutoscrollWithoutCapture(); + m_canvas->DisableAutoscrollWithoutCapture(); return wxDropTarget::OnDrop(x, y); } virtual wxDragResult OnData(wxCoord x, wxCoord y, wxDragResult def) override @@ -780,14 +779,14 @@ public: return wxDragNone; } - m_frame->OnDrop(x, y, + m_canvas->OnDrop(x, y, ((DnDShapeDataObject *)GetDataObject())->GetShape()); return def; } private: - DnDShapeFrame *m_frame; + DnDShapeCanvas *m_canvas; }; #endif // wxUSE_DRAG_AND_DROP @@ -867,20 +866,20 @@ wxEND_EVENT_TABLE() #if wxUSE_DRAG_AND_DROP -wxBEGIN_EVENT_TABLE(DnDShapeFrame, wxFrame) - EVT_MENU(Menu_Shape_New, DnDShapeFrame::OnNewShape) - EVT_MENU(Menu_Shape_Edit, DnDShapeFrame::OnEditShape) - EVT_MENU(Menu_Shape_Clear, DnDShapeFrame::OnClearShape) +wxBEGIN_EVENT_TABLE(DnDShapeCanvas, wxScrolledCanvas) + EVT_MENU(Menu_Shape_New, DnDShapeCanvas::OnNewShape) + EVT_MENU(Menu_Shape_Edit, DnDShapeCanvas::OnEditShape) + EVT_MENU(Menu_Shape_Clear, DnDShapeCanvas::OnClearShape) - EVT_MENU(Menu_ShapeClipboard_Copy, DnDShapeFrame::OnCopyShape) - EVT_MENU(Menu_ShapeClipboard_Paste, DnDShapeFrame::OnPasteShape) + EVT_MENU(Menu_ShapeClipboard_Copy, DnDShapeCanvas::OnCopyShape) + EVT_MENU(Menu_ShapeClipboard_Paste, DnDShapeCanvas::OnPasteShape) - EVT_UPDATE_UI(Menu_ShapeClipboard_Copy, DnDShapeFrame::OnUpdateUICopy) - EVT_UPDATE_UI(Menu_ShapeClipboard_Paste, DnDShapeFrame::OnUpdateUIPaste) + EVT_UPDATE_UI(Menu_ShapeClipboard_Copy, DnDShapeCanvas::OnUpdateUICopy) + EVT_UPDATE_UI(Menu_ShapeClipboard_Paste, DnDShapeCanvas::OnUpdateUIPaste) - EVT_LEFT_DOWN(DnDShapeFrame::OnDrag) + EVT_LEFT_DOWN(DnDShapeCanvas::OnDrag) - EVT_PAINT(DnDShapeFrame::OnPaint) + EVT_PAINT(DnDShapeCanvas::OnPaint) wxEND_EVENT_TABLE() wxBEGIN_EVENT_TABLE(DnDShapeDialog, wxDialog) @@ -1596,7 +1595,7 @@ bool DnDFile::OnDropFiles(wxCoord, wxCoord, const wxArrayString& filenames) // DnDShapeDialog // ---------------------------------------------------------------------------- -DnDShapeDialog::DnDShapeDialog(wxFrame *parent, DnDShape *shape) +DnDShapeDialog::DnDShapeDialog(wxWindow *parent, DnDShape *shape) :wxDialog( parent, 6001, "Choose Shape", wxPoint( 10, 10 ), wxSize( 40, 40 ), wxDEFAULT_DIALOG_STYLE | wxRAISED_BORDER | wxRESIZE_BORDER ) @@ -1741,28 +1740,14 @@ void DnDShapeDialog::OnColour(wxCommandEvent& WXUNUSED(event)) // DnDShapeFrame // ---------------------------------------------------------------------------- -bool wxCreateScrolled(wxFrame* self, - wxWindow *parent, wxWindowID winid, - const wxPoint& WXUNUSED(pos), const wxSize& WXUNUSED(size), - long WXUNUSED(style), const wxString& name) -{ - return self->Create(parent, winid, name); -} - -DnDShapeFrame *DnDShapeFrame::ms_lastDropTarget = nullptr; - DnDShapeFrame::DnDShapeFrame(wxFrame *parent) - : wxScrolled(parent, wxID_ANY, wxDefaultPosition, wxDefaultSize, wxScrolledWindowStyle, "Shape Frame") + : wxFrame(parent, wxID_ANY, "Shape Frame") { #if wxUSE_STATUSBAR CreateStatusBar(); #endif // wxUSE_STATUSBAR - // this is completely arbitrary and is done just for illustration purposes - SetVirtualSize(1000, 1000); - SetScrollRate(20, 20); - EnableAutoScrollInside(20); - DisableAutoScrollOutside(); + m_canvas = new DnDShapeCanvas(this); wxMenu *menuShape = new wxMenu; menuShape->Append(Menu_Shape_New, "&New default shape\tCtrl-S"); @@ -1783,30 +1768,53 @@ DnDShapeFrame::DnDShapeFrame(wxFrame *parent) #if wxUSE_STATUSBAR SetStatusText("Press Ctrl-S to create a new shape"); #endif // wxUSE_STATUSBAR +} + +bool DnDShapeFrame::TryBefore(wxEvent& event) +{ + if (event.GetEventType() == wxEVT_MENU || + event.GetEventType() == wxEVT_UPDATE_UI) + { + wxPropagationDisabler disablePropagation(event); + if (m_canvas && m_canvas->ProcessWindowEvent(event)) + { + return true; + } + } + + return wxFrame::TryBefore(event); +} + +DnDShapeCanvas *DnDShapeCanvas::ms_lastDropTarget = nullptr; + +DnDShapeCanvas::DnDShapeCanvas(DnDShapeFrame *parent) + : wxScrolledCanvas(parent) +{ + // this is completely arbitrary and is done just for illustration purposes + SetVirtualSize(1000, 1000); + SetScrollRate(20, 20); + EnableAutoScrollInside(20); + DisableAutoScrollOutside(); SetDropTarget(new DnDShapeDropTarget(this)); - m_shape = nullptr; - SetBackgroundColour(*wxWHITE); } -DnDShapeFrame::~DnDShapeFrame() +DnDShapeFrame *DnDShapeCanvas::GetParent() { - if (m_shape) - delete m_shape; + wxWindow *parent = wxScrolledCanvas::GetParent(); + return wxCheckCast(parent); } -void DnDShapeFrame::SetShape(DnDShape *shape) +void DnDShapeCanvas::SetShape(DnDShape *shape) { - if (m_shape) - delete m_shape; - m_shape = shape; + m_shape.reset(shape); Refresh(); } // callbacks -void DnDShapeFrame::OnDrag(wxMouseEvent& event) +void DnDShapeCanvas::OnDrag(wxMouseEvent& event) { if ( !m_shape ) { @@ -1816,7 +1824,7 @@ void DnDShapeFrame::OnDrag(wxMouseEvent& event) } // start drag operation - DnDShapeDataObject shapeData(m_shape); + DnDShapeDataObject shapeData(m_shape.get()); wxDropSource source(shapeData, this); wxString msg; @@ -1829,7 +1837,7 @@ void DnDShapeFrame::OnDrag(wxMouseEvent& event) case wxDragNone: #if wxUSE_STATUSBAR - SetStatusText("Nothing happened"); + GetParent()->SetStatusText("Nothing happened"); #endif // wxUSE_STATUSBAR break; @@ -1848,7 +1856,7 @@ void DnDShapeFrame::OnDrag(wxMouseEvent& event) case wxDragCancel: #if wxUSE_STATUSBAR - SetStatusText("Drag and drop operation cancelled"); + GetParent()->SetStatusText("Drag and drop operation cancelled"); #endif // wxUSE_STATUSBAR break; } @@ -1856,13 +1864,13 @@ void DnDShapeFrame::OnDrag(wxMouseEvent& event) if (msg.length() ) { #if wxUSE_STATUSBAR - SetStatusText(wxString("Shape successfully ") + msg); + GetParent()->SetStatusText(wxString("Shape successfully ") + msg); #endif // wxUSE_STATUSBAR } //else: status text already set } -void DnDShapeFrame::OnDrop(wxCoord x, wxCoord y, DnDShape *shape) +void DnDShapeCanvas::OnDrop(wxCoord x, wxCoord y, DnDShape *shape) { ms_lastDropTarget = this; @@ -1872,16 +1880,16 @@ void DnDShapeFrame::OnDrop(wxCoord x, wxCoord y, DnDShape *shape) #if wxUSE_STATUSBAR wxString s; s.Printf("Shape dropped at (%d, %d)", pt.x, pt.y); - SetStatusText(s); + GetParent()->SetStatusText(s); #endif // wxUSE_STATUSBAR shape->Move(pt); SetShape(shape); } -void DnDShapeFrame::OnEditShape(wxCommandEvent& WXUNUSED(event)) +void DnDShapeCanvas::OnEditShape(wxCommandEvent& WXUNUSED(event)) { - DnDShapeDialog dlg(this, m_shape); + DnDShapeDialog dlg(this, m_shape.get()); if ( dlg.ShowModal() == wxID_OK ) { SetShape(dlg.GetShape()); @@ -1889,27 +1897,27 @@ void DnDShapeFrame::OnEditShape(wxCommandEvent& WXUNUSED(event)) #if wxUSE_STATUSBAR if ( m_shape ) { - SetStatusText("You can now drag the shape to another frame"); + GetParent()->SetStatusText("You can now drag the shape to another frame"); } #endif // wxUSE_STATUSBAR } } -void DnDShapeFrame::OnNewShape(wxCommandEvent& WXUNUSED(event)) +void DnDShapeCanvas::OnNewShape(wxCommandEvent& WXUNUSED(event)) { SetShape(new DnDEllipticShape(wxPoint(10, 10), wxSize(80, 60), *wxRED)); #if wxUSE_STATUSBAR - SetStatusText("You can now drag the shape to another frame"); + GetParent()->SetStatusText("You can now drag the shape to another frame"); #endif // wxUSE_STATUSBAR } -void DnDShapeFrame::OnClearShape(wxCommandEvent& WXUNUSED(event)) +void DnDShapeCanvas::OnClearShape(wxCommandEvent& WXUNUSED(event)) { SetShape(nullptr); } -void DnDShapeFrame::OnCopyShape(wxCommandEvent& WXUNUSED(event)) +void DnDShapeCanvas::OnCopyShape(wxCommandEvent& WXUNUSED(event)) { if ( m_shape ) { @@ -1921,11 +1929,11 @@ void DnDShapeFrame::OnCopyShape(wxCommandEvent& WXUNUSED(event)) return; } - wxTheClipboard->AddData(new DnDShapeDataObject(m_shape)); + wxTheClipboard->AddData(new DnDShapeDataObject(m_shape.get())); } } -void DnDShapeFrame::OnPasteShape(wxCommandEvent& WXUNUSED(event)) +void DnDShapeCanvas::OnPasteShape(wxCommandEvent& WXUNUSED(event)) { wxClipboardLocker clipLocker; if ( !clipLocker ) @@ -1946,17 +1954,17 @@ void DnDShapeFrame::OnPasteShape(wxCommandEvent& WXUNUSED(event)) } } -void DnDShapeFrame::OnUpdateUICopy(wxUpdateUIEvent& event) +void DnDShapeCanvas::OnUpdateUICopy(wxUpdateUIEvent& event) { event.Enable( m_shape != nullptr ); } -void DnDShapeFrame::OnUpdateUIPaste(wxUpdateUIEvent& event) +void DnDShapeCanvas::OnUpdateUIPaste(wxUpdateUIEvent& event) { event.Enable( wxTheClipboard->IsSupported(wxDataFormat(ShapeFormatId())) ); } -void DnDShapeFrame::OnPaint(wxPaintEvent& WXUNUSED(event)) +void DnDShapeCanvas::OnPaint(wxPaintEvent& WXUNUSED(event)) { wxPaintDC dc(this); DoPrepareDC(dc);