I am trying to make a program currently that outputs a polygon to the desktop for a simple animation. The problem I am currently running into is that the animation gets an "onion" effect because the desktop isn't refreshing. I have searched for a method to refresh the desktop however because it's an animation, none of the solutions can refresh it fast enough. Below is an example of my code:
#include <iostream>
#include <Windows.h>
#include <math.h>
#include <Shlobj.h>
int main() {
//start ambrose
POINT amby[5];
POINT pos;
/* hide console window */
ShowWindow(FindWindowA("ConsoleWindowClass", NULL), false);
/* Calling GetDC with argument 0 retrieves the desktop's DC */
HDC hDC_Desktop = GetDC(0);
//This is just an example of what I am doing
for (int i = 0; i < 10000; i++) {
pos.x = 600+sin(double(i)/50)*200;
pos.y = 500+cos(double(i)/50)*200;
amby[0].x = -10+pos.x;
amby[0].y = -10+pos.y;
amby[1].x = -50+pos.x;
amby[1].y = -50+pos.y;
amby[2].x = 50+pos.x;
amby[2].y = -50+pos.y;
Polygon(hDC_Desktop,amby, 3);
Sleep(10);
}
//The method I was trying before that didn't work VVVVV
//LPITEMIDLIST pidl;
//SHGetSpecialFolderLocation(NULL,CSIDL_DESKTOP,&pidl);
//SHChangeNotify(SHCNE_ASSOCCHANGED,SHCNF_IDLIST,pidl,0);
return 0;
}
Thanks
Edit
I have tried using invalidateRect as such:
...
for (int i = 0; i < 10000; i++) {
pos.x = 600+sin(double(i)/50)*200;
pos.y = 500+cos(double(i)/50)*200;
amby[0].x = -10+pos.x;
amby[0].y = -10+pos.y;
amby[1].x = -50+pos.x;
amby[1].y = -50+pos.y;
amby[2].x = 50+pos.x;
amby[2].y = -50+pos.y;
Polygon(hDC_Desktop,amby, 3);
InvalidateRect(GetDesktopWindow(),NULL, true);
Sleep(10);
}
...
I am wondering if there is anyway to call WM_ERASEBKGND or WM_DISPLAYCHANGE to force a change. Does anyone know if there is a way to call these?
I am not sure what you are trying to achieve. Let me just answer to problem of onion effect. A quick and dirty solution to erase what was drawn in the previous iteration could be to draw using XOR mode but the solution has a few downsides, like flicker and color could be arbitrary. A proper solution that would address both the downsides would be to do all the drawing in a memory DC and BitBlt the same to the screen.
Code for the quick and dirty solution would be -
SetROP2(hDC_Desktop,R2_XORPEN);
//This is just an example of what I am doing
for (int i = 0; i < 100; i++)
{
if(i!=0)
{
pos.x = 600+sin(double(i-1)/50)*200;
pos.y = 500+cos(double(i-1)/50)*200;
amby[0].x = -10+pos.x;
amby[0].y = -10+pos.y;
amby[1].x = -50+pos.x;
amby[1].y = -50+pos.y;
amby[2].x = 50+pos.x;
amby[2].y = -50+pos.y;
Polygon(hDC_Desktop,amby, 3);
}
pos.x = 600+sin(double(i)/50)*200;
pos.y = 500+cos(double(i)/50)*200;
amby[0].x = -10+pos.x;
amby[0].y = -10+pos.y;
amby[1].x = -50+pos.x;
amby[1].y = -50+pos.y;
amby[2].x = 50+pos.x;
amby[2].y = -50+pos.y;
Polygon(hDC_Desktop,amby, 3);
Sleep(10);
}
There's an easy solution, and that's to not actually draw on the desktop. Instead, create a transparent full-screen window. Since it's transparent, any pixel that you don't draw will show the desktop underneath. Hence, only your polygon pixels will hide the underlying desktop.
As a result, the desktop window never needs to be invalidated or repainted etc.
Why don't you use a transparent wnd.
class COverlayWnd : public CWnd
{
DECLARE_DYNAMIC(COverlayWnd)
public:
COverlayWnd();
virtual ~COverlayWnd();
protected:
afx_msg void OnPaint();
afx_msg int OnCreate(LPCREATESTRUCT lpCreateStruct);
DECLARE_MESSAGE_MAP()
};
// OverlayWnd.cpp : implementation file
//
The implementation. Just move the window if you want animations to run all over the desktop.
#include "stdafx.h"
// COverlayWnd
IMPLEMENT_DYNAMIC(COverlayWnd, CWnd)
COverlayWnd::COverlayWnd()
{
}
COverlayWnd::~COverlayWnd()
{
}
BEGIN_MESSAGE_MAP(COverlayWnd, CWnd)
ON_WM_PAINT()
ON_WM_CREATE()
END_MESSAGE_MAP()
void COverlayWnd::OnPaint()
{
CPaintDC dc(this);
CRect rect;
GetClientRect( &rect );
dc.FillSolidRect(&rect, RGB(1,1,1));
//paint other stuff that don't have RGB(1,1,1)
}
int COverlayWnd::OnCreate(LPCREATESTRUCT lpCreateStruct)
{
if (CWnd::OnCreate(lpCreateStruct) == -1)
return -1;
BOOL bRet = 0;
bRet = ModifyStyleEx(0,WS_EX_LAYERED|WS_EX_TRANSPARENT);
bRet = ModifyStyle(DS_SETFONT | DS_MODALFRAME | DS_FIXEDSYS | WS_POPUP | WS_CAPTION | WS_SYSMENU,0);
bRet = ModifyStyle(WS_POPUP,0);
bRet = SetLayeredWindowAttributes(RGB(1,1,1),0,LWA_COLORKEY);
//the RGB(1,1,1) is the transparent color
ASSERT(bRet);
//this->EnableWindow(FALSE);
return 0;
}
Related
Hello and happy new year, (it is acceptable to say it until Thursday)
I am trying to change the color of the tabs in the CTabCtrl class. I am trying to create my own ReskinCTablCtrl so that I can just call it in separate classes and easily use it throughout my program.
Currently I am able to change the background color of the CTabCtrl but I cannot modify the tab's themselves.
I used ON_WM_ERASEBKGND() for painting the background and it worked without a problem:
BOOL ReskinCTabCtrl::OnEraseBkgnd(CDC* pDC)
{
CRect rect;
GetClientRect(&rect);
CBrush myBrush(RGB(51, 51, 51)); // dialog background color
BOOL bRes = pDC->PatBlt(0, 0, rect.Width(), rect.Height(), PATCOPY);
pDC->SetBkColor(RGB(51, 51, 51));
pDC->FillRect(&rect, &myBrush);
return bRes;
}
However, I have been unsuccesfull at changing the tab colors themselves. They are still the default MFC colors. I have tried to implement ON_WM_PAINT() and ON_WM_DRAWITEM() without any success. I think I can get to the specific tab rect with using both OnDraw and DrawItem similar to the second link example that I have posted in the end of this question.
void ReskinCTabCtrl::OnPaint() {
...
// paint the tabs first and then the borders
int nTab = GetItemCount();
int nSel = GetCurSel();
if (!nTab) // no pages added
return;
while (nTab--)
{
if (nTab != nSel)
{
dis.itemID = nTab;
dis.itemState = 0;
VERIFY(GetItemRect(nTab, &dis.rcItem));
dis.rcItem.bottom -= 2;
DrawItem(&dis);
DrawItemBorder(&dis);
}
}
...
}
I would really appreciate at least some direction to go about this problem, perhaps some more examples or what methods I should focus on using. I don't need the tabs to be different colors, maybe there is an easy way of doing this?
I've been trying to follow some examples like the following links but I still couldn't figure out the right way to do it.
https://support.microsoft.com/en-us/help/179909/how-to-change-the-background-color-of-a-tab-control
https://www.codeproject.com/Articles/1786/Ownerdraw-Tab-Controls-Borders-and-All
Enable OwnerDraw for tab control, either in resource editor, or set TCS_OWNERDRAWFIXED in OnInitDialog
CTabCtrl has message reflection for WM_DRAWITEM therefore we don't want to override WM_DRAWITEM/OnDrawItem from parent class. Instead override in CTabCtrl::DrawItem(LPDRAWITEMSTRUCT).
Unfortunately the result is rather ugly. It's sort of like overriding DrawItem in a button.
If Visual Style is available and enabled, then you can override CTabCtrl::OnPaint and draw everything manually. Example:
void CMyTabCtrl::OnPaint()
{
CPaintDC dc(this);
dc.SelectObject(GetFont());
CPen pen, pen_active;
COLORREF color_off = RGB(240, 240, 240);
COLORREF color_active = RGB(200, 240, 240);
CBrush brush_off, brush_active;
brush_off.CreateSolidBrush(color_off);
brush_active.CreateSolidBrush(color_active);
pen.CreatePen(PS_SOLID, 1, RGB(200, 200, 200));
pen_active.CreatePen(PS_SOLID, 1, color_active);
CRect rcitem;
GetItemRect(0, &rcitem);
CRect rc;
GetClientRect(&rc);
rc.bottom = rcitem.bottom;
dc.FillSolidRect(&rc, GetSysColor(COLOR_3DFACE));
GetClientRect(&rc);
rc.top = rcitem.bottom - 1;
dc.SelectObject(&pen);
dc.SelectObject(&brush_active);
dc.Rectangle(&rc);
for(int i = 0; i < GetItemCount(); i++)
{
dc.SelectObject(&pen);
if(i == GetCurSel())
{
dc.SelectObject(&brush_active);
dc.SetBkColor(color_active);
}
else
{
dc.SelectObject(&brush_off);
dc.SetBkColor(color_off);
}
GetItemRect(i, &rcitem);
rcitem.right++;
dc.Rectangle(&rcitem);
if(i == GetCurSel())
{
dc.SelectObject(pen_active);
dc.MoveTo(rcitem.left+1, rcitem.bottom - 1);
dc.LineTo(rcitem.right, rcitem.bottom - 1);
}
TCITEM item = { 0 };
wchar_t buf[32];
item.pszText = buf;
item.cchTextMax = 32;
item.mask = TCIF_TEXT;
GetItem(i, &item);
dc.DrawText(buf, &rcitem, DT_CENTER | DT_VCENTER | DT_SINGLELINE);
}
}
BOOL CMyTabCtrl::OnEraseBkgnd(CDC*)
{
return TRUE;
}
I need to rotate certain elements of the UI (not all). I have tried a couple of things already and none of them have provided the solution I require.
I attempted to use the QGraphicsProxyWidget and Graphics View, this worked well for if I wanted to rotate the entire UI, but provided no way to rotate only certain elements.
I attempted to promote the widget to a custom class that overrides the paintEvent. I am unable to get this way to work though. The following is my current code
#include <QPushButton>
#include <QPainter>
#include <QPaintEvent>
class QRotateButton : public QPushButton
{
Q_OBJECT
public:
QRotateButton (QWidget *parent = 0) {}
protected:
void paintEvent(QPaintEvent *event) override {
if (isDrawing)
return QRotateButton::paintEvent(event);
isDrawing = true;
QPixmap buttonPixmap = grab();
QPainter painter(this);
painter.rotate(90);
painter.drawPixmap(0, 0, height(), width(), buttonPixmap);
isDrawing = false;
}
private:
bool isDrawing = false;
};
This returns the following errors
QWidget::repaint: Recursive repaint detected
Segmentation fault
Thanks in advance
Update
Following the comments below I changed the code to call QPushButton::paintEvent(Event) instead of QRotateButton::paintEvent(Event)
The application does now open, where as it didn't before. But unfortunately no button is shown. Whenever the paintEvent gets invoked now, the following error messages appear in the console:
QWidget::repaint: Recursive repaint detected
QWidget::paintEngine: Should no longer be called
QPainter::begin: Paint device returned engine == 0, type: 1
QPainter::rotate: Painter not active
After playing around with this further, I managed to figure it out.
#include <QPushButton>
#include <QPainter>
#include <QPaintEvent>
class QRotateButton : public QPushButton
{
Q_OBJECT
public:
QRotateButton (QWidget *) {
//Set variables to defaults
_isDrawing = false;
_recursiveCounter = 0;
_recursiveMax = 1;
_rotationSetting = 0;
_rotationAngle = 0;
_rotationX = 0;
_rotationY = 0;
_rotationWidth = 0;
_rotationHeight = 0;
}
/* Set rotation setting */
void SetRotation(int setting) {
//Set rotation setting
_rotationSetting = setting;
//Call paintEvent
this->update();
}
protected:
void paintEvent(QPaintEvent *event) override {
//If its drawing then it just wants to do the normal paintEvent
if (_isDrawing)
{
QPushButton::paintEvent(event);
return;
}
//Setup the variables depending on setting
setupRotationVariables();
//Setup painter and rotate to angle
QPainter painter(this);
painter.rotate(_rotationAngle);
//Without this here, this function is called over and over,
//this stops that whilst maintaing the rotated image we want
if (_recursiveCounter > 0 && _requiresRotation)
{
_recursiveCounter--;
painter.drawPixmap(_rotationX, _rotationY, _drawing.width(), _drawing.height(), _drawing);
return;
}
//When rotating 90/270 degrees, we resize the image so that we don't stretch the image
//This is not required when flipping the image
if (_requiresRotation)
resize(_rotationWidth, _rotationHeight);
//Get the button image, this will call this function so we set _isDrawing to true whilst it's going on so meaning that it's does the base method and returns
_isDrawing = true;
_drawing = grab();
_isDrawing = false;
//Now we have the image, if rotation is being done resize the widget back to it's previous measurements
if (_requiresRotation)
resize(_rotationHeight, _rotationWidth);
//Draw the image to the picture
painter.drawPixmap(_rotationX, _rotationY, _drawing.width(), _drawing.height(), _drawing);
//Depending on if this has rotated or not, setup the catch the tailend of the rotation event
if (_requiresRotation)
_recursiveCounter = _recursiveMax;
else
_recursiveCounter = 0;
}
private:
/* Stops Recursive resizing when resizing is required */
int _recursiveCounter;
int _recursiveMax;
/* If the widget is currently drawing, stops certain recursive calls */
bool _isDrawing;
/* If the widget needs to rotated instead of flipped */
bool _requiresRotation;
/* Contains image of previous render of button, for use in recursive calls */
QPixmap _drawing;
/* Rotation Setting (0-3) defines rotation type. This is used since rotations should only be multiples of 90 */
int _rotationSetting;
/* Variables after rotation */
int _rotationAngle;
int _rotationX;
int _rotationY;
int _rotationWidth;
int _rotationHeight;
/* Fetch variables based on Rotation Setting */
void setupRotationVariables () {
//Figure out what rotation is required
switch (_rotationSetting)
{
case 0: //Up side up
_rotationAngle = 0;
_rotationWidth = width();
_rotationHeight = height();
_rotationX = 0;
_rotationY = 0;
_requiresRotation = false;
break;
case 1: //Right side up
_rotationAngle = 90;
_rotationWidth = height();
_rotationHeight = width();
_rotationX = 0;
_rotationY = -width();
_requiresRotation = true;
break;
case 2: //Down side up
_rotationAngle = 180;
_rotationWidth = width();
_rotationHeight = height();
_rotationX = -width();
_rotationY = -height();
_requiresRotation = false;
break;
case 3: //Left side up
_rotationAngle = 270;
_rotationWidth = height();
_rotationHeight = width();
_rotationX = -height();
_rotationY = 0;
_requiresRotation = true;
break;
default:
printf("INVALID ROTATION\n");
break;
}
}
};
Note
This will still throw QWidget::repaint: Recursive repaint detected every time paintEvent is called, but we expect this and it is handled in code. Whilst I would prefer it not show, I am unable to figure out any way to stop it.
I am trying to display a GIF picture as splash as starting of my small program in visual studio. I am really getting crazy. I looked it is possible in Qt IDE but I really need it in visual studio because my other code works only with visual studio. And yes I tried to convert my code for Qt giving me too many errors.
I have seen this post.
I am using GDI+ but still dunno how to simply display it instead of play and stop. It's okay even instead of splash to display a small form that plays the GIF file, can you guys give me a small code snippet in how to do it in c++?
Thanks.
Here is an MFC window class that implements a splash screen using GDI Plus for displaying an (animated) GIF.
I've encapsulated everything inside a header file to simplify using it with your project. Save it as an .h file (maybe "SplashWnd.h"), then include it wherever you want to set up the splash. You app's InitInstance might be a good place to add it - something like this line (before any DoModal calls):
SplashWnd splash(_T("Filname.gif"));
The constructor can also take parameters for controlling the delay before auto-closing, and also specifying a function to call when the splash is closed.
The splash window has no border or caption - it appears only as the loaded image, floating on top of any other windows. Its icon doesn't appear in the taskbar, and will close when its timeout expires, or the user clicks the window or presses a key.
#pragma once
#include <functional>
#include <afxwin.h>
#include <gdiplus.h>
#pragma comment(lib,"gdiplus.lib")
inline void ManageGdiPlusInit(bool release=false) {
static int refcount = 0;
static ULONG_PTR token;
if(release) {
if(--refcount == 0) {
Gdiplus::GdiplusShutdown(token);
}
} else if(++refcount == 1) {
Gdiplus::GdiplusStartupInput startup_input;
Gdiplus::GdiplusStartup(&token, &startup_input, 0);
} }
inline void GdiPlusInit() { ManageGdiPlusInit(false); }
inline void GdiPlusRelease() { ManageGdiPlusInit(true); }
namespace {
class SplashWnd : public CWnd {
protected:
static CString WindowClass() {
static CString name;
if(name.IsEmpty()) {
name = AfxRegisterWndClass(CS_DROPSHADOW, 0, (HBRUSH)GetStockObject(GRAY_BRUSH), 0);
}
return name;
}
Gdiplus::Image *m_pImage;
UINT m_FrameCount;
unsigned char *m_FrameDelayData;
const UINT *m_FrameDelays;
UINT m_CurFrameIndex;
UINT m_AnimationTimerId;
UINT m_ExpireTimerId;
CRect m_WindowRect;
std::function<void()> m_DismissCallback;
DECLARE_MESSAGE_MAP()
afx_msg void OnLButtonDown(UINT nFlags, CPoint point) {
DestroyWindow();
}
afx_msg void OnKeyDown(UINT nChar, UINT nRepCnt, UINT nFlags) {
DestroyWindow();
}
afx_msg void OnDestroy() {
if(m_AnimationTimerId != UINT(-1)) {
KillTimer(m_AnimationTimerId);
}
if(m_ExpireTimerId != UINT(-1)) {
KillTimer(m_ExpireTimerId);
}
if(m_DismissCallback) {
m_DismissCallback();
}
CWnd::OnDestroy();
}
afx_msg void OnTimer(UINT nIDEvent) {
if(nIDEvent == m_AnimationTimerId) {
if(++m_CurFrameIndex >= m_FrameCount) {
m_CurFrameIndex = 0;
}
DrawCurFrame();
KillTimer(m_AnimationTimerId);
m_AnimationTimerId = SetTimer(1, m_FrameDelays[m_CurFrameIndex], 0);
return;
}
if(nIDEvent == m_ExpireTimerId) {
DestroyWindow();
return;
} }
void PostNcDestroy() {
if(m_DeleteSelf) {
delete this;
}
}
void DrawCurFrame() {
Gdiplus::Graphics g(m_hWnd);
GUID dim_select_id = Gdiplus::FrameDimensionTime;
m_pImage->SelectActiveFrame(&dim_select_id, m_CurFrameIndex);
g.DrawImage(m_pImage, 0, 0, m_WindowRect.Width(), m_WindowRect.Height());
}
public:
// set m_DeleteSelf to true if a SplashWnd is created with new, and you want it to
// auto-delete itself when the window expires or is dismissed.
bool m_DeleteSelf;
// file_path the gif file path
// ExpireMs the time, in milliseconds until the window automatically closes itself
// WidthFactor the fraction of the width of the primary display to use as the splash screen's width
// HeightFactor the fraction of the height of the primary display to use as the height
// If WidthFactor or HeightFactor are 0, the original image aspect ratio is preserved
// If both are 0, the original image size, in pixels is used
SplashWnd(CString file_path, DWORD ExpireMs=2000, double WidthFactor=0.4, double HeightFactor=0) {
GdiPlusInit();
m_pImage = new Gdiplus::Image(file_path);
// Set up an array of frame times for animated images
UINT dimension_count = m_pImage->GetFrameDimensionsCount();
GUID dimension_id;
m_pImage->GetFrameDimensionsList(&dimension_id, 1);
m_FrameCount = m_pImage->GetFrameCount(&dimension_id);
UINT frame_delay_size = m_pImage->GetPropertyItemSize(PropertyTagFrameDelay);
m_FrameDelayData = new unsigned char[frame_delay_size];
Gdiplus::PropertyItem* frame_delay_item = reinterpret_cast<Gdiplus::PropertyItem*>(m_FrameDelayData);
m_pImage->GetPropertyItem(PropertyTagFrameDelay, frame_delay_size, frame_delay_item);
m_FrameDelays = reinterpret_cast<const UINT*>(frame_delay_item->value);
// Figure out the size and location of the splash window
int primary_width = GetSystemMetrics(SM_CXFULLSCREEN);
int primary_height = GetSystemMetrics(SM_CYFULLSCREEN);
int splash_width = int(primary_width * WidthFactor);
int splash_height = int(primary_height * HeightFactor);
if(splash_width == 0) {
if(splash_height == 0) {
splash_width = m_pImage->GetWidth();
splash_height = m_pImage->GetHeight();
} else {
splash_width = primary_width * splash_height / primary_height;
}
} else if(splash_height == 0) {
splash_height = primary_height * splash_width / primary_width;
}
int l = (primary_width - splash_width) / 2;
int t = (primary_height - splash_height) / 2;
int r = l + splash_width;
int b = t + splash_height;
m_WindowRect.SetRect(
(primary_width - splash_width) / 2,
(primary_height - splash_height) / 2,
(primary_width + splash_width) / 2,
(primary_height + splash_height) / 2);
// WS_EX_TOPMOST makes the window cover up other, regular windows
// WS_EX_TOOLWINDOW prevents an icon for this window in the taskbar
// WS_POPUP prevents caption and border from being drawn
CreateEx(WS_EX_TOPMOST | WS_EX_TOOLWINDOW, WindowClass(), _T("Splash"), WS_VISIBLE | WS_POPUP, m_WindowRect, 0, 0);
// Show the first frame
m_CurFrameIndex = 0;
DrawCurFrame();
// Set up the frame-flipping animation timer
m_ExpireTimerId = m_AnimationTimerId = UINT(-1);
if(m_FrameCount > 1) {
m_AnimationTimerId = SetTimer(1, m_FrameDelays[m_CurFrameIndex], 0);
}
// Set up the expiration timer
if(ExpireMs != INFINITE) {
m_ExpireTimerId = SetTimer(2, ExpireMs, 0);
}
m_DeleteSelf = false;
}
// Constructor which takes a callback function which will be called when the splash window closes
template <typename F>
SplashWnd(CString file_path, DWORD ExpireMs, double WidthFactor, double HeightFactor, F DismissCallback)
: SplashWnd(file_path, ExpireMs, WidthFactor, HeightFactor)
{
m_DismissCallback = DismissCallback;
}
~SplashWnd() {
delete [] m_FrameDelayData;
delete m_pImage;
GdiPlusRelease();
}
};
// Message map, usually in an implementation file, but here encapsulated inside the header
// using an anonymous namespace to prevent possible ODR problems.
BEGIN_MESSAGE_MAP(SplashWnd, CWnd)
ON_WM_KEYDOWN()
ON_WM_LBUTTONDOWN()
ON_WM_TIMER()
ON_WM_DESTROY()
END_MESSAGE_MAP()
}
If console aplication starts and system creates console window for it, but sometimes this window is created in such coordinates that some of it's contents slide out of right screen edge. Then the user must use mouse to show everything.
How to cope with that ?
What functions use to detect upper right corner coordinates of console window ?
Then I will be able to check if it is outside the screen and move the window just the distance that is needed.
What function use to move window ?
Or maybe there is all in one solution to prevent window from moving outside the screen ?
Here's a fully multi-monitor aware and taskbar aware implementation that does what you describe.
#include <Windows.h>
int main()
{
ClampConsoleToScreen();
return 0;
}
void ClampConsoleToScreen()
{
HWND window = GetConsoleWindow();
RECT windowRect;
GetWindowRect(window, &windowRect);
HMONITOR monitor = MonitorFromWindow(window, MONITOR_DEFAULTTOPRIMARY);
MONITORINFO mi;
memset(&mi, 0, sizeof(mi));
mi.cbSize = sizeof(mi);
GetMonitorInfo(monitor, &mi);
int adj, any;
adj = 0;
any = 0;
if (windowRect.right > mi.rcWork.right)
{
// Get negative adjustment value to move it left onto screen
adj = mi.rcWork.right - windowRect.right;
}
if (windowRect.left < mi.rcWork.left)
{
// Get positive adjustment value to move it right onto screen
adj = mi.rcWork.left - windowRect.left;
}
windowRect.left += adj;
windowRect.right += adj;
any |= adj;
adj = 0;
if (windowRect.bottom > mi.rcWork.bottom)
{
// Get negative adjustment value to move it up onto screen
adj = mi.rcWork.bottom - windowRect.bottom;
}
if (windowRect.top < mi.rcWork.top)
{
// Get positive adjustment value to move it down onto screen
adj = mi.rcWork.top - windowRect.top;
}
windowRect.top += adj;
windowRect.bottom += adj;
any |= adj;
if (any)
{
MoveWindow(window,
windowRect.left,
windowRect.top,
windowRect.right - windowRect.left,
windowRect.bottom - windowRect.top, TRUE);
}
}
I'm using a CListCtrl/CListView report view (LVS_REPORT) in virtual mode (LVS_OWNERDATA) with LVS_EX_DOUBLEBUFFER enabled and I encounter ugly flickering. Double buffer have a real effect but it doesn't stop all flickering (without it very slow).
I'm not looking for switching to other controls that would require a high amount of rework (like ObjectListView)
How does the flickering behaves:
on column resize - the background is first clean using lightgray and after this is displayed the text (background is white)
on mouse scroll (animated) - for a very short time there is lightgray-bar displayed in the area where new lines are to be displayed.
It looks like it does clean the background using the default window background color (lightgray) for the area where it has to redraw.
How do I solve the flickering problem?
Try to do the following:
- Set Clip Children and Clip Sibling for paremt dialog of List Control.
- Make dirived from CListCtrl class. In this class overwrite OnEraseBkgnd. In the OnEraseBkgnd fill with background color area around of visible items of the list.
The OnEraseBkgnd can look like:
BOOL CListCtrlEx::OnEraseBkgnd(CDC* pDC)
{
CBrush br;
CRect rcCli;
CRect rcItemsRect(0, 0, 0, 0);
int nHeadHeight = 0;
int nItems = GetItemCount();
GetClientRect(&rcCli);
CHeaderCtrl* pHeadCtrl = GetHeaderCtrl();
if (pHeadCtrl)
{
CRect rcHead;
pHeadCtrl->GetWindowRect(&rcHead);
nHeadHeight = rcHead.Height();
}
rcCli.top += nHeadHeight;
if (nItems > 0)
{
CPoint ptItem;
CRect rcItem;
GetItemRect(nItems - 1, &rcItem, LVIR_BOUNDS);
GetItemPosition(nItems - 1, &ptItem);
rcItemsRect.top = rcCli.top;
rcItemsRect.left = ptItem.x;
rcItemsRect.right = rcItem.right;
rcItemsRect.bottom = rcItem.bottom;
if (GetExtendedStyle() & LVS_EX_CHECKBOXES)
rcItemsRect.left -= GetSystemMetrics(SM_CXEDGE) + 16;
}
br.CreateSolidBrush(GetBkColor());
if (rcItemsRect.IsRectEmpty())
pDC->FillRect(rcCli, &br);
else
{
if (rcItemsRect.left > rcCli.left) // fill left rectangle
pDC->FillRect(
CRect(0, rcCli.top, rcItemsRect.left, rcCli.bottom), &br);
if (rcItemsRect.bottom < rcCli.bottom) // fill bottom rectangle
pDC->FillRect(
CRect(0, rcItemsRect.bottom, rcCli.right, rcCli.bottom), &br);
if (rcItemsRect.right < rcCli.right) // fill right rectangle
pDC->FillRect(
CRect(rcItemsRect.right, rcCli.top, rcCli.right, rcCli.bottom), &br);
}
return TRUE;
}
I know only way to have flicker free is using double buffering or MemDC.
have found this article: Flicker-free-drawing-of-any-control
This article explains it well how to quickly perform Non Flickering drawing on your CListCtrl.
And it works excellent.
PS: VS 2005 doesn't have CMemDC class you will need to implement it your self, or use the following code:
//
// CMemDC.h header file
//
#pragma once
class CMemDC
{
public:
CMemDC(CDC& dc, CWnd* pWnd);
CMemDC(CDC& dc, const CRect& rect);
virtual ~CMemDC();
CDC& GetDC() { return m_bMemDC ? m_dcMem : m_dc; }
BOOL IsMemDC() const { return m_bMemDC; }
BOOL IsVistaDC() const { return m_hBufferedPaint != NULL; }
void EraseBkClip();
protected:
CDC& m_dc;
BOOL m_bMemDC;
HANDLE m_hBufferedPaint;
CDC m_dcMem;
CBitmap m_bmp;
CBitmap* m_pOldBmp;
CRect m_rect;
};
//
// CMemDC.cpp source file
//
#include "CMemDC.h"
CMemDC::CMemDC(CDC& dc, CWnd* pWnd) :
m_dc(dc), m_bMemDC(FALSE), m_hBufferedPaint(NULL), m_pOldBmp(NULL)
{
ASSERT_VALID(pWnd);
pWnd->GetClientRect(m_rect);
m_rect.right += pWnd->GetScrollPos(SB_HORZ);
m_rect.bottom += pWnd->GetScrollPos(SB_VERT);
if (m_dcMem.CreateCompatibleDC(&m_dc) &&
m_bmp.CreateCompatibleBitmap(&m_dc, m_rect.Width(), m_rect.Height()))
{
m_bMemDC = TRUE;
m_pOldBmp = m_dcMem.SelectObject(&m_bmp);
}
}
CMemDC::CMemDC(CDC& dc, const CRect& rect) :
m_dc(dc), m_bMemDC(FALSE), m_hBufferedPaint(NULL), m_pOldBmp(NULL), m_rect(rect)
{
ASSERT(!m_rect.IsRectEmpty());
if (m_dcMem.CreateCompatibleDC(&m_dc) &&
m_bmp.CreateCompatibleBitmap(&m_dc, m_rect.Width(), m_rect.Height()))
{
m_bMemDC = TRUE;
m_pOldBmp = m_dcMem.SelectObject(&m_bmp);
}
}
CMemDC::~CMemDC()
{
if (m_bMemDC)
{
CRect rectClip;
int nClipType = m_dc.GetClipBox(rectClip);
if (nClipType != NULLREGION)
{
if (nClipType != SIMPLEREGION)
{
rectClip = m_rect;
}
m_dc.BitBlt(rectClip.left, rectClip.top, rectClip.Width(), rectClip.Height(), &m_dcMem, rectClip.left, rectClip.top, SRCCOPY);
}
m_dcMem.SelectObject(m_pOldBmp);
}
}
void CMemDC::EraseBkClip()
{
CRect clip;
m_dcMem.GetClipBox(&clip);
m_dcMem.FillSolidRect(clip, GetSysColor(COLOR_WINDOW));
}
There is an ultra simple way I found that worked for me:
Turn off redraw with m_List1.SetRedraw(false)
Reset contents with m_List1.ResetContents()
Add new strings in loop with m_List1.AddString()
Then finalize by turning back on redraw and a m_List1.UpdateWindow().