MFC getting mouse pointer coordinate problem - mfc

I'm trying to get coordinate of the Rect (Picture control) but It's a bit glitchy.
So here're the process that I've done.
1st. made a picture control
2nd. I've earned WindowRect through GetWindowRect
// myDialogDlg.cpp
CRect m_rcDisp // (is acually in myDialogDlg.h)
BOOL myDialogDlg::OnInitDialog()
{
// IDC_PIC1 == ID of the (static) picture control
GetDlgItem(IDC_PIC1)->GetWindowRect(m_rcDisp);
...
}
3rd. I've made OnMouseMove event, and used PtInRect to make some action while mouse pointer is inside the picture control.
// myDialogDlg.cpp
void myDialogDlg::OnMouseMove(UINT nFlags, CPoint point)
{
CString debug;
{
if (m_rcDisp.PtInRect(point))
{
// my event starts
OutputDebugString(_T("here"));
if (m_CamTrig == CAMERA_TRIG_SW)
{
m_CurSor.x = point.x;
m_CurSor.y = point.y;
InvalidateRect(m_rcDisp, NULL);
}
// my event ends
}
}
CDialogEx::OnMouseMove(nFlags, point);
}
and... where actually PtInRect works is about here inside the red box
:( Hope I get the best answer.
thx!

GetWindowRect returns the window dimensions in screen coordinates. WM_MOUSEMOVE reports the mouse position in client coordinates. ScreenToClient1 can be used to translate from screen coordinates to client coordinates, making the picture control's window rectangle coordinates and hit testing function agree on a common origin.
This needs to be done whenever the picture control is moved relative to its parent dialog. If the picture control is never moved you only need to adjust the rectangle once.
This answer links to the Windows API documentation, since it's generally more informative and better maintained than the respective MFC entries. You can call either one from MFC code, though it's usually more convenient to just call into the CWnd members.
1 Use MapWindowPoints instead if you plan on supporting RTL and LTR layouts. See Window Layout and Mirroring for guidance.

Related

How to simulate a mouse click with touch data?

I'm working on a Qt5 application to attempt to make use of raw input data from a touchscreen because touch is not fully supported by my Linux kernel (2.6.32). I wrote a parser for the raw data coming from /dev/input/eventX because even though X doesn't support touch, the events are still there.
I'm able to read the events just fine, and wrote a wrapper called "TouchPoint" which contains the ID and (x,y) coordinates of each touch point, as well as a boolean indicating whether or not the touch point is currently "active" (ie: the user currently has that finger on the screen). It's worth noting that I also output the number of points of touch currently on the screen, and the value is accurate.
My issue is that I can't seem to figure out how to accurately simulate a mouse click with it. With multi-touch events in Linux, each touch point is assigned a "tracking ID" when the user presses a finger to the screen, and when that point is lifted, an event setting that slot's tracking ID to -1 is generated. I use this change of ID from some value to -1 to indicate that a touch point is "down" or not:
void TouchPoint::setID(int nid) {
if((id == -1) && (nid >= 0)) down = true;
else if(nid == -1) down = false;
id = nid;
}
So to try and simulate a mouse click, I do this when a tracking ID event is read:
else if(event.code == ABS_MT_TRACKING_ID) {
bool before = touchPoints[cSlot].isDown(); // where cSlot is the current slot the events are referring to
touchPoints[cSlot].setID(event.value);
bool after = touchPoints[cSlot].isDown();
if(!before && after) touch(touchPoints[cSlot]);
}
And then the touch method does this:
void MainWindow::touch(TouchPoint tp) {
if(ui->touchMe->rect().contains(QPoint(tp.getX(), tp.getY()))) {
on_touchMe_clicked();
}
}
The button does not respond to me directly touching it, but sometimes if I wildly flail my fingers around the screen, the message box that should show when it's pressed will show, but when it does, my fingers are always somewhere on another area of the screen.
Can anyone think of something I might be doing wrong here? Is my method of checking to see if the touch point is within the button's bounds wrong? Or is my logic for keeping track of the "down" status of the touch points wrong? Or something else entirely?
UPDATE: I just noticed two things by outputting the screen coordinates of both the touch location and the button location.
The digitizer resolution is larger than the screen resolution, so I needed to scale the X and Y coordinates coming from the raw events.
My coordinates are offset because they are screen coordinates.
UPDATE 2: I've now dealt with the two issues mentioned in my last update, but I'm still not able to figure out how to accurately simulate a touch/mouse event to be able to interact with the interface. I also modified my TouchPoint class to have a boolean flag indicating whether or not the touch point was just updated, which is set to true when the tracking ID is updated and reset when an EV_SYN event is raised. This is so I can get the X and Y position events before creating the event for the application. I tried the following:
Using the QApplication class to post a mouse event to the QApplication::desktop()->screen() widget that has the position of the touch point.
Using the QTest class to raise a touchEvent to the QApplication::desktop()->screen() widget using the press and release methods for the given slot and position of the touch point and then using the bool event(QEvent*) method to try to catch the event. I also enabled the WA_AcceptTouchEvents attribute on the main window.
Neither of these works, as for some reason, when I have the "bool event(QEvent*)" method in place, the signal emitted from the thread reading /dev/input/eventX doesn't trigger the slot in the main window class. and I can't seem to find any other method to accomplish simulating the events. Anyone have any ideas?
You wrote that you sometimes get your message box "clicked" when your fingers are in a "wrong position" so it sounds like you a mixing global screen coordinates and widget local coordinates. It seems, in your MainWindow::touch you try to check if a point in global screen coordinates, e.g. (530, 815) is inside a widget's geometry (in its local coordinates). QWidget::rect() returns internal geometry of the button (widget), i.e. a rectangle of widget's width and height, e.g. (0, 0, 60, 100).
You have to move the rect to a right position in global screen coordinates. QWidget::pos() returns widget local position to its parent. You can use QWidget::mapToGlobal to translate widget coordinate position to global screen coordinates. Then your rect is something like (500, 850, 60, 100) and you should get a hit and get your slot called.
However, better approach is to use QApplication::widgetAt to get the widget in a specific screen position and generate a mouse click for it.
You can generate a mouse click for a widget by posting a mouse press and release to it, something like below:
QPoint screenPos = QPoint(tp.getX(), tp.getY());
QWidget *targetWidget = QApplication::widgetAt(screenPos);
QPoint localPos = targetWidget->mapFromGlobal(screenPos);
QMouseEvent *eventPress = new QMouseEvent(QEvent::MouseButtonPress, localPos, screenPos, Qt::LeftButton, Qt::LeftButton, Qt::NoModifier);
QApplication::postEvent(targetWidget, eventPress);
QMouseEvent *eventRelease = new QMouseEvent(QEvent::MouseButtonRelease, localPos, screenPos, Qt::LeftButton, Qt::LeftButton, Qt::NoModifier);
QApplication::postEvent(targetWidget, eventRelease);

GetWindowRect coordinates not screen-relative

I am working in Visual Studio 2008 C++. I have an MFC dialog with a control inside it. I am trying to position another dialog in the control.
SetWindowPos() on the second dialog is clearly using screen coordinates, so I need to get the screen coordinates of the control or the parent dialog. The MSDN documentation says GetWindowRect() provides "screen coordinates relative to the upper-left corner of the display screen" but this is NOT what I am getting. On the control it gives coordinates relative to the parent. On the parent it gives left=0 and top=0. I have tried the rectangle from GetWindowPlacement() as well and it gives the same thing. Everything is relative to the parent.
Why is GetWindowRect() not returning screen-relative coordinates? Is there another way to get them?
I'm not new to programming, but fairly new to Windows programming, Visual Studio, and MFC, so I may be missing something obvious.
Here is what I am doing in OnInitDialog for the parent dialog:
// TestApp message handlers
BOOL TestApp::OnInitDialog()
{
CDialog::OnInitDialog();
FILE * pFile = fopen("out.txt","w");
CRect winRect;
GetWindowRect(&winRect);
fprintf(pFile,"left=%li top=%li right=%li bottom=%li\n",winRect.left,winRect.top,winRect.right,winRect.bottom); fflush(pFile);
fclose(pFile);
return TRUE; // return TRUE unless you set the focus to a control
}
When run, the dialog does NOT appear at the upper-left corner of the screen, but out.txt contains:
left=0 top=0 right=297 bottom=400
OnInitDialog is called by the framework, before the dialog is shown. At this point, neither the final size nor position are known:
Windows sends the WM_INITDIALOG message to the dialog box during the Create, CreateIndirect, or DoModal calls, which occur immediately before the dialog box is displayed.
The final size and position of a dialog are the result of window positioning negotiations. The first message sent to a dialog where this information is available is WM_WINDOWPOSCHANGED. Using MFC, this message is handled through CWnd::OnWindowPosChanged. Custom handling code can be implemented by overriding OnWindowPosChanged in your CDialog-derived class.
As written in the other answer:
OnInitDialog is called before the window is moved to its final position. If you call GetWindowRect later you'll see it return the proper coordinates.
Just use PostMessage with a WM_APP+n message. This message will arrive when the message pump is running and the message will arrive when the window is positioned and shown on the screen.
Or use a timer. This has the same effect.
OnInitDialog is called before the window is moved to its final position. If you call GetWindowRect later you'll see it return the proper coordinates.

How does one retrieve selected area from QGraphicsView?

I need my QGraphicsView to react on user selection - that is, change display when user selects area inside it. How can i do that?
As far as i can tell, selection in Qt Graphics framework normaly works through selection of items. I haven't found any methods/properties that touch on selected area, save for QGraphicsVliew::rubberBandSelectionMode, which does not help.
After some going through documentation, i found different solution.
In QGraphicsView there is a rubberbandChanged signal, that contained just the information i wanted to use. So, i handled it in a slot, resulting in the handler of following form:
void
MyImplementation::rubberBandChangedHandler(QRect rubberBandRect, QPointF fromScenePoint, QPointF toScenePoint)
{
// in default mode, ignore this
if(m_mode != MODE_RUBBERBANDZOOM)
return;
if(rubberBandRect.isNull())
{
// selection has ended
// zoom onto it!
auto sceneRect = mapToScene(m_prevRubberband).boundingRect();
float w = (float)width() / (float)sceneRect.width();
float h = (float)height() / (float)sceneRect.height();
setImageScale(qMin(w, h) * 100);
// ensure it is visible
ensureVisible(sceneRect, 0, 0);
positionText();
}
m_prevRubberband = rubberBandRect;
}
To clarify: my implementation zooms on selected area. To that effect class contains QRect called m_prevRubberband. When user stop selection with rubberband, parameter rubberBandRect is null, and saved value of rectangle can be used.
On related note, to process mouse events without interfering with rubber band handling, m_prevRubberband can be used as a flag (by checking it on being null). However, if mouseReleaseEvent is handled, check must be performed before calling default event handler, because it will set m_prevRubberband to null rectangle.
You can use qgraphicsscenemouseevent.
On MousePress save the current position and on MouseRelease you can compute a bounding rect using the current position and the MousePress position.
This gives you the selected area.
If you need custom shapes you could track the mouse movement (MouseMove) to get the shape.
An Example that uses qgraphicsscenemouseevent can be found here.

MFC selection of a user drawn rectangle on picture control

I'm trying to create an interface that allows the user to draw a rectangle over a picture control box. I have a picture control class and used CRectTracker to allow the user to draw a rectangle. I want the user to also be able to select a previously drawn rectangle but I don't know how to handle the selection of a drawn rectangle.
I want to be able to select the rectangle and also add resize handlers on it.
Here is my code for drawing the rect.
void PictureCtrl::OnLButtonDown(UINT nFlags, CPoint point)
{
// If mouse click is outside of rectangle
if(m_drawRect.m_tracker.HitTest(point) < 0 ) {
if(m_drawRect.m_tracker.TrackRubberBand(this, point, TRUE)) {
CDC* pDC = GetDC();
m_drawRect.m_tracker.m_nStyle &= CRectTracker::resizeInside;
// Paint transparent rectangle
pDC->SelectStockObject(NULL_BRUSH);
pDC->Rectangle(m_drawRect.m_tracker.m_rect);
ReleaseDC(pDC);
}
}
CStatic::OnLButtonDown(nFlags, point);
}
Any help would be appreciated. Thank you.
You will need to store the coordinates of the rectangle in your class (also save/load) and perform a HitTest during mouse-down.
To implement the resize handles, you will need a boolean to denote that the rectangle is selected (set boolean to FALSE if the click is not on the rectangle) and draw grab handles during paint if the boolean is TRUE; if the mouse moves over the grab handles, change the mouse-cursor, perform the resize during mouse-down and mouse-up in this case.
It's all quite complicated and gets more so if you have more than just one rectangle!
Here is a DrawCLI MSDN example which does all of that with rectangles, rounded rectangles, ellipses, lines and polylines plus support for OLE -- maybe this will help, it's probably easier to delete classes/functions from DrawCLI before it's in a state to merge with your application...

Drawing in window while resizing leaves Unpainted border

The problem that I have seems to be trivial, but I cannot find a way to solve it. Here it is. I have a window with some graphics in it.
For simplicity lets say it's a solid green rectangle which fills the entire client area of the window. I want this rectangle to be redrawn and to fill the entire window every time the window changes its size. What I did originally was this. I posted WM_PAINT message from WM_SIZE handler.
It works, but if I move mouse fast I see a bit of unpainted (white) area around the green rectangle (actually one or two sides only, close to where mouse is). My understanding of the problem is that system thread which handles user input (mouse) works faster than my handler of WM_PAINT message. It means that by the time I start drawing an updated rectangle (its size is taken from WM_SIZE), mouse actually moves a little bit and system draws a new window frame which is different from what I'm trying to fill with the green. This creates unfilled areas next to borders which move during resizing.
When I stop resizing, green eventually fills the entire window, but during resizing there is a bit of flickering happening close to borders which is annoying. In order to solve the problem I tried the following.
bool finishedPainting;
RECT windowRect;
case WM_PAINT :
// ..... painting here
finishedPainting = TRUE;
break;
case WM_SIZE :
// .... some actions
// posting WM_PAINT
InvalidateRect(hWnd, NULL, FALSE);
PostMessage(hWnd, WM_PAINT, 0, 0);
break;
case WM_SIZING :
// this supposedly should prevent the system from passing
// new window size to WM_SIZE
if (!finishedPainting) memcpy((void*)lParam, &windowRect, sizeof(windowRect));
else {
// remember current window size for later use
memcpy(&windowRect, (void*)lParam, sizeof(windowRect));
finishedPainting = FALSE;
}
return TRUE;
It doesnt' work. As a slight variation, I also tried this.
bool finishedPainting;
POINT cursorPos;
case WM_PAINT :
// ..... painting here
finishedPainting = TRUE;
break;
case WM_SIZE :
if (!finishedPainting) SetCursorPos(cursorPos.x, cursorPos.y);
else {
finishedPainting = FALSE;
GetCursorPos(&cursorPos);
// .... some actions
InvalidateRect(hWnd, NULL, FALSE);
PostMessage(hWnd, WM_PAINT, 0, 0);
}
break;
This also doesn't work. As far as I understand the solution to the problem lies in somehow slowing the mouse down so that it moves to the next position on the screen (dragging the corner or the side of the window with it) only after the painting is finished.
Any ideas how to achieve this? Or maybe there is something fundamentally wrong with the way I see the problem and solution lies somewhere else?
// ====================================================
Update
I did a few experiments and here is what I found
1) When resizing, the sequence of messages is WM_SIZING - WM_NCPAINT - WM_SIZE - WM_PAINT. This looks a bit strange to me. I would expect WM_SIZE to follow WM_SIZING without interruption by WM_NCPAINT
2) In each message handler I was checking the width of a window during resizing (for simplicity I was only changing width). Surprisingly, the width measured in WM_SIZE turned out to be different from the one in WM_SIZING, but the same as in WM_NCPAINT and WM_PAINT. This is not a problem as such, just a wierd fact.
3) I came to the conclusion that there are two major causes for flicker happening near the window borders. The first one is that WM_NCPAINT comes before WM_PAINT. Imagine that you are stretching your window. The new frame will appear first (WM_NCPAINT comes first), then WM_PAINT fills the client area. A human eye catches that short period of time when the new frame is already on the screen, but it is empty. Even if you specify that you don't want window background to be deleted before repainting, still newly added area is empty and you can see it for a split second. This reason for flicker is best demonstrated when you grab the right window edge and move it quickly to the right. The other reason for flickering effect is less obvious and best seen when you grab the left window edge and move it to the left. During this move you will see unfilled areas along the RIGHT edge. As far as I understand the effect is caused by this. When user is doing resize Windows does the following: A) it sends WM_NCPAINT to draw the new frame, B) it copies the content of the old client area into the new top left window corner (in our case it moved to the left), C) it sends WM_PAINT to fill the new client area. However during stage B for some reason Windows produces those unfilled areas along the right edge, although it seems like it shouldn't because the old content should just stay where it is until it gets repainted over during WM_PAINT.
Ok, the question remains - how to get rid of those artefacts during resizing. As far as I can see now it is impossible to do using standard techniques and functions, because they are caused by the sequence of steps Windows performs during resizing. Swapping WM_NCPAINT and WM_PAINT would probably help, but this seems to be beyond our control (unless there is a simple way to do that which I just don't know about).
You shouldn't post or send WM_PAINT messages yourself. Rather, use ::InvalidateRect to invalidate parts of your window and let Windows decide when to send the WM_PAINT messages.
Windows works this way on purpose. It's generally considered more important to be responsive to the user (i.e. the mouse) than to have a fully up-to-date painted window.
If you're always painting the entire window in your WM_PAINT handler, you can eliminate a lot of flicker by overriding the WM_ERASEBKGND handler and return without doing anything.
If you really insist on preferring window updates over mouse responsiveness, replace the InvalidateRect call with a RedrawWindow call using the RDW_UPDATENOW flag.
Edit: Your observations make sense. WM_SIZING comes before the window is resized, to give you a chance to modify the size and/or position. You might try painting the client area in the WM_NCPAINT handler, even before the border is drawn. After you paint it you can validate the client area to keep it from drawing again.
It's a bad idea to manually post WM_PAINT or WM_SIZE. One weird hacky crazy thing you can do though, is to record the new resized co-ordinates in a RECT in WM_SIZE, Use MoveWindow to change the size of the window back to it's previous one, then manually resize it again using MoveWindow in the WM_PAINT message after you've done the painting.
Another possible solution is to constantly fill your window with the color regardless of whether the screen is resized or not. i.e
// in the WinMain function
if (GetMessage(&msg,NULL,NULL,0))
{
TranslateMessage(&msg,NULL,NULL);
DispatchMessage(&msg,NULL,NULL);
}
else
{
// fill your window with the color in here
}
Or, of course, you can just set the background color of your window to green, instead of doing all the painting yourself:
// Before RegisterClass or RegisterClassEx
// wincl is a WNDCLASS or WNDCLASSEX
wincl.hbrBackground = CreateSolidBrush(RGB(50, 238, 50));