Here is an example of using an InkEdit control in "inkless mode" as a Unicode-aware RichTextBox.
But on Windows 8 and later there is more!
The program turns on the built-in Windows spellcheck capabilities of RichEdit version 8, which lives inside the InkEdit control when running on current versions of Windows.
Code:
Private Const WM_USER As Long = &H400&
Private Const EM_SETLANGOPTIONS As Long = WM_USER + 120&
Private Const IMF_SPELLCHECKING As Long = &H800&
Private Const IMF_TKBPREDICTION As Long = &H1000&
Private Const IMF_TKBAUTOCORRECTION As Long = &H2000&
Private Const EM_SETEDITSTYLE As Long = WM_USER + 204&
Private Const SES_USECTF As Long = &H10000
Private Const SES_CTFALLOWEMBED As Long = &H200000
Private Const SES_CTFALLOWSMARTTAG As Long = &H400000
Private Const SES_CTFALLOWPROOFING As Long = &H800000
Private Declare Function SendMessage Lib "user32" Alias "SendMessageW" ( _
ByVal hWnd As Long, _
ByVal wMsg As Long, _
ByVal wParam As Long, _
ByVal lParam As Long) As Long
Private Sub Form_Load()
With InkEdit1
SendMessage .hWnd, _
EM_SETLANGOPTIONS, _
0, _
IMF_SPELLCHECKING _
Or IMF_TKBPREDICTION _
Or IMF_TKBAUTOCORRECTION
SendMessage .hWnd, _
EM_SETEDITSTYLE, _
SES_USECTF _
Or SES_CTFALLOWEMBED _
Or SES_CTFALLOWSMARTTAG _
Or SES_CTFALLOWPROOFING, _
SES_USECTF _
Or SES_CTFALLOWEMBED _
Or SES_CTFALLOWSMARTTAG _
Or SES_CTFALLOWPROOFING
End With
End Sub
This post describes a little trick that lets you insert a table into RichEdit and into Word just using the keyboard. Admittedly in Word, you can use the handy table tool on the Insert tab with a mouse or with a keyboard. But in WordPad and the Windows RT RichEditBox, you don’t have a table tool.
So here’s the trick: on a line of its own type a plus (+) followed by as many dashes (-) as needed for the width of the first cell, followed by a plus to end that cell. If you only want a one column table, just hit the Enter key and you have a one cell table. To have more cells in the row, type more dashes and pluses, being sure to end the line with a plus. For example, hitting the Enter key at the end of the line
When you have as many cells as you want, to get another row, put the insertion point at the end of the row and hit the Enter key. Alternatively put the insertion point in the last cell and hit the Tab key. If the insertion point isn’t in the last cell of the table, a Tab advances to the next cell and a Shift+Tab backs up to the previous cell. When the insertion point is in the last cell of a table, the Tab key inserts another row. So pretty quickly you can enter a simple table with arbitrary numbers of cells, rows, and cell sizes. You can use the mouse to change the width of the columns.
You can also change the alignment of a table by selecting the whole table including the end of row marks and hitting ctrl+e or ctrl+r for centering and right-justifying the table, respectively. Ctrl+L left justifies the table. In Word there are a number of other cool tools such as the one to autofit the contents of a table (select the table, right-mouse click on it and choose the Autofit option).
No-acetate selection - makes selected text background color look like other controls', such as a TextBox. The selection color blue instead of the paler "acetate" blue (in most cases) that Windows controls have been moving to.
Underline color. Choose one of 16 predefined colors (or the text color) for underlining operations. The attached demo uses green.
Underline color will probably work for any InkEdit control back to at least Windows Vista. Possibly even in XP Tablet Edition and maybe even in the redist non-ink InkEdit that was once a free down for desktop XP Editions.
The other two may require Windows 8 or later, or they might work all the way back to XP Tablet Edition as well. I haven't tested this yet and the documentation doesn't say.
Last edited by dilettante; Oct 29th, 2017 at 10:04 AM.
To save downloading, here is the code in the second version:
Code:
Option Explicit
Private Const WM_USER As Long = &H400&
Private Const EM_GETOLEINTERFACE As Long = WM_USER + 60&
Private Const EM_SETTEXTEX As Long = WM_USER + 97&
Private Enum CP_PAGES
CP_UNICODE = 1200&
End Enum
Private Enum ST_FLAGS
ST_UNICODE = 8&
ST_PLACEHOLDERTEXT = 16&
End Enum
Private Type SETTEXTEX
flags As ST_FLAGS
codepage As CP_PAGES
End Type
Private Const EM_SETLANGOPTIONS As Long = WM_USER + 120&
Private Const IMF_SPELLCHECKING As Long = &H800&
Private Const IMF_TKBPREDICTION As Long = &H1000&
Private Const IMF_TKBAUTOCORRECTION As Long = &H2000&
Private Const EM_SETEDITSTYLE As Long = WM_USER + 204&
Private Const SES_USECTF As Long = &H10000
Private Const SES_CTFALLOWEMBED As Long = &H200000
Private Const SES_CTFALLOWSMARTTAG As Long = &H400000
Private Const SES_CTFALLOWPROOFING As Long = &H800000
Private Const EM_SETEDITSTYLEEX As Long = WM_USER + 275&
Private Const SES_EX_NOACETATESELECTION As Long = &H100000
Private Declare Function SendMessage Lib "user32" Alias "SendMessageW" ( _
ByVal hWnd As Long, _
ByVal wMsg As Long, _
ByVal wParam As Long, _
ByVal lParam As Long) As Long
Private Enum EF_COLORS
EF_TEXTCOLOR = 0 'Text color.
EF_BLACK = 1 'RGB(0, 0, 0)
EF_BLUE = 2 'RGB(0, 0, 255)
EF_CYAN = 3 'RGB(0, 255, 255)
EF_GREEN = 4 'RGB(0, 255, 0)
EF_MAGENTA = 5 'RGB(255, 0, 255)
EF_RED = 6 'RGB(255, 0, 0)
EF_YELLOW = 7 'RGB(255, 255, 0)
EF_WHITE = 8 'RGB(255, 255, 255)
EF_NAVY = 9 'RGB(0, 0, 128)
EF_TEAL = 10 'RGB(0, 128, 128)
EF_DKGREEN = 11 'RGB(0, 128, 0)
EF_PURPLE = 12 'RGB(128, 0, 128)
EF_DKRED = 13 'RGB(128, 0, 0)
EF_OLIVE = 14 'RGB(128, 128, 0)
EF_GRAY = 15 'RGB(128, 128, 128)
EF_SILVER = 16 'RGB(192, 192, 192)
End Enum
Private Doc As tom.ITextDocument
Private Sub Form_Load()
Dim SETTEXTEX As SETTEXTEX
Dim IUnknown As IUnknown
With InkEdit1
SendMessage .hWnd, _
EM_SETLANGOPTIONS, _
0, _
IMF_SPELLCHECKING _
Or IMF_TKBPREDICTION _
Or IMF_TKBAUTOCORRECTION
SendMessage .hWnd, _
EM_SETEDITSTYLE, _
SES_USECTF _
Or SES_CTFALLOWEMBED _
Or SES_CTFALLOWSMARTTAG _
Or SES_CTFALLOWPROOFING, _
SES_USECTF _
Or SES_CTFALLOWEMBED _
Or SES_CTFALLOWSMARTTAG _
Or SES_CTFALLOWPROOFING
SendMessage .hWnd, _
EM_SETEDITSTYLEEX, _
SES_EX_NOACETATESELECTION, _
SES_EX_NOACETATESELECTION
With SETTEXTEX
.flags = ST_PLACEHOLDERTEXT Or ST_UNICODE
.codepage = CP_UNICODE
End With
SendMessage .hWnd, _
EM_SETTEXTEX, _
VarPtr(SETTEXTEX), _
StrPtr("Prompt text...")
SendMessage .hWnd, _
EM_GETOLEINTERFACE, _
0, _
VarPtr(IUnknown)
Set Doc = IUnknown
End With
End Sub
Private Sub Form_Resize()
If WindowState <> vbMinimized Then
With Option1
InkEdit1.Move 0, .Top + .Height, ScaleWidth, ScaleHeight - (.Top + .Height)
End With
End If
End Sub
Private Sub InkEdit1_KeyPress(Char As Long)
'Implement Ctrl-U for Underline:
If Char = (vbKeyU And &H1F&) Then
With Doc.Selection.Font
If .Underline = tomNone Then
.Underline = tomSingle Or EF_GREEN * &H100&
Else
.Underline = tomNone
End If
End With
End If
End Sub
I am not sure about the const IMF_TKBAUTOCORRECTION since its value isn't documented. This value was suggested by a user and not confirmed authoritatively. The value shown above seems to be named IMF_IMEUIINTEGRATION in the Win8.1 and later Windows SDKs.
In any case it relates to the "touch keyboard" so it isn't needed in most desktop applications but it doesn't seem to hurt. IMF_TKBPREDICTION is also for "touch keyboard" usage.
This looks very interesting, but I have a quick question for you before I dive in. I have a lot of existing code using the TOM object library and some EM_* messages against a RichTextBox control (and in some cases against a RichEdit control created via the CreateWindow API). Does InkEdit support using the TOM object library and typically handle EM_* messages in the same way as the RichEdit library in your experience? Obviously I'll have to do my own in-depth testing, but I'm just wondering if it's even worth attempting.
Not yet! I only got as far as the first one and believe it or not I hadn't refreshed my tab since yesterday! I see tom.ITextDocument in the second example now, thanks for posting these demos.
I'll be interested to find out if the rendering/output is the exact same when coming from the Inkedit (especially EM_FORMATRANGE - not sure if the Inkedit plumbing re-uses the MS rich edit DLLs or if they have re-implemented stuff). My existing code relies heavily on specific text heights, widths, etc... for WYSIWYG printing, PDF export, page breaks, etc... so I don't want to break my existing documents. I'll try and experiment a bit on the weekend (the table stuff looks really useful too, so I hope there won't be a lot of heavy lifting!).
As far as I know a RichEdit 8 or later uses the Windows SpellCheck API which follows the current "keyboard language" of the user's session. The InkEdit wraps a RichEdit control without emulating the old version 1.0 control of Riched32.dll as a RichTextBox does.
If anyone wants to dig deeper, it should be possible to make use of the Windows Spell Checking API more directly as well.
The starting point appears to be the ISpellCheckerFactory interface. Using this API from VB6 probably requires a type library though, since none is contained within the MsSpellCheckingFacility.dll or as a .TLB file in Windows.
You will have a hard time getting this level of functionality out of a TextBox.
The idea seems to be to use late-model RichEdit controls to replace them everywhere. The InkEdit control is just an easy way to get them in a VB6 program. You could also create UserControls that wrap a late-model RichEdit, but it is far more effort of course.
You may be way ahead to just stop using TextBox for anything. RichEdit controls can be placed into "text only" mode to prevent users from adding RTF markup via the EM_SETTEXTMODE message.
I'll investigate a bit when I'll have time
Anyway, in my applications, I don't use Textbox, but my own usercontrol that encapsulate textbox.
So, it could be quite easy to update it, regarding the version of windows. Textbox for older windows, and inkedit for newer.
But of course, more investigations should be done to check performance, resources...
There is no spell check API until Windows 8, so I'm not sure what "older Windows" you expect this to work on. InkEdit itself comes with everything from XP Tablet Edition to Windows 10, and there is a redist version for desktop Editions of XP SP1 or later.
I'm not sure that the Tablet PC SDK is available any longer though. The downloads for version 1.5 and the newer 1.7 all seem to have been taken down years ago. You snooze you lose!
That's one of the main reasons why developers should not settle in on old versions of Windows too long. The Tablet Ink libraries have been around for more than a decade now.
Thanks for providing this info dilettante. I am finding the InkEdit control more and more useful, and I now find myself using it in preference to the TextBox control. Where does all this functionality come from? Is it Msftedit.dll?
J.A. Coutts
Addendum: In one of my applications, the spell checker works in the IDE, but not in the compiled version. Any ideas?
Last edited by couttsj; Nov 4th, 2017 at 09:47 PM.
As far as I know it is in Msftedit.dll's MSFTEDIT_CLASS.
Works fine compiled for me. I'm not sure why it wouldn't for you.
It appears to be a timing issue. For a simple demonstration program, it works fine in both modes. The program I am having trouble with is a lot more complex. So I took the one form that I was having trouble with and isolated it a new program. An low and behold, the same problem appeared. When I moved the SendMessage call from the form Load routine to the form Activate routine, it worked in both the IDE and compiled modes. It still didn't work in the original program, but at least I think I am on the right track.
Try doing this in Form_Initialize, no reason to do it repeatedly during a Form's lifetime.
This is what I ended up with. I used an InkEdit control and a 20 ms timer. The 20 ms was enough delay to allow the SendMessage call to take effect. I can't really explain this, but it may have something to do with VB delaying UI until after all initialization code is executed.
Code:
Option Explicit
Private Declare Function SendMessage Lib "user32" Alias "SendMessageW" (ByVal hWnd As Long, ByVal wMsg As Long, ByVal wParam As Long, ByVal lParam As Long) As Long
Private Sub Timer1_Timer()
Const EM_SETLANGOPTIONS As Long = &H478&
Const IMF_SPELLCHECKING As Long = &H800&
Timer1.Enabled = False
SendMessage txtMessage.hWnd, EM_SETLANGOPTIONS, 0, IMF_SPELLCHECKING
txtMessage.Text = "This is a dumy message with speling mistakees."
If Len(txtMessage.Text) Then txtMessage.SetFocus
End Sub
No idea. I did all of this on Windows 10 and it works fine.
You seem to be running into a lot of odd issues. I can't imagine why you need to delay ending those message, for example. The controls are already created and sited on the Form by the time Form_Activate is raised.
Something to consider is that the spell checking only occurs after "user typing." Assigning to .Text isn't "typing" though programmatic paste would be considered "typing."
No idea. I did all of this on Windows 10 and it works fine.
You seem to be running into a lot of odd issues. I can't imagine why you need to delay ending those message, for example. The controls are already created and sited on the Form by the time Form_Activate is raised.
Something to consider is that the spell checking only occurs after "user typing." Assigning to .Text isn't "typing" though programmatic paste would be considered "typing."
Interesting! I had already discovered that Spell Check must be enabled before applying text to the Ink Edit box, but on Win 8.1 Spell Check is applied to anything that is added. Just for the heck of it, I tried typing some new text on the Win 10 machine after applying the misspelled text programmatically, and indeed spell check was applied to the new text. So it appears that Win 8.1 and Win 10 work slightly different.
Once again I have no explanation as to why, but the Win 10 response is quite adequate.
Well even the guy who works on RichEdit itself describes the Windows 10 behavior. I presume that like most people Windows 8.1 Update 2 is now obsolete, ancient history, much less the first Windows 8.1 or Windows 8.
There may be nobody at Microsoft even working with the old OSs anymore except for those who have to create security patches for the ones still in extended support.
Why it changed behavior isn't explained, but a lot was done to optimize RichEdit 8 after its initial version. Maybe STREAMIN operations bypass the part that spell checking is hooked into.
It has taken me a bit longer than I'd hoped to try replacing my current RichTextBox with the InkEdit, but I've finally gotten started.
One little "gotcha" - If you have your RTB on a UserControl and you replace it with an InkEdit control you may get a "circular references" error when you try to run/compile. This error is misleading - the problem is that some InkEdit events have different signatures that the RichTextBox. In particular Mouse* events have ByVal parameters, and the first 2 parameters are Integer, while the next 2 are Longs. RTB has ByRef parameters and while the first 2 are also Integers, the second 2 are Singles.
Compare:
Code:
Private Sub rtbInkEdit_MouseMove(ByVal Button As Integer, ByVal Shift As Integer, ByVal x As Long, ByVal y As Long)
vs:
Code:
Private Sub rtbInkEdit_MouseMove(Button As Integer, Shift As Integer, x As Single, y As Single)
I haven't done enough testing yet to know if the Long/Single difference means that the InkEdit is ignoring the parent ScaleMode and just passing pixels or twips, or if it is just dropping precision for ScaleModes like Inches.
Anyway, all of this is a bit off topic here, so I'll probably start a RTB>InkEdit migration topic as a separate thread but I just thought I'd update here first since I posted here earlier.
Thanks to Diletantte for introducing me to this control and for his continued demonstrations of its features.
For your info, I discovered a number of differences between a TextBox and an InkEdit control. The biggest one for me had to do with CR/CRLF. I could not set the CR to zero in the KeyPress event. It always displayed regardless, so I had to use the KeyDown event instead.
The other issue was with SelText. When using SelStart, that function only uses CR when accounting for multiple lines, whereas the .Text function returns a CRLF for each line.
There also appears to be differences between Windows versions. In more than one situation, I had to use a 20ms delay using Win 8.1, whereas dilettante did not using Win 10.
Thanks for that info couttsj. The CR/CRLF thing is causing me a bit of grief right now since I have stored character positions for special text runs in a database and I have to now account for the difference in length when the runs contain new lines. It's a pain, but I'll figure something out.
Anyway, instead of polluting Diletantte's thread further, I've started the following thread for tips on migrating from RTB to InkEdit here:
I started to embed the InkEdit in a usercontrol, in order to replace some textbox in my application, to benefit the spellchecking.
Everything is going nearly well.
But, I have an annoying problem.
As the InkEdit is embed in a UserControl, it seems that it eats the TabKey used to go on the next control, or the previous control.
I tried everything, but no way to go on the next control when pressing the Tab key.
Of course I put debug everywhere with no success