RefEdit Exit not firing

P

Paul

I have a form where I want the user to select a range on which to process.
In order to check the range that the user has selected, I want to use the
RefEdit_Exit.

However, Excel keeps failing when attempting to put validation code behind
it - sadly to the extent that I get the 'Excel has encountered ....... etc.'
and Excel restarts.

I've tried a simple MsgBox in the Exit code and that seems to work fine, but
as soon as I try any other code it doesn't fire.
I've tried putting a breakpoint on the first line of code, but the operation
fails without reaching the breakpoint.

I must admit, I'm at a bit of a loss.
 
O

OssieMac

Hi Paul,

Without seeing your code I wonder if you are getting into an eternal loop by
trying to set the focus back to the control when there is an error. Need to
disable events.

The following little test works.

Private Sub RefEdit1_Exit(ByVal Cancel As MSForms.ReturnBoolean)
Application.EnableEvents = False
On Error GoTo ReEnableEvents

If Range(Me.RefEdit1).Column > 4 Then
Cancel = True 'Cancels the Exit
MsgBox "Must Select from first 4 columns"
End If

ReEnableEvents:
Application.EnableEvents = True
End Sub


During development you can comment out the following line so that you can
easily identify errors.
On Error GoTo ReEnableEvents

However, having said that, you then need the following code to re-enable
events if they get turned off and not turned back on due to an error. You can
put the code anywhere and to run just place the cursor anywhere in the sub
and press F5. (You might have already known this but it will save you from
tearing your hair out if you didn't.)

Sub Re_EnableEvents()
'Use during development to turn events on
'if code leaves them turned off.
Application.EnableEvents = True
End Sub

If your question is still not answered then perhaps you can post the
validation code you are using.
 
O

OssieMac

Hi again Paul,

I meant to include that you don't need to disable events in the example I
posted. I included them to show how to disable them if your validation code
is causing an endless loop by re-calling the Exit event.
 
O

OssieMac

Hi yet again Paul,

I have been doing some testing and found a problem that can send Excel into
a Flip and it has to close. Trying to use the RefEdit value (which is
actually a string) when it will return an invalid range then Excel flips.
Biggest problem is if it is blank and you try to use it but have also found
typing invalid values can do it.

The most complete test I can think of is to attempt to use it to assign to a
range variable and if an error is returned then it is invalid. If it is a
valid range then you can use that range variable to continue your validity
testing to see if it is within the desired range selections.

The following is an example.
Private Sub RefEdit1_Exit(ByVal Cancel As MSForms.ReturnBoolean)

Dim rngTest As Range

'Attempt to assign to a range variable
On Error Resume Next
Set rngTest = Range(Me.RefEdit1.Value)
If Err.Number > 0 Then 'Invalid range
MsgBox "Invalid range. Must select a range."
Cancel = True 'Cancels the Exit
Exit Sub
End If

On Error GoTo 0 'Reset error trapping

'Can now use the range variable to test
'if it is within the valid ranges to select.
If rngTest.Column > 4 Then
Cancel = True 'Cancels the Exit
MsgBox "Must Select from first 4 columns."
End If

End Sub


As before, feel free to get back to me.
 
P

Paul

I've tried using your code 'as is', but get exactly the same result.

I've been using the control to select a range on the screen rather than
entering text, so there's little chace that the text in the control is
invalid as a range.

Even placing a breakpoint on the very first line is either ignored or the
failure occurs before any code is run !!
 
P

Paul

This works

Private Sub SrcRef_Exit(ByVal Cancel As MSForms.ReturnBoolean)
n = MsgBox("It works so far")
End Sub

But this doesn't

Private Sub SrcRef_Exit(ByVal Cancel As MSForms.ReturnBoolean)
n = MsgBox("It works so far")


Dim rngTest As Range

'Attempt to assign to a range variable
On Error Resume Next
Set rngTest = Range(Me.SrcRef.Value)
If Err.Number > 0 Then 'Invalid range
MsgBox "Invalid range. Must select a range."
Cancel = True 'Cancels the Exit
Exit Sub
End If

On Error GoTo 0 'Reset error trapping

zUserEntry = 0
Call Valid_Range_Selection

End Sub

However, if I disable the call - it works fine again
 
O

OssieMac

Hi again Paul,

I have managed to lock up the system by calling another sub. It appears to
be loosing a connection and/or reference between the objects when calling
subs.

A few questions and comments.

What version of xl are you using? (I have been testing in xl2007 but I also
have xl2002 and can get access to xl2003 at times.)

What reference style are you using? (A1:B2 etc or R1C1 style because R1C1
has problems.

Have you tried putting the validation code in the Private Sub SrcRef_Exit
instead of calling another routine? With my testing it appears to work
probably because it does not loose the connections between objects.

Modeless forms also cause lockups. Need to have showModal property = true
(or when showing the form with code it must be modal.)

Can you post the code you are using for the validation then perhaps I can do
some further testing.

Any errors in the code do not necessary cause the code to stop at the error.
It either ignores the sub and it does not run or locks up the system.
Therefore compile all code before running. (click on Debug -> compile.)
 
P

Paul

Hi OssieMac

I'm using XL2003
Reference style is A1:B2
I have tried with the validation code within the _Exit sub
Modal is set to True

I'll post the code once I've stripped out the comments (otherwise there's
pages !!)
 
P

Paul

_Exit Code :
Private Sub SrcRef_Exit(ByVal Cancel As MSForms.ReturnBoolean)
nRangeOK = Valid_Range_Selection
Select Case nRangeOK
Case 0
' This changes the visible or enabled status of other controls on the form,
but removing it has no effect on the failure or otherwise
Call Set_Control_Status
Case 1
pCancel = True
Case 2
zDoIt = False
Unload Me
End Select
End Sub



Validation Code :
Function Valid_Range_Selection(Optional pMessage, Optional pPopulate)
' Valid_Range_Selection is the value returned to the calling code to
determine the validation and selection
' 0 - Validation OK
' 1 - Validation failed - user selected to retry
' 2 - Validation failed - user selected to cancel import
Valid_Range_Selection = 0
cErrorText = ""

' If zFirst = True And nSourceColumns = 1 And nSourceRows = 1 Then
' If zFirst = True Then
' Exit Function
' End If

If IsMissing(pMessage) Then pMessage = True
If IsMissing(pPopulate) Then pPopulate = True

nSourceCol = Selection.Column
nSourceColumns = Selection.Columns.Count
nSourceRows = Selection.Rows.Count

GoSub Check_Range_Size

GoSub Check_No_Blanks

GoSub Check_No_Duplicates

If Valid_Range_Selection = 0 And pPopulate = True Then
Call Populate_Source_Data_View
End If

If Valid_Range_Selection <> 0 And pMessage = True Then
n = MsgBox(cErrorText, vbstop + vbOKOnly, "Table import")
Valid_Range_Selection = 1
End If
Exit Function

Check_Range_Size:
' Check that the range specified includes at least two columns and at least
two rows
If nSourceColumns < 2 Or nSourceRows < 2 Then
If Len(Trim(cErrorText)) <> 0 Then cErrorText = cErrorText + Chr(13)
cErrorText = cErrorText + "Range must include at least two columns
and at least two rows"
Valid_Range_Selection = 1
End If
Return

Check_No_Blanks:
' Check that there are no blank column headers
lBlankErr = False
For nCols = nSourceCol To nSourceCol + nSourceColumns - 1
If IsEmpty(myData(0, nCols - nSourceCol)) And lBlankErr = False Then
If Len(Trim(cErrorText)) <> 0 Then cErrorText = cErrorText +
Chr(13)
cErrorText = cErrorText + "Column headers cannot be blank"
lBlankErr = True
Valid_Range_Selection = 1
End If
Next
Return

Check_No_Duplicates:
' Check that there are no duplicate column headers
lDuplicate = False
For nCols = nSourceCol To nSourceCol + nSourceColumns - 1
For nCols2 = nSourceCol To nSourceCol + nSourceColumns - 1
If myData(0, nCols - nSourceCol) = myData(0, nCols2 -
nSourceCol) _
And nCols <> nCols2 And lDuplicate = False Then
If Len(Trim(cErrorText)) <> 0 Then cErrorText =
cErrorText + Chr(13)
cErrorText = cErrorText + "Column headers cannot be
duplicated"
lDuplicate = True
Valid_Range_Selection = 1
End If
Next
Next
Return

End Function
 
O

OssieMac

OK Paul,

I think that it is errors in your code that are causing you the most
heartache. Like I said in a previous post, some errors cause it to lock up
rather than stop on the error.

You cannot use Selection in your code like the following:
nSourceCol = Selection.Column
nSourceColumns = Selection.Columns.Count
nSourceRows = Selection.Rows.Count

Reason is that the worksheet is not selected and it is not active. The
control on the form is the active/Selected object. Selecting a range to
populate the control does not really select the range on the worksheet.

Therefore you need to assign the range from the forms control to a range
variable and pass it to the function as a range and use that range variable
in lieu of Selection.

I can't really test your code to the nth degree because I just don't have
sufficient data. However, the following code is an example of assigning the
range to a variable and passing the range to the Function.

Private Sub SrcRef_Exit(ByVal Cancel As MSForms.ReturnBoolean)
Dim rngSrcRef As Range
Dim nRangeOK As Integer

'Assign the controls range to a variable
Set rngSrcRef = Range(SrcRef.Value)

'Pass the range variable to the function as an argument.
nRangeOK = Valid_Range_Selection(rngSrcRef)

Select Case nRangeOK
Case 0
MsgBox "Case is: " & nRangeOK & _
vbLf & "So don't allow exit"
Cancel = True 'Cancel Exit
Case 1
MsgBox "Case is: " & nRangeOK & _
vbLf & "So continue"
End Select

End Sub


'****************************************
'Note that the function should be in a standard module;
'not in the forms module.
'******************************************
Function Valid_Range_Selection(ByVal rngToTest As Range)
Dim nSourceColumns
Dim nSourceRows

'Use the range passed to the function to
'reference the columns and rows.
With rngToTest
nSourceColumns = .Columns.Count
nSourceRows = .Rows.Count
End With

'Following is for example only.
'Nothing to do with your actual validation
If nSourceColumns < 2 Or _
nSourceRows < 2 Then

Valid_Range_Selection = 0 'Invalid
Else
Valid_Range_Selection = 1 'Valid
End If

End Function

You should be able to test the function by calling it from a test sub in the
standard module. Whatever arguments need to be passed to it you should be
able to set some dummy variables in the test sub and pass them as arguments
to the function. That way the code should stop on any errors instead of
loosing its relationship to objects like the form and its controls. Then so
long as you are passing the correct data in the arguments when you call it
from the forms control event, it should work.
 
O

OssieMac

Hi again Paul,

I thought it might help you a bit if I posted some sample code that might
help you to test the Function. I have used the Application.InputBox to
emulate selecting a range similar to RefEdit control.

Putting the test code in a standard module with the Function code will
overcome the problems of loosing relationships between objects and allow you
to test. The code should stop on errors instead of Excel chucking a mental as
it can with some of the errors you are encountering.

You may need to temporarily assign values to some of your variables
sufficiently to test and remove them later.

I have not run a test on the code because I don't have any idea what values
to temporarily assign to variables.

Another point is don't use + signs when concatenating strings. A lot of the
time they will work but there are times when Excel will confuse them with
maths operations. Use the ampersand to concatenate strings and only use the +
sign for adding numeric values. it also saves confusion for others
interpreting your code. One does not know whether you are adding numeric
values or concatenating strings.

My testing indicates that valid error free functions can be called with the
RefEdit Exit event so if you can get the function working properly then it
should work with your Exit event.

Sub Test_Valid_Range_Selection()
'Run this sub to test the Function.
'Assign dummy values to variables for the
'function if necessary.
Dim rngSelect As Range
Dim nRangeOK As Integer
Dim strMsge As String

Set rngSelect = Application.InputBox _
(Prompt:="Select the required range", Type:=8)

nRangeOK = Valid_Range_Selection(rngSelect)

strMsge = "Valid_Range_Selection returned: "
Select Case nRangeOK
Case 0
MsgBox strMsge & nRangeOK
Case 1
MsgBox strMsge & nRangeOK
Case 2
MsgBox strMsge & nRangeOK
End Select

End Sub

'Validation Code:
Function Valid_Range_Selection _
(ByVal rngToTest As Range, _
Optional pMessage, Optional pPopulate)

' Valid_Range_Selection is the value returned to the calling code to
'determine the validation and selection
' 0 - Validation OK
' 1 - Validation failed - user selected to retry
' 2 - Validation failed - user selected to cancel import
Valid_Range_Selection = 0
cErrorText = ""

' If zFirst = True And nSourceColumns = 1 And nSourceRows = 1 Then
' If zFirst = True Then
' Exit Function
' End If

If IsMissing(pMessage) Then pMessage = True
If IsMissing(pPopulate) Then pPopulate = True

'********************************************
'OssieMac's change
With rngToTest
nSourceCol = .Column
nSourceColumns = .Columns.Count
nSourceRows = .Rows.Count
End With
'********************************************
GoSub Check_Range_Size

GoSub Check_No_Blanks

GoSub Check_No_Duplicates

If Valid_Range_Selection = 0 And pPopulate = True Then
Call Populate_Source_Data_View
End If

If Valid_Range_Selection <> 0 And pMessage = True Then
n = MsgBox(cErrorText, vbStop + vbOKOnly, "Table import")
Valid_Range_Selection = 1
End If
Exit Function

Check_Range_Size:
' Check that the range specified includes at least two columns and at least
two Rows
If nSourceColumns < 2 Or nSourceRows < 2 Then
If Len(Trim(cErrorText)) <> 0 Then cErrorText = cErrorText + Chr(13)
cErrorText = cErrorText + "Range must include at least two columns
and at least two rows"
Valid_Range_Selection = 1
End If
Return

Check_No_Blanks:
' Check that there are no blank column headers
lBlankErr = False
For nCols = nSourceCol To nSourceCol + nSourceColumns - 1
If IsEmpty(myData(0, nCols - nSourceCol)) And lBlankErr = False Then
If Len(Trim(cErrorText)) <> 0 Then cErrorText = cErrorText &
Chr(13)
cErrorText = cErrorText + "Column headers cannot be blank"
lBlankErr = True
Valid_Range_Selection = 1
End If
Next
Return

Check_No_Duplicates:
' Check that there are no duplicate column headers
lDuplicate = False
For nCols = nSourceCol To nSourceCol + nSourceColumns - 1
For nCols2 = nSourceCol To nSourceCol + nSourceColumns - 1
If myData(0, nCols - nSourceCol) = myData(0, nCols2 -
nSourceCol) _
And nCols <> nCols2 And lDuplicate = False Then
If Len(Trim(cErrorText)) <> 0 Then cErrorText =
cErrorText & Chr(13)
cErrorText = cErrorText & "Column headers cannot be
duplicated "
lDuplicate = True
Valid_Range_Selection = 1
End If
Next
Next
Return

End Function
 
P

Paul

So many thanks for taking your time with this one.

I can't get to try it out for a couple of days, but I'll let you know how I
get on
 
O

OssieMac

Hi Paul,

The lockups and failure to run are definitely caused by code errors.

I have researched and found what I believe is a better method of validating
a range and returning the function value. (Thanks to Chip Pearson MVP)

It uses Enum. See http://www.cpearson.com/excel/Enums.aspx for more details
about working with enums.

In the Enum declaration you can add more variables for more testing. Simply
increment the power by 1 for the value for each variable that you add. Of
course you will need more code in the function and more in the SrcRef_Exit to
concatenate the strMsge.

Basically it assigns a number created by 2 power n (2^n) where n is 0 to 4
in this case. It sums the values for each of the errors and then with the use
of a binary comparison you can identify which messages to concatenate.

Don’t know how much you know about binary but Google some information on
binary and decimal to binary if you need more information. (Worksheet
function DEC2BIN actually converts decimal number to binary but it is not
required here.)

The function code needs to be logical and terminate the function if an
invalid selection is found which will affect further testing. See the
comments in the code re this.

Try the code in a new workbook. Create a simple userform with a RefEdit
control named ‘SrcRef’ and a couple of text boxes. (Textboxes so the cursor
can be moved off the SrcRef control.)

Set up some dummy data on a worksheet with column headers in the first row
on the worksheet and a few rows of dummy data below it.

Show the worksheet and test the RefEdit field. You will need to close the
userform to make changes to the worksheet to apply errors like blank headers,
duplicate headers etc.

If you can’t successfully modify the code to perform all of the tests you
require on the selected range then let me know what other tests are required
(and which ones are not required) and I will modify it for you.

The following code is tested and works. Form must be modal. (ShowModal = True)

Copy the following code into your Forms Module.

Private Sub SrcRef_Exit _
(ByVal Cancel As MSForms.ReturnBoolean)

Dim rngSrcRef As Range
Dim strMsge As String
Dim dataErr As Long

'Assign the controls range to a variable
Set rngSrcRef = Range(SrcRef.Value)

'Pass the range variable as an argument
'for the Function.
'Function returns a long number equal to
'the sum of the errors expressed as 2 ^ n
dataErr = Valid_Range_Selection(rngSrcRef)

If dataErr = Success Then 'If dataErr is zero
Exit Sub 'No errors so exit
Else
'Start concatenating the error messages.
strMsge = "Errors:"

'Data is compared in the following If's.
'It simply tests if the bit is turned on
'for the variable value.
If dataErr And NoHeaderRow Then
strMsge = strMsge & vbLf & _
"Header Row not in selection."
End If

If dataErr And BlankHeader Then
strMsge = strMsge & vbLf & _
"Blank cell/s in Header Row."
End If

If dataErr And DuplicateHeader Then
strMsge = strMsge & vbLf & _
"Duplicate Header name/s."
End If

If dataErr And MinRowColError Then
strMsge = strMsge & vbLf & _
"Min 2 Rows and 2 Cols required."
End If

If dataErr And blankdata Then
strMsge = strMsge & vbLf & _
"Blank cells in data range."
End If

MsgBox strMsge

'Setting Cancel to True cancels the
'Exit and cursor remains in the control.
Cancel = True
End If
End Sub


Copy the following code between the asterisk lines
to the top of a standard module.
Note the comments.

'**************************************
'The section between the asterisk lines
'must be in the declarations area
'at the top of a standard module
'and before any other subs.
Public Enum DataTableError
Success = 0
NoHeaderRow = 2 ^ 0
BlankHeader = 2 ^ 1
DuplicateHeader = 2 ^ 2
MinRowColError = 2 ^ 3
blankdata = 2 ^ 4
End Enum
'*************************************

Copy the following Function code into the standard module.

Function Valid_Range_Selection _
(ByVal rngToTest As Range) As Long

Dim dataError As SelectError
Dim i As Long
Dim strMsge As String

dataError = Success 'Assume correct. (Equals zero)

With rngToTest
'Test if header row included in selection.
'Tests if 1st row of selection is 1st row
'on the worksheet.
If .Rows(1).Row <> 1 Then
dataError = dataError Or NoHeaderRow
'Header required for further testing
'so to avoid code errors, skip further
'testing if header row not in selection.
GoTo EndTest
End If

'Test for no blank column headers.
'Counts blanks in 1st row of selected range.
If WorksheetFunction.CountBlank(.Rows(1)) > 0 Then
dataError = dataError Or BlankHeader
End If

'Test for duplicate column headers.
'CountIf counts number of occurrences
'of each value in the header.
For i = 1 To .Columns.Count
If WorksheetFunction.CountIf(.Rows(1), _
.Cells(1, i)) > 1 Then
dataError = dataError Or DuplicateHeader
Exit For 'Cease testing on first duplicate
End If
Next i

'Test for at least 2 rows and 2 columns.
'Counts columns and rows in selected range.
If .Rows.Count < 2 Or .Columns.Count < 2 Then
dataError = dataError Or MinRowColError
'Min 2 rows & 2 cols required for next
'test so to avoid code errors, skip
'further testing.
GoTo EndTest
End If

'NOTE: Not sure if this test required.
'Tests for blank cells within actual data
'range below the header row.
If WorksheetFunction.CountBlank _
(.Offset(1, 0) _
.Resize(.Rows.Count - 1, _
.Columns.Count)) > 0 Then

dataError = dataError Or blankdata
End If

End With

EndTest:
Valid_Range_Selection = dataError

End Function
 

Ask a Question

Want to reply to this thread or ask your own question?

You'll need to choose a username for the site, which only take a couple of moments. After that, you can post your question and our members will help you out.

Ask a Question

Top