News:

Masm32 SDK description, downloads and other helpful links
Message to All Guests
NB: Posting URL's See here: Posted URL Change

Main Menu

Problem: Getting a scrollbar to appear in a custom control

Started by NoCforMe, August 27, 2022, 02:39:36 PM

Previous topic - Next topic

NoCforMe

Working with a pretty gnarly problem: I'm designing a custom Win32 control, and I cannot get a scrollbar to appear in it.

I'm not going to post any code just yet: it's complicated, and I want to approach this from a general, theoretical angle first to try to resolve it. Hoping that someone who knows a lot about Win32 stuff comes along.

This is a project I've been working on for some time, and the last time I got it up it had working scrollbars. What I have now isn't that much different, but for some reason the scrollbars just won't appear.

It's actually a fairly simple control, basically a listbox with multiple columns. I want the scrollbar (horizontal only) to appear when needed, when there are more items in the control than will appear in the visible viewport.

I tried using SetScrollInfo() to set the scrollbar to a state where the scrollbar should appear (page size > total range), but nothing.

I've set the WS_HSCROLL style on creation, and I've confirmed that this style is making it through to the CREATESTRUCT, so Windows apparently knows that I want a scrollbar.

This is a purely custom control, not based on any existing Windows control; I'm registering the class and then creating it. I've tried it both in a dialog and as a stand-alone child window (using CreateWindowEx() ); doesn't work either way.

One specific question I have is the role of DefWindowProc() in a custom control like this. With standard Win32 controls, this function can take care of practically anything that needs doing, since it knows all the details of the window class. But in my case, it knows almost nothing. I'm wondering what the role of this function is in cases like this where the window class is unknown to Windows. Does it have anything to do with the creation of scrollbars? (Meaning the non-child-control type of scrollbars, the ones intrinsic to windows.)

I could create a scrollbar "by hand" (by using CreateWindowEx() ), but I don't want to do this. It's so much easier to have Windows take of all that for you and just forward the scrollbar messages to you. (I tried this, and it works, but like I say I don't want to do it that way.)

I think the earlier version was run under XP, and I'm now using Windows 7-64, so that may be the problem ...
Assembly language programming should be fun. That's why I do it.

Greenhorn

Quote from: NoCforMe on August 27, 2022, 02:39:36 PM
One specific question I have is the role of DefWindowProc() in a custom control like this. With standard Win32 controls, this function can take care of practically anything that needs doing, since it knows all the details of the window class. But in my case, it knows almost nothing. I'm wondering what the role of this function is in cases like this where the window class is unknown to Windows. Does it have anything to do with the creation of scrollbars? (Meaning the non-child-control type of scrollbars, the ones intrinsic to windows.)

The DefWindowProc() handles the default window messages for any window, no matter if it is a common control or a custom control or any kind of other window.

Without the relevant parts of code one can just guess what's wrong.
Assuming you call the SetScrollInfo in the WM_SIZE with the correct values set in the SCROLLINFO the scrollbar should appear to be visible if necessary.
Kole Feut un Nordenwind gift en krusen Büdel un en lütten Pint.

jj2007

Little testbed attached, pure Masm32 SDK code and less than 100 lines. It adds a scrollbar to an edit control.

You might wish to add a subclass for the edit, and catch the scrollbar messages there. The scrollbar is a child of the edit control, so dealing with it in the main WndProc will probably not work.

NoCforMe

Maybe you missed the part where I said I don't want to create any child scrollbars; I want to use the "intrinsic" Win32 scrollbars, which aren't actually separate controls (windows) but, as Raymond Chen puts it, "decorations" to a control.
Assembly language programming should be fun. That's why I do it.

jj2007

If you are not using one of the predefined registered controls, there might be no "intrinsic" Win32 scrollbar. So you might have to add it... but I might be wrong, too.

NoCforMe

Well, there kinda sorta is (an intrinsic scrollbar). Ackshooly, I know that there is, because I got it to work earlier. Though I don't know how or why.

Ackshooly, this is getting verrry interesting. After reading Greenhorn's reply, I started experimenting with the window proc of the control, specifically the sequence of message processing. Since he mentioned WM_SIZE as a place to call SetScrollInfo() from, I tried various sequences involving WM_SIZE.

Let me tell you, WM_SIZE is a goddamn can of worms. It's quite mysterious to me what is actually going on with that message. Yeah, I know what MSDN has to say about it: it is "sent to a window after its size has changed".
There are a number of values sent depending on what kind of resizing is supposed to have occurred. When I captured this message, I found only two of them were being sent to my control: SIZE_RESTORED (0) and SIZE_MINIMIZED (1). I don't understand the latter, since so far as I know the control was never minimized (unless it started out life in this state). SIZE_RESTORED basically means "neither maximized nor minimized", therefore "normal" (I'm guessing).

In any case, I'm not sure I even need to mess around with WM_SIZE, since my controls are intended to be fixed-size and not resizeable. There are other ways of getting the actual control size (like GetClientRect(), or through the CREATESTRUCT passed with WM_CREATE). So maybe I don't even need to bother with this message?

I read a discussion of this online where a programmer basically said that it's almost impossible to tell when the "real" WM_SIZE comes along, and that he had given up on this and put SetScrollInfo() in his WM_PAINT handler instead.

Oh, I almost forgot to mention the other can-o'worms aspect: setting the scroll info can then generate even more WM_SIZE messages, because if the scrollbar appears or disappears the size of the client rectangle changes, which can lead to a cascade of recursive (or at least nested) messages. So this makes it seem like WM_SIZE would be the last place you'd want to put SetScrollInfo().

Anyhow, I experimented with the placement of a WM_SIZE handler, with wildly divergent results. I've attached 3 small executables that show what happens. What's strange is that even if the WM_SIZE handler goes straight to default processing (via DefWindowProc() ), the results are very different depending on when this message is handled.

In one case (first program, mcLB_SBtest1), where I look for WM_SIZE first thing (and use DefWindowProc() to handle it), I get a blank control but I get a non-operational but perfectly visible scroll bar! Weird, huh? The second program, mcLB_SBtest2, shows the control normally (and operational) but shows a "ghost" scrollbar. In this case, the scrollbar isn't visible, but you can tell it's (sort of) there by clicking and dragging in the area where it should be, at the bottom of the control; I forward all the WM_HSCROLL messages to the parent window which displays them in the status window at the bottom. Only 2 values of this message ever come through: SB_ENDSCROLL and SB_THUMBTRACK. (The position value of the latter never changes from 0 even if you "drag" the scrollbar thumb, which of course you cannot see.) This is probably because the scrollbar isn't properly set, even though I call SetScrollbarInfo() with values that should make it both visible and active. I suspect that the trick here, if it is even possible to make this work, is to call that function at the right time ...

Try out these testbed programs to see what happens. Explanations are displayed.

So I'm really scratching my head now. Any ideas?
Assembly language programming should be fun. That's why I do it.

NoCforMe

One question this raises for me is this: what is the proper sequence of handling messages in a window proc? This is something I've never read anything about, but given that there are obviously interactions between the different message (as proven by my little testbed programs), it seems important.

The messages I need to handle are:

  • WM_CREATE
  • WM_DESTROY
  • WM_PAINT
  • WM_ERASEBKGND
  • WM_LBUTTONDOWN
  • WM_LBUTTONDBLCLK
  • WM_HSCROLL
That's pretty much it.
Assembly language programming should be fun. That's why I do it.

Greenhorn

Typically you call the DefWindowProc for messages your WndProc does NOT handle. This function proceeds the default actions for window messages.

Quote from: NoCforMe on August 28, 2022, 09:25:17 AM
One question this raises for me is this: what is the proper sequence of handling messages in a window proc? This is something I've never read anything about, but given that there are obviously interactions between the different message (as proven by my little testbed programs), it seems important.

The messages I need to handle are:

  • WM_CREATE
  • WM_DESTROY
  • WM_PAINT
  • WM_ERASEBKGND
  • WM_LBUTTONDOWN
  • WM_LBUTTONDBLCLK
  • WM_HSCROLL
That's pretty much it.

Handle these messages and return the appropriate return value for each message. The sequence doesn't matter.
For all messages you don't handle call DefWindowProc.
https://docs.microsoft.com/en-us/windows/win32/winmsg/using-window-procedures

Quote from: NoCforMe on August 28, 2022, 08:30:33 AM
In any case, I'm not sure I even need to mess around with WM_SIZE, since my controls are intended to be fixed-size and not resizeable. There are other ways of getting the actual control size (like GetClientRect(), or through the CREATESTRUCT passed with WM_CREATE). So maybe I don't even need to bother with this message?

The WM_SIZE is called at least once after creation and before displaying, so you can place it there, even if the size never changes.

Apply this to the columns of your control ... How to Scroll Text
Kole Feut un Nordenwind gift en krusen Büdel un en lütten Pint.

NoCforMe

Quote from: Greenhorn on August 28, 2022, 11:01:35 AM
Quote from: NoCforMe on August 28, 2022, 09:25:17 AM
One question this raises for me is this: what is the proper sequence of handling messages in a window proc?

Handle these messages and return the appropriate return value for each message. The sequence doesn't matter.


Except that it does matter, as shown by the programs I posted here.
Assembly language programming should be fun. That's why I do it.

NoCforMe

Quote from: jj2007 on August 28, 2022, 07:04:17 AM
If you are not using one of the predefined registered controls, there might be no "intrinsic" Win32 scrollbar. So you might have to add it... but I might be wrong, too.
At this point I cannot say definitively that you are wrong, only that you might be wrong. Here's a static control with an "intrinsic" scrollbar. (Only thing is, it doesn't work at all. But it sure looks purty, don't it?) Interesting thing is, this is the only scrollbar I could get to show with the thumb correctly sized (about 1/10 of the scroll range), so we're halfway there ... but no cigar.

I tried this with an edit control: it may not be possible to get to work, since you can use the ES_AUTOHSCROLL and ES_AUTOVSCROLL styles to have the control automatically scroll, and that built-in code probably won't work with an outside attempt to interfere with it.

Try attached .exe.
Assembly language programming should be fun. That's why I do it.

NoCforMe

Progress!

I now have a custom control with a working intrinsic scrollbar. .exe attached. It can be done.

It doesn't work correctly--the scrolling and painting code is still screwed up under construction, but it works: you can click on the scrollbar or move the thumb and it does stuff. Proof of concept at least.

(I knew it should work because I had a leftover testbed of an earlier version of the control but no source code. So what I did was fire up Olly Debug and print out the disassembly of the control's window proc. The problem was just in my SetScrollInfo() code. And I'm still trying to figure out how to figure "page size" vs. "range" to get it to work correctly. Small steps ...)

@Greenhorn, you're actually right, for the most part: it doesn't matter what order you look for messages in your message handler. (It does matter in part of my code, but that's because I'm using a scheme that allows me to have multiple controls of the same class using a lookup table; I have to treat WM_CREATE (and WM_NCCREATE) specially because the table hasn't been constructed before those messages are received.)
Assembly language programming should be fun. That's why I do it.

Greenhorn

Quote from: NoCforMe on August 28, 2022, 08:26:46 PM
@Greenhorn, you're actually right, for the most part: it doesn't matter what order you look for messages in your message handler. (It does matter in part of my code, but that's because I'm using a scheme that allows me to have multiple controls of the same class using a lookup table; I have to treat WM_CREATE (and WM_NCCREATE) specially because the table hasn't been constructed before those messages are received.)

A lookup table ? To keep the data assigned to a control ? That's how MFC is doin' it.
You can do it easier by using the window's extra bytes to store a pointer to a structure.  Custom Controls

WM_NCCREATE is preferable used because if something went wrong on creation - e.g. allocating memory for the control's data structure - you can abort the window creation by returning FALSE.
Kole Feut un Nordenwind gift en krusen Büdel un en lütten Pint.

NoCforMe

Quote from: Greenhorn on August 29, 2022, 01:03:59 AM
Quote from: NoCforMe on August 28, 2022, 08:26:46 PM
@Greenhorn, you're actually right, for the most part: it doesn't matter what order you look for messages in your message handler. (It does matter in part of my code, but that's because I'm using a scheme that allows me to have multiple controls of the same class using a lookup table; I have to treat WM_CREATE (and WM_NCCREATE) specially because the table hasn't been constructed before those messages are received.)

A lookup table ? To keep the data assigned to a control ? That's how MFC is doin' it.
You can do it easier by using the window's extra bytes to store a pointer to a structure.  Custom Controls

Mmm, six of one, half a dozen of the other. I allocate a fixed-size table (to handle a maximum # of controls) and make a new entry in it for each WM_CREATEd control. For my purposes, works fine. I can see how using the window's extra bytes would better generalize the control, and if I were making a production control I'd do it that way. Easy to change if needed.

Of course, if I were in production I'd also have a better version-control system than the current one here at NoCforMe Laboratories, GmbH, which is basically a 1-level "undo" (get last backup from external drive) ...

Quote
WM_NCCREATE is preferable used because if something went wrong on creation - e.g. allocating memory for the control's data structure - you can abort the window creation by returning FALSE.

You can abort creation handling WM_CREATE as well by returning -1.
Assembly language programming should be fun. That's why I do it.

NoCforMe

More Progress!

The little control is getting better every day. It is now actually usable. Scrolling works correctly*. In this little testbed you can actually enter items into the second box and see them appear (and the scrollbar appear at the appropriate time). You can also click on any item to select it, and double-click on any item; see the response in the status bar.

Still a lot more work to go, but I'm pretty happy at this point. (My idea is to use this in a browse-for-folder dialog that actually works the way I want it to.)

* Dragging the thumb is not implemented yet, but every other way of scrolling works (I've set "scroll by lines" to do the same thing as "scroll by pages"). Any Day Now ...
Assembly language programming should be fun. That's why I do it.

zedd151

Quote from: NoCforMe on August 30, 2022, 01:44:22 PM
More Progress!
Still a lot more work to go, but I'm pretty happy at this point. (My idea is to use this in a browse-for-folder dialog ...)
I'm on the porch at the moment. (iPad post) I'll take a look at it when I go back inside.


If you're happy with it, it's all that matters.