Getting Acquainted with Document/View Framework – Simple Image Viewer

In my previous posts here and here I showed how to create a simple application which uses Document/View framework. Now I’m going to show more complex example – image viewer with scrolling and selection rectangle. As far as I can see from wxForum, implementation of selection-related functionality is some kind of complex but very useful task.

So, let’s start from simple part. The simplest task here is modification of our wxDocument-related class.


DocViewTestDocument.h

#ifndef _DOC_VIEW_TEST_DOCUMENT_H
#define _DOC_VIEW_TEST_DOCUMENT_H

#include 
#include 

class DocViewTestDocument : public wxDocument
{
	DECLARE_DYNAMIC_CLASS(DocViewTestDocument)
	wxImage m_Image;
public:
	DocViewTestDocument();

	virtual wxInputStream & LoadObject(wxInputStream& stream);

	wxImage & GetImage();
};

#endif

As you can see, we added m_Image class member for storing loaded image, accessor method GetImage() for this class member and virtual LoadObject() method which will be called automatically by document/view framework when user opens a file.
DocViewTestDocument.cpp

#include "DocViewTestDocument.h"
#include < ../src/tiff/tiffiop.h>

IMPLEMENT_DYNAMIC_CLASS(DocViewTestDocument, wxDocument)

DocViewTestDocument::DocViewTestDocument()
{
	wxLogTrace(wxTraceMask(), wxT("DocViewTestDocument::DocViewTestDocument"));
}

wxImage & DocViewTestDocument::GetImage()
{
	return m_Image;
}

wxInputStream& DocViewTestDocument::LoadObject(wxInputStream& stream)
{
	// disable tiff warnings
	TIFFSetErrorHandler(0);
	TIFFSetWarningHandler(0);
	m_Image.LoadFile(stream, wxBITMAP_TYPE_ANY);
    return stream;
}

In LoadObject() method we added libtiff-specific calls to avoid all warning messages when loading TIFF images.
Now, after we made all needed changes to our document class, let’s create a control for displaying our image.
DocViewTestCanvas.h

#ifndef _DOC_VIEW_TEST_CANVAS_H
#define _DOC_VIEW_TEST_CANVAS_H

#include 

#define DocViewTestCanvasStyle wxNO_BORDER|wxHSCROLL|wxVSCROLL|wxTAB_TRAVERSAL
#define DocViewTestCanvasName wxT("DocViewTestCanvas")

class DocViewTestView;

class DocViewTestCanvas : public wxWindow
{
	DECLARE_DYNAMIC_CLASS(DocViewTestCanvas)
	DocViewTestView * m_View;
	void Init();
public:
	DocViewTestCanvas();
	DocViewTestCanvas(wxWindow * parent, 
		wxWindowID id = wxID_ANY,
		const wxPoint & pos = wxDefaultPosition,
		const wxSize & size = wxDefaultSize,
		long style = DocViewTestCanvasStyle,
		const wxString & name = DocViewTestCanvasName);
	bool Create(wxWindow * parent, 
		wxWindowID id = wxID_ANY,
		const wxPoint & pos = wxDefaultPosition,
		const wxSize & size = wxDefaultSize,
		long style = DocViewTestCanvasStyle,
		const wxString & name = DocViewTestCanvasName);
	DECLARE_EVENT_TABLE()
};

#endif

DocViewTestCanvas.cpp

#include "DocViewTestCanvas.h"
#include "DocViewTestView.h"
#include 

IMPLEMENT_DYNAMIC_CLASS(DocViewTestCanvas, wxWindow)

BEGIN_EVENT_TABLE(DocViewTestCanvas, wxWindow)
END_EVENT_TABLE()

DocViewTestCanvas::DocViewTestCanvas()
{
	Init();
}

DocViewTestCanvas::DocViewTestCanvas(wxWindow * parent, wxWindowID id,
		const wxPoint & pos, const wxSize & size, long style, const wxString & name)
{
	Init();
	Create(parent, id, pos, size, style, name);
}

bool DocViewTestCanvas::Create(wxWindow * parent, wxWindowID id,
		const wxPoint & pos, const wxSize & size, long style, const wxString & name)
{
	bool res = wxWindow::Create(parent, id, pos, size, style, name);
	if(res)
	{
	}
	return res;
}

void DocViewTestCanvas::Init()
{
	m_View = NULL;
}

So, we created a stub control which will be used for displaying images. Now let’s add changes to our view class.
DocViewTestView.h

#ifndef _DOC_VIEW_TEST_VIEW_H
#define _DOC_VIEW_TEST_VIEW_H

#include 
#include 

class DocViewTestCanvas;

class DocViewTestView : public wxView
{
	DECLARE_DYNAMIC_CLASS(DocViewTestView)
	double m_Scale;
	wxBitmap m_ScaledBitmap;
	wxSize m_ViewOffset;
	DocViewTestCanvas * m_Canvas;
	void ReCreateScaledBitmap();
public:
	DocViewTestView();

	wxBitmap & GetScaledBitmap();

	const wxSize & GetViewOffset();
	void SetViewOffset(const wxSize & value);

	virtual void OnDraw(wxDC* dc);
	virtual void OnUpdate(wxView *sender, wxObject *hint = (wxObject *) NULL);
virtual bool OnClose(bool deleteWindow = true);
};

#endif

DocViewTestView.cpp

#include "DocViewTestView.h"
#include "DocViewTestCanvas.h"
#include "DocViewTestDocument.h"
#include "DocViewTestMainFrame.h"

IMPLEMENT_DYNAMIC_CLASS(DocViewTestView, wxView)

DocViewTestView::DocViewTestView()
: m_Scale(1.0)
{
	m_Canvas = NULL;
	DocViewTestMainFrame * mainFrame = 
		wxDynamicCast(wxTheApp->GetTopWindow(), DocViewTestMainFrame);
	if(mainFrame)
	{
		SetFrame(mainFrame);
		m_Canvas = mainFrame->m_Canvas;
		m_Canvas->SetView(this);
	}
	else
	{
		wxFAIL_MSG(_("Can't retrieve a pointer to main window"));
	}
}

void DocViewTestView::OnDraw(wxDC* dc)
{
	if(m_ScaledBitmap.IsOk())
	{
		dc->DrawBitmap(m_ScaledBitmap, m_ViewOffset.GetWidth(), m_ViewOffset.GetHeight());
	}
}

void DocViewTestView::OnUpdate(wxView *sender, wxObject *hint)
{
	ReCreateScaledBitmap();
    if (m_Canvas)
	{
		m_Canvas->AdjustScrollBars();
m_Canvas->Refresh();
	}
}

void DocViewTestView::ReCreateScaledBitmap()
{
	DocViewTestDocument * document = wxDynamicCast(GetDocument(), DocViewTestDocument);
	if(document)
	{
		wxSize scaledSize(document->GetImage().GetWidth() * m_Scale,
			document->GetImage().GetHeight() * m_Scale);
		m_ScaledBitmap = wxBitmap(document->GetImage().Scale(scaledSize.GetWidth(), scaledSize.GetHeight()));
	}
	else
	{
		m_ScaledBitmap = wxNullBitmap;
	}
}

bool DocViewTestView::OnClose(bool deleteWindow)
{
	wxLogTrace(wxTraceMask(), wxT("DocViewTestView::OnClose"));
	if (!GetDocument()->Close())
	{
        return false;
	}

	if(m_Canvas)
	{
		m_Canvas->ClearBackground();
		m_Canvas->SetView(NULL);
		m_Canvas = NULL;
	}
    
    wxFrame * frame = wxDynamicCast(GetFrame(), wxFrame);
	if(frame)
	{
		frame->SetTitle(DocViewTestMainFrameTitle);
	}
    
    SetFrame(NULL);
    Activate(false);
    return true;
}

wxBitmap & DocViewTestView::GetScaledBitmap()
{
	return m_ScaledBitmap;
}

const wxSize & DocViewTestView::GetViewOffset()
{
	return m_ViewOffset;
}

void DocViewTestView::SetViewOffset(const wxSize & value)
{
	m_ViewOffset = value;
}

As you can see in the code above, some new method of DocViewTestCanvas class are used. These are SetView() and AdjustScrollBars() methods. We’ll need to add these and some other methods to our canvas control. But now let’s see what changes we made to our view class. We added new members: m_Scale variable which should be used as zooming level, m_ScaledBitmap variable which will store zoomed (or scaled) image. As you can see, document class stores image as wxImage and view class stores zoomed image as wxBitmap. The reason of this is that wxDC uses wxBitmap for DrawBitmap() method and there will be no need to convert image to wxBitmap each time when we need to draw it. m_ViewOffset will contain scrolling offset (actually a scrollbar position of our canvas control). m_Canvas will contain pointer to canvas control obtained from main window. This will allow not to obtain this pointer each time by casting GetFrame() to DocViewTestMainFrame. DocViewTestView::ReCreateScaledBitmap() recreates the zoomed image according to zoom level.
OK, now let’s add missing methods to our canvas control:
DocViewTestCanvas.h

#ifndef _DOC_VIEW_TEST_CANVAS_H
#define _DOC_VIEW_TEST_CANVAS_H

#include 

#define DocViewTestCanvasStyle wxNO_BORDER|wxHSCROLL|wxVSCROLL|wxTAB_TRAVERSAL
#define DocViewTestCanvasName wxT("DocViewTestCanvas")

class DocViewTestView;

class DocViewTestCanvas : public wxWindow
{
	DECLARE_DYNAMIC_CLASS(DocViewTestCanvas)
	
	wxPoint m_TR;
	wxPoint m_BL;

	DocViewTestView * m_View;
	wxRect m_Selection;
	static wxSize ScrollingIncrement;

	void Init();
public:
	...
	DocViewTestView * GetView();
	void SetView(DocViewTestView * value);

	const wxRect & GetSelection();

	wxSize GetCurrentSize();
	wxPoint GetScrollPosition();

	void Scroll(wxPoint pt);
	void Scroll(int x, int y);

	void FixViewOffset();
	void AdjustScrollBars();

	wxRect GetImageDisplayRect(const wxPoint & scrollPos);
	wxPoint ClientToImage(const wxPoint & pos);

	bool AutoScroll(wxPoint currentPos, wxPoint scrollPos);

	DECLARE_EVENT_TABLE()
	void OnScrollLineUp( wxScrollWinEvent& event );
    void OnScrollLineDown( wxScrollWinEvent& event );
    void OnScrollPageUp( wxScrollWinEvent& event );
    void OnScrollPageDown( wxScrollWinEvent& event );
    void OnScrollThumbtrack( wxScrollWinEvent& event );
    void OnScrollThumbRelease( wxScrollWinEvent& event );
    void OnPaint( wxPaintEvent& event );
    void OnIdle( wxIdleEvent& event );
    void OnEraseBackground( wxEraseEvent& event );
    void OnLeftDown( wxMouseEvent& event );
    void OnLeftUp( wxMouseEvent& event );
    void OnMotion( wxMouseEvent& event );
};

#endif

DocViewTestCanvas.cpp

#include "DocViewTestCanvas.h"
#include "DocViewTestView.h"
#include 

IMPLEMENT_DYNAMIC_CLASS(DocViewTestCanvas, wxWindow)

wxSize DocViewTestCanvas::ScrollingIncrement = wxSize(10,10);

BEGIN_EVENT_TABLE(DocViewTestCanvas, wxWindow)
	EVT_SCROLLWIN_LINEUP( DocViewTestCanvas::OnScrollLineUp )
    EVT_SCROLLWIN_LINEDOWN( DocViewTestCanvas::OnScrollLineDown )
    EVT_SCROLLWIN_PAGEUP( DocViewTestCanvas::OnScrollPageUp )
    EVT_SCROLLWIN_PAGEDOWN( DocViewTestCanvas::OnScrollPageDown )
    EVT_SCROLLWIN_THUMBTRACK( DocViewTestCanvas::OnScrollThumbtrack )
    EVT_SCROLLWIN_THUMBRELEASE( DocViewTestCanvas::OnScrollThumbRelease )
    EVT_PAINT( DocViewTestCanvas::OnPaint )
    EVT_IDLE( DocViewTestCanvas::OnIdle )
    EVT_ERASE_BACKGROUND( DocViewTestCanvas::OnEraseBackground )
    EVT_LEFT_DOWN( DocViewTestCanvas::OnLeftDown )
    EVT_LEFT_UP( DocViewTestCanvas::OnLeftUp )
    EVT_MOTION( DocViewTestCanvas::OnMotion )
END_EVENT_TABLE()

...

DocViewTestView * DocViewTestCanvas::GetView()
{
	return m_View;
}

void DocViewTestCanvas::SetView(DocViewTestView * value)
{
	m_View = value;
}

const wxRect & DocViewTestCanvas::GetSelection()
{
	return m_Selection;
}

wxPoint DocViewTestCanvas::GetScrollPosition()
{
	return wxPoint(GetScrollPos(wxHORIZONTAL), GetScrollPos(wxVERTICAL));
}

void DocViewTestCanvas::Scroll(wxPoint pt)
{
	if(pt.x >= 0)
		SetScrollPos(wxHORIZONTAL, pt.x);
	if(pt.y >= 0)
		SetScrollPos(wxVERTICAL, pt.y);
	Refresh();
}

void DocViewTestCanvas::Scroll(int x, int y) 
{ 
	Scroll(wxPoint(x, y)); 
}

wxSize DocViewTestCanvas::GetCurrentSize()
{
	if(!m_View)
	{
		return wxSize(0,0);
	}
	return wxSize(m_View->GetScaledBitmap().GetWidth(), m_View->GetScaledBitmap().GetHeight());
}

void DocViewTestCanvas::AdjustScrollBars()
{
	// image sampled size
	wxSize imageSize = GetCurrentSize();
	// old scroll position
	wxPoint pt = GetScrollPosition();
	SetScrollbar(wxHORIZONTAL, 0, GetClientSize().x, imageSize.x);
	SetScrollbar(wxVERTICAL, 0, GetClientSize().y, imageSize.y);
	Scroll(pt.x, pt.y);
}

void DocViewTestCanvas::FixViewOffset()
{
	if(m_View)
	{
		wxPoint pt = GetScrollPosition();
		wxRect displayRect = GetImageDisplayRect(pt);
		wxSize offset(displayRect.GetPosition().x, displayRect.GetPosition().y);
		m_View->SetViewOffset(offset);
	}
}

wxRect DocViewTestCanvas::GetImageDisplayRect(const wxPoint & scrollPos)
{
	wxSize currentSize = GetCurrentSize();
	// calculate actual image position if it is centered
	wxPoint ptTest(
		(GetClientSize().x - currentSize.GetWidth()) < = 0 ? 
			-scrollPos.x : 
			((GetClientSize().GetWidth() - currentSize.GetWidth()) * 0.5f), 
		(GetClientSize().y - currentSize.GetHeight()) <= 0 ? 
			-scrollPos.y : 
			((GetClientSize().GetHeight() - currentSize.GetHeight()) * 0.5f));
	// calculate actual image diaply rectangle if centered
	return wxRect(ptTest, currentSize);
}

void DocViewTestCanvas::OnIdle( wxIdleEvent& event )
{
	do
	{
		if(!HasCapture()) break;

		// get scroll position
		wxPoint scrollPos = GetScrollPosition();
		// get mouse in client coordinates
		wxPoint currentPos = ScreenToClient(wxGetMousePosition());

		// auto scroll
		// check current drag position and update scroll regularly
		if(AutoScroll(currentPos, scrollPos))
		{
			event.RequestMore();
		}
		FixViewOffset();
	}
	while(false);
}

void DocViewTestCanvas::OnLeftDown( wxMouseEvent& event )
{
	SetFocus();
	wxRect imageRect = GetImageDisplayRect(GetScrollPosition());
	do
	{
		if(!m_View) break;
		wxPoint cursorPosOnImage = ClientToImage(event.GetPosition());
		
		if(!imageRect.Contains(event.GetPosition())) break;
		CaptureMouse();
		Refresh();
		m_TR = m_BL = event.GetPosition();
		m_Selection.SetPosition(event.GetPosition());
		m_Selection.SetSize(wxSize(0,0));
	}
	while(false);
}

void DocViewTestCanvas::OnLeftUp( wxMouseEvent& event )
{
	if(HasCapture())
	{
		ReleaseMouse();
		Refresh();

		wxMessageBox(wxString::Format(
			_("Selection rectangle\r\nScreen coordinates:\r\nPosition=(%i,%i);\r\nSize=(%i,%i)\r\nLogical coordinates:\r\nPosition=(%i,%i);\r\nSize=(%i,%i)"),
			m_Selection.GetLeft(), m_Selection.GetTop(),
			m_Selection.GetWidth(), m_Selection.GetHeight(),
			ClientToImage(m_Selection.GetTopLeft()).x,
			ClientToImage(m_Selection.GetTopLeft()).y,
			m_Selection.GetWidth(), m_Selection.GetHeight()));

		m_TR = m_BL = wxPoint(0,0);
		m_Selection.SetPosition(m_TR);
		m_Selection.SetSize(wxSize(0,0));
	}
}

void DocViewTestCanvas::OnMotion( wxMouseEvent& event )
{
	if(HasCapture())
	{
		// current point
		wxPoint currentPos = event.GetPosition();
		// get scroll position
		wxPoint scrollPos = GetScrollPosition();
		AutoScroll(currentPos, scrollPos);
		FixViewOffset();
		Refresh();
	}
}

bool DocViewTestCanvas::AutoScroll(wxPoint currentPos, wxPoint scrollPos)
{
	bool res = false;

	// If mouse is captured set rect coords if inside the image and autoscroll if posible
	// get image display rectangle
	wxRect displayRect = GetImageDisplayRect(scrollPos);

	wxSize oldSelection(m_TR.x-m_BL.x, m_TR.y-m_BL.y);

	// check if the current drag position is inside the image - do not allow to draw rectangle out of the image
	m_TR.x = 
		wxMin(wxMax(currentPos.x, displayRect.GetLeft()), displayRect.GetRight());
	m_TR.y = 
		wxMin(wxMax(currentPos.y, displayRect.GetTop()), displayRect.GetBottom());
	
	// Check current drag position and update scroll regularly
	if(currentPos.x <= 0) 
	{
		Scroll(wxMax(scrollPos.x - DocViewTestCanvas::ScrollingIncrement.GetWidth(), 0), -1);
		m_BL.x += (scrollPos.x - GetScrollPosition().x);
		res = true;
	}
	if(currentPos.y <= 0) 
	{
		Scroll(-1,wxMax(scrollPos.y - DocViewTestCanvas::ScrollingIncrement.GetHeight(), 0));
		m_BL.y += (scrollPos.y - GetScrollPosition().y);
		res = true;
	}
	if(currentPos.x >= GetClientSize().GetWidth()) 
	{
		Scroll(scrollPos.x + DocViewTestCanvas::ScrollingIncrement.GetWidth(), -1);
		m_BL.x -= (GetScrollPosition().x - scrollPos.x);
		res = true;
	}
	if(currentPos.y >= GetClientSize().y) 
	{
		Scroll(-1, scrollPos.y + DocViewTestCanvas::ScrollingIncrement.GetHeight());
		m_BL.y -= (GetScrollPosition().y - scrollPos.y);
		res = true;
	}
	m_Selection = wxRect(wxMin(m_TR.x, m_BL.x), wxMin(m_TR.y, m_BL.y),
		abs(m_TR.x-m_BL.x), abs(m_TR.y-m_BL.y));
	return res;
}

void DocViewTestCanvas::OnScrollLineUp( wxScrollWinEvent& event )
{
	int increment = (event.GetOrientation() == wxHORIZONTAL ?
		DocViewTestCanvas::ScrollingIncrement.GetWidth() :
		DocViewTestCanvas::ScrollingIncrement.GetHeight());
	SetScrollPos(event.GetOrientation(), GetScrollPos(event.GetOrientation()) - increment);
	FixViewOffset();
	Refresh();
}

void DocViewTestCanvas::OnScrollLineDown( wxScrollWinEvent& event )
{
	int increment = (event.GetOrientation() == wxHORIZONTAL ?
		DocViewTestCanvas::ScrollingIncrement.GetWidth() :
		DocViewTestCanvas::ScrollingIncrement.GetHeight());
	SetScrollPos(event.GetOrientation(), GetScrollPos(event.GetOrientation()) + increment);
	FixViewOffset();
	Refresh();
}

void DocViewTestCanvas::OnScrollPageUp( wxScrollWinEvent& event )
{
	SetScrollPos(event.GetOrientation(), 
		GetScrollPos(event.GetOrientation()) - GetScrollThumb(event.GetOrientation()));
	FixViewOffset();
	Refresh();
}

void DocViewTestCanvas::OnScrollPageDown( wxScrollWinEvent& event )
{
	SetScrollPos(event.GetOrientation(), 
		GetScrollPos(event.GetOrientation()) + GetScrollThumb(event.GetOrientation()));
	FixViewOffset();
	Refresh();
}

void DocViewTestCanvas::OnScrollThumbtrack( wxScrollWinEvent& event )
{
	SetScrollPos(event.GetOrientation(), event.GetPosition());
	FixViewOffset();
	Refresh();
}

void DocViewTestCanvas::OnScrollThumbRelease( wxScrollWinEvent& event )
{
	FixViewOffset();
	Refresh();
}

void DocViewTestCanvas::OnPaint( wxPaintEvent& event )
{
	wxBufferedPaintDC dc(this);
	dc.SetBackground(wxBrush(GetBackgroundColour()));
	dc.Clear();
	if(m_View)
	{
		m_View->OnDraw(&dc);
		if(HasCapture() && !m_Selection.IsEmpty())
		{
			dc.SetBrush(*wxTRANSPARENT_BRUSH);
			dc.SetLogicalFunction(wxXOR);
			dc.SetPen(wxPen(*wxWHITE, 3));
			dc.DrawRoundedRectangle(m_Selection.GetPosition(), m_Selection.GetSize(), 1.0f);
		}
	}
}

wxPoint DocViewTestCanvas::ClientToImage(const wxPoint & pos)
{
	wxPoint scrollPos = GetScrollPosition();
	wxRect imageRect = GetImageDisplayRect(scrollPos);
	return wxPoint(pos.x - imageRect.GetLeft(), pos.y - imageRect.GetTop());
}

void DocViewTestCanvas::OnEraseBackground(wxEraseEvent & event)
{
}

As you can see, the amount of changes is large enough. So, let’s see what each method does.

  • GetView() and SetView() are accessor and mutator methods for m_View variable which contains pointer to DocViewTestView object associated with our control.
  • m_Selection class member contains coordinates of selection rectangle and GetSelection() is accessor method for this class member.
  • GetScrollPosition() method returns the scrolling offset as wxPoint object.
  • Scroll() method scrolls the control to specified offset.
  • GetCurrentSize() method returns the size of scrolling area obtained from DocViewTestView object associated with control.
  • FixViewOffset() method updates m_ViewOffset class member of DocViewTestView object associated with control with value obtained from current scrolling offset.
  • GetImageDisplayRect () method returns the coordinates of currently visible area.
  • OnLeftDown() method is wxEVT_LEFT_DOWN event which occurs when user presses left mouse button on the control.
  • OnLeftUp() method is wxEVT_LEFT_UP event which occurs when user releases left mouse button.
  • OnMotion() method is event handler for wxEVT_MOTION event which occurs when user moves mouse pointer over the control.
  • OnScroll*() methods are avant handlers for scroll events.
  • OnPaint() method is wxEVT_PAINT handler and calls OnDraw() method from DocViewTestView object associated with control and then draws selection rectangle of needed.
  • ClientToImage() method converts client coordinates to logical coordinates on zoomed image.

Very important role here plays OnIdle() method which is an event handler for wxEVT_IDLE event and makes the control to scroll smoothly (in fact, it causes the control to refresh more often and that’s why scrolling looks more smoothly). AutoScroll() method performs scrolling and recalculates the coordinates of selection area.

Selection area (m_Selection) is calculated from m_TR and m_BR variables which contain the coordinates of top-right and bottom-left corners of selection area.
Now, after we added all needed changes to document, view and canvas classes, we need to add the canvas to our main frame.
DocViewTestMainFrame.h

#ifndef _DOC_VIEW_TEST_MAINFRAME_H
#define _DOC_VIEW_TEST_MAINFRAME_H

#include 
#include 
#include 

#define DocViewTestMainFrameTitle _("DocView Test")

class DocViewTestCanvas;

class DocViewTestMainFrame : public wxDocParentFrame
{
	DECLARE_DYNAMIC_CLASS(DocViewTestMainFrame)

	wxAuiManager m_AuiManager;

	DocViewTestCanvas * m_Canvas;

	void CreateControls();
	wxMenuBar * CreateMenuBar();
public:
	DocViewTestMainFrame();
	DocViewTestMainFrame(wxDocManager * docManager, wxFrame * parent, 
		wxWindowID id = wxID_ANY, 
		const wxString & title = DocViewTestMainFrameTitle);
	~DocViewTestMainFrame();
	bool Create(wxDocManager * docManager, wxFrame * parent, 
		wxWindowID id = wxID_ANY, 
		const wxString & title = DocViewTestMainFrameTitle);

	DocViewTestCanvas * GetCanvas();

	DECLARE_EVENT_TABLE()
	void OnExit(wxCommandEvent & event);
};

#endif

DocViewTestMainFrame.cpp

#include "DocViewTestCanvas.h"
...
void DocViewTestMainFrame::CreateControls()
{
	SetMenuBar(CreateMenuBar());
	m_AuiManager.SetManagedWindow(this);

	m_Canvas = new DocViewTestCanvas(this);

	m_AuiManager.AddPane(m_Canvas, 
		wxAuiPaneInfo().CenterPane().Name(_("Canvas")));

	m_AuiManager.Update();
}

wxMenuBar * DocViewTestMainFrame::CreateMenuBar()
{
	wxMenuBar * result = new wxMenuBar;

	wxMenu * fileMenu = new wxMenu;
	//fileMenu->Append(wxID_NEW, _("New\tCtrl+N"));
	fileMenu->Append(wxID_OPEN, _("Open\tCtrl+O"));
	fileMenu->AppendSeparator();
	fileMenu->Append(wxID_SAVE, _("Save\tCtrl+S"));
	fileMenu->Append(wxID_SAVEAS, _("Save as..."));
	fileMenu->AppendSeparator();
	fileMenu->Append(wxID_EXIT, _("Exit\tAlt+F4"));

	wxMenu * helpMenu = new wxMenu;
	helpMenu->Append(wxID_ABOUT, _("About..."));

	result->Append(fileMenu, _("File"));
	result->Append(helpMenu, _("Help"));

	return result;
}

I commented the creation of wxID_NEW menu item because our application is image viewer and should load only existing files but not create new ones.
Now we can compile the source code and after we execute the application and load some image file, we’ll see something like this:
Simple Image Viewer with Selection Rectangle
After we select some area and release the mouse button, message box with coordinates of selection rectangle will appear:
Simple Image Viewer in wxWidgets - Selection Rectangle
That’s all.

You can download the source code of sample application described above.

Leave a Reply

Your email address will not be published. Required fields are marked *

Please leave these two fields as-is: