Intercepting standard "Save All" Visual Studio command - c#

I am writing the functionality of a deprecated VS Addin into a Package extension for VS 2015.
In the package extension there is a tool window with a toolbar containing some buttons which execute commands. Amongst these, there is a 'Save All' button, which should not only save the files of the solution to the local disk, but also save the files back to the database to which the user is connected. This works fine at present. However, the user will probably click on the standard Visual Studio 'Save All' button, and expect the files to be written to the database as well. So I need to intercept the standard 'Save All' command and add my method that handles the saving of the files to the database.
I managed to do this, but it is just not as neat as I think it should be. I'm not knowledgeable regarding events and delegates. I read up about it, and then I don't use it often enough, and then I forget what I read and so on.
So the functionality for the 'Save All' toolbar button in the package extension is found as follows in the Package.cs file. In the Initialize method:
protected override void Initialize()
{
OleMenuCommandService mcs = GetService(typeof(IMenuCommandService)) as OleMenuCommandService;
if (null != mcs)
{
CommandID saveAllCommandID = new CommandID(new Guid(Guids.guidConnectCommandPackageCmdSet), (int)PkgCmdIDList.cmdidSaveAllCommand);
command = new OleMenuCommand(new EventHandler(SaveAllCommandCallback), saveAllCommandID);
command.BeforeQueryStatus += BeforeQueryStatusCallback;
mcs.AddCommand(command);
}
visualStudioInstance = (DTE2)this.ServiceProvider.GetService(typeof(DTE));
CreateEventHandlers(visualStudioInstance);
}
and here is the SaveAllCommandCallback:
private void SaveAllCommandCallback(object caller, EventArgs args)
{
SaveAllDocuments();
OutputCommandString("In SaveAll Command Callback");
}
SaveAllDocuments is the method responsible for saving the files to the database. I don't think it is necessary to post that.
My attempt to intercept the standard Visual Studio "Save All" button's command is as follows:
In the Initialize method above, there is a method CreateEventHandlers:
private void CreateEventHandlers(DTE2 visualStudioInstance)
{
Events2 evs = visualStudioInstance.Events as Events2;
Commands cmds = visualStudioInstance.Commands;
Command cmdobj = cmds.Item("File.SaveAll", 0);
saveAllCommandEvents = evs.get_CommandEvents(cmdobj.Guid, cmdobj.ID);
saveAllCommandEvents.BeforeExecute += new _dispCommandEvents_BeforeExecuteEventHandler(m_SaveAllCommand_BeforeExecute);
}
And then here is m_SaveAllCommand_BeforeExecute:
private void m_SaveAllCommand_BeforeExecute(string Guid, int ID, object CustomIn, object CustomOut, ref bool CancelDefault)
{
SaveAllDocuments();
CancelDefault = true;
}
As you can see, it calls SaveAllDocuments and sets CancelDefault to true, which I think signals to the caller that it has interfered with the command and not only been a listener. Should it be set to true?
Anyways, my main question is, how do I link saveAllCommandEvents.BeforeExecute to the SaveAllCommandCallback? I think it would be better to have SaveAllDocuments called from only one place, in SaveAllCommandCallback, but I don't know how to link the callback to the event of the standard Visual Studio "Save All" button.
I guess this should be simple if one is familiar with events and delegates, but I'm not. I just have a feeling that what I'm currently doing could be improved.

I recommend not to use CancelDefault = true; in m_SaveAllCommand_BeforeExecute and just call SaveTheFilesBackToTheDatabase() from it.

Related

c# customizing controls on a save dialog -- how to disable parent folder button?

I am working from the sample project here: http://www.codeproject.com/Articles/8086/Extending-the-save-file-dialog-class-in-NET
I have hidden the address/location bar at the top and made other modifications but I can't for the life of me manage to disable the button that lets you go up to the parent folder. Ist is in the ToolbarWindow32 class which is the problem. This is what I have at the moment but it is not working:
int parentFolderWindow = GetDlgItem(parent, 0x440);
//Doesn't work
//ShowWindow((IntPtr)parentFolderWindow, SW_HIDE);
//40961 gathered from Spy++ watching messages when clicking on the control
// doesn't work
//SendMessage(parentFolderWindow, TB_ENABLEBUTTON, 40961, 0);
// doesn't work
//SendMessage(parentFolderWindow, TB_SETSTATE, 40961, 0);
//Comes back as '{static}', am I working with the wrong control maybe?
GetClassName((IntPtr)parentFolderWindow, lpClassName, (int)nLength);
Alternatively, if they do use the parent folder button and go where I don't want them to, I'm able to look at the new directory they land in, is there a way I can force the navigation to go back?
Edit: Added screenshot
//Comes back as '{static}', am I working with the wrong control maybe?
You know you are using the wrong control, you expected to see "ToolbarWindow32" back. A very significant problem, a common one for Codeproject.com code, is that this code cannot work anymore as posted. Windows has changed too much since 2004. Vista was the first version since then that added a completely new set of shell dialogs, they are based on IFileDialog. Much improved over its predecessor, in particular customizing the dialog is a lot cleaner through the IFileDialogCustomize interface. Not actually what you want to do, and customizations do not include tinkering with the navigation bar.
The IFileDialogEvents interface delivers events, the one you are looking for is the OnFolderChanging event. Designed to stop the user from navigating away from the current folder, the thing you really want to do.
While this looks good on paper, I should caution you about actually trying to use these interfaces. A common problem with anything related to the Windows shell is that they only made it easy to use from C++. The COM interfaces are the "unfriendly" kind, interfaces based on IUnknown without a type library you can use the easily add a reference to your C# or VB.NET project. Microsoft published the "Vista bridge" to make these interfaces usable from C# as well, it looks like this. Yes, yuck. Double yuck when you discover you have to do this twice, this only works on later Windows versions and there's a strong hint that you are trying to do this on XP (judging from the control ID you found).
This is simply not something you want to have to support. Since the alternative is so simple, use the supported .NET FileOk event instead. A Winforms example:
private void SaveButton_Click(object sender, EventArgs e) {
string requiredDir = Environment.GetFolderPath(Environment.SpecialFolder.MyDocuments);
using (var dlg = new SaveFileDialog()) {
dlg.InitialDirectory = requiredDir;
dlg.FileOk += (s, cea) => {
string selectedDir = System.IO.Path.GetDirectoryName(dlg.FileName);
if (string.Compare(requiredDir, selectedDir, StringComparison.OrdinalIgnoreCase) != 0) {
string msg = string.Format("Sorry, you cannot save to this directory.\r\nPlease select '{0}' instead", requiredDir);
MessageBox.Show(msg, "Invalid folder selection");
cea.Cancel = true;
}
};
if (dlg.ShowDialog() == DialogResult.OK) {
// etc...
}
}
}
I don't this is going to work. Even if you disable the button they can type ..\ and click save and it will take them up one level. You can't exactly disable the file name text box and maintain the functionality of the dialog.
You'd be better off either using the FolderBrowserDialog and setting it's RootFolder property and asking the user to type the filename in or auto generating it.
If the folder you are wanting to restrict the users to isn't an Environment.SpecialFolder Then you'll need to do some work to make the call to SHBrowseForFolder Manually using ILCreateFromPath to get a PIDLIST_ABSOLUTE for your path to pass to the BROWSEINFO.pidlRoot
You can reflect FolderBrowserDialog.RunDialog to see how to make that call.
Since you want such custom behaviors instead of developing low level code (that is likely yo break in the next versions of windows) you can try to develop your file picker form.
Basically it is a simple treeview + list view. Microsoft has a walk-through .
It will take you half a day but once you have your custom form you can define all behaviors you need without tricks and limits.

Check if the current document in Visual Studio 2012 is a Code Window

I am looking for a way to have my extension check if the currently opened window in visual studio 2012 is one, where the user can write code (or any kind of text, really).
To check if the currently opened window has changed, I use
_DTE.Events.WindowEvents.WindowActivated.
This gives me the EnvDTE.Window that received the focus.
When I look at the properties of that window while debugging, and I look at EnvDTE.Window.Document.Type and it's value is "Text".
However, if I stop debugging and try to access the Document.Type property, it does not exist.
If I look for this property in the documentation of EnvDTE.Window.Document, its description says
Infrastructure. Microsoft Internal Use Only.
So now I am looking for any advice on how I could check if the currently active window is one, where I can write code (or anything else), or some other kind of document (like the solution properties for example).
Edit:
I also tried checking Window.Type and Window.Kind of the active window, but they just tell me that it's a document, not making any differentiation between a resource file, an image file or an actual source file, which is what I'm trying to find out.
Edit²:
The reason why I want to check if the current document is one where I can write code in, is because I want my extension to store information about some of those documents and I want to modify the right-click context menu based on the information I have stored, if any.
It is not a "real" answer, but you can follow status of VS GoTo command - it is available only for text editors:
bool isCodeWindow = IsCommandAvailable("Edit.GoTo");
private bool IsCommandAvailable(string commandName)
{
EnvDTE80.Commands2 commands = dte.Commands as EnvDTE80.Commands2;
if (commands == null)
return false;
EnvDTE.Command command = commands.Item(commandName, 0);
if (command == null)
return false;
return command.IsAvailable;
}
You can check to see if the document is a 'TextDocument'
bool isCodeWindow = dte.CurrentDocument.Object() is EnvDTE.TextDocument;

Building C# GUI without using Visual Studio GUI designer (Toolbox)

In Java Swing, we can create GUI with only coding Java (for example in Eclipse). Using NetBeans's toolbox to drag and drop components to the UI is optional.
I am wondering if there is the same concept in C#. Can I put my components into my GUI and add them behaviors with only coding? That way I would feel that I have more control over my application.
Example: I do not want to g to toolbox to add "mousehover" to my button! Instead, I want to write the code myself. I know where I can find the code, but is it the only place that I should write that line of code?
Please compare Java Swing with C# in this.
Regarding C# :
In order for you to run a C# application from a cmd, following steps are needed :
Go to C:\Windows\Microsoft.NET\Framework\v4.0.30319 location on your File System and copy the path.
Now right click Computer go to Properties.
Under System Properties, select Advanced Tab, and click on Environment Variables.
On Environment Variables under User Variables, select New.
For Variable Name write CSHARP_HOME or something else, though I am using the same for further needs to explain this out. For Variable Value simply Paste what you copied in Step 1. Click OK.
Again perform Step 4, if path variable does not exist, else you can simply select path and then click Edit to perform this next thingy (after putting ;(semi-colon) at the end of the Variable Value and write %CSHARP_HOME%\(or use what you used in Step 5) ). This time for Variable Name write path, and for Variable Value use %CSHARP_HOME%\ and click OK.
Open cmd and type csc and press ENTER, you might be able to see something like this as an output
Now consider I am creating a directory structure for my CSharp Project like this (on File System) at this location C:\Mine\csharp\command. Here I created two folders inside command folder. source and build.
Now from any Text Editor create a small sample program (I am using Notepad++), as below, save it as WinFormExample.cs under source folder :
using System;
using System.Drawing;
using System.Windows.Forms;
namespace CSharpGUI {
public class WinFormExample : Form {
private Button button;
public WinFormExample() {
DisplayGUI();
}
private void DisplayGUI() {
this.Name = "WinForm Example";
this.Text = "WinForm Example";
this.Size = new Size(150, 150);
this.StartPosition = FormStartPosition.CenterScreen;
button = new Button();
button.Name = "button";
button.Text = "Click Me!";
button.Size = new Size(this.Width - 50, this.Height - 100);
button.Location = new Point(
(this.Width - button.Width) / 3 ,
(this.Height - button.Height) / 3);
button.Click += new System.EventHandler(this.MyButtonClick);
this.Controls.Add(button);
}
private void MyButtonClick(object source, EventArgs e) {
MessageBox.Show("My First WinForm Application");
}
public static void Main(String[] args) {
Application.Run(new WinFormExample());
}
}
}
Now type csc /out:build\WinFormExample.exe source\WinFormExample.cs (more info is given at the end, for compiler options)and press ENTER to compile as shown below :
Now simply run it using .\build\WinExample, as shown below :
Now your simple GUI Application is up and running :-)
Do let me know, I can explain the same thingy regarding Java as well, if need be :-)
More info regarding Compiler Options can be found on C# Compiler Options Listed Alphabetically
There is nothing magic in WinForms drag & drop. You can reference the same classes from System.Windows.Forms. If you are going to roll your own, I would suggest looking at the Model View Presenter pattern.
Here is a nice article comparing Java Swing and WinForms:

Cannot add icon in shell extension with C#

I've found a very nice tutorial and i am trying to understand something that is not in this tutorial (because the tut itself works fine)
http://www.codeproject.com/Articles/9163/File-Rating-a-practical-example-of-shell-extension
When you look at applications like WinRar, TortoiseSVN, Antivirus-apps and many more, there is an icon next to the Shell Extension Item.
I would like to know how this is done. (Programmatically with C#)
Adding a separator works, adding a submenu works and click+action also works, but i'm struggling with the icon. This cannot be so hard. Can somebody help me?
And please don't say that Microsoft doesn't longer support this in .NET 4.0, because it is not guaranteed and therefore they don't supply samplecode. If all those other apps can do it, then it is possible.
Please supply me some sample code, some tutorials or maybe even a working piece of code.
Please have a look at the following article, it uses .NET 4.0 it to create Windows Shell Extensions using the SharpShell nuget package.
NET Shell Extensions - Shell Context Menus
Using this library, you can set the image directly while creating the contextmenustrip as shown below
protected override ContextMenuStrip CreateMenu()
{
// Create the menu strip.
var menu = new ContextMenuStrip();
// Create a 'count lines' item.
var itemCountLines = new ToolStripMenuItem
{
Text = "Count Lines...",
Image = Properties.Resources.CountLines
};
// When we click, we'll count the lines.
itemCountLines.Click += (sender, args) => CountLines();
// Add the item to the context menu.
menu.Items.Add(itemCountLines);
// Return the menu.
return menu;
}
You only have to add to the following registry key: HKEY_LOCAL_MACHINE\SOFTWARE\Classes*\shellex\ContextMenuHandlers
and here is the code:
string TimeStamp = DateTime.Now.ToString("dd-MM-yyyy");
string key = "HKEY_LOCAL_MACHINE\\SOFTWARE\\Classes\\*\\shellex\\ContextMenuHandlers\\Winrar";
string valueName = "MyWinrar";
Microsoft.Win32.Registry.SetValue(key, valueName, HERE WHAT YOU WANT TO START, Microsoft.Win32.RegistryValueKind.String);
i hope it works for you!
All the apps you listed use COM and unmanaged code to create overlay icon handlers. There is even a special project TortoiseOverlays that provides a common library for drawing icons for TortoiceCSV, TortoiseSVN and TortoiseGIT. You can take a look at it's source code to find out how it is done. If you want to draw similar icons, you should probably just reuse it.
Using .Net for this type of extensions is not recommended, because when multiple extensions, built against different .Net versions would attempt to load in explorer process, they will crash the explorer.

How to read in text from the visual studio debug output window

I've read several articles that tell you how to add text to the output window in visual studio from within an Add-On (specifically, a visual studio 2008 integration package, via the visual studio 2008 SDK 1.1), but no examples of how to read text from the output window. My goal is to parse text from the debug output window while debugging a certain application (TRACE output and possibly stdin/stdout). The IVsOutputWindowPane interface has no methods for reading in text from the output window. The documentation seems to imply that it is possible, but it doesn't provide an example:
http://msdn.microsoft.com/en-us/library/bb166236(VS.80).aspx
Quote: In addition, the OutputWindow and OutputWindowPane objects add some higher-level functionality to make it easier to enumerate the Output window panes and to retrieve text from the panes.
Preferably I'd like to be able to subscribe to an event that fires when a new line of text arrives, similar to a StreamReader's asynchronous reads.
It is possible, it is just a long winding path to get to it:
ServiceProvider -> IVsOutputWindow -> GetPane( debugwindow ) -> IVsUserData -> GetData( wpftextviewhost ) -> IWpfTextViewHost -> IWpfTextView -> TextBuffer -> Changed event.
Presuming you have a VS IServiceProvider from somewhere else (vsix extension/whatever, global service provider), and without any error checking, it looks like this:
IVsOutputWindow outWindow = ServiceProvider.GetService(typeof(SVsOutputWindow)) as IVsOutputWindow;
Guid debugPaneGuid = VSConstants.GUID_OutWindowDebugPane;
IVsOutputWindowPane pane;
outWindow.GetPane(ref debugPaneGuid, out pane);
// from here up you'll find in lots of other stackoverflow answers,
// the stuff from here down is interesting to this question
IVsUserData userData = (IVsUserData)pane;
object o;
Guid guidViewHost = DefGuidList.guidIWpfTextViewHost;
userData.GetData(ref guidViewHost, out o);
IWpfTextViewHost viewHost = (IWpfTextViewHost)o;
IWpfTextView textView = viewHost.TextView;
textView.TextBuffer.Changed += YourTextChangedHandlerHere;
Your text changed handler will then get called every time the output window gets more data. you won't necessarily get it line by line, but you'll probably more likely than not get big chunks you'll need to deal with on your own.
It is highly likely that some of the above did not even exist in VS in 2010. But it exists now!
The default behavior (when you don’t set the listener explicitly) of VS is to display trace massages in the debugger output window, which you appreciate if you want a simple solution and do no other actions with the massages.
Unfortunately this is not your case. So you have to define a trace listener to send (and store) your trace massages where you then will be able to read them. The trace listener could be a file (for example XML) or you can create a custom listener by deriving a class from the base class TraceListener if you don't want to bother yourself with an additional file.
I don't know that what you ask is possible. But, you can register your add-in as a debugger for your application so that you get the output the trace messages. These are typically routed to OutputDebugString, and can be captured as described in this article: http://www.drdobbs.com/showArticle.jhtml?articleID=184410719. It does not give you the normal output, only debug, but it does not depend on the technology of the debugged application.
The solution on this page selects the text in order to read it. I'm hoping there's a better way.
Automatically stop Visual C++ 2008 build at first compile error?
Private Sub OutputWindowEvents_OnPaneUpdated(ByVal pPane As OutputWindowPane) Handles OutputWindowEvents.PaneUpdated
pPane.TextDocument.Selection.SelectAll()
Dim Context As String = pPane.TextDocument.Selection.Text
pPane.TextDocument.Selection.EndOfDocument()
End Sub

Categories

Resources