Link 2 in-cell data validation lists - vba

I have 2 in-cell data validation lists in Excel. Both cells contain dropdown arrows pointing to 2 columns of a table. What I would like is for when 1 cell contains a value, the other cell contains the value from the corresponding row in the table, but its own column, and vice-versa.
So if I select a value from the dropdown in cell1 it overwrites cell2, if I select a value from the dropdown in cell2 it overwrites cell1
i.e. for a table GoalTbl with columns cl and d; a cell named cl_val contains a data validation list pointing to GoalTbl[cl]. Another cell called d_val points to GoalTbl[d]
And so to get a d_val based on cl_val I use a formula like =INDEX(GoalTbl[d],MATCH(cl_val,GoalTbl[cl],0))
Similarly to get a value for cl_val based on d_val, =INDEX(GoalTbl[cl],MATCH(d_val,GoalTbl[d],0))
I can't put these formulae in their respective cells for 2 reasons:
Since each formula refers to the other cell, I would get a circular reference
If I use the drop-down arrow to select a value, it overwrites the formula in that cell
So can I get this linked functionality by changing the lists that the data validation points to - or with a VBA approach?
I suppose this is a combination of a dynamic default formula for a validation, and an overwriting mechanism for 1 cell based on the other - 2 areas I'm not sure how to tackle simultaneously with formulae.

Thanks for the pointer towards Worksheet_Change, I have a VBA approach;
Private Sub Worksheet_Change(ByVal Target As Range)
If Not Intersect(Target, [cl_val]) Is Nothing Then
With Application.WorksheetFunction
UI False
[d_val] = .Index([Goaltbl[d]], .Match([cl_val], [Goaltbl[cl]], 0))
UI True
End With
ElseIf Not Intersect(Target, [d_val]) Is Nothing Then
With Application.WorksheetFunction
UI False
[cl_val] = .Index([Goaltbl[cl]], .Match([d_val], [Goaltbl[d]], 0))
UI True
End With
End If
End Sub
Where UI is simply a Sub* to turn on/off screen updating and events (I have a Worksheet_Calculate macro elsewhere which I don't want triggered)
Still, a function approach would be nice to know about - I'm sure something can be done by changing the list input
*UI code for reference
Public Sub UI(t As Boolean)
Application.EnableEvents = t
Application.ScreenUpdating = t
End Sub

Related

Excel VBA to call a single macro by mutliple independent worksheet changes

I have used the following Worksheet Change VBA code which is applied to a single cell reference, and is used to call a macro dependent on selection from a data validation list. The event triggered by the macro applies to the row of the active cell.
Private Sub Worksheet_Change(ByVal Target As Range)
If Target.Address(True, True) = "$H$2" Then
Select Case Target
Case "Yes"
Call StandardEntry
Case Else
'Do nothing
End Select
End If
End Sub
I would now like to be able to apply this worksheet change event to be triggered by individual cells within the same column, generating the same event within the active cells row and not affecting any other rows. Identical data validation has been applied to the other cells in the column.
I would appreciate assistance in writing the appropriate code or adjusting the code above to suit.
Thanks #YowE3K!Changing
If Target.Address(True, True) = "$H$2" Then
to
If Target.Column = 8 Then
did the trick and is a really simple solution! Yeehar!

Worksheet_Change determine value not content

I am trying to detect if there are changes in a cell value, not particularly the cell contents. I have found multiple solutions to find out if a cell contents has changed, but it does not work when a cell is equal to another cell.
For example, I have cell A1 set to equal B1 and then B1 has a formula that calls in multiple other cells, so I am not able to go back to the beginning and determine whether the cell has changed from that. It needs to come directly from A1.
This is one of the examples I found on this site, but does not determine if the value of A1 has changed, just whether the contents has changed.
Private Sub Worksheet_Change(ByVal Target As Range)
If Target.Column = 1 Then
Cells(Target.Row, 3).Value = Date
End If
End Sub
The function application.volatile TRUE at the top of your sub will make your sub calculate each time any value in Excel changes. So then you need a global variable which stores the last-known value of your specified range, and any time the sub runs, start with an
If new_cell_value <> stored_global_variable then...
and close with
stored_global_variable = new_cell value'
End If
See here for further info [h/t to vzczc for the original answer and method]: Refresh Excel VBA Function Results

Excel macro select two ranges and compare

This is a question that was asked to me in an interview. I have a excel list. It is copied to another location and then by mistake a row in the new location gets deleted.
Now I need to write a macro to compare the old and new ranges and then provide the missing data as result.
I can perhaps perform the comparison part. But the problem is I don't know how to get the selected range as input in a macro.
For eg. as soon as I select a range, it should be sent as input to the macro, then the macro should wait for another selection. As soon as I select the new range, the macro should compare and find the missing lines in new range.
Regarding the selection per mouse click you could look at the link I sent in the comments of the other answer. Selection_Change is an event which gets triggered when you change the selection of a worksheet (not only mouseclick but move-by-keys as well). The target coming in is the cell which you have selected. You can pass this as a range on to a function.
Private Sub Worksheet_SelectionChange(ByVal Target As Range)
showMsg Target
End Sub
Private Function showMsg(r As Range)
MsgBox r.Address
End Function
You can just as well use another event like BeforeDoubleClick or BeforeRightClick. Check out the events of Excel and choose the one you feel fits best.
If you only want the function to be triggered for a certain range you can filter it.
If target.column <> 1 then exit function
If you don't want the event to trigger your function each time you change a selection you can choose one cell to be the switch which gets triggered by the same event.
If target.address = "$A$1" Then Call toggleSearch()
with toggleSearch being the switching function.
This is a classical diff (and a simple one at that), you shouldn't select by hand or anything. Just sort the two lists in an identical way, then run a Sub which loops over the number of rows in the source sheet comparing each row with the same row in the target sheet. The first mismatch you get is the missing line.
This example assumes both sheets are in the same workbook but you can easily adapt it
Public Sub diffThem()
Dim src as Worksheet, trg as Worksheet
Dim r as Range, i as Integer
Set src = ThisWorkbook.Sheets("Source")
Set trg = ThisWorkbook.Sheets("Destination")
Set r = src.Range("A1")
For i = 1 to ThisWorkbook.Sheets("Source").UsedRange.Rows.Count
If r.EntireRow <> trg.Range("A" & r.Row).EntireRow Then
MsgBox("The missing row is " & r.Row)
Exit Sub
End if
Set r = r.Offset(1,0)
Next i
End Sub
If EntireRow cannot be run due to different layouts or whatever then loop the columns at that point.

Excel, 2 sheets, 2 columns, same value?

I have 2 sheets sheet1 and sheet2 in an excel 2007 file.
In sheet2 I have a column that is managed by a form/macro(with a tree view control). When an element has been selected, the cell is filled with an "x", when it has been unselected, the cell is filled with "" (nothing).
In sheet1 I want to create a column equal to the sheet2 column.
So for example: if sheet2!C24 = "x" then sheet1!c24 should also be "x"
I also would like it to work both ways. If the user changes sheet1!c24 to "x", then I want sheet2!c24 to take the same value.
Problems:
- in Sheet1, I tried sheet1!c24 = sheet2!c24, but then when sheet2!c24 = "", sheet1!c24 displays 0 instead of nothing
- in Sheet2, I tried sheet2!c24 = sheet1!c24, but then the cells display the formula (='sheet1!c24') instead of the value...
So basically, what I want is that whatever change you do, in sheet1 or in sheet2, both columns in sheet1 and sheet2 are updated...
How can I achieve this?
What I think you need to do is use the Worksheet_Change events for both sheets and if a change is made in the column you are interested in, then you update the same cell in the other sheet.
Something like this would go in the worksheet code module:
Private Sub worksheet_change(ByVal target As Range)
Dim c As Range
'Test to see if the cell just changed is
'in the column we are interested in
Set c = Application.Intersect(target, Range("A:A"))
If Not c Is Nothing Then
'Copy across to other sheet
If Not beingEdited Then
beingEdited = True
Sheet1.Range(target.Address) = target.Value
beingEdited = False
End If
End If
End Sub
You'd need a beingEdited variable to be declared somewhere else with larger scope so that you could avoid the events triggering themselves and Excel getting stuck in a loop.
In the other sheet you'd basically have the same procedure, except that it would reference the first worksheet, e.g. Sheet1.Range(target.Address) = target.Value.
Obviously, you'd have to tweak this to your ranges/sheets.
You've got the right idea, but you probably need to turn off events before making the change, otherwise you'll end up in a loop
Private Sub worksheet_change(ByVal target As Range)
application.enableevents = false
sheet1.range("c24").value = sheet2.("c24").value
application.enableevents = true
end sub
Just make sure you enable events again at the end.
i did something like this where i had a summary sheet and a tests sheet. When I added a new value in tests sheet and it passed (P) a cell in summary sheet will keep increment. This is to keep a count of how many tests passed. here it is:
COUNTIF(tests!$C$5:$C$1017, "P");
hope this helps.

OnClick in Excel VBA

Is there a way to catch a click on a cell in VBA with Excel? I am not referring to the Worksheet_SelectionChange event, as that will not trigger multiple times if the cell is clicked multiple times. BeforeDoubleClick does not solve my problem either, as I do not want to require the user to double click that frequently.
My current solution does work with the SelectionChange event, but it appears to require the use of global variables and other suboptimal coding practices. It also seems prone to error.
Clearly, there is no perfect answer. However, if you want to allow the user to
select certain cells
allow them to change those cells,
and
trap each click,even repeated clicks
on the same cell,
then the easiest way seems to be to move the focus off the selected cell, so that clicking it will trigger a Select event.
One option is to move the focus as I suggested above, but this prevents cell editing. Another option is to extend the selection by one cell (left/right/up/down),because this permits editing of the original cell, but will trigger a Select event if that cell is clicked again on its own.
If you only wanted to trap selection of a single column of cells, you could insert a hidden column to the right, extend the selection to include the hidden cell to the right when the user clicked,and this gives you an editable cell which can be trapped every time it is clicked. The code is as follows
Private Sub Worksheet_SelectionChange(ByVal Target As Range)
'prevent Select event triggering again when we extend the selection below
Application.EnableEvents = False
Target.Resize(1, 2).Select
Application.EnableEvents = True
End Sub
In order to trap repeated clicks on the same cell, you need to move the focus to a different cell, so that each time you click, you are in fact moving the selection.
The code below will select the top left cell visible on the screen, when you click on any cell. Obviously, it has the flaw that it won't trap a click on the top left cell, but that can be managed (eg by selecting the top right cell if the activecell is the top left).
Private Sub Worksheet_SelectionChange(ByVal Target As Range)
'put your code here to process the selection, then..
ActiveWindow.VisibleRange.Cells(1, 1).Select
End Sub
SelectionChange is the event built into the Excel Object model for this. It should do exactly as you want, firing any time the user clicks anywhere...
I'm not sure that I understand your objections to global variables here, you would only need 1 if you use the Application.SelectionChange event. However, you wouldn't need any if you utilize the Workbook class code behind (to trap the Workbook.SelectionChange event) or the Worksheet class code behind (to trap the Worksheet.SelectionChange) event. (Unless your issue is the "global variable reset" problem in VBA, for which there is only one solution: error handling everywhere. Do not allow any unhandled errors, instead log them and/or "soft-report" an error as a message box to the user.)
You might also need to trap the Worksheet.Activate() and Worksheet.Deactivate() events (or the equivalent in the Workbook class) and/or the Workbook.Activate and Workbook.Deactivate() events so that you know when the user has switched worksheets and/or workbooks. The Window activate and deactivate events should make this approach complete. They could all call the same exact procedure, however, they all denote the same thing: the user changed the "focus", if you will.
If you don't like VBA, btw, you can do the same using VB.NET or C#.
[Edit: Dbb makes a very good point about the SelectionChange event not picking up a click when the user clicks within the currently selected cell. If you need to pick that up, then you would need to use subclassing.]
I don't think so. But you can create a shape object ( or wordart or something similiar ) hook Click event and place the object to position of the specified cell.
This has worked for me.....
Private Sub Worksheet_Change(ByVal Target As Range)
If Mid(Target.Address, 3, 1) = "$" And Mid(Target.Address, 2, 1) < "E" Then
' The logic in the if condition will filter for a specific cell or block of cells
Application.ScreenUpdating = False
'MsgBox "You just changed " & Target.Address
'all conditions are true .... DO THE FUNCTION NEEDED
Application.ScreenUpdating = True
End If
' if clicked cell is not in the range then do nothing (if condttion is not run)
End Sub
NOTE: this function in actual use recalculated a pivot table if a user added a item in a data range of A4 to D500. The there were protected and unprotected sections in the sheet so the actual check for the click is if the column is less that "E" The logic can get as complex as you want to include or exclude any number of areas
block1 = row > 3 and row < 5 and column column >"b" and < "d"
block2 = row > 7 and row < 12 and column column >"b" and < "d"
block3 = row > 10 and row < 15 and column column >"e" and < "g"
If block1 or block2 or block 3 then
do function .....
end if
I had a similar issue, and I fixed by running the macro "onTime", and by using some global variables to only run once the user has stopped clicking.
Public macroIsOnQueue As Boolean
Private Sub Worksheet_SelectionChange(ByVal Target As Range)
macroIsOnQueue = False
Application.OnTime (Now() + TimeValue("00:00:02")), "addBordersOnRow"
macroIsOnQueue = True
End sub
Sub addBordersOnRow()
If macroIsOnQueue Then
macroIsOnQueue = False
' add code here
End if
End sub
This way, whenever the user changes selection within 2 seconds, the macroIsOnQueue variable is set to false, but the last time selection is changed, macroIsOnQueue is set to true, and the macro will run.
Hope this helps,
Have fun with VBA !!
Just a follow-up to dbb's accepted answer: Rather than adding the immediate cell on the right to the selection, why not select a cell way off the working range (i.e. a dummy cell that you know the user will never need). In the following code cell ZZ1 is the dummy cell
Private Sub Worksheet_SelectionChange(ByVal Target As Range)
Application.EnableEvents = False
Union(Target, Me.Range("ZZ1")).Select
Application.EnableEvents = True
' Respond to click/selection-change here
End Sub