I'm using BeginBufferedAnimation() during WM_PAINT to animate a custom control (cross-fade).
I know when the first frame of the animation is painted; when I call EndBufferedAnimation() with fUpdateTarget = TRUE.
I can determine if the animation is in progress by checking the return value from BufferedPaintRenderAnimation().
Question is: how do I know when the last frame of the animation is painted?
I couldn't find an API call that can do this.
One thing that comes to mind, is to use GetTickCount() to determine the time that elapsed since the call to EndBufferedAnimation() and check that against the dwDuration member of the BP_ANIMATIONPARAMS struct.
However, I'm not sure if this method is very accurate.
Is there another (foolproof) way to determine if WM_PAINT is painting the last frame of the animation?
EDIT
Obviously, checking the elapsed time doesn't work when the animation is prematurely stopped by a call to BufferedPaintStopAllAnimations() or invalidating the part of the client area that is being animated.
EDIT 2
This is my (simplified) WM_PAINT code:
PAINTSTRUCT ps;
HDC DC = BeginPaint(Handle, &ps);
if (DC != NULL)
{
// If animation in progress, paint the next frame.
if (!BufferedPaintRenderAnimation(Handle, DC))
{
BP_ANIMATIONPARAMS ap = {sizeof(ap)};
ap.style = BPAS_LINEAR;
ap.dwDuration = 1000;
RECT R;
GetClientRect(Handle, &R);
HDC hdcFrom, hdcTo;
HANIMATIONBUFFER Animation = BeginBufferedAnimation(Handle, DC, &R, BPBF_COMPATIBLEBITMAP, NULL, &ap, &hdcFrom, &hdcTo);
if (Animation)
{
if (hdcFrom) Paint(hdcFrom, &R, OldState);
if (hdcTo ) Paint(hdcTo, &R, NewState);
// Paint the first frame.
EndBufferedAnimation(Animation, true);
}
else
Paint(DC, &R, NewState);
}
else
{
// Here, I want to check if BufferedPaintRenderAnimation() painted the last frame.
}
EndPaint(Handle, &ps);
}
EDIT 3
My custom control has a child control, which needs to be animated in sync with its parent. For this purpose, I'm letting the child control paint itself into the first and last animation frame by sending it a WM_PRINT message inside my Paint() method. The child control receives its WM_PAINT message after the parent is painted. To prevent the child control from painting while the parent is being animated, I wanted to hide the child control when I start the animation and show it again after the animation finished.
Since I couldn't find a foolproof way to detect when the animation finishes, I decided not to hide the child control. Instead, I avoid repainting the child control with RedrawWindow(ChildHandle, NULL, NULL, RDW_VALIDATE | RDW_NOFRAME) after the call to EndBufferedAnimation() and when BufferedPaintRenderAnimation() returns true. This way,
BufferedPaintRenderAnimation() paints the child control in its final state when the animation finishes, so the child control doesn't have to.
I found a better solution to my problem.
See EDIT 3.
Related
I have CMFCRibbonBar control. I need to create my custom tooltip. My tooltip derives from CMFCToolTipCtrl and works quite well. But...
When I hover a ribbon button, tooltip shows up. That's great. But when I move the mouse out of the button, tooltip is closed. That is not what I want. I just need to be able to move the mouse on the tooltip and click the link that is on the tooltip. Imagine this is some kind of interactive tooltip. What can I do to achieve that?
OK, I've done something that is useful, but the outcome is not satisfying 100%.
So, first of all, create your own tooltip, inheriting from CMfcToolTipCtrl.
The idea is that:
- user may want to interact with your tooltip, or not. So we have to create some smart way from closing and showing the tooltip.
- We can assume, that when user hovers the tooltip with mouse, then he wants to interact.
Unfortunately whenever user moves the mouse from the ribbon button, the tooltip dissapears. But sometimes we can catch MouseMove inside it. But it's rather rare. So, we have to get the moment, when tooltip is closed by a system.
There is such a message that we can add to message map:
ON_NOTIFY_REFLECT(TTN_POP, &CAsInteractiveToolTip::OnPop)
Now, our OnPop will look like that (I am using pImpl idiom):
void CAsInteractiveToolTip::OnPop(NMHDR* pNMHDR, LRESULT* pResult)
{
if (m_pImpl->m_forceClose)
{
CMFCToolTipCtrl::OnPop(pNMHDR, pResult);
m_pImpl->m_forceOpened = false;
m_pImpl->m_forceClose = false;
m_pImpl->StopForceOpenTimer();
}
else
{
m_pImpl->StartForceOpenTimer();
}
*pResult = 0;
}
Now, what's happening here is:
- when tooltip is being closed, check if it's force closed by our code. If not, it means that it's closed by system. In such case, we have to give the user a chance to hover the mouse over our tooltip. So, we have to show the tooltip again (force it to show). This is done in timer method. StartForceOpenTimer is simple method that starts the timer:
void StartForceOpenTimer()
{
if (!m_forceOpenTimerActive)
{
m_self.SetTimer(IDT_FORCE_OPEN_TIMER, 100, (TIMERPROC)NULL);
m_forceOpenTimerActive = true;
}
}
Now, the magic starts in timer method:
void CAsInteractiveToolTip::OnForceTimer()
{
static DWORD waitForUserStartTime = 0;
static bool waitingForUserReaction = false;
if (!waitingForUserReaction)
{
//open and give the user chance to mouse move over it within 0.5 seconds
SetWindowPos(&wndTopMost, -1, -1, -1, -1, SWP_NOMOVE | SWP_NOSIZE | SWP_NOACTIVATE | SWP_SHOWWINDOW);
waitForUserStartTime = GetTickCount();
waitingForUserReaction = true;
return;
}
if (GetTickCount() - waitForUserStartTime > 500)
{
m_pImpl->StopForceOpenTimer();
m_pImpl->m_forceClose = true;
waitingForUserReaction = false;
m_pImpl->PopToolTip();
return;
}
if (m_pImpl->m_doForceOpen)
{
m_pImpl->StopForceOpenTimer();
waitingForUserReaction = false;
m_pImpl->m_forceOpened = true;
}
}
Overall idea is:
- force to show the tooltip
- wait about 0.5 second for a user to hover the mouse
- if user hovers the mouse over tooltip window, we can assume that he wants to interact. So we can leave the window opened.
- if user doens't interact with the window within 0.5 second, just close the tooltip again.
Now, PopToolTip method just starts another timer with interval of 100 ms.
And here is the other part of the magic:
void CAsInteractiveToolTip::OnPopTimer()
{
m_pImpl->StopForceOpenTimer();
KillTimer(IDT_POP_TIMER);
//Pop();
m_pImpl->m_forceClose = true;
m_pImpl->m_hdr.idFrom = 2;
m_pImpl->m_hdr.hwndFrom = GetSafeHwnd();
m_pImpl->m_hdr.code = (int)TTN_POP; //4294966774
GetParent()->SendMessage(WM_NOTIFY, 1, (LPARAM)&m_pImpl->m_hdr);
//GetParent()->SendMessage(WM_NOTIFY, 2, (LPARAM)&m_pImpl->m_hdr);
ShowWindow(SW_HIDE);
}
Now, this method should just pop (hide) the tooltip. But for some reason in my case calling Pop() method does nothing. So I would have to send WM_NOTIFY message with appropriate parameters (they are taken from my debug observations).
Now, OnPop will start again, but this time m_forceClose is set to true, so the tooltip will not show again (the first timer will not run).
Now the third part of the magic - Mouse Move. Just add it to your message map:
ON_WM_MOUSEMOVE()
And the method:
void CAsInteractiveToolTip::OnMouseMove(UINT nFlags, CPoint point)
{
m_pImpl->m_doForceOpen = true; //let the first timer know, that user wants to interact
CMFCToolTipCtrl::OnMouseMove(nFlags, point);
}
And you can just hide the tooltip when user clicks on it. Just:
void CAsInteractiveToolTip::OnLButtonDown(UINT nFlags, CPoint point)
{
m_pImpl->m_forceClose = true;
m_pImpl->PopToolTip();
}
This is not the ideal solution, but it somehow works. If anyone has any suggestions, I will be happy to hear them :)
So basically am learning OpenGL and the GLFW libraries from the tutorial on page: http://www.opengl-tutorial.org/beginners-tutorials/tutorial-6-keyboard-and-mouse/
My problems is with this less lesson showing the control of camera movement with mouse.
Basicaly it makes the application to get "FPS" like camera, with disabled cursor being moved on center of screen with each frame. But the camera gets crazy when we lose focus on the window and then it regains. For example if we click on the window to regain focus away from the middle of view, the camera will be moved by big amount. I tried to fix this issue with adding window focus callback:
void window_focus_callback(GLFWwindow* window, int focused){
if (focused)
{
//center mouse on screen
int width, height;
glfwGetWindowSize(window, &width, &height);
glfwSetCursorPos(window, 1024 / 2, 768 / 2);
windowFocused = true;
}
else
{
windowFocused = false;
}
And in the main application loop:
if(windowFocused) computeMatricesFromInputs();
But for some reason this solution doesnt work.
Is there any way to fix this issue using glfw?
Question is a bit old, but I recently suffered a similar issue. So just sharing, more solutions exist.
I use GLFW_CURSOR_DISABLED. In this mode the mouse position is not (yet) updated when you receive the 'on' focus event, so call to GetCursorPos delivers the previous value. The new cursor position arrives in the MouseMove callback AFTER the 'on' focus event.
I solved it by keeping track of the regain of focus and use this int he OnMouseMove callback to either dispatch a MouseInit (to snap the cursor) or a regular MouseMove.
This way I can ALT+TAB out of my window and return with the cursor somewhere else without nasty jumps/rotations of the camera.
void InputManagerGLFW::Callback_OnMouseMove(
GLFWwindow* window,
double xpos, //
double ypos) //
{
if (!mFocusRegained)
{
mMouseBuffer.Move(xpos, ypos);
}
else
{
mFocusRegained = false;
mMouseBuffer.Init(xpos, ypos);
}
}
void InputManagerGLFW::Callback_OnFocus(
GLFWwindow* window,
int focused)
{
if (focused)
{
// The window gained input focus
// Note: the mouse position is not yet updated
// the new position is provided by the mousemove callback AFTER this callback
Log::Info("focus");
// use flag to indicate the OnMouseMove that we just regained focus,
// so the first mouse move must be handled differently
mFocusRegained = true;
// this will NOT work!!!
// double x,y;
// glfwGetCursorPos(window,&x,&y);
// mMouseBuffer.Init(x,y);
}
else
{
// The window lost input focus
Log::Info("focus lost");
}
}
I want to show on the screen some value, that are changeable. I have following code
void CMainWnd::OnPaint()
{
CPaintDC dc(this);
CRect rcText( 0, 0, 500 ,500 );
wchar_t text[36];
unsigned int num = server->GetNumClients(num);
wsprintf(text, L"Number of connected clients: %d", num);
dc.DrawText(text, &rcText, DT_LEFT);
CFrameWnd::OnPaint();
}
void CMainWnd::OnTimer(UINT timerID)
{
SendMessage(WM_PAINT, 0, 0);
}
It draws text when window appears. But in next calls when text is different the text on the screen didn't change. Using the debugger I can see that OnPaint was called, text has been changed, but on my window text remains the same. GetLastError() returns 0. I think I'm missing something important how DrawText works. I tried to call UpdateWindow() in the end, but nothing changed. For some reason screen is not updates..
You should not send a paint message directly, but instead invalidate the area to be repainted (InvalidateRect(&area) ) and let the system handle it. By only sending a paint, you don't get anything because the system says 'There's no area that needs painting, so for efficiency I won't bother' - or rather, the clip area that constrains painting will be empty (no update area). By invalidating an area you tell the system that that area needs repainting, so the next paint call will have a valid clip area and you will see a change.
(Better to use wsprintf_s() with the buffer size - in fact, as you appear to be using MFC use CString and CString::Format() instead - and you should not call the base class OnPaint() (it has no effect, since when the CPaintDC goes out of scope it clears any update area).
I have a program which draw a Rectangle under mouse cursor and show the pixel color, but I can't manage it to clear the shape inside the while loop, if I use 'InvalidateRect()' it clear rectangle too fast and flickering, if not use 'InvalidateRect()' then Rectangle keep duplicating like THIS, how to fix that?
HWND hwnd;
POINT p;
unsigned short R=0, G=0, B=0;
void drawRect()
{
GetCursorPos(&p);
HDC hdc = GetDC(NULL);
HPEN border = CreatePen(PS_SOLID, 2, RGB(0, 0, 0));
HBRUSH background = CreateSolidBrush(RGB(R, G, B));
SelectObject(hdc, border);
SelectObject(hdc, background);
Rectangle(hdc, p.x+10, p.y+10, p.x+40, p.y+40);
DeleteObject(border);
DeleteObject(background);
}
void init()
{
while (GetAsyncKeyState(VK_RBUTTON) & 0x8000)
{
grabPixel(); //get RGB color from cursor coordination
drawRect(); //draw preview rectangle under cursor
InvalidateRect(hwnd, NULL, true);
}
}
Note: it doesn't have WinMain() or WndProc()
There are all sorts of things wrong with this. What are you actually trying to do?
From the fact that you're using GetDC(NULL), it looks like this is supposed to be drawing a rectangle on the entire screen.
Where is the hwnd value coming from? If that window does have a message loop (and it probably does), then that's the window being invalidated and redrawing itself.
A note: InvalidateRect merely marks the rectangle as needing-to-be-painted the next time that that application's (actually thread's, more-or-less) message queue is empty. UpdateWindow will cause a WM_PAINT message to be sent immediately.
drawRect isn't cleaning up properly, either. It should call ReleaseDC when it's finished, and it ought to restore the previous drawing objects after it's finished (and most definitely before it deletes them) as well:
HBRUSH oldBackground = SelectObject(hDC, background);
// ...
SelectObject(hDC, oldBackground);
What you probably want to do is, when selection starts, create a window the size of the screen and copy the existing screen into it. Then you can draw all over that intelligently.
The DrawDragRect function (see my blog) is designed for this sort of thing.
At first my code set up the SDL environment, and proceeded to update the OpenGL context, without performing any SDL_Event processing whatsoever. This causes the window, as long as it was open, to appear to Windows to be unresponsive. The window flickers a bit. The titlebar would get "(Not Responding)" appended to it, and upon clicking inside the window it becomes grayed out, as Windows does this by default on non responsive windows. However in this state (even as and after it becomes grayed out), the OpenGL display continues to update and animate, and here's the kicker, it even does so while the window is being dragged. Clearly in this case the application isn't handling events from windows correctly, causing windows to think that it is in a hanged state. But there is clear evidence that the opengl continues to render.
Now I make one single modification to the code, which is these three lines placed in an appropriate spot inside the loop (which also does the OpenGL draw):
SDL_Event event;
if (SDL_PollEvent(&event) && event.type == SDL_QUIT)
break;
All this is doing is flushing the message queue using SDL.
Now the behavior is that Windows no longer thinks it is "Not Responding" and it does not get grayed out. No flicker. Everything seems to run swimmingly. But once I click and drag the title bar to drag the window, rendering gets blocked. I haven't debugged it to be sure, but I suspect that SDL_PollEvent blocks for the duration of the window drag.
Is there a way around this? This is interesting because part of the behavior exhibited by failing to handle events is proof that what I want is possible in theory.
Update: I found this thread: http://www.gamedev.net/topic/488074-win32-message-pump-and-opengl---rendering-pauses-while-draggingresizing/
The verdict seems to be that it comes down to certain choices that Microsoft made for us... It basically gets stuck in DefWindowProc() till the mouse is released. It would get very messy to hack a fix for this and I might be able to do a work around by rendering in another thread. But I don't even want to begin to think about juggling an OpenGL context from multiple threads, if that's even something that's possible.
Some workaround that works for me - add event filter for SDL_WINDOWEVENT_SIZE_CHANGED event and do additional SetViewport and draw frame.
int SDLApp::eventFilter(void* pthis, const SDL_Event *event)
{
if (event->type == SDL_WINDOWEVENT &&
event->window.event == SDL_WINDOWEVENT_SIZE_CHANGED)
{
SDLApp* app = (SDLApp*)pthis;
// Note: NULL rectangle is the entire window
SDL_RenderSetViewport(app->renderer_, NULL);
app->DrawFrame();
}
return 1;
}
...
SDL_SetEventFilter((SDL_EventFilter)SDLApp::eventFilter, this);
This question is old, but the solution I'm using doesn't seem to be mentioned anywhere else, so here it is.
I got my inspiration from this answer, and it doesn't use additional threads.
#include <SDL.h>
#define WIN32_LEAN_AND_MEAN
#define NOMINMAX
#include <Windows.h>
#include <SDL_syswm.h>
#define SIZE_MOVE_TIMER_ID 1
bool sizeMoveTimerRunning = false;
int eventWatch(void*, SDL_Event* event) {
if (event->type == SDL_SYSWMEVENT) {
const auto& winMessage = event->syswm.msg->msg.win;
if (winMessage.msg == WM_ENTERSIZEMOVE) {
// the user started dragging, so create the timer (with the minimum timeout)
// if you have vsync enabled, then this shouldn't render unnecessarily
sizeMoveTimerRunning = SetTimer(GetActiveWindow(), SIZE_MOVE_TIMER_ID, USER_TIMER_MINIMUM, nullptr);
}
else if (winMessage.msg == WM_TIMER) {
if (winMessage.wParam == SIZE_MOVE_TIMER_ID) {
// call your render function
render();
}
}
}
return 0;
}
// rendering function
void render() {
/* do your rendering here */
}
// event loop - call this function after setting up your window to start the event loop
void eventLoop() {
SDL_AddEventWatch(eventWatch, nullptr); // register the event watch function
SDL_EventState(SDL_SYSWMEVENT, SDL_ENABLE); // we need the native Windows events, so we can listen to WM_ENTERSIZEMOVE and WM_TIMER
while (true) {
SDL_Event event;
while (SDL_PollEvent(&event)) {
if (sizeMoveTimerRunning) {
// modal drag/size loop ended, so kill the timer
KillTimer(GetActiveWindow(), SIZE_MOVE_TIMER_ID);
sizeMoveTimerRunning = false;
}
/* handle the events here */
}
render();
}
}
Of course, if your rendering function needs to keep additional state (e.g. if you're using OOP), use the void* parameter of eventWatch(void*, SDL_Event*) to pass the state.
I had a similar problem in which it would freeze video playback when the window was dragged or resized. The solution I found was to spawn a separate thread for rendering and use the main thread for input.
Example:
DWORD RenderThread(SDL_Window* window)
{
//Rendering stuff here...
}
int main()
{
SDL_Init(SDL_INIT_EVERYTHING);
SDL_Window* window = SDL_CreateWindow("Title Here",
SDL_WINDOWPOS_UNDEFINED, SDL_WINDOWPOS_UNDEFINED, h, w, SDL_WINDOW_RESIZABLE);
HANDLE hRenderThread = CreateThread(NULL, 0, (LPTHREAD_START_ROUTINE)RenderThread, window, 0, NULL);
SDL_Event event;
while (1)
{
SDL_PollEvent(&event);
switch (event.type)
{
//Event handling here...
}
}
}
Keep in mind that you MUST create the window in the thread that does event handling. If not it won't work. You can create the window in your event handling thread then pass that window pointer to your rendering thread.
I propose you created 2 threads:
Thread 1: loops calling SDL_PollEvent() (without rendering anything)
Thread 2: does OpenGL rendering (without calling SDL_PollEvent())
This way, your OpenGL context would be manipulated from a single thread. The whole solution has a minimum impact the architecture of your application.
Many windows procedures run a separate message loop until a certain event occurs, so you shouldn't rely on your main loop to do the drawing. If possible, application logic and rendering should always be handled in a separate thread.
Your main thread (that only handles message processing) doesn't need GL context at all, so you wouldn't need to worry about sharing.