How to open a tool window of a visual studio extension programmatically? - c#

So I've got two tool windows in my visual studio extension (package) and I'd like to open up the second window via a button on the first window.
I expected this to be explained here: "How to: Open a Tool Window Programmatically", but it wasn't.

You should use either Package.FindToolWindow or IVsUIShell.FindToolWindow to find or create a tool window.
If used from your own package (or if you have a reference to the package, just put it there instead of this):
private void OpenFromPackage()
{
ToolWindowPane window = this.FindToolWindow(typeof(MyToolWindow), 0, true); // True means: crate if not found. 0 means there is only 1 instance of this tool window
if (null == window || null == window.Frame)
throw new NotSupportedException("MyToolWindow not found");
IVsWindowFrame windowFrame = (IVsWindowFrame)window.Frame;
ErrorHandler.ThrowOnFailure(windowFrame.Show());
}
If you can't do it from your package, or don't have a reference to it, use IVSUIShell:
private void OpenWithIVsUIShell()
{
IVsUIShell vsUIShell = (IVsUIShell)Package.GetGlobalService(typeof(SVsUIShell));
Guid guid = typeof(MyToolWindow).GUID;
IVsWindowFrame windowFrame;
int result = vsUIShell.FindToolWindow((uint)__VSFINDTOOLWIN.FTW_fFindFirst, ref guid, out windowFrame); // Find MyToolWindow
if (result != VSConstants.S_OK)
result = vsUIShell.FindToolWindow((uint)__VSFINDTOOLWIN.FTW_fForceCreate, ref guid, out windowFrame); // Crate MyToolWindow if not found
if (result == VSConstants.S_OK) // Show MyToolWindow
ErrorHandler.ThrowOnFailure(windowFrame.Show());
}

When you create a new package with toolwindow support, you get a single toolwindow and a command that displays it. This command is handled in the package class with the ShowToolWindow method.
Examining that, you'll see that the base package object has a FindToolWindow method that you can use to find (and create if needed) any toolwindow you have implemented in your package. That FindToolWindow method is just a nice wrapper around the IVsUIShell.FindToolWindow method, which is what ultimately gets invoked when displaying any toolwindow.
So instead of using the old EnvDTE automation interface, I would recommend using the lower level services built into the actual package object.

Here's how I solved it, the following code is the code-behind method of the button on the first window:
private void ButtonBase_OnClick(object sender, RoutedEventArgs e)
{
var dte = Package.GetGlobalService(typeof(DTE)) as DTE;
if (dte == null) return;
var window = dte.Windows.Item("{WindowGUID}");
window.Visible = true;
}
You should find the "WindowGUID" in the Guids class and above the class of the ToolWindow.

Related

Enable the menu item of my Visual studio extension only when code window is open

I'd like to enable my extension menu item only when code editor is open.
I try to detect when Code window is opening with the following code without successful result...
private void MenuItem_BeforeQueryStatus(object sender, EventArgs e)
{
OleMenuCommand menuCommand = sender as OleMenuCommand;
if (null != menuCommand)
{
IVsMonitorSelection monitorSelection = this.ServiceProvider.GetService(typeof(IVsMonitorSelection)) as IVsMonitorSelection;
int pfActive;
Guid codeWindowGuid = VSConstants.UICONTEXT_CodeWindow;
uint solutionExistCookie;
monitorSelection.GetCmdUIContextCookie(ref codeWindowGuid, out solutionExistCookie);
monitorSelection.IsCmdUIContextActive(solutionExistCookie, out pfActive);
menuCommand.Enabled = (pfActive == 1);
}
}
Although IsCmdUIContextActive is returing S_OK, I'm always receiving pfActive as false even if code window is really opened..
What am I doing wrong ?
Maybe you forgot to add out keyword in your IsCmdUIContextActive method signature in the place where it is defined?
Component Diagnostics extension can show you all currently active contexts so that you choose the needed one:
Checking DTE.ActiveWindow.Document is not null is probably the easiest way to check for an active editor.

Tracking changes in C# interactive window

VS now comes with an interactive window, but unlike running the raw CSI.EXE Roslyn process, Visual Studio adds IntelliSense and a few other features such as being able to load in the current project.
I want to write a VS plug-in that tracks all text editor changes in this window. Is this possible? What I'm looking for is something akin to PreviewKeyDown/PreviewTextInput WPF events. Can I get those on the C# interactive window and, if so, how?
Here's how far I got so far:
var dte = Shell.Instance.GetComponent<DTE>();
foreach (Window window in dte.MainWindow.Collection)
{
if (window.Kind.ToUpper().Contains("TOOL"))
{
if (window.Caption == "C# Interactive")
{
WpfWindow wpfWindow = (WpfWindow)HwndSource.FromHwnd((IntPtr) window.HWnd).RootVisual;
for (int i = 0; i < VTH.GetChildrenCount(wpfWindow); ++i)
{
// now what?
}
}
}
}
Here is some code that will get an IWpfTextViewHost reference on the C# interactive Window. From there, you can have access to all text services from Visual Studio: Text lines, Text buffer, etc. (or you can hook directly on WPF's controls, which I don't recommend)
// get global UI shell service from a service provider
var shell = (IVsUIShell)ServiceProvider.GetService(typeof(SVsUIShell));
// try to find the C# interactive Window frame from it's package Id
// with different Guids, it could also work for other interactive Windows (F#, VB, etc.)
var CSharpVsInteractiveWindowPackageId = new Guid("{ca8cc5c7-0231-406a-95cd-aa5ed6ac0190}");
// you can use a flag here to force open it
var flags = __VSFINDTOOLWIN.FTW_fFindFirst;
shell.FindToolWindow((uint)flags, ref CSharpVsInteractiveWindowPackageId, out IVsWindowFrame frame);
// available?
if (frame != null)
{
// get its view (it's a WindowPane)
frame.GetProperty((int)__VSFPROPID.VSFPROPID_DocView, out object dv);
// this pane implements IVsInteractiveWindow (you need to add the Microsoft.VisualStudio.VsInteractiveWindow nuget package)
var iw = (IVsInteractiveWindow)dv;
// now get the wpf view host
// using an extension method from Microsoft.VisualStudio.VsInteractiveWindowExtensions class
IWpfTextViewHost host = iw.InteractiveWindow.GetTextViewHost();
// you can get lines with this
var lines = host.TextView.TextViewLines;
// and subscribe to events in text with this
host.TextView.TextBuffer.Changed += TextBuffer_Changed;
}
private void TextBuffer_Changed(object sender, TextContentChangedEventArgs e)
{
// text has changed
}
Note "Microsoft.VisualStudio.VsInteractiveWindow" assembly is not specifically documented but the source is open: http://sourceroslyn.io/#Microsoft.VisualStudio.VsInteractiveWindow

How to open a file with an executable already open?

i have a C#/WPF project that represents a dbf editor that allows you to open files in 3 ways:
- with apposit button in toolbar
- with drag & drop
- with double click on file
Now i use a TabControl that contain every dbf open.
I can handle it with my internal button and drag add item to container.
if I open a file with the double click and there is an open instance I would like to add it to the container and instead open a new instance.
My code :
App
public partial class App : Application
{
private static Editor mainWindow = null;
protected override void OnStartup(StartupEventArgs e)
{
Editor mainWindow = new Editor(e.Args);
mainWindow.Show();
}
}
Editor:
public partial class Editor : Window
{
ChooseMessage.Choose choose;
public Dictionary<int, DBFStructure> ds;
string DbfName;
private string[] OldNew;
public Editor(string[] e)
{
InitializeComponent();
ds = new Dictionary<int, DBFStructure>();
OldNew = new string[2];
choose = ChooseMessage.Choose.OK;
if (e.Length > 0)
if (File.Exists(e[0]) && e[0].EndsWith(".dbf", StringComparison.InvariantCultureIgnoreCase))
EffOpen(e[0]);
}
private void dbf_Drop(object sender, DragEventArgs e)
{
if (e.Data.GetDataPresent(DataFormats.FileDrop, true) == true)
{
string filename = ((string[])e.Data.GetData(DataFormats.FileDrop, true))[0];
if (File.Exists(filename) && filename.EndsWith(".dbf", StringComparison.InvariantCultureIgnoreCase))
EffOpen(filename);
}
}
....
}
I apologize for code display but I can not set it correctly.
my problem is to intercept the opening of a dbf from the last open editor instance and add it to the controltab, otherwise create a new instance.
P.S. EffOpen(filename) represents the method that, by passing the fil name, loads it and adds it to the container
Thanks to all
When you open a file with a double click, a new application instance will be started by Windows. This is by design, you cannot change this.
In this new instance you have no direct references to the objects of your first application instance, so you cannot just add the file being opened to the TabControl of the first instance.
You need to implement it like that:
since you can open files via double click, your application can handle files using the command line arguments - that's good
you need a way to check whether there is an instance already running - use a Mutex with a dedicated and unique name to do that; try creating a Mutex on app startup and check the out parameter of the Mutex(bool, string, out bool) constructor - if it's false, then there is an instance already
you need a way to tell the already running instance that it needs to open a file that has been passed as the command line argument to the second instance; use any of the inter-proc methods that better suits you: a NamedPipe would be a pretty simple way

Open a WPF window app form VS extension command

I'm writing my first VS extension.
so far i have the code to get selected text and display a message box or manipulate the selection:
the CTOR of the extention..
private StringRefactor(Package package)
{
if (package == null)
{
throw new ArgumentNullException("package");
}
this.package = package;
OleMenuCommandService commandService = this.ServiceProvider.GetService(typeof(IMenuCommandService)) as OleMenuCommandService;
if (commandService != null)
{
var menuCommandID = new CommandID(CommandSet, CommandId);
var menuItem = new MenuCommand(this.MenuItemCallback, menuCommandID);
commandService.AddCommand(menuItem);
}
}
The callback:
private void MenuItemCallback(object sender, EventArgs e)
{
var selection = getSelection();
var selectedText = selection == null ? "No text selected..." : selection.StreamSelectionSpan.GetText();
string message = string.Format(CultureInfo.CurrentCulture, "Inside {0}.MenuItemCallback()", this.GetType().FullName);
string title = "StringRefactor";
// Show a message box to prove we were here
VsShellUtilities.ShowMessageBox(
this.ServiceProvider,
selectedText,
title,
OLEMSGICON.OLEMSGICON_INFO,
OLEMSGBUTTON.OLEMSGBUTTON_OK,
OLEMSGDEFBUTTON.OLEMSGDEFBUTTON_FIRST);
}
now instead of VsShellUtilities.ShowMessageBox(... i would like to open a prompt window that display several textboxes and ok\cancel button..
I thought of creating another WPF app project and launching it from the callback but I'm not sure this is the right way to write an extension that opens a custom tool ..
so what is the right way to open a custom window with functionality from a VISIX ?
You can create your own WPF dialogs in the VSIX extension. In fact Visual Studio is designed for that (since the UI is WPF).
See this article for further instructions:
Creating and Managing Modal Dialog Boxes

Displaying a WPF Window by name

Project A contains a WPF Window (Data Entry Form) with a stack panel of commands that launch various reports. The menu is a dynamically built list from a database. What I’m attempting to do is launch the corresponding WPF window based on the CommandText associated with the menu choice. I’d like to create a single function that accepts the name of the WPF Window (CommandText) and launches a new instance of the window by name.
I’ve found examples of how to launch methods on classes, but can’t seem to find a method that works with a window. I know it can be done with a switch and just map all the windows, but there are 60-70 and I was trying to avoid bloat.
I’m failed repeatedly trying to use the TypeOf and Activator.CreateInstance. Suggestions? Is this even possible?
Activator works fine for me. What error do you have? Try if below code will work for you
private void Button_Click(object sender, RoutedEventArgs e)
{
Window wnd = (Window)CreateWindow("WpfApplication1.Window2");
wnd.Show();
}
public object CreateWindow(string fullClassName)
{
Assembly asm = this.GetType().Assembly;
object wnd = asm.CreateInstance(fullClassName);
if (wnd == null)
{
throw new TypeLoadException("Unable to create window: " + fullClassName);
}
return wnd;
}
You might try this:
string windowClass = "CreateWindow.MyWindow";
Type type = Assembly.GetExecutingAssembly().GetType(windowClass);
ObjectHandle handle = Activator.CreateInstance(null, windowClass);
MethodInfo method = type.GetMethod("Show");
method.Invoke(handle.Unwrap(), null);
The code above assumes that your window is called "CreateWindow.MyWindow" (with namespace prefix) and that the type "CreateWindow.MyWindow" is in the currently executing assembly.

Categories

Resources