I'm writing a control that may have some of its parts transparent or semitransparent. Basically this control shows a png image (with alpha channel). The control's parent window has some graphics on it. Therefore, to make the png control render correctly it needs to get image of what the parent window would draw beneath it. Parent dialog might have WS_CLIPCHILDREN flag set, that means that the the parent window won't draw anything under the the png control and in this case png control won't work properly.
This control has to work on Windows Mobile also, so it can't have WS_EX_TRANSPARENT
Funny you should ask. I just wrote code to do this yesterday. It's in the code base for Project Resistance. Look at how ResistorView (which has a PNG with transparency) interacts with MainForm (which has the background image).
I'm not doing c#, so I don't think that could be helpful. I tried to look into your code and I don't see anything that can solve my problem even though you accomplish the same task as me.
To give more details, my control also supports gif animation and I also used the same IImage load from stream as you do in your project. From my experience, that IImage load from stream is unbelievable junk code, it's extremely slow. I have no idea how that possibly could be so slow. Loading 32x32 gif with 31 frames takes like 1.5secods using that junk IImage stuff. I wrote my own loader (using some opensource gif library) and without any optimization entire decoding/loading of the gif frames takes less than 100ms. I'm using TouchPro2... I can't imagine how badly this code would perform on a low end device.
As a quick alternative here's a possible solution to my question:
in the WM_PAINT handler of the child control that draws images (gif or png) I do the following:
first, I call SetRedraw(false) on self and on parent window.
then I hide the child control, and send WM_PAINT to parent window with optional HDC (as wParam). The parent window renders everything to the offscreen bitmap (passed via hdc to WM_PAINT) and after WM_PAINT successfully returns I take relevant part of the offscreen bitmap.
Then I enable show the child window and call SetRedraw(true) on both child and the parent window. That trick works but has some disadvantages obviously (I need to createa huge offscreen bitmap to capture entire screen area even though I need 32x32 pixel from the middle of parent window).
the code is below:
bool pic_control::get_parent_bg(MyBitmap & bg)
{
CWindow parent = GetParent();
CClientDC dc(parent);
bool is_visible = IsWindowVisible() && parent.IsWindowVisible();
if(!is_visible){
return false;
}
parent.SetRedraw(false);
SetRedraw(false);
CRect rect;
parent.GetClientRect(rect);
MyBitmap bmp;
bmp.create(rect.Width(), rect.Height());
ShowWindow(SW_HIDE);
parent.SendMessage(WM_PAINT, (WPARAM)(HDC)bmp.dc());
ShowWindow(SW_SHOW);
GetWindowRect(rect);
parent.ScreenToClient(rect);
bg.create(rect.Width(), rect.Height());
bg.dc().BitBlt(0, 0, rect.Width(), rect.Height(), bmp.dc(), rect.left, rect.top, SRCCOPY);
IF_DEBUG SAL::saveHBITMAPToJpeg(bg.GetBitmap(), "frames/BG.jpg", 100);
SetRedraw(true);
parent.SetRedraw(true);
return true;
}
WS_CLIPCHILDREN is forced on in WinCe, you cannot toggle it. I dont know why, maybe it is done for performance reasons.
From my experience what i did in this situation.
1) If the parent window bacgkround is dynamic (for instance window that contains map, which can be moved), then it is painted to memory canvas first, then to the screen, and memory canvas is saved and used for painting transparent childs. Memory canvas would not contain holes in place of child windows, so it can be used for futher pixel merge. The disadvantage here is memory consumption to hold canvas in memory.
2) If parent window background is static (dialog box, menu, etc) then you can make non-window childs.
class CImageButton
{
public:
bool IsPointInside(POINT pt);
void OnPaint(HDC canvas);
void OnClick();
void SetRect(RECT& rc);
private:
RECT m_rc;
};
Your parent window will contain an array of such objects and redirect WM_PAINT and
mouse clicks to them.
The disavantage is additional code needed to be added to parent window, but you can make a base class for all your parent windows, which would handle issues with non-windowed controls.
Related
I'm currently trying to build a player literally from scratch using Media Foundation Interfaces.
I use the example taken from here:
https://msdn.microsoft.com/en-us/library/windows/desktop/dd979592(v=vs.85).aspx
As the basis for the player.
I want to add buttons (back/forward button , progress bar etc.) to the video screen window, but it isn't so clear to me how.
I can not simply add a button as a child of the main window because it is hidden by the player, I tried to add the buttons as children of Video window but it did not work ...
What is the right way to add buttons to a video window?
This is part of the code where you register to the player window (to the full code please enter the link I have attached).
// create the instance of the player hwnd = handle of main window
HRESULT hr = CPlayer::CreateInstance(hwnd, hwnd, &g_pPlayer);
// inner call to cplayer
CPlayer *pPlayer = new (std::nothrow) CPlayer(hVideo, hEvent);
// Create a partial topology. (m_hwndVideo == hVideo == hwnd)
hr = CreatePlaybackTopology(m_pSource, pSourcePD, m_hwndVideo, &pTopology);
i try to use this function in order to change video paint so i will have some
place left at the bottom to put there my buttons, here is my function:
VOID update_lower_window(HWND hwnd)
{
PAINTSTRUCT ps;
HDC hdc = BeginPaint(hwnd, &ps);
RECT rc;
GetClientRect(hwnd, &rc);
//rc = father size
RECT repaint;
memcpy(&repaint, &rc, sizeof(RECT));
repaint.top = repaint.bottom - DOWN_SPACE; // DOWN_SPACE == 50
// paint back bottom to white
FillRect(hdc, &repaint, (HBRUSH)COLOR_WINDOW);
EndPaint(hwnd, &ps);
}
i call this function when:
WM_PAINT happend
WM_SIZE happend
WM_CREATE happend
With this said I still could not achieve my goals:
When the movie starts playing it starts full screen and does not leave the space I wanted down (I can not seem to find the message sent when the movie is played or in other words when the movie paint on the screen it's not sent one of the messages on which I inserted my function)
When I reduce the screen, the remnants of the previous screen still remain down and I can't see the background.
When using the Minimize button it get the same phenomenon as in 2.
Another interesting detail The phenomenon I mentioned in 2 occurs only when I change window size by drag from the bottom up, if I change window size by drag from one corner (enlarges or decreases height and width simultaneously) then the window corrects itself and I see in the bottom white part (the background) all the time.
You can reposition video area so your win32 GUI (buttons and progress) is below. See WM_SIZE handler in that sample for how to do it, they calculate rectangle to occupy the window, you can adjust to accommodate your controls.
If you want translucent overlaid controls you can write a EVR Presenter.
https://msdn.microsoft.com/en-us/library/windows/desktop/bb530107(v=vs.85).aspx
https://github.com/Microsoft/Windows-classic-samples/tree/master/Samples/Win7Samples/multimedia/mediafoundation/evrpresenter
But that’s relatively hard.
P.S. If you just want a simple solution, use something higher-level. Like Windows Media Player control note they have a C++ example, or .NET for GUI and MediaElement in it.
Update: I think the right place to set initial video position in that sample is likely CPlayer::OnTopologyStatus, after MFGetService(MR_VIDEO_RENDER_SERVICE) line.
Anyway, another approach that might be simpler for your task, create a child window dedicated to the video. Position it so it occupies the majority of your app’s main window, and only leaves bottom 50px unoccupied. Don’t forget to handle at least WM_SIZE to reposition and ideally also WM_DISPLAYCHANGED, WM_ENDSESSION, WM_GETMINMAXINFO. Setup the MF playback so that video occupies the complete child video window.
This will ensure the video won’t interfere with Win32 stuff you’re painting on your bottom 50px panel.
BTW that’s what MPC-HC is doing, see the pic.
As you see on Spy++ screenshot, they have dedicated video window, and also other child windows for Win32 controls of the player.
I am currently developing a kind of "WinMerge" clone, and currently I am trying to implement a custom scrollbar which should later represent both compared files as a rectangle in the background each.
This is what it looks like at the startup:
However, after scrolling around a little bit, this is what I end up with:
As you can clearly see, only those parts look correct which I explicitely paint over in my paint routine:
void LocationPane::OnPaint(CDCHandle dc)
{
DefWindowProc();
dc = GetDC();
DrawLocationPaneFigures(dc);
}
This is how my control is configured in my .rc file:
CONTROL "",IDC_LOCATIONPANE,"Static",SS_OWNERDRAW | SS_NOTIFY | WS_BORDER | WS_GROUP,7,21,91,541
As you can see, it is an owner drawn control.
How can I erase the background for this control while repainting it?
A static control with SS_OWNERDRAW style receives a WM_DRAWITEM message when it needs to be redrawn.
So first you need to replace your OnPaint() handler by a handler for WM_DRAWITEM. Instead of calling GetDC() use the device context supplied to you in the DRAWITEMSTRUCT.
To erase the background it's generally best to do it as part of the regular painting code to reduce flickering (by calling FillRect() for instance).
I suggest to always draw the whole client area of your control. Then you may handle WM_ERASEBKGND to return TRUE without calling DefWindowProc() to reduce flickering even more.
I'm programming an MFC application. I created a CStatic derived control on my Form dialog, so that I see the form directly when starting the program. I'm now drawing in this control with the OnPaint() Method in the derived class. So far, everything works.
However, strangely the drawings aren't clipped outside of the control. Instead,the whole form is painted if I draw really big things. In my understanding, only the control should be painted.
Thanks in advance
Andreas D.
#dlb Got it now.
CDC* pDC = GetDC();
CRect rClient();
GetClientRect(rClient);
CRgn ClipRgn;
if (ClipRgn.CreateRectRgnIndirect(&rClient))
{
pDC->SelectClipRgn(&ClipRgn);
}
// Drawing content
pDC->SelectClipRgn(NULL);
ReleaseDC(pDC);
Thanks for your answer
Check if your control is using the 'Unclipped Device Context' option. You can check this by getting the control flags.
You can also call IsOptimizedDraw() and if that return true then your drawing can draw outside the control.
Consider a plain Win32 dialog with listview control (in report mode) written in C++. Upon a certain event all items and all columns are deleted and new columns and items are created. Basically, as content changes, columns are automatically generated based on content.
When old items/columns are removed and new ones added, listview flickers like hell. I have tried WM_SETREDRAW and LockWindowUpdate() with no change to visual experience.
I have even set extended listview style LVS_EX_DOUBLEBUFFER and that didn't help at all.
The parent dialog has WS_CLIPCHILDREN set.
Any suggestions how to make this work with as little flicker as possible? I am thinking of using two listviews, alternating visibility, using the hidden one as a back buffer but this sounds like an overkill. There must be an easy way.
The default list control painting is pretty flawed. But there is a simple trick to implement your own double-buffering technique:
CMyListCtrl::OnPaint()
{
CRect rcClient;
GetClientRect(rcClient);
CPaintDC dc(this);
CDC dcMem;
dcMem.CreateCompatibleDC(&dc);
CBitmap bmMem;
bmMem.CreateCompatibleBitmap(&dc, rcClient.Width(), rcClient.Height());
CBitmap* pbmOld = dcMem.SelectObject(&bmMem);
dcMem.FillSolidRect(rcClient, ::GetSysColor(COLOR_WINDOW));
this->DefWindowProc(WM_PAINT, (WPARAM)dcMem.m_hDC, (LPARAM)0);
dc.BitBlt(0,0,rcClient.Width(), rcClient.Height(), &dcMem, 0, 0, SRCCOPY);
dcMem.SelectObject(pbmOld);
CHeaderCtrl* pCtrl = this->GetHeaderCtrl();
if (::IsWindow(pCtrl->GetSafeHWnd())
{
CRect aHeaderRect;
pCtrl->GetClientRect(&aHeaderRect);
pCtrl->RedrawWindow(&aHeaderRect);
}
}
This will create a bitmap and then call the default window procedure to paint the list control into the bitmap and then blitting the contents of the bitmap into the paint DC.
You should also add a handler for WM_ERASEBKGND:
BOOL CMyListCtrl::OnEraseBkgnd(CDC* pDC)
{
return TRUE;
}
This will stop the control from always erasing the background before a redraw.
You can optimize the OnPaint further if you add a member variable for the bitmap and only (re)create it when the size of the window changed (because always creating a bitmap may be costly depending on the size of the window).
This should work pretty well.
After trying many things and most of all humbagumba's suggestions I have come to a very simple conclusion. LockWindowUpdate is everybody's friend in this sort of situation. I am not sure how come it failed to work for me the first time but after custom painting failed to deliver in all situations I have tried LockWindowUpdate once again and it worked!
Basically, just wrap all work on listview in a LockWindowUpdate(hWnd) and LockWindowUpdate(NULL) and things work beautifully. There is not even a scrollbar flicker any more.
Just make sure not to nest LockWindowUpdate as only one window can be locked at a time.
I am currently writing a program in c++ (no MFC) and want to update a label (win32 static control) using the win32 DrawText function. However when i call the function nothing is written to the label. I use the following code:
HDC devCon = ::GetDC(GetDlgItem(IDC_TITLE).m_hWnd);
RECT rect = {10, 10, 100, 15};
::DrawText(devCon, _T("TEST DC TEXT!!!"), -1, &rect, DT_NOCLIP);
::ReleaseDC(GetDlgItem(IDC_TITLE).m_hWnd, devCon);
As you see with the GetDlgItem(...) I am using ATL but that should not be a problem in my opinion. When I specify NULL in the GetDC method the text is drawn in the upper left corner of the screen as it is supossed to be since the method return the DC to the entire screen.
Why doesn't this work with the DC of the label?
Hope you folks can help me.
If you want to draw the text manually because setting the control text doesn't do what you want, then you need to tell Windows that you're doing that. Otherwise the control will draw itself over whatever you do whenever it needs to be redrawn.
To draw it yourself, mark your control as owner draw by setting the SS_OWNERDRAW style, and then handle the WM_DRAWITEM message to draw it in the window procedure of the parent window, or subclass the window and handle the WM_PAINT message in your new window procedure.
I guess that the text is drawn but at the next window message is set to the default text.
Try to set the text with SendMessage(..,WM_SETTEXT,...);
Use SetDlgItemText() to set the text for the control.
You are trying to paint directly onto the static control's device context.
This is not going to be so simple, because:
the control will repaint itself whenever it's update region is invalidated
usually the controls share the device context with the parent window, so what you are getting in GetDC(...) is actually your dialog's device context.
So, use SetDlgItemText, or SetWindowText to set the text of the window.
To use a custom font (or set the text/background color), handle the WM_CTLCOLORSTATIC message in your WindowProc.