Mutually excluding checkboxes in Word table - vba

I created a Word Document (Word 2016) that, by clicking a button, generates a Table with predefined text and formatting.
Column1
Column2
Column3
QUESTION (Y/N)
YES_Checkbox
No_Checkbox
Row number 8 (shown above) contains a YES/NO question; to manage that, I inserted two Checkboxes (one in Column2, the other in Column3) using the following syntax:
For ii = 2 To 3
Set rng = mytable.Cell(8, ii).Range
rng.Select
With Selection
.Collapse Direction:=wdCollapseStart
.Range.ContentControls.Add wdContentControlCheckBox
.MoveRight Unit:=wdCharacter, Count:=2
End With
Next ii
My idea is to have mutually exclusive checkboxes: that means, if User selects "YES" the "NO" is automatically deselected (and vice-versa). To do so, I created a Sub within the main button script. It checks for the checkbox value and then tries to 'tick it' (on un-tick it) using the following code (not yet completed):
Sub Check_YES_Not(tblNew)
Dim thisCell As Range
check1 = mytable.Cell(8, 2).Range.ContentControls(1).Checked
check2 = mytable.Cell(8, 3).Range.ContentControls(1).Checked
'If condition to be added
check1 = True 'This is how I am trying to modify the checkbox, but it is not working
'ElseIf add other conditions...
'Msgbox "to be completed..."
'End If
End Sub
Code runs and creates the table correctly, but the checkbox scripts is not behaving as I wish. Could someone please tell me what I am doing wrong and how I could make it work? Thank you very much.
UPDATE
This is how I solved it. There are still minor concerns (e.g. if I click "Yes", I need to select another cell before clicking "No", otherwise the control is not able to work correctly). Apart from this, many thanks to #macropod and Timothy for their kind support.
In order to allow the User to create additional table, I created a Public counter that is increased at each added table. It is not an elegant solution, but it works for now. The initial table has a different tag (I did not include it here).
Private Sub Document_ContentControlOnExit(ByVal oCC As ContentControl, Cancel As Boolean)
Dim collCCs As ContentControls
Dim oCCTarget As ContentControl
Dim ii As Integer
For ii = 1 To 500 'Additional tables
If Not IsEmpty(ActiveDocument.SelectContentControlsByTag("NO" + Str(ii))) Then
Select Case oCC.Tag
Case "YES" + Str(ii)
Set collCCs = ActiveDocument.SelectContentControlsByTag("NO" + Str(ii))
For Each oCCTarget In collCCs
If oCCTarget.Checked = True Then
oCCTarget.Checked = False
Exit For
End If
Next
Case "NO" + Str(ii)
Set collCCs = ActiveDocument.SelectContentControlsByTag("YES" + Str(ii))
For Each oCCTarget In collCCs
If oCCTarget.Checked = True Then
oCCTarget.Checked = False
Exit For
End If
Next
End Select
End If
Next ii

Your code isn't working as you are only setting the value of a variable, not the checked status of the checkbox. The simplest way of achieving your desired result is to set the checked status of one checkbox to the opposite of the other, like this:
mytable.Cell(8, 2).Range.ContentControls(1).Checked = Not mytable.Cell(8, 3).Range.ContentControls(1).Checked
Also your code to insert the content controls can be simplified to:
For ii = 2 To 3
mytable.Cell(8, ii).Range.ContentControls.Add wdContentControlCheckBox
Next ii
There is rarely any need to select anything when working with Word.
Finally, you appear not to be forcing variable declaration. This can lead to avoidable errors in your code. Just add Option Explicit at the top of every code module. This will prevent your code from compiling when you have undeclared variables. To add this automatically to new modules open the VBE and go to Tools | Options. In the Options dialog ensure that "Require Variable Declaration" is checked.

Related

VBA MS Project. How to move entries of lookuptable programmatically?

I have an issue. I'm working on VBA macros in MS Project 2013, that can automatically fill and change lookup table, which linked with local custom field in project professional. I have these code sections on VBA for:
-adding entries
Set objStateEntry = objOutlineCode.LookupTable.AddChild(entryName)
-changing descrption of entries
objStateEntry.Description = "some description"
-changing level of entires
objStateEntry.level = entryLevel
But I can't find how to programmatically move entries up/down in lookup table. In other words I need to use marked in the screenshot buttons programmatically. Please help me. Thank you!
Try something like this:
Private Sub SpinButton1_SpinDown()
On Error Resume Next
If ListBox1.ListIndex = ListBox1.ListCount - 1 Then Exit Sub
With Me.ListBox1
.ListIndex = .ListIndex + 1
End With
End Sub
Private Sub SpinButton1_SpinUp()
On Error Resume Next
If ListBox1.ListIndex = 0 Then Exit Sub
With Me.ListBox1
.ListIndex = .ListIndex - 1
End With
End Sub
goodluck
I tried recording a macro while using the spin button you highlighted, but this indicates that VBA can't control the spin button...
So it seems the only way to change the index of a lookup table entry by VBA is to first delete the entry then add it back, for example moving entry at index=4 up to index=2 (after saving the name and description of entry index=4):
Dim lteName As String
Dim lteDesc As String
lteName = Application.CustomFieldValueListGetItem(pjCustomResourceOutlineCode2, pjValueListValue, 4)
lteDesc = Application.CustomFieldValueListGetItem(pjCustomResourceOutlineCode2, pjValueListDescription, 4)
Application.CustomFieldValueListDelete FieldID:=pjCustomResourceOutlineCode2, Index:=4
Application.CustomFieldValueListAdd FieldID:=pjCustomResourceOutlineCode2, Value:=lteName, Description:=lteDesc, Index:=2
Two caveats:
1: It appears you can't do the above in the same macro that you are adding the lookup table entries. It only works in another macro run after adding the entries.
2: At least in my version of MS Project, the index number is flaky (it should be consecutive but sometimes index numbers repeat or there are gaps but then it corrects itself!), no doubt due to deleting and adding entries like I am suggesting. The code won't work if the index numbers that VBA is looking for don't match what is displayed in the lookuptable window. Oh dear... wish MSProject was perfect!
Noted same behaviour on another SO thread here.

Is it possible set my Excel Userform Combo box to begin narrowing the fields while data is entered?

VBA Newbie here.
I have searched high and low for this answer, and even come across other questions very similar to mine, but cannot get an answer. So I am hoping that this is my lucky day.
I have a Userform in excel that has Four combo boxes. Each combo box has a drop down with several choices. In two of these boxes, there are many business names and a lot of these names are similar. I was wanting to have the feature where are the data was being typed into the box, it would begin to narrow the options. EXAMPLE: if I type "heating and air" it begins to only show items in the list that include that word or phrase.
Is this a properties change in the box, or a code written, or something else?
Please help- I am stumped and no one seems to have the answer.
Very grateful-
Excel Newbie
Yes this is very possible. All you have to do is create a sub that populates that combo box, set it up so that when adding it checks the value of the box for example if it was the basic typing example. basic format. This assumes the possible values are stored in an array. This would add any item that has the string entered in it. (in any position)
For I = 0 to Number of Values
If instr(Value(I), ComboBox.Text) > 0 then
add item
endif
next
I played around a bit and came up with something to get you started. This basically functions as an "auto search". It is not autocomplete because it will search entire terms, not just terms which begin with whatever you've typed in. Basically I assume you have a range of cells (in this example cells A2:A121) that have the date for your drop down in it.
Setup
Add a new generic module (I named mine GlobalVars and add the following code:
Option Explicit
Public IgnoreChange As Boolean
Public RangeOfData As Variant
The Code
Open the code to your UserForm.
Add the following code:
Private Sub UserForm_Initialize()
RangeOfData = Application.WorksheetFunction.Transpose(Sheet1.Range("A2:A121").Value)
IgnoreChange = False
End Sub
Be sure to update A2:A121 and Sheet1 (I am using code name, but Worksheets("Sheet1") would work just as well) to point to the data which contains your combobox choices.
Now, the meat of the job is handled in the Combobox_Change event
Private Sub ComboBox1_Change()
If Me.ComboBox1.Text = vbNullString Then
Me.ComboBox1.Clear
SendKeys ("{Enter}")
End If
If Me.ComboBox1.TextLength > 2 Then
Dim i As Long, j As Long
If IgnoreChange = False Then
Me.ComboBox1.Clear
SendKeys ("{Enter}")
DoEvents 'Bug with NumLock
For i = LBound(RangeOfData) To UBound(RangeOfData)
If UCase(RangeOfData(i)) Like "*" & UCase(Me.ComboBox1.Text) & "*" Then
Me.ComboBox1.AddItem RangeOfData(i)
End If
Next i
Me.ComboBox1.DropDown
End If
End If
IgnoreChange = False
End Sub
Be sure to change ComboBox1 to the name of your combobox control.
Basically, what this does is it handles user input when it reaches two characters or longer. The code will search through your input data range and then return results that match the string as the user is entering it. The results is like a suggestions box.
I should note that with this method, the combobox is NOT pre-populated with data, so users must begin typing something into the combobox.
Additionally, I added the following code to handle the backspace key:
Private Sub ComboBox1_KeyDown(ByVal KeyCode As MSForms.ReturnInteger, ByVal Shift As Integer)
If KeyCode = 8 Then
IgnoreChange = True
End If
End Sub
Again be sure to change ComboBox1 as appropriate.
For my example, I loaded all 120 crayola crayon colors into a spreadsheet (that is what is in Sheet1 from A2:A121).
Here is example output for when I start typing, first I input blu:
As you can see I am getting all values that contain blu, including those that don't start with blu such as Cadet Blue or Midnight blue.
As another example, I will search for flower
So as you can see, instead of being a combobox with 120 static options, it is updated based on what the user types and tied to a list of values in your cells.
I did notice that SendKeys sometimes toggled my NumLock, this is a known issue. The point of that line is to collapse the drop down after the user deletes all of the text or continues texting so as to "refresh" the auto-generated list.

VB, excel macro pause and resume working if possible

I cannot figure out the best way to do the following problem. Basically my macro (excel, VB) is checking several (100+) worksheets for correct values, if wrong value is found, I want it to stop, give me a warning, then I could have a look into the reason why the value is wrong, correct it manually and then I want to resume the macro, or remember the last value checked so if I return, it remembers where to continue (resume).
My current problem is that it finds the wrong value, then I can either make it stop so I check the problem, or it goes through all the sheets and then I have to remember which sheets had the wrong value.
What I thought of is make a list where the name of sheet is added every time a wrong value is found. The problem is that usually there is more than 1 wrong value in the same sheet if there is a wrong value at all and this added the same sheet name several times to the list. Another problem with that is that I cannot correct the values straight away.
I'm very inexperienced with programming and so would appreciate your idea on how to best approach this problem (I don't want to spend a long time on coding something which wouldn't be efficient for such a "simple" problem).
When the error is found (I'm assuming you've already been able to identify this), you can use the Application.InputBox function to prompt you for a new value.
For example, if rng is a Range variable that represents the cell being checked, and you have some logic to determine where the error happens, then you can just do:
rng.Value = Application.InputBox("Please update the value in " & rng.Address, "Error!", rng.Value)
The inputbox function effectively halts execution of the procedure, while waiting for input from the user.
If InputBox isn't robust enough, then you can create a custom UserForm to do the same sort of thing. But for modifying single range values, one at a time, the InputBox is probably the easiest to implement.
I believe you can handle this task by using one or two static local variables in your macro. A variable declared with "static" rather than "dim" will remember its value from the last time that procedure was run. This can hold where you left off so you can resume from there.
One thing that could be a problem with this solution would be if the macro gets recompiled. That would probably cause VBA to clear the value that the static variable was holding. Just doing a data edit in Excel should not cause a recompile, but you will want to watch for this case, just to make sure it doesn't come up. It almost certainly will if you edit any code between executions.
Create a public variable that stores the cell address of the last checked cell and use a conditional statement to see if it's "mid-macro" for want of a better phrase. here is a very crude example...
Public lastCellChecked As String
Sub Check_Someting()
Dim cell As Excel.Range
Dim WS As Excel.Worksheet
If Not lastCellChecked = vbNullString Then Set cell = Evaluate(lastCellChecked)
'// Rest of code...
'// Some loop here I'm assuming...
lastCellChecked = "'" & WS.Name & "'!" & cell.Address
If cell.Value > 10 Then Exit Sub '// Lets assume this is classed as an error
'// Rest of loop here...
lastCellChecked = vbNullString
End Sub
The best way to do this is to create a userform and as mentioned by prior users create a public variable. When the program finds an error store the cell and initiate the userform. Your code will stop on the userform. When you're done checking the problem have a button on the userform that you can click to continue checking. Your loop can be something like the below.
public y as integer
sub temp1 ()
rw1= range("a500000").end(xlup).row 'any method to create a range will do
if y = null then y=1
for x = y to rw1
cells(x,1).select
'check for the problem your looking for
if errorX=true then
userform1.show
y = activecell.row
exit sub
end if
next x
end sub
What about inserting a button (on the sheet or in a menubar) for stopping?
Insert the code below:
'This at the top of the module
Public mStop As Boolean
'This in the module
Sub MyBreak()
mStop = True
End Sub
'This is your macro
Sub YourMacro()
'This at the top of your code
mStop = False
'Your code
'...
'This code where you want to break
DoEvents '<<<< This makes possible the stop
If mStop Then
mCont = MsgBox("Do you want to continue?", vbYesNo)
If mCont = vbNo Then
Exit Sub
Else
mStop = False
End If
End If
'Your code
'...
End Sub
Now you need to create a button and link it to the macro called "MyBreak".

VBA - error handling for deleted checkboxes

I am working in MS Word 2013 and attempting to programatically delete rows in a table when Checkbox1 through CheckboxN is unchecked through use of a command button. The following abbreviated code works once (and can delete one or more rows) but breaks when run a second since the code still makes references to checkboxes that have been deleted. I have tried basic error handling but have been unsuccessful. Can anyone provide assistance so the code can be run multiple times and delete subsequent rows/checkboxes? If this is not the right forum to post this request, please let me know. Thank you!
Sub CommandButton1_Click()
If CheckBox1.Value = False Then
Selection.GoTo What:=wdGoToBookmark, Name:="Row6"
Selection.Rows.Delete
End If
If CheckBoxN.Value = False Then
Selection.GoTo What:=wdGoToBookmark, Name:="Row6"
Selection.Rows.Delete
End If
End sub
So, I'd probably take a different approach here. Rather than relying on named checkboxes, cycle backwards through all relevant checkboxes and determine the row to delete. I'm assuming that the checkboxes are in a cell within the row to be deleted. It's important to go backwards because, as you've noted, you'll be removing items as you go.
Sub checkChecks()
Dim objTable As Table
Dim objCC As ContentControl
Dim i As Long
Set objTable = ActiveDocument.Tables(1)
For i = objTable.Range.ContentControls.Count To 1 Step -1
Set objCC = objTable.Range.ContentControls(i)
If objCC.Type = wdContentControlCheckBox Then
If objCC.Checked = False Then objCC.Range.Rows(1).Delete
End If
Next i
End Sub
Obviously this only works if you can isolate the table in question, but that shouldn't be too hard.

Excel macro to create a command button and assign macro to the button

I am trying to build a main page for my costing model. On this page I have created a drop down list using a combo box and then I want to assign a macro that creates a list of different buttons/command buttons once an option is selected from the list. Then I want to build another macro that is assigned to those buttons which then take the user to another tab/sheet within the same workbook depending on the option they selected from the drop down.
Can someone please give me an idea as to what code I should be using, first to create a command button that refers to the selected option from the drop down and then assign a simple macro to that button which then takes me to the specified tab/sheet?
So far, I have got the following:
Option Explicit
Sub Select_Change()
With ThisWorkbook.Sheets("Main Page").Shapes("Select").ControlFormat
Select Case .List(.Value)
Case "Vehicle1": All_States1
Case "Vehicle2": All_States2
Case "Vehicle3": All_States3
Case "Vehicle4": All_States4
Case "Vehicle5": All_States5
Case "Vehicle6": All_States6
Case "Vehicle7": All_States7
End Select
End With
End Sub
I then tried to use the name All_States1 to create various buttons but it's not working properly, as all selected options are showing the same button and the button won't go away either. Also, I can't seem to assign a macro to the created button.
This is just an example of :
creating a Button
assigning a macro to it
.
Sub button_maker()
Dim r As Range
Set r = Selection
ActiveSheet.Buttons.Add(94.5, 75.75, 51, 27.75).Select
With Selection
.OnAction = "mooney"
.Characters.Text = "Bump"
End With
r.Select
End Sub
Sub mooney()
Range("A1").Value = Range("A1").Value + 3
End Sub
If I understand the problem correctly, you want to have a dropdown (combo box) on your sheet, and when a button is clicked you want to run a macro based on the selection. The following does that - see if it helps you.
First - create a combobox, and a range for the inputs (names in the combobox) and output (value selected). For example, you could call the input selectionIn and the result selectionOut.
Exact steps:
Wrote values of combobox selections in E1:E4 . Selected the four cells, then typed selectionIn in the name box (to the left of the formula bar). That creates a named range (there are other ways to create named ranges, but this is my preferred method).
Called cell F1 selectionOut
Created a combobox, and referenced these two ranges for its input and output:
Created a button, gave it the label "Go" and linked it to the action runIt.
Finally, I created the following code in a workbook module:
Sub runIt()
Dim whatToDo, makeName As Boolean
' look up the name of the combo based on the value:
whatToDo = Range("selectionIn").Cells([selectionOut].Value, 1)
MsgBox "have to do '" & whatToDo & "'"
makeName = False
Select Case whatToDo
Case "one"
' example of putting the code you need right in the select:
MsgBox "doing the first thing"
Case "two"
' example of calling a specific routine:
Call caseTwo
Case "three"
Application.Run "case" & whatToDo ' making the name of the function on the fly
Case "four"
makeName = True
End Select
If makeName Then
Dim nameToRun
nameToRun = "case" & whatToDo
Application.Run nameToRun
End If
End Sub
Sub caseTwo()
MsgBox "called the code for case two"
End Sub
Sub caseThree()
MsgBox "doing case three here"
End Sub
Sub caseFour()
MsgBox "even four can be done"
End Sub
This shows a few different ways to handle different cases depending on what was selected. Of course you can have a macro run every time the combobox selection is changed - but it sounds from your description like that is not what you want.
Let me know how you get on with this code example - I tried to keep it simple but show some options at the same time.
One alternative (which might be simpler) would be to have an array with the names of the functions that you want to call:
Sub otherMethod()
Dim functionList()
functionList = Array("caseOne", "caseTwo", "caseThree", "caseFour")
Application.Run functionList([selectionOut].Value - 1)
End Sub
That's certainly the most compact way I can think of to do this... you need the offset of -1 because the array index is base 0 (by default anyway) and the combobox returns 1 for the first selection. You could make your code more robust by writing
functionIndex = [selectionOut].Value + LBound(functionList) - 1
Application.Run functionList(functionIndex)
This ensures that if you change the base index of the functionList array to another value, it will all still work correctly.