Select 在 C++ 中使用 wxwidget 使用可拖动矩形的图像区域
Select an area from an image using a draggable rectangle using wxwidget in c++
我用 c++ 编写了一个程序 wxwidgets.I 我在图像上放置了一个矩形,并希望 select 矩形覆盖的图像部分应该是可拖动的。但问题是,当我单击鼠标时,图像消失了,唯一的矩形(可以拖动)仍然存在,反之亦然。
`
class BasicDrawPane : public wxPanel
{
public:
BasicDrawPane();
BasicDrawPane(wxFrame* parent);
void paintEvent(wxPaintEvent & evt);
void render(wxDC& dc);
void mouseMoved(wxMouseEvent& event);
void mouseDown(wxMouseEvent& event);
void mouseWheelMoved(wxMouseEvent& event);
void mouseReleased(wxMouseEvent& event);
void rightClick(wxMouseEvent& event);
void mouseLeftWindow(wxMouseEvent& event);
DECLARE_EVENT_TABLE()
};
class MyFrame: public wxFrame{
public:
MyFrame(const wxString& title, const wxPoint& pos, const wxSize& size);
wxString path;
BasicDrawPane panel;
private:
void OnHello(wxCommandEvent& event);
void OnExit(wxCommandEvent& event);
void OnAbout(wxCommandEvent& event);
void OnOpen(wxCommandEvent& event);
void OnPaint(wxCommandEvent& event);
void OnRect(wxCommandEvent& event);
void OnSave(wxCommandEvent& event);
DECLARE_EVENT_TABLE();
wxBitmap bmp;
wxMemoryDC memDC;
};
enum
{
ID_Hello = 1, ID_PAINT = 2, ID_RECT = 3, ID_SAVE = 4
};
BEGIN_EVENT_TABLE( MyFrame, wxFrame )
EVT_MENU(ID_Hello,MyFrame::OnHello)
EVT_MENU(wxID_EXIT,MyFrame::OnExit)
EVT_MENU(wxID_ABOUT,MyFrame::OnAbout)
EVT_MENU(wxID_OPEN,MyFrame::OnOpen)
EVT_MENU(ID_PAINT,MyFrame::OnPaint)
EVT_MENU(ID_RECT,MyFrame::OnRect)
EVT_MENU(ID_SAVE,MyFrame::OnSave)
END_EVENT_TABLE()
void MyFrame::OnPaint(wxCommandEvent& event)
{
//wxPaintDC dc( this );
//dc.DrawBitmap( m_bitmap, 0, 0, true /* use mask */ );
//wxStaticBitmap *b1 = new wxStaticBitmap(this, -1, wxBitmap(wxImage(path)));
bmp.LoadFile((path),wxBITMAP_TYPE_ANY);
// bmp.LoadFile((path),wxBITMAP_TYPE_PNG);
memDC.SelectObject( bmp );
//memDC.SetBackground(*wxWHITE_BRUSH);
//memDC.Clear();
/* memDC.SetPen(*wxGREEN_PEN);
memDC.SetBrush(*wxTRANSPARENT_BRUSH);
memDC.DrawRectangle( m_x, m_y, WIDTH, HEIGHT );*/
//Check();
memDC.SelectObject(wxNullBitmap);
// wxSize sz(512,384);
// wxSize sz(900,600);
wxStaticBitmap *b1 = new wxStaticBitmap(/* dynamic_cast<wxFrame*>*/this, -1, bmp, wxDefaultPosition);
Refresh();
}
class MyApp: public wxApp
{
public:
virtual bool OnInit();
//MyFrame *frame;
BasicDrawPane * drawPane;
};
IMPLEMENT_APP(MyApp)
bool MyApp::OnInit()
{
// wxBoxSizer* sizer = new wxBoxSizer(wxHORIZONTAL);
//frame = new MyFrame((wxFrame *)NULL, -1, wxT("Hello wxDC"), wxPoint(50,50), wxSize(800,600));
MyFrame *frame = new MyFrame( _T("Hello World"), wxPoint(50, 50), wxSize(600, 600) );
// drawPane = new BasicDrawPane( (wxFrame*) frame );
// sizer->Add(drawPane, 1, wxEXPAND);
//frame->SetSizer(sizer);
// /* dynamic_cast<wxFrame*>(this)*/ frame-> SetAutoLayout(true);
/* dynamic_cast<wxFrame*>(this)*/frame -> Show();
return true;
}
BEGIN_EVENT_TABLE(BasicDrawPane, wxPanel)
EVT_MOTION(BasicDrawPane::mouseMoved)
EVT_LEFT_DOWN(BasicDrawPane::mouseDown)
EVT_LEFT_UP(BasicDrawPane::mouseReleased)
EVT_RIGHT_DOWN(BasicDrawPane::rightClick)
EVT_LEAVE_WINDOW(BasicDrawPane::mouseLeftWindow)
EVT_MOUSEWHEEL(BasicDrawPane::mouseWheelMoved)
EVT_PAINT(BasicDrawPane::paintEvent)
// catch paint events
END_EVENT_TABLE()
void BasicDrawPane::mouseDown(wxMouseEvent& event)
{
/* if (event.GetPosition().x >= m_x && event.GetPosition().x <= m_x + WIDTH &&
event.GetPosition().y >= m_y && event.GetPosition().y <= m_y + HEIGHT)
{
m_dragging = true;
m_previous_mouse_x = event.GetPosition().x;
m_previous_mouse_y = event.GetPosition().y;
}*/
}
void BasicDrawPane::mouseWheelMoved(wxMouseEvent& event) {}
void BasicDrawPane::mouseReleased(wxMouseEvent& event)
{
m_dragging = true;
}
void BasicDrawPane::mouseMoved(wxMouseEvent& event)
{
if (m_dragging && event.Dragging())
{
int delta_x = event.GetPosition().x - m_previous_mouse_x;
int delta_y = event.GetPosition().y - m_previous_mouse_y;
m_x += delta_x;
m_y += delta_y;
m_previous_mouse_x = event.GetPosition().x;
m_previous_mouse_y = event.GetPosition().y;
// trigger paint event
Refresh();
}
}
void BasicDrawPane::mouseLeftWindow(wxMouseEvent& event)
{
m_dragging = true;
}
void BasicDrawPane::rightClick(wxMouseEvent& event) {}
BasicDrawPane::BasicDrawPane(wxFrame* parent) :
wxPanel(parent)
{
// m_dragging = true;
// m_x = 100;
// m_y = 100;
}
/*
* Called by the system of by wxWidgets when the panel needs
* to be redrawn. You can also trigger this call by
* calling Refresh()/Update().
*/
void BasicDrawPane::paintEvent(wxPaintEvent & evt)
{
//wxCommandEvent w1(wxEVT_NULL, ID_PAINT);
//OnPaint(w1);
wxPaintDC dc(this);
render(dc);
}
void BasicDrawPane::render(wxDC& dc)
{
dc.SetPen(*wxGREEN_PEN);
dc.SetBrush(*wxTRANSPARENT_BRUSH);
dc.DrawRectangle( m_x, m_y, WIDTH, HEIGHT );
}
`
为了回答这个问题,有几件事需要解释,所以我会一个一个地解释。我认为你的基本想法是好的,所以我不会详细介绍实际选择应该如何进行等。
首先,我建议使用 Connect() or Bind() 而不是事件 table。这允许您将子 window 事件连接回父 window 并在一个地方处理它们。
例如,如果您的主框架 class 称为 MainFrame
并且您有一个 wxPanel
成员称为 m_DrawPanel
,在 MainFrame ctor 中您可以:
MainFrame::MainFrame(wxWindow* parent)
{
// Connect mouse event handlers.
m_DrawPanel->Connect(wxEVT_LEFT_DOWN,
wxMouseEventHandler(MainFrame::OnPanelLDown), NULL, this);
m_DrawPanel->Connect(wxEVT_LEFT_UP,
wxMouseEventHandler(MainFrame::OnPanelLUp), NULL, this);
m_DrawPanel->Connect(wxEVT_MOTION,
wxMouseEventHandler(MainFrame::OnPanelMotion), NULL, this);
// Connect paint and erase handlers.
m_DrawPanel->Connect(wxEVT_PAINT,
wxPaintEventHandler(MainFrame::OnPanelPaint), NULL, this);
m_DrawPanel->Connect(wxEVT_ERASE_BACKGROUND,
wxEraseEventHandler(MainFrame::OnPanelErase), NULL, this);
// Load the bitmap and set the mode to 'not currently selecting'.
m_Picture.LoadFile ("wxwidgets.png", wxBITMAP_TYPE_PNG);
m_SelectionMode = false;
}
注意:我包含了一个 wxEVT_ERASE_BACKGROUND
事件覆盖,因为否则面板往往会被清除,从而导致闪烁(这是一种简单的方法)。
3 个鼠标事件处理程序可以实现您的选择逻辑(我想这基本上就是您想要的):
void MainFrame::OnPanelLDown(wxMouseEvent& event)
{
m_SelectionMode = true;
m_SelectionRect = wxRect(event.GetPosition(), wxSize(0, 0));
}
void MainFrame::OnPanelLUp(wxMouseEvent& event)
{
m_SelectionMode = false;
// ... handle what to do with the selection here
// (selected area is defined by m_SelectionRect).
// ...
// Zero the selection rectangle for neatness (not really required).
m_SelectionRect = wxRect ();
}
void MainFrame::OnPanelMotion(wxMouseEvent& event)
{
m_SelectionRect = wxRect(m_SelectionRect.GetTopLeft(), event.GetPosition());
// Call Refresh() to trigger a paint event.
m_mainPanel->Refresh();
}
如前所述,覆盖面板的 wxEVT_ERASE_BACKGROUND
事件不执行任何操作:
void MainFrame::OnPanelErase(wxEraseEvent& event)
{
}
最后,我认为这是你在问题中真正要问的部分(我包括了其他部分以帮助你构建一个工作程序):
void MainFrame::OnPanelPaint(wxPaintEvent& event)
{
// Obtain a wxPaintDC.
wxPaintDC pdc (m_mainPanel);
// Draw our image.
pdc.DrawBitmap(m_Picture, wxPoint(0, 0));
// If the user is currently selecting (left mouse button is down)
// then draw the selection rectangle.
if (m_SelectionMode)
{
pdc.SetPen(*wxRED_PEN);
pdc.SetBrush(*wxTRANSPARENT_BRUSH);
pdc.DrawRectangle(m_SelectionRect);
}
}
这是一个绘画事件处理程序,所以首先我们需要创建一个 wxPaintDC
上下文。接下来,我们绘制位图,这确保它每次都刷新并且不会被鼠标移动、调整大小或其他 windows 拖动等损坏。最后,如果用户当前正在移动鼠标并按下左键,那么绘制选择矩形。
有许多其他方法可以实现同一目标。其中一些可能更好或更有效,但这是一种简单的工作方式,直到您更加熟悉 wxWidgets。
我用 c++ 编写了一个程序 wxwidgets.I 我在图像上放置了一个矩形,并希望 select 矩形覆盖的图像部分应该是可拖动的。但问题是,当我单击鼠标时,图像消失了,唯一的矩形(可以拖动)仍然存在,反之亦然。
`
class BasicDrawPane : public wxPanel
{
public:
BasicDrawPane();
BasicDrawPane(wxFrame* parent);
void paintEvent(wxPaintEvent & evt);
void render(wxDC& dc);
void mouseMoved(wxMouseEvent& event);
void mouseDown(wxMouseEvent& event);
void mouseWheelMoved(wxMouseEvent& event);
void mouseReleased(wxMouseEvent& event);
void rightClick(wxMouseEvent& event);
void mouseLeftWindow(wxMouseEvent& event);
DECLARE_EVENT_TABLE()
};
class MyFrame: public wxFrame{
public:
MyFrame(const wxString& title, const wxPoint& pos, const wxSize& size);
wxString path;
BasicDrawPane panel;
private:
void OnHello(wxCommandEvent& event);
void OnExit(wxCommandEvent& event);
void OnAbout(wxCommandEvent& event);
void OnOpen(wxCommandEvent& event);
void OnPaint(wxCommandEvent& event);
void OnRect(wxCommandEvent& event);
void OnSave(wxCommandEvent& event);
DECLARE_EVENT_TABLE();
wxBitmap bmp;
wxMemoryDC memDC;
};
enum
{
ID_Hello = 1, ID_PAINT = 2, ID_RECT = 3, ID_SAVE = 4
};
BEGIN_EVENT_TABLE( MyFrame, wxFrame )
EVT_MENU(ID_Hello,MyFrame::OnHello)
EVT_MENU(wxID_EXIT,MyFrame::OnExit)
EVT_MENU(wxID_ABOUT,MyFrame::OnAbout)
EVT_MENU(wxID_OPEN,MyFrame::OnOpen)
EVT_MENU(ID_PAINT,MyFrame::OnPaint)
EVT_MENU(ID_RECT,MyFrame::OnRect)
EVT_MENU(ID_SAVE,MyFrame::OnSave)
END_EVENT_TABLE()
void MyFrame::OnPaint(wxCommandEvent& event)
{
//wxPaintDC dc( this );
//dc.DrawBitmap( m_bitmap, 0, 0, true /* use mask */ );
//wxStaticBitmap *b1 = new wxStaticBitmap(this, -1, wxBitmap(wxImage(path)));
bmp.LoadFile((path),wxBITMAP_TYPE_ANY);
// bmp.LoadFile((path),wxBITMAP_TYPE_PNG);
memDC.SelectObject( bmp );
//memDC.SetBackground(*wxWHITE_BRUSH);
//memDC.Clear();
/* memDC.SetPen(*wxGREEN_PEN);
memDC.SetBrush(*wxTRANSPARENT_BRUSH);
memDC.DrawRectangle( m_x, m_y, WIDTH, HEIGHT );*/
//Check();
memDC.SelectObject(wxNullBitmap);
// wxSize sz(512,384);
// wxSize sz(900,600);
wxStaticBitmap *b1 = new wxStaticBitmap(/* dynamic_cast<wxFrame*>*/this, -1, bmp, wxDefaultPosition);
Refresh();
}
class MyApp: public wxApp
{
public:
virtual bool OnInit();
//MyFrame *frame;
BasicDrawPane * drawPane;
};
IMPLEMENT_APP(MyApp)
bool MyApp::OnInit()
{
// wxBoxSizer* sizer = new wxBoxSizer(wxHORIZONTAL);
//frame = new MyFrame((wxFrame *)NULL, -1, wxT("Hello wxDC"), wxPoint(50,50), wxSize(800,600));
MyFrame *frame = new MyFrame( _T("Hello World"), wxPoint(50, 50), wxSize(600, 600) );
// drawPane = new BasicDrawPane( (wxFrame*) frame );
// sizer->Add(drawPane, 1, wxEXPAND);
//frame->SetSizer(sizer);
// /* dynamic_cast<wxFrame*>(this)*/ frame-> SetAutoLayout(true);
/* dynamic_cast<wxFrame*>(this)*/frame -> Show();
return true;
}
BEGIN_EVENT_TABLE(BasicDrawPane, wxPanel)
EVT_MOTION(BasicDrawPane::mouseMoved)
EVT_LEFT_DOWN(BasicDrawPane::mouseDown)
EVT_LEFT_UP(BasicDrawPane::mouseReleased)
EVT_RIGHT_DOWN(BasicDrawPane::rightClick)
EVT_LEAVE_WINDOW(BasicDrawPane::mouseLeftWindow)
EVT_MOUSEWHEEL(BasicDrawPane::mouseWheelMoved)
EVT_PAINT(BasicDrawPane::paintEvent)
// catch paint events
END_EVENT_TABLE()
void BasicDrawPane::mouseDown(wxMouseEvent& event)
{
/* if (event.GetPosition().x >= m_x && event.GetPosition().x <= m_x + WIDTH &&
event.GetPosition().y >= m_y && event.GetPosition().y <= m_y + HEIGHT)
{
m_dragging = true;
m_previous_mouse_x = event.GetPosition().x;
m_previous_mouse_y = event.GetPosition().y;
}*/
}
void BasicDrawPane::mouseWheelMoved(wxMouseEvent& event) {}
void BasicDrawPane::mouseReleased(wxMouseEvent& event)
{
m_dragging = true;
}
void BasicDrawPane::mouseMoved(wxMouseEvent& event)
{
if (m_dragging && event.Dragging())
{
int delta_x = event.GetPosition().x - m_previous_mouse_x;
int delta_y = event.GetPosition().y - m_previous_mouse_y;
m_x += delta_x;
m_y += delta_y;
m_previous_mouse_x = event.GetPosition().x;
m_previous_mouse_y = event.GetPosition().y;
// trigger paint event
Refresh();
}
}
void BasicDrawPane::mouseLeftWindow(wxMouseEvent& event)
{
m_dragging = true;
}
void BasicDrawPane::rightClick(wxMouseEvent& event) {}
BasicDrawPane::BasicDrawPane(wxFrame* parent) :
wxPanel(parent)
{
// m_dragging = true;
// m_x = 100;
// m_y = 100;
}
/*
* Called by the system of by wxWidgets when the panel needs
* to be redrawn. You can also trigger this call by
* calling Refresh()/Update().
*/
void BasicDrawPane::paintEvent(wxPaintEvent & evt)
{
//wxCommandEvent w1(wxEVT_NULL, ID_PAINT);
//OnPaint(w1);
wxPaintDC dc(this);
render(dc);
}
void BasicDrawPane::render(wxDC& dc)
{
dc.SetPen(*wxGREEN_PEN);
dc.SetBrush(*wxTRANSPARENT_BRUSH);
dc.DrawRectangle( m_x, m_y, WIDTH, HEIGHT );
}
`
为了回答这个问题,有几件事需要解释,所以我会一个一个地解释。我认为你的基本想法是好的,所以我不会详细介绍实际选择应该如何进行等。
首先,我建议使用 Connect() or Bind() 而不是事件 table。这允许您将子 window 事件连接回父 window 并在一个地方处理它们。
例如,如果您的主框架 class 称为 MainFrame
并且您有一个 wxPanel
成员称为 m_DrawPanel
,在 MainFrame ctor 中您可以:
MainFrame::MainFrame(wxWindow* parent)
{
// Connect mouse event handlers.
m_DrawPanel->Connect(wxEVT_LEFT_DOWN,
wxMouseEventHandler(MainFrame::OnPanelLDown), NULL, this);
m_DrawPanel->Connect(wxEVT_LEFT_UP,
wxMouseEventHandler(MainFrame::OnPanelLUp), NULL, this);
m_DrawPanel->Connect(wxEVT_MOTION,
wxMouseEventHandler(MainFrame::OnPanelMotion), NULL, this);
// Connect paint and erase handlers.
m_DrawPanel->Connect(wxEVT_PAINT,
wxPaintEventHandler(MainFrame::OnPanelPaint), NULL, this);
m_DrawPanel->Connect(wxEVT_ERASE_BACKGROUND,
wxEraseEventHandler(MainFrame::OnPanelErase), NULL, this);
// Load the bitmap and set the mode to 'not currently selecting'.
m_Picture.LoadFile ("wxwidgets.png", wxBITMAP_TYPE_PNG);
m_SelectionMode = false;
}
注意:我包含了一个 wxEVT_ERASE_BACKGROUND
事件覆盖,因为否则面板往往会被清除,从而导致闪烁(这是一种简单的方法)。
3 个鼠标事件处理程序可以实现您的选择逻辑(我想这基本上就是您想要的):
void MainFrame::OnPanelLDown(wxMouseEvent& event)
{
m_SelectionMode = true;
m_SelectionRect = wxRect(event.GetPosition(), wxSize(0, 0));
}
void MainFrame::OnPanelLUp(wxMouseEvent& event)
{
m_SelectionMode = false;
// ... handle what to do with the selection here
// (selected area is defined by m_SelectionRect).
// ...
// Zero the selection rectangle for neatness (not really required).
m_SelectionRect = wxRect ();
}
void MainFrame::OnPanelMotion(wxMouseEvent& event)
{
m_SelectionRect = wxRect(m_SelectionRect.GetTopLeft(), event.GetPosition());
// Call Refresh() to trigger a paint event.
m_mainPanel->Refresh();
}
如前所述,覆盖面板的 wxEVT_ERASE_BACKGROUND
事件不执行任何操作:
void MainFrame::OnPanelErase(wxEraseEvent& event)
{
}
最后,我认为这是你在问题中真正要问的部分(我包括了其他部分以帮助你构建一个工作程序):
void MainFrame::OnPanelPaint(wxPaintEvent& event)
{
// Obtain a wxPaintDC.
wxPaintDC pdc (m_mainPanel);
// Draw our image.
pdc.DrawBitmap(m_Picture, wxPoint(0, 0));
// If the user is currently selecting (left mouse button is down)
// then draw the selection rectangle.
if (m_SelectionMode)
{
pdc.SetPen(*wxRED_PEN);
pdc.SetBrush(*wxTRANSPARENT_BRUSH);
pdc.DrawRectangle(m_SelectionRect);
}
}
这是一个绘画事件处理程序,所以首先我们需要创建一个 wxPaintDC
上下文。接下来,我们绘制位图,这确保它每次都刷新并且不会被鼠标移动、调整大小或其他 windows 拖动等损坏。最后,如果用户当前正在移动鼠标并按下左键,那么绘制选择矩形。
有许多其他方法可以实现同一目标。其中一些可能更好或更有效,但这是一种简单的工作方式,直到您更加熟悉 wxWidgets。