I'm writing a little extension for Visual Studio 2015.
I added a VSPackage to to embed some CustomCommands on the Shortcut Menu you get when you RightClick on a Project or a Folder inside the Solution Explorer.
What I would like to do now is "open the Add New Item dialog and select the one of the templates I've installed with this VSPackage".
This is the Code I use to initialize my commands:
private TemplateCommand(Package package)
{
if (package == null)
throw new ArgumentNullException(nameof(package));
_package = package;
var commandService = ServiceProvider.GetService(typeof(IMenuCommandService)) as OleMenuCommandService;
if (commandService == null)
return;
AddCommand(commandService, CommandId, CreateCustomTemplate);
}
The CreateCustomTemplate callback code is this:(for the moment I simply create a messageBox, just to ensure it works)
private void CreateCustomTemplate(object sender, EventArgs eventArgs)
{
//TODO: code to replace!
var message = string.Format(CultureInfo.CurrentCulture, "Inside {0}.CreateCustomTemplate()", GetType().FullName);
// Show a message box to prove we were here
VsShellUtilities.ShowMessageBox(
ServiceProvider,
message,
"CREATE CustomTemplate",
OLEMSGICON.OLEMSGICON_INFO,
OLEMSGBUTTON.OLEMSGBUTTON_OK,
OLEMSGDEFBUTTON.OLEMSGDEFBUTTON_FIRST);
}
So, to recap, how do I open the Add New Item dialog box and select a specific item template?
As an example, when you try to create a Class or a UserControl right-clicking on a folder in the solution explorer
You obtain something similar to this:
And this is, exactly, what I'm trying to achieve. Obviously I would like to create my own template and not the UserControl.
If you need any clarification feel free to ask.
Thank you in advance for any suggestion
In the end I resolved my problem.
Unfortunately the command File.AddNewItem (or Project.AddNewItem) is not suitable in my case as I want to see the AddNewFile Dialog (and these command simply adds the item to the specified project).
I Found the solution digging the web, exactly here, specifically thanks to the answer of Vladimir.Ilic.
This is the code I use to achieve my goal:
internal sealed class TemplateCommand
{
private const int CustomCommandId = 0x1023;
private static readonly Guid CommandSet = COMMANDSET_GUID;
private readonly Package _package;
// ReSharper disable MemberCanBePrivate.Global
// ReSharper disable UnusedAutoPropertyAccessor.Global
public IServiceProvider ServiceProvider => _package;
public static TemplateCommand Instance { get; set; }
// ReSharper restore UnusedAutoPropertyAccessor.Global
// ReSharper restore MemberCanBePrivate.Global
public static void Initialize(Package package)
{
Instance = new TemplateCommand(package);
}
private TemplateCommand(Package package)
{
if (package == null)
throw new ArgumentNullException(nameof(package));
_package = package;
var commandService = ServiceProvider.GetService(typeof(IMenuCommandService)) as OleMenuCommandService;
if (commandService == null)
return;
AddCommand(commandService, CustomCommandId, CreateCustomCommand);
}
private static void AddCommand(IMenuCommandService commandService, int commandId, EventHandler callback)
{
var command = new CommandID(CommandSet, commandId);
var menuItem = new MenuCommand(callback, command);
commandService.AddCommand(menuItem);
}
private void CreateCustomCommand(object sender, EventArgs eventArgs)
{
AddNewItem("MyCustomCommand");
}
private void AddNewItem(string itemName)
{
var dte = ServiceProvider.GetService(typeof(DTE)) as DTE;
if (dte == null)
return;
int iDontShowAgain;
uint projectItemId;
var strFilter = string.Empty;
var hierarchy = GetCurrentVsHierarchySelection(out projectItemId);
if (hierarchy == null)
return;
var project = ToDteProject(hierarchy);
if (project == null)
return;
var vsProject = ToVsProject(project);
if (vsProject == null)
return;
var addItemDialog = ServiceProvider.GetService(typeof(IVsAddProjectItemDlg)) as IVsAddProjectItemDlg;
if (addItemDialog == null)
return;
const uint uiFlags = (uint)(__VSADDITEMFLAGS.VSADDITEM_AddNewItems | __VSADDITEMFLAGS.VSADDITEM_SuggestTemplateName | __VSADDITEMFLAGS.VSADDITEM_AllowHiddenTreeView);
const string categoryNameInNewFileDialog = "MyCustomTemplates";
// ProjectGuid for C# projects
var projGuid = new Guid("FAE04EC0-301F-11D3-BF4B-00C04F79EFBC");
string projectDirectoryPath;
hierarchy.GetCanonicalName(projectItemId, out projectDirectoryPath);
var itemNameInNewFileDialog = itemName;
addItemDialog.AddProjectItemDlg(projectItemId,
ref projGuid,
vsProject,
uiFlags,
categoryNameInNewFileDialog,
itemNameInNewFileDialog,
ref projectDirectoryPath,
ref strFilter,
out iDontShowAgain);
}
private static IVsHierarchy GetCurrentVsHierarchySelection(out uint projectItemId)
{
IntPtr hierarchyPtr, selectionContainerPtr;
IVsMultiItemSelect mis;
var monitorSelection = (IVsMonitorSelection)Package.GetGlobalService(typeof(SVsShellMonitorSelection));
monitorSelection.GetCurrentSelection(out hierarchyPtr, out projectItemId, out mis, out selectionContainerPtr);
var hierarchy = Marshal.GetTypedObjectForIUnknown(hierarchyPtr, typeof(IVsHierarchy)) as IVsHierarchy;
return hierarchy;
}
private static Project ToDteProject(IVsHierarchy hierarchy)
{
if (hierarchy == null)
throw new ArgumentNullException(nameof(hierarchy));
object prjObject;
if (hierarchy.GetProperty(0xfffffffe, (int)__VSHPROPID.VSHPROPID_ExtObject, out prjObject) == VSConstants.S_OK)
return (Project)prjObject;
throw new ArgumentException("Hierarchy is not a project.");
}
private IVsProject ToVsProject(Project project)
{
if (project == null)
throw new ArgumentNullException(nameof(project));
var vsSln = ServiceProvider.GetService(typeof(IVsSolution)) as IVsSolution;
if (vsSln == null)
throw new ArgumentException("Project is not a VS project.");
IVsHierarchy vsHierarchy;
vsSln.GetProjectOfUniqueName(project.UniqueName, out vsHierarchy);
// ReSharper disable SuspiciousTypeConversion.Global
var vsProject = vsHierarchy as IVsProject;
// ReSharper restore SuspiciousTypeConversion.Global
if (vsProject != null)
return vsProject;
throw new ArgumentException("Project is not a VS project.");
}
}
Big thanks to the ones that passed by and that have tried (or even thinked) to help!
Hope this helps someone,
Sincerely
You can execute the commands "Project.AddNewItem" or "File.AddNewItem" to show the dialog. There are several ways to execute a command programmatically, the easiest is to get the EnvDTE.DTE instance and call dte.ExecuteCommand(commandName).
As for selecting the desired template, see the parameters for the command File.AddNewItem. With some luck the are the same for the Project.AddNewItem command.
Related
want to change project settings programatically there are many settings like this
EnvDTE80.DTE2 dte2 = (EnvDTE80.DTE2)System.Runtime.InteropServices.Marshal.GetActiveObject("VisualStudio.DTE.12.0");
Project project = dte2.Solution.Projects.Item(1);
Configuration configuration = project.ConfigurationManager.ActiveConfiguration;
configuration.Properties.Item("StartAction").Value = VSLangProj.prjStartAction.prjStartActionProgram;
configuration.Properties.Item("StartProgram").Value = "your exe file";
configuration.Properties.Item("StartArguments").Value = "command line arguments";
bu i cant find any option to switch between Debug and Release :
Try (this was written for VS2019: check the GetActiveObject is right for you):
using EnvDTE80;
using System;
namespace SelectConfig
{
class Program
{
static void Main(string[] args)
{
ChangeConfiguration("Release", "Any CPU");
}
private static void ChangeConfiguration(string configurationName, string platformName)
{
DTE2 dte2 = (DTE2)System.Runtime.InteropServices.Marshal.GetActiveObject("VisualStudio.DTE.16.0");
bool found = false;
foreach (SolutionConfiguration2 configuration in dte2.Solution.SolutionBuild.SolutionConfigurations)
{
if (configuration.Name == configurationName && configuration.PlatformName == platformName)
{
configuration.Activate();
found = true;
break;
}
}
if (found)
{
SolutionConfiguration2 finalConfig = (SolutionConfiguration2)dte2.Solution.SolutionBuild.ActiveConfiguration;
Console.WriteLine($"End Configuration: {finalConfig.Name}/{finalConfig.PlatformName}");
}
else
{
Console.WriteLine($"Requested configuration {configurationName}/{platformName} not found");
}
}
}
}
I am creating a VSIX project having TextAdornment features. Here when My CreateGeom() method executes, and try to run new Image{};, It throws exception The calling thread must be STA, because many UI components require this. I tried setting ApartmentState manually but no luck. Following is my code:
[STAThread]
internal async void OnLayoutChanged(object sender, TextViewLayoutChangedEventArgs e)
{
string lang = getCurruntCodeLanguage();
if (lang.ToString() != "java" && lang.ToString() != "ts" && lang.ToString() != "js")
{
}
else
{
try
{
currentSnapshotText = this.view.TextBuffer.CurrentSnapshot.GetText();
this.currentSnapshotLinesList = this.view.TextBuffer.CurrentSnapshot.GetText().Split('\n');
foreach (string str in this.currentSnapshotLinesList.ToList<string>())
{
TextAdornment1.allLinesList.Add(str.Trim('\r'));
}
if (numberOfLinesBeforeEnter != this.currentSnapshotLinesList.Length)
{
boundstart = 0;
boundend = 0;
this.checkCountIfMarked = 1;
this.markthis.Clear();
if (this.image != null)
{
RemoveMarkedArea();
}
if (threadSendText != null)
{
if (threadSendText.IsAlive)
{
threadSendText.Abort();
}
}
var v = System.Threading.Thread.CurrentThread.GetApartmentState(); //returns 'STA'
threadSendText = new System.Threading.Thread(new ThreadStart(SndTextCall)); // Apartment State is 'Unknown'
threadSendText.SetApartmentState(ApartmentState.STA); // Apartment State is 'STA'
threadSendText.IsBackground = true;
threadSendText.Priority = ThreadPriority.Highest;
threadSendText.Start();
}
numberOfLinesBeforeEnter = this.currentSnapshotLinesList.Length;
}
catch (Exception exc)
{
//MessageBox.Show(exc.ToString());
}
}
}
There are other recursive methods also in SndTextCall(). All are working fine but when CreateGeom() method comes into execution, it throws an exception.
private void CreateGeom(SnapshotSpan span)
{
IWpfTextViewLineCollection textViewLines = this.view.TextViewLines;
this.geometry = textViewLines.GetMarkerGeometry(span);
if (this.geometry != null)
{
this.drawing = new GeometryDrawing(this.brush, this.pen, this.geometry);
this.drawing.Freeze();
var drawingImage = new DrawingImage(this.drawing);
drawingImage.Freeze();
image = new Image
{
Source = drawingImage,
}; // Here the exception comes
Canvas.SetLeft(image, this.geometry.Bounds.Left);
Canvas.SetTop(image, this.geometry.Bounds.Top);
this.drawingImageList.Add(image);
}
}
You can explicitly switch to the UI Thread using the VS Threading Rules or the explanations in the Cookbook for Visual Studio.
private void CreateGeom(SnapshotSpan span)
{
ThreadHelper.JoinableTaskFactory.Run(async delegate
{
await ThreadHelper.JoinableTaskFactory.SwitchToMainThreadAsync();
// You're now on the UI thread.
// ... Create your image here
});
}
I use Microsoft SyncFramework 2.1.
I synchronize remote source directory with local destination directory.
So, if framework detect changes - it download changs from source (remote) to destination (local).
But:
DetectChanges always detect changes, but directory not changed.
That remote directory contains 1 file.
So, i write code to synchronize it:
public class SyncService
{
private FileSyncProvider _provider;
private FileSyncOptions _syncOptions;
private FileSyncScopeFilter _filter;
private string _toLocalDirPath;
private string _fromSourceDirectory;
private string _lastFromSourceDirectory; // save last directory (it can be changed);
public SyncService(string localDirPath,string
fromSourceDirectory)
{
_syncOptions = FileSyncOptions.ExplicitDetectChanges |
FileSyncOptions.RecycleDeletedFiles |
FileSyncOptions.RecyclePreviousFileOnUpdates |
FileSyncOptions.RecycleConflictLoserFiles;
_filter = new FileSyncScopeFilter();
_toLocalDirPath=localDirPath;
_fromSourceDirectory=fromSourceDirectory;
}
public void Sync()
{
if (_lastFromSourceDirectory !=Constants.FromSourceDirectory) //if directory path changed - we should dispose old provider and create new
{
if (_provider != null)
{
_provider.DetectedChanges -= Provider_DetectedChanges;
_provider.ApplyingChange -= Provider_ApplyingChange;
_provider.AppliedChange -= Provider_AppliedChange;
_provider.CopyingFile -= Provider_CopyingFile;
_provider.SkippedChange -= Provider_SkippedChange;
_provider.SkippedFileDetect -= Provider_SkippedFileDetect;
_provider.Dispose();
}
}
_provider = new FileSyncProvider(_lastFromSourceDirectory, _filter,
_syncOptions);
_provider.DetectedChanges += Provider_DetectedChanges;
_provider.ApplyingChange += Provider_ApplyingChange;
_provider.AppliedChange += Provider_AppliedChange;
_provider.CopyingFile += Provider_CopyingFile;
_provider.SkippedChange += Provider_SkippedChange;
_provider.SkippedFileDetect +=Provider_SkippedFileDetect;
DetectChangesOnFileSystemReplica();
SyncFileOneWay(_fromSourceDirectory,
_toLocalDirPath,_filter,_syncOptions);
}
private void DetectChangesOnFileSystemReplica()
{
_provider?.DetectChanges();
}
private void SyncFileOneWay(
string sourceRootPath, string desctinationRootPath,
FileSyncScopeFilter filter, FileSyncOptions options)
{
FileSyncProvider sourceProvider = null;
FileSyncProvider destinationProvider = null;
try
{
sourceProvider = new FileSyncProvider(
sourceRootPath, filter, options);
destinationProvider = new FileSyncProvider(
desctinationRootPath, filter, options);
SyncOrchestrator agent = new SyncOrchestrator();
agent.LocalProvider = destinationProvider;
agent.RemoteProvider = sourceProvider;
agent.Direction = SyncDirectionOrder.Download; //
Sync source to destination (download destination to local source)
//agent.Direction = SyncDirectionOrder.Upload;
var sourcePath = sourceProvider.RootDirectoryPath.TrimEnd('\\');
var destinationPath = destinationProvider.RootDirectoryPath.TrimEnd('\\');
agent.Synchronize(); //sync
}
finally
{ // Release resources
if (sourceProvider != null)
{
sourceProvider.Dispose();
}
if (destinationProvider != null)
{
destinationProvider.Dispose();
}
}
}
private void Provider_DetectedChanges(object sender, DetectedChangesEventArgs e)
{
Console.WriteLine($"{nameof(e.TotalFileSize)}:{e.TotalFileSize}");
}
}
So, i run Sync() method every 5 minutes and DetectChanges() says that it detected changes.
And then it syncronize.
So, why method DetectChanges detect changes if i do not change file or directory?
It is remote directory.
I want to synchronize directories only if remote directory really have some changes.
So, I researched this question and realized that after receiving the command for synchronization, the provider synchronizes what is needed and does not transfer the data.
Outlook 2016
.Net Framework 4.5
i encounter a really strange behaviour:
when i iterate through the items collection of a contact folder in some very special undefined cases (which i do not really understand) some userproperties of the first item of the collection fail to load. However the UserProperties are definitly set.
The approach is following:
I open the contact folder (to which the items will be moved) in outlook.
then i execute the "test"
the execution of the test can be suammrized as following:
click button ->
start thread
iterate through the items (on first iteration no items are present).
add new items{
create item
set userproperty PRE before item is initially saved
save item
move item to desired folder
set userproperty POST after item is moved
save item
}
end thread
click button ->
start thread
iterate through the items (here the userproperty POST sometimes fails to load on the first item of the collection, however when i investigate it, it IS there. It only fails for the first item and succeeds for every other following item).
...END
it seems to me that outlook somehow fails to update the userproperty definitions timely. But note that the first BackgroundWorker thread is already finished when iterating through the items with the second backgroundworker thread.
The problem could be related to the fact that iam viewing the folder in the explorer while the items are added and iterated.
This bug is hard to reproduce and does only occur rarely.
however i'm really missing insight into the inner workings of outlook so i can only speculate.
Idea for workarounds:
I could add an item with all userproperties before moving it. the problem here is that i need to add new userproperties, after the item is initially saved and moved to the folder, in some scenarios.
in few cases the userproperty key is dynamically created (with a pattern) so it wouldn't be optimal to predefine all userproperties.
It's very important that the userProperties are reliably loaded because some important features are based upon them.
Does anybody has a clue how the problem is caused and how to solve it? because this behaviour is driving me crazy.
some Code (not the original but it should contain all the relevant aspects)
//Ribbon
TestNS.TestCaller testCaller;
string folderID = "00000000BDB409934ED327439481EB6E1E1CC4D3010055B62301B58E32478DCD8C0D3FA6304600002C4CA4400000";
public void testButton0_Action(Office.IRibbonControl control)
{
if(testCaller == null){
testCaller = new TestNS.TestCaller(ThisAddIn.Outlook,folderID);
}
testCaller.Run();
}
//Ribbon end
using System.Runtime.InteropServices;
using Outlook = Microsoft.Office.Interop.Outlook;
using System.Diagnostics;
using System.Windows.Forms;
using System.ComponentModel;
namespace TestNS
{
public class TestCaller{
private Outlook.Application application;
private BackgroundWorker worker = new BackgroundWorker();
private Test test = null;
private string folderId;
private bool init = true;
private bool busy = false;
public TestCaller(Outlook.Application application, string folderId){
this.application = application;
this.folderId = folderId;
worker.DoWork += new DoWorkEventHandler(DoWork);
worker.RunWorkerCompleted += new RunWorkerCompletedEventHandler(OnCompleted);
}
public void Run()
{
if (!busy)
{
busy = true;
test = new Test(application, folderId, init);
worker.RunWorkerAsync();
}
}
private void DoWork(object sender, DoWorkEventArgs e)
{
test.Process();
test = null;
}
private void OnCompleted(object sender, RunWorkerCompletedEventArgs e)
{
busy = false;
init = false;
}
}
class Test
{
public const string key_preCreateProperty ="preCreate";
public const string key_postCreateProperty = "postCreate";
private Outlook.Application application;
private string folderId;
private bool createData;
public Test(Outlook.Application application,string folderId,bool createData)
{
this.application = application;
this.folderId = folderId;
this.createData = createData;
}
public void Process(){
Examine();
if(createData){
CreateData();
}
}
public void CreateData()
{
List<Poco> pocos = new List<Poco>();
for (int i = 0; i < 10; i++)
{
pocos.Add(
new Poco
{
Pre = "Pre" + i,
Post = "Post" + i
}
);
}
CreateContactItems(folderId,pocos);
}
public void Examine()
{
bool preIsLoaded = false;
bool postIsLoaded = false;
Debug.WriteLine(">>>Examine");
Outlook.MAPIFolder folder = application.Session.GetFolderFromID(folderId);
Outlook.Items folderItems = folder.Items;
int i = 0;
//print UserProperties registered to the items
foreach(Outlook.ContactItem contactItem in folderItems){
var itemUserProperties = contactItem.UserProperties;
string itemUserPropertiesString = "";
foreach (var itemProp in itemUserProperties)
{
Outlook.UserProperty prop = (Outlook.UserProperty)itemProp;
itemUserPropertiesString += " " +prop.Name + " " + prop.Value + " \n";
}
//HERE: sometimes it prints only Pre on the first index of the iteration
Debug.WriteLine(string.Format("i={0} , itemUserProperties Count={1} , following UserProperties: \n{2}", i++, itemUserProperties.Count, itemUserPropertiesString));
string pre = null;
string post = null;
try
{
pre = contactItem.GetUserProperty(key_preCreateProperty);
preIsLoaded = true;
}
catch(KeyNotFoundException ex){
Debug.WriteLine("Error: Pre Not found"); //should not happen - doesn't happen
}
try
{
post = contactItem.GetUserProperty(key_postCreateProperty);
postIsLoaded = true;
}
catch (KeyNotFoundException ex)
{
Debug.WriteLine("Error: Post Not found"); //shoul not happen - happens rarely totally indeterminitic
}
Marshal.ReleaseComObject(itemUserProperties);
}
Debug.WriteLine("<<<Examine");
if (folderItems.Count > 0 && (!preIsLoaded || !postIsLoaded))
{
MessageBox.Show("preIsLoaded="+preIsLoaded +" \n" +"postIsLoaded="+postIsLoaded);
}
Marshal.ReleaseComObject(folderItems);
Marshal.ReleaseComObject(folder);
}
public void CreateContactItems(string folderId,List<Poco> pocos)
{
Outlook.MAPIFolder folder = application.Session.GetFolderFromID(folderId);
foreach(Poco poco in pocos){
CreateContactItem(folder,poco);
}
Marshal.ReleaseComObject(folder);
}
public void CreateContactItem(Outlook.MAPIFolder testFolder,Poco data)
{
Outlook.ContactItem contactItem = application.CreateItem(Outlook.OlItemType.olContactItem);
contactItem.SetUserProperty(key_preCreateProperty, data.Pre);
contactItem.Save();
Outlook.ContactItem movedContactItem = (Outlook.ContactItem)contactItem.Move(testFolder);
Marshal.ReleaseComObject(contactItem);
contactItem = movedContactItem;
contactItem.FirstName = data.Pre;
contactItem.LastName = data.Post;
contactItem.SetUserProperty(key_postCreateProperty, data.Post);
contactItem.Save();
Marshal.ReleaseComObject(contactItem);
}
}
public static class Util
{
public static void SetUserProperty(this Outlook.ContactItem item, string name, dynamic value)
{
Outlook.UserProperty property = item.UserProperties[name];
if (property == null)
{
property = item.UserProperties.Add(name, Outlook.OlUserPropertyType.olText);
}
property.Value = value;
}
public static dynamic GetUserProperty(this Outlook.ContactItem item, string name)
{
Outlook.UserProperty property = item.UserProperties[name];
if (property != null)
{
return property.Value;
}
throw new KeyNotFoundException(string.Format("UserProperty name={0} not found", name));
}
}
public class Poco
{
public string Pre
{
get;
set;
}
public string Post
{
get;
set;
}
}
}
Thank you for any replies
Outlook Object Model cannot be used on a secondary thread within a COM addin. Outlook 2016 will raise an exception as soon as it detects an OOM object being accessed on a secondary thread.
I am trying to implement Adrian Brown's very nice Outlook Add-In code and it works 2 out of 3 times. ItemAdd and ItemChange events are firing as expected, but the event handler for MAPIFolderEvents_12_Event.BeforeItemMove does not appear to be doing anything - I don't even hit a breakpoint on the first line of the event handler.
More Code for Clarity
This is the CalendarMonitor class; it monitors ItemAdd, ItemChange events on the Items collection of the folder, as well as BeforeItemMove on the MAPIFolder:
public class CalendarMonitor
{
private Explorer _explorer;
private List<string> _folderPaths;
private List<MAPIFolder> _calendarFolders;
private List<Items> _calendarItems;
private MAPIFolder _deletedItemsFolder;
public event EventHandler<EventArgs<AppointmentItem>> AppointmentAdded;
public event EventHandler<EventArgs<AppointmentItem>> AppointmentModified;
public event EventHandler<CancelEventArgs<AppointmentItem>> AppointmentDeleting;
public CalendarMonitor(Explorer explorer)
{
_folderPaths = new List<string>();
_calendarFolders = new List<MAPIFolder>();
_calendarItems = new List<Items>();
_explorer = explorer;
_explorer.BeforeFolderSwitch += Explorer_BeforeFolderSwitch;
var session = _explorer.Session;
try
{
_deletedItemsFolder = session.GetDefaultFolder(OlDefaultFolders.olFolderDeletedItems);
HookupDefaultCalendarEvents(session);
}
finally
{
Marshal.ReleaseComObject(session);
session = null;
}
}
private void HookupDefaultCalendarEvents(_NameSpace session)
{
var folder = session.GetDefaultFolder(OlDefaultFolders.olFolderCalendar);
if (folder == null) return;
try
{
HookupCalendarEvents(folder);
}
finally
{
Marshal.ReleaseComObject(folder);
folder = null;
}
}
private void Explorer_BeforeFolderSwitch(object obj, ref bool cancel)
{
var folder = (obj as MAPIFolder);
if (folder == null) return;
try
{
// Hookup events to any other Calendar folder opened.
if (folder.DefaultItemType == OlItemType.olAppointmentItem)
HookupCalendarEvents(folder);
}
finally
{
Marshal.ReleaseComObject(folder);
folder = null;
}
}
private void HookupCalendarEvents(MAPIFolder calendarFolder)
{
if (calendarFolder.DefaultItemType != OlItemType.olAppointmentItem)
{
throw new ArgumentException("The MAPIFolder must use AppointmentItems as the default type.");
}
// Ignore other user's calendars.
if (_folderPaths.Contains(calendarFolder.FolderPath) || (!IsUsersCalendar(calendarFolder))) return;
var items = calendarFolder.Items;
// Store folder path to prevent repeating listeners
_folderPaths.Add(calendarFolder.FolderPath);
// Store a reference to the folder & items to prevent garbage collection
_calendarFolders.Add(calendarFolder);
_calendarItems.Add(items);
// Add listeners
((MAPIFolderEvents_12_Event)calendarFolder).BeforeItemMove += Calendar_BeforeItemMove;
items.ItemChange += CalendarItems_ItemChange;
items.ItemAdd += CalendarItems_ItemAdd;
}
private void CalendarItems_ItemAdd(object obj)
{
var appointment = (obj as AppointmentItem);
if (appointment == null) return;
try
{
if (AppointmentAdded != null)
AppointmentAdded(this, new EventArgs<AppointmentItem>(appointment));
}
finally
{
Marshal.ReleaseComObject(appointment);
appointment = null;
}
}
private void CalendarItems_ItemChange(object obj)
{
var appointment = (obj as AppointmentItem);
if (appointment == null) return;
try
{
if (AppointmentModified != null)
AppointmentModified(this, new EventArgs<AppointmentItem>(appointment));
}
finally
{
Marshal.ReleaseComObject(appointment);
appointment = null;
}
}
private void Calendar_BeforeItemMove(object obj, MAPIFolder moveToFolder, ref bool cancel)
{
if ((moveToFolder != null) && (!IsDeletedItemsFolder(moveToFolder))) return;
var appointment = (obj as AppointmentItem);
if (appointment == null) return;
try
{
if (AppointmentDeleting == null) return;
// Listeners to the AppointmentDeleting event can cancel the move operation if moving
// to the deleted items folder.
var args = new CancelEventArgs<AppointmentItem>(appointment);
AppointmentDeleting(this, args);
cancel = args.Cancel;
}
finally
{
Marshal.ReleaseComObject(appointment);
appointment = null;
}
}
private bool IsUsersCalendar(MAPIFolder folder)
{
// This is based purely on my observations so far - a better way?
return (folder.Store != null);
}
private bool IsDeletedItemsFolder(MAPIFolder folder)
{
return (folder.EntryID == _deletedItemsFolder.EntryID);
}
public AppointmentItem Item { get; set; }
}
New Information:
I have done some additional "troubleshooting" and come up with more information: on a whim, I created a new calendar in Outlook (while debugging) and lo and behold the BeforeItemMove event fires just like I expect it to when deleting an appointment in the new calendar, but it still doesn't work in the original.
If I exit the debug session and restart, neither calendar's event functions as expected, despite working fine earlier. Any new calendar's BeforeItemMove event will work fine, until I close Outlook - then it's back to not responding.
I am hoping that this additional information will provide insight to those wiser than I. Any assistance is greatly appreciated.
calFolder variable must be declared on the global/class level to avoid being released by the Garbage Collector.
Where did you declare the source object? Is it alive when the event should be fired?
Anyway, you may consider developing an inspector wrapper. See Developing an Inspector Wrapper for Outlook 2010 and How to: Implement a Wrapper for Inspectors and Track Item-Level Events in Each Inspector for more information.
The sample project in C# and VB.NET is also available - Outlook 2010: Developing an Inspector Wrapper.