Multiple Ribbons in Outlook Addin - c#

I have an Outlook addin where I need to display a ribbon in the main Outlook window and in the Mail Read window as well. To do this I have added two ribbon xml files with the right markups in them. I then added a C# class that implements the Office.IRibbonExtensibility interface where I have implemented the GetCustomUI method which returns the right XML. Finally I did this in the ThisAddIn.cs class
protected override Office.IRibbonExtensibility CreateRibbonExtensibilityObject()
{
try
{
_ribbon = new Ribbon();
return _ribbon;
}
catch (Exception e)
{
}
return null;
}
So far so good. The ribbons load and everything shows in correct place.
Now the problem is that this Ribbon.cs file is getting rather huge as all the callbacks live in this file. Is there a way to split the callbacks into multiple classes? So if I have a Ribbon1.xml and RIbbon2.xml can I have equivalent Ribbon1.cs and Ribbon2.cs?

OK so as it turns out this is really not possible in the VSTO model. You can really only have one class which must have all the event handlers in it. The recommended approach is to use partial classes and split the code between multiple code files.

Related

Editor Pane in IWpfTextView is not Editable

I am trying to create a Visual Studio extension that will open a document window containing a custom control of mine that hosts an editor pane. I can get the document to load, the correct content type loads with my custom content extensions (classifier, quick info), a window with my custom control showing the editor pane loads, but the text cannot be modified. I can select and highlight text, but no keys work, no commands, and no mouse input other than text selection.
The following is what I have in my Package class:
[PackageRegistration(UseManagedResourcesOnly = true, AllowsBackgroundLoading = true)]
[ProvideEditorExtension(typeof(MyCustomEditorFactory), ".cust")]
[Guid(MyCustomExtensionsPackage.PackageGuidString)]
public sealed class MyCustomExtensionsPackage : AsyncPackage
{
public const string PackageGuidString = "ec2c4646-d0cc-42c6-b0a6-d0ff3e318cef";
#region Package Members
protected override async Task InitializeAsync(CancellationToken cancellationToken, IProgress<ServiceProgressData> progress)
{
await this.JoinableTaskFactory.SwitchToMainThreadAsync(cancellationToken);
RegisterEditorFactory(new MyCustomEditorFactory(this));
}
#endregion
}
I have verified that the RegisterEditorFactory method is called, and that my editor factory is called when files with the .cust extension are opened.
The following is my editor factory:
using Microsoft.VisualStudio;
using Microsoft.VisualStudio.ComponentModelHost;
using Microsoft.VisualStudio.Editor;
using Microsoft.VisualStudio.Shell;
using Microsoft.VisualStudio.Shell.Interop;
using Microsoft.VisualStudio.Text.Editor;
using Microsoft.VisualStudio.TextManager.Interop;
using Microsoft.VisualStudio.Utilities;
using System;
using System.ComponentModel.Composition;
using System.Runtime.InteropServices;
[ComVisible(true)]
[Guid(EditorFactoryGuidString)]
public sealed class MyCustomEditorFactory : IVsEditorFactory
{
public const string EditorFactoryGuidString = "38AD95BA-8891-46A2-A5EA-25F5F36EEAE0";
private MyCustomExtensionsPackage _package;
private Microsoft.VisualStudio.OLE.Interop.IServiceProvider _vsServiceProvider;
[Import]
public IContentTypeRegistryService ContentTypeRegistry { get; set; }
[Import]
public ITextEditorFactoryService TextEditorFactory { get; set; }
public MyCustomEditorFactory(MyCustomExtensionsPackage package)
{
_package = package;
}
public int SetSite(Microsoft.VisualStudio.OLE.Interop.IServiceProvider psp)
{
_vsServiceProvider = psp;
return (VSConstants.S_OK);
}
public int Close()
{
return (VSConstants.S_OK);
}
public int MapLogicalView(ref Guid rguidLogicalView, out string pbstrPhysicalView)
{
pbstrPhysicalView = null;
return (VSConstants.LOGVIEWID_Primary == rguidLogicalView ? VSConstants.S_OK : VSConstants.E_NOTIMPL);
}
public int CreateEditorInstance(uint grfCreateDoc, string pszMkDocument, string pszPhysicalView, IVsHierarchy pvHier, uint itemid, IntPtr punkDocDataExisting, out IntPtr ppunkDocView, out IntPtr ppunkDocData, out string pbstrEditorCaption, out Guid pguidCmdUI, out int pgrfCDW)
{
ThreadHelper.ThrowIfNotOnUIThread();
ppunkDocView = IntPtr.Zero;
ppunkDocData = IntPtr.Zero;
pbstrEditorCaption = string.Empty;
pguidCmdUI = VSConstants.GUID_TextEditorFactory;
pgrfCDW = 0;
int retVal = VSConstants.E_FAIL;
if ((grfCreateDoc & (VSConstants.CEF_OPENFILE | VSConstants.CEF_SILENT)) != 0)
{
IVsTextLines textBuffer = null;
if (punkDocDataExisting == IntPtr.Zero)
{
IComponentModel mef = _package.GetService<SComponentModel, IComponentModel>();
mef.DefaultCompositionService.SatisfyImportsOnce(this);
IVsEditorAdaptersFactoryService eafs = mef.GetService<IVsEditorAdaptersFactoryService>();
textBuffer = eafs.CreateVsTextBufferAdapter(_vsServiceProvider, ContentTypeRegistry.GetContentType("CUST")) as IVsTextLines;
string fileText = System.IO.File.ReadAllText(pszMkDocument);
textBuffer.InitializeContent(fileText, fileText.Length);
string[] roles = new string[]
{
PredefinedTextViewRoles.Analyzable,
PredefinedTextViewRoles.Editable,
PredefinedTextViewRoles.Interactive,
PredefinedTextViewRoles.Document,
PredefinedTextViewRoles.PrimaryDocument
};
IWpfTextView dataView = TextEditorFactory.CreateTextView(eafs.GetDataBuffer(textBuffer), TextEditorFactory.CreateTextViewRoleSet(roles));
dataView.Options.SetOptionValue(DefaultTextViewHostOptions.LineNumberMarginName, true);
dataView.Options.SetOptionValue(DefaultTextViewHostOptions.ShowCaretPositionOptionName, true);
dataView.Options.SetOptionValue(DefaultTextViewHostOptions.ChangeTrackingName, true);
dataView.Options.SetOptionValue(DefaultTextViewOptions.ViewProhibitUserInputName, false);
IWpfTextViewHost wpfHost = TextEditorFactory.CreateTextViewHost(dataView, false);
MyCustomEditor editor = new MyCustomEditor(wpfHost);
ppunkDocData = Marshal.GetIUnknownForObject(textBuffer);
ppunkDocView = Marshal.GetIUnknownForObject(editor);
retVal = VSConstants.S_OK;
}
else
{
//code for document already open
}
else
{
retVal = VSConstants.E_INVALIDARG;
}
}
return (retVal);
}
}
There's a lot to unpack there, but ultimately I'm just
Creating an IVsTextBuffer from an IVsEditorAdaptersFactoryService
Loading the contents of the text buffer
Creating an IWpfTextView with the IVsTextBuffer from a ITextEditorFactoryService
Creating an IWpfTextViewHost with the IWpfTextView from the same ITextEditorFactoryService
Creating my my custom window pane and passing in the IWpfTextViewHost
Returning the IVsTextBuffer as the document data and my custom window pane as the document view
My custom window pane code is simply:
using Microsoft.VisualStudio.Shell;
using Microsoft.VisualStudio.Text.Editor;
using System.Runtime.InteropServices;
[ComVisible(true)]
public sealed class MyCustomEditor : WindowPane
{
private IWpfTextViewHost _wpfHost;
public MyCustomEditor(IWpfTextViewHost wpfHost)
{
_wpfHost = wpfHost;
Content = new MyCustomEditorControl(wpfHost.HostControl);
}
}
I've omitted the code for the MyCustomEditorControl for the sake of brevity, but just assume it's a simple WPF UserControl with a ContentPresenter. The constructor just sets Content property of the ContentPresenter with the wpfHost.HostControl that's passed in.
All of this works insofar as displaying the contents of a file, but I just can't edit anything. It's like the input bindings aren't wired up or enabled, but I can't find what properties to set or enable on which objects, and the documentation is rather poor on this topic. I've gone through the Visual Studio extensions walkthroughs on MSDN, but it seems to be a mish-mash of pre-WPF and post-WPF APIs with no clear guidance on what the authoritative approach is.
Admittedly, I may be naive in assuming that the IWpfTextView would automatically handle user input, but in my defense it's created from an ITextEditorFactoryService and having "TextEditor" in the name carries certain implications.
I'm going to go through a rather exhaustive list of things I've tried and checked to hopefully help narrow down the possibilities or where my mistake is:
After the document window is open, I've checked the Running Document Table and there is an entry for it that is automatically created after CreateEditorInstance exits. It links to the data buffer that's returned, the correct file path, and has an edit lock but no read lock. I've inspected the RDT when C# files are opened in a normal editor window and those have read and write locks. I tried manually setting a read lock on the RDT as well, the counter increments, but that doesn't seem to make a difference.
I've inspected the return result from ITextBuffer.CheckEditAccess on the data buffer and it returns true so the text buffer is reporting it's editable.
The textview roles DO have an effect on the final rendered IWpfTextViewHost. If I add or omit roles, the appearance and functionality changes. For example, adding or removing "ZOOMABLE" adds or removes the dropdown box with the control zoom level. However, "EDITABLE" has no effect on editability.
The editor options DO have an effect. For example, adding or removing the line number margin options does show/hide the line numbers. However, ViewProhibitUserInputName has no effect on editability.
I don't create a code window with CreateVsCodeWindowAdapter because the resulting IWpfTextViewHost that can be retrieved from GetWpfTextViewHost is already parented to another control. If I disconnect it with (IWpfTextViewHost.HostControl.Parent as Border).Child = null, then the contents of IWpfTextViewHost.HostControl gets disposed of for some reason. The IWpfTextViewHost.HostControl loads and it displays a window with margins and scrollbars, but the content is blank and if I click into the left margin of the content pane then Visual Studio throws an ObjectDisposedException. Please note I'm not nulling out the host control itself or its contents, I'm only telling its existing parent it no longer has a child.
I've inspected the editor GUID property of the IVsWindowFrame that opens my document and it's set to the value returned in the pguidCmdUI parameter, which I believe is correct.
I'm not certain if the value being set for the pguidCmdUI parameter in the editor factory CreateEditorInstance is correct. I'm aware it's used for command routing, which might explain why I can't type or use the mouse, but I'm not sure what the correct value should be. I read somewhere it's supposed to be the GUID of the factory that created the editor, so I set it to the text editor factory GUID since that's what created the IWpfTextView. I tried setting it to my editor factory GUID at the top of the file, as well as typeof(IWpfTextView).GUID, Guid.Empty, and assorted other GUIDs, but there's no change.
If I pass my IWpfTextView into IVsEditorAdapterFactoryService.GetViewAdapter, I get back null.
If I create a IVsTextView from IVsEditorAdapterFactoryService.CreateVsTextViewAdapter, I can have both an IVsTextView and an IWpfTextView, but the two don't know about each other and I don't know see any way to map them. I don't even know if they need to be mapped, or if IVsTextView needs to exist or is just the legacy text view interface.
When I inspect the created IVsTextView, it's base type is a SimpleTextViewWindow that is an undocumented class that's internal to Microsoft's Visual Studio implementation, it has WpfTextView and WpfTextViewHost properties, but they're both null (or throw an exception that they can't be read) and are unsettable. Microsoft obviously has some internal voodoo magic to map IVsTextViews to WpfTextViews in their own code, but I don't know what it is.
I'm aware of other code samples that create an IVsInvisibleEditor and get the data buffer from that, but that seems kind of hacky. I mean, it technically works and no one seems to know of any alternative because the documentation in this area is so poor, so I'm not judging or criticizing, it just seems like that shouldn't be necessary. It's my (possibly incorrect) understanding that an invisible editor is a virtual editor that is created for documents that are opened in memory but not hosted in a window, but I'm hosting mine in a window. Additionally all of the working code samples I could find are all copy/pasting from the same Microsoft VSIX code example. In that example, the virtual editor is created in a tool window that's created under the assumption that a document is already open in an existing editor window. So that tool window is creating a virtual editor side-by-side to a physical one for a tool window. If I follow the same invisible editor approach, then I'm effectively creating a virtual editor in memory and then a second physical document editor window. So basically, I'm creating two editors for one document. That doesn't seem right. Again, it technically works, but it seems off.
If the expectation is that we have to implement IOleCommandTarget on our editor window and manually handle every single keystroke, shortcut, and mouse button and manipulate the underlying text buffer directly, then that is going to be the most depressing thing ever. That would mean they tout being able to use WPF, but then give us a document view without a WPF control that natively handles text editing even though they exist in the framework.
Sorry for the ridiculously long post, but I wanted to provide as much information as I possibly could about where I'm at and what I've tried.
Regarding CreateVsCodeWindowAdapter, see the "official" explanation how to make it work: https://developercommunity.visualstudio.com/t/projectionbuffertutorial-gives-error-in-dev16/498617

Revit API: Is SetLayerSelection the best solution?

Our company has hundreds of legacy AutoCAD details to choose from to import into Revit. We are using Revit 2021 and C# (Visual Studio 2019) to access the Revit API. We already have the code to create the drafting views and import the DWG files. What we still need to figure out is how to NOT import certain layers (i.e., Defpoints), and how to change the line graphics (color and/or line weight) on a per layer basis. Normally this is done via the VG Overrides dialog in Revit, but we would like to do it programmatically during the import process. Which of the following is best practice?
Solution #1: Import only “visible” layers. This is our current, and less than ideal, solution and requires turning off layers manually before the import.
DWGImportOptions options = new DWGImportOptions
{
VisibleLayersOnly = true,
};
doc.Import(#detailfilename, options, detailview, out elemId);
Solution #2: Use the SetLayerSelection method under the DWGImportOptions class. This requires that an ICollection object be passed to the method that contains the layers to be imported. How are the layers read from the DWG file into the ICollection object? A snippet of code would be greatly appreciated.
Solution #3: Something else we have not thought of.
Thank you in advance for your assistance and expertise. Snippets of code would be greatly appreciated.
Afaict, the method of choice to generate the lists of layers to import would be to implement an AutoCAD.NET application that reads the existing layers in every DWG file and processes them according to given rules.
Since that requires AutoCAD.NET work anyway, I would go whole hog and implement full preprocessing of the DWG files in that app, so that nothing more than you already have remains to be done in the Revit API.
My personal 2c only, of course.
Thank you Jeremy Tammick. I am curious if the code from your 4/12/2013 blog post could be used. I modified that code as follows, but the Open command throws an exception: "Method not available in MDI mode. Use Open method of Documents collection". Any thoughts? Many thanks.
[TransactionAttribute(TransactionMode.Manual)]
public class OpenDWG : IExternalCommand
{
public Result Execute(ExternalCommandData commandData, ref string message, ElementSet elements)
{
Autodesk.AutoCAD.Interop.AcadApplication app = new Autodesk.AutoCAD.Interop.AcadApplication();
Autodesk.AutoCAD.Interop.AcadDocument adoc = app.Documents.Application.ActiveDocument;
string pfile = "C:\\Testfile.dwg";
adoc.Open(pfile); //this command throws an exception
//add more code here to turn off unwanted layers
adoc.Save();
app.Quit();
return Result.Succeeded;
}
}

Child Form cannot find text file

I have created a boolean algebraic simplifier. It simplifies expressions and I am content with it. However, I am trying to add a feature that allows users to check if two expressions are equivalent. For this I have created a new form that allows the user to input two expression by clicking buttons. To do this, I thought it best to simplify both expressions and then compare the two for equivalency. As I have got lots of subroutines and code that works for simplification in another form, I thought making the form a child form of the form with the code in would allow me to call the subroutines instead of copying them onto the form. I have made these protected in the parent form. I have inherited like so:
public partial class Expression_Equivalency_Form : Expression_Simplifier
However, when I click onto the form designer, this error appears and I cannot view the graphical interface of the form:
"Could not find file File Path"
The file is in the debug folder which is within the bin folder within the folder containing the program and is recongised in the parent class. The file is read from and appeneded by the parent form without issue. I have tried to research this but have been unable to find a solution. Does anyone know one?
I have read to the file and appended to it. I have also used the following code to remove any blank lines from my text file:
File.WriteAllLines("PreviousExpressionInputs.txt",
File.ReadAllLines("PreviousExpressionInputs.txt").Where(l => !string.IsNullOrWhiteSpace(l)));
Code that writes to the file:
using (BinaryWriter Writer = new BinaryWriter(File.Open("PreviousExpressionInputs.txt",
FileMode.Append)))
{
Writer.Write(expressionandanswertowritetotextfile);
}
Code that reads from the file:
foreach (string line in File.ReadLines("PreviousExpressionInputs.txt"))
{
try
{
LinesInFile.Add(line);
}
catch (Exception)
{
}
}
Consider following facts:
When you open a form in design mode, the constructor of its base class will run.
When you look for a relative file name, the path will be resolved relative to the current working directory of the application.
When the form is in design mode, the current application is Visual Studio and its working directory is where the devenv.exe is located.
It describes why you cannot find your text files. Because you have some code in the constructor of your base form(or fir example load event handler of the base form) which looks for the file and since the filename is relative, its looking for the file in the Visual Studio working directory and could not find file.
How to prevent the problem? Check DesignMode property to prevent running the code:
public partial class MyBaseForm : Form
{
public MyBaseForm()
{
InitializeComponent();
}
private void MyBaseForm_Load(object sender, EventArgs e)
{
MessageBox.Show("This will show both in run-time and design time.");
if (!DesignMode)
MessageBox.Show("This will show just in run-time");
}
}
Create the derived form and open it in designer to see what happens:
public partial class Form1 : MyBaseForm
{
public Form1()
{
InitializeComponent();
}
}
To learn more about how designer works take a look at this post.

uwp inheriting from base page giving compile errors

I am trying to inherit from a base page instead of Normal Page control of XAML but Visual Studio is giving me compile time errors that OnNavigatedTo method not found to be overridden.
I am doing this because I have multiple pages with a lot of similar code and I want to write that code just once, so I want to write that code in parent class and then inherit all pages from that parent class, I followed guidelines as stated on multiple resources on internet and below is my code.
Parent Class:
public class VideoParentPage : Page
{
}
Child Class (AllVideoPage.xaml.cs):
public sealed partial class AllVideosPage : VideoParentPage
{
public AllVideosPage() : base()
{
InitializeComponent();
}
protected override void OnNavigatedTo(NavigationEventArgs e)
{
SetBanner();
}
}
XAML in file AllVideosPage.xaml:
<local:VideoParentPage
xmlns:local="using:Fluent_Video_Player.Views.Shared"
...some unrelated XAML code...
</local:VideoParentPage>
Note: I have used all necessary using statement in C# classes.
You should try cleaning the solution Build > Clean Solution, closing VS, removing the remaining bin and obj folders and then compiling the app again.

How to call VSTO class from other c# project

In my solution I have 2 projects.
One is the controller, which in the final product is used to check if a execution is issued from console/non user input and therefore will execute the wanted changes in background based on imput from a xml-file, or if the execution was issued by user input, which will open an interface.
A non user input would maybe a planed task or something like that, but thats is for an other time now and I just write that for some context.
In both cases, sooner or later there is the need to access word-documents and read, write and change document-properties.
To do that I created a VSTO-Word-Addin with the needed functions and up to this point I hardcoded the paths and didn't return the results anywhere else than an other document.
Since I am sure my code in VSTO itself works, I wanted to extend the prototype to the next level and tried adding the connections between console and VSTO.
For testing I am simplifying the process a bit and just try to establish the connection between console and VSTO without any userinput and trying to execute some methods to test functionality of my VSTO.
My approach was to open the console, which then opens Word/the addin, open the file hidden and do the magic.
First thing to do is to set a path for the document to be opened and then call multiple methods with returned values.
In this case my VSTO returns a true for
SetCustomProperty
and a new List of Tuples for
GetCustomProperties
Those are placeholders and will be replaced in developement.
I already tried some possible solutions, but most of them go the other way around to start a WinForms/WPF/Console out of VSTO or try to call an other AddIn from their AddIn.
The approach I had the most success with was this one:
MSDN
Calling Code in VSTO Add-ins from Other Office Solutions
But of course this is for office so I hit the problem of not being able to use
Globals
More info about Globals can be found here in MSDN
So maybe I am missing the point and am just blind, but how can I call a class in a VSTO-project from console?
Here are some codesamples of my current failure:
The class with the used interface I want to access:
[ComVisible(true)]
public interface IPropertyReadWriter
{
bool Open(string Path);
bool SetCustomProperty(String Name, PropertyTypes Type, object Value);
List<Tuple<String, PropertyTypes, object>> GetCustomProperties();
}
[ComVisible(true)]
public class PropertyReaderWriter : IPropertyReadWriter
{
public List<Tuple<string, PropertyTypes, object>> GetCustomProperties()
{
return new List<Tuple<string, PropertyTypes, object>>();
}
public bool Open(string Path)
{
return false;
}
public bool SetCustomProperty(string Name, PropertyTypes Type, object Value)
{
return false;
}
}
The code used in the MSDN article about calling from other office-project:
object addInName = "ExcelImportData";
Office.COMAddIn addIn = Globals.ThisAddIn.Application.COMAddIns.Item(ref addInName);
ExcelImportData.IAddInUtilities utilities = (ExcelImportData.IAddInUtilities)addIn.Object;
utilities.ImportData();
I dont know how to make use of this, because I don't have access to Globals outsite of VSTO?
Somewhat similar question on so with no answer I could use, because lack of context or example:
I don't know what Dan Byström meant with his answer, also Mike Regan's answer lead to prior stated MSDN.
How to call a VSTO AddIn method from a separate C# project?
First, to your Addin that you want to call into add an Interface:
[ComVisible(true)]
public interface IExcelUtilities
{
bool DoSomething();
}
Next, add a class that implements the interface:
[ComVisible(true)]
[ClassInterface(ClassInterfaceType.None)]
public class AddInUtilities :
StandardOleMarshalObject,
IExcelUtilities
{
public bool DoSomething()
{
return true;
}
}
Then override object RequestComAddInAutomationService in ThisAddin.cs:
private AddInUtilities utilities;
protected override object RequestComAddInAutomationService()
{
try
{
if (utilities == null)
{
utilities = new AddInUtilities();
}
return utilities;
}
catch (System.Exception ex)
{
// Catch your ex here
}
}
Now you should be able to call the exposed method from your external application like this:
foreach (COMAddIn comaddin in addins)
{
if (comaddin.ProgId.Equals("YourAddinNameHere", StringComparison.InvariantCultureIgnoreCase) == true)
{
bool returnvalue = comaddin.Object.DoSomething();
break;
}
}
for some more deep info on this subject, also read:
http://blogs.msdn.com/b/andreww/archive/2008/08/11/why-your-comaddin-object-should-derive-from-standardolemarshalobject.aspx
Hope it helps :-)
This isn't an answer exactly, but for others coming across this, document-level solutions cannot expose interfaces to other solutions.
Expose an object in a VSTO Add-in to other Microsoft Office solutions.
VSTO Add-in projects. Call code in VSTO Add-ins from other Office solutions

Categories