I was wondering if there is any way to catch a 'Project add' event. I know you can do that with the DTE e.g. SolutionEvents.ProjectAdded event. However I am trying to accomplish this without the use of the DTE. I already tried it with the IVsHierarchyEvents, sadly the OnItemAdded and OnItemsAppended methods never get called for added Projects.
Note that I want to catch Projects which are either direct children of the solution or a solution-folder.
SolutionEvents.ProjectAdded is the best way to do this as far as I can tell. Sometimes (not always), but sometimes there aren't more appropriate lower level APIs or interfaces that provide the same or similar functionality found in the EnvDTE automation interfaces. In this particular instance the SolutionEvents ProjectAdded event isn't built upon a lower level API or service, and it's the only event I could find that fits what you're looking for here.
Sincerely,
Related
As the title suggests I want to query all the Startup Projects set in a IVsSolution/IVsHierarchy, also I would love to achieve that without the DTE needed. Therefor DTE2.Solution.SolutionBuild.StartupProjects isn't really a suitable option for me. I have look into the MSDN, but didn't find quite what I needed. I came across the IVsSolutionManager with its get_StartupProject method. Unfortunately it will only return a single startup project. Is this even doable with using the DTE?
It appears, the EnvDTE.Solution.SolutionBuild.StartupProjects is the only way to do this. I did a walk through of the underlying VS IDE code base, and the startup projects are denoted by an internal flag on an internal C++ class tracking all the loaded projects. Unfortunately, neither that particular class or it's m_dwStartupOpt member are directly (or indirectly exposed.
So your best/only option here is to use that StartupProjects property.
Sincerely,
I am not sure the best way to explain this so please leave comments if you do not understand.
Basically, I have a few libraries for various tasks to work with different programs - notification is just one example.
Now, I am building a new program, and I want it to be as lightweight as possible. Whilst I would like to include my notification engine, I do not think many people would actually use its functionality, so, I would rather not include it by default - just as an optional download.
How would I program this?
With unmanaged Dlls and P/Invoke, I can basically wrap the whole lot in a try/catch loop, but I am not sure about the managed version.
So far, the best way I can think of is to check if the DLL file exists upon startup then set a field bool or similar, and every time I would like a notification to be fired, I could do an if/check the bool and fire...
I have seen from the debug window that DLL files are only loaded as they are needed. The program would obviously compile as all components will be visible to the project, but would it run on the end users machine without the DLL?
More importantly, is there a better way of doing this?
I would ideally like to have nothing about notifications in my application and somehow have it so that if the DLL file is downloaded, it adds this functionality externally. It really is not the end of the world to have a few extra bytes calling notification("blabla"); (or similar), but I am thinking a lot further down the line when I have much bigger intentions and just want to know best practices for this sort of thing.
I do not think many people would
actually use its functionality, so, I
would rather not include it by default
- just as an optional download.
Such things are typically described as plugins (or add-ons, or extensions).
Since .NET 4, the standard way to do that is with the Managed Exensibility Framework. It is included in the framework as the System.ComponentModel.Composition assembly and namespace. To get started, it is best to read the MSDN article and the MEF programming guide.
You can use System.Reflection.Assembly and its LoadFile method to dynamically load a DLL. You can then use the methods in Assembly to get Classes, types etc. embedded in the DLL and call them.
If you just check if the .dll exists or load every .dll in a plugin directory you can get what you want.
To your question if the program will run on the user's machine without the dlls already being present - yes , the program would run. As long as you dont do something that needs the runtime to load the classes defined in the dll , it does not matter if the dll is missing from the machine. To the aspect you are looking for regarding loading the dll on demand , I think you are well of using some sort of a configuration and Reflection ( either directly or by some IoC strategy. )
Try to load the plugin at startup.
Instead of checking a boolean all over the place, you can create a delegate field for the notification and initialize it to a no-op function. If loading the plugin succeeds, assign the delegate to the plugin implementation. Then everywhere the event occurs can just call the delegate, without worrying about the fact that the plugin might or might not be available.
Documentation for extending Visual Studio is virtually nonexistent, and I managed to assemble a few lines of functioning code hacked together from a dozen or more obscure sources around the interwebs before crashing into a brick wall.
All I need to do is subscribe to an event that is fired after a snippet is inserted. Sounds easy enough, or so I thought. Almost immediately into my research I stumbled upon the following morsel:
IVsExpansionClient.OnAfterInsertion
That describes perfectly my intention, so this MUST be the answer! After looking around my SDK assemblies for longer than I would like to admit, I finally ended up yanking the assembly (Microsoft.VisualStudio.TextManager.Interop.8.0.dll) out of my GAC so that I could reference it explicitly in my solution.
Now that I have access to the interface, I just need to figure out how to get an instance of it.
IVsExpansionManager
Ah HA! That MUST somehow provide me with a mechanism for obtaining an instance of IVsExpansionClient, right? Well, not exactly. At least not in a way that I can see. I have stitched together the following code:
IVsExpansionManager expansionManager = null; IVsTextManager2
textManager =
(IVsTextManager2)Package.GetGlobalService(typeof(SVsTextManager)); int
manager = textManager.GetExpansionManager(out expansionManager);
Which gives me a IVsExpansionManager COM object, but that is about as far as I can get.
I have taken notice of another potential provider:
IVsExpansionEvents
I thought perhaps like solution events, document events, window events or text events, I might be able to invoke DTE to hook these events, but they appear to be absent.
There are methods that accept IVsExpansionClient as a parameter such as:
IVsExpansion.InsertNamedExpansion
So there simply must be a way to fetch an instance of this object, right? I suppose it's possible to create my own class that implements the IVsExpansionClient interface, but I wouldn't know where to begin with implementing the interface members, and I still wouldn't know how to instantiate it in a meaningful way within my package.
I am grateful to anyone who can point me in a different direction, or provide a working sample that I can adapt for my needs. Thanks for taking the time to read through this, I appreciate your time.
EDIT: I want to receive a notification that a snippet has been inserted into the active document window. Ideally, I would like the snippet itself to be included in the delegate event args, as I have some processing to do on the inserted snippet...and it would be cumbersome to process the entire document, or try to identify the recently inserted snippet without context.
Maybe you want to explain what you actually want to achieve. The documentation of the IVsExpansionClient interface states:
Notes to implementers
This interface is implemented by a VSPackage that supports insertion of code snippets.
I don´t see why one would like to consume an instance of it, because it´s an interface allowing the package to receive notifications/callbacks, if something related to code-snippets is going to happen (it provides nohting else than callback functions).
It states furthermore...
Notes to Callers
This interface is instantiated and passed to the InvokeInsertionUI method in the IVsExpansionManager interface. This interface is also instantiated and passed to the methods in the IVsExpansion interface.
Background
I am converting the TreeViewAdv(TVA) project on SourceForge to vb.net. Thus far I have successfully converted the code, successfully built it, added a reference of the dll to a new project, added the control to the toolbox, added the control to a form, and modified the controls properties. I have also coded functionality into the form prior to building that accepted the Aga.Controls namespace.
The Problem
When I go to debug the application that I have placed the TVA control in, I get the error: ''Aga' is not declared. It may be inaccessible due to its protection level.' on all calls to that namespace. So, I researched this problem on SourceForge and there is a thread here: https://sourceforge.net/p/treeviewadv/discussion/568369/thread/005e61ef/ that discusses this issue. Supposedly somebody figured out what the problem is when you are seeing behavior like this, but failed to share any details of their wisdom. The general issue is that when referencing a dll compiled in 2008 in a 2010 project 'is that VS 2010 requires you have Designers separate from the main assembly.' I tried contacting people there, but there seem to be no real activity on any thread in the forum at all. That leads me to my first question...
The Question(s)
1.) Hoping beyond hope, is there anybody on StackOverflow that has successfully done this for the treeviewadv project specifically? If so, I would really appreciate either a somewhat detailed description of what was done, or a short description with the final resulting code/fix. While I understand this is highly unlikely, I thought I would ask before asking more general questions on 'how to'?
2.) Barring anybody that fits the bill for number 1, is there anybody that has knowledge of this general process and at least enough knowledge of the TVA project and desire to work with me on this endeavor?
2.) Barring 1 and 2, is there anybody that has done this with any project and can either describe the general process in relative detail, and/or point to example code?
3.) Barring 1, 2 and 3, is there a particularly good resource that I can access that outlines how to update a VS2008 project in the manner described above?
Disclaimer
I understand that this process might be too involved to discuss here, so am willing to take the discussion/effort elsewhere if needed. If someone of category 1 or 2 can (answer my question/work with me on this) and you feel the discussion should be taken elsewhere please inform me as to how we can contact each other as there seems to be no formal mechanism on S.O.. I am still interested in posting (or linking) the results here for all to share if an answer can be found.
Here is a bit more info addressing the general issue of a Designer in a different assembly. There are some caveats: first I (we?) are not sure that the core problem as to do with a UI Designer. Given the project appears to be a custom TreeView, it seems likely to be the case, but the term 'Designer' could be used in a more generic fashion for this control. A second caveat is that all I have to go on is the description above and havent seen the code for the control.
That said, I am just finishing a drop in UnDo Manager component (ie it inherits from Component and sits in the form tray). Part of what it needed was a way for the dev to select controls on the form to be subject to UnDo. The layout/construction is this:
Imports Plutonix.UIDesigners
Namespace Plutonix.UnDoMgr
Public Class UndoManager
Inherits Component
Implements ISupportInitialize
Private _TgtControls As New Collection(Of Control)
<EditorAttribute(GetType(UnDoControlCollectionUIEditor), _
GetType(System.Drawing.Design.UITypeEditor))> _
<DesignerSerializationVisibility(DesignerSerializationVisibility.Content)> _
Public Property UnDoTargets() As Collection(Of Control)
Get
Return _TgtControls
End Get
Set(ByVal value As Collection(Of Control))
If value IsNot Nothing Then
_TgtControls = value
Else
_TgtControls.Clear()
End If
End Set
End Property
'...
The <EditorAttribute... decoration specifies that this component uses a special designer called UnDoControlCollectionUIEditor. If the project you are converting does not have this on one or more properties, the issue may not be related to UI designers.
Later, there is the UI editor for the COntrols collection editor. This is a separate class though it is in the same file:
<System.Security.Permissions.PermissionSetAttribute( _
System.Security.Permissions.SecurityAction.Demand, Name:="FullTrust")> _
Public Class UnDoControlCollectionUIEditor
Inherits ControlCollectionUIEditor
Public Sub New()
MyBase.bExcludeForm = True
MyBase.bExcludeSelf = True
' create a list of supported control TYPES
typeList.Add(GetType(TextBox))
'... 9 more lines adding control types to List(of System.Type)
End Sub
End Class
Nearly all the code resides in the base class ControlCollectionUIEditor which is in a different assembly (a DLL). My component though is actually using one defined locally, so as a test, I changed the editor to use to ControlCollectionUIEditor which is the base class in my designer DLL. Labels, panels, GroupBoxes etc dont have/need UnDo capability so my Designer exempts them from showing in the Designer - when I use the base class, they all show up in the designer list as expected.
ALL the standard UI Editors (String Collection Editor and the like) are in NET assemblies, so are defined in one assembly and used in another (yours/ours/the devs). A few years ago, I decided to put several different UIDesigners I had written into a UIDesigner.DLL (ie their own assembly) and they worked fine.
Beyond that, I am confused on some specifics. It sounds like you are trying to use this 2008 assembly (a DLL?) in your conversion. Is that where the designer is? If so, it is already in another assembly from your VS 2010 project, so why is there an issue? Can the whole thing be avoided by also converting whatever is in this 2008 assembly (still not clear on what is in it).
HTH
EDIT
I had a quick look at the source, and it is using at least 1 UIDesigner. TreeViewAdv.Properties.cs defines NodeControlCollectionEditor as a custom controls collection editor for the NodeControls property. The Editor is in NodeControlsCollection.cs. Coincidentally, it is doing EXACTLY what my UnDoManager does: define what control types are valid for a CollectionEditor. Your thing then calls the standard NET CollectionEditor, mine calls the CodeProject DialogForm version. There is also a StringCollectionEditor.cs file but I cant tell if that is a UI Designer or something for the user at runtime.
Since you have the code for these, you should be able to mimic something like what I did above. I would also verify that VS 2010 does indeed have the quirk mentioned. But I am also confused what is in the 2008 assembly. Is there some piece that you dont have the source for? You might also try adding the security attribute to any UI Designers in the project since the CS version does not have them and the msg quoted indicates something about 'protection level'. It seems unlikely to help, but since you are dealing with a quirk, who knows...?
Also, that is a pretty ambitious conversion project!
Cause of Problem Verified
First, I would like to point out that indeed, the issue of losing track of the namespace in the referenced dll was because of the presence of custom UI editor/designers in that dll.
The Fix
The general process of separating Custom Editors/Designers from a 'primary' class library is this:
1.) Find all custom editors/designers in the project. If you are only somewhat familiar with the project, a good way to do that is to Find (Ctrl + F) 'UITypeEditor' in the entire solution. If you are the one who designed it, then you should have no problem.
2.) Delete or comment out the entire custom editors/designers class(es). I prefer commenting out for easy documentation (just in case you need to go back).
3.) Create new project in solution. If you cannot see the solution (i.e. you can only see the project) go to tools-->options-->Projects and Solutions. There you will see a check box that says 'Always show solution'. After revealing the solution, right click and select add-->New Project... Can be named whatever, that will have little to no effect on the code.
4.) Within the new project rename Class1 to whatever is convenient. Transfer all 'using' statements at the top of the files that originally held the custom editor/designer classes. EDIT: Add using statements for any namespaces that would allow you to access the types needed from the primary project. Declare the appropriate namespace for each class. Copy and paste custom classes into correct namespaces (You can, if you want, place ALL of your custom editor/designers in this one file). Change any classes that are declared as 'internal' to 'public' (internal is only the scope of the assembly).
5.) If the new project requires any references, add those now. If your custom editor is editing custom types, you will likely need a reference to the project that defines those types. If those types are defined in your 'primary' assembly this can get a bit tricky as it could cause a circular reference issue. One way to get around this, and probably the right way, is to remove the declaration of those types from your primary assembly and create a new project/assembly just for their declaration. If they are, for some reason, inseparable from your primary assembly, set aside a successful build (dll) previously made of you primary assembly and reference that. This reduces future sustainability of code as those types may chance, but gets the job done now if that is what you want.
6.) After debugging the custom editor/designer project, build it and add that project's build (dll) as reference in the primary project/assembly.
7.) Debug internally, create a new project in solution and add BOTH dlls (Primary and Custom Editor) to the references. Verify controls/properties behave as they are supposed to in both design time AND run time.
8.) Finally, debug externally. Create new solution, reference both dlls, verify functionality. It may seem overkill to debug in both the native solution and externally, but I found many differences in behavior between the environments. Be thorough.
Important Note: I spent a LONG time figuring that both dlls needed to be added. You see, when adding just the primary dll to the test project, it would act as though BOTH were added. I though this was reasonable (& quite dandy) as the primary assembly references the other assembly. However, close and open Visual Studio and it does not work. Long story short add BOTH dlls.
TreeViewAdv Specifics
1.) There were two Custom UIEditors. The first is in NodeControlsCollection.cs called NodeControlCollectionEditor, which inherits the standard .NET CollectionEditor. The only functionality added was the explicit assigning of what kind of controls the editor is allowed to work with. It seems this was largely done as a workaround to allow ALL NodeControl types to be added to the collection (this required the passing of type NodeControl), but get around the fact that passing the NodeControl type causes an error because you cannot instantiate an abstract type. The second is StringCollectionEditor in StringCollectionEditor.cs. This also inherets the standard .NET CollectionEditor and adds a little functionality (Not sure of the purpose).
2 - 4.) Same as the general process.
5.) I currently had to use the latter method (setting aside a dll of Aga.Controls for my custom UIEditor to reference). Later I hope to separate some of the object declarations from the primary assembly to make the solution more reliable.
6 - 8.) The original bug (losing the aga namespace) did not occur when running the testing application inside the same solution (even if different project). Additionally, some fixes that worked externally did not run correctly internally and vise-versa. Thus, my advice for testing in both environments.
Final Request
While both the general and specifics of my question is answered here, Plutonix's help was vital in my coming to the solution. While I am marking this as the answer. I would like if people also upvote Plutonix's answer given the effort that he has put forth in helping me find the answer (in addition to the fact that his answer is also correct if less specific).
EDIT: The process outlined above worked when I was modifying the original TVA C# code. I was even able to reference and successfully use the resulting DLLs in a VB.net project. When I tried to apply the same process to the TVA code line that I had converted into VB.net, it resulted in the same problem that I started with. Everything works until I go to run the application and then it loses sight of the aga namespace.
EDIT SOLUTION: Go to properties (of project losing reference)--> Compile tab --> Advanced Compile Options button. Under target framework, change to ".NET Framework 4" if not so already. If that value is already selected, you are likely looking at a different cause.
I want to create a single dll that is merged with a 3rd party dll. This means end consumers will only have to deal with 1 dll instead of 2.
For augments sake lets say that the 3rd party dll is nLog. How do I deal with cases where the consumer of the merged dll already has NLog as a reference in their project?
Ideally what I would like to be able to do is change NLog namespace within my project to "XyzNLog", meaning that the user wouldn't need to do any aliasing... Any idea how I might do this?
Now I know I can add aliases to my project for NLog so that I have to refer to it as XyzNLog, but I want the same to carry over to consumers of the merged dll so that there is never a conflict.
UPDATE - Solution
http://blog.mattbrailsford.com/2010/12/10/avoiding-dependency-conflicts-using-ilmerge/
Bingo! So by using ILMerge, it becomes
possible to merge the third-party
libraries DLLs in with the Providers
own DLL, meaning we will only have one
DLL to deploy. But that’s not all, we
can actually go one step further, and
tell ILMerge to internalize all
dependencies. What this does it
converts all the third party classes
to be declared as internal, meaning
they can only be used from within the
final DLL. Woo hoo! problem solved =)
Given this the problem where the consumer of my dll could also have NLog goes away... as my referenced NLog shifts to being all internal! This is exactly what I want.
Does anyone have any feedback or thoughts on this?
I agree with Hans, I would strongly suggest releasing with registering the DLLs separately.
Otherwise, you could be in DLL hell which would drive your consumers away.
You could then devise some clever deploy methods to detect if the DLL is already registered, etc.
I have to agree with #Hans Passant (and here's some info about the oft-discussed DLL hell), but since you've asked the question, I'll try to answer it.
You can bundle the third-party DLL as a resource. Please see this question for details.
As far as your other questions, I'd just expose the relevant classes from a third-party DLL under your own namespace, and maybe use extension methods to provide whatever additional functionality you want.
For instance, you can provide access to NLog's Log() method using a static method in your class, say XyzNLog.Logger.Log(), taking care of initialization, and whatever else internally, inside your code (static constructor or whatever else you fancy up).
Since you load the NLog assembly using the method above, you'll be the only one having access to the embedded NLog assembly directly and the user won't be able to access it. Now, you don't get the benefit of having all classes autoexposed from NLog, you still have to expose them manually in this case.
EDIT: Another approach would be to try to use ILMerge with /internalize flag as described here. You may not be able to completely resolve the issue, but look at this article to see if you can avoid the pitfalls the author described. Spoiler alert: it's not all peaches'n'cream on this one either, but it may work, with enough extra effort.