I am developing a VSTO application for PowerPoint using C#. The goal is to export the selected slide of the opened PowerPoint presentation to PNG file on user's computer every 5 seconds.
PowerPoint API provides the following way to export the slide:
(Slide)Application.ActiveWindow.View.Slide.Export("D:/path", "png")
However, every time this method is called, PowerPoint window freezes (maybe deactivates?) for a split second, and because of this any expanded menus get closed (for example, the menu opened by right-clicking on a slide, menu for inserting shapes, etc.)
I am looking for a way to avoid this. Is there a way to fix this issue when using Slide.Export method? Or maybe there are some alternatives to using it?
I tried using custom libraries like Aspose.Slides, and they can fix this issue, but cause an even worse one: they can't access the Presentation object presented by PowerPoint assembly, so in order to use them on your assembly, you would have to save a copy to the computer and open it, which is a bad solution in my case.
Any ideas on how to fix my issue will be very helpful.
Edit: to reproduce the issue, create a VSTO add-in project for PowerPoint and replace ThisAddIn with the following code:
public partial class ThisAddIn
{
public Form form = new Form
{
Opacity = 0.01,
Visible = false,
};
delegate void InvokeEventHandler();
private void ThisAddIn_Startup(object sender, System.EventArgs e)
{
form.Show();
var del = new InvokeEventHandler(() => Timer_Tick());
form.Invoke(del);
var timer = new System.Timers.Timer();
timer.Interval = 5000;
timer.Elapsed += (s, ea) => form.Invoke(del);
timer.Start();
}
private void Timer_Tick()
{
try
{
var slide = (Slide)Application.ActiveWindow.View.Slide;
slide.Export(Path.Combine(Path.GetTempPath(), "test"), "png");
}
catch
{
return;
}
}
private void ThisAddIn_Shutdown(object sender, System.EventArgs e)
{
}
#region VSTO generated code
/// <summary>
/// Required method for Designer support - do not modify
/// the contents of this method with the code editor.
/// </summary>
private void InternalStartup()
{
this.Startup += new System.EventHandler(ThisAddIn_Startup);
this.Shutdown += new System.EventHandler(ThisAddIn_Shutdown);
}
#endregion
}
When PowerPoint opens, right-click on a slide and wait for a couple of seconds. When timer ticks, the menu will be closed.
The specified graphics format must have an export filter registered in the Windows registry. You can specify either the registered extension or the registered filter name. Microsoft PowerPoint will first search for a matching extension in the registry. If no extension that matches the specified string is found, PowerPoint will look for a filter name that matches.
Try using the JPG file format instead:
With Application.ActivePresentation.Slides(3)
.Export "c:\my documents\Graphic Format\" & _
"Slide 3 of Annual Sales", "JPG"
End With
Related
I'm writing Outlook VSTO add-in and I want to do size check before attachment add and if the file is too big I want to upload it to the cloud, so the code looks like:
public partial class ThisAddIn
{
private void ThisAddIn_Startup(object sender, EventArgs e)
{
Application.Inspectors.NewInspector += InspectorsOnNewInspector;
}
private void InspectorsOnNewInspector(Inspector inspector)
{
if (inspector.CurrentItem is MailItem mailItem)
{
mailItem.BeforeAttachmentAdd += MailItemOnBeforeAttachmentAdd;
}
}
private void MailItemOnBeforeAttachmentAdd(Attachment attachment, ref bool cancel)
{
// check and upload
cancel = true;
}
private void ThisAddIn_Shutdown(object sender, EventArgs e)
{
// Note: Outlook no longer raises this event. If you have code that
// must run when Outlook shuts down, see https://go.microsoft.com/fwlink/?LinkId=506785
}
#region VSTO generated code
/// <summary>
/// Required method for Designer support - do not modify
/// the contents of this method with the code editor.
/// </summary>
[SuppressMessage("ReSharper", "ArrangeThisQualifier")]
[SuppressMessage("ReSharper", "RedundantDelegateCreation")]
[SuppressMessage("ReSharper", "RedundantNameQualifier")]
private void InternalStartup()
{
this.Startup += new System.EventHandler(ThisAddIn_Startup);
this.Shutdown += new System.EventHandler(ThisAddIn_Shutdown);
}
#endregion
}
The problem is that all works fine until the file exceeds the size limit configured in MS Exchange. When it happens I get a notification message and after clicking "OK" mailItem.BeforeAttachmentAdd event doesn't fire. How can I deal with it?
None of your event handlers will work for more than a few seconds - you are setting the event handler either on a temporary variable (created by the compiler) in case of Application.Inspectors.NewInspector or on a local variable (when you set mailItem.BeforeAttachmentAdd event handler).
The object raising the events must be alive - store these objects on the global (class) level to make sure they are not collected by the Garbage Collector.
Also, there is no particular / guaranteed order of events, but I would imagine Outlook would always get the first pick. Worst case, you can patch IDropTarget implementation of the Outlook window and provide your own implementation. Not much you can do if an attachment is being inserted from the Ribbon...
I am writing a VSTO C# code that would copy the content of active projects in Visual Basic editor in any Office application and process it following some rules. The AddIn adds a button to the VBE toolbar and an event for a click.
When debugging, it works fine the first couple of times (between 8 and 12) I click the button but then it just stops responding to the clicks. Restarting the application does not help, but repeat debugging does (again, for the first 8 - 12 clicks).
I experimented with the code and found that it behaves this way whenever the code uses Regex, even something rather basic. Any ideas what may be going wrong?
This piece of code is for the illustration only. All it is supposed to do is to produce a message box "OK" after it applies Regex to each code module content. And so it does - for the first 8 to 12 times. After that, the button clicks produce no response whatsoever.
No error messages are shown, except when it is building the solution, among many lines it shows Exception thrown: 'System.Deployment.Application.DeploymentException' in System.Deployment.dll, which does not seem to affect the program during the first clicks.
using Office = Microsoft.Office.Core;
using VBA = Microsoft.Vbe.Interop;
using System.Text.RegularExpressions;
namespace CodeControl
{
public partial class ThisAddIn
{
private void ThisAddIn_Startup(object sender, System.EventArgs e)
{
AddButton();
}
public void AddButton()
{
Office.CommandBar cbr = Globals.ThisAddIn.Application.VBE.CommandBars["Edit"];
Office.CommandBarButton button = cbr.Controls.Add(Type: 1, Temporary: true).Control;
button.FaceId = 10;
button.Visible = true;
Globals.ThisAddIn.Application.VBE.Events.CommandBarEvents[button].Click +=
new VBA._dispCommandBarControlEvents_ClickEventHandler(DoWork);
}
void DoWork(object CommandBarControl, ref bool handled, ref bool CancelDefault)
{
foreach (VBA.VBComponent component in Globals.ThisAddIn.Application.VBE.ActiveVBProject.VBComponents)
{
VBA.CodeModule module = component.CodeModule;
if (module.CountOfLines < 4)
continue;
string text = module.Lines[1, module.CountOfLines];
foreach (string lineText in Regex.Split(text, #"(?:\r\n|\n|\r)"))
Console.WriteLine(lineText);
System.Windows.Forms.MessageBox.Show(module.Name);
}
}
private void ThisAddIn_Shutdown(object sender, System.EventArgs e){}
#region VSTO generated code
private void InternalStartup()
{
this.Startup += new System.EventHandler(ThisAddIn_Startup);
this.Shutdown += new System.EventHandler(ThisAddIn_Shutdown);
}
#endregion
}
}
I've added a CustomTaskPane to Excel 2013 that lets users quickly search for photos. It works well and fine if the user only opens/creates one workbook. Problem is if they open another workbook or create a new one, the task pane doesn't appear in the new window that appears. It simply stays put in the original window. I know this behavior is caused by the fact I was only initializing the panel upon opening Excel. I added an event handler to the ActiveWindow event to initialize a new panel when they open another workbook.
Problem is I cannot figure out how to tell if the CustomTaskPane is already present in a window already. If it is, it simple creates another CustomTaskPane, so there are now two within that window. I wrote the following code to dispose the original and create a new one, but it introduces some lag (1-5 seconds) that would drive users crazy every time they change workbook windows. Is there a way to see if a CustomTaskPane already exists in a window to avoid disposing and recreating a new one to avoid stacking duplicate task panes?
Microsoft.Office.Tools.CustomTaskPane PartPhotoTaskPane;
Globals.ThisAddIn.Application.WindowActivate += Application_WindowActivate;
void Application_WindowActivate(Excel.Workbook Wb, Excel.Window Wn)
{
if (PartPhotoTaskPane != null)
{
PartPhotoTaskPane.Dispose();
InitalizePartPhotoViewerTaskPane(EPPF);
}
else
{
InitalizePartPhotoViewerTaskPane(EPPF);
}
}
/// <summary>
/// Start up the part photo viewer task pane
/// </summary>
private void InitalizePartPhotoViewerTaskPane(ExcelPartPhotoFunctions _EPPF)
{
//intialize the part search
try
{
PartPhotoTaskPane = Globals.ThisAddIn.CustomTaskPanes.Add(new PartPhotoSearchPane(_EPPF), "Part Information", Globals.ThisAddIn.Application.ActiveWindow);
PartPhotoTaskPane.Visible = Properties.Settings.Default.InfoPaneOpenStatus;
PartPhotoTaskPane.Width = 260;
}
catch (Exception e)
{
MessageBox.Show("Error starting Part Info Toolbar:" + Environment.NewLine +
e.Message + Environment.NewLine + e.StackTrace, "Error!", MessageBoxButtons.OK,
MessageBoxIcon.Error);
}
}
Use the hwnd (Globals.ThisAddIn.Application.Hwnd) to identify the Excel window. This will work well both for Office2013 (which uses an SDI approach) and older versions of Office that use MDI windows. Here is a class you can use for this:
public class TaskPaneManager
{
static Dictionary<string, CustomTaskPane> _createdPanes = new Dictionary<string, CustomTaskPane>();
/// <summary>
/// Gets the taskpane by name (if exists for current excel window then returns existing instance, otherwise uses taskPaneCreatorFunc to create one).
/// </summary>
/// <param name="taskPaneId">Some string to identify the taskpane</param>
/// <param name="taskPaneTitle">Display title of the taskpane</param>
/// <param name="taskPaneCreatorFunc">The function that will construct the taskpane if one does not already exist in the current Excel window.</param>
public static CustomTaskPane GetTaskPane(string taskPaneId, string taskPaneTitle, Func<UserControl> taskPaneCreatorFunc)
{
string key = string.Format("{0}({1})", taskPaneId, Globals.ThisAddIn.Application.Hwnd);
if (!_createdPanes.ContainsKey(key))
{
var pane = Globals.ThisAddIn.CustomTaskPanes.Add(taskPaneCreatorFunc(), taskPaneTitle);
_createdPanes[key] = pane;
}
return _createdPanes[key];
}
}
Here I'm actually combining the Excel window hwnd and some arbitrary string identifier to identify the taskpane. The idea is to support multiple taskpanes in the same addin.
Here is an example for how to use it from the ribbon:
private void button1_Click(object sender, RibbonControlEventArgs e)
{
var taskpane = TaskPaneManager.GetTaskPane("A", "Task pane type A", () => new UserControl1());
taskpane.Visible = !taskpane.Visible;
}
private void button2_Click(object sender, RibbonControlEventArgs e)
{
var taskpane = TaskPaneManager.GetTaskPane("B", "Task pane type B", () => new UserControl2());
taskpane.Visible = !taskpane.Visible;
}
If you open multiple workbooks in Excel, both Excel window will have their own taspaneA and taskpaneB.
Below is my code (simplified version for readability) from a VSTO-based Word addin.
The problem is that if I have two documents open, such as documentation and a template, my addin helps develop the template and works fine until the template is closed and re-opened in the same Word instance (the documentation file kept Word alive). Once that happens the SelectionChange event is not received, even though the listener is attached (confirmed with debugger).
Is there anything wrong with this code? Any other ways to attach a selection changed event?
void Application_DocumentOpen(Word.Document Doc)
{
// this method gets called as intended
Document vstoDoc = Globals.Factory.GetVstoObject(doc);
vstoDoc.SelectionChange += new Microsoft.Office.Tools.Word.SelectionEventHandler(ThisDocument_SelectionChange);
}
private void Application_DocumentBeforeClose(Word.Document doc, ref bool Cancel)
{
// this one also gets called as intended
Document vstoDoc = Globals.Factory.GetVstoObject(doc);
vstoDoc.SelectionChange -= new Microsoft.Office.Tools.Word.SelectionEventHandler(ThisDocument_SelectionChange);
}
void ThisDocument_SelectionChange(object sender, SelectionEventArgs e)
{
// this doesn't get called if the document is closed and open again within the same Word instance
Log("Selection changed");
}
UPDATE: This seems like VSTO bug.
Attaching to other events works fine, I can use ContentControlOnEnter/Exit:
vstoDoc.SelectionChange += ThisDocument_SelectionChange; // doesn't work
vstoDoc.ContentControlOnEnter += vstoDoc_ContentControlOnEnter; // works
vstoDoc.ContentControlOnExit += vstoDoc_ContentControlOnExit; // works
Why dont you use
Globals.ThisAddIn.Application.WindowSelectionChange +=
new ApplicationEvents4_WindowSelectionChangeEventHandler(Application_WindowSelectionChange);
instead of converting your Microsoft.Office.Interop.Word.Document object to Microsoft.Office.Tools.Word.Document
I wrote a Visio 2010 Add-In(in C#) which processes many Visio documents. Within a Ribbon1.button1_Click the method I invoke a new thread(processVisioFiles) which processes all vsd files in a big loop (which takes over 40min).
public partial class Ribbon1
{
private ThisAddIn exportAddIn;
...
...
public Thread StartTheThread(DirectoryInfo startFolder, bool someVal)
{
var t = new Thread(() => exportAddIn.processVisioFiles(startFolder, false));
t.SetApartmentState(ApartmentState.STA);
t.Name = "processVisioFiles";
t.IsBackground = true;
t.Start();
return t;
}
private void button1_Click(object sender, RibbonControlEventArgs e)
{
//exportAddIn.processVisioFiles(startFolder, false);
StartTheThread(startFolder, false);
}
}
Every time before I programmatically close a Visio vsd document I do document.Save() and then document.Close(). Nevertheless, they are many documents (not all but always the same) which prompts me to manually save them.
I need to say, that I haven't the problem with manually save the documents when I start the big loop within one thread with the UI
private void button1_Click(object sender, RibbonControlEventArgs e)
{
exportAddIn.processVisioFiles(startFolder, false);
StartTheThread(startFolder, false);
}
but then Visio UI is freezing("Application is not responding" message) and after a certain time, Visio aborts the loop through all files.
I tested to save with document.SaveAs(...) and document.SaveAsEx(...) and
if(document.Saved == false)
{
document.Save();
}
document.Close();
but the result is the same. Every time the same Visio files aren't saved and remain unsaved and the UI prompts the user to save or discard manually the changes.
How can I programmatically save and close without prompting the user to do this manually?
p.s. Besides (when I'm in debug-mode) I waited a certain time after document.Save() and then step to document.Close() line, but the result is the same.
Try adding DoEvents before .Save()
System.Windows.Forms.Application.DoEvents();
document.Save()