RemoveFromSelection(Item) does not stop rules being applied to the item - vba

I want to remove an E-Mail from my selection after a rule is applied, so the next rule doesn't check this E-Mail again.
But after using RemoveFromSelection, it still goes through it in the next loop.
For Each Rule In Rules
For Each Item In Application.ActiveExplorer.Selection
' Testing Rules here and if it fits I want to remove the Item
Application.ActiveExplorer.RemoveFromSelection (Item)
Next Item
Next Rule

Calling the RemoveFromSelection method in the loop is not really a good idea. You don't let the OS/Outlook to remove the item from the section. Here is what the Explorer.RemoveFromSelection method says:
If the specified item is selected, calling RemoveFromSelection will cause the SelectionChange event to fire. If the item is not selected, calling RemoveFromSelection will not cause the SelectionChange event to fire.
So, the UI should be updated to remove the item from the selection. To wait until is excluded I'd suggest using the DoEvents function which yields execution so that the operating system can process other events.

Related

Listbox not displaying in UserForm

I'm trying to display a listbox in a UserForm from a separate sheet called "Fields". The problem is, the list will not display. It shows as a drop down arrow next to the cell, but not in the userform like I'm wanting.
Private Sub UserForm_Activate2()
On Error Resume Next
Me.ListBox2.Clear
For Each element In gFieldsListArr
Me.ListBox2.AddItem element
Next element
UserForm_initialize2
End Sub
Private Sub UserForm_initialize2()
For Each element In Split(gCellCurrVal2, ",")
For ii = 0 To ListBox2.ListCount - 1
If element = Me.ListBox2.List(ii) Then
Me.ListBox2.Selected(ii) = True
End If
Next ii
Next element
End Sub
TL;DR: You can't rename event handlers or change their member signature in any way*, because the correct member definition is defined by the event, not its handlers.
Watch the dropdowns at the top of the editor as you navigate between procedure scopes:
Whenever the left-side dropdown says (General), you're not inside an event handler.
Contrast with:
The left-side dropdown is listing all available interfaces and event sources; to handle the events of a form, you must pick UserForm from that dropdown, and then pick a member from the right-side dropdown.
When you do this, the VBE creates the procedure stubs for you, with the correct name and signature every time.
Whenever you navigate to what's intended to be an event handler and the left-side dropdown says (General), you're looking at dead code that isn't responding to any events.
* You may change the accessibility from Private to Public, but invoking an event handler directly is a design smell so there shouldn't be a need to do that. You may change the parameter names, but not their type; renaming handler parameters is a rather surprising thing to do though, and best avoided too. So yeah, best not change these member signatures in any way.

VBA event handler for checking cells in a table in Word

I have a table in Word:
Table Example
The table is bookmarked in the document, as it can appear in different places in the document. I access the table using the bookmark like this:
Set Tbl = ActiveDocument.Bookmarks("bookmarkname").Range.Tables(1)
I have a script that checks if a cell has a checkmark and displays a Msgbox when it has a checkmark and shouldn’t, based on certain criteria of the Row and Column name.
Here’s the question:
I would like this script to fire by a Cell_OnLeave type of event, so that when user leaves a cell, script will run. Is this possible?
If this is not possible, I would like the script to fire when user leaves the table, and script can check the whole table? Maybe a bookmark_deselected event would work for that? How could this be done?
It is possible to create custom events for table cells in Word.
Since the code is quite long, I'll just link to an example document and a GitHub gist.
I tried to comment the code as much as possible to make it understandable.
The module creates on enter, on change and on exit events for table cells and an event manager to define each event's behavior.
If you don't want to read everything, use the module by modifying the CellEventManager subroutine.
The sub receives the event type and the cell that triggered the event, so you can properly define event responses to your likings.
I also implemented a Cancel functionality for the OnExit event: Set CellEventManager = Fail during the handling of a OnExit event to prevent the selection from leaving the cell.
Only the built-in events can be handled in Word. There are no specific events for either tables or bookmarks. The only event that gets you anywhere close is the WindowSelectionChange event which fires each time the selection is changed, i.e. each time the insertion point is moved.

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

me.control.remove is removing every second control in a loop for some reason

I have some dynamically created checkboxes on my form and I want a function to delete them all.
I've got the following function:
Sub delete_checkboxes()
Dim radios = Controls.OfType(Of RadioButton).AsQueryable()
For Each r As RadioButton In radios
Me.Controls.Remove(r)
Next
End Sub
For some reason the above function only deletes every second radio button and leaves the rest.
Just as a test, I changed the function to delete radio buttons which are ticked:
Dim radios = Controls.OfType(Of RadioButton).AsQueryable()
For Each r As RadioButton In radios
If r.Checked Then
Me.Controls.Remove(r)
End If
Next
With the above I can tick each radio button and it will delete them invididually... so what it is in the first function which could be causing it to skip every second radio button?
Change AsQueryable() to ToList()
The reason it fails is that you are not supposed to modify a iterator while you are still looping over it. AsQueryable() is just using a state machine internally to know your current position in the Me.Controls collection. It doesn't actually keep it's own collection of controls, but just knows which controls you need from your original collection.
When you remove a control in the middle of loop, that position state is now wrong... in fact, it's off by one. You then remove the next control, which puts that internal position state off by one again, and so on. After a whole set of off-by-one adjustments and you end up with half of the controls still on your form.
ToList() will work, because it creates a separate collection for your controls, so that you don't have to mess with that state as your remove them from your Me.Controls collection.
this is usually how I accomplish it.
For Each cont As Control In Me.Controls
If cont.GetType().Name = "RadioButton" Then Me.Controls.Remove(cont)
Next

Removing items from listbox using backgroundworker

I want to delete all the items of a listbox that does NOT contain "mysite" and here's my code that works fine without backgroundworker.
Do Work Event:
Dim relevantSite As Integer = 0
Do Until relevantSite = lstLinks.Items.Count
If lstLinks.Items.Item(relevantSite).ToString.Contains("mysite") Then
relevantSite += 1
Else
bgWorker.ReportProgress(relevantSite)
End If
Loop
ProgressChanged Event:
lstLinks.Items.RemoveAt(CInt(e.ProgressPercentage))
What it does is, it removes alot of items, sometimes all items. I know I'm making some terrible mistake with e and reportProgress thing.
Please explain them to me, I searched various sites but could not understand it...
Rather than directly changing the items in the list you should create a new list in your background worker. This way you can add remove items from the list and return it to the UI once all the processing is completed and rebind the drop down.
I want to delete all the items of a listbox that does NOT contain
"mysite"
Walk the ListBox backwards and delete the offending items as you go. Wrap the process in BeginUpdate() and EndUpdate() so the ListBox only refreshes once when you're all done:
lstLinks.BeginUpdate()
Dim NumItems As Integer = lstLinks.Items.Count - 1
For i As Integer = NumItems To 0 Step -1
If Not lstLinks.Items(i).ToString.Contains("mysite") Then
lstLinks.Items.RemoveAt(i)
End If
Next
lstLinks.EndUpdate()
lstLinks.Refresh()
You are expecting the code to act as if it is synchronized. But multi-threading does not work that way.
Your code in do work will be processing the next record before report progress has completed. In other words the loop will not pause and wait for report progress to complete. This is a problem because when you call out to remove a item from the list, you reuse the index assuming the item is gone. After a few removals, the index passed in will not indicate the correct item. If you were to use an identifier rather than an index it would work. But the whole thing seems wrong to me since you are not doing any heavy lifting in the do work method.