Thread Rating:
  • 0 Vote(s) - 0 Average
  • 1
  • 2
  • 3
  • 4
  • 5
GetWindowTextW - can you get it to work?
#1
I'm trying to get the name of the current window that has focus. I've already successfully figured out how to get the window handle of any window in focus but I'm having trouble getting the window's title. According to Microsoft's docs here:

https://learn.microsoft.com/en-us/window...indowtextw

GetWindowTextW should be able to do this. ( There is also a GetWindowTextA function for ASCII return but that's not working for me either. )

It's pretty straight forward:

DECLARE DYNAMIC LIBRARY "user32"
    FUNCTION GetWindowTextW (BYVAL hWnd AS _INTEGER64, lpString AS STRING, BYVAL nMaxCount AS INTEGER)
END DECLARE

DIM Dummy AS INTEGER
DIM n AS STRING

_TITLE "This is a test"
Dummy = GetWindowTextW(_WINDOWHANDLE, n, 255)
PRINT n

Does anyone have any insights on why this is not working? The string seems to be null and Dummy returns the value of 0.
There are two ways to write error-free programs; only the third one works.
QB64 Tutorial
Reply
#2
You probably want https://learn.microsoft.com/en-us/window...indowtexta

Most of the windows API functions come in two varieties -- A and W.  (And they're not rootbeer.)

A basically is used for ASCII/ANSI text.
W basically is used for Wide text.  (Unicode)
Reply
#3
A working example, plus a few changes which you'll want to make use of:

Code: (Select All)
DECLARE DYNAMIC LIBRARY "user32"
FUNCTION GetWindowTextA (BYVAL hWnd AS _OFFSET, lpString AS STRING, BYVAL nMaxCount AS INTEGER)
FUNCTION GetWindowTextW (BYVAL hWnd AS _OFFSET, lpString AS STRING, BYVAL nMaxCount AS INTEGER)
END DECLARE

DIM Dummy AS INTEGER
DIM n AS STRING * 255

_TITLE "This is a test"
Dummy = GetWindowTextA(_WINDOWHANDLE, n, 255)
PRINT n
Dummy = GetWindowTextW(_WINDOWHANDLE, n, 255)
PRINT n
_DELAY .5
Dummy = GetWindowTextA(_WINDOWHANDLE, n, 255)
PRINT n
Dummy = GetWindowTextW(_WINDOWHANDLE, n, 255)
PRINT n


Note that since this is a Windows call, you'll need a delay after the _TITLE to give the OS time to update things to return the proper title for you.

Also note that I'm passing an _OFFSET and not an _INTEGER64.

And you can see the difference in A and W function calls here.

Also notice the STRING * 255 here. Wink
Reply
#4
(06-07-2024, 07:21 PM)SMcNeill Wrote: A working example, plus a few changes which you'll want to make use of:

Code: (Select All)
DECLARE DYNAMIC LIBRARY "user32"
    FUNCTION GetWindowTextA (BYVAL hWnd AS _OFFSET, lpString AS STRING, BYVAL nMaxCount AS INTEGER)
    FUNCTION GetWindowTextW (BYVAL hWnd AS _OFFSET, lpString AS STRING, BYVAL nMaxCount AS INTEGER)
END DECLARE

DIM Dummy AS INTEGER
DIM n AS STRING * 255

_TITLE "This is a test"
Dummy = GetWindowTextA(_WINDOWHANDLE, n, 255)
PRINT n
Dummy = GetWindowTextW(_WINDOWHANDLE, n, 255)
PRINT n
_DELAY .5
Dummy = GetWindowTextA(_WINDOWHANDLE, n, 255)
PRINT n
Dummy = GetWindowTextW(_WINDOWHANDLE, n, 255)
PRINT n


Note that since this is a Windows call, you'll need a delay after the _TITLE to give the OS time to update things to return the proper title for you.

Also note that I'm passing an _OFFSET and not an _INTEGER64.

And you can see the difference in A and W function calls here.

Also notice the STRING * 255 here.  Wink
I tried everything your code has except for _OFFSET and that seems to be the trick.

I thought _OFFSET and _INTEGER64 were basically the same thing. I'll need to investigate that further.

Thank you for the explanation and code Steve. I'll get the hang of this yet.

Ah, the string length also must be identified: n AS STRING * 255

I also found out you need to pad the string with spaces: n = SPACE$(255) before using it otherwise LTRIM, RTRIM, and _TRIM won't work on it.

Ugh, and a CHR$(0) is appended to the end of the name that needs to be removed.
There are two ways to write error-free programs; only the third one works.
QB64 Tutorial
Reply
#5
_OFFSET and _INTEGER64 are only the same thing in 64-bit programs, for 32-bit programs _OFFSET is 32-bits in size instead. It's the correct type to use for pointer arguments because those are also 64-bit or 32-bit depending on the program. If you're using 64-bit QB64 though then `_INTEGER64` should work (if you do it along with the fixed-length string thing).

I would also note this is a case similar to your `GetWindowRect` code where the state according to QB64 is not always the state according to Windows, and in this instance it's intentional. If you check the `_TITLE$` function it will always return the correct title that you set with `_TITLE "foobar"` with no delay, but `GetWindowText`may return something different if the window title hasn't actually be updated yet by the QB64 runtime.
Reply
#6
A few more changes which you'll want to look at and make use of:

Code: (Select All)
DECLARE DYNAMIC LIBRARY "user32"
    FUNCTION GetWindowTextA& (BYVAL hWnd AS _OFFSET, lpString AS STRING, BYVAL nMaxCount AS LONG)
    FUNCTION GetWindowTextW& (BYVAL hWnd AS _OFFSET, lpString AS STRING, BYVAL nMaxCount AS LONG)
END DECLARE
DIM Dummy AS INTEGER
DIM n AS STRING * 255
_TITLE "This is a test"
Dummy = GetWindowTextA(_WINDOWHANDLE, n, 255)
PRINT LEFT$(n, Dummy)
Dummy = GetWindowTextW(_WINDOWHANDLE, n, 255)
PRINT LEFT$(n, Dummy * 2) '*2 as we're not print wide-text and just printing ASCII text
_DELAY .5
Dummy = GetWindowTextA(_WINDOWHANDLE, n, 255)
PRINT LEFT$(n, Dummy)
Dummy = GetWindowTextW(_WINDOWHANDLE, n, 255)
PRINT LEFT$(n, Dummy * 2) 'as above

Notice that I made the return function types LONGs, instead of SINGLES.   Now you can get the proper length back from the function.

Note #2 -- also notice that I changed your last type.  You were sending it an INTEGER, while it was wanting a LONG.  See the Windows Types here: https://learn.microsoft.com/en-us/window...data-types   (Not that it really mattered at this point, as you were sending it a pure value of 255 -- which is more than small enough to work for both INTEGER or LONG.  I just wanted to point this mismatch out, to draw attention to it so you wouldn't get bit by it in the future when it would matter more.)

Quote:INT
A 32-bit signed integer. The range is -2147483648 through 2147483647 decimal.

This type is declared in WinDef.h as follows:

typedef int INT;

I think this should have all the little glitches out and have the functions working 100% as advertised now.
Reply
#7
(06-08-2024, 04:47 AM)SMcNeill Wrote: A few more changes which you'll want to look at and make use of:

Code: (Select All)
DECLARE DYNAMIC LIBRARY "user32"
    FUNCTION GetWindowTextA& (BYVAL hWnd AS _OFFSET, lpString AS STRING, BYVAL nMaxCount AS LONG)
    FUNCTION GetWindowTextW& (BYVAL hWnd AS _OFFSET, lpString AS STRING, BYVAL nMaxCount AS LONG)
END DECLARE
DIM Dummy AS INTEGER
DIM n AS STRING * 255
_TITLE "This is a test"
Dummy = GetWindowTextA(_WINDOWHANDLE, n, 255)
PRINT LEFT$(n, Dummy)
Dummy = GetWindowTextW(_WINDOWHANDLE, n, 255)
PRINT LEFT$(n, Dummy * 2) '*2 as we're not print wide-text and just printing ASCII text
_DELAY .5
Dummy = GetWindowTextA(_WINDOWHANDLE, n, 255)
PRINT LEFT$(n, Dummy)
Dummy = GetWindowTextW(_WINDOWHANDLE, n, 255)
PRINT LEFT$(n, Dummy * 2) 'as above

Notice that I made the return function types LONGs, instead of SINGLES.   Now you can get the proper length back from the function.

Note #2 -- also notice that I changed your last type.  You were sending it an INTEGER, while it was wanting a LONG.  See the Windows Types here: https://learn.microsoft.com/en-us/window...data-types   (Not that it really mattered at this point, as you were sending it a pure value of 255 -- which is more than small enough to work for both INTEGER or LONG.  I just wanted to point this mismatch out, to draw attention to it so you wouldn't get bit by it in the future when it would matter more.)

Quote:INT
A 32-bit signed integer. The range is -2147483648 through 2147483647 decimal.

This type is declared in WinDef.h as follows:

typedef int INT;

I think this should have all the little glitches out and have the functions working 100% as advertised now.

Thanks Steve, I even referred to the page in the Wiki pointing out equivalent values (int = long, etc) but neglected to make the change. Like I said before, I'll get this. Smile

@DSMan195276 Thank you for the info as well. I only need this function to get the name of the current window that has focus outside of the main program. When the main window has focus (the window handles match) I'll indeed use _TITLE$ to avoid needing a delay.

I want to add a section in the tutorial's library lesson on how to use API calls so I'm taking a deep dive into the subject. Right now I just state it's possible and give a simple example.
There are two ways to write error-free programs; only the third one works.
QB64 Tutorial
Reply
#8
GetWindowTextW would require you to handle wide strings. I don't recommend unless you plan on making your whole program operate on wide strings. It can be done, as I did with my Win32 Media Player. However, it is quite the hassle.
Tread on those who tread on you

Reply
#9
(06-10-2024, 11:08 AM)SpriggsySpriggs Wrote: GetWindowTextW would require you to handle wide strings. I don't recommend unless you plan on making your whole program operate on wide strings. It can be done, as I did with my Win32 Media Player. However, it is quite the hassle.
Yep, I ended up using GetWindowTextA since it works for what I need but I do plan to investigate GetWindowTextW as it appears the return string is just padded with spaces and the return length needs to be multiplied by 2 ... I think.
There are two ways to write error-free programs; only the third one works.
QB64 Tutorial
Reply
#10
Not quite.   Wide-text is basically UNICODE.

https://learn.microsoft.com/en-us/window...th-strings

Now, unicode tends to work kinda odd.  It tries to keep file/memory sizes as small as possible.  Let me try and give a simplified version.

Let's say I have a dataset of 15 items, but the first 8 items appear 90% of the time.  How can I pack my data to make it as small as possible, using base-10 numbers?

Sure, I could use 2-digit numbers for everything:

Item 1 = 01
Item 2 = 02
Item 3 = 03
...
Item 14 = 14
Item 15 = 15
Item 16 = 16

If I have X items in a list, then that list is going to be 2 * X characters long.  Rather simple to understand.  Right?

But it's not the SMALLEST we can make!   Instead, let's say:
ITEM 1 to 8 are represented by the numbers 1 to 8.
Items 9 to 16 are represented by the number 9, followed by a 1 to 8.

Now, since 9 to 16 are rarely used (think the box characters in ASCII), we now can write the vast majority of our data with single digit values, and only occasionally need double digit values.

......

And that's basically UNICODE in a nutshell.

128 base characters for ANSI text.
128 extended characters which then point to extended character pages.

For most folks, 128 characters are all they need and use.  (How often do you use anything higher than that, honestly?)

For most strings, (65) is A.   (129, 65) = Accented A.  (130, 65 ) = Reverse accent A.

(See the ANSI value in there, and that extended value before it??)



Now, the problem comes with Unicode *ALWAYS* expanding!   First we had basically ANSI and the various ANSI code pages.  Then someone added japanses, Korean, Greek, hebrew, Klingon, Wingdings....   and then they added EMOJI... and then...

We ran out of room, so how do we expand??

Extended-Extended Characters!   And then Extend those more!  And extend those more!

In my 1 to 16 example, we saw:

1 to 8 was represented by 1 to 8.
9 to 16 was represented by 9, followed by 1 to 8.

So for 17 to 24, following that same pattern, it'd be:  9, followed by 9, followed by 1 to 8.
And 25 to 32?   9, 9, 9, 1 to 8...



And that's UNICODE!!

Unicode might be 1 character.  Or 2.  Or 8!!

So you can't honestly say "character * 2 for width".

"Steve is Big Grin " may be 16 chatacters in Unicode:
Steve_is_ <--- that's 9 characters.
Big Grin <-- this might be 6 unicode characters!

So that basic idea of "space + ASCII character" = WIDE character is only going to apply true for a small subset of characters.  If folks change character pages or use extended characters, that won't hold true at all.

And 2-bytes may not hold the whole character info with Wide characters, so you'll need to account for that as well.
Reply




Users browsing this thread: 13 Guest(s)