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。