Closed. This question needs to be more focused. It is not currently accepting answers.
Want to improve this question? Update the question so it focuses on one problem only by editing this post.
Closed 3 years ago.
Improve this question
I made a little App using SDL to show some sprite animations, and it works really fine. It's important that the rendering is as smooth as possible.
Since now I need some GUI, I decided to use wxWidgets 3 to create kind of the same application but where I can now also add some GUI elements like menus, modals, etc.
After diving into the wxWidget's wiki, I found there are different ways to draw into a window.
For example:
wxClientDC
wxPaintDC
wxGLCanvas
I also found other kind of classes like the "Graphics Context classes" which I'm still not sure if they could be useful for what I need.
My application already uses an internal buffer which just needs to be copied.
So my question is What's the most appropriate way to do this on wxWidgets?
And, how much performance/overhead does that solution impose?
What would be the difference in performance/overhead between the different methods wxWidgets has for drawing pixels inside a window? Why one uses OnPaint events while others don't.
Here's one way to do this:
// For compilers that support precompilation, includes "wx/wx.h".
#include "wx/wxprec.h"
#ifdef __BORLANDC__
#pragma hdrstop
#endif
// for all others, include the necessary headers (this file is usually all you
// need because it includes almost all "standard" wxWidgets headers)
#ifndef WX_PRECOMP
#include "wx/wx.h"
#endif
#include <wx/timer.h>
#include <wx/dcclient.h>
#include <wx/rawbmp.h>
///////////// Declarations
class MyFrame : public wxFrame
{
public:
MyFrame( wxWindow* parent, int id = wxID_ANY, wxString title = "Demo",
wxPoint pos = wxDefaultPosition, wxSize size = wxDefaultSize,
int style = wxDEFAULT_FRAME_STYLE|wxTAB_TRAVERSAL );
~MyFrame();
private:
// Event Handlers
void OnPaint( wxPaintEvent& event );
void OnTimer(wxTimerEvent& event);
// Helper function
void RebuildBufferAndRefresh();
// Private data
wxWindow* m_renderSurface;
int m_width;
int m_height;
wxBitmap m_bitmapBuffer;
wxTimer m_timer;
int m_curRGB;
unsigned char* m_pixelData;
};
class MyApp : public wxApp
{
public:
virtual bool OnInit() wxOVERRIDE;
};
wxDECLARE_APP(MyApp);
///////////// Implementation
MyFrame::MyFrame( wxWindow* parent, int id, wxString title, wxPoint pos,
wxSize size, int style )
:wxFrame( parent, id, title, pos, size, style )
{
m_width=100;
m_height=100;
m_pixelData = new unsigned char[3*m_width*m_height];
m_renderSurface = new wxWindow(this, wxID_ANY, wxDefaultPosition,
wxSize(m_width,m_height));
m_renderSurface->SetBackgroundStyle(wxBG_STYLE_PAINT);
m_renderSurface->Bind(wxEVT_PAINT,&MyFrame::OnPaint,this);
wxBoxSizer* bSizer = new wxBoxSizer( wxVERTICAL );
bSizer->Add( m_renderSurface, 0 );
this->SetSizer( bSizer );
Layout();
m_timer.SetOwner(this);
m_timer.Start(17);
this->Bind(wxEVT_TIMER,&MyFrame::OnTimer,this);
m_curRGB=0;
}
MyFrame::~MyFrame()
{
m_timer.Stop();
delete[] m_pixelData;
}
void MyFrame::OnPaint( wxPaintEvent& event )
{
wxPaintDC dc(m_renderSurface);
if(m_bitmapBuffer.IsOk())
{
dc.DrawBitmap(m_bitmapBuffer,0,0);
}
}
void MyFrame::OnTimer(wxTimerEvent& event)
{
RebuildBufferAndRefresh();
}
void MyFrame::RebuildBufferAndRefresh()
{
// Build the pixel buffer here, for this simple example just set all
// pixels to the same value and then increment that value.
for ( int y = 0; y < m_height; ++y )
{
for ( int x = 0; x < m_width; ++x )
{
m_pixelData[3*y*m_width+3*x]=m_curRGB;
m_pixelData[3*y*m_width+3*x+1]=m_curRGB;
m_pixelData[3*y*m_width+3*x+2]=m_curRGB;
}
}
++m_curRGB;
if(m_curRGB>255)
{
m_curRGB=0;
}
// Now transfer the pixel data into a wxBitmap
wxBitmap b(m_width,m_height,24);
wxNativePixelData data(b);
if ( !data )
{
// ... raw access to bitmap data unavailable, do something else ...
return;
}
wxNativePixelData::Iterator p(data);
int curPixelDataLoc = 0;
for ( int y = 0; y < m_height; ++y )
{
wxNativePixelData::Iterator rowStart = p;
for ( int x = 0; x < m_width; ++x, ++p )
{
p.Red() = m_pixelData[curPixelDataLoc++];
p.Green() = m_pixelData[curPixelDataLoc++];
p.Blue() = m_pixelData[curPixelDataLoc++];
}
p = rowStart;
p.OffsetY(data, 1);
}
m_bitmapBuffer=b;
m_renderSurface->Refresh();
m_renderSurface->Update();
}
bool MyApp::OnInit()
{
MyFrame* frame = new MyFrame(NULL);
frame->Show();
return true;
}
wxIMPLEMENT_APP(MyApp);
The basic idea here is copy your buffer into a wxBitmap and then draw that bitmap in the paint handler. The paint handler is then triggered by the calls to Refresh and Update. The reason for this is that system will also call the paint method for various reasons such as the mouse cursor running over the render surface. By doing it this way, there is a bitmap that can be drawn for both your and the system's calls to refresh the surface.
In the simple example posted above, I'm just using a simple timer to call the RebuildBufferAndRefresh method approximately 60 times a second. Your application will probably have a better way to to determine when a refresh is needed and you can use that way to call RebuildBufferAndRefresh instead.
Obviously most of the work is done in the RebuildBufferAndRefresh method. That method is broken into 3 parts. The first part builds the internal buffer. The second part copies that buffer into a wxBitmap for the reasons stated above. The third part just calls refresh and update to force the render surface to be redrawn.
There may be better ways to do this, but I think this is the most straight forward wxWidgets-ey to do constantly render a buffer of pixel data to a window.
As to how much overhead this approach requires, it's basically a memcopy from your buffer to a bitmap, but the nested loop will be less efficient than a real memcopy. After that the paint handler does nothing but draw the bitmap. That will probably be accomplished by a blit to the system's backbuffer and should be quite fast. I hope that helps.
Related
Hi i have a borderless wxFrame but it is not resizable. I want to make the frame resizable just like when it is resizable with a border. I am using wxRESIZE_BORDER and wxBORDER_NONE style properties. I know i have to catch the mouse event manually and then implement it but am not able do that. I have also looked at the shaped sample directory but there also there is no "border-less resizable frame". I am using Ubuntu 18.04 and wxWidgets 3.1.5. Is this doable/possible in wxWidgets and is there any example of the same?
Here's an example of the basic machinery needed to allow a frame to be resized without using a resize border. It basically shows Igor's comments above.
#include "wx/wx.h"
#include <wx/timer.h>
#include <wx/dcbuffer.h>
#include <wx/version.h>
#if __WXGTK__
#define BORDERLESS_FRAME_STYLE (wxCAPTION | wxCLOSE_BOX)
#else
#define BORDERLESS_FRAME_STYLE (wxCAPTION | wxCLOSE_BOX | wxBORDER_NONE)
#endif // __WXGTK__
class MyFrame: public wxFrame
{
public:
MyFrame();
private:
enum ResizeMode
{
ResizeNone,
ResizeFromTop,
ResizeFromUpperLeft,
ResizeFromLeft,
ResizeFromLowerLeft,
ResizeFromBottom,
ResizeFromLowerRight,
ResizeFromRight,
ResizeFromUpperRight
};
// General event handlers
void OnBgPanelPaint(wxPaintEvent&);
// Event handlers for resizing
void OnLeftDownForResizeFromLowerRight(wxMouseEvent&);
void OnLeftDownForResizeFromLowerLeft(wxMouseEvent&);
void OnLeftUp(wxMouseEvent&);
void OnMouseCaptureLost(wxMouseCaptureLostEvent&);
void OnResizeTimer(wxTimerEvent&);
// Resizing helper functions
void DoDragBasedResize();
void StartResize(wxWindow*, const wxPoint&);
void CompleteResize(bool doFinalResize = false);
// Data and objects needed for resizing.
wxTimer m_resizeTimer;
int m_resizeFrequency;
int m_resizeAreaLength;
ResizeMode m_resizeMode;
wxPoint m_resizeStartPoint;
wxSize m_initialFrameSize;
wxPoint m_initialFramePosition;
wxWindow* m_clickToResizeFromLowerRightWindow;
wxWindow* m_clickToResizeFromLowerLeftWindow;
wxWindow* m_curResizeWindow;
// GUI controls
wxPanel* m_bgPanel;
};
MyFrame::MyFrame():wxFrame(NULL, wxID_ANY, "Resizing Demo", wxDefaultPosition,
wxSize(400, 300), BORDERLESS_FRAME_STYLE)
{
// Set up the UI.
m_bgPanel = new wxPanel(this, wxID_ANY, wxDefaultPosition, wxDefaultSize,
wxTAB_TRAVERSAL|wxFULL_REPAINT_ON_RESIZE);
m_bgPanel->SetBackgroundStyle(wxBG_STYLE_PAINT );
m_bgPanel->Bind(wxEVT_PAINT, &MyFrame::OnBgPanelPaint, this);
wxSizer* mainSizer = new wxBoxSizer(wxVERTICAL);
mainSizer->Add(m_bgPanel, wxSizerFlags(1).Expand());
SetSizer(mainSizer);
Layout();
// Initialize the data needed for resizing.
m_resizeMode = ResizeNone;
#if wxCHECK_VERSION(3,1,0)
m_resizeAreaLength = FromDIP(20);
#else
m_resizeAreaLength = 20;
#endif // wxCHECK_VERSION
m_resizeTimer.Bind(wxEVT_TIMER, &MyFrame::OnResizeTimer, this);
m_resizeFrequency = 50;
m_curResizeWindow = NULL;
// Set window and event handlers for resizing from lower right.
m_clickToResizeFromLowerRightWindow = m_bgPanel;
m_clickToResizeFromLowerRightWindow->Bind(wxEVT_LEFT_DOWN,
&MyFrame::OnLeftDownForResizeFromLowerRight, this);
m_clickToResizeFromLowerRightWindow->Bind(wxEVT_LEFT_UP,
&MyFrame::OnLeftUp, this);
m_clickToResizeFromLowerRightWindow->Bind(wxEVT_MOUSE_CAPTURE_LOST,
&MyFrame::OnMouseCaptureLost, this);
// Set window and event handlers for resizing from lower left.
m_clickToResizeFromLowerLeftWindow = m_bgPanel;
m_clickToResizeFromLowerLeftWindow->Bind(wxEVT_LEFT_DOWN,
&MyFrame::OnLeftDownForResizeFromLowerLeft, this);
m_clickToResizeFromLowerLeftWindow->Bind(wxEVT_LEFT_UP,
&MyFrame::OnLeftUp, this);
m_clickToResizeFromLowerLeftWindow->Bind(wxEVT_MOUSE_CAPTURE_LOST,
&MyFrame::OnMouseCaptureLost, this);
}
void MyFrame::OnLeftDownForResizeFromLowerLeft(wxMouseEvent& event)
{
wxPoint p = event.GetPosition();
wxSize sz = m_clickToResizeFromLowerLeftWindow->GetClientSize();
// Check if the click is in the lower left of the window.
if ( p.x < m_resizeAreaLength &&
sz.GetHeight() - p.y < m_resizeAreaLength )
{
StartResize(m_clickToResizeFromLowerLeftWindow, p);
m_resizeMode = ResizeFromLowerLeft;
SetTitle("Resize From lower left in progress...");
SetCursor(wxCursor(wxCURSOR_SIZENESW));
}
else
{
event.Skip();
}
}
void MyFrame::OnLeftDownForResizeFromLowerRight(wxMouseEvent& event)
{
wxPoint p = event.GetPosition();
wxSize sz = m_clickToResizeFromLowerRightWindow->GetClientSize();
// Check if the click is in the lower right of the window.
if ( sz.GetWidth() - p.x < m_resizeAreaLength &&
sz.GetHeight() - p.y < m_resizeAreaLength )
{
StartResize(m_clickToResizeFromLowerRightWindow, p);
m_resizeMode = ResizeFromLowerRight;
SetTitle("Resize from lower right in progress...");
SetCursor(wxCursor(wxCURSOR_SIZENWSE));
}
else
{
event.Skip();
}
}
void MyFrame::OnLeftUp(wxMouseEvent& event)
{
if ( m_resizeMode != ResizeNone )
{
CompleteResize(true);
}
else
{
event.Skip();
}
}
void MyFrame::OnMouseCaptureLost(wxMouseCaptureLostEvent&)
{
if ( m_resizeMode != ResizeNone )
{
CompleteResize(false);
}
}
void MyFrame::OnResizeTimer(wxTimerEvent&)
{
DoDragBasedResize();
}
void MyFrame::DoDragBasedResize()
{
wxMouseState ms = ::wxGetMouseState();
wxPoint curMousePsn = ms.GetPosition();
wxPoint dragVector = curMousePsn - m_resizeStartPoint;
wxSize newSize(m_initialFrameSize);
wxPoint newPsn(m_initialFramePosition);
if ( m_resizeMode == ResizeFromLowerRight )
{
newSize = wxSize(m_initialFrameSize.GetWidth() + dragVector.x,
m_initialFrameSize.GetHeight() + dragVector.y);
}
else if ( m_resizeMode == ResizeFromLowerLeft )
{
newSize = wxSize(m_initialFrameSize.GetWidth() - dragVector.x,
m_initialFrameSize.GetHeight() + dragVector.y);
newPsn = wxPoint(m_initialFramePosition.x + dragVector.x,
m_initialFramePosition.y);
}
SetSize(newPsn.x, newPsn.y, newSize.GetWidth(), newSize.GetHeight());
}
void MyFrame::StartResize(wxWindow* win, const wxPoint& p)
{
m_curResizeWindow = win;
m_resizeTimer.Start(m_resizeFrequency);
m_resizeStartPoint = m_curResizeWindow->ClientToScreen(p);
m_curResizeWindow->CaptureMouse();
m_initialFramePosition = GetPosition();
m_initialFrameSize = GetSize();
}
void MyFrame::CompleteResize(bool doFinalResize)
{
if ( doFinalResize )
{
DoDragBasedResize();
}
m_resizeTimer.Stop();
m_resizeMode = ResizeNone;
SetTitle("Resize complete");
SetCursor(wxCursor(wxCURSOR_ARROW));
if ( m_curResizeWindow && m_curResizeWindow->HasCapture() )
{
m_curResizeWindow->ReleaseMouse();
}
m_curResizeWindow = NULL;
}
void MyFrame::OnBgPanelPaint(wxPaintEvent&)
{
wxAutoBufferedPaintDC dc(m_bgPanel);
dc.Clear();
wxPen pen(*wxRED,5);
dc.SetPen(pen);
// Draw some red marks to indicate the lower right resize area
wxPoint lowerRight = m_bgPanel->GetClientRect().GetBottomRight();
dc.DrawLine(lowerRight - wxPoint(0,m_resizeAreaLength), lowerRight);
dc.DrawLine(lowerRight, lowerRight - wxPoint(m_resizeAreaLength,0));
// Draw some red marks to indicate the lower left resize area
wxPoint lowerLeft = m_bgPanel->GetClientRect().GetBottomLeft();
dc.DrawLine(lowerLeft - wxPoint(0,m_resizeAreaLength), lowerLeft);
dc.DrawLine(lowerLeft, lowerLeft + wxPoint(m_resizeAreaLength,0));
}
class MyApp : public wxApp
{
public:
virtual bool OnInit()
{
MyFrame* frame = new MyFrame();
frame->Show();
return true;
}
};
wxIMPLEMENT_APP(MyApp);
This only shows resizing if the click is in the lower right corner of a panel covering the client area of the frame. I tried to add enough generality that if you have another control covering the lower right, you should be able to set that control to be m_clickToResizeWindow in the code above.
There is one case where that might not work however. I could be wrong, but I think some native controls completely consume mouse clicks and won't even generate a wxMouseEvent. In this case, resizing won't be possible if such a control is in the lower right.
There are some parameters that can be changed to modify the resizing behavior. The m_resizeAreaLength determines how close to the edge a click can be to start the resizing process. I've set this to 20 DIPs. The m_resizeFrequency determines how frequently the size is updated during a resize operation. I've set this to 50ms. A smaller value will provide smoother updates. In this example, I drew some red marks on the lower right to indicate the resizing area. This is completely unnecessary and can be removed.
This example only shows resizing based on a click on the lower right. However it shouldn't be too hard to modify the code to allow only horizontal resizing based on a click on the right edge or vertical resizing based on a click on the bottom edge. However, it might get complicated if there are multiple controls covering the left or bottom edges. This system only works well if there is one control covering those edges.
edit: I've updated the code to show resizing from both the lower left and lower right while keeping the rest of the frame in the appropriate position.
This can be further expanded to allow for resizing from other edges or corners. To do so it will be necessary to:
add a pointer for the window that will be used to receive the click starting the resize.
add a left down handler for that window to start the process.
Bind the new left down handler, the existing left up handler, and the existing capture lost handler for the window.
add to the DoDragBasedResize method setting a new size and position for the frame appropriate for the type of resizing being done.
I am trying to make a basic GUI using wxWidgets and wxFormBuilber to display a chess gameboard and chess pieces from a sprite sheet. I would really appreciate some sample code for:
uploading an image from a file
blitting an image at a specific location
Here's a simplest example I can think of. This uses this set of pieces from wikipedia (licensed under the Creative Commons license). This example only draws the black pieces, but you should be able to figure out how to draw the white pieces with a few simple modifications.
// For compilers that support precompilation, includes "wx/wx.h".
#include "wx/wxprec.h"
#ifdef __BORLANDC__
#pragma hdrstop
#endif
// for all others, include the necessary headers (this file is usually all you
// need because it includes almost all "standard" wxWidgets headers)
#ifndef WX_PRECOMP
#include "wx/wx.h"
#endif
class MyFrame: public wxFrame
{
public:
MyFrame();
private:
void OnPaint(wxPaintEvent& event);
wxPanel* m_board;
wxBitmap m_blackRook;
wxBitmap m_blackKnight;
wxBitmap m_blackBishop;
wxBitmap m_blackQueen;
wxBitmap m_blackKing;
wxBitmap m_blackPawn;
};
MyFrame::MyFrame()
:wxFrame(NULL, wxID_ANY, "Chess", wxDefaultPosition, wxSize(1000, 1000))
{
m_board = new wxPanel(this, wxID_ANY);
m_board->Bind(wxEVT_PAINT, &MyFrame::OnPaint, this);
// Load the image.
wxImage im("c:\\640px-Chess_Pieces_Sprite.svg.png", wxBITMAP_TYPE_PNG);
// Extract the images of the pieces from the larger image.
wxBitmap b(im);
m_blackKing = b.GetSubBitmap(wxRect(5,113,100,100));
m_blackQueen = b.GetSubBitmap(wxRect(110,113,100,100));
m_blackBishop = b.GetSubBitmap(wxRect(215,113,100,100));
m_blackKnight = b.GetSubBitmap(wxRect(323,113,100,100));
m_blackRook = b.GetSubBitmap(wxRect(433,113,100,100));
m_blackPawn = b.GetSubBitmap(wxRect(535,113,100,100));
}
void MyFrame::OnPaint(wxPaintEvent&)
{
wxPaintDC dc(m_board);
dc.Clear();
// Draw thie light squares
dc.SetPen(wxColour(209,139,71));
dc.SetBrush(wxColour(209,139,71));
dc.DrawRectangle(0,0,800,800);
// Draw thie dark squares
dc.SetPen(wxColour(255,206,158));
dc.SetBrush(wxColour(255,206,158));
for ( int i = 0 ; i< 8 ; ++i )
{
for ( int j = i%2 ; j< 8 ; j+=2 )
{
dc.DrawRectangle(i*100,j*100,100,100);
}
}
// Draw thie black pieces
dc.DrawBitmap(m_blackRook, 0, 0, true);
dc.DrawBitmap(m_blackKnight, 100, 0, true);
dc.DrawBitmap(m_blackBishop, 200, 0, true);
dc.DrawBitmap(m_blackQueen, 300, 0, true);
dc.DrawBitmap(m_blackKing, 400, 0, true);
dc.DrawBitmap(m_blackBishop, 500, 0, true);
dc.DrawBitmap(m_blackKnight, 600, 0, true);
dc.DrawBitmap(m_blackRook, 700, 0, true);
for ( int i = 0 ; i < 8 ; ++i )
{
dc.DrawBitmap(m_blackPawn, 100*i, 100, true);
}
}
class MyApp : public wxApp
{
public:
virtual bool OnInit()
{
::wxInitAllImageHandlers();
MyFrame* frame = new MyFrame();
frame->Show();
return true;
}
};
wxIMPLEMENT_APP(MyApp);
On windows, the application will look something like this:
The important items demonstrated here are
loading the image.
extracting subbitmaps for the individual pieces.
drawing the pieces in a paint handler for the board.
Obviously, you should change the location of the png file in line 38. If you want to take a few extra steps, you can embed the png file in your application.
I tried to keep the example as simple as possible, so there are many things that could be improved upon. For example. I used a separate bitmap for each type of piece. If you want you can use a wxImageList instead. In addition it would be better to use an wxAutoBufferedPaintDC in the paint handler.
Is it possible to make it so that all drawing to an area "A" is translated to an area "B"?
For example drawing to the area(0,0)(100,100) and have it appear in area(200,200)(300,300).
The question is actually tagged with windows and graphics. This might have been targeted to Win32 and GDI (where I've unfortunately nearly no experience). So, the following might be seen as proof of concept:
I couldn't resist to implement the idea / concept using QWindow and QPixmap.
The concept is:
open a window fullscreen (i.e. without decoration)
make a snapshot and store it internally (in my case a )
display the internal image in window (the user cannot notice the difference)
perform a loop where pixmap is modified and re-displayed periodically (depending or not depending on user input).
And this is how I did it in Qt:
I opened a QWindow and made it fullscreen. (Maximum size may make the window full screen as well but it still will have decoration (titlebar with system menu etc.) which is unintended.)
Before painting anything, a snapshot of this window is done. That's really easy in Qt using QScreen::grabWindow(). The grabbed contents is returned as QPixmap and stored as member of my derived Window class.
The visual output just paints the stored member QPixmap.
I used a QTimer to force periodical changes of the QPixmap. To keep the sample code as short as possible, I didn't make the effort of shuffling tiles. Instead, I simply scrolled the pixmap copying a small part, moving the rest upwards, and inserting the small stripe at bottom again.
The sample code qWindowRoll.cc:
#include <QtWidgets>
class Window: public QWindow {
private:
// the Qt backing store for window
QBackingStore _qBackStore;
// background pixmap
QPixmap _qPixmap;
public:
// constructor.
Window():
QWindow(),
_qBackStore(this)
{
showFullScreen();
}
// destructor.
virtual ~Window() = default;
// disabled:
Window(const Window&) = delete;
Window& operator=(const Window&) = delete;
// do something with pixmap
void changePixmap()
{
enum { n = 4 };
if (_qPixmap.height() < n) return; // not yet initialized
const QPixmap qPixmapTmp = _qPixmap.copy(0, 0, _qPixmap.width(), n);
//_qPixmap.scroll(0, -n, 0, n, _qPixmap.width(), _qPixmap.height() - n);
{ QPainter qPainter(&_qPixmap);
qPainter.drawPixmap(
QRect(0, 0, _qPixmap.width(), _qPixmap.height() - n),
_qPixmap,
QRect(0, n, _qPixmap.width(), _qPixmap.height() - n));
qPainter.drawPixmap(0, _qPixmap.height() - n, qPixmapTmp);
}
requestUpdate();
}
protected: // overloaded events
virtual bool event(QEvent *pQEvent) override
{
if (pQEvent->type() == QEvent::UpdateRequest) {
paint();
return true;
}
return QWindow::event(pQEvent);
}
virtual void resizeEvent(QResizeEvent *pQEvent)
{
_qBackStore.resize(pQEvent->size());
paint();
}
virtual void exposeEvent(QExposeEvent*) override
{
paint();
}
// shoot screen
// inspired by http://doc.qt.io/qt-5/qtwidgets-desktop-screenshot-screenshot-cpp.html
void makeScreenShot()
{
if (QScreen *pQScr = screen()) {
_qPixmap = pQScr->grabWindow(winId());
}
}
private: // internal stuff
// paint
void paint()
{
if (!isExposed()) return;
QRect qRect(0, 0, width(), height());
if (_qPixmap.width() != width() || _qPixmap.height() != height()) {
makeScreenShot();
}
_qBackStore.beginPaint(qRect);
QPaintDevice *pQPaintDevice = _qBackStore.paintDevice();
QPainter qPainter(pQPaintDevice);
qPainter.drawPixmap(0, 0, _qPixmap);
_qBackStore.endPaint();
_qBackStore.flush(qRect);
}
};
int main(int argc, char **argv)
{
QApplication app(argc, argv);
// setup GUI
Window win;
win.setVisible(true);
// setup timer
QTimer qTimer;
qTimer.setInterval(50); // 50 ms -> 20 Hz (round about)
QObject::connect(&qTimer, &QTimer::timeout,
&win, &Window::changePixmap);
qTimer.start();
// run application
return app.exec();
}
I compiled and tested with Qt 5.9.2 on Windows 10. And this is how it looks:
Note: On my desktop, the scrolling is smooth. I manually made 4 snapshots and composed a GIF in GIMP – hence the image appears a bit stuttering.
What am trying to accomplish is having two Panels , one to draw on , and the other one will hold the tools , so I'm using the default Panel with size of the whole screen for drawing shapes on drawPanel , and a custom Panel on top of it for the tools , so I can add a background to it toolsPanel:
#ifndef WXIMAGEPANEL_H
#define WXIMAGEPANEL_H
#include <wx/wx.h>
#include <wx/custombgwin.h>
#include <wx/dcbuffer.h>
class wxImagePanel : public wxCustomBackgroundWindow<wxPanel>
{
public:
wxImagePanel();
wxImagePanel (wxWindow *parent,
wxWindowID winid = wxID_ANY,
const wxPoint& pos = wxDefaultPosition,
const wxSize& sizee = wxDefaultSize,
long style = wxTAB_TRAVERSAL | wxNO_BORDER,
const wxString& name = wxPanelNameStr);
void SetBackgroundImage(const wxBitmap & background);
virtual ~wxImagePanel();
protected:
private:
const wxBitmap * ptr_backgorund;
void paintEvent(wxPaintEvent & evt);
void OnEraseBackground(wxEraseEvent& event);
DECLARE_EVENT_TABLE()
};
#endif // WXIMAGEPANEL_H
----------------------------------------------------
#include "wxImagePanel.h"
wxImagePanel::wxImagePanel()
{
//ctor
//SetBackgroundStyle(wxBG_STYLE_PAINT);
}
wxImagePanel::wxImagePanel (wxWindow *parent,
wxWindowID winid ,
const wxPoint& pos ,
const wxSize& sizee ,
long style ,
const wxString& name)
{
Create(parent,winid,pos,sizee,style,name);
//SetBackgroundStyle(wxBG_STYLE_PAINT);
}
void wxImagePanel::SetBackgroundImage(const wxBitmap & background)
{
this->ptr_backgorund = &background;
SetBackgroundBitmap(background);
}
wxImagePanel::~wxImagePanel()
{
//dtor
if(ptr_backgorund)
delete ptr_backgorund;
}
BEGIN_EVENT_TABLE(wxImagePanel, wxPanel)
//EVT_PAINT(wxImagePanel::paintEvent)
EVT_ERASE_BACKGROUND(wxImagePanel::OnEraseBackground)
END_EVENT_TABLE()
void wxImagePanel::OnEraseBackground(wxEraseEvent& event)
{
}
void wxImagePanel::paintEvent(wxPaintEvent & evt)
{
wxAutoBufferedPaintDC dc(this);
PrepareDC(dc);
if(ptr_backgorund)
dc.DrawBitmap( *ptr_backgorund, 0, 0);
}
I have tried both ways (drawing the background myself, and using SetBackgroundBitmap method) , both ways are flickering when am calling drawPanel->Refresh() on MouseMove event , so what am missing here , that causing the toolsPanel to flicker?
The flicker is unavoidable if you call Refresh() on the entire window on each mouse move, this simply shouldn't be done. At the very least, you need to refresh just the small area which really needs to be repainted and not the entire window, which would reduce the flicker significantly but might still be not enough. The best solution is to use the (unfortunately still undocumented) wxOverlay class to overlay whatever you're drawing when the mouse moves on top of the window.
try refreshing like this:
window->Refresh(false)
Here's a similar thread containing a very useful piece of advice: on Refresh, "the option eraseBackground = FALSE is very important to avoid flickering"
how to animate picture without making it flicker using wxWidgets in erlang?
The documentation says Refresh repaints "window, and all of its children recursively (except under wxGTK1 where this is not implemented)". Although I don't understand the behaviour, I'm suspecting the recursive nature of Refresh causing the flicker, since I'm usually experiencing it under Windows 7 but not under Linux GTK (with the exact same code).
However, under Windows 10 I don't see any flicker even with eraseBackground = TRUE.
Is there a way to programmatically invoke the Aera maximize effect using C or C++ for a specific window/window ID?
For example:
or
(source: thebuzzmedia.com)
I am using a border-less Qt window and Qt has an API for getting the window ID. I want to programmatically trigger the windows effects without the known triggers.
I don't want to talk about every single detail involved in achieving this effect, not only there's a lot that goes on but you also mentioned you understand the logic to place the windows at their specific locations. In this answer I'll address what I believe are the 2 main challenges:
How to receive and handle a maximize event?
How to create an approximation of the aero snap effect?
In order to answer the first question, we must analyze which event handlers are triggered when the window is maximized:
void resizeEvent(QResizeEvent* evt); // Invoked first,
void paintEvent(QPaintEvent* event); // then second,
void changeEvent(QEvent* evt); // and at last.
A Qt application is first notified of a resizeEvent(), which is followed by a paintEvent() to draw the window (or widget), and only after everything has been displayed, changeEvent() is invoked to let you know the widget was maximized (maybe it's a little bit late to receive such notification, I don't know).
Of all these, the only one we care about is resizeEvent(). This event handler informs the new window/widget size that can be used for comparison with the desktop size, thus allowing us to know if the event was actually a maximize request. Once we identify a maximize request, we can figure out whether the application should be maximized (and anchored) to right, left or to the center of the screen.
This would be the time to create the aero snap widget and place it on the screen as a visual clue to the user.
To answer the second question, I don't think is possible to call the native Windows API and ask it politely to perform this effect on your window. The only other logical choice is to write a code that approximates this effect ourselves.
The visual appearance can be replicated by drawing a transparent window with a shadow-ish border. The approach demonstrated in the source code below, creates and customizes a QWidget to make it behave and look like a aero snap window:
It's not the most beautiful thing in the world, I know. This demo creates a regular window for the user to interact with, and once it's maximized, it places itself to the left of the screen. To the right size of the screen it displays something that resembles an aero snap window (shown above).
The idea behind the aero snap widget is very simple: a QWidget with transparent background and a custom painting procedure. In other words, it's a transparent window which draws a rounded rectangle with a shadow and that's it.
To make it a bit more realistic, you should add some animation to resize the widget little by little. A for loop might do the trick, but if you need something fancy you'll end up using timers. If you take a look here, you can see the quickest & dirtiest method to perform animation with Qt in action, and better ways to deal with animation. However, for simple tasks like this, stick with frame-based animation.
main.cpp:
#include "window.h"
#include <QApplication>
int main(int argc, char* argv[])
{
QApplication app(argc, argv);
Window window;
window.show();
return app.exec();
}
window.h:
#pragma once
#include "snapwindow.h"
#include <QMainWindow>
#include <QEvent>
class Window : public QMainWindow
{
public:
Window();
void resizeEvent(QResizeEvent* evt);
//void paintEvent(QPaintEvent* event);
void changeEvent(QEvent* evt);
private:
SnapWindow* _sw;
};
window.cpp:
#include "window.h"
#include "snapwindow.h"
#include <QDebug>
#include <QWindowStateChangeEvent>
#include <QApplication>
#include <QDesktopWidget>
Window::Window()
{
setWindowTitle("AeroSnap");
resize(300, 300);
_sw = new SnapWindow(this);
_sw->hide();
}
void Window::changeEvent(QEvent* evt)
{
if (evt->type() == QEvent::WindowStateChange)
{
QWindowStateChangeEvent* event = static_cast<QWindowStateChangeEvent*>(evt);
if (event->oldState() == Qt::WindowNoState &&
windowState() == Qt::WindowMaximized)
{
qDebug() << "changeEvent: window is now maximized!";
}
}
}
// resizeEvent is triggered before window_maximized event
void Window::resizeEvent(QResizeEvent* evt)
{
qDebug() << "resizeEvent: request to resize window to: " << evt->size();
QSize desktop_sz = QApplication::desktop()->size();
//qDebug() << "resizeEvent: desktop sz " << desktop_sz.width() << "x" << desktop_sz.height();
// Apparently, the maximum size a window can have in my system (1920x1080)
// is actually 1920x990. I suspect this happens because the taskbar has 90px of height:
desktop_sz.setHeight(desktop_sz.height() - 90);
// If this not a request to maximize the window, don't do anything crazy.
if (desktop_sz.width() != evt->size().width() ||
desktop_sz.height() != evt->size().height())
return;
// Alright, now we known it's a maximize request:
qDebug() << "resizeEvent: maximize this window to the left";
// so we update the window geometry (i.e. size and position)
// to what we think it's appropriate: half width to the left
int new_width = evt->size().width();
int new_height = evt->size().height();
int x_offset = 10;
setGeometry(x_offset, 45, new_width/2, new_height-45); // y 45 and height -45 are due to the 90px problem
/* Draw aero snap widget */
_sw->setGeometry(new_width/2-x_offset, 0, new_width/2, new_height);
_sw->show();
// paintEvent() will be called automatically after this method ends,
// and will draw this window with the appropriate geometry.
}
snapwindow.h:
#pragma once
#include <QWidget>
class SnapWindow : public QWidget
{
public:
SnapWindow(QWidget* parent = 0);
void paintEvent(QPaintEvent *event);
};
snapwindow.cpp:
#include "snapwindow.h"
#include <QPainter>
#include <QGraphicsDropShadowEffect>
SnapWindow::SnapWindow(QWidget* parent)
: QWidget(parent)
{
// Set this widget as top-level (i.e. owned by user)
setParent(0);
/* Behold: the magic of creating transparent windows */
setWindowFlags(Qt::Widget | Qt::FramelessWindowHint);
setStyleSheet("background:transparent;");
setAttribute(Qt::WA_NoSystemBackground, true); // speed up drawing by removing unnecessary background initialization
setAttribute(Qt::WA_TranslucentBackground);
//setAutoFillBackground(true);
/* Use Qt tricks to paint stuff with shadows */
QGraphicsDropShadowEffect* effect = new QGraphicsDropShadowEffect();
effect->setBlurRadius(12);
effect->setOffset(0);
effect->setColor(QColor(0, 0, 0, 255));
setGraphicsEffect(effect);
}
void SnapWindow::paintEvent(QPaintEvent *event)
{
QWidget::paintEvent(event);
/* Lazy way of painting a shadow */
QPainter painter(this);
QPen pen(QColor(180, 180, 180, 200));
pen.setWidth(3);
painter.setPen(pen);
// Offset 6 and 9 pixels so the shadow shows up properly
painter.drawRoundedRect(QRect(6, 6, (width()-1)-9, (height()-1)-9), 18, 18);
}
This is just a quick demo to point you to the right direction. It is by no means a complete implementation of the effect you are looking for.
Maybe it is not what you need, but this effect is just resizing and moving window then try use Qt methods to do this.
bool left = false;
QSize size = QApplication::desktop()->size();//resolution of current screen
if(left)
{//left side
this->setGeometry(0, 0, size.width()/2, size.height());//(maybe need do some changes)
}
else
{//right side
this->setGeometry(size.width()/2, 0, size.width()/2, size.height());
}
With QApplication::desktop() it will work properly on screen with different resolutions.
In web I found something similar in winapi, but it didn't work properly:
HWND act = GetForegroundWindow();
PostMessage((HWND)act,WM_NCLBUTTONDBLCLK, HTTOP, 0);
The best way
Combine this approaches. For example:
HWND act = GetForegroundWindow();
bool left = false;
QSize size = QApplication::desktop()->size();
if(left)
{
this->move(0,0);
PostMessage((HWND)act,WM_NCLBUTTONDBLCLK, HTTOP, 0);
this->resize(size.width()/2,QApplication::desktop()->height());
}
else
{
this->move(size.width()/2,0);
PostMessage((HWND)act,WM_NCLBUTTONDBLCLK, HTTOP, 0);
this->resize(size.width()/2,QApplication::desktop()->height());
}
Why? Because move() regulate left and right sides, but PostMessage (winapi) set window's height properly on every screen (window will not locate lower then taskbar, as in your example)
EDIT
I changed code a little and now it is better. Yes, it is resizing again, but now it hasn't winapi code (PostMessage etc), so Photoshop doesn't catch it, there is one interesting method in Qt which called availableGeometry. It return normal height of screen which we need, with this method borderless windows perfectly simulates Aero Snap effects in different directions. It is works, maybe don't so good, but as I can see, there isn't API for Aero effects. Maybe this approach will be normal for yoo.
There is Aero Peek in Qt : http://qt-project.org/doc/qt-5/qtwinextras-overview.html , but it is can't solve this problem too.
Code:
bool left = true;
bool upper = true;
if(upper)
{
QRect rect = QApplication::desktop()->availableGeometry(-1);
this->setGeometry(rect);
}
else if(left)
{
QRect rect = QApplication::desktop()->availableGeometry(-1);
rect.setWidth(rect.width()/2);
this->setGeometry(rect);
}
else
{
QRect rect = QApplication::desktop()->availableGeometry(-1);
int half = rect.width()/2;
rect.setX(half);
rect.setWidth(half);
this->setGeometry(rect);
}
Try it with frameless window! You should choose one direction or let user choose it.