I have the following situation: on one cell, I have a Data - validation - list which should restrict the user to selecting only items from that list.
On the same cell I have on change triggers in VBA, so when the cell value is changed it also records the change into a Changelog sheet (it uses undo to get the previous value, and undo again to redo the change to the new value)
The problem is, right now, the Data - validation - list is being totally ignored, so the users can put what ever they want in that specific cell, even though I have "Show error" checked.
Is there a way to enforce the list validation, so the users can only select items from the list and not enter whatever they want? Or to trigger the on cell change event after the validation?
Maybe someone can clarify the order in which these things happen.
I recently worked on range.Validation.type=xlValidateList and wanted to prevent paste and cut and disallow typing wrong characters into the cell containing the dropdown list(and making it possible to type characters that match an entry in the list and show that entry).
While working on the last thing that was not OK (cutting a dropdown and pasting it into another cell already containing a dropdown) I discoverd an EASY way of prohibiting paste and cut for Data Validation Lists that do allow further processing depending on what has been entered,
the following code has to be on the worksheet module.
It prevents the user from destroying Data Validation on cells and because of that using Application.Undo to revert to the state before pasting or cutting is no longer needed.
I don't know if it makes a difference but I protected the sheet and unlocked the cells the user may alter.
Private Function HasValidation(ByVal rng As Range) As Boolean
' See: https://superuser.com/questions/870926/restrict-paste-into-dropdown-cells-in-excel
' Returns True if every cell in Range r uses Data Validation
On Error Resume Next
Dim rngType: rngType = rng.Validation.Type
HasValidation = (Err.number = 0)
End Function
Private Sub Worksheet_SelectionChange(ByVal Target As Range)
' Switch DragAndDrop on/off on a range containing DataValidation
Dim modeCutCopy: modeCutCopy = Application.CutCopyMode
Dim usesValidation: usesValidation = HasValidation(Target)
If ((usesValidation = True) And (modeCutCopy <> 0)) Then Application.CutCopyMode = False ' MUST reset to avoid copying / cutting xlValidateList into another xlValidateList
' Turn DragAnddDrop on/off depending on if a cell uses Validation or not
If (usesValidation = Application.CellDragAndDrop) Then Application.CellDragAndDrop = (Not usesValidation)
GoTo SKIP
' Don't allow DragAndDrop if Target intersects with specified ranges
Dim myRange As Range
Set myRange = Worksheets("Evenementen").Range("Oordeel", "Waardering")
Dim rngIntersect As Range
Set rngIntersect = Application.Intersect(Target, myRange)
Application.CellDragAndDrop = (rngIntersect Is Nothing)
'' CheckInputValidationList(Target) ' Sophisticated testing and actions depending on the selection made
'' CheckInputValidationList(Target, myRange) ' Sophisticated testing and actions depending on the selection made for specific ranges
SKIP:
Debug.Print "SelectionChange " & Target.Address & " usesValidation=" & usesValidation & " cellDragAndDrop=" & Application.CellDragAndDrop
End Sub
In this case there are two named ranges containing (different) Data Validation Lists.
As soon as the user clicks on a cell a check will be performed if that cell uses data validation.
If so cut and paste will be turned off and CutCopyMode will be cleared until the user selects another cell.
Important is to retrieve the current CutCopyMode BEFORE any VBA-code will be executed that changes something as VBA will automatically change CutCopyMode from xlCut or xlCopy into 0 when something changes.
That initiall state is required to avoid pasting a dropdownlist over another.
The Worksheet_SelectionChange subroutine contains SKIPPED code that does something similar if a user accesses a cell in one of two ranges.
It also contains the (turned into comment) code to perform additional actions when the user selects a cell. That code might also be put in worksheet_Change
Related
I am working on a spreadsheet to copy a users data from the previous day, then delete any numerical values but keep cells with "NA". What I want is for users to not be able to change/delete the cells that still have "NA" in them. I found some code that used OFFSET to move down one cell if a certain cell was selected (based on the row and column) but I haven't been able to figure out how to use the OFFSET to move down one cell if the current cell contains "NA". (https://www.extendoffice.com/documents/excel/3820-excel-lock-cell-without-protecting-sheet.html) This worksheet is already locked with a Quality-set password, so I can't do anything to unlock the spreadsheet, then select the "NA" cells to be locked, then relock the spreadsheet, thus looking for a creative way to keep the cells from being selected or changed. Also, the code would need to run all the time, not just when a macro was selected to run. Any ideas?
If it's possible for the user to open the book without macros enabled, then I'm not sure what you're asking is possible.
If you can assume macros are enabled though, you could use events to either prevent the user selecting the cell (similar to the OFFSET you mention) or you could track changes manually onto a hidden tab in order to note changes and deal with them as you see fit. There are many ways you can achieve the latter, just search "VBA tracking changes to a sheet" etc.
This is how you'd use the OFFSET method:
Private Sub Worksheet_SelectionChange(ByVal Target As Range)
If Target.Cells(1, 1).Text = "NA" Then
Beep
Cells(Target.Row, Target.Column).Offset(0, 1).Select
End If
End Sub
Keep in mind though, this is a very simplistic method. It won't prevent users selecting multiple cells (a range) and deleting the contents. Nor will it prevent values being pasted to range that includes the 'NA'.
UPDATE:
The following is an improved version that will at least prevent users from selecting multiple cells (for pasting into or deleting) if one of the cells contains "NA".
Private Sub Worksheet_SelectionChange(ByVal Target As Range)
Dim testarea As Range
Set testarea = Intersect(UsedRange, Target.Cells)
If Not (testarea Is Nothing) Then
Application.EnableEvents = False
For Each cell_to_check In Intersect(UsedRange, Target.Cells)
If cell_to_check.Text = "NA" Then
Beep
Cells(cell_to_check.Row, cell_to_check.Column).Offset(0, 1).Select
Do Until Selection.Text <> "NA"
Selection.Offset(0, 1).Select
Loop
Exit For
End If
Next
Application.EnableEvents = True
End If
End Sub
This is still slightly flawed however, as it is still possible to drag-fill cells from other areas over a cell containing "NA".
Good Morning/Afternoon/Evening to all :)
My First time on here so.................... :)
Spreadsheet Example
I use this code in "Conditional Formatting"
=AND(ISNUMBER(SEARCH($B$3,A6)),$B$3<>””)
so i only able to highlight the cell as on Screenshot (Whenever type text on B3 it's automatically highlight the contain Cell as on Screenshot) but what I want is it's only highlight the cell but cannot jump (select) cell when i type ""text"" on Search B3 box.
For Example: On Screenshot I type DW353 and It's highlight in Redcolor on A18 Cell No. but if i type other text as DW364, 365 and on which located on A24, A25 and continue to down, it's only highlight the specific Cell and I have to Search (Scroll up and down) for look that RED Highlight. What i want is whenever i type a "Text" on B3 it's Highlight and direct Jump on Contain Cell as (Find and Replace).
Thank you in Advance and Sorry for Long Question :)
The easiest way to accomplish this kind of functionality is to use VBA to trigger the Advanced Filter functionality, so that all other rows are hidden.
Here's how:
Add a Named Range called "MyList" covering the entire range of data in the MACHINE NO: column that you want to filter. (Best if you turn this block of data into an Excel Table, and then just reference the MACHINE NO column, as this will mean the named range is dynamic i.e. it will automatically adjust should the underlying data grow).
Put the text "MACHINE NO:" in B2
Add a Named Range called "Criteria" covering B2:B3
Add a Named Range called "Input" covering B3
This should look something like the below:
Put this code in the Sheet Module that corresponds to the Per Machine Movement tab (and NOT in a standard Code Module) :
Option Explicit
Private Sub Worksheet_Change(ByVal Target As Range)
If Not Intersect(Target, Range("Input")) Is Nothing Then
On Error Resume Next
ActiveSheet.ShowAllData
On Error GoTo 0
Range("MyList").AdvancedFilter Action:=xlFilterInPlace, CriteriaRange:=Range("Criteria"), Unique:=False
End If
End Sub
End Sub
i.e. like so:
Now, any time someone enters a Machine Number, the sheet will be filtered to show just the row of interest:
You can find a working example at Daniel Ferry's excellent blog, at the following link:
http://www.excelhero.com/blog/2010/07/excel-partial-match-database-lookup.html
Look for the second sample file he posted under the heading --- UPDATE ---
This approach can be tweaked to search across multiple columns, as per your follow-up question. First, here's the setup of the Named Ranges (including a new one above the input cell called "Header"):
...and here's the amended code:
Option Explicit
Private Sub Worksheet_Change(ByVal Target As Range)
Dim vRanges As Variant
Dim vItem As Variant
If Not Intersect(Target, Range("Input")) Is Nothing Then
On Error GoTo errhandler
With Application
.EnableEvents = False
.ScreenUpdating = False
.Calculation = xlManual
End With
'Clear any existing filter
On Error Resume Next
ActiveSheet.ShowAllData
On Error GoTo 0
'Cycle through the search arrays one by one, and run the advanced filter until you find a match
vRanges = Array("Range1", "Range2", "Range3", "Range4") '<<< Change these to match your range names
For Each vItem In vRanges
Range("Header") = Range(vItem).Cells(1)
Range(vItem).AdvancedFilter Action:=xlFilterInPlace, CriteriaRange:=Range("Criteria"), Unique:=False
If Range(vItem).SpecialCells(xlCellTypeVisible).Count > 1 Then Exit For
Next vItem
errhandler:
With Application
.EnableEvents = True
.ScreenUpdating = True
.Calculation = xlAutomatic
End With
End If
End Sub
And here's a couple of screenshots showing it matching on different columns:
You can format cell B2 so that it can't be seen if you want.
I would suggest turning the different input areas in your workbook into Excel Tables (also known as ListObjects) before you set up the "RangeX" named ranges. That's because under the hood, Tables are basically dynamic named ranges that automatically expand to accommodate new data. So if you then manually set up a Named Range that points at a Table column, you never need to remember to adjust your Named Ranges in order to handle new data, because the Table automatically does this for you. See the image below:
Note that you can change the formatting of Tables using the Table Styles option in the ribbon, or even turn off the formatting entirely:
I have a large spreadsheet and have validation on several columns that have drop down lists.
I have the below VBA code in place that restricts user from hitting the delete button and blanking out cell in column with drop down. This works nicely but it does not prevent user from copying a cell from another column and pasting over the dropdown. Below is the code for one column.
Private Sub Worksheet_Change(ByVal Target As Range)
If Not Intersect(Target, Range("C5:C5004")) Is Nothing Then
If Len(Target.Text) = 0 Then
MsgBox "You must select an item from the list!"
Target.Select
Application.Undo
End If
End If
Please advise if there is a way to limit copy and paste to the same column.
The spreadsheet that I am working with is for users to compile a large list of data, and I want to maintain data integrity with the drop down lists and validation of lengths etc. Once they are done I will take and using SSIS load the application with data in various tables as a speed load.
This is the only missing ingredient that I am needing. I am not a master at VBA which is why I am asking you.
From the code present in Excel VBA How to detect if something was pasted in a Worksheet you can adapt it to have something similar to this:
Option Explicit
Private Sub Worksheet_Change(ByVal Target As Range)
Dim lastAction As String
' Get the last action performed by user
lastAction = Application.CommandBars("Standard").Controls("&Undo").List(1)
If Not Intersect(Target, Range("C5:C5004")) Is Nothing Then
' Check if the cell was cleared or the last action was a paste
If Len(Target.Text) = 0 Or Left(lastAction, 5) = "Paste" Then
MsgBox "You must select an item from the list!"
Target.Select
Application.Undo
End If
End If
End Sub
Tip: You can also detect other actions performed in the sheet. To do so just print lastAction to a MsgBox or Debug.Print and catch the ones you need.
HTH ;)
I need Help!
I am not well versed in VBA or Macros but i cannot find any other way to accomplish what i need to do without using it.
I have a sheet which i will be using to track Purchase orders, and what i need to do is; when i have a row in sheet 1 (Purchase Orders) which has been recieved i.e. the date of receipt has been recorded in column H i need for the entire row to be cut and pasted into sheet 2 (Received orders).
The header takes up the first 7 rows the rows, so i need the macro to look at rows 8-54. Once the received items are removed from sheet 1, i need the row to also be deleted or preferably for the list to be sorted by column A moving the now empty row which has been cut from open for a future entry.
Any help would be greatly appreciated.
The "Record Macro" feature should be enough to do the task you describe.. In Excel 2007, go to the Developer tab in the Ribbon, and select "Record Macro", and perform exactly the steps you are describing. It will record the equivalent VBA code, which you can then execute - or tweak/modify.
I tested this out, here's one way to do it:
Private Sub Worksheet_Change(ByVal Target As Range)
Application.EnableEvents = False
Dim receivedDate As Range, nextOpen As Range, isect As Range
Set receivedDate = Sheet1.Range("H8:H54")
Set isect = Application.Intersect(Target, receivedDate)
If Not (isect Is Nothing) And IsDate(Target) = True Then
Set nextOpen = Sheet2.Range("A1").End(xlDown).Offset(1, 0)
Target.EntireRow.Copy Destination:=nextOpen.EntireRow
Target.EntireRow.Delete
End If
Application.EnableEvents = True
End Sub
This would be pasted into the Sheet1 code. Any time a cell is changed on sheet1, the code checks to see if it's in the critical range that you specified. (H8:H54) If it is, it then checks to see if it's a date. If it is, it then copies the entire row, puts it in the next open row on Sheet2, and deletes the original row. The cells below it will get shifted up so there are no gaps.
Since the code functions on a cell changing event, it disables "Application.EnableEvents" in order to avoid a loop of changing a cell to call an event which changes a cell to call an event... etc.
I'm working on a protected Excel spreadsheet and ran into a problem with cells that I've programmatically locked that are still selectable. I can't click on them directly to select them, but if I select the cell to the lower right of a locked cell and then shift-click the one to the upper left, all 9 cells (including the 'locked' one) are selected. I can also click-drag to select the 9 cells. I'm using the following VBA code to protect and set the selection criteria:
Worksheets("Sheet1").Protect UserInterfaceOnly:=True
Worksheets("Sheet1").EnableSelection = xlUnlockedCells
I've tried the two commands in the reverse order and get the same results.
I've also used the Excel cell formatting / protection menus to do the same thing and get the same results.
Am I missing something obvious or is there some other programmatic way to mark a cell so that it can't be selected later on?
I think you'll need to create some code in the Worksheet_SelectionChange event that checks whether your protected cells are included in the new selection and, if so, selects some other cell (perhaps the next unprotected one to the right or below).
I haven't reviewed the code, but a cursory search for "Worksheet_SelectionChange protected cells selection" brought up http://www.vbaexpress.com/kb/getarticle.php?kb_id=383.
You not missing anything, this is simply Excel maintaining the shape of the selection in preference to the protection status of the cells.
The cells are still protected, but can be copied.
Using the Worksheet_SelectionChange event will allow you to run code to redirect the selection of protected cells to another cell. This code needs to be stored in the sheet you are trying to protect.
Private Sub Worksheet_SelectionChange(ByVal Target As Range)
Dim Selectedrange As Range
Dim ProtectedRange As Range
Dim ws As Worksheet
Application.EnableEvents = False
Set ProtectedRange = ActiveSheet.Range("A1:A10")
Set Selectedrange = Intersect(Target, ProtectedRange)
If Selectedrange Is Nothing Then
Exit Sub
Else
ActiveSheet.Range("B1").Select
End If
Application.EnableEvents = True
End Sub
If you just want to stop the user copying out the formulas then just add a line to your code to set 'Formula Hidden' to true. If the protected cells are copied, only the values will be copied.
Worksheets("Sheet1").Range("A1:A10").Locked = True
Worksheets("Sheet1").Range("A1:A10").FormulaHidden = True
Worksheets("Sheet1").Protect UserInterfaceOnly:=True, Password:="123"
Worksheets("Sheet1").EnableSelection = xlUnlockedCells
An alternate is to move your protected data into another sheet and set it visibilty to 'very hidden', so that it is no longer visible through the UI. The values in the 'very hidden' sheet can still be accessed via formula.
Worksheets("Sheet2").Visible = xlVeryHidden