Programatically change selected item in Outlook - vba

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

Related

Outlook Instant Search - hit and miss results

I have the below code to run an instant search from a tool I've developed that iterates through all Outlook folders and then uses the restrict method to get two counts, first the total and the second, a count of those items that are two years or older.
Once done, this is displayed to the user in a listview and the code below is supposed to do an instant search query on the selected result using the 'received' date to limit the results.
What I've found is that sometimes the instant result filters the results and in other cases it purely displays all items from within the selected folder. For example, a folder has 90 items but 5 are over 2 years old, sometimes it will show 5 (generally after the selection has been made from the listview twice) and the rest of the time the full 90 are shown.
Has anyone else come across this is and managed to resolve it?
Private Sub OpenOlFolder(sender As Object, e As EventArgs) Handles lvwProgress.DoubleClick
With olApp.ActiveExplorer
'// CLEAR SEARCH
.ClearSearch()
'// SWITCH TO SELECTED FOLDER
.CurrentFolder = GetOlFolderFromPath(Me.lvwProgress.Items(Me.lvwProgress.FocusedItem.Index).SubItems(0).Text)
'// DO SEARCH
.Search(String.Concat("received:<", RetentionDate.ToString("MM/dd/yyyy")), Outlook.OlSearchScope.olSearchScopeCurrentFolder)
End With
End Sub
In your code I have noticed multiple dots in the single line of code. It is hard to understand where your code fails if something unexpected happens. So, breaking the chain of property and method calls is essential in troubleshooting such cases.
In the code you change the current folder by setting the CurrentFolder property of the Explorer class. It is a time-consuming operation, so it make sense to wait until it is done. For example, you may try to run the Search method in the Explorer.FolderSwitch event which is fired when when the explorer goes to a new folder, either as a result of user action or through program code.
Also it make sense to do the same operation manually to make sure the filter is correct.

Access VBA reselect items in List Box

I'm working on a DB where the users have to select items from multiple list boxes, at the end of the all of this, the form closes and opens a new form. All of that works great.
My issue is that when the user goes back to the original form, I want it to load in with their list box items already highlighted. Sort of a "here is where you left off" kind of thing.
I have code that works, but it's somewhat limited:
If InStr(me.Backup_Chain, "Apple") > 0 then
Me.List_Type.Selected(0) = true
End if
If InStr(me.Backup_Chain, "Grapes") > 0 then
Me.List_Type.Selected(1) = true
End if
"List_Type" is just a textbox that I populate with the users previous selection. So if they selected Apples and Grapes it reads: 'Apples','Grapes'
This works pretty well, but it's not dynamic. So if I add "Bananas" to the list, Bananas because item number 1 in the list, and thus screws up all of my other references.
So, my question is 2 parts:
Is there an easier way or reselecting the list box items?
If not, is there a way to make the Selected(#) dynamic? i.e. to tell that "Grapes" is in location 1 vs 2.
Thanks everyone!
Edit: In addition to this, I have a run a query to filter one of the list boxes down to a subgroup of clients (based on the other list box selections). Which means I need to refresh the page. So I can't get by with just hiding the form.

VBA - insert objects next to each other

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.

Excel VBA - Pause code execution

I have a complex VBA function and a workbook with multiple sheets.
Let's say the code goes through every row of Sheet1 and does something with that data.
I would like to be able to pause on SourceRow = 16, check out results I'm getting on different sheets and then continue on (press ok, or some key stroke)
I've tried
If SourceRow = 16 Then
MsgBox "ReachedRow 16"
End If
But the Message box is modal and I can not switch to a different sheet(s) to see the data.
P.S. 16 is just as an example, hardcoded for now but will not be later on.
You can use the Stop statement and then press F5 to resume the code execution. This is akin to adding a breakpoint.
If SourceRow = 16 Then
Stop
End If
The row will be highlighted yellow while it is paused and you should be able to navigate work sheets.
Example:
In similar fashion you can use Debug.Assert (sourcerow <> 16) which will pause the code when sourcerow<>16 evaluates to false, that is to say when sourcerow equals 16. (Thanks to RBarryYoung for the comment)
If you don't want a permanent stop in your code, consider adding a breakpoint by clicking the gray region to the left of the editing window in the VB editor, which should will look like this:
The code will stop when the line is reached. Again press F5 to resume code execution. Alternatively you can continually press F8 in order to step through the code line by line. This eliminates the need to set many breakpoints.
Simply click the maroon circle to dismiss the breakpoint. Breakpoints are not saved in your code, so this is a temporary method (as opposed to the two methods listed above which would stay in your VBA code if you save the workbook).
The images were taken from this tutorial from Wise Owl regarding breakpoints.
Right-click on SourceRow (anywhere in your code) and click Add Watch....
In the dialog that appears, enter the following in the Expression textbox:
SourceRow = 16
Under Watch Type, select Break when value is True.
Click OK.
This will automatically cause your program to break/pause when the value is reached and there's no need to add any new code just for debugging/pausing purposes.

Select next record on datasheet subform from separate form?

I have 2 forms. 1 subform.
The main form, MainFormF, has a subform which is hooked up to a query that takes data from a table and outputs it into a datasheet. Lets call this subform MainSubformF.
The 2nd form is loaded from a button on MainFormF. It has 2 buttons, previous and next. How do I attach these buttons to switch the next/previous record HIGHLIGHTED in the datasheet subform?
As you can see, that is what it looks like highlighted. It turns blue, if you didn't know.
The code I currently use but doesn't switch the selected records at all is this:
Private Sub Command65_Click()
On Error GoTo new_Err
Forms!MainformF!MainSubformF.SetFocus 'sets the focus to MainSubformF
DoCmd.RunCommand acCmdRecordsGoToNew
new_Err:
End Sub
For the button to open up the More Info form, it takes information from a text box (lets name it InfoTxt) with this as it's control source:
=[MainSubformF].[Form]![ProjectID]
I am thinking I can do something with that variable, like add +1 to it so when the button is clicked it adds +1 to the ProjectID? I don't think that will work now that I am typing this out, but I'll keep this here in case I am right... though I am thinking further into it and ProjectID's aren't always +1, as in case a record was deleted it may go 1,2,3,5,6 and if you get to 3 then click next it'll do nothing as it'll go to 4 not 5.
I simply don't have time to write out a complete answer, but Dev Ashish is a well-respected developer in the Access community and he devised a way to determine which records have been selected. My guess is you'll need to bookmark them somehow. Start with this Sub that Dev wrote:
Determine selected records in datasheet view