I have created a PowerPoint VSTO Addin with a custom Task pane - and a ribbon where a toggle button defines the display / hide Status of the custom Task pane. Basis for this was the Microsoft Walkthrough information for custom Task pane and synchronizing the Ribbon with the Task pane.
So fare everything works fine with the first PowerPoint window. I'm able to show the Task pane in the second and third PowerPoint window, but the toggle button on the ribbon only reacts to the last opened / created PowerPoint window and not to the Task pane displayed / hidded in the active PowerPoint window.
I've found another thread which explains exactly the same Problem here:
C# VSTO-Powerpoint-TaskPanes in separate windows.
But I don't understand the answer neither I don't know how to implement a PowerPoint Inspector Wrapper.
I'm new in C# and just getting a keyword like "Inspector Wrapper" is to less for me. I already spend hours in searching the net but wasn't successfull till now.
Is there a chance to get a COMPLETE code example for PowerPoint how this works, what has to be done?
Code added:
I took the code from the General walkthrough: https://msdn.microsoft.com/en-us/library/bb608590.aspx and changed it with an Event for new presentations:
The code for the ThisAddIn.cs is as follow:
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Xml.Linq;
using PowerPoint = Microsoft.Office.Interop.PowerPoint;
using Office = Microsoft.Office.Core;
namespace PowerPointAddIn1
{
public partial class ThisAddIn
{
private TaskPaneControl taskPaneControl1;
private Microsoft.Office.Tools.CustomTaskPane taskPaneValue;
private void ThisAddIn_Startup(object sender, System.EventArgs e)
{
this.Application.AfterNewPresentation += new Microsoft.Office.Interop.PowerPoint.EApplication_AfterNewPresentationEventHandler(NewPresentation);
//taskPaneControl1 = new TaskPaneControl();
//taskPaneValue = this.CustomTaskPanes.Add( taskPaneControl1, "MyCustomTaskPane");
//taskPaneValue.VisibleChanged += new EventHandler(taskPaneValue_VisibleChanged);
}
void NewPresentation(Microsoft.Office.Interop.PowerPoint.Presentation oPres)
{
PowerPoint.Application app = this.Application;
PowerPoint.DocumentWindow docWin = null;
foreach (PowerPoint.DocumentWindow win in Globals.ThisAddIn.Application.Windows)
{
if (win.Presentation.Name == app.ActivePresentation.Name)
{
docWin = win;
}
}
this.taskPaneControl1 = new TaskPaneControl();
this.taskPaneValue = this.CustomTaskPanes.Add(taskPaneControl1, "MyCustomTaskPane", docWin);
this.taskPaneValue.VisibleChanged += new EventHandler(taskPaneValue_VisibleChanged);
}
private void taskPaneValue_VisibleChanged(object sender, System.EventArgs e)
{
Globals.Ribbons.ManageTaskPaneRibbon.toggleButton1.Checked =
taskPaneValue.Visible;
}
public Microsoft.Office.Tools.CustomTaskPane TaskPane
{
get
{
return taskPaneValue;
}
}
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
}
}
I remember the learning curve oh so well. Here's a sample that I believe addresses your issue. You need to link the task pane to the document. I relied on the naming scheme for new documents here, but a DocumentVariable would be a much better choice (they are discarded at the end of the current session). Add a variable to the presentation, store the task pane id in the control, and compare them to get the right task pane.
You need an XML ribbon (could probably use a Ribbon Designer but those are not as good). I removed some of the boilerplate and irrelevant code from this.
ThisAddIn.cs:
namespace PowerPointAddIn1
{
public partial class ThisAddIn
{
public static int counter = 0;
private void ThisAddIn_Startup(object sender, System.EventArgs e)
{
this.Application.AfterNewPresentation += Application_AfterNewPresentation;
}
private void Application_AfterNewPresentation(PowerPoint.Presentation Pres)
{
int count = ++counter;
UserControl1 uc = new UserControl1("task pane " + count);
CustomTaskPane ctp = CustomTaskPanes.Add(uc, "custom task pane " + count);
ctp.Visible = true;
}
private void ThisAddIn_Shutdown(object sender, System.EventArgs e)
{
}
protected override Microsoft.Office.Core.IRibbonExtensibility CreateRibbonExtensibilityObject()
{
return new Ribbon1();
}
}
}
Ribbon1.cs:
namespace PowerPointAddIn1
{
[ComVisible(true)]
public class Ribbon1 : Office.IRibbonExtensibility
{
private Office.IRibbonUI ribbon;
public Ribbon1()
{
}
public void toggleTaskPane(Office.IRibbonControl control, bool enabled)
{
var CTPs = Globals.ThisAddIn.CustomTaskPanes;
var pres = Globals.ThisAddIn.Application.ActivePresentation;
foreach (var x in CTPs)
{
if (pres.Name.EndsWith(x.Title.Replace("custom task pane ", "")))
{
x.Visible = enabled;
}
}
}
public bool isPressed(Office.IRibbonControl control)
{
var CTPs = Globals.ThisAddIn.CustomTaskPanes;
var pres = Globals.ThisAddIn.Application.ActivePresentation;
foreach (var x in CTPs)
{
if (pres.Name.EndsWith(x.Title.Replace("custom task pane ", "")))
{
return x.Visible;
}
}
return false;
}
}
}
Ribbon1.xml:
<?xml version="1.0" encoding="UTF-8"?>
<customUI xmlns="http://schemas.microsoft.com/office/2009/07/customui" onLoad="Ribbon_Load">
<ribbon>
<tabs>
<tab idMso="TabAddIns">
<group id="MyGroup"
label="My Group">
<checkBox id="mycheckbox" label="show task pane" onAction="toggleTaskPane" getPressed="isPressed" />
</group>
</tab>
</tabs>
</ribbon>
</customUI>
UsreControl1.cs (just has a label on it):
namespace PowerPointAddIn1
{
public partial class UserControl1 : UserControl
{
public UserControl1(string labelValue)
{
InitializeComponent();
label1.Text = labelValue;
}
}
}
I just want to share my results which works now for me (Thanks to Chris who gave me some valuable inputs). I do have a customtaskpane management which works for each presentation. The only Thing which is not yet implemented is if a user opens the document in a separate window (View / New Window). This one I don't know how to manage.
As fare as I can test it this works now.
here is the link to the whole solution:
https://happypc-my.sharepoint.com/personal/roger_heckly_happy-pc_ch/_layouts/15/guestaccess.aspx?docid=0426d40dc5df74d66ba42a3b928111ce8&authkey=Aa6yX6QWUnqXp1jcUfGveL8
Please be Aware - I'm beginner - so if you have feedback / inputs please let me know. For sure, some code could be written easier etc.
Related
I have a requirement to intercept the sending of a new email. All I want to do initially is ask the user "Are you sure you want to send?" and then to either proceed with the sending or cancel it depending on their response.
I found this code snippet which looks perfect for my needs but couldn't get it to work in a Win Forms test application either in VB.Net or after trying to convert it to C#. It then occurred to me that the code may only work in a VSTO Add-in (Is this correct?).
So I then used this Walkthrough to create a VSTO Add-in in C# and made sure that it worked as described, which it does (it pumps some text into the Subject and Body of a new message).
I have tried to add the first example which is in VB.Net into the working C# example but I'm a novice and don't know enough about VSTO or C# to see where I'm going wrong.
The code compiles without errors but when run, Outlook takes a long time loading the Add-in and displays a dialogue stating:
An add-in could not be found or loaded
and then:
Could not create an instance of startup object
PromptToFile_Plug_in.ThisAddIn in assembly PromptToFile Plug-in,
Version=1.0.0.0, Culture=neutral, PublicKeyToken=null.
Where am I going wrong?
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Xml.Linq;
using System.Diagnostics;
using Outlook = Microsoft.Office.Interop.Outlook;
using Office = Microsoft.Office.Core;
using System.Windows.Forms;
namespace PromptToFile_Plug_in
{
public partial class ThisAddIn
{
Outlook.Inspectors inspectors;
Outlook.Application myOlApp = new Outlook.Application();
private void ThisAddIn_Startup(object sender, System.EventArgs e)
{
inspectors = this.Application.Inspectors;
inspectors.NewInspector +=
new Microsoft.Office.Interop.Outlook.InspectorsEvents_NewInspectorEventHandler(Inspectors_NewInspector);
}
private void ThisAddIn_Shutdown(object sender, System.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
}
void Inspectors_NewInspector(Microsoft.Office.Interop.Outlook.Inspector Inspector)
{
Outlook.MailItem mailItem = Inspector.CurrentItem as Outlook.MailItem;
if (mailItem != null)
{
if (mailItem.EntryID == null)
{
mailItem.Subject = "This text was added by using code";
mailItem.Body = "This text was added by using code";
}
}
}
private void Initialize_handler()
{
myOlApp = this.Application;
}
private void myOlApp_ItemSend(object Item, bool Cancel)
{
string prompt;
// prompt = "Are you sure you want to send " + Item.Subject + "?";
prompt = "Are you sure you want to send?";
MessageBox.Show(prompt, "Prompt to File", MessageBoxButtons.OKCancel);
//if (MessageBox.(prompt, Constants.vbYesNo + Constants.vbQuestion, "Sample") == Constants.vbNo)
// Cancel = true;
}
#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
}
}
First of all, there is no need to create a new Outlook Application instance in the add-in class:
Outlook.Application myOlApp = new Outlook.Application();
Instead, use the Application property available in your add-in class.
Second, there is no need to keep the following method because it will never be called:
private void Initialize_handler()
{
myOlApp = this.Application;
}
Unlike VBA, you can't subscribe to the events just declaring the source objects with keywords. Instead, you need to subscribe to the events in the code like you did for the NewInspector event. For example, the following code can be used for handling the ItemSend event in VSTO add-ins:
private void ThisAddIn_Startup(object sender, System.EventArgs e)
{
this.Application.ItemSend += new Microsoft.Office.Interop.Outlook.ApplicationEvents_11_ItemSendEventHandler(Application_ItemSend);
}
void Application_ItemSend(object Item, ref bool Cancel)
{
if (Item is Outlook.MailItem)
{
Outlook.MailItem mail = (Outlook.MailItem)Item;
// your code goes here
}
}
Finally, pay attention to the ref attribute for the Cancel parameter in the event handler signature.
Background
I'm currently working on an application in VSTO2015 and Excel 2016. The application manages a number of CustomTaskPanes in different windows. I am trying to get // some code to fire when the task pane is opened or closed. In order to handle the various windows, I've implemented a structure very similar to this;
CustomTaskPane in Excel doesn't appear in new Workbooks
ThisAddIn.cs contains the following class;
public class TaskPaneManager
{
static Dictionary<string, Microsoft.Office.Tools.CustomTaskPane> _createdPanes = new Dictionary<string, Microsoft.Office.Tools.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 Microsoft.Office.Tools.CustomTaskPane GetTaskPane(string taskPaneId, string taskPaneTitle, Func<UserControl> taskPaneCreatorFunc)
{
string key = string.Format("{0}({1})", taskPaneId, Globals.ThisAddIn.Application.Hwnd);
string title = taskPaneId;
string windowId = Globals.ThisAddIn.Application.Hwnd.ToString();
if (!_createdPanes.ContainsKey(key))
{
var customTaskPane = taskPaneCreatorFunc();
var pane = Globals.ThisAddIn.CustomTaskPanes.Add(customTaskPane, taskPaneTitle);
_createdPanes[key] = pane;
//
// Set the task pane width as set in the App.Config
//
pane.Width = Convert.ToInt32(ConfigurationManager.AppSettings["TaskPaneWidth"]);
}
return _createdPanes[key];
}
....
My calls from Ribbon.cs;
private void btnUploadWizard_Click(object sender, RibbonControlEventArgs e)
{
// Check for configuration sheet first.
string title = "Upload Wizard";
TaskPaneManager.isConfigurationCreated();
var UploadWizardTaskpane = TaskPaneManager.GetTaskPane(title, title, () => new TaskPaneUploadWizard());
UploadWizardTaskpane.Visible = !UploadWizardTaskpane.Visible;
}
The Problem: Event Handlers
I'm having difficulty getting an event handler to fire. I can't tell what I'm doing wrong. In the TaskPaneDesigner I am attaching the event using this.VisibleChanged += new System.EventHandler(this.TaskPaneUploadWizard_VisibleChanged);, and then defining it in my TaskPaneUploadWizard class as follows;
public partial class TaskPaneUploadWizard : UserControl
{
...
public TaskPaneUploadWizard()
{
InitializeComponent();
}
private void TaskPaneUploadWizard_VisibleChanged(object sender, EventArgs e)
{
// Some code
}
My thoughts
It seems to me as though I am either attaching the eventHandler to something other than the CustomTaskPane object, or I am attaching it before the CustomTaskPane is created.
Help!
To detect if the task pane was opened or closed, you have to attach to the VisibleChanged event of pane.
So the simplest solution would be to add just one line of code to the GetTaskPane method:
var pane = Globals.ThisAddIn.CustomTaskPanes.Add(customTaskPane, taskPaneTitle);
// This is the new line to be added.
pane.VisibleChanged += (s, e) => customTaskPane.Visible = pane.Visible;
_createdPanes[key] = pane;
Now the visibility of the whole task pane will be passed on to its content and // some code should be executed.
Alternatively, if you don't want to manually set customTaskPane.Visible for whatever reason, you could as well execute your code directly in this new event handler:
pane.VisibleChanged += (s, e) => { /* some code */ };
But personally I would rather recommend the first approach because it seems to fit a bit better into your existing code.
var UploadWizardTaskpane = TaskPaneManager.GetTaskPane(title, title, () => new TaskPaneUploadWizard());
This is creating a CustomTaskPane not a TaskPaneUploadWizard object
It seems like this.VisibleChanged += new System.EventHandler(this.TaskPaneUploadWizard_VisibleChanged); is acting on TaskPaneUploadWizard which is only the guest of CustomTaskPane
Note that The visibility of CustomTaskPane doesn't affect TaskPaneUploadWizard
My suggestion
You remove VisibleChanged from the designer, you will add it manually in your TaskPaneUploadWizard
public partial class TaskPaneUploadWizard : UserControl
{
//{---}
CustomTaskPane _HostPane;
public CustomTaskPane HostPane
{
get => _HostPane;
set
{
if(_HostPane == value)
return;
_HostPane?.VisibleChanged -= TaskPaneUploadWizard_VisibleChanged;
_HostPane = value;
_HostPane?.VisibleChanged += TaskPaneUploadWizard_VisibleChanged;
}
}
//{---}
private void TaskPaneUploadWizard_VisibleChanged(object sender, EventArgs e)
{
// Some code
}
//{---}
Then in GetTaskPane you say
//{---}
var pane = Globals.ThisAddIn.CustomTaskPanes.Add(customTaskPane, taskPaneTitle);
(customTaskPane as TaskPaneUploadWizard).HostPane = pane;
//{---}
I do the tutorial here. Everything works fine with a blank excel page
https://msdn.microsoft.com/en-us/library/bb608590(v=vs.120).aspx
When I load up a excel sheet someone gave me and go to click the toggleButton1 to show the pane I get
{"The taskpane has been deleted or is otherwise no longer valid."}
on the line
private void toggleButton1_Click(object sender, RibbonControlEventArgs e)
{
Globals.ThisAddIn.TaskPane.Visible = ((RibbonToggleButton)sender).Checked;
}
Is a pointer to that task pane somehow going away?
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);
}
}
ATTEMPT 1:
Still error on the click event. I am guessing it is something related to me needing to share that pane between classes?
private void toggleButton1_Click(object sender, RibbonControlEventArgs e)
{
Globals.ThisAddIn.TaskPane.Visible = ((RibbonToggleButton)sender).Checked;
}
private void ExcelEvents_WorkbookActivate(Excel.Workbook wb)
{
var taskPane = (customTaskPanes.Where(kvp => kvp.Key.Target == wb).Select(kvp => kvp.Value).FirstOrDefault());
if (taskPane == null)
{
UcCenlarInvest control = new UcCenlarInvest();
taskPane = this.CustomTaskPanes.Add(control, "My pane for workbook " + wb.Name);
customTaskPanes[new WeakReference(wb)] = taskPane;
}
}
ATTEMPT 2:
So now the Ribbon class can get the TaskPane yet I Still get the same error. Added this :
private CustomTaskPane taskPane;
public CustomTaskPane TaskPane
{
get
{
//return (customTaskPanes.Where(kvp => kvp.Key.Target == wb).Select(kvp => kvp.Value).FirstOrDefault());
return pane;
}
set
{
taskPane = value;
}
}
.....
TaskPane = (customTaskPanes.Where(kvp => kvp.Key.Target == wb).Select(kvp => kvp.Value).FirstOrDefault());
Excel 2016 is a single document interface (SDI), each workbook in a single instance of Excel contains its own ribbon UI. more information
If you open a new workbook, a new ribbon appears, however the pointer to the taskpane is lost. You should implement a WorkbookActivate event and add a new task pane if no task pane already exist for it. You should also keep a static list of pointer to the custom taskpanes.
see this solution : CustomTaskPane in Excel doesn't appear in new Workbooks
You can also work around this by checking the taskpane control's IsDisposed property, for example
if (taskPane.CustomTaskPane?.Control.IsDisposed == false)
{
taskPane.CustomTaskPane.Visible = false;
}
I have a C# VSTO application with .NET 4.0 that uses two custom CommandBarButtons in the Globals.ThisAddIn.Application.CommandBars["Cell"] context menu. I create the buttons once on the ThisAddIn_Startup event and they work wonderfully for all workbooks. If my add in is shut down at some point without shutting down Excel then an issue occurs if there's more than 1 open workbook. Only the active workbook's right click context menu has the buttons deleted.
The code:
public partial class ThisAddIn
{
private const string TAG_PASTE_EVENT = "PASTE EVENT";
private const string TAG_COPY_EVENT = "COPY EVENT";
private Office.CommandBarButton copy_event, paste_event;
private void ThisAddIn_Startup(object sender, System.EventArgs e)
{
try
{
DefineShortcutMenu();
/*
* Other start up stuff that works fine
*/
}
catch (Exception ex)
{
ExceptionHandler.HandleException(ex);
}
}
private void ThisAddIn_Shutdown(object sender, System.EventArgs e)
{
/*
* Other shut down stuff that works fine
*/
// Remove the right click menu buttons
foreach (Office.CommandBarControl control in Application.CommandBars["Cell"].Controls)
{
if (control.Tag.Equals(TAG_PASTE_EVENT))
{
control.Delete(true);
}
else if (control.Tag.Equals(TAG_COPY_EVENT))
{
control.Delete(true);
}
}
}
private void copy_event_Click(Office.CommandBarButton Ctrl, ref bool CancelDefault)
{
// Copy code
}
private void paste_event_Click(Office.CommandBarButton Ctrl, ref bool CancelDefault)
{
// Paste code
}
private void DefineShortcutMenu()
{
// Create and add the paste button
paste_event = (Office.CommandBarButton)Application.CommandBars["Cell"].Controls.Add(Office.MsoControlType.msoControlButton, missing, missing, 1, true);
paste_event.Style = Office.MsoButtonStyle.msoButtonCaption;
paste_event.Caption = "Paste Event";
paste_event.Tag = TAG_PASTE_EVENT;
paste_event.DescriptionText = "Stuff happens";
paste_event.Enabled = false;
paste_event.Click += paste_event_Click;
// Create and add the copy button
copy_event = (Office.CommandBarButton)Application.CommandBars["Cell"].Controls.Add(Office.MsoControlType.msoControlButton, missing, missing, 1, true);
copy_event.Style = Office.MsoButtonStyle.msoButtonCaption;
copy_event.Caption = "Copy Event";
copy_event.Tag = TAG_COPY_EVENT;
copy_event.DescriptionText = "Stuff happens";
copy_event.Enabled = false;
copy_event.Click += copy_event_Click;
}
#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
}
I'm using Excel 2013 which I know has one bug related to the right click context menu (which is why I'm using a foreach for the CommandBarControls instead of using the global variables). Any workflows that you've found that work would be much appreciated!
TO CLARIFY: Everything works fine, the only issue is CommandBarButtons not deleting from the right click context menus of the non-active workbooks if the Add In is shut down. If the Add In is turned back on during the same session all the workbooks are given the Copy and Paste buttons again meaning the workbooks whose context menus didn't update properly now have 2 Copy buttons and 2 Paste buttons.
Command Bars were deprecated with Office 2010. You need to use the Fluent UI controls instead.
See Customizing Context Menus in Office 2010 for more information.
Is there any way to query the state of an IE plugin (Adobe Reader)? I have a situation where we present many multi-page reports to users through an embedded browser (the preferred pattern based on my research) hosted by a tab page. When the user navigates away from the report, usually to modify the data, they return to re-initialized Adobe Reader. This means their place was lost and expanded bookmark nodes have been collapsed.
Below is a simplified code snippet which I hope full expresses the nature of my problem.
Public partial class ReportView : UserControl
{
private System.Windows.Forms.WebBrowser webBrowser;
private MyNamespace.ReportGenerator reportGen;
private String currentPDFtempPath;
public ReportView()
{
InitializeComponent();
this.Leave += (o, e) => { /*how can I save current place in pdf?*/ };
this.Enter += (o, e) => { /*return user last place in large pdf*/ };
}
public void ViewReport(string reportName)
{
currentPDFtempPath = reportGen.GetReport(reportName);
webBrowser.Navigate(currentPDFtempPath);
}
private void RefreshReport()
{
webBrowser.Navigate(currentPDFtempPath); /*reinitializes Adobe Reader*/
}
}
public class ReportController
{
private DataModel model;
private ReportView view;
ReportController(DataModel m, ReportView v)
{
this.model = m;
this.view = v;
model.Changed += (o, e) => { view.RefreshReport(); }
}
}
If your user base has standardized to one version of Acrobat or continuously updates to the latest and greatest Acrobat, an alternate solution is to eliminate the webbrowser control and add a reference to the AcroPDF/AxAcroPDF library/active x control, which works from a windows form. I have used this in my company for the past 6 years and it has worked flawlessly.