acCmdSpelling doesn't move out of field when spell check is done

As a Class there are some problems in structure.
1. The way it is set up now is really slow. You are creating an instance of your class on every call to the before update. Create it once and then use it for the life of the form. You could do this on the first before update, but it takes time for Word to load in the background. I would just do it on form load and after that it is very fast.
2. You pass in a form instance to your spelling check but do not use it in your code. No need for it.
3. I modified the function to return a list of words that are misSpelled. This gives the user a chance to determine if they want to spell check or not
Then I relied on the built in spell checker.

Code:
Private Sub ErrorDescription_BeforeUpdate(Cancel As Integer)
    '1. This is a lot of overhead to create a whole new instance of WDSC on every before update. Works best to set it once in the form load
    ' Set WDSC = New WDSpellCheck
    If WDSC Is Nothing Then Set WDSC = New WDSpellCheck
 
   '2. Do not need to pass the form since not used
    Dim MisSpelledWords As String
    MisSpelledWords = WDSC.MySpellCheck(Nz(Me.ActiveControl.OldValue, vbNullString), Nz(Me.ActiveControl.Value, vbNullString))
    If MisSpelledWords <> "" Then
       Dim rtn As String
       rtn = MsgBox("The following words were potentially mispelled:" & vbCrLf & vbCrLf & MisSpelledWords & vbCrLf & vbCrLf & "If you would like to spell check these then select YES.", vbYesNo, "Mispelled")
       If rtn = vbYes Then
         RunSpellCheck = True
       Else
         RunSpellCheck = False
       End If
     End If
End Sub


In your class you have a property Application you should set it once, and then just call it. You seem to do this multiple times even using local instances. You are defeating the purpose of a class the way this is structured

Code:
Public Function MySpellCheck(strToCheckOld As String, StrToCheckNew As String) As String
 
'1. You pass in the form and it is unused
' MySpellCheck(frm As Access.Form, strToCheckOld As String, StrToCheckNew As String) As Boolean
'2. You create a local variable with the same name as the class variable and then create another instance of WDSpellCheck
'Dim WDSPC As WDSpellCheck
  Dim aryStringtoCheck() As String
  Dim intAryIndex As Integer
  varStatusWait = SysCmd(acSysCmdSetStatus, "Invoking Your Custom Spellchecker. Please wait....")
'3. The below defeats the purpose of a class module
    'Set WDSPC = New WDSpellCheck
   
   '4. WHY pass in the Form? Unused.
   ' With frm
        If Nz(strToCheckOld, vbNullString) <> Nz(StrToCheckNew, vbNullString) Then
            aryStringtoCheck() = Split(Nz(StrToCheckNew, vbNullString), " ")
           mySpellCheck = ""
           For intAryIndex = 0 To UBound(aryStringtoCheck)
                If Me.Application.checkspelling(aryStringtoCheck(intAryIndex)) = False Then  '5. Use Me.application
                    'True means error
                    'pass back the word
                    MySpellCheck = MySpellCheck & aryStringtoCheck(intAryIndex) & vbCrLf
                    'If MsgBox("""" & aryStringtoCheck(intAryIndex) & """" & Chr(13) & Chr(10) & Chr(13) & Chr(10) & "is not spelled correctly." & Chr(13) & Chr(10) & _
                    '    "Correct the spelling or add the misspelled word to the dictionary.", vbYesNo, "Change or Add Word") = vbYes Then
                    '     Call AddToDict(aryStringtoCheck(intAryIndex))
                    'End If
                End If
            Next intAryIndex
        End If
    ' End With
    varStatusWait = SysCmd(acSysCmdClearStatus)

End Function

Trying to run the spell check in the before update not possible. See if this version using some of the class and the built in spell checker makes sense. Gives you a little of both. It is not truly before update, but provides much more flexibility
1. It alerts you if there are potential errors.
2. Allows you to decide if you want to spell check
3. Runs real spell check.

I would likely stick with using the built in spell checker. I always find these "unseen" automation instances to get hung up and the next thing you know you have 20 instances of Word running in the background. The first example uses a modified version of the word class, the second example uses just built in spelling.
 

Attachments

Thanks for the feedback. Although this is built around the spell checker, my primary goal was to try to understand classes and how to implement them.
I was well aware of problems in this V1, particularly around calling the classes repeatedly; I figured that was a bad idea and I was hoping there would be a better way to do things.

I'm also not opposed to invoking the built-in Access SpellChecker. In fact, last night I created my own V2 which calls Access' Spellchecker to compare the two approaches.

With regard to passing in the form reference, yup. I started out thinking one thing and neglected to clean up the arguments that I didn't need when I abandoned that approach.
 
particularly around calling the classes repeatedly; I figured that was a bad idea and I was hoping there would be a better way to do things.
The biggest issue I saw was you have a class property called Application which holds a reference to a Word Application, but you where not directly using it.
Once you set that in the class instantiation you open it and keep a reference to it.
So for the life of the instantiated class you simply need to refer to it as Me.Application from within the class. Or from the outside as WDSC.Application

There are some limitations of the built in spell checker where and when you can use it. AFAIK it will not work on a modal form. I think where you might get some utility is to fully build a spell checker pop up form with the same features as the normal spell checker window. Since Access does not expose any spelling checker object you could stick with word.
1. Push the text from the control to your form (probably open args). Show in textbox
2. Loop the words
3. Check for bad spelling highlight bad word using checkspelling
4. Return the spellingSuggestions collection and populate a listbox with the suggestions
4a. Click on an item in the listbox and swap with the selected word
4b. allow user to ignore
4c. continue if the user edited the text by hand
4d. Allow ignore all by saving to a dictionary
4e. Allow replace all
5. have options to modify search critieria
6. Push finished text back to the control

I think you could roll all the functionality of a standard spell checker window, but this may give you more flexibility when and where you can use it. This may be a lot of work for little payoff, but it still would be a good exercise in code and class design. I think if you did build it, it might come in handy in a lot of places where you run into limitations with calling the regular spell checker.
Might have to use Regexp to do you find and replace. Since you only want to replace full words.
 
Some time last year, I worked on a method of spell checking selected columns in a datasheet.
I used the built in spell checker and it worked fine
The tricky bit was selecting specified column(s) in code.
The approach I used may be of interest here:

 
I used the built in spell checker and it worked fine
The tricky bit was selecting specified column(s) in code.
Perhaps a little out of topic for the OP.

Working with dynamical forms make these things very easy.
In each control, where-ever it is situated, I can (dbl)click on a control to open a Zoom-form.
In this Zoom-form you find much functionality to "manipulete" the value of the control.
E.g. (the build-in) Spelling_checker, Translate to almost any language, word- or character-statistics, conversion (or part of) to uppercase, lowercase, Replacement of texts, etc. Depending on authorization and other context, it can be stored in the appropriate field.
If necessary, different Spelling_checkers can be added.

Regarding selecting column(s).
I do not use datasheets, because there is hardly control over the fields/controls. Instead I use a continuous (dynamical) form that is completely automatic generated for every Table/Identity.
Clicking on the header opens a form with many functions acting on colums: make the column non-/editable, colour a column with any colour, histogram. (desc) sorting on alfabet or on length, increase/decrease width of the column, change values in the column depending on a criterion, but depending on authorization, etc.
Even sorting after the first space could be an option. This functionality is build-in for any table in all applications.


Imb.
 
The biggest issue I saw was you have a class property called Application which holds a reference to a Word Application, but you where not directly using it.
Once you set that in the class instantiation you open it and keep a reference to it.
So for the life of the instantiated class you simply need to refer to it as Me.Application from within the class. Or from the outside as WDSC.Application

There are some limitations of the built in spell checker where and when you can use it. AFAIK it will not work on a modal form. I think where you might get some utility is to fully build a spell checker pop up form with the same features as the normal spell checker window. Since Access does not expose any spelling checker object you could stick with word.
1. Push the text from the control to your form (probably open args). Show in textbox
2. Loop the words
3. Check for bad spelling highlight bad word using checkspelling
4. Return the spellingSuggestions collection and populate a listbox with the suggestions
4a. Click on an item in the listbox and swap with the selected word
4b. allow user to ignore
4c. continue if the user edited the text by hand
4d. Allow ignore all by saving to a dictionary
4e. Allow replace all
5. have options to modify search critieria
6. Push finished text back to the control

I think you could roll all the functionality of a standard spell checker window, but this may give you more flexibility when and where you can use it. This may be a lot of work for little payoff, but it still would be a good exercise in code and class design. I think if you did build it, it might come in handy in a lot of places where you run into limitations with calling the regular spell checker.
Might have to use Regexp to do you find and replace. Since you only want to replace full words.
I haven't yet had time to work through all of the generous suggestions you've made. I very much appreciate your sharing your expertise.

Today should be free of other distractions.

To the comments above.

Application is, in fact, used, but it is ambiguous. I violated one of my own rules there, in fact.

Application is a reserved word, so of course, it's misleading.

Here:

If WDSPC.Application.checkspelling(aryStringtoCheck(intAryIndex)) = False Then

In that line, Application is a call to the Class property; without it an error is raised when that line tries to execute. Why? That's one of the things I hope to learn.

1736000960647.png





Once again, this spell checker is a vehicle to try to work through creating classes in VBA. I recognize that this is a long overdue effort on my part. But better late than never.

I really don't worry so much about implementing this as a useful tool; I'm not doing any serious data entry these days.

On the other hand, I am worried about putting off the day when I can't learn something new about Access.
 
Application is, in fact, used, but it is ambiguous. I violated one of my own rules there, in fact.
Application is a reserved word, so of course, it's misleading.
Here:
If WDSPC.Application.checkspelling(aryStringtoCheck(intAryIndex)) = False Then
In that line, Application is a call to the Class property; without it an error is raised when that line tries to execute. Why? That's one of the things I hope to learn.
Reserved words have to do with access database object names (mostly tables, fields, reports, forms, and controls) and not an issue here.
Reserved words" are words and symbols that have a specific meaning to Microsoft Access. If you use a reserved word or symbol to name a field in a desktop database or web app table, Access warns you that the word is reserved and that you might encounter errors when referring to the field.

You might also encounter errors if you use a reserved word to name a control, an object, or a variable. The error messages you receive don't necessarily tell you that a reserved word is the cause of the problem. As a result, it can be difficult to identify what needs to be changed. For example, Access might display a message similar to the following:

A class is fully encapsulated so all properties are protected. This is one of the beauties of a class. I routinely name properties of my classes the exact same names as common objects, methods, and properties (examples recordset, textbox, combbox, database, field, ..... etc.). This mirrors the functionality. This will not cause a conflict. I also routinely have methods like (requery, Insert, Find,...). I do this because the names are descriptive of what the property/method is. One example is I often create a custom collection class. And its properties have all the same names as a collection (item, add, count, remove). This way you would use it just like the Forms, Fields, Controls .... collection or any collection class you are used to.

If you think about it how many different types of objects have the same properties like
(text, name, value, key, ). It cannot be confused. If you have a property Application the only way it is called is through an instantiated object.
WDSC.Application.... cannot be confuse with Access.application, Word.application, Excel.Application or any other class that has an Application property.
 
Reserved words have to do with access database object names (mostly tables, fields, reports, forms, and controls) and not an issue here.


A class is fully encapsulated so all properties are protected. This is one of the beauties of a class. I routinely name properties of my classes the exact same names as common objects, methods, and properties (examples recordset, textbox, combbox, database, field, ..... etc.). This mirrors the functionality. This will not cause a conflict. I also routinely have methods like (requery, Insert, Find,...). I do this because the names are descriptive of what the property/method is. One example is I often create a custom collection class. And its properties have all the same names as a collection (item, add, count, remove). This way you would use it just like the Forms, Fields, Controls .... collection or any collection class you are used to.

If you think about it how many different types of objects have the same properties like
(text, name, value, key, ). It cannot be confused. If you have a property Application the only way it is called is through an instantiated object.
WDSC.Application.... cannot be confuse with Access.application, Word.application, Excel.Application or any other class that has an Application property.
I appreciate your efforts to help me get my head around this.

The way I created this originally, the .Application property would raise an error. So, I added the class property and it works.
 
See if this organization and naming makes more sense.
It was really confusing the way you had it. You had a class variable called WDSPC of type WordApplication. You had a local variable called WDSPC of type WDSpellCheck. You had an Application property that return the class variable WDSPC.

Code:
Private m_WordApplication As Object

Private Sub class_initialize()
    On Error Resume Next
    Set Me.WordApplication = GetObject(, "Word.Application")
    If Me.WordApplication Is Nothing Then Set Me.WordApplication = CreateObject("Word.Application")
End Sub

'The class holds a reference to a Word Application
Public Property Get WordApplication() As Object
    Set WordApplication = m_WordApplication
End Property
Public Property Set WordApplication(ByVal objNewValue As Object)
    Set m_WordApplication = objNewValue
End Property

Public Property Get MyCustomDictionary() As Object
    Set MyCustomDictionary = Me.WordApplication.CustomDictionaries.ActiveCustomDictionary
End Property

Private Sub class_terminate()
    On Error Resume Next
    If Not Me.WordApplication Is Nothing Then
        Me.WordApplication.Quit
        Set Me.WordApplication = Nothing
    End If
End Sub
'****************************************************************************************************************************************************************
'-----------------------------------------------------------------------------------   Function   -------------------------------------------------------------
'*****************************************************************************************************************************************************************

Public Function MySpellCheck(strToCheckOld As String, StrToCheckNew As String) As String
   Dim aryStringtoCheck() As String
   Dim intAryIndex As Integer
   Dim varStatusWait
   varStatusWait = SysCmd(acSysCmdSetStatus, "Invoking Your Custom Spellchecker. Please wait....")
    
    If Nz(strToCheckOld, vbNullString) <> Nz(StrToCheckNew, vbNullString) Then
            aryStringtoCheck() = Split(Nz(StrToCheckNew, vbNullString), " ")
            For intAryIndex = 0 To UBound(aryStringtoCheck)
                'Call the WordApplication
                If Me.WordApplication.checkspelling(aryStringtoCheck(intAryIndex)) = False Then  'Me.application
                    MySpellCheck = aryStringtoCheck(intAryIndex)
                    If MsgBox("""" & aryStringtoCheck(intAryIndex) & """" & Chr(13) & Chr(10) & Chr(13) & Chr(10) & "is not spelled correctly." & Chr(13) & Chr(10) & _
                        "Correct the spelling or add the misspelled word to the dictionary.", vbYesNo, "Change or Add Word") = vbYes Then
                         Call AddToDict(aryStringtoCheck(intAryIndex))
                    End If
                End If
            Next intAryIndex
        End If
    ' End With
    varStatusWait = SysCmd(acSysCmdClearStatus)

End Function
Public Sub AddToDict(strAddWord As String)
    Dim strDict As String
    'call the word application and the custom dictionary
    strDict = Me.WordApplication.Path & "\" & Me.MyCustomDictionary.Name
    Debug.Print strDict
    'I get an access error
    Open strDict For Append As #1
    Print #1, strAddWord & Chr(13) & Chr(10)
    Close #1
End Sub

I called the Class Properties WordAppliation and MyCustomDictionary just for clarity. But these could be called Application, CustomDictionary without conflict. So all methods that need to reference the word application can call it from inside the class using Me.WordApplication. Since it is public from outside the class it would be something like MySpellCheck.WordApplication. / MySpellCheck.MyCustomDictionary.
 
See if this organization and naming makes more sense.
It was really confusing the way you had it. You had a class variable called WDSPC of type WordApplication. You had a local variable called WDSPC of type WDSpellCheck. You had an Application property that return the class variable WDSPC.

Code:
Private m_WordApplication As Object

Private Sub class_initialize()
    On Error Resume Next
    Set Me.WordApplication = GetObject(, "Word.Application")
    If Me.WordApplication Is Nothing Then Set Me.WordApplication = CreateObject("Word.Application")
End Sub

'The class holds a reference to a Word Application
Public Property Get WordApplication() As Object
    Set WordApplication = m_WordApplication
End Property
Public Property Set WordApplication(ByVal objNewValue As Object)
    Set m_WordApplication = objNewValue
End Property

Public Property Get MyCustomDictionary() As Object
    Set MyCustomDictionary = Me.WordApplication.CustomDictionaries.ActiveCustomDictionary
End Property

Private Sub class_terminate()
    On Error Resume Next
    If Not Me.WordApplication Is Nothing Then
        Me.WordApplication.Quit
        Set Me.WordApplication = Nothing
    End If
End Sub
'****************************************************************************************************************************************************************
'-----------------------------------------------------------------------------------   Function   -------------------------------------------------------------
'*****************************************************************************************************************************************************************

Public Function MySpellCheck(strToCheckOld As String, StrToCheckNew As String) As String
   Dim aryStringtoCheck() As String
   Dim intAryIndex As Integer
   Dim varStatusWait
   varStatusWait = SysCmd(acSysCmdSetStatus, "Invoking Your Custom Spellchecker. Please wait....")
   
    If Nz(strToCheckOld, vbNullString) <> Nz(StrToCheckNew, vbNullString) Then
            aryStringtoCheck() = Split(Nz(StrToCheckNew, vbNullString), " ")
            For intAryIndex = 0 To UBound(aryStringtoCheck)
                'Call the WordApplication
                If Me.WordApplication.checkspelling(aryStringtoCheck(intAryIndex)) = False Then  'Me.application
                    MySpellCheck = aryStringtoCheck(intAryIndex)
                    If MsgBox("""" & aryStringtoCheck(intAryIndex) & """" & Chr(13) & Chr(10) & Chr(13) & Chr(10) & "is not spelled correctly." & Chr(13) & Chr(10) & _
                        "Correct the spelling or add the misspelled word to the dictionary.", vbYesNo, "Change or Add Word") = vbYes Then
                         Call AddToDict(aryStringtoCheck(intAryIndex))
                    End If
                End If
            Next intAryIndex
        End If
    ' End With
    varStatusWait = SysCmd(acSysCmdClearStatus)

End Function
Public Sub AddToDict(strAddWord As String)
    Dim strDict As String
    'call the word application and the custom dictionary
    strDict = Me.WordApplication.Path & "\" & Me.MyCustomDictionary.Name
    Debug.Print strDict
    'I get an access error
    Open strDict For Append As #1
    Print #1, strAddWord & Chr(13) & Chr(10)
    Close #1
End Sub

I called the Class Properties WordAppliation and MyCustomDictionary just for clarity. But these could be called Application, CustomDictionary without conflict. So all methods that need to reference the word application can call it from inside the class using Me.WordApplication. Since it is public from outside the class it would be something like MySpellCheck.WordApplication. / MySpellCheck.MyCustomDictionary.
Thank you so much.

The basis of the V1 attempt was some examples I found on the internet and wove together; poorly as it turns out.

However, I had partially stumbled my way through some of the boo-boos and made a couple of those changes. And that means your changes make even more sense.


I think it may be sinking in. I also appreciate your patience in explaining what must seem basic and obvious.
 
I learned OOP before learning Access. So to me every problem looks like a class solution is needed. In Access VBA if developing classes there are a few topics that I think you will get the most bang for your buck because they really support the things you do in access. They are discussed here.


1. Expanding the capabilities of a control by developing a class that traps the controls events. This is done using "withevents". This is probably the most common reason I am building a class
2. Raising custom events in a class
3. Building custom collection classes to hold and manage instances of your custom class. I seem to always want to do this.
 
I learned OOP before learning Access. So to me every problem looks like a class solution is needed. In Access VBA if developing classes there are a few topics that I think you will get the most bang for your buck because they really support the things you do in access. They are discussed here.


1. Expanding the capabilities of a control by developing a class that traps the controls events. This is done using "withevents". This is probably the most common reason I am building a class
2. Raising custom events in a class
3. Building custom collection classes to hold and manage instances of your custom class. I seem to always want to do this.
One thing I'm thinking about next, after I really feel like I've got a handle on the basics of this starter, is how to write a class so that it handle events on a form without having to write the code into an event in every control. For example, can you write it so that it responds to the GotFocus even of all textbox controls on a form without putting a line of code in each one of those GotFocus events. I assume that's possible.
 
For example, can you write it so that it responds to the GotFocus even of all textbox controls on a form without putting a line of code in each one of those GotFocus events. I assume that's possible.
Yes that is exactly what I was hinting to. Those are the types of things in Access you can really leverage classes.
There are several similar implementation of that here. All approaches are about the same idea, just slightly different implementation.

This also allows you to trap the same event in multiple ways. Imagine a textbox has a click event procedure which does something. The class can also trap that event and do something else. This is nice because you could associate all textboxes to a class/collection class and when they are clicked a specific generic thing happens. But the user could still define something within the on click event for a specific control and these would not usually interfere.
 
One thing I'm thinking about next, after I really feel like I've got a handle on the basics of this starter, is how to write a class so that it handle events on a form without having to write the code into an event in every control. For example, can you write it so that it responds to the GotFocus even of all textbox controls on a form without putting a line of code in each one of those GotFocus events. I assume that's possible.
Give this thread a read. I was trying to something very similar and I even uploaded a demo once I got it working.

Thread 'My Kung-fu no good' https://www.access-programmers.co.uk/forums/threads/my-kung-fu-no-good.294107/
 

Users who are viewing this thread

Back
Top Bottom