Wrote a little app (attached) that shows strings in a file. Works, but has problems.
I used a listbox control to display the strings. I think I realize now that this is the wrong control for the job, for reasons given below. Which is too bad, because the listbox makes a couple of things reeeeally easy for me, the programmer:
- It handles all the nasty memory-allocation and pointer management stuff
- I can easily add strings one at a time, rather than having to handle the entire block of text as one chunk, as I'd have to do with an edit control
- Sorting the strings is a piece of cake (just add the LBS_SORT style)
Here are the problems:
1. The listbox displays correctly after I fill it with strings (this is done in my FindStrings() subroutine). But the display is very flaky; if I click in the listbox, the text disappears. I can make it reappear (sometimes) by playing with the scrollbar, if there's more than a window-full of text there. But it's clearly not working properly.
2. The listbox is dog-sloooooooooow if there are a lot of strings (for example, I tried loading the .lst file from this project; it took a couple of minutes to load). Not efficient at all.
(This is under Win2K. Your results with another OS flavor may vary.)
I tried subclassing the list box, but that only made matters worse. (Code is there but commented out.) I tried handling the WM_PAINT message in the window proc, and I think what was happening is that I was just generating more messages recursively, or something. Anyhow, it didn't work at all.
So I have a couple questions (kind of risky here: I find that at most, one of my questions gets answered while the others are ignored, but whatever):
1. Knowing that a listbox isn't the ideal control here, I would nonetheless like to learn how to use it properly. What am I doing wrong here (regarding the incorrect display problem, not the inefficiency one)?
2. I guess the other candidate for a control is an edit box. However, this means that I have to:
- Handle memory allocation and pointers to my strings
- Create the text in the edit control as one big "chunk", rather than being able to add strings one at a time
- Sort the list (not hard to do, but it's nice when Big Daddy Bill does this for you!)
So is there anything besides an edit box that will do this job?
When adding text to an edit box, does it have to be done all at once (using WM_SETTEXT), or is there some way to add text incrementally to an edit box, the way one does with a listbox?
Oh, and
another little problem: Notice that the listbox doesn't qiuite occupy the full height of the parent window's client area. It stops a little above the bottom. I can't figure out why this is. I tried two methods of calculating the height to make the listbox when resizing it:
- Use the new window sizes passed in through lParam
- Use GetClientRect() to get the new size of the parent's client area
Both give the same (wrong) results. I tried adding the height of the menu strip to the starting point, but that was wrong as well. This is annoying.
Thanks in advance to all helpful answers!
Quote from: NoCforMe on December 17, 2011, 07:56:36 PM
2. The listbox is dog-sloooooooooow if there are a lot of strings
http://www.masm32.com/board/index.php?topic=12802.msg98826#msg98826
OK, that's interesting. So I take it by this you're saying "yes, listboxes can be used efficiently this way, but they have to be tweaked"?
Another thought: maybe neither listboxes nor edit boxes are the way to go, since all I really want to do is display text: I could just put the text into the client area using DrawText() or ExtTextOut(). (BTW, which of these would you recommend?)
Except that now I'd have to handle scrolling, which is nicely taken care of by both edit boxes and list boxes ...
The other nice thing about an edit box is that the user can select text from it to copy. This could be done by drawing your own text but would require a lot more code. (Does anyone have some examples of selecting text from application-drawn text? I don't know how to do this yet.)
Sorry about all the cascading questions, but that's the nature of the beast. It'd be nice to get answers to all of them ...
What is the issue here? Just opening a file, reading in strings, sorting them, displaying them? Sort the file and read it into a RichEdit...
include \masm32\MasmBasic\MasmBasic.inc ; download (http://www.masm32.com/board/index.php?topic=12460)
Init[/b]
Recall "\Masm32\include\Windows.inc", L$()
QSort L$()
Store "WinIncSorted.inc", L$()
Inkey Str$("%i strings sorted", eax)
Exit
end start
In my quick test, done entirely in the WM_INITDIALOG handler before the list was displayed so redraws were not a factor, about half of the time spent adding the strings was sorting time.
;==============================================================================
include \masm32\include\masm32rt.inc
;==============================================================================
IDC_LB equ 1000
;==============================================================================
.data
hInstance dd 0
hwndLB dd 0
buffer db 100 dup(0)
.code
;==============================================================================
;==============================================================================
DialogProc proc hwndDlg:HWND, uMsg:UINT, wParam:WPARAM, lParam:LPARAM
SWITCH uMsg
CASE WM_INITDIALOG
invoke GetDlgItem, hwndDlg, IDC_LB
mov hwndLB, eax
invoke GetTickCount
push eax
push ebx
push edi
push esi
mov ebx, 50000
.WHILE ebx
invoke RtlZeroMemory, ADDR buffer, SIZEOF buffer
invoke nrandom, 50
add eax, 11
mov esi, eax
mov edi, OFFSET buffer
.WHILE esi
invoke nrandom, 'Z' - 'A' + 1
add eax, 'A'
stosb
dec esi
.ENDW
invoke SendMessage, hwndLB, LB_ADDSTRING, 0, ADDR buffer
dec ebx
.ENDW
pop esi
pop edi
pop ebx
invoke GetTickCount
pop edx
sub eax, edx
invoke SetWindowText, hwndDlg, str$(eax)
CASE WM_SIZE
mov eax, lParam
mov edx, eax
and eax, 0ffffh
shr edx, 16
invoke MoveWindow, hwndLB, 0, 0, eax, edx, TRUE
CASE WM_COMMAND
SWITCH wParam
CASE IDCANCEL
invoke EndDialog, hwndDlg, NULL
ENDSW
CASE WM_CLOSE
invoke EndDialog, hwndDlg, NULL
ENDSW
return 0
DialogProc endp
;==============================================================================
start:
;==============================================================================
invoke GetModuleHandle, NULL
mov hInstance, eax
Dialog "Test", \
"MS Sans Serif",10, \
WS_OVERLAPPED or WS_SYSMENU or DS_CENTER, \
1, \
0,0,300,300, \
1024
DlgList LBS_STANDARD,0,0,0,0,IDC_LB ;~29500 ticks, list sorted
;DlgList 0,0,0,0,0,IDC_LB ;~15300 ticks, list not sorted
CallModalDialog hInstance, 0, DialogProc, NULL
exit
;==============================================================================
end start
Quote from: jj2007 on December 17, 2011, 09:28:10 PM
What is the issue here?
Did you look at my app? Listbox misbehaves , that's the issue.
QuoteJust opening a file, reading in strings, sorting them, displaying them? Sort the file and read it into a RichEdit...
[snip code]
Um, not quite. My program extracts strings from any kind of file, including executables, object files, etc. (that's its intended purpose).
Anyhow, let me try things this way: I'll start with my first question:
Why does my listbox (mis)behave the way it does?What happens is when I open a file, it fills it with strings from the file. Fine. But if you click anywhere in the listbox, the strings (and the scrollbar if there is one) disappears! If you play around with the scrollbar a few times (assuming there's more text than will fit in the window), it then starts behaving normally.
Except that if you open another file, it shows the strings from that file correctly,
but then if you make the text disappear and then reappear as described above, you get the strings ... from the original file!!!?!! (This is even after I added sending the
LB_RESETCONTENT message to clear it out each time I process the strings from a file.)
I'm clearly doing something wrong here, and I'd like to know what it is so I don't do it anymore.
Regarding you MasmBasic stuff, don't take this the wrong way, but I'm not interested in using it. I appreciate how much effort you put into that package, and how much praise you deserve for such a useful tool. I might use it sometime in the future, but I'm just not there yet.
check your WM_COMMAND-handler!
You should use .if/.elseif/.else - this constructs increase the readability and help to avoid such nasty bugs :wink
When you "click" on the listbox, it sends a notification to your parent window, you don't handle that, you only handle if a menu item was clicked.. fine but what happens for all other commands? Your WM_COMMAND handler "falls through" to your WM_CREATE Handler!!! So eack click you are creating a NEW listbox.. So your command handler should look like:
do_command:
MOV AX, WORD PTR wParam
CMP AX, $menuOpenFile
JE do_openfile
CMP AX, $menuExit
JE do_close
xor eax, eax ; <<<<<<< Needed
ret ; <<<<<<< This too!
I agree qWord, but I think he feels it is TOO high level.... Produces almost the same code BUT with less headaches.
Thank you, Gunner, that was exactly the problem. Doh!
Qword, if you're going to tell me there's a problem with my WM_COMMAND code, perhaps you could be a little more specific next time.
And thanks, but I don't like using .if ... .else constructs in assembly language.
Different strokes for different folks, dontcha know...
And Gunner, I actually added this:
JMP dodefault
which goes to this:
dodefault:
INVOKE DefWindowProc, hWin, uMsg, wParam, lParam
RET
because you're supposed to use the default handler for anything you don't handle yourself, correct?
OK, now we're getting somewhere. Fixed my problem with the listbox control.
Question no. 2: What is the most appropriate control to use here? Keep in mind that large files may produce lots of strings, and that without doing any speeding-up, the sorted listbox seems to be much too slow for this application.
Should I instead use
- an edit control?
- drawing text directly into the client area?
- something else?
Much thanks for the help so far.
I believe listboxes have limits unless you owner draw. You could also use a listview control with 1 column with the header hidden, has more features too, and you could owner draw that too, or edit control... your choice
QuoteDifferent strokes for different folks, dontcha know...
:bg
Quote from: NoCforMe on December 19, 2011, 12:02:37 AM
Qword, if you're going to tell me there's a problem with my WM_COMMAND code, perhaps you could be a little more specific next time.
He took the pains of deciphering your very unorthodox setup and pointed exactly at the (obvious) problem. A simple "thank you" would have been enough.
So jj, I implemented your "speed-up", holding off redrawing the listbox control and using LB_INITSTORAGE to allocate memory. It didn't make much, if any, difference. Which tells me that most of the time is being spent sorting strings, not displaying them.
Of course, my computer is probably considerably slower than yours. The program ate up about 90-95% of CPU on my system. (W2K Pro.)
I wonder if you'd care to try this app on your computer? I tested it on its own .lst file (with no .NOLISTs, so it included the entire windows.inc file). Took a couple minutes before I saw any text on my computer.
Next question: What's with that little bit of space at the bottom of the window between the window edge and the bottom of the scrollbar? That ain't right! Can't seem to size the listbox correctly within the main window client area.
Quote from: NoCforMe on December 19, 2011, 01:02:10 AM
I wonder if you'd care to try this app on your computer?
Attach it, and we'll see :bg
I'll take you up on your offer, jj.
Odd behavior: if you resize the window, the size of the listbox doesn't follow it smoothly; it "jumps". Try resizing the window vertically and you'll see what I mean.
I added writing the strings to a file (the write routine also removes duplicate strings).
Here's the resizing code (alternate method commented out, behaves exactly the same):
INVOKE GetClientRect, hWin, ADDR gpRect
; MOVZX EAX, WORD PTR lParam
; MOV newWidth, EAX
; MOVZX EAX, WORD PTR lParam + 2
; MOV newHeight, EAX
INVOKE MoveWindow, StringsWinHandle, 0, 0, gpRect.right, gpRect.bottom, TRUE
; INVOKE MoveWindow, StringsWinHandle, 0, 0, newWidth, newHeight, TRUE
XOR EAX, EAX
RET
This same method seems to work fine in every other app of mine.
Just had a flash (or a brain fart?): since a listbox control contains lines of text, it can only be sized in increments of those lines. So that's why its size "jumps" when it's resized.
Is this correct?
Quote from: NoCforMe on December 19, 2011, 06:24:38 AM
I'll take you up on your offer, jj.
Minor suggestions. Your prog works fine. Re performance, there is obviously a problem - it loads in about 12 seconds on my P4. A factor 60 compared to 0.2 seconds for reading & sorting the file with MB Recall & QSort.
MOV OldListboxProcPtr, EAX
invoke SetTimer, hWin, 127, 500, 0
MOV HeapPtr, NULL ;Indicate heap hasn't been allocated.
....
dodefault:
.if uMsg==WM_TIMER
invoke KillTimer, hWin, 127
invoke lstrcpy, addr fileOpenName, chr$("Strings.lst")
jmp open8
.endif
....
INVOKE CreateWindowEx, 0, ADDR StringsClassName, NULL, $stringsWinAttrs or LBS_NOINTEGRALHEIGHT, 0,
0, gpRect.right, gpRect.bottom, hWin, NULL, InstanceHandle, NULL
....
INVOKE MoveWindow, StringsWinHandle, 0, 0, gpRect.right, gpRect.bottom, FALSE