VBA - insert objects next to each other - vba

I have some VBA code, such that when a button is pressed, the user can choose a file from their computer, and insert it as an object that looks like an icon into the spreadsheet.
The code is such that it counts each time the button is clicked, so each new icon inserted by the user appears to the right of the existing one, i.e:
count = count + 1
ActiveSheet.Shapes("Object 1").IncrementLeft 90*count
Now, the problem is, I don't know how to change the count so that each time a user deletes an object from the spreadsheet, the count would decrease by 1 (so that the next object inserted would still be in line and the appropriate spacing to the right of the first object). I would assume that I would use an event, but there doesnt seem to be an event for when an object is deleted.
Any advice?

Since the embedded files are OLE objects then you don't need to maintain a count. You can just calculate it on the fly using the OLEObjects collection.
If there are other ActiveX objects then you might need to need to iterate through and just count the items of type xlOLEEmbed.
Count = 0
For Each o In Sheet1.OLEObjects
If o.OLEType = xlOLEEmbed Then Count = Count + 1
Next

How do you handle the deletes?
Maybe you can prompt the user to press a button to delete beforehand.
So user presses a button, you see how many objects are in whatever field you're referencing, and then keep scanning each time the user initiates something.
Either way you're going to have to have some way for the to track each time the user initiates something. Or you can track by selection; IE if user selects one of the objects, see how many objects remain, after each action performed by the user.
Presumably they're only selecting it in order to delete.

Related

When the user clicks a text box on my form, I need to assign an ID to the current record if there isn't one already

I have a form, frmEvent, for editing and creating records. Each record represents an Event. I have a pop-up form that displays the many-to-many relationships that associate one or more Persons to the current Event. This pop-up form is opened when the user clicks a text box on frmEvent.
The pop-up form displays all of the records in tblEventPersons where EventID = the ID of the Event record that is currently open. This normally works great. However, when my form is used to create a new Event record, Access does not assign an ID for the new Event record until at least one field has been changed. If a user creates a new Event record and tries to immediately open the frmEventPersons form, they get an error because the new Event record does not yet have an ID assigned.
I'm sure there's a very simple solution to this, but my searches have not yielded anything useful.
Well it is assumed that the main form has a record, and has some data and has a "ID" as you well asked.
However, if the form is moved to a new record, and you want to launch that next form that requires and needs the current forms ID that as you WELL note has not yet been generated?
Well, keep in mind that you current form WILL IN FACT generate the ID (assuming a auto number PK id column) if the current record has been "dirtied". We thus assume that the current record had SOME data editing. If it has NO EDITING done, then it actauly turns out is a bit of a trick to get Access force feed and generate that ID.
About the MOST easy way?
Dirty some field/column in the current form.
eg:
if isnull(me!ID) then
' this record does not yet have the PK id
' dirty some bound control.
me.MyEditDate = date()
end if
me.Dirty = false ' this will force a recrod save - gereate the "ID" you want.
docmd.Open "frmDetails",,,"FKColumn = " & me!id

Programatically change selected item in Outlook

I'm trying to write a piece of code that takes a user-selected list of items (typically mailitems) from the active explorer, performs some action on them (e.g. setting the value of a custom field), then - when complete - selects the next item in the explorer window and ends execution there.
I have no problem identifying the selected list of items
I have no problem looping through the selected items and performing required actions on each
But I cannot, for the life of me, figure out how to programatically select the next item in the explorer window upon completing the above process (keeping in mind, the next item is NOT in the list of user-selected items.
Example ...
Inbox has 10 items
User selects items 1, 4 and 7
Code identifies items 1, 4 and 7 as the active selection
Code performs an action on items 1, 4 and 7
--> Upon completion, I want item #8 to be selected
Is this possible ???
The wrinkle, and why I can't just use a SendKeys "{DOWN}" statement at the end: the active view is grouped by a custom field (... call it a flag, for ease of reference) - where flag=false show up at the top of the screen in group #1, and flag=true show up in group #2 at the bottom of the screen. It's a way of relegating mailitems to a "basement" of sorts, without deleting them or having to move them to some other folder. They stay in the inbox, but get grouped separately, out of sight.
The actions being performed on items 1, 4 and 7 consist of setting the flag to TRUE, thereby causing the items to disappear from the upper portion of the screen, and moving to the lower part of the screen (i.e. in group #2). This all works great.
... until the code reaches the final item in the selection, and again does what it needs to do, and ends ... but at this point the active/selected mailitem that is displayed in the explorer window is the same last mailitem (#7). So the user is now seeing a mailitem way down the inbox in group #2. In other words, the user is now in the basement of the inbox, not at the top, because Outlook is displaying the last mailitem in the selection, which has been moved to the basement by the code.
I want the code to change the active/selected mailitem to be #8 (which still has a value of FALSE for that custom field I'm setting), so that upon completing code execution, the screen position remains where it was when the code was executed.
The only thing I've managed to do is apply a SendKeys "{HOME}" at the end, so at least the user's view continues to show the mailitems in group #1 ... but not the right mailitem.
I'm really stumped here.
Any help would be appreciated.
NB> My thought process was, at the time of initiating the code, to identify the last mailitem in the selection (... either by index # or by EntryID) - which I can do easy enough - but then somehow finding a way to i) locate the NEXT mailitem immediately following it, and storing this reference, and then ii) upon completing the code, getting outlook to select the mailitem with the reference ID I saved. I can't figure out how to do this.
Thanks
If received time is in some order you could use that to determine the next item to select.
The process could look like this.
selCount = ActiveExplorer.Selection.Count
' Assumes you select downwards not randomly
minRecTime = ActiveExplorer.Selection(selCount).ReceivedTime
Once the items are moved loop downward through the items.
currRecTime = currFolder.items(i).ReceivedTime
If currRecTime < minRecTime Then
Exit For
End If
Now you have a position with "i".
Remember to run from a button not the VB editor to see SendKeys work.
SendKeys "{HOME}"
For j = 1 To i - 1
SendKeys "{DOWN}"
Next j

Delete function first or last record

My main menu is where all records are displayed. The user can navigate between records by a list table which has all the records.
When i use the this code to delete a displayed record, allways the first record gets deleted.
BindingContext(JobsDBDataSet, "T_Jobs")
.RemoveAt(BindingContext(JobsDBDataSet, "T_Jobs").Position)
Ive added an extra line in which the position should match the ID of the record.
BindingContext(JobsDBDataSet, "T_Jobs").Position = Job_IDTextBox
BindingContext(JobsDBDataSet, "T_Jobs")
.RemoveAt(BindingContext(JobsDBDataSet, "T_Jobs").Position)
NOTE: Job_IDTextBox is directly connected to the column in the database (I dragged it from the datasource menu into the form in details form)
My goal is so that i can delete the job that has been selected (and displayed) from the list box (with all records in it)
Right so ive changed my code to
BindingContext(JobsDBDataSet, "T_Jobs").Position = Convert.ToInt32(Job_IDTextBox.Text)
BindingContext(JobsDBDataSet, "T_Jobs").RemoveAt(BindingContext(JobsDBDataSet, "T_Jobs").Position)
And the same thing still happens: last job is deleted and not the selected one (from the listbox). I have a feeling its a problem regard the listbox, and maybe I'd firstly have to tell the program to change its pointer to the job ive chosen from the list box but i have no idea how to do this - and this is just an assumption.

Number Picker in Access / VBA

I am trying to put a number picker in a form in MS Access 2007. Here's an example of what I am trying to make:
I cannot find this in the default form controls, and have tried to make one myself using a listbox. Listboxes can be modified to look just like the number picker above, however the arrows only change the view, of the list, and not the actual selection (that is the value). For example, with the list box, if I have it range from 1 to 3, and default at 1 - when I change it to 2 via the arrows, the value of the listbox does not change, and is still one.
Does anyone know how to get a number picker in Access?
So you want to create a list of numbers and allow users to change the value displayed (AND stored as the control's value) using up and down arrows, such that they select the next or previous in the list.
I would suggest creating a text box and two buttons. Populate an array with the list of values. When a button is clicked it would:
A. Find the position in the array of any value already entered into the text box (eg loaded from a database)
B. Get the next or previous item from the array.
The array is populated as required (probably when the form is opened).
If you just need to allow the user to enter a whole integer number (ie a number spinner) you would do as follows:
Create one using a (locked) textbox and two buttons. Just add a textbox (name it something like txtValue) and two buttons (btnUp and btnDown), then add code like this to the Click event of those buttons:
Private Sub btnUp_Click()
Me.txtValue = Nz(Me.txtValue, 0) + 1
End Sub
Private Sub btnDown_Click()
Me.txtValue = Nz(Me.txtValue, 0) - 1
End Sub
You could add if statements to limit the data being entered
Or you can use a 3rd party control.
http://www.fmsinc.com/microsoftaccess/controls/components/spin-button/index.html
There are probably more, but be aware that using these sorts of controls in Access is unsupported, and there is no guarantee moving forward that they will work in Access. You're far better off using the native methods described earlier.

Gray out a form row's (detail's) button conditionally with VBA code

I have a standard form in MS-Access which lists a bunch of orders, and each row contains order no, customer, etc fields + a button to view notes and attached document files.
On request from our customer we should gray out the button btnAnm (or check or uncheck a checkbox) depending on a calculation from two queries to two other tables (a SELECT COUNT WHERE and a check if a text field is empty).
I've tried btnAnm_BeforeUpdate(...) and btnAnm_BeforeRender(...) and put breakpoints in the subs, but none of them trigger. The same if I use the control Ordernr instead of btnAnm.
I'd like a function in the Detail VBA code to be triggered for each "Me." (row) so to speak, and set the row's control's properties in that sub.
What do I do? I've looked at the help file and searched here.
*Edit: So I want to do something that "isn't made to work that way"? Ie. events are not triggered in Details.
As an alternative, could I base the value of a checkbox on each line on a query based on the 'Ordernr' field of the current row and the result of a SELECT COUNT from another table and empty field check?
Do I do this in the query the list is based on, or can I bind the extra checkbox field to a query?
A description of how to do this (combine a COUNT and a WHERE "not empty" to yes/no checkbox value) would be perfectly acceptable, I think! :)*
You cannot do much with an unbound control in a continuous form, anything you do will only apply to the current record. You can use a bound control with a click event so that it acts like a button.
Presumably the related documents have a reference to the order number that appears on your form, which means that you can create a control, let us call it CountOrders, with a ControlSource like so:
=DCount("OrderID","QueryName","OrderID=" & [OrderID])
The control can be hidden, or you can set it up to return true or False for use with a textbox, you can also use it for Conditional Formatting, but sadly, not for command buttons.
Expression Is [CountOrders]>0
You can also hide the contents and add a click event so that is acts in place of the command button. Conditional Formatting will allow you to enable or disable a textbox.
As I understand your question, you have a continuous form with as command button that appears on each row - and you'd like to enable/disable the button conditionally depending on the contents of the row.
Unfortunately you can't do that. It seems that you can't reference the individual command buttons separately.
Having wanted to do something similar in the past I came up with two alternate ways of setting up my interface.
Put a trap into the onClick code for the Button. Which is icky, because it is counter intuitive to the user. But it gets you that functionality now.
Move the command button (and editable fields) up into the form header, and make the rows read only. Your users then interact with the record only in the header, and select the record they want work with in the list below. As I recall this is known a Master-Detail interface.