Could anyone point me to (or provide?) some nice, clear examples of how to implement scrolling in Win32? Google brings up a lot of stuff, obviously, but most examples seem either too simple or too complicated for me to be sure that they demonstrate the right way of doing things. I use LispWorks CAPI (cross-platform Common Lisp GUI lib) in my current project, and on Windows I have a hard-to-figure-out bug relating to scrolling; basically I want to do some tests directly via the Win32 API to see if I can shed some light on the situation.
Many thanks,
Christopher
I think you are talking for an example how to handle WM_VSCROLL/WM_HSCROLL event. If so first step is to handle that event. You shouldn't use the HIWORD(wParam) value of that call but use GetScrollInfo, GetScrollPos, and GetScrollRange functions instead.
Following is an example code snipped by MSDN - Using Scroll Bars. xCurrentScroll is determined before by calling GetScrollPos() for example.
int xDelta; // xDelta = new_pos - current_pos
int xNewPos; // new position
int yDelta = 0;
switch (LOWORD(wParam)) {
// User clicked the scroll bar shaft left of the scroll box.
case SB_PAGEUP:
xNewPos = xCurrentScroll - 50;
break;
// User clicked the scroll bar shaft right of the scroll box.
case SB_PAGEDOWN:
xNewPos = xCurrentScroll + 50;
break;
// User clicked the left arrow.
case SB_LINEUP:
xNewPos = xCurrentScroll - 5;
break;
// User clicked the right arrow.
case SB_LINEDOWN:
xNewPos = xCurrentScroll + 5;
break;
// User dragged the scroll box.
case SB_THUMBPOSITION:
xNewPos = HIWORD(wParam);
break;
default:
xNewPos = xCurrentScroll;
}
[...]
// New position must be between 0 and the screen width.
xNewPos = max(0, xNewPos);
xNewPos = min(xMaxScroll, xNewPos);
[...]
// Reset the scroll bar.
si.cbSize = sizeof(si);
si.fMask = SIF_POS;
si.nPos = xCurrentScroll;
SetScrollInfo(hwnd, SB_HORZ, &si, TRUE);
Here's one, ScrollCall, (copy from page):.
ScrollCall is a demo program that takes a sample of Windows standard
controls, along with a standard GDI image, and arranges them on a
Device Context (or DC), in a window. Depending on the dimensions of
the image, and the size of the containing window, horizontal and/or
system scrollbars become visible, to enable scrolling for the image
and controls. Thus ScrollCall is as at least as much focused on sizing
as it is scrolling, and both offer unique challenges for the
programmer.
ScrollCall features:
System scroll bars
Optional groupbox
Button to open images on the Device Context (DC)
Radio options for choice of window scroll function
Checkbox to stretch rather than scroll the image
Label Paint Mult with UpDown and Buddy to increase the wait times of WM_SIZE during sizing, thus reduced WM_PAINT processing
Right click for system snapshot of view in default or monitor attached to desktop
Double-click to print the visible part of the (mostly empty) client window to the DC, and back to the client window (experimental)
ScrollCall temporarily turns on SPI_SETDRAGFULLWINDOWS for the testing of the visual effects of dragging, if ever it was toggled off
Compatibility with AeroSnap sizing
Related
The status bar window of this program needs to be updated every time the user press a key that is likely to move the caret of the EDIT control, and the code below works like a charm! In a nutshell, pressing a key on the keyboard will update some values and send a message "ECM_GETLINEINFOS" that is next processed in the main window procedure (code below)
However, there is flickering that is not disturbing, of course, but I wonder if it's related to how I set the text on the status bar (maybe too many updates ?) or just a problem with the drawing part.
PS: The flickering occurs on the text, not the status bar in itself, so that is why I'm questioning how I should manage the update of my window.
constexpr int failed_val = -1;
LRESULT MainWindow::HandleMessage(UINT msg, WPARAM wParam, LPARAM lParam)
{
switch (msg)
{
// Custom message sent by an EDIT control, I
// use this message to tell the status bar it must update its text.
case CEM_GETLINEINFO:
{
const size_t buffSz = 24;
std::wstring buffer(buffSz, L'\0');
int line = LOWORD(wParam);
int column = HIWORD(wParam);
int count = _snwprintf_s(buffer.data(), buffer.size(),
_TRUNCATE, L"Ln %d, Col %d", line, column);
if (count != failed_val) {
// Param 1 : The text to be displayed
// Param 2 : Which status bar part
m_statusBar->SetText(buffer, 0);
}
}
return 0;
}
}
Just as Flicker-Free Displays Using an Off-Screen DC directed by the answer said,
What makes this window flicker when we update it frequently? The
answer is that Windows asks the window procedure to repaint the window
as a two-step process. First, it sends a WM_ERASEBKGND message and
then a WM_PAINT message. The default handling for the WM_ERASEBKGND
message is to fill the area with the current window background color.
So the sequence of events is first to fill the area with solid color
and then to draw the text on top. The net result of doing this
frequently is that the window state alternates between its erased
state and its drawn state—it flickers.
And
To prevent the control from flickering when we update it frequently,
we need to make two changes to how the control handles messages.
First, we need to prevent Windows from providing the default handling
of WM_ERASEBKGND messages. Secondly, we need to handle WM_PAINT
messages so that the background is painted with the window background
color and so that the changes to the control's client area happen at
once.
A status bar flicker free solution in .NET: Searching Visual Studio .NET style status bar. Or Simple Mode Status Bars could be enough.
as the Toolbar or Titlearea on scroll animation feature is referenced in the last section of the Toolbar API, and also in this great video tutorial (starting at about min 45), the animation works well under given circumstances.
I was not able to find any documentation about what these have to be, however I found one circumstance, in which it does not work. Here is a working example to demonstrate the problem:
Form hi = new Form("Title", new BoxLayout(BoxLayout.Y_AXIS));
EncodedImage placeholder = EncodedImage
.createFromImage(Image.createImage(hi.getWidth(), hi.getWidth() / 5, 0xffff0000), true);
URLImage background = URLImage.createToStorage(placeholder, "400px-AGameOfThrones.jpg",
"http://awoiaf.westeros.org/images/thumb/9/93/AGameOfThrones.jpg/400px-AGameOfThrones.jpg");
background.fetch();
Style stitle = hi.getToolbar().getTitleComponent().getUnselectedStyle();
stitle.setBgImage(background);
stitle.setBackgroundType(Style.BACKGROUND_IMAGE_SCALED_FILL);
stitle.setPaddingUnit(Style.UNIT_TYPE_DIPS, Style.UNIT_TYPE_DIPS, Style.UNIT_TYPE_DIPS, Style.UNIT_TYPE_DIPS);
stitle.setPaddingTop(15);
// hi.setLayout(new BorderLayout()); // uncomment this for the animation to break
Container contentContainer = new Container(BoxLayout.y());
contentContainer.setScrollableY(true);
// add some elements so we have something to scroll
for (int i = 0; i < 50; i++)
contentContainer.add(new Label("Entry " + i));
hi.add(contentContainer);
// hi.add(BorderLayout.CENTER, contentContainer); // use this line instead of the above for the animation to break
ComponentAnimation anim = hi.getToolbar().getTitleComponent().createStyleAnimation("Title", 200);
hi.getAnimationManager().onTitleScrollAnimation(anim);
hi.show();
With my current app and the codesample from the Toolbar API (which is roughly adapted here), I found out that the onScrollAnimation event is not being called, when a scroll occurs inside a BorderLayout. Even when I have a separate container, which is not the contentpane itself, and I set setScrollableY(true); to true, the animation works properly. The animation stops working, when this very container is put into Center of the Form, via Borderlayout. in the example above, the layout is exactly the same, as there are no other components in other areas of course, but it breaks the animation.
How to solve this? In my app, I have the need for a BorderLayout but still want to use this cool feature. Also, this is a very un-intuitive feature, if it works for some, but not all layouts. It should be completely layout-agnostic and work in every case.
Thank you.
The adapter is bound to the forms content pane scrolling so it won't work if you have a border layout in here. In that case scrolling isn't detected because the code just isn't aware of the scrolling. It would need to track the scrolling of any component in the UI to detect that scrolling.
as hinted by Shai, the solution is the following:
hi.setLayout(new BorderLayout());
Container contentContainer = new Container(BoxLayout.y());
contentContainer.setScrollableY(true);
// add some elements so we have something to scroll
for (int i = 0; i < 50; i++)
contentContainer.add(new Label("Entry " + i));
hi.add(BorderLayout.CENTER, contentContainer); // use this line instead of the above for the animation to break
ComponentAnimation anim = hi.getToolbar().getTitleComponent().createStyleAnimation("Title", 200);
hi.getAnimationManager().onTitleScrollAnimation(contentContainer, anim);
instead of using the onTitleScollAnimation to just add the animation, provide your own scrollable "body" or content container as the first argument, appended by the animation(s).
My app has a main window which creates and opens an instance of a subclass of a QML Window {} using createObject(). This window has its flags: set to be a borderless window (I've added code so that it can be grabbed and dragged around).
When I attach a monitor to my laptop and set its font scale factor to 125% (or 150%), when I drag my main window over to the second monitor, you can see it suddenly "snap" to the larger size when it reaches the halfway point. Likewise, when I drag it back to my laptop screen it again "snaps" to the smaller size when I get halfway over (this behavior is what I want, so no problems here).
My problem is that when I drag my created borderless window over into the monitor, it keeps the original 100% scale factor and does not "snap" to a larger size. If I drag my main window over to the monitor, it gets larger but the borderless window remains at the smaller scale; only when I grab the borderless window and move it slightly does it suddenly "snap" to the larger scale size. The same thing happens in reverse - if I then drag the borderless window back onto the laptop, it remains at the larger size until I drag the main window back over and then move the borderless window slightly (at which point it suddenly "snaps" to the smaller size).
So it appears that this created Window uses the scale factor of the screen that the parent window window that created it is currently in, even if it is in a different screen itself.
Is this happening because the Window is borderless? (I'm about to test this but my build process is incredibly slow) Or is there any way to set this borderless Window up so that it detects that it is crossing into a new screen and re-scales itself (in the same way that my main window does)?
Update: I just ran a test giving my Window a native titlebar, and with a titlebar the window instantly adopts ("snaps to") the scale factor of whichever screen it happens to be in, just like my main window (and independent of the main window's scale factor).
So is there any way to duplicate this auto-scaling window behavior with a borderless window? Some flag I need to call, or some method(s) I need to call to get the OS to rescale the window?
Update 2: I tried out Felix's SetWindowPos solution. It does move the window, but this does not fix the scaling problem - the behavior of the frameless window is the same and it still does not correctly pick up the scaling factor of the screen it is in.
I am running a test using MoveWindow instead of SetWindowPos to see if that affects anything [edit: MoveWindow does not work, either - same problem]. Then I'm going to try SendMessage or PostMessage along with NoBugz' suggestion of the WM_DPICHANGED message.
Any other suggestions are welcome.
Update 3: I just created a quick C# app (winforms) to see if the same problem occurs with that, and it doesn't - when a borderless form in the C# app is dragged over into the other monitor, it immediately picks up the scale factor change. So it appears this is a Qt problem.
Update 4: See my answer below for a working solution to this problem (if a bit of a hack).
So as far as I understand, your current goal is to move the window via the WIN-API.
You will have to do so via C++. The approach would be:
Pass the QML Window to a C++-Method exposed to QML as a QQuickWindow (The QML window instanciates that type, as seen in the documentation)
Use QWindow::winId to get the native HWND
Call the SetWindowPos WIN-API method to move it
Code sample (C++-part):
// the method
void moveWindow(QQuickWindow *window, int x, int y) {
HWND handle = (HWND)window->winId();
Q_ASSERT(handle);
::SetWindowPos(handle, HWND_TOP,
x, y, 0, 0,
SWP_NOSIZE | SWP_NOZORDER);
}
// assuming "moveWindow" is a member of "MyClass"
qmlEngine->rootContext()->setContextProperty("mover", new MyClass(qmlEngine));
Code sample (QML-part):
// call this method as soon as the drag has finished, with the new positions
mover.moveWindow(idOfWindow, xPos, yPos);
Note: I would recommend you try out calling this only after the drag was finished (and move the window as you do right now until then). If that works, you can try out what happens if you call this during the drag instead of changing the x/y of the window.
I figured out a relatively simple way to fix this problem. Since a frameless window in Qt gets its scaling factor from the window that created it, the trick is to create another window (that has a titlebar but is not visible to the user) and create the frameless window there, and then add code to the frameless window to keep the hidden window positioned underneath it as the user drags it. When the frameless window is dragged into another screen, the hidden window goes with it, picks up the new scale factor (since it has a titlebar) and then the frameless window immediately gets the new screen's scale factor as well.
Here is sample solution code:
// HiddenWindow.qml
Window {
id: hiddenWindow
// note: just making window visible: false does not work.
opacity: 0
visible: true
flags: Qt.Tool | Qt.WindowTitleHint | Qt.WindowTransparentForInput |
Qt.WindowStaysOnTopHint // Qt.Tool keeps this window out of the
// taskbar
function createVisibleWindow() {
var component = Qt.createComponent("VisibleWindow.qml")
if (component.status === Component.Ready) {
var win = component.createObject(hiddenWindow)
return win
}
}
}
// VisibleWindow.qml
Window {
id: visibleWindow
property var creatorWindow: undefined
flags: Qt.FramelessWindowHint
onXChanged: {
creatorWindow.x = x
}
onYChanged: {
creatorWindow.y = y
}
onWidthChanged: {
creatorWindow.width = width
}
onHeightChanged: {
creatorWindow.height = height
}
}
And then to use these classes from your main window QML:
property var hiddenWindow: undefined
property var visibleWindow: undefined
Component.onCompleted: {
var component = Qt.createComponent("HiddenWindow.qml")
if (component.status === Component.Ready) {
hiddenWindow = component.createObject(null)
}
visibleWindow = hiddenWindow.createVisibleWindow()
visibleWindow.creatorWindow = hiddenWindow
visibleWindow.show()
}
You need to resize window when window move to other screen
MouseArea {
anchors.fill: parent
acceptedButtons: Qt.LeftButton
onPressed: {
movePos = Qt.point(mouse.x, mouse.y)
isDoubleClicked = false
lastWindowWidth = mainWindow.width
lastWindowHeight = mainWindow.height
}
onPositionChanged: {
if (!isDoubleClicked) {
const delta = Qt.point(mouse.x - movePos.x, mouse.y - movePos.y)
if (mainWindow.visibility !== Window.Maximized) {
mainWindow.x = mainWindow.x + delta.x
mainWindow.y = mainWindow.y + delta.y
mainWindow.width = lastWindowWidth
mainWindow.height = lastWindowHeight
}
}
}
}
I consider using an owner draw menu in a Windows application that should have the same look as the standard menu. (Reason: the standard menu doesn't work well in some mixed DPI situations.)
Currently I have a problem providing the correct width during WM_MEASUREITEM.
This is a screenshot of the Edit menu of notepad where each item has a shortcut.
We can see that there is a constant gap between the item texts and shortcut texts as if they were columns. It seems as if the widths of the item texts and the widths of the shortcut texts are retrieved separately, as the longest item text "Time/Date" reserves a shortcut width suitable for Ctrl+A while it only needs one for F5.
I could not find any API functionality where I can give the width of the item text and the shortcut text separately, nor did I find any metric specifying the size of the gap.
So my question is: Is it possible to achieve the desired behavior within the usual WM_MEASUREITEM message and if yes, how? If not, is there any other means to get this right or is it just not possible at all?
This is how ReactOS does it:
To measure a menu item:
if ((p = wcschr( lpitem->Xlpstr, '\t' )) != NULL) {
RECT tmprc = rc;
LONG tmpheight;
int n = (int)( p - lpitem->Xlpstr);
/* Item contains a tab (only meaningful in popup menus) */
/* get text size before the tab */
txtheight = DrawTextW( hdc, lpitem->Xlpstr, n, &rc,
DT_SINGLELINE|DT_CALCRECT);
txtwidth = rc.right - rc.left;
p += 1; /* advance past the Tab */
/* get text size after the tab */
tmpheight = DrawTextW( hdc, p, -1, &tmprc,
DT_SINGLELINE|DT_CALCRECT);
lpitem->dxTab += txtwidth;
txtheight = max( txtheight, tmpheight);
txtwidth += MenuCharSize.cx + /* space for the tab */
tmprc.right - tmprc.left; /* space for the short cut */
}
Then to draw it:
Text = lpitem->Xlpstr;
if(Text)
{
for (i = 0; Text[i]; i++)
if (Text[i] == L'\t' || Text[i] == L'\b')
break;
}
if(lpitem->fState & MF_GRAYED)
DrawTextW( hdc, Text, i, &rect, uFormat);
/* paint the shortcut text */
if (!menuBar && L'\0' != Text[i]) /* There's a tab or flush-right char */
{
if (L'\t' == Text[i])
{
rect.left = lpitem->dxTab;
uFormat = DT_LEFT | DT_VCENTER | DT_SINGLELINE;
}
else
{
rect.right = lpitem->dxTab;
uFormat = DT_RIGHT | DT_VCENTER | DT_SINGLELINE;
}
DrawTextW( hdc, Text + i + 1, -1, &rect, uFormat );
}
So to insert a keyboard accelerator in a menu item, you simply separate it from the item text with the tab character. The measuring and drawing code then looks for this tab character and acts accordingly.
Note, however, that for the keyboard accelerator to be right-aligned in the menu like it is in your screenshot (which is achieved with DrawText with DT_RIGHT) the drawing code expects it to be separated from the item text with the '\b' character, not the tab character, and unless I'm missing something this is not accounted for in the measuring code, so you might want to compensate for that.
Replicating the standard menu with owner draw is a world of pain. You have to deal with Visual Styles on and off, mnemonics/Access keys, accessibility and all the undocumented metrics. It is better just to use the normal menu if you can.
Per-monitor DPI support seems to change in every Windows 10 release. 1607 added EnableNonClientDpiScaling which scales the menu and other non-client areas. 1703 added Per Monitor v2 and MSDN says this about PMv2:
Scaling of non-client area - All windows will automatically have their non-client area drawn in a DPI sensitive fashion. Calls to EnableNonClientDpiScaling are unnecessary.
Scaling of Win32 menus - All NTUSER menus created in Per Monitor v2 contexts will be scaling in a per-monitor fashion.
Notepad is PMv2 and its menu seems to work fine:
Windows 8.1 and < 10 Anniversary Update will require more work and I would suggest that you just don't declare yourself DPI aware on these systems and let Windows scale your window for you (with some blurriness) if the system has multiple monitors.
I have such menu situation:
int menu_create(Ihandle *menu)
{
hamburger = IupItem("&Hamburger", "hamburger");
IupSetAttributes(hamburger, "AUTOTOGGLE=YES, RADIO=YES");
char* ce = "Ćev&apčići";
cevapcici = IupItem(utf8_to_cp1250(ce), "cevapcici");
IupSetAttributes(cevapcici, "AUTOTOGGLE=YES, RADIO=YES");
exit = IupItem("Exit\tAlt+F4", "exit");
img4 = IupLoadImage("icons\\delete_16x16.ico");
IupSetAttributeHandle(exit, "TITLEIMAGE", img4);
menu = IupMenu(
IupSubmenu("File",
IupMenu(
hamburger,
cevapcici,
IupSeparator(),
IupItem("Carro&t", "carrot"),
IupSeparator(),
exit,
NULL)),
NULL);
IupSetFunction("exit", (Icallback)mnu_exit);
... etc...
IupSetHandle("menu", menu);
return IUP_DEFAULT;
}
How to get "radio toggle group" functionality with items hamburger and cevapcici so first turns off a second checkmark and opposite. This is my try but it don't work.
2) I try webbrowser example from IUP suite on my windows 7. Problem is that bad black flickering appear's during resize (increase). Also, background of webbrowser flicker black during showing.
I try a same example on Ubuntu and there flickering appear's too but it is not so much visible since background is there white.
Is here any way to get rid of those flickering or if not to get white background of webbrowser window on windows?
3) Since webbrowser is ole object (on windows) is it possible to use say "print preview" or "zoom" function by reference from IUP handle or at any other way like we used to do from MS programming tools?
wbInstance.ExecWB(Exec.OLECMDID_OPTICAL_ZOOM, ExecOpt.OLECMDEXECOPT_DONTPROMPTUSER, 150, DBNull.Value)
4) How can I get key_up event fired from IupTree?
5) Interesting situation with IupTabs:
frame3 = IupHbox(mat, val, NULL);
vboxt1 = IupVbox(frame3, NULL);
vboxt2 = IupVbox(frame3, NULL);
IupSetAttribute(vboxt1, "TABTITLE", "First documents... ");
IupSetAttribute(vboxt2, "TABTITLE", "Second documents... ");
tabs = IupTabs(vboxt1, vboxt2, NULL);
hbox1 = IupHbox(tabs, IupVbox(frame, tree, frame2, NULL), NULL);
dlg = IupDialog(hbox1);
When I set frame3 which should be a same for both tabs my GUI frozes.
However, I have to got same "mat" (IupMatrix) in both tabs because by changing tabs other data load in matrix but similar enough to use same matrix and related functions.
What to do here?
1) The RADIO attribute belongs to the IupMenu, not to the IupItem. This also means that all the IupItems inside that menu will be part of the radio.
A workaround would be to manually unset the other toggle inside the action callback.
2) That flicker is not caused by IUP. Don't know why the native controls are doing it.
3) Yes, but you will have to program that using the OLE API. If you take a look at the IupOleControl and IupWebBrower source code and send me the code to do it, I will be happy to add it to IUP.
4) You don't. Use the K_ANY callbacks.
5) You can not reuse a control in different places in any dialog. So you must have two different frames, with two different matrices. What you can do is to encapsulate your matrix, so the same function will create a matrix with the same attributes and callbacks any time you want one.