This Demo depends on
vbRichClient5 (version 5.0.21 and higher), as well as the
latest vbWidgets.dll.
One can download both new packages from the Download-page at:
http://vbrichclient.com/#/en/Downloads.htm
(vbWidgets.dll needs to be extracted from the GitHub-Download-Zip and placed beside vbRichClient5.dll,
there's small "RegisterInPlace-Scripts" for both Dll-Binaries now).
After both dependencies were installed, one can load the below Demo-Project into the VB-IDE:
QRandIMEDemo.zip
According to the Title of this Thread, we try to show the:
Free positioning of an IME-Window:
delegating its IME_Char-Messages into free choosable Widget-controls (the Demo does
that against cwTextBox-Widgets exclusively - but could accomplish that also against cwLabels
or cwImages.
Here is the interesting part (the IME-API-Declarations and Wrapper-Functions are left out),
which is contained in the new cIME-Class (available in the Code-Download from the vbWidgets-GitHub-Repo):
Code:
'RC5-SubClasser-Handler
Private Sub SC_WindowProc(Result As Long, ByVal Msg As Long, ByVal wParam As Long, ByVal lParam As Long)
Const WM_IME_SETCONTEXT = 641, WM_IME_STARTCOMPOSITION = 269, WM_IME_CHAR = 646
On Error GoTo 1
Select Case Msg
Case WM_IME_SETCONTEXT
SwitchOpenStatus wParam
Case WM_IME_STARTCOMPOSITION
HandleIMEPos
Case WM_IME_CHAR
Dim WFoc As cWidgetBase, KeyCode As Integer
Set WFoc = FocusedWidget: KeyCode = CInt("&H" & Hex(wParam And &HFFFF&))
If Not WFoc Is Nothing Then
If WFoc.Key = tmrFoc.Tag Then RaiseEvent HandleIMEChar(WFoc, KeyCode, ChrW(KeyCode))
End If
Exit Sub 'handled ourselves - so we skip the default message-handler at the end of this function
End Select
1: Result = SC.CallWindowProc(Msg, wParam, lParam)
End Sub
Private Sub tmrFoc_Timer()
HandleIMEPos
End Sub
Private Function FocusedWidget() As cWidgetBase
If Cairo.WidgetForms.Exists(hWnd) Then Set FocusedWidget = Cairo.WidgetForms(hWnd).WidgetRoot.ActiveWidget
End Function
Private Sub HandleIMEPos()
Dim WFoc As cWidgetBase, AllowIME As Boolean
On Error GoTo 1
Set WFoc = FocusedWidget
If WFoc Is Nothing Then
tmrFoc.Tag = ""
Else
RaiseEvent HandleIMEPositioning(WFoc, AllowIME)
If AllowIME Then tmrFoc.Tag = WFoc.Key
End If
1: SwitchOpenStatus AllowIME
End Sub
As one can see, this Class is (currently) only raising two Events to the outside -
received by a hosting (RC5) cWidgetForm-Class.
The elsewhere mentioned problems with "forcibly ANSIed" IME-WChars do not happen in
this Demo, because of a "full queue of W-capable APIs" (including a W-capable MessageLoop,
which is available in the RC5 per Cairo.WidgetForms.EnterMessageLoop...
The Integration of an RC5-cWidgetForm into an existing VB6-Project is relative easy (no need
to rewrite everything you have) - this Demo shows how one can accomplish that, by showing
the RC5-Form modally - starting from a normal VB-Form-CommandButton:
Here's all the code in the normal VB6-Starter-Form, which accomplishes that:
Code:
Option Explicit
Private VBFormAlreadyUnloaded As Boolean
Private Sub cmdShowRC5IMEForm_Click()
With New cfQRandIME ' instantiate the RC5-FormHosting-Class
.Form.Show , Me 'this will create and show the RC5-Form with the VB-Form as the underlying Parent
'now we enter the W-capable RC5-message-pump, which will loop "in place" till the RC5-Form gets closed again
Cairo.WidgetForms.EnterMessageLoop True, False
'the RC5-Form was closed, so let's read-out the Public Vars of its hosting cf-Class
If Not VBFormAlreadyUnloaded Then '<- ... read the comment in Form_Unload, on why we need to check this flag
Set Picture1.Picture = .QR1.QRSrf.Picture
Set Picture2.Picture = .QR2.QRSrf.Picture
End If
End With
End Sub
Private Sub Form_Unload(Cancel As Integer) 'this can happen whilst the RC5-ChildForm is showing, ...
VBFormAlreadyUnloaded = True 'so we set a Flag, to not implicitely load this VB-ParentForm again, when filling the Result-PicBoxes
End Sub
Private Sub Form_Terminate() 'the usual RC5-cleanup call (when the last VB-Form was going out of scope)
If Forms.Count = 0 Then New_c.CleanupRichClientDll
End Sub
The above Starter-Form (fMain.frm) will look this way
And pressing the CommandButton, it will produce the modal RC5-WidgetForm:
What one can see above is two (cwTextBox-based) Edit-Widgets - and the left one
is showing the free positioned IME-Window - the IME-Window (when visible), will
jump automatically, as soon as the user switches the Input-Focus to a different Widget.
To test this in a bit more extreme scenario even, I've made the two cwQRSimple-Widgets
(in the lower section of the Form) movable - and in case the IME-Window is shown
below one of them as in this ScreenShot:
... the IME-Window will follow the currently focused QR-Widget around, when it's dragged
with the Mouse...
Here's the complete code of the cfQRandIME.cls (which hosts the RC5-cWidgetForm-instance):
Code:
Option Explicit
Public WithEvents Form As cWidgetForm, WithEvents IME As cIME
Public QREnc As New cQREncode, QRDec As New cQRDecode 'the two (non-visible) QR-CodecClass-Vars
Public TB1 As cwTBoxWrap, TB2 As cwTBoxWrap 'the two TextBox-Wrapper-Classes
Public QR1 As cwQRSimple, QR2 As cwQRSimple 'the two QR-Widgets
Private Sub Class_Initialize()
Set Form = Cairo.WidgetForms.Create(vbFixedDialog, "QR-Widgets and IME-Window-Positioning", , 800, 600)
Form.IconImageKey = "QRico2"
Form.WidgetRoot.ImageKey = "bgPatForm"
Form.WidgetRoot.ImageKeyRenderBehaviour = ImgKeyRenderRepeat
Set IME = New cIME 'create the vbWidgets.cIME-instance
IME.BindToForm Form '...and bind our cWidgetForm-instance to it (IME will throw two Events at us then)
End Sub
Private Sub Form_Load() 'handle Widget-Creation and -Adding on this Form
Form.Widgets.Add(New cwSeparatorLabel, "Sep1", 11, 8, Form.ScaleWidth - 22, 42).SetCaptionAndImageKey "EditBox-DemoArea", "Edit", &H11AA66
Set TB1 = Form.Widgets.Add(New cwTBoxWrap, "TB1", 25, 60, 280, 38)
TB1.TBox.CueBannerText = "Session-Login..."
TB1.Widget.ImageKey = "session1"
Set TB2 = Form.Widgets.Add(New cwTBoxWrap, "TB2", 325, 60, 280, 38)
TB2.TBox.CueBannerText = "Place some Info here..."
TB2.Widget.ImageKey = "info1"
Form.Widgets.Add(New cwSeparatorLabel, "Sep2", 11, 155, Form.ScaleWidth - 22, 42).SetCaptionAndImageKey "QRCode-DemoArea", "Preview", &H1030EE
Set QR1 = Form.Widgets.Add(New cwQRSimple, "QR1", 25, 240, 250, 220)
Set QR2 = Form.Widgets.Add(New cwQRSimple, "QR2", 325, 280, 250, 220)
End Sub
Private Sub Form_BubblingEvent(Sender As Object, EventName As String, P1 As Variant, P2 As Variant, P3 As Variant, P4 As Variant, P5 As Variant, P6 As Variant, P7 As Variant)
If EventName = "Change" And TypeOf Sender Is cwTextBox Then 'we handle the Change-Event of the QRWidget-Child-Textboxes here
If Not (Sender Is QR1.TBox Or Sender Is QR2.TBox) Then Exit Sub
'resolve to the (TextBox-Hosting) cwQRSimple-Widget in question
Dim QR As cwQRSimple: Set QR = IIf(Sender Is QR1.TBox, QR1, QR2)
'Encode the current Text of our QR-Widget - and place the returned Pixel-Surface in QR.QRSrf
Set QR.QRSrf = QREnc.QREncode(New_c.Crypt.VBStringToUTF8(QR.Text))
'to verify, we perform a true Decoding of the QR-Text from the Pixels of the just created QR-Widgets QR-Surface
QRDec.DecodeFromSurface QR.QRSrf
'and reflect this decoded Unicode-StringResult in the Caption of the QR-Widget (so, ideally QR.Caption should match QR.Text)
If QRDec.QRDataLen(0) Then QR.Caption = New_c.Crypt.UTF8ToVBString(QRDec.QRData(0)) Else QR.Caption = ""
End If
'the QR-Widgets (cwQRSimple) are moveable - and in case they have an active IME-Window, we will move that too
If EventName = "W_Moving" And TypeOf Sender Is cwQRSimple Then IME_HandleIMEPositioning Sender.TBox.Widget, True
End Sub
Private Sub IME_HandleIMEPositioning(FocusedWidget As cWidgetBase, AllowIME As Boolean)
If TypeOf FocusedWidget.Object Is cwTextBox Then
AllowIME = True '<- here we allow IME-Windows only for cwTextBox-Widgets (but we could also allow IME on other Widget-Types)
IME.SetPosition FocusedWidget.AbsLeftPxl + 3, FocusedWidget.AbsTopPxl + FocusedWidget.ScaleHeightPxl + 4
End If
End Sub
Private Sub IME_HandleIMEChar(FocusedWidget As cWidgetBase, ByVal IMEKeyCode As Integer, IMEWChar As String)
FocusedWidget.KeyPress IMEKeyCode 'simply delegate the incoming IMEKeyCode into the Widget in question
'the above is the more generic delegation-method into any Widget (which are all derived from cWidgetBase)
'*alternatively* (for cwTextBoxes, which is the only Widget-Type we allow IME for in this Demo here)
'we could also use:
' Dim TB As cwTextBox
' Set TB = FocusedWidget.Object
' TB.SelText = IMEWChar
End Sub
Note the two blue marked EventHandlers at the bottom of the above code-section, which
make use of the two cIME-Events, which were mentioned at the top of this posting.
QR-Code Generation and Decoding:
The base QR-Encoding/Decoding-support is now included in vb_cairo_sqlite.dll (from two C-libs which are now statically contained).
And the vbWidgets.dll project contains the two Wrapper-Classes (cQREncode, cQRDecode) for these new exposed APIs.
cQREncode/cQRDecode is used in conjunction with thrown Change-Events of our cwQRSimple-Widgets
(which you saw in the ScreenShot above).
Here's the central Eventhandler which is contained in the RC5-WidgetForm-Hosting Class (cfQrandIME):
Code:
Private Sub Form_BubblingEvent(Sender As Object, EventName As String, P1, P2, P3, P4, P5, P6, P7)
If EventName = "Change" And TypeOf Sender Is cwTextBox Then 'we handle the Change-Event of the QRWidget-Child-Textboxes here
If Not (Sender Is QR1.TBox Or Sender Is QR2.TBox) Then Exit Sub
'resolve to the (TextBox-Hosting) cwQRSimple-Widget in question
Dim QR As cwQRSimple: Set QR = IIf(Sender Is QR1.TBox, QR1, QR2)
'Encode the current Text of our QR-Widget - and place the returned Pixel-Surface in QR.QRSrf
Set QR.QRSrf = QREnc.QREncode(New_c.Crypt.VBStringToUTF8(QR.Text))
'to verify, we perform a true Decoding of the QR-Text from the Pixels of the just created QR-Widgets QR-Surface
QRDec.DecodeFromSurface QR.QRSrf
'and reflect this decoded Unicode-StringResult in the Caption of the QR-Widget (so, ideally QR.Caption should match QR.Text)
If QRDec.QRDataLen(0) Then QR.Caption = New_c.Crypt.UTF8ToVBString(QRDec.QRData(0)) Else QR.Caption = ""
End If
'the QR-Widgets (cwQRSimple) are moveable - and in case they have an active IME-Window, we will move that too
If EventName = "W_Moving" And TypeOf Sender Is cwQRSimple Then IME_HandleIMEPositioning Sender.TBox.Widget, True
End Sub
So that's quite simple as far as QR-codes are concerned (because of the Bubbling-Event-mechanism of the
RC5-WidgetEngine - but also due to the quite powerful Cairo-ImageSurface-Objects, which are used in the
cQREncode/Decode-classes to transport the encoded (or to be decoded) Pixel-Information.
From a cCairoSurface it is possible, to write to PNG-, or JPG-ByteArrays or -Files at any time,
so exporting of the QR-Code-Images is not covered by this Demo - but would require only
a line of Code or two, in concrete adaptions of the above example.
Have fun,
Olaf