I am currently working on an major recode of a project I created some time ago.
It consists of two VSTO-AddIns, one for Excel and one for Word. These AddIns do have almost the same things to do, but differ in details like the data-object. In Word it is a Document, in Excel Workbooks are used. So I can't just copy and paste the code between the AddIns.
Because of that, I created the same stuff for both AddIns manually. This causes a lot of redundant code in both projects. This is a pain to maintain.
So for this refactoring I favor using a central Project both AddIns access exactly the same way.
This is how I got it working with my prior Word-AddIn as example:
Now I moved the whole Word_PropertyReadWrite into a own Project (a class library) and want to merge it with the Excel_PropertyReadWrite which exists parallel to Word_PropertyReadWrite atm. Before moving, both classes implemented IPropertyReadWrite. I think this will be needed now too, so I keep at it and also moved the interface into the new classlibrary.
This seems to work up until now, I have to create a class which redirects calls for the specific methods to the interface. These still need to exist in both AddIns parallel, but there is way less code to be maintained and the redundancy there is likely to be very hard to be terminated, though it would be nice to get rid of that, too.
But, and this is the problem I am having now, I seem to not being able to get all methods to work in this new class library.
Specific:
I can't use the Globals-Variables I need to open documents/workbooks.
I have this Open-Method, which worked before:
/// <summary>
/// Opens the file at the given path.
/// </summary>
/// <param name="Path">File's full path</param>
/// <returns>Returns success.</returns>
public bool Open(string Path)
{
//Checks, if there is already an file open. This code is not good, I know that,
//but it got the job done. This will get thrown out, since I want to get multithreating
//to work ;)
if (IsInUse)
{
log.Warn("Es ist bereits eine Datei offen und in Bearbeitung");
return false;
}
try
{
Document = Globals.ThisAddIn.Application.Documents.Open(FileName: Path,
ConfirmConversions: false, ReadOnly: false, AddToRecentFiles: false,
Revert: false, Visible: false, OpenAndRepair: true, NoEncodingDialog: false);
IsInUse = true;
Orig_FullPath = Path;
Orig_FileName = System.IO.Path.GetFileName(Path);
Orig_DirectoryPath = System.IO.Path.GetDirectoryName(Path);
log.Debug("Open war erfolgreich");
return true;
}
catch (Exception ex)
{
log.Error(string.Format("Beim Öffnen der Datei ist ein Fehler aufgetreten.\nPfad: {0}",
Path), ex);
return false;
}
}
The specific line to create problems is:
Document = Globals.ThisAddIn.Application.Documents.Open...
The use of Globals is not possible outside of the AddIn's project, since it accesses, in this case, Word itself.
Any attemp of referencing maybe ThisAddIn or Application failed until now.
The only way I see this would fit is by keeping all methods making use of this functionality in the AddIns.
But this would make the code, in my opinion, worse to maintain, because then there will be, again, more redundant code and I would tear maybe 2 or 3 methods out of the cleanly separated AddInFunctions-class. (Here is the new layout of my new class:)
So, is it possible to outsource all of these methods to an class library and if so, how is this achived?
Related
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;
}
}
The .NET Shell extension framework called SharpShell is great; I've developed a right-click file Shell ContextMenu "quite easily" that works selecting both files and directories.
Now I would like to develop a Shell ContextMenu by righ-clicking on an empty space (that is, on the Desktop or on a white spot while I'm inside a folder).
Is it possible do that still using SharpShell? Or do I need to move to a different solution?... and in 2nd case... what do you suggest?
Thanks
The two solutions presented below work, but in the meantime I have found that there is an easier solution that is actually already used in the samples that come with SharpShell.
See the CopyDirectoryLocationHandler class as an example of a context menu handler that is registered for the directory background (and the desktop):
[ComVisible(true)]
[COMServerAssociation(AssociationType.Class, #"Directory\Background")]
public class CopyDirectoryLocationHandler : SharpContextMenu
{
// ...
}
If you want the handler to only handle clicks on the desktop background, use this code instead:
[ComVisible(true)]
[COMServerAssociation(AssociationType.Class, #"DesktopBackground")]
public class CopyDirectoryLocationHandler : SharpContextMenu
{
// ...
}
Old obsolete answer:
You can use SharpShell for this purpose without problem. There are two possible approaches:
Register the Shell Extension to handle the folder background
yourself
or
Modify SharpShell to handle the registration of the
extension for the folder background for you.
Register the Shell Extension to handle the folder background yourself
Your shell extension is a COM server and as such is identified to the system via a GUID. This GUID is then used at places in the registry to register the COM extension for different purposes. When we manually want to register the extension for a purpose such as extending the context menu for folder backgrounds, it is best when our extension has a fixed GUID.
Currently your class looks like this:
[ComVisible(true)]
[COMServerAssociation(AssociationType.Directory)]
public class MyContextMenuExtension : SharpContextMenu
{
When compiling, the compiler will automatically generate a GUID to use for that class. But we can specify a specific one to use like this:
[Guid("A75AFD0D-4A63-41E3-AAAA-AD08A574B8B0")]
[ComVisible(true)]
[COMServerAssociation(AssociationType.Directory)]
public class MyContextMenuExtension : SharpContextMenu
{
Do not use the same GUID as shown here but create your own unique one in Visual Studio via Menu Tools > Create GUID. Use a different GUID for every shell extension you write.
Then recompile the dll and install and register it again (using regasm or the SharpShell Server Manager tool.
Then create a text file named "registry.reg" with the following content (use your own specific GUID). Instead of "MyContextMenuExtension" specify the name of your extension.
Windows Registry Editor Version 5.00
[HKEY_CLASSES_ROOT\Directory\Background\shellex\ContextMenuHandlers\MyContextMenuExtension]
#="{A75AFD0D-4A63-41E3-AAAA-AD08A574B8B0}"
Install the "registry.reg" file by double clicking. Your extension should now be active when you open the context menu for a folder background or the Desktop.
Instead of using the *.reg file, you can also make the changes manually using registry editor or if you have an installer instruct the installer to make those registry changes.
Modify SharpShell to handle the registration of the extension for the folder background for you
Make the following changes to the SharpShell source code:
In the file AssociationType.cs add a new enum value to the AssociationType enumeration:
/// <summary>
/// Create an association to the unknown files class.
/// </summary>
UnknownFiles,
/// <summary>
/// Create an association to the background of folders and the desktop
/// </summary>
DirectoryBackground
In the file ServerRegistrationManager.cs add a new private string constant:
/// <summary>
/// The 'directory' special class.
/// </summary>
private const string SpecialClass_Directory = #"Directory";
/// <summary>
/// The 'directory background' special class.
/// </summary>
private const string SpecialClass_DirectoryBackground = #"Directory\Background";
Also in the file ServerRegistrationManager.cs in the method CreateClassNamesForAssociations in the big switch statement add a new case like this:
case AssociationType.Directory:
// Return the directory class.
return new[] { SpecialClass_Directory };
case AssociationType.DirectoryBackground:
// Return the directory background class.
return new[] { SpecialClass_DirectoryBackground };
Finally you only have to tell your own extension class to use this new enumeration value:
[Guid("A75AFD0D-4A63-41E3-AAAA-AD08A574B8B0")]
[ComVisible(true)]
[COMServerAssociation(AssociationType.Directory)]
[COMServerAssociation(AssociationType.DirectoryBackground)]
public class MyContextMenuExtension : SharpContextMenu
{
I have used SharpShell some time ago, forgotten it since then (because it works flawlessly). I have used it on files and folders, so your question intrigued me. A little research on the tool led me to the answer No(unfortunately).
The binding is done through the com server associations on SharpShell. And by looking at the documentation of the com server associations I am not seeing the way to your desired functionality.
PS: I encourage you to leave a comment on the documentation page, or contact directly with the author of the library. He seems to be really helpful(I've contacted him before).
I am going to create a LicFileLicenseProvider based license provider. I have read many articles, questions and answers regarding this content. Finally, I have created my license provider, MyLicFileLicenseProvider and it has two modes, RunTime and DesignTime. I am going to set license for design time only. I did it and it is working good as well. Therefore, the license will be (and should be) used only during the compiling with existance of the "licenses.licx" file.
A. Normal Case
1) Create a class:
[LicenseProvider(typeof(MyLicFileLicenseProvider))]
public class XXXObject
{
private License license = null;
/// <summary>
/// Creates an instance of the XXXObject object.
/// </summary>
public XXXObject()
{
try
{
license = LicenseManager.Validate(typeof(XXXObject), this);
}
catch (Exception ex)
{
throw new SecurityException("Invalid license!");
}
}
}
2) Then, create a licenses.licx file, content
Object Full Name, Name space, Version, Culture, PublicKeyToken
3) Create a license file, XXX.XXXObject.lic file.
Now, I have created all necessary stuffs and compiled the project. It is compiled and working well.
B. Bi-normal case.
My question is, as I mentioned in above I have decorated the XXXObject class with the attribute [LicenseProvider(typeof(MyLicFileLicenseProvider))].
Then, I exclude the licenses.licx file from the project. Then, compiled the project. However, it is still compiling. This is not make sense, because I decorated
the class with [LicenseProvider(typeof(MyLicFileLicenseProvider))], and it should be looking for the licenses.licx, but it did not and compiled without error?
For a simple prototype, I've to display a workflow file, and show on which step we are actually.
I found a sample here: http://msdn.microsoft.com/en-us/library/ee624139.aspx
Which does almost what I need.
In this sample, all action never wait on another action. So I created myself some very dummy Activity:
public class WaitForNextCall :NativeActivity
{
public const String WaitBookmark = "WaitingStep";
#region Overrides of NativeActivity
/// <summary>
/// When implemented in a derived class, runs the activity’s execution logic.
/// </summary>
/// <param name="context">The execution context in which the activity executes.</param>
protected override void Execute(NativeActivityContext context)
{
context.CreateBookmark(WaitBookmark);
}
protected override bool CanInduceIdle
{
get
{
return true;
}
}
#endregion
}
Which seems to work. Since there I was creating my workflow directly in-code:
return new Sequence()
{
Activities =
{
new WaitForNextCall(){DisplayName = "Step one"},
new WaitForNextCall(){DisplayName = "Step Two"},
new WaitForNextCall(){DisplayName = "Step Three"},
new WaitForNextCall(){DisplayName = "Step Four"}
}
};
The only problem with that is that I only see a "Sequence" element in the workflow designer.
So I created a xaml file which describe exactily the same thing, and I load it like this:
return ActivityXamlServices.Load("Workflows/WorkflowSample.xaml") as DynamicActivity;
But then I got this exception:
System.Xaml.XamlObjectWriterException occurred
Message=Impossible de créer le type inconnu '{clr-MyTestNameSpace.UserInterface.WorkflowItems}WaitForNextCall'.
Source=System.Xaml
LineNumber=0
LinePosition=0
StackTrace:
à System.Xaml.XamlObjectWriter.WriteStartObject(XamlType xamlType)
à System.Xaml.XamlWriter.WriteNode(XamlReader reader)
[...]
I double-checked, the file is present(it appears, that there is another error message when file isn't specified correctly). But it cannot find my "WaitFornextCall" class, which is used just several lines below.
I'm a little desesperate, I'm trying to understand how works Workflow foundations, but it's a little hard for now :(
Any advice will be welcome
Edit:
I tried this to read the file, I don't have any exception in my constructor, but I don't know why, some libraries I'm using(ActiPro) seems to doesn't work anymore after the load:
XamlReader reader = new XamlXmlReader("Workflows/WorkflowSample.xaml", new XamlXmlReaderSettings(){LocalAssembly = System.Reflection.Assembly.GetExecutingAssembly()});
return ActivityXamlServices.Load(reader);
I found the solution !!!!
YEAAAAAAAAAAAAAH :P
In fact the problem is that my Workflow was in the same project than my custom activity. And then visual studio doesn't indicate in which assembly the designer could found this activity, but when you read the file, this is an independant reader, which doesn't know you current assembly.
So having TWO different project, one containing the workflow, one containing activities, and then my workflow specify the assembly in which I can found the chart. Like this I can easily do a WorkflowDesigner.Load("myWorkflowHere.xaml"); without any problem!
"Workflows/WorkflowSample.xaml" probably was built against a prior version of your code.
Delete it, rebuild your solution, then recreate it.
I am trying to properly integrate our app with the Windows 7 Jump Lists. We allow opening files within the application and I added this a while ago to add the items to the jump list:
var list = JumpList.CreateJumpList()
list.AddToRecent(file);
list.Refresh();
where JumpList is from the WindowsAPICodePack
There were two issues with this approach.
Occasionally users would get a ComException on the Refresh() call (Unable to remove the file to be replaced. (Exception from HRESULT: 0x80070497)).
The JumpList would only contain files with the applications file extension.
We allow importing other files in our application via the Open method and I want these files to also show up in the Jump List but they don't.
I searched through the questions regarding JumpLists here on SO and found a different way to add recently used files in this answer:
void AddFileToRecentFilesList(string fileName)
{
SHAddToRecentDocs((uint)ShellAddRecentDocs.SHARD_PATHW, fileName);
}
/// <summary>
/// Native call to add the file to windows' recent file list
/// </summary>
/// <param name="uFlags">Always use (uint)ShellAddRecentDocs.SHARD_PATHW</param>
/// <param name="pv">path to file</param>
[DllImport("shell32.dll")]
public static extern void SHAddToRecentDocs(UInt32 uFlags,
[MarshalAs(UnmanagedType.LPWStr)] String pv);
enum ShellAddRecentDocs
{
SHARD_PIDL = 0x00000001,
SHARD_PATHA = 0x00000002,
SHARD_PATHW = 0x00000003
}
This seemed more appropriate as it is also backwards compatible with XP, Vista - Problem is that the JumpList still only contains files with my associated file extension.
I have two questions:
What is the better way to add items to the Jump List.
How do I get any file to show up on my Jump List, regardless of file extension?
From MSDN:
An application must be a registered
handler for a file type for an item of
that type to appear in its Jump List.
It does not, however, need to be the
default handler for that file type
So you must add register yourself with every filetype you care about, either by adding a verb to the ProgId or possibly just adding your ProgId or exe name to OpenWithProgIds or OpenWithList (HKCR\%.ext%\OpenWithProgIds)
The fact that windows requires this is a bit stupid IMHO, but I guess they need to know how to pass the file path to your app when you click on a jump list item.
SHAddToRecentDocs has more parameter types than you have listed, the docs for SHARDAPPIDINFOLINK does not say if you need to be registered anywhere for it to work so you could try that instead of adding a basic path...