I have a code which running on a table in excel sheet (T_List). the code purpose is to load data from tabl to user forms controls. the user forms and controls are veriable. The code is stucking when it's on:
UserForms(FormName).Show
and the error massage is "Type Mismatch":
Public Sub Load_Data_to_Form()
Dim T_Test As Integer
Dim CurrRaw As Integer
Dim CurrValue As String
Dim FormName As String
Dim Control_Yes As String
Dim Control_No As String
T_Test = Sheets("T_list").Range("T1").Value 'Total raws in excel table
CurrRaw = Sheets("T_list").Range("N7").Value 'current raw
For i = 1 To T_Test 'running on test parameter table
CurrValue = Sheets("Test_Data").Range("D_Start").Offset(CurrRaw, i + 7).Value
FormName = Sheets("T_list").Range("T_Start").Offset(i, 6).Value
Control_Yes = Sheets("T_list").Range("T_Start").Offset(i, 4).Value
Control_No = Sheets("T_list").Range("T_Start").Offset(i, 5).Value
If CurrValue = "Pass" Then
UserForms(FormName).Show '---> **the code stuck here**
UserForms(FormName).Controls(Control_Yes) = True
UserForms(FormName).Controls(Control_No) = False
ElseIf CurrValue = "Fail" Then
UserForms(FormName).Show
UserForms(FormName).Controls(Control_Yes) = False
UserForms(FormName).Controls(Control_No) = True
End If
Next
End Sub
What i'm doing wrong? how to call the veriable userform to be opened and saving the value into its controls?
This one worked for me - for referencing MSForms.UserForm object.
but requires additional permissions.
Excel Options > Trust Centre > Macro Settings > Trust access to the
VBA Project object model
below function return the userform object matching with input name.
Public Function UserForms(FormName As String)
Dim c As Object
For Each c In ThisWorkbook.VBProject.VBComponents
If c.Type = 3 And c.Name = FormName Then
Set UserForms = c
Exit For
End If
Next
End Function
and modify your code as below :
Dim frm As Object
Set frm = UserForms(FormName)
If CurrValue = "Pass" Then
frm.Show
frm.Controls(Control_Yes) = True
frm.Controls(Control_No) = False
ElseIf CurrValue = "Fail" Then
frm.Show
frm.Controls(Control_Yes) = False
frm.Controls(Control_No) = True
End If
UserForms.Add Method - without additional functions.
Dim frm As Object
Set frm = UserForms.Add(FormName)
If CurrValue = "Pass" Then
frm.Show
frm.Controls(Control_Yes) = True
frm.Controls(Control_No) = False
ElseIf CurrValue = "Fail" Then
frm.Show
frm.Controls(Control_Yes) = False
frm.Controls(Control_No) = True
End If
Since the 'Trust access to the VBA Project object model' option is blocked due to my organisation policy, and i can't use the UserForms(FormName) function as suggested below, i solved the problem by using array which save the userforms names.
Here is my code:
Public Sub Load_Data_to_Form()
Dim T_Test As Integer
Dim CurrRaw As Integer
Dim CurrValue As String
Dim FormName As String
Dim Control_Yes As String
Dim Control_No As String
Dim FormArray As Variant 'define array which hold userforms names
FormArray = Array(Test_Procedure1, Test_Procedure2, Test_Procedure3, Test_Procedure4)
T_Test = Sheets("T_list").Range("T1").Value 'Total raws in excel table
CurrRaw = Sheets("T_list").Range("N7").Value 'current raw
For i = 1 To T_Test 'running on test parameter table
CurrValue = Sheets("Test_Data").Range("D_Start").Offset(CurrRaw, i + 7).Value
FormName = Sheets("T_list").Range("T_Start").Offset(i, 6).Value
Control_Yes = Sheets("T_list").Range("T_Start").Offset(i, 4).Value
Control_No = Sheets("T_list").Range("T_Start").Offset(i, 5).Value
For j = 0 To 3 'Running on 4 values in the FormArray
If FormName = FormArray(j).Name Then
If CurrValue = "Pass" Then
FormArray(j).Controls(Control_Yes) = True
FormArray(j).Controls(Control_No) = False
ElseIf CurrValue = "Fail" Then
FormArray(j).Controls(Control_Yes) = False
FormArray(j).Controls(Control_No) = True
End If
End If
Next
Next
Test_Procedure1.Show '(show the first form and the for rest form the user can press on button 'next' in each form..)
End Sub
Related
Look for some help on deleting any existing exceptions in the calendar before I add the new ones from my spreadsheet. I'm not sure what else they want me to write.
Sub LoadHolidaysFromExcel()
Dim objXL As Object
Dim objWB As Object
Dim objWS As Object
Set objXL = CreateObject("Excel.Application")
MyFile = objXL.GetOpenFilename
Set objWB = objXL.Workbooks.Open(MyFile)
Set objWS = objWB.Worksheets(1)
'this next line is commented out for running the code, makes it visable
'objXL.Visible = True
objWS.Range("A1").Select
LR = objXL.ActiveCell.CurrentRegion.Rows.Count
'Call deleteCalendarExceptions
For x = 1 To LR - 1
MyName = objXL.ActiveCell.Offset(x, 0).Value
MyStart = objXL.ActiveCell.Offset(x, 1).Value
MyFinish = objXL.ActiveCell.Offset(x, 2).Value
MyCalendar = objXL.ActiveCell.Offset(x, 3).Value
ActiveProject.BaseCalendars(MyCalendar).Exceptions.Add Type:=1, Start:=MyStart, Finish:=MyFinish, name:=MyName
Next x
objXL.Workbooks.Close
MsgBox "all done!"
End Sub
I've tried the following but it fails
For Each x In ActiveProject.BaseCalendars(MyCalendar).Exceptions
x.Delete
Here is the script to deleteCalendarExceptions
Sub deleteCalendarExceptions()
Dim e As Exception
Dim CalNam As String
CalNam = ActiveProject.Calendar.name
For Each e In ActiveProject.BaseCalendars(CalNam).Exceptions
e.Delete
Next e
End Sub
This works for me:
Public Sub DeleteExceptions(cal As MSProject.Calendar)
Dim e As MSProject.Exception
For Each e In cal.Exceptions
e.Delete
Next e
End Sub
help me:
I have 2 UserForms: UF123 and UF456
I want call name from Sheet6.Cells(11, 12) = UF123 or UF456 : UForm = Sheet6.Cells(11, 12).Value
I want set UserForms(UForm).ComboBox5.Value = abc
but it not run.
Dim UForm As String
UForm = Sheet6.Cells(11, 12).Value
UserForms(UForm).ComboBox5.Value = 5
Note that this cannot work as UserForms only contains userforms that are actually shown at the moment.
You can easily test this by
Debug.Print VBA.UserForms.Count
it will show 0 if no userform is shown and if you do
UF123.Show vbModeless
Debug.Print VBA.UserForms.Count
it will show 1.
Also you cannot access it by its name using UserForms("UF123") this is not supported.
So there is only a workaround:
Dim UForm As Object
Select Case Sheet6.Cells(11, 12).Value
Case "UF123"
Set UForm = New UF123
Case "UF456"
Set UForm = New UF456
Case Else
MsgBox "not found"
End Select
UForm.ComboBox5.Value = 5
UForm.Show
Set UForm = Nothing
I am now trying to achieve something like the query function in Google Sheets. Obviously in this GIF, someone has already done that. I wonder how they could do that in Excel / VBA.
My specific question is: in VBA, how to fill other cells' formulas by entering a formula in a specific cell? (replicate the function used in this GIF and not using VBA + advanced filter)
Enter a formula in cell A3
Press CTRL + SHIFT + ENTER
Receive results
This is what I got so far:
The code in a standard module:
Sub run_sql_sub(sql)
On Error Resume Next
Set cn = CreateObject("ADODB.Connection")
Set rs = CreateObject("ADODB.Recordset")
With cn
.ConnectionString = "Provider=Microsoft.ACE.OLEDB.12.0;Data Source=" & _
This Workbook.FullName _
& ";Extended Properties=""Excel 12.0 Xml;HDR=YES;IMEX=1"";"
.Open
End With
rs.Open sql, cn
Application.ScreenUpdating = False
ActiveSheet.Range("A1:XFD1048576").ClearContents
For intColIndex = 0 To rs.Fields.Count - 1
Range("A1").Offset(0, intColIndex).Value = rs.Fields(intColIndex).Name
Next
Range("A2").CopyFromRecordset rs
Application.ScreenUpdating = True
rs.Close: cn.Close: Set rs = Nothing: Set cn = Nothing
End Sub
And this code is in activesheet's module:
Private Sub Worksheet_Change(ByVal Target As Range)
Dim KeyCells As Range
Set KeyCells = ActiveSheet.Range("A1")
If Not Application.Intersect(KeyCells, Range(Target.Address)) _
Is Nothing Then
If InStr(KeyCells.Value2, "mi_sql") > 0 Then
sql = Right(KeyCells.Value2, Len(KeyCells.Value2) - Len("mi_sql "))
run_sql_sub sql
End If
End If
End Sub
Update 08.04.2019: found a solution
' Code in standard Module
Public collectCal As Collection
Private ccal As CallerCal
Sub subResizeKQ(caller As CallerInfo)
On Error Resume Next
Application.EnableEvents = False
If caller.Id <> "" Then
Application.Range(caller.Id).ClearContents
Application.Range(caller.Id).Resize(caller.rows, caller.cols).FormulaArray = caller.FomulaText
End If
Application.EnableEvents = True
End Sub
Function ResizeKQ(value As Variant) As Variant
If ccal Is Nothing Then Set ccal = New CallerCal
If collectCal Is Nothing Then Set collectCal = New Collection
Dim caller As New CallerInfo
Dim rows As Long, cols As Long
Dim arr As Variant
arr = value
rows = UBound(arr, 1) - LBound(arr, 1) + 1
cols = UBound(arr, 2) - LBound(arr, 2) + 1
Dim rgcaller As Range
Set rgcaller = Application.caller
caller.Id = rgcaller.Address(True, True, xlA1, True, True)
caller.rows = rgcaller.rows.Count
caller.cols = rgcaller.Columns.Count
caller.FomulaText = rgcaller.Resize(1, 1).Formula
If caller.rows <> rows Or caller.cols <> cols Then
caller.rows = rows
caller.cols = cols
collectCal.Add caller, caller.Id
End If
ResizeKQ = arr
End Function
Function fRandArray(numRow As Long, numCol As Long) As Variant
Application.Volatile True
ReDim arr(1 To numRow, 1 To numCol)
For i = 1 To numRow
For j = 1 To numCol
arr(i, j) = Rnd
Next
Next
fRandArray = ResizeKQ(arr)
End Function
'--------------------------------------------------------------------------
' code in Class Module name CallerCal
Private WithEvents AppEx As Application
Private Sub AppEx_SheetCalculate(ByVal Sh As Object)
Dim caller As CallerInfo
If collectCal Is Nothing Then Exit Sub
For Each caller In collectCal
subResizeKQ caller
collectCal.Remove caller.Id
Set caller = Nothing
Next
Set collectCal = Nothing
End Sub
Private Sub Class_Initialize()
Set AppEx = Application
End Sub
Private Sub Class_Terminate()
Set AppEx = Nothing
End Sub
'--------------------------------------------------------------------------
' code in Class Module name CallerInfo
Public rows As Long
Public cols As Long
Public Id As String
Public FomulaText As String
To test it, go to Excel Sheet, enter the following test formula in A1:
=fRandArray(10,10)
P.S: If anyone is using Excel 365 Insider Program, Microsoft has published this kind of formula called Dynamic Array Function:
https://support.office.com/en-ie/article/dynamic-arrays-and-spilled-array-behavior-205c6b06-03ba-4151-89a1-87a7eb36e531
I agree with the other comments - MS doesn't seem to provide a way to do this natively, and any way of doing it directly would probably involve some Excel-breaking memory manipulation.
However...
I suggest taking your method one step further and generalizing it
Copy and paste this class into a text file, then import it into VBA (which allows Attribute VB_PreDeclaredID = True and Attribute Item.VB_UserMemId = 0):
RangeEdit
VERSION 1.0 CLASS
BEGIN
MultiUse = -1 'True
END
Attribute VB_Name = "RangeEdit"
Attribute VB_GlobalNameSpace = False
Attribute VB_Creatable = False
Attribute VB_PredeclaredId = True
Attribute VB_Exposed = False
Private colRanges As Collection
Private colValues As Collection
Private Sub Class_Initialize()
Set colRanges = New Collection
Set colValues = New Collection
End Sub
Public Property Let Item(rng_or_address As Variant, value As Variant)
Attribute Item.VB_UserMemId = 0
colRanges.Add rng_or_address
colValues.Add value
End Property
Public Sub flush(sh As Worksheet)
Application.EnableEvents = False
While colRanges.Count > 0
If TypeName(colRanges(1)) = "Range" Then
colRanges(1).value = colValues(1)
ElseIf TypeName(colRanges(1)) = "String" Then
sh.Range(colRanges(1)).value = colValues(1)
End If
colRanges.Remove 1
colValues.Remove 1
Wend
Application.EnableEvents = True
End Sub
Make your Workbook_SheetChange method the following:
Private Sub Workbook_SheetChange(ByVal sh As Object, ByVal Target As Range)
RangeEdit.flush sh
End Sub
Now you can create a UDF that modifies other cells. The way it works is it queues up all the modifications you make and only runs them after the cell loses focus. The syntax allows you to treat it almost like the regular Range function. You can run it either with an address string or with an actual range (though you might want to add an error if it's not either one of those).
Here is a quick example UDF that can be run from an Excel cell formula:
Public Function MyUDF()
RangeEdit("A1") = 4
RangeEdit("B1") = 6
RangeEdit("C4") = "Hello everyone!"
Dim r As Range
Set r = Range("B12")
RangeEdit(r) = "This is a test of using a range variable"
End Function
For your specific case, I would replace
For intColIndex = 0 To rs.Fields.Count - 1
Range("A1").Offset(0, intColIndex).Value = rs.Fields(intColIndex).Name
Next
with
For intColIndex = 0 To rs.Fields.Count - 1
RangeEdit(Range("A1").Offset(0, intColIndex)) = rs.Fields(intColIndex).Name
Next
And to copy the recordset I would define the following function (it assumes that the recordset cursor is set to the first record. if you Move it previously you might want to have rs.MoveFirst in there):
Public Sub FormulaSafeRecordsetCopy(rs As ADODB.Recordset, rng As Range)
Dim intColIndex As Long
Dim intRowIndex As Long
While Not rs.EOF
For intColIndex = 0 To rs.Fields.Count - 1
RangeEdit(rng.Offset(intRowIndex, intColIndex)) = rs.Fields(intColIndex).value
Next
rs.MoveNext
intRowIndex = intRowIndex + 1
Wend
End Sub
My app below checks a workbook which has a list of items sold for a particular month identified with a serial number. There is also a comment column next to the item.
Each month when I run the app it tells me if the same item was sold and the comments next to the item.
"Item found in sheet labeled august 2014"
"Comments for that item"
if I run the app again on the workbook when it gets an additional sheet added, it's going to add the "items found..." Again.
I have the results starting from column 20 and beyond, I only need to delete the duplicates in those columns.
Option Explicit On
Option Infer Off
Imports System.Net.Mail
Imports System.IO
Imports Microsoft.Office.Interop
Imports Microsoft.Office.Interop.Excel
Imports System.Runtime.InteropServices
Imports Excel = Microsoft.Office.Interop.Excel
Imports System.Text.RegularExpressions
Public Class Form1
Dim fileName As String = ""
Private Sub Form1_Load(sender As Object, e As EventArgs) Handles MyBase.Load
End Sub
Private Function ColumnIndexToColumnLetter(colIndex As Integer) As String
Dim div As Integer = colIndex
Dim colLetter As String = String.Empty
Dim modnum As Integer = 0
While div > 0
modnum = (div - 1) Mod 26
colLetter = Chr(65 + modnum) & colLetter
div = CInt((div - modnum) \ 26)
End While
Return colLetter
End Function
Private Sub Button1_Click(sender As Object, e As EventArgs) Handles Button1.Click
Button1.Enabled = False
Button1.Text = "Patience"
Button1.Refresh()
System.Windows.Forms.Application.DoEvents()
Dim app As New Excel.Application
app.Visible = False
Dim wbBase As Excel.Workbook = app.Workbooks.Open(TextBox1.Text)
' * create style *
'
Dim xlStyles As Excel.Styles = wbBase.Styles
Dim xlStyle As Excel.Style = Nothing
Dim isstyleexists As Boolean = False
'
' * check if this style exist *
'
For Each xlStyle In xlStyles
If xlStyle.Name = "NewStyle" Then
isstyleexists = True
Exit For
End If
Next
'
' * if this does not exist so add new one *
' ' get Range "A1"
If (Not isstyleexists) Then
xlStyles.Add("NewStyle")
xlStyle = xlStyles.Item("NewStyle")
End If
Dim snName As String
Dim snName2 As String
Dim cmt2 As String
For Each basesheet As Excel.Worksheet In wbBase.Sheets
Dim iiii As Integer = basesheet.Cells(1, basesheet.Columns.Count).End(Excel.XlDirection.xlToLeft).Column + 1
Dim iii As Integer = basesheet.Cells(1, basesheet.Columns.Count).End(Excel.XlDirection.xlToLeft).Column + 1
Dim iv As Integer = iii + 1
For i As Integer = 1 To 20
If Not basesheet.Cells(1, i).Value Is Nothing AndAlso basesheet.Cells(1, i).Value.Contains("Serial Number") Then
snName = ColumnIndexToColumnLetter(i)
Exit For
End If
Next
If Not snName Is Nothing Then
For Each checksheet As Excel.Worksheet In wbBase.Sheets
If basesheet.Name <> checksheet.Name Then
For i As Integer = 1 To 20
If Not checksheet.Cells(1, i).Value Is Nothing AndAlso checksheet.Cells(1, i).Value.Contains("Serial Number") Then
snName2 = ColumnIndexToColumnLetter(i)
Exit For
End If
Next
For i As Integer = 1 To 20
If Not checksheet.Cells(1, i).Value Is Nothing AndAlso checksheet.Cells(1, i).Value.Contains("Comments") Then
cmt2 = ColumnIndexToColumnLetter(i)
Exit For
End If
Next
If Not snName2 Is Nothing Then
Dim baseobj As Object = basesheet.Range(snName & "2:" & snName & basesheet.Range(snName & basesheet.Rows.Count).End(Excel.XlDirection.xlUp).Row).Value
Dim checkobj As Object = checksheet.Range(snName2 & "2:" & snName2 & checksheet.Range(snName2 & checksheet.Rows.Count).End(Excel.XlDirection.xlUp).Row).Value
Dim cmtobj As Object = checksheet.Range(cmt2 & "2:" & cmt2 & checksheet.Range(snName2 & checksheet.Rows.Count).End(Excel.XlDirection.xlUp).Row).Value
Dim basetmp(DirectCast(baseobj, Object(,)).Length, 1) As Object
Dim v As Integer = 0
Dim bool As Boolean = False
For i As Integer = 1 To DirectCast(baseobj, Object(,)).Length
For ii As Integer = 1 To DirectCast(checkobj, Object(,)).Length
If Not baseobj(i, 1) Is Nothing AndAlso Not checkobj(ii, 1) Is Nothing AndAlso Trim(baseobj(i, 1).ToString) = Trim(checkobj(ii, 1).ToString) Then
bool = True
basetmp(i, 0) = "Serial # Exists in " & checksheet.Name
basetmp(i, 1) = cmtobj(ii, 1)
End If
Next
v += 1
Next
If bool Then
basesheet.Range(basesheet.Cells(1, iii), basesheet.Cells(v, iv)).Style = "NewStyle"
basesheet.Range(basesheet.Cells(1, iii), basesheet.Cells(v, iv)).Borders.Weight = Excel.XlBorderWeight.xlThin
basesheet.Range(basesheet.Cells(1, iii), basesheet.Cells(v, iv)).Borders.LineStyle = Excel.XlLineStyle.xlContinuous
basesheet.Range(basesheet.Cells(1, iii), basesheet.Cells(v, iv)).Value = basetmp
basesheet.Cells(1, iii).value = "Results Found"
basesheet.Cells(1, iii).Font.Color = System.Drawing.ColorTranslator.ToOle(System.Drawing.Color.Red)
basesheet.Cells(1, iii).HorizontalAlignment = Excel.Constants.xlCenter
basesheet.Cells(1, iii).Font.Bold = True
basesheet.Columns.AutoFit()
iii += 2
iv += 2
End If
End If
End If
Next
End If
Next
wbBase.Save()
wbBase.Close()
app.Quit()
MessageBox.Show("Done", "Three in Thirty", MessageBoxButtons.OK)
Button1.Text = "Start"
Button1.Enabled = True
End Sub
It looks like you are reprocessing the previously processed sheets each month.
It might be easier to avoid reprocessing old sheets, than to avoid duplicating entries from reprocessing old sheets.
Instead of using nested loops of worksheets, I would probably try a pattern like:
* find basesheet
* find latest checksheet
* process the checksheet for items sold
If users might accidentally add things to old sheets, then I would consider locking old sheets when you do the processing to help ensure data integrity.
I have a form in Excel macro. This form will capture the values inserted in textboxes, listbox and store in Sheet2.
There are 2 buttons in the form applet named "Next" and "Previous". These button will be used for navigating between the saved records. I am able to navigate between records and the values display fine in textboxes. However, I am not sure how can I display the Values from listboxes. My list box is a multiselect list box.
I have provided snippet of my code on how the records are saved in sheet2 and how the navigation happens when clicked on Next button.
Private Sub Save_Click()
Dim ctl As Control
Dim S As String
Dim i As Integer
RowCount = Worksheets("Sheet2").Range("A1").CurrentRegion.Rows.Count
With Worksheets("Sheet2").Range("A1")
.Offset(RowCount, 0).Value = Me.Name1.Value ' capture value from list box
'below code is for capturing value from multiselect listbox
With AOI
For i = 0 To .ListCount - 1
If .Selected(i) = True Then S = S & ", " & .List(i)
Next i
Range("A1").Offset(RowCount, 10).Value = S
End With
End Sub
Below code is for navigating between saved records..
Private Sub Next1_Click()
strCurrentSetofRows = Worksheets("Sheet2").Range("A1").CurrentRegion.Rows.Count
i = i + 1: j = 0
If i > (strCurrentSetofRows - 1) Then
MsgBox "No More Records"
Exit Sub
End If
Set sRange = Worksheets("Sheet2").Range("A1")
Name1.Text = sRange.Offset(i, j).Value: j = j + 1
End Sub
Any thoughts on how can I display saved values of AOI (my field).
Since you are storing the values using , as a separator, you can use the same to split the values and upload it to the ListBox. BTW, I hope you are generating the ListBox with the complete list in the UserForm's Initialize event?
Here is a very basic example. Please amend it to suit your needs.
Let's say Cell A1 has Blah1,Blah2,Blah6. Then try this code
Option Explicit
Dim i As Long, j As Long
Private Sub UserForm_Initialize()
ListBox1.MultiSelect = fmMultiSelectMulti
For i = 1 To 10
ListBox1.AddItem "Blah" & i
Next
End Sub
Private Sub CommandButton1_Click()
Dim ArValues As Variant
Dim sValue As String
Dim multivalues As Boolean
If InStr(1, Range("A1").Value, ",") Then
ArValues = Split(Range("A1").Value, ",")
multivalues = True
Else
sValue = Range("A1").Value
multivalues = False
End If
If multivalues = True Then
For i = 0 To UBound(ArValues)
For j = 0 To ListBox1.ListCount - 1
If ListBox1.List(j) = ArValues(i) Then
ListBox1.Selected(j) = True
Exit For
End If
Next j
Next i
Else
For j = 0 To ListBox1.ListCount - 1
If ListBox1.List(j) = sValue Then
ListBox1.Selected(j) = True
Exit For
End If
Next j
End If
End Sub
Screenshot