display obscured control on bitmap - windows

I have the HDC=hdc of a bit map, a rectangle R with logical coordinates in hdc, and the HWND=hwnd of a scroll control created by CreateWindow with SBS_HORZ. The scroll control is is the child of another window. I want to display the scroll control on the bitmap in rectangle R.
I obtained a HDC for the scroll control and used BitBlt to copy the control to the rectangle. All works well if the entire scroll control is visible in it's parent BUT if the scroll bar is obscured, I get what ever is on top of the bar. If the control is off the screen I get nothing.
This is all part of an effort to periodically save a screen image of the app in case you are wondering how the scroll bar can be obscured. I do not want to bring the scroll bar's parent to the front.
Is there anyway I can get a true image of the scroll bar in these conditions?
Or alternatively, could I somehow make a scroll bar that wasn't displayed who's contents I could copy? I do know all the parameters needed.

I found the following seems to work even if the control is obscured or off the screen. Create a DC and compatible bitmap from the control. Send the control a WM_PRINT message asking it to print itself in the DC/Bitmap. Then copy the bitmap using BitBlt.
Pretty ugly! Is there a better way?
Something like this...
HDC hdcScroll;
WINDOWPLACEMENT WP;
HDC memdc;
HBITMAP membit;
hdcScroll = GetDC (hwndScroll);
GetWindowPlacement (hwndScroll, &WP);
int Height = WP.rcNormalPosition.bottom - WP.rcNormalPosition.top;
int Width = WP.rcNormalPosition.right - WP.rcNormalPosition.left;
memdc = CreateCompatibleDC(hdcScroll); // destination DC
membit = CreateCompatibleBitmap(hdcScroll, Width, Height); // destination bitmap
HBITMAP hOldBitmap =(HBITMAP) SelectObject(memdc, membit); // add bitmap to DC
SendMessage (hwndScroll,WM_PRINT,(WPARAM) memdc, PRF_CLIENT);
BitBlt
(hdc, // destination HDC
rt_scroll.left, // dest upper left corner X
rt_scroll.top, // dest upper left corner Y
rt_scroll.right-rt_scroll.left+1, // width of dest rectangle
rt_scroll.bottom-rt_scroll.top+1, // height of dest rectangle
memdc, // source HDC
0, // source upper left corner X
0, // source upper left cornet Y
SRCCOPY
);
SelectObject(memdc, hOldBitMap);
DeleteObject (membit);
DeleteDC (memdc);
ReleaseDC (hwndScroll, hdcScroll);

Related

Reduce Drag & Drop Flickering

I've a problem with my code. I've created a class for Drag & Drop but during the dragging of the object i see a annoying flickering effect for the object that i'm dragging
void CDragDropListBox::DrawDragRect(CPoint point, CDragItem* DragItem,SIZE Size)
{
CDC* pDC = GetDC();
ScreenToClient(&point);
//Rect centered under mouse pointer
point.x -= Size.cx/2;
point.y -= Size.cy/2;
CRect rectFull(point,m_RectSize);
//Delete previous rect
ClientToScreen(&m_OldRect);
_DstWnd->ScreenToClient(&m_OldRect);
_DstWnd->InvalidateRect(m_OldRect, true);
_DstWnd->UpdateWindow();
//Draw new rect based on mouse position
DrawSelectFrame(pDC,rectFull);
DrawSingleItem(DragItem,pDC,rectFull);
m_OldRect = rectFull;
}
In my code every time i move the mouse i delete the previously drawn drag-rect and paint a new one, but the flickering is very fastidious...
There is anything i can do?
The main problem here is, that the erasing and painting is done in two steps.
And even if it is executed fast. It looks like flickering.
So don't use WM_ERASEBKGND, and use double buffering (with a memory DC) in WM_PAINT.
See CMemDC in Codeproject for a good and easy class.
I've edited my code for remove the use of UpdateWindow()
void CDragDropListBox::DrawDragRect(CPoint point, CDragItem* DragItem,SIZE Size)
{
CDC* pDC = _DstWnd->GetDC();
CDC dcMemory;
ScreenToClient(&point);
dcMemory.CreateCompatibleDC(pDC);
CDC* olddc= pDC;
CRect tmprect;
pDC->GetClipBox(&tmprect);
CBitmap tmpbmp;
tmpbmp.CreateCompatibleBitmap(pDC, tmprect.Width(), tmprect.Height());
CBitmap* OldBmp;
OldBmp = dcMemory.SelectObject(&tmpbmp);
point.x -= Size.cx/2;
point.y -= Size.cy/2;
CRect rectFull(point,m_RectSize);
ClientToScreen(&m_OldRect);
_DstWnd->ScreenToClient(&m_OldRect);
_DstWnd->InvalidateRect(m_OldRect, TRUE);
m_OldRect = rectFull;
ClientToScreen(&rectFull);
_DstWnd->ScreenToClient(&rectFull);
DrawSelectFrame(pDC,rectFull);
DrawSingleItem(DragItem,pDC,rectFull);
_DstWnd->ValidateRect(rectFull);
//_DstWnd->UpdateWindow();
olddc->BitBlt(tmprect.left, tmprect.top, tmprect.Width(), tmprect.Height(), pDC, tmprect.left, tmprect.top, SRCCOPY);
dcMemory.SelectObject(OldBmp);
}
Flickering less, but can i further improve?

GetObject() on bitmap handle from LoadImage() sometimes returns incorrect bitmap size

We are seeing an intermittent problem in which owner drawn buttons under Windows XP that are using a bitmap as a backdrop are displaying the bitmap incorrectly. The window containing multiple buttons that are using the same bitmap file for the bitmap image used for the button backdrop will display and most of the buttons will be correct though in some cases there may be one or two buttons which are displaying the bitmap backdrop reduced to a smaller size.
If you exit the application and then restart it you may see the same behavior of the incorrect display of the icon on the buttons however it may or may not be the same buttons as previously. Nor is this behavior of incorrect display of icons on the buttons always seen. Sometimes it shows and sometimes it does not. Since once we load an icon for a button we just keep it, once the button is displayed incorrectly it will always be displayed incorrectly.
Using the debugger we have finally found that what appears to be happening is that when the GetObject() function is called, the data returned for the bitmap size is sometimes incorrect. For instance in one case the bitmap was 75x75 pixels and the size returned by GetObject() was 13x13 instead. Since this size is used as part of the drawing of the bitmap, the displayed backdrop becomes a small decoration on the button window.
The actual source area is as follows.
if (!hBitmapFocus) {
CString iconPath;
iconPath.Format(ICON_FILES_DIR_FORMAT, m_Icon);
hBitmapFocus = (HBITMAP)LoadImage(NULL, iconPath, IMAGE_BITMAP, 0, 0, LR_LOADFROMFILE);
}
if (hBitmapFocus) {
BITMAP bitmap;
int iNoBytes = GetObject(hBitmapFocus, sizeof(BITMAP), &bitmap);
if (iNoBytes < 1) {
char xBuff[128];
sprintf (xBuff, "GetObject() failed. GetLastError = %d", GetLastError ());
NHPOS_ASSERT_TEXT((iNoBytes > 0), xBuff);
}
cxSource = bitmap.bmWidth;
cySource = bitmap.bmHeight;
//Bitmaps cannot be drawn directly to the screen so a
//compatible memory DC is created to draw to, then the image is
//transfered to the screen
CDC hdcMem;
hdcMem.CreateCompatibleDC(pDC);
HGDIOBJ hpOldObject = hdcMem.SelectObject(hBitmapFocus);
int xPos;
int yPos;
//The Horizontal and Vertical Alignment
//For Images
//Are set in the Layout Manager
//the proper attribute will have to be checked against
//for now the Image is centered on the button
//Horizontal Alignment
if(btnAttributes.horIconAlignment == IconAlignmentHLeft){//Image to left
xPos = 2;
}else if(btnAttributes.horIconAlignment == IconAlignmentHRight){//Image to right
xPos = myRect.right - cxSource - 5;
}else {//Horizontal center
xPos = ((myRect.right - cxSource) / 2) - 1;
}
//Vertical Alignment
if(btnAttributes.vertIconAlignment == IconAlignmentVTop){//Image to top
yPos = 2;
}else if(btnAttributes.vertIconAlignment == IconAlignmentVBottom){//Image to bottom
yPos = myRect.bottom - cySource - 5;
}else{//Vertical Center
yPos = ((myRect.bottom - cySource) / 2) - 1;
}
pDC->BitBlt(xPos, yPos, cxSource, cySource, &hdcMem, 0, 0, SRCCOPY);
hdcMem.SelectObject(hpOldObject);
}
Using the debugger we can see that the iconPath string is correct and the bitmap is loaded as hBitmapFocus is not NULL. Next we can see that the call to GetObject() is made and the value returned for iNoBytes equals 24. For those buttons that display correctly the values in bitmap.bmWidth and bitmap.bmHeight are correct however for those that do not the values are much too small leading to an incorrect sizing when drawing the bitmap.
The variable is defined in the class header as
HBITMAP hBitmapFocus;
As part of doing the research for this I found this stack overflow question, GetObject returns strange size and I am wondering if there is some kind of an alignment issue here.
Does the bitmap variable used in the call to GetObject() need to be on some kind of an alignment boundary? While we are using packed for some of our data we are using pragma directives to only specify specific portions of code containing specific structs in include files that need to be packed on one byte boundaries.
Please read this Microsoft KB how to load a bitmap with palette information. It has a great example as well.
On the side note: I do not see anywhere in your code where you call ::DeleteObject(hBitmapFocus). It is very important to call this, as you can run out of GDI objects very quickly.
It is always a good idea to use Windows Task manager to see that your program does not exhaust the GDI resources. Just add "GDI Objects" column to the Task Manager and see that the number of objects is not constantly increasing in your app, but stays within an expected range, similar to other programs

Setting a windows region without disabling theming

Does anyone know how to assign a window region (SetWindowRgn or Control.Region in WinForms) without killing the theming in the non-client area?
For example, running the following generates a Windows 2000-style unthemed title bar, border, etc:
var form = new Form { Width=500, Height=500, BackColor = Color.Azure };
form.Text = "But soft, what light through yonder window breaks?";
var region = new Region (new Rectangle (Point.Empty, form.Size));
region.Exclude (new Rectangle (100, 100, 300, 300));
form.Region = region;
form.ShowDialog();
I'm guessing it's to do with this MSDN article which says:
As long as a window has a non-NULL
region applied to it (SetWindowRgn),
the UxTheme Manager assumes that this
is a specialized window and the window
will not use visual styles.
...hence UxThemes assumes it's a specialized window. Is there a way to tell the UxTheme Manager explicitly to theme a window?
The answer to your question is that you cannot.
But a workaround, to give you a transparent section in your form, would be to add the WS_EX_LAYERED extended window style to your form. Then you can tell the Window Manager that you want to use a chroma-color key to make part of your form transparent:
SetLayeredWindowAttributes(
Form.Handle, // __in HWND hwnd,
RGB(0, 255, 0), //green is the color key __in COLORREF crKey,
255, //window is opaque otherwise __in BYTE bAlpha,
LWA_COLORKEY //use color-key (rather than per-pixel alpha) __in DWORD dwFlags
);
Then you can put your "transparent" area as lime green:
Which then at runtime will be transparent:
Update: When i use layered window to have full transparency mouse events do trickle through to what's underneath. Notice the "flag" icon highlight:
See also
Window Overview -> Window Features -> Layered Windows
SetLayeredWindowAttributes Function
Extended Window Styles
Layered Windows

QT: Scroll widget that renders directly to the DC

I'm trying to create a widget which paints directly to the windows Device Context by calling getDC() and painting an HBITMAP to it.
The widget I'm painting resides inside a scroll widget.
I've implemented the paintEvent() and it does seem to paint but immediatly after painting the widget gets painted over again with a blank gray color.
I've tried setting WA_PaintOnScreen and Qt::WA_NoSystemBackground but none of those help.
In theory this should be possible since this is basically how the GLWidget works.
What am I missing?
Found the answer here:
http://www.qtchina.net/qt4c++guiprogramming/ch20lev1sec1.html/
void GdiControl::paintEvent(QPaintEvent * /* event */)
{
RECT rect;
GetClientRect(winId(), &rect);
HDC hdc = GetDC(winId());
FillRect(hdc, &rect, HBRUSH(COLOR_WINDOW + 1));
SetTextAlign(hdc, TA_CENTER | TA_BASELINE);
TextOutW(hdc, width() / 2, height() / 2, text.utf16(), text.size());
ReleaseDC(winId(), hdc);
}
For this to work, we must also
reimplement
QPaintDevice::paintEngine() to return
a null pointer and set the
Qt::WA_PaintOnScreen attribute in the
widget's constructor.

If window spans multiple monitors, I can't draw to it

If I have a window that spans both monitors on a multimonitor system, I can't seem to erase (paint black) the entire window. Instead, only the primary window is drawn black. The secondary remains the original white color. Has anyone seen this behavior?
wxwidgets:
wxClientDC dc(this);
Erase(dc);
void SpriteWindowFrame::Erase(wxDC& dc)
{
dc.SetBackground(*wxBLACK_BRUSH);
dc.SetBrush(*wxBLACK_BRUSH);
dc.Clear();
//wxLogDebug("Erase called. Rect is %i, %i w:%i, h:%i", GetPosition().x, GetPosition().y, GetSize().GetWidth(), GetSize().GetHeight());
}
Inside dc.Clear() function, there is this code
wxwidgets:
void wxDC::Clear()
{
WXMICROWIN_CHECK_HDC
RECT rect;
if ( m_canvas )
{
GetClientRect((HWND) m_canvas->GetHWND(), &rect);
}
else
{
// No, I think we should simply ignore this if printing on e.g.
// a printer DC.
// wxCHECK_RET( m_selectedBitmap.Ok(), wxT("this DC can't be cleared") );
if (!m_selectedBitmap.Ok())
return;
rect.left = -m_deviceOriginX; rect.top = -m_deviceOriginY;
rect.right = m_selectedBitmap.GetWidth()-m_deviceOriginX;
rect.bottom = m_selectedBitmap.GetHeight()-m_deviceOriginY;
}
#ifndef __WXWINCE__
(void) ::SetMapMode(GetHdc(), MM_TEXT);
#endif
DWORD colour = ::GetBkColor(GetHdc());
HBRUSH brush = ::CreateSolidBrush(colour);
::FillRect(GetHdc(), &rect, brush);
::DeleteObject(brush);
#ifndef __WXWINCE__
int width = DeviceToLogicalXRel(VIEWPORT_EXTENT)*m_signX,
height = DeviceToLogicalYRel(VIEWPORT_EXTENT)*m_signY;
::SetMapMode(GetHdc(), MM_ANISOTROPIC);
::SetViewportExtEx(GetHdc(), VIEWPORT_EXTENT, VIEWPORT_EXTENT, NULL);
::SetWindowExtEx(GetHdc(), width, height, NULL);
::SetViewportOrgEx(GetHdc(), (int)m_deviceOriginX, (int)m_deviceOriginY, NULL);
::SetWindowOrgEx(GetHdc(), (int)m_logicalOriginX, (int)m_logicalOriginY, NULL);
#endif
}
Using the debugger, I checked what GetClientRect returned and sure enough it returns a rectange with location 0 and width/height of the combined two monitors so it's right. Maybe fillrect function is not capable of drawing to two displays?
Can you trace into the constructor of the wxClientDC?
wxClientDC dc(this);
A lot depends on what type of DC wx has given you. The windows API to retrieve a window DC is hdc = GetDC(hwnd), and, on multimonitor systems, it retrieves a handle to a 'mirror driver' DC, thats meant to reflect calls to all the underlying display device DCs that the monitor spans.
The only possible reason I can think of for this behaviour is wx is somehow retrieving a display DC rather than a window DC.
I'm sure Chris is correct, that the "overlapping window" case is handled somewhere for you. But where?
Rendering with windows GDI and "display contexts" such as you mention is very primitive and prone to all sorts of problems. GDI is one of poorest interfaces ever seen, poor even for Microsoft. Since most "window" programs work OK on multiple monitors, think of animating things in a "window" - and how that "window" makes its way to the "display" is best left a mystery.
Maybe DC is fundamentally not multi-monitor capable. Look for anything that allows multiple DCs to be treated uniformly. Rending graphics onto a grid of paper sheets would be like a tiled "printer DC". A video wall would be a tiled "display DC" and you would be happy with a 2-monitor hack, i.e. "multimon dc" echoes to "owning" display and "another one" if a window spans both.
If you want to do "real" animation on windows, you will need to move to DirectX. It is also a lot to learn, but much more capable: scene graphs, textures, video, alpha channels, ...

Resources