Hope somebody can help!
I am programming in VB6 and am trying to write an activeX control for an indicator. The indicator should change color relative to an excel open workbook cell being true or false. The indicator should be auto updating i.e. the indicator needs to link live to the excel cell.
I can then place several of the indicators c/w links to different cells on a userform. The workbook is opened and tested in the userform and object references set up ok.
I can't figure out how to link the indicator to the excel cell.
This is part of a larger project I am trying. Other control such as bargraphs, Switches etc. to be added if I can get the first one working.
Thanks in advance
You need to first add a reference to the Microsoft Excel x.x library in your control's references.
Next, you should add a private module level variable of type Excel.Worksheet, and declare it WithEvents, e.g.
Private WithEvents m_oWorksheet As Excel.Worksheet
You should also create a Property Set procedure called Worksheet which sets this variable.
Then you should add code for its Change event, e.g.
Private Sub m_oWorksheet_Change(ByVal Target As Range)
If Target.Column = 1 And Target.Row = 2 Then ' m_oWorksheet.Range("A2")
'Do some coding here
End If
End Sub
Obviously, .Column = 1 and .Row = 2 would be replaced by the cell coordinates you are interested in. I originally used coordinates like "A2", but found that the objects returned from m_oWorksheet.Range("A2") cannot be directly compared with the Target object, e.g.
If Target Is m_oWorksheet.Range("A2") Then
I tried to extract the cell reference "A2" from Target, but I can't seem to find a way to do it, unless you write a function to do the conversion of Column/Row to a string reference.
Note the previous answer I provided was very wrong, since I was testing
If Target = m_oWorksheet.Range("A2) Then
... which only worked because the default value properties were identical. This would fall over if any changed cell had the same value as a "watched" cell.
Related
I'd like to preface this question by saying that I am an undergrad in college who knows C++ and has a very rudimentary understanding of VBA.
Now then, as stated in the title I need some help configuring some VBA code for an Excel worksheet so that whenever a cell in a column (specifically the D column) is modified it will automatically update other cells within the same row.
Essentially I want this to work such that when user Bob modifies cell D26 (for example) it will call a custom function I built and insert that code into cell B26 and then repeat with a different function for cell C26.
However, this function needs to be such that if cell D27 is modified it will only modify other cells in row 27, leaving row 26 and prior or subsequent rows alone until such a time as this function is called in D28 and so on.
I'm not entirely sure if this is even possible but I'd be gracious if anybody could help me configure this.
The code I built/scavenged from the internet for my custom function is this:
http://pastebin.com/RE0V2nrT
The second function I want to call for this project is the =TODAY() function built into Excel.
The code I have scraped together so far for checking if the cell has changed is this:
http://pastebin.com/S5E8cmty
If anybody could help me understand how to write what I'm looking for it would be much appreciated. If you have a different approach to solving the issue I would also love to hear it... as long as you could help me then enact your solution, haha!
Anyways, thanks to anybody who replies.
Have a look at the worksheet events available within the Excel namespace.
For this, you would use the Change event
If you double click on the worksheet you want to monitor, you can insert a Worksheet_Change sub. Then you can use the intersect function to check if the changed cell was within your range you want to monitor (e.g. D:D).
You can specify which cells you want to change. Here I just gave an example based on what you asked. This will put the output of your function into cell B[R] and put the current date into cell C[R]. Note that I'm using the Now() function since there is no Today() function in VBA. Since this returns both date and time, I'm using the Format function to get just the date.
Just for fun, let's go a little further into the object model and first get the Worksheet object to which the target range belongs. This is not 100% necessary - you could just rely on ActiveSheet. Now, you probably don't need to do this, and it's mostly just for fun, but it's also worth noting that if you were programmatically making changes to this sheet, but had not activated this sheet first (so another sheet was active) and you had not turned off EnableEvents you would get some strange results :)
Private Sub Worksheet_Change(ByVal Target As Range)
Dim TargetSheet As Worksheet
Set TargetSheet = Target.Parent
With TargetSheet
If Not Application.Intersect(Target, .Range("D:D")) Is Nothing Then
.Cells(Target.Row, 2) = ExtractWindowsUser()
.Cells(Target.Row, 4) = Format(Now(), "YYYY-MM-DD")
End If
End With
End Sub
Explanation
Worksheet change sub is declared like this. The Worksheet objects have pre-defined method stubs for events. Kind of like an interface, though not listed as an interface in the documentation. If you think of it in that concept, this is your event handshake. See the link I posted above for a list of the worksheet events available.
Private Sub Worksheet_Change(ByVal Target As Range)
In the next lines we are getting the worksheet object to which the object named Target belongs. You can see in the sub declaration that Target is declared as an object of the type Range. If you check out the Worksheet object (linked above) or the Range object documentation you'll see that the range object is a member of the worksheet object, and the documentation kind of sucks here, but FYI the worksheet object is contained within the Parent property. Now, originally I had my code using the ActiveSheet member of the Application object - but I've edited it for the reasons given in my answer above.
Dim TargetSheet As Worksheet
Set TargetSheet = Target.Parent
I use With Blocks to save typing the same Worksheet reference in multiple places. A With block just lets me access the members of the namespace specified (in this case members of the object TargetSheet) by typing .SomeMember. The compiler understands that every reference like this refers to whatever is specified in the opening With .... statement. I personally like this for readability, but I also recommend it for maintenance (change reference one place vs many). Also having a single reference gives a tiny, insignificant, probably not worth mentioning performance boost over multiple references as well.
With TargetSheet
Next we check whether or not Target is within the range of cells we want to watch. The If....Then should look familiar enough. For our condition we use the boolean operator Not to check if the result of the intersect function (linked above) Is Nothing. The reason we do this is to check if the return is allocated. If an object is allocated the Not SomeObject Is Nothing condition will evaluate to False. If the object is not allocated (i.e. our Intersect function failed to return anything) then the statement evaluates to True. So, from the Intersect function documentation we know that if our return is allocated, the ranges intersect and the intersecting range object was returned. Thus if we want to know if they intersect, we can just check for the opposite of a failure.
If Not Application.Intersect(Target, .Range("D:D")) Is Nothing Then
The next lines then just execute some code on cells within the same row as Target. We use the Cells member of the worksheet object to specify what cells to modify. Per the documentation, the default property for Cells is Item which lets us access a range object through a row and column index like this: .Cells[Row,Column]. So, I simply use the row of our Target object and the column you wanted (column "A" =1, "B"=2, etc. You can see this by changing excel properties to R1C1 reference style if you are interested).
.Cells(Target.Row, 2) = ExtractWindowsUser()
And I think the Format() and Now() functions are pretty well explained in the documentation.
I tried running the private sub code:
Private Sub Worksheet_Calculate()
Dim target As Range
Set target = Range("G3")
If Not Intersect(target, Range("G3")) Is Nothing Then
End If
End Sub
I am pulling a value from one sheet and putting it into another cell on a separate sheet. I want the VBA to run when the cell is automatically changed. When I tried the code above, nothing happened when I updated the cell.
From the sounds of things the issue seems to be that you're trying to put the code in a Module like a regular macro. To have it run based on a worksheet event, you need to have the code in that worksheet's code window (in the VBA window, there's the "Microsoft Excel Objects" folder, inside is the list of worksheets, double-click the worksheet to open it's code).
Once you've opened the worksheet's code, at the top of the window you should see two drop-downs. The left one should show "(General)". In that drop-down select "Worksheet" (should be the only option).
In the drop-down to the right of that, select "Change". Then you need to validate the Target is the right cell (If Target.Address = "$<Column letter>$<row #>"). Inside that If statement is where you'd nest your code to copy the value to the second worksheet
I have a named comboBox, let's call it: "comboBox1"
I want to reference the value of comboBox1 from a cell.
=if(comboBox1.Value=1,1,0)
The idea above is what I'm looking for. I know I can attach an even to comboBox1, which populates a cell, which can be read by other cells, but that just introduces more moving parts and complexity.
This has to be possible, right? Any help would be great, thanks!
I think something like this is possible.
For your combobox change event, you will need to trigger a recalculation:
Private Sub ComboBox1_Change()
Application.Calculate
End Sub
Next, you will need to add a custom user defined function. The important piece of this the Application.Volatile line. This will make sure its recalculated, after any calculation.
Function GetComboVal(cmbName As String) As String
Application.Volatile 'will always recalculate
Dim cmb As OLEObject
Set cmb = Sheet1.OLEObjects(cmbName)
GetComboVal = cmb.Object.Value
End Function
So in your cell, you will need to use a call like this:
=if(GetComboVal("ComboBox1")=1,1,0)
The Downside to this technique is that if your worksheet has many calculations, it could take a while to recalculate.
Good Afternoon,
There is a much easier way to link a cell to a combo-box. With-in the properties of the ComboBox, above ListFillRange is Linked Cell. You would just designate this cell to whatever you want your combobox value to equal too.
Excel allows a cell link on both an ActiveX and a forms doropdown (combo). This will write the value to a cell without any code.
ActiveX combobox objects in Excel do not behave well when their ListFillRange refers to a formula-based Named Range (Defined Name).
I think I have encountered other errors and possibly even Excel crashes thanks to this, but right now all that happens is the combobox_change() event is triggered anytime ANY cell in the workbook is changed.
I am not sure if this is really a bug, or if there is a fix, or a workaround. If it is a bug, how do I report it to the Excel people?
And finally, the real meat of my question is "How do I work around this issue best?" I would like to have some formula-based named ranges, but it seems like this won't be possible.
To reproduce this bug, do the following:
Create a new workbook. On Sheet3, create a small table 3 columns across, and several rows high.
Create a named range with this formula (or an equivalent): =OFFSET(Sheet3!$A$2:$C$36,0,0,COUNTA(Sheet3!$A:$A),COUNTA(Sheet3!$4:$4)) To do this use Input>Name>Define. Name the range something like "demoRange"
Go to Sheet1 and create a combobox, (it must be on a separate sheet). (Use the Control Toolbox menu, not the Forms menu).
Click on the Design Mode button (the blue triangle with pencil), then right click on the combo box and go to Properties.
In the properties window for the combobox, change the ListFillRange property so that it points at the named range you created in step 2 ("demoRange").
You may want to change the ColumnCount property to 3, and the ColumnWidths property to "50,50,50"
Set the linkedCell property to cell "A1" by typing A1 in the linkedCell property.
Close the properties window, and double click on the combobox to define its change() event.
Put a Debug.Assert(false) or Msgbox("demo") line in the subroutine for the new combobox's change event.
Exit design mode
important - Now select an item in the combobox. The event should trigger normally the first time. (The bug will not show if you don't do this step--something must be selected in the combobox)
Edit cells anywhere in the workbook [Edit] or any other open workbook [/edit], on any sheet and any location. Each time you edit any cell, (at least for me), the onchange event for the combo box is run.
Again, is this normal, and what is the best alternative for what I am doing? This combo box gets linked to various cells, and is supposed to be a replacement for the tiny font in the data validation dropdowns excel provides by default.
My advice is to never use ListFillRange and LinkedCell. They are just trouble. Fill your listbox with List and use the Change event to write to the cell. Somewhere, maybe the Workbook_Open event, fill the listbox
Private Sub Workbook_Open()
Sheet2.ListBox1.Clear
Sheet2.ListBox1.List = Sheet1.Range("demoRange").Value
End Sub
Then in the change event in the Sheet2 module, check that something was clicked and write it to the cell
Private Sub ListBox1_Change()
If Me.ListBox1.ListIndex >= 0 Then
Sheet2.Range("A1").Value = Me.ListBox1.Value
End If
End Sub
I have a few options available that I am aware of thus far. The best I can come up with is this:
Avoid directly using formula-based named ranges. Instead, define a subroutine that will check whether the defined range "demoRange" should be changed from what its current value is. Run this subroutine on the workbook_open and sheet3_deactivate events. If needed, prompt the user to ask if it's all right to update the named range. [edit] The macro that updates "demoRange" could probably just copy from a "demoRange_FormulaBased" named range into "demoRange" which would be static. [/edit]
This solution works well because you can keep using the linkedcell property, you don't have to use VBA to populate the comboboxes, and the named range can still be used for whatever other purposes it already had. Avoid using the onchange event to run this new subroutine, since it might end up being triggered thousands of times if a user opens the Find/Replace dialog and chooses "Replace All".
Background Details
I have an excel spreadsheet with Activex dropdown (combobox) objects which help the user to know what options are available. I did this because the data validation list dropdowns are way too small in font size, and were gathering a lot of complaints.
So my solution was to add combobox objects which allow the user to select from a range of options. However, I have to link the comboboxes to a cell with the linkedcell property, so that both the user and various formulas can see what has been chosen. I also set up the combobox to disappear when it's not in use (much in the same way as the data validation dropdown button only appears when you select the relevant cell).
Here is the problem:
I don't want the users to edit the value in the linked cell, so I make sure the linked cell is locked whenever the combobox is not selected:
Private Sub comboBox1_GotFocus()
Call unlockComboBoxTargetCell(comboBox1)
End Sub
the procedure above does this:
If (targetComboBox.LinkedCell <> "") Then
Dim targetCell As Variant
Set targetCell = Range(targetComboBox.LinkedCell)
If Not targetCell Is Nothing And targetCell.Locked <> False Then
unlockSheet (activesheet.Name)
targetCell.MergeArea.Locked = False
lockSheet (activesheet.Name)
End If
End If
Equivalent procedures exist to lock the target cell.
However, whenever you do a "Save As" action on the workbook, it seems that the linked and locked cells create a problem: Excel gives this error out of the blue:
"The cell or chart you are trying to change is protected and therefore read-only..."
This error comes up about twice or three times for each cell that is locked and is the linkedcell for a combobox.
Is there a good way to overcome this problem? Right now my best solution is to leave the cells unlocked and place data validation on the cell, so that if the user edits the cell they will at least be refused when they type something invalid. I could make sure that the combobox covers up the linked cell whenever it is selected, but sometimes that means having a very large, annoying combo box with a very tiny dropdown button on its right side.
Perhaps I am being a bit too particular about the user interface?
Thanks in advance for reading this long and involved post.
In the "lockSheet" procedure you have created, the code to 'protect' the worksheet needs an additional parameter, UserInterfaceOnly, set to true.
I imagine the LockSheet sub is something like this;
sub lockSheet(strSheetName as string)
thisworkbook.sheets(strSheetName).Protect
end sub
Try this:
sub lockSheet(strSheetName as string)
thisworkbook.sheets(strSheetName).Protect, UserInterfaceOnly=True
end sub
UserInterfaceOnly allows programmatic changes to the protected sheet.
Bill