How to Hide 2 Columns if a column has a value? - excel

Private Sub Worksheet_SelectionChange(ByVal Target As Range)
If Range("M1:N1").Columns(1).Value = "ΕΜΒΑΣΜΑ" Then
Columns("U").EntireColumn.Hidden = False
Columns("V").EntireColumn.Hidden = False
Else
Columns("U").EntireColumn.Hidden = True
Columns("V").EntireColumn.Hidden = True
End If
End Sub
So I have been having trouble with this code here. What I want to do is hide U, V columns if there is a value in M column called "ΕΜΒΑΣΜΑ".
Every time I let it run, it automatically hides the columns even if I have the value already in my column. Other than that, it doesn't seem to work in real time so even if I change anything, nothing happens.
Any ideas?

(a) If you want to check a whole column, you need to specify the whole column, e.g. with Range("M:M").
(b) You can't compare a Range that contains more than one cell with a value. If Range("M:M").Columns(1).Value = "ΕΜΒΑΣΜΑ" Then will throw a Type mismatch error (13). That is because a Range containing more that cell will be converted into a 2-dimensional array and you can't compare an array with a single value.
One way to check if a column contains a specific value is with the CountIf-function:
If WorksheetFunction.CountIf(Range("M:M"), "ΕΜΒΑΣΜΑ") > 0 Then
To shorten your code, you could use
Dim hideColumns As Boolean
hideColumns = (WorksheetFunction.CountIf(Range("M:M"), "ΕΜΒΑΣΜΑ") = 0)
Columns("U:V").EntireColumn.Hidden = hideColumns
Update
If you want to use that code in other events than a worksheet event, you should specify on which worksheet you want to work. Put the following routine in a regular module:
Sub showHideColumns(ws as Worksheet)
Dim hideColumns As Boolean
hideColumns = (WorksheetFunction.CountIf(ws.Range("M:M"), "ΕΜΒΑΣΜΑ") = 0)
ws.Columns("U:V").EntireColumn.Hidden = hideColumns
End Sub
Now all you have to do is to call that routine whenever you want and pass the worksheet as parameter. This could be the Workbook.Open - Event, or the click event of a button or shape. Eg put the following code in the Workbook module:
Private Sub Workbook_Open()
showHideColumns ThisWorkbook.Sheets(1)
End Sub

on a fast hand I would go like this...
maybe someone can do it shorter...
Option Explicit
Private Sub Worksheet_SelectionChange(ByVal Target As Range)
Dim sht As Worksheet: Set sht = ActiveSheet
Dim c As Range
With sht.Range("M1:M" & sht.Cells(sht.Rows.Count, "M").End(xlUp).Row)
Set c = .Find("XXX", LookIn:=xlValues)
If Not c Is Nothing Then
Columns("U:V").EntireColumn.Hidden = True
Else
Columns("U:V").EntireColumn.Hidden = False
End If
End With
End Sub

Related

Attempting to use worksheetfunction in userform?

I have a free form text box, which I'd like to populate with a value once a value is inputted in an above text box (txtStore) <-based on a 4 digit number.
here is the code I've attempted, but not sure if other subs (boolean logic) is needed to trigger something?
Dim ws as worksheet
Private Sub txtMall_Change()
Set ws = Worksheets("Lists")
Dim txtStore As Integer: txtStore = Me.txtStore.Value
txtMall.Value = Application.WorksheetFunction.IfError(VLookup(txtStore, ws.Range("A2:B1047", 2, False)), "-")
End Sub
how can I get the txtMall to populate based on that worksheet function once a value is placed in the txtStore text input? Do I need to change the procedure to something else like I'd have to with a combobox?
Untested:
Private Sub txtStore_Change()
Dim r
'Avoid possible run-time error on no match by skipping WorksheetFunction
' Instead test the return value for errors
r = Application.VLookup(CLng(Me.txtStore.Value), _
Worksheets("Lists").Range("A2:B1047"), 2, False)
txtMall.Value = IIf(iserror(r), "'-", r)
End Sub

Is it possible to enable Excel Manual Calculation for specific formulas?

I have a formula that makes an API request every time it's executed, which makes it slow. I'd like to prevent Excel from automatically recalculating cells containing this formula but still automatically recalculate other cells.
I've tried setting calculation mode to Manual with:
Application.Calculation = xlCalculationManual
However this prevents other cells without my formula from calculating automatically.
Another idea I've had is to check if a cell has been "frozen" and then return it's current value instead of calling the API for a new value. The issue with this is that Excel doesn't provide a way to exit the function without altering the cell value.
Function MyFormula() As Variant
If CellIsFrozen() Then
MyFormula = Application.Caller.Value 'return current value
Else
MyFormula = GetNewValueFromAPI() 'expensive call to server
End If
End Function
My issue with the above is that Application.Caller.Value returns the cell value by performing a recalculation and results in an infinite recursion.
FYI - the CellIsFrozen method is just an example sub that would somehow check whether the cell was called automatically or manually.
I'm also aware of Application.Caller.Value2 and .text, unfortunately these don't help me. Value2 also causes a recalculation, and text just returns a string representation (which is not useful because it could be "######" if the value is a date and the column is too narrow).
Is there a way to interrupt Excel's recalculation process for specific formulas?
Otherwise, is it possible to extract a value of a cell without performing a recalculation - I'm guessing that Excel stores the value somewhere because it's visible on the worksheet, it makes no sense to insist on recalculating every time.
In the context my previous answer to the post involving single cell, i also want share our old experience involving multiple cells. that days We used the formula in an indexed fashion like =myformula(1)... etc and stored it in a global array. Now today thanks to your great idea of Caller function. I recreated another improvised solution involving multiple cells.
Here again in module1
Global Flag As Boolean, LastValArr(1 To 10, 1 To 2) As Variant, Ws As Worksheet, Rng As Range
Public Function MyFormula() As Variant
Dim Adr As String, X As Integer
If Flag Then
MyFormula = GetNewValueFromAPI() 'expensive call to server
Else
Adr = Application.Caller.Address
For X = 1 To 10
If InStr(1, LastValArr(X, 2), Adr) > 0 Then
MyFormula = LastValArr(X, 1)
Exit For
End If
Next
End If
End Function
Function GetNewValueFromAPI() As Variant
GetNewValueFromAPI = Application.WorksheetFunction.RandBetween(1, 1000)
End Function
Sub CalcA1()
Flag = True
Rng.Dirty
Flag = False
Ws.Range("F1").Value = IIf(Flag, "On", "Off")
End Sub
Sub ToggleFlag()
Flag = Not Flag
Ws.Range("F1").Value = IIf(Flag, "On", "Off")
If Flag Then Rng.Dirty
End Sub
in Workbook_Open event
Private Sub Workbook_Open()
Dim X As Integer
Dim Cell As Range
Set Ws = ThisWorkbook.Sheets("Sheet1")
Set Rng = Ws.Range("A1:A5")
Set Rng = Union(Rng, Ws.Range("C1:C5"))
Flag = True
Rng.Dirty
Flag = False
Ws.Range("F1").Value = IIf(Flag, "On", "Off")
End Sub
in Sheet1 Worksheet_Calculate event
Private Sub Worksheet_Calculate()
Dim X As Integer
Dim Cell As Range
X = 1
For Each Cell In Rng.Cells
LastValArr(X, 1) = Cell.Value
LastValArr(X, 2) = Cell.Address
X = X + 1
Next
End Sub
Edit: On second thought after initial feel good of posting the Demo answer, I found it lacks User friendliness and ease of copy pasting UDF formulas while working in Excel Therefore i tried improvise it further so it could be used by users don't have access to VBA code and could work with copy paste of the UDF.
So 1st I came across a solution to store the Last Values in a temp sheet (may be Very Hidden Sheet). with apprehension that working with cell access may degrade performance of the code, I refrained from posting it and I finally restored to Dictionary Object.
This solution have added with basic advantage of Auto mapping of formula cells (by searching "=myformula" in used range of the Sheet) to enable/disable calculation. This would enable users without access to code modules to work freely with UDF.
Here reference to Microsoft scripting runtime has been added.
Code in module:
Global Flag As Boolean, Ws As Worksheet, Rng As Range, Dict As Dictionary
Public Function MyFormula() As Variant
Dim Adr As String
If Flag Then
MyFormula = GetNewValueFromAPI() 'expensive call to server
Else
Adr = Application.Caller.Address
'Debug.Print Adr
MyFormula = IIf(Dict.Exists(Adr), Dict(Adr), 0)
End If
End Function
Function GetNewValueFromAPI() As Variant
'Delay (2)
GetNewValueFromAPI = Application.WorksheetFunction.RandBetween(1, 1000)
End Function
Sub CalcA1()
Flag = True
If Not Rng Is Nothing Then Rng.Dirty
'Debug.Print "in calA1"
Flag = False
Ws.Range("F1").Value = IIf(Flag, "On", "Off")
End Sub
Sub ToggleFlag()
Flag = Not Flag
Ws.Range("F1").Value = IIf(Flag, "On", "Off")
If Flag And Not Rng Is Nothing Then Rng.Dirty
End Sub
Sub BuildRange()
Application.EnableEvents = False
Dim Cell As Range
CalcCnt = CalcCnt + 1
Set Rng = Nothing
Dict.RemoveAll
For Each Cell In Ws.UsedRange.Cells
If Left(Cell.Formula, 10) = "=myformula" Then
'Debug.Print "From Sht Calc -" & Cell.Address
If Dict.Exists(Cell.Address) = False Then
Dict.Add Cell.Address, Cell.Value
Else
Dict(Cell.Address) = Cell.Value
End If
If Rng Is Nothing Then
Set Rng = Cell
Else
Set Rng = Union(Rng, Cell)
End If
End If
Next
Application.EnableEvents = True
End Sub
In Workbook_Open
Private Sub Workbook_Open()
'Dim X As Integer
Dim Cell As Range
Set Ws = ThisWorkbook.Sheets("Sheet1")
Set Dict = New Dictionary
Flag = True
BuildRange
If Not Rng Is Nothing Then Rng.Dirty
Flag = False
Ws.Range("F1").Value = IIf(Flag, "On", "Off")
End Sub
In Sheet Calculate event
Private Sub Worksheet_Calculate()
BuildRange
End Sub
If you are using an UDF in the cell, I will like to make it like this workaround.
For demo and test, Only used a single cell A1 in "Sheet1" , instead of using any API, I used WorksheetFunction.RandomBetween May use range and array if multiple cells are used.
In "Sheet1" cell A1 used =myFormula()
in a module
Public Flag As Boolean, LastVal As Variant
Public Function MyFormula() As Variant
If Flag Then
MyFormula = GetNewValueFromAPI() 'expensive call to server
Else
MyFormula = LastVal
End If
End Function
Function GetNewValueFromAPI() As Variant
GetNewValueFromAPI = Application.WorksheetFunction.RandBetween(1, 1000)
End Function
Sub CalcA1 in Module1 would be used to recalculate A1 whenever necessary. It could be called from any events also according to actual requirement.
Sub CalcA1()
Flag = True
Worksheets("Sheet1").Range("A1").Dirty
Flag = False
End Sub
In workbook Open event the the LastVal was calculated with Flag as true and then Flag was reset to false to prevent further calling GetNewValueFromAPI
Private Sub Workbook_Open()
Flag = True
Worksheets("Sheet1").Range("A1").Dirty
LastVal = Worksheets("Sheet1").Range("A1").Value
Flag = False
End Sub
In Worksheet_Calculate event of Sheet1 the LastVal is being recorded.
Private Sub Worksheet_Calculate()
LastVal = Worksheets("Sheet1").Range("A1").Value
End Sub
Working Demo
Regret, I came across this post (A Real Good Question) late, since We had already been used something in this line in our workplace. Thanks to #Pawel Czyz for editing the post it came under Active List today only.

Same Worksheet_Activate Code But With Different Ranges Not Working on Sheet 2

First of all, I know nothing about macros and vba used in Excel and other applications. I copied from the internet and ran the following code in sheet 1 as:
Option Explicit
Private Sub Worksheet_Activate()
Dim r As Range, c As Range
Set r = Range("a129:a1675")
Application.ScreenUpdating = False
For Each c In r
If Len(c.Text) = 0 Then
c.EntireRow.Hidden = True
Else
c.EntireRow.Hidden = False
End If
Next c
Application.ScreenUpdating = True
End Sub
The code is working fine in Sheet 1 but the same code but with different range,i.e. "a5:a100" is not working for sheet 2.
Do we need to deactivate the code for sheet 1?
Thanks in Advance,
Regards,
ID
You might create one sub like this one and place it in a standard code module, for example Module1' (you will have to insert it: Right-click in the Project explorer while selecting the workbook's VBA project, selectInsertandModule`).
Option Explicit
Sub HideRows(Rng As Range)
Dim Ws As Worksheet
Dim R As Long
Application.ScreenUpdating = False
With Rng
Set Ws = .Worksheet
For R = 1 To .Rows.Count
Ws.Rows(.Row).EntireRow.Hidden = Not CBool(Len(.Cells(R)))
Next R
End With
Application.ScreenUpdating = True
End Sub
Then call that same sub from all the worksheets to be affected, each one with a different range as argument.
Option Explicit
Private Sub Worksheet_Activate()
HideRows Range("A1:A1675")
End Sub
The idea is that the range should have only one column. If you feed a multi-column range the Hidden status of the row will depend upon the last cell's content in each row.

Excel VBA: Worksheet Change cell value to different sheet

I've been working on this for some time now and have hit a real stumbling block.
I have a set of values that are available via a validated dropdown menu in Sheet 3, Column D. Once selected this value currently displays in a different sheet (Sheet 7) using excel function ='Sheet 3'!D4 and so on, and I have some code that reads this and performs an IF statement to produce a value in another cell.
My problem is the code is dependant on reading the value and not the formula.
I currently have a worksheet change command for a separate function I want to run, is there a way for this to run a second function and call any changes from sheet 3 column D into sheet 8 column D and then run my other change function?
Sheet 7 Code:
Private Sub Worksheet_Change(ByVal Target As Range)
Dim c As Range
If Intersect(Target, Range("D2:D102")) Is Nothing Then Exit Sub
Application.EnableEvents = False
On Error GoTo Finalize
For Each c In Target.Cells
Select Case c.Column
Case 4
Call Print_Quality(c)
End Select
Next c
Finalize:
Application.EnableEvents = True
End Sub
Sheet 7 Module:
Sub Print_Quality(c As Range)
Dim PrintQuality As String
Dim PrintSpeed As String
PrintQuality = c.Value
If PrintQuality = "A Quality 1" Then PrintSpeed = "100"
c.Offset(0, 5).Value = PrintSpeed
End Sub
I've been trying this route but to no avail:
Worksheet 3 code:
Private Sub Worksheet_Change(ByVal Target As Range)
If Intersect(Target, Range("D4:D104")) Is Nothing Then Exit Sub
Application.EnableEvents = False
On Error GoTo Finalize
UpdateVal
Finalize:
Application.EnableEvent = True
End Sub
Module:
Sub UpdateVal()
Worksheets("Sheet 7").Range("D2").Value = Worksheets("Sheet 3").Range("D4").Value
End Sub
Many thanks
Sods law, I've managed to fix this an hour after my desperation post.
I completed another worksheet change from the sheet it was calling from (Sheet 3)
Private Sub Worksheet_Change(ByVal Target As Range)
Dim KeyCells As Range
Set KeyCells = Range("D4:D104")
If Not Application.Intersect(KeyCells, Range(Target.Address)) Is Nothing Then
Call UpdateVal
End If
End Sub
then added this function into the module
Sub UpdateVal()
Sheet8.Cells(2, 4).Value = Sheet3.Cells(4, 4)
End Sub
this now references the value of the dropdowns in sheet 8 and allows other functionality to continue using the cell value
Have you tried stepping through your code to see where it is having an issue? It not, I would suggest putting a break at the beginning of each module, and then use F8 to step through. This will confirm it is running as it should.
You should also be fully-qualifying your references to worksheets. While presumably the sheet references should carry through given that they are in worksheet modules, there is the chance of failure. You can simply assign a variable to hold the worksheet like so:
Dim wb as Workbook
Dim ws as Worksheet
Set wb = ThisWorkbook
Set ws = wb.Sheets("YourSheetName")
Additionally, your worksheet 3 code:
EnableEvent = True
Should be:
EnableEvents = True

get value from specific background

I have a list with background colors in "A" column and value in their ceil is the name of colors.
I want to do that when I select a cell with a background color this will change the value of "C1" value to the value that have in "A" column.
(this is not the my real name of the colors, I have a specific name for each colors.)
Like vlookup but with background colors and in the same ceil.
For example:
Thank you!
Put this in the code section of the worksheet :
Private Sub Worksheet_SelectionChange(ByVal Target As Range)
With Target
If dictColours.Exists(.Interior.ColorIndex) Then
Sheets("Sheet1").Range("C1").Value = dictColours(.Interior.ColorIndex)
End If
End With
End Sub
And add this to a new module, replacing the sheet reference:
Public dictColours As Scripting.Dictionary
Sub test()
Set dictColours = New Scripting.Dictionary
Dim rngTarget As Range
Set rngTarget = Sheets("Sheet1").Range("A1")
Do While rngTarget.Value <> ""
dictColours.Add rngTarget.Interior.ColorIndex, rngTarget.Value
Set rngTarget = rngTarget.Offset(1, 0)
Loop
End Sub
Think of using the conditional formatting.
elaborating on the very fine solution form Will I'd propose the following alternative code to be entirely put in the code section of the relevant worksheet
Option Explicit
Private Sub Worksheet_SelectionChange(ByVal Target As Range)
Dim dictColours As Scripting.Dictionary
Set dictColours = GetDictColours(Target.Parent)
With Target
If dictColours.Exists(.Interior.ColorIndex) Then
.Parent.Range("C1").Value = dictColours(.Interior.ColorIndex)
End If
End With
End Sub
Function GetDictColours(sht As Worksheet) As Scripting.Dictionary
Dim i As Long
Set GetDictColours = New Scripting.Dictionary
Do While sht.Range("A1").Offset(i) <> ""
GetDictColours.Add sht.Range("A1").Offset(i).Interior.ColorIndex, sht.Range("A1").Offset(i).Value
i = i + 1
Loop
End Function
aside from some stylistic choices (everyone has his own favorites), it should be more simple for the OP to handle, he being (as he himself stated) a total VBA beginner!

Resources