[RESOLVED] How to get Unicode char on IME window in VB6?
You just imagine you are doing an Editable Flexgrid or Grid type of UserControl...
I have a Flexgrid. I add a textbox for text input.
Double Click the Cell, the textbox will appear then I can typing my text without any problem. After typing, I press <Enter> key to move focus to next Cell (textbox will turn invisible,the text set to Cell of course) ,then press Alphabetic Key, before textbox1 turn visible, the Alphabetic KeyCode automatically goto invisible textbox,after that,the textbox appear.
The problem is that I lost Unicode if the Alphabetic KeyCode represent Unicode on IME But English and numeric is always OK.
The sequence logic is:
Select a Cell by Mouse --> Press <Alphabetic Key> -->Bring out IME -->IME will appear word combinations ---> I choose the 1st then press <Enter> key --> the TextBox appearing with the word -->Textbox got focus already,I can typing what I want -->Press Enter key to finish typing--->Focus Move to Next Cell ---> Press <Alphabetic Key> ---...
The problem is not vb6 textbox is non-unicode, VB6 textbox fully support Chinese/Japanese/Korean when OS is Chinese/Japanese/Korean
The problem is how to get IME Unicode without using Textbox or RichEdit.
I made a demo for simulation.
1. Select your IME input Language except English, otherwise,IME window doesn't appear. I use Baidu input as IME. You can use MS IME.
2. Click Picture1 to gain focus
3. Typing Alphabetic Key, IME is appearing and IME windows got few word combinations. Choose one.
4. Enter key to transfer text on IME to textbox.
5. The word is suppose go to invisible textbox before visible.
6. After textbox visible, I see my Unicode lost with question mark or weird characters.
Refer to screenshot and demo project.
You also can learn how to pisition IME window.
Code:
Select Case uMsg
Case WM_IME_SETCONTEXT
If Not wParam = 0 Then
Dim flag As Boolean
flag = ImmAssociateContextEx(lng_hWnd, 0, 16)
If flag Then
Dim IntPtr As Long
IntPtr = ImmGetContext(lng_hWnd)
flag = ImmSetOpenStatus(IntPtr, True)
End If
End If
Case WM_IME_STARTCOMPOSITION
Dim hIMC As Long
hIMC = ImmGetContext(lng_hWnd)
Dim cf As COMPOSITIONFORM
cf.dwStyle = 2
cf.ptCurrentPos.X = Picture1.ScaleLeft + 3
cf.ptCurrentPos.Y = Picture1.ScaleTop + Picture1.Height - 16
ImmSetCompositionWindow hIMC, cf
Case WM_IME_CHAR
'Send IME Char to Picture1.KeyPress
Picture1_KeyPress CUIntToInt(wParam And &HFFFF&)
End Select
Last edited by Jonney; Jan 16th, 2015 at 11:43 PM.
I used ImmGetCompositionString API but always get zero size. Any idea?
Code:
Private Const GCS_RESULTREADSTR As Long = &H200
Private Const GCS_COMPSTR As Long = 8
Private Declare Function ImmAssociateContextEx Lib "imm32.dll" (ByVal hwnd As Long, ByVal himc As Long, ByVal dwFlags As Long) As Long
Private Declare Function ImmGetContext Lib "imm32.dll" (ByVal hwnd As Long) As Long
Private Declare Function ImmSetOpenStatus Lib "imm32.dll" (ByVal himc As Long, ByVal b As Long) As Long
Private Declare Function ImmSetCompositionWindow Lib "imm32.dll" (ByVal himc As Long, lpCompositionForm As COMPOSITIONFORM) As Long
Private Const WM_KEYDOWN As Long = &H100
Private Const WM_KEYUP As Long = &H101
Private Const WM_CHAR As Long = &H102
Private Const WM_UNICHAR As Long = &H109, UNICODE_NOCHAR As Long = &HFFFF&
Private Declare Function PeekMessage Lib "user32" Alias "PeekMessageW" (ByRef lpMsg As TMSG, ByVal hwnd As Long, ByVal wMsgFilterMin As Long, ByVal wMsgFilterMax As Long, ByVal wRemoveMsg As Long) As Long
Public Declare Function ImmGetCompositionStringW Lib "imm32.dll" _
(ByVal himc As Long, ByVal dw As Long, _
ByRef lpv As Long, ByVal dw2 As Long) As Long
Public Declare Function ImmReleaseContext Lib "imm32.dll" (ByVal hwnd As Long, ByVal himc As Long) As Long
Public Function GetCompStr(lngTargetHwnd As Long) As String
Dim lngICHwnd As Long
Dim lngdwSize As Long
Dim strBuffer As String
lngICHwnd = ImmGetContext(lngTargetHwnd)
If lngICHwnd <> 0 Then
lngdwSize = ImmGetCompositionStringW(lngICHwnd, GCS_RESULTREADSTR, 0, 0)
strBuffer = String$(lngdwSize + 1, vbNullChar)
lngdwSize = ImmGetCompositionStringW(lngICHwnd, GCS_RESULTREADSTR, StrPtr(strBuffer), lngdwSize + 1)
Call ImmReleaseContext(lngTargetHwnd, lngICHwnd)
strBuffer = Left$(strBuffer, lngdwSize)
GetCompStr = strBuffer
End If
End Function
Edited: I cannot get this GetCompStr function to work properly at this moment. Will try later on.
OK.
After I use Krool's Subclasser, now I can get CORRECT Unicode Char. Very Strange. The Subclasser may internally turn Unicode to ANSI or failed to prevent Windows from UNICODE to ANSI conversion.
Re: [RESOLVED] How to get Unicode char on IME window in VB6?
In Krool's TextBoxW, since it is Unicode Window which created by CreateWindowExW, I can see wParam is Unicode Keycode in WM_CHAR and WM_IME_CHAR. But the UserControl and Form is ANSI window, how to get its assocoated IME Unicode Char? I saw wParam give keycode 63 (Question mark) in in WM_CHAR and WM_IME_CHAR. Window crash the unicode.
Re: [RESOLVED] How to get Unicode char on IME window in VB6?
Yes, there was a lot of discussion on this in Krool's Common Controls thread. A few different solutions were provided, but georgekar gave the simplest one - here is Krool's discussion of how he used georgekar's idea:
WM_CHAR and WM_IME_CHAR are too late to recover the original Unicode character. You have to cache the value before WM_CHAR; otherwise VB's ANSI pump will eat it. (Between the WM_KEYDOWN/WM_IME_KEYDOWN events, and the WM_CHAR/WM_IME_CHAR events, VB's parent window gets a chance to preview the key data, so it can look for accelerator keypresses. That preview is what ruins Unicode data, because VB uses TranslateMessageA.)
I think georgekar's solution could also be used for the IME window, but in the WM_IME_KEYDOWN event instead of WM_KEYDOWN. Use PeekMessage in the same way, to see the correct Unicode character value in WM_IME_KEYDOWN. Cache this value, then later, when WM_IME_CHAR happens, you can use the cached value to obtain the correct Unicode value, since you intercepted it before VB's ANSI message pump ruined it.
Re: [RESOLVED] How to get Unicode char on IME window in VB6?
Originally Posted by Tanner_H
Yes, there was a lot of discussion on this in Krool's Common Controls thread. A few different solutions were provided, but georgekar gave the simplest one - here is Krool's discussion of how he used georgekar's idea:
WM_CHAR and WM_IME_CHAR are too late to recover the original Unicode character. You have to cache the value before WM_CHAR; otherwise VB's ANSI pump will eat it. (Between the WM_KEYDOWN/WM_IME_KEYDOWN events, and the WM_CHAR/WM_IME_CHAR events, VB's parent window gets a chance to preview the key data, so it can look for accelerator keypresses. That preview is what ruins Unicode data, because VB uses TranslateMessageA.)
I think georgekar's solution could also be used for the IME window, but in the WM_IME_KEYDOWN event instead of WM_KEYDOWN. Use PeekMessage in the same way, to see the correct Unicode character value in WM_IME_KEYDOWN. Cache this value, then later, when WM_IME_CHAR happens, you can use the cached value to obtain the correct Unicode value, since you intercepted it before VB's ANSI message pump ruined it.
the subclasser failed to Intercept WM_IME_CHAR for some reason.
I think the problem is more complicated than TextBoxW. Normal UC or Form is ANSI window in locale English 1033. I see wParam is always 63 in WM_IME_CHAR or WM_UNICHAR or WM_CHAR when I am typing Unicode. But in Krool's TextBoxW (the window createWindowExW) is unicode window, I can see wParam is Unicode Keycode in English locale 1033. PeekMessage technique in Krool's TextBoxW is useful only when we using Kazah keyboard.
The same code in C# works with whatever locale either 1033 or 2052, since C# window is always Unicode. I guess.
If it does, the PeekMessage technique should work.
PeekMessage technique in Krool's TextBoxW is useful only when we using Kazah keyboard.
The PeekMessageW technique is useful any time you are not receiving Unicode characters correctly. (The reason for not receiving the characters doesn't really matter.)
PeekMessage is useful because it lets you see the matching WM_CHAR (or WM_IME_CHAR, if you modify the code) for a given keypress, before the message gets dispatched to VB. So if VB's message pump is removing the Unicode character, for any reason, PeekMessage lets you work around it.
So my question is: if you subclass WM_IME_KEYDOWN or WM_KEYDOWN, and use PeekMessageW inside the wndProc to check for a WM_CHAR (or WM_IME_CHAR) value, does PeekMessageW return the correct Unicode value?
If it does, the PeekMessage technique should work.
The PeekMessageW technique is useful any time you are not receiving Unicode characters correctly. (The reason for not receiving the characters doesn't really matter.)
PeekMessage is useful because it lets you see the matching WM_CHAR (or WM_IME_CHAR, if you modify the code) for a given keypress, before the message gets dispatched to VB. So if VB's message pump is removing the Unicode character, for any reason, PeekMessage lets you work around it.
So my question is: if you subclass WM_IME_KEYDOWN or WM_KEYDOWN, and use PeekMessageW inside the wndProc to check for a WM_CHAR (or WM_IME_CHAR) value, does PeekMessageW return the correct Unicode value?
I can't intercept WM_IME_KEYDOWN in UserControl Subclass procedure.
I think my problem is that IME attached to ANSI UserControl window (Form/Picturebox are all ANSI window),so WM_IME_CHAR just give me wParam=63 always.
We can easily simulated in locale =1033 (Language setting in English USA) by turning Krools TextBoxW into ANSI using CreateWindowExA API, then WM_IME_CHAR will give 63 always. (I haven't tried, but theory is here).
If it does, the PeekMessage technique should work.
The PeekMessageW technique is useful any time you are not receiving Unicode characters correctly. (The reason for not receiving the characters doesn't really matter.)
PeekMessage is useful because it lets you see the matching WM_CHAR (or WM_IME_CHAR, if you modify the code) for a given keypress, before the message gets dispatched to VB. So if VB's message pump is removing the Unicode character, for any reason, PeekMessage lets you work around it.
So my question is: if you subclass WM_IME_KEYDOWN or WM_KEYDOWN, and use PeekMessageW inside the wndProc to check for a WM_CHAR (or WM_IME_CHAR) value, does PeekMessageW return the correct Unicode value?
I can't intercept WM_IME_KEYDOWN at all in UserControl Subclass procedure. I falied to detect this message.
I think my problem is that IME attached to ANSI UserControl window (Form/Picturebox are all ANSI window),so WM_IME_CHAR just give me wParam=63 always because VB6 will deal with ANSI type API instead of W type API internally (sorry,not accurate really). Unicode just lost.
We can easily simulated in locale =1033 (Region and Language setting in English USA) by turning Krools TextBoxW into ANSI using CreateWindowExA API, then WM_IME_CHAR will give 63 always if we are typing Unicode. (I haven't tried, but theory is here).
Re: [RESOLVED] How to get Unicode char on IME window in VB6?
Thanks for clarifying, Jonney. I also tried the sample project you provided, and even after trying many things, I couldn't find a way to successfully trap WM_IME_KEYDOWN. I have no idea why that message isn't being captured. I think LaVolpe's suggestion in your other thread, to just use a temporary TextBoxW, is the best solution.
For what it's worth, I did find some bugs in your ImmGetCompositionString code. Change the declaration to this:
Code:
Public Declare Function ImmGetCompositionStringW Lib "imm32.dll" (ByVal himc As Long, ByVal dw As Long, ByVal lpv As Long, ByVal dw2 As Long) As Long
Whenever you pass a pointer directly to an API (StrPtr, VarPtr), you need to change the API parameter to be ByVal instead of ByRef.
Another bug is this: when you pass 0 as the length to ImmGetCompositionString, so it will return the size of the string buffer, the buffer size is returned in bytes, not characters, and it doesn't include the null-terminating character. Because of this, you might find it easier to pass the function a byte array instead of a VB string, then convert the byte array after the API has filled it.
However, even with these changes, I still don't get useful results from the function. It could possibly be an ANSI/Unicode issue, or maybe there is another bug I haven't caught...?
Re: [RESOLVED] How to get Unicode char on IME window in VB6?
Originally Posted by Tanner_H
Thanks for clarifying, Jonney. I also tried the sample project you provided, and even after trying many things, I couldn't find a way to successfully trap WM_IME_KEYDOWN. I have no idea why that message isn't being captured. I think LaVolpe's suggestion in your other thread, to just use a temporary TextBoxW, is the best solution.
For what it's worth, I did find some bugs in your ImmGetCompositionString code. Change the declaration to this:
Code:
Public Declare Function ImmGetCompositionStringW Lib "imm32.dll" (ByVal himc As Long, ByVal dw As Long, ByVal lpv As Long, ByVal dw2 As Long) As Long
Whenever you pass a pointer directly to an API (StrPtr, VarPtr), you need to change the API parameter to be ByVal instead of ByRef.
Another bug is this: when you pass 0 as the length to ImmGetCompositionString, so it will return the size of the string buffer, the buffer size is returned in bytes, not characters, and it doesn't include the null-terminating character. Because of this, you might find it easier to pass the function a byte array instead of a VB string, then convert the byte array after the API has filled it.
However, even with these changes, I still don't get useful results from the function. It could possibly be an ANSI/Unicode issue, or maybe there is another bug I haven't caught...?
Thank you for follow-up.
I tried many definitions for ImmGetCompositionStringW API but always return 0. The problem is that I can't intercept most IME Message except 3: WM_IME_SETCONTEXT,WM_IME_STARTCOMPOSITION and WM_IME_CHAR.
I do believe it can be done, since there're thousand free INPUT methods in Asia except MS keyboard PinYin.
Wondering I haven't seen any stuff in VB6. vbaccelerator and cyberActivex don't have such stuff.
If you got time (don't spend much), you can do some research since you have much more knowledge on Hook... than me.
Your applications should use RegisterClassW to cause the WM_CHAR and WM_IME_CHAR messages to retrieve Unicode characters instead of DBCS characters in the wParam parameter.
So, as already suggested before, creating a Unicode Edit control is your best bet.
On Local Error Resume Next: If Not Empty Is Nothing Then Do While Null: ReDim i(True To False) As Currency: Loop: Else Debug.Assert CCur(CLng(CInt(CBool(False Imp True Xor False Eqv True)))): Stop: On Local Error GoTo 0
So, as already suggested before, creating a Unicode Edit control is your best bet.
Not sure if that alone will solve the issue, since VBs Main-MessagePump is still ANSI...
I have no issues here, to capture the IME-Char-Message directly (quite similar to the C#-code the OP was showing),
when the (Unicode)SubClassing happens against an RC5-cWidgetform (which run on their own W-capable MessagePump).
In that case no Extra-PeekMessageW-Tricks are necessary.
Re: [RESOLVED] How to get Unicode char on IME window in VB6?
Originally Posted by Schmidt
Not sure if that alone will solve the issue, since VBs Main-MessagePump is still ANSI...
I have no issues here, to capture the IME-Char-Message directly (quite similar to the C#-code the OP was showing),
when the (Unicode)SubClassing happens against an RC5-cWidgetform (which run on their own W-capable MessagePump).
In that case no Extra-PeekMessageW-Tricks are necessary.
Olaf
Not yet study to use RichCleint5 to build an UserControl in class (or module), if a demo is available, please give me so at I can try.
The problem is VB6's UC is ANSI window, so if locale is 1033 (English), IME never give Unicode Char KeyCode when we intercept WM_IME_CHAR / WM_UNICHAR or WM_CHAR.
Last edited by Jonney; Jan 23rd, 2015 at 04:27 AM.