Tracking changes in C# interactive window - c#

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

Related

UWP multiple views not closing

PROBLEM
I am using a secondary view to run my media files, but When I close my secondary view with close button on it ( while media is still playing ) the secondary view/window closes but the media somehow keeps playing because I can hear the sound and source of sound seems to be the primary view ( main app window ). how can I completely terminate the secondary window when I close it?
TRIED
I followed windows samples multiple views and was able to complete all steps, I copied the ViewLifetimeControl.cs file from the sample and used it in my project. the code runs fine until it reaches Windows.Current.Close() in released event of the secondary view.
Then it gives an exception when it tries "Window.Current.Close()" with in the released event. according to documentation exception occurs due to any on going changes ( which might be because of media file playing ), but I need to force close the window even when media file is playing how can I do that? btw here is the exception :
Message = "COM object that has been separated from its underlying RCW cannot be used."
Code to Create and Show secondary view
internal static async Task CompactOpen(string Title, string caption)
{
ViewLifetimeControl viewControl = null;
await CoreApplication.CreateNewView().Dispatcher.RunAsync(CoreDispatcherPriority.Normal, () =>
{
viewControl = ViewLifetimeControl.CreateForCurrentView();
viewControl.Title = Title;
viewControl.StartViewInUse();
var frame = new Frame();
frame.MinHeight = 200;
frame.MinWidth = 200;
frame.Navigate(typeof(CompactNowPlayingPage), new object[] { viewControl,caption});
Window.Current.Content = frame;
Window.Current.Activate();
ApplicationView.GetForCurrentView().Title = viewControl.Title;
});
((App)App.Current).SecondaryViews.Add(viewControl);
var selectedView = viewControl;
var sizePreference = new SizePreferenceString() { Title = "SizePreference", Preference = ViewSizePreference.Default };
var anchorSizePreference = new SizePreferenceString() { Title = "AnchorSizePreference", Preference = ViewSizePreference.Default };
if (selectedView != null && sizePreference != null && anchorSizePreference != null)
{
try
{
selectedView.StartViewInUse();
var viewShown = await ApplicationViewSwitcher.TryShowAsStandaloneAsync(
selectedView.Id,
sizePreference.Preference,
ApplicationView.GetForCurrentView().Id,
anchorSizePreference.Preference);
if (!viewShown)
{
// The window wasn't actually shown, so release the reference to it
// This may trigger the window to be destroyed
}
// Signal that switching has completed and let the view close
selectedView.StopViewInUse();
}
catch (InvalidOperationException)
{
// The view could be in the process of closing, and
// this thread just hasn't updated. As part of being closed,
// this thread will be informed to clean up its list of
// views (see SecondaryViewPage.xaml.cs)
}
}
}
Released Event
private async void ViewLifetimeControl_Released(Object sender, EventArgs e)
{
((ViewLifetimeControl)sender).Released -= ViewLifetimeControl_Released;
// The ViewLifetimeControl object is bound to UI elements on the main thread
// So, the object must be removed from that thread
await mainDispatcher.RunAsync(CoreDispatcherPriority.Normal, () =>
{
((App)App.Current).SecondaryViews.Remove(thisViewControl);
});
// The released event is fired on the thread of the window
// it pertains to.
//
// It's important to make sure no work is scheduled on this thread
// after it starts to close (no data binding changes, no changes to
// XAML, creating new objects in destructors, etc.) since
// that will throw exceptions
Window.Current.Close(); //this is where that exception occurs
}
Note : both of above methods and even all the related variables, all of them I have followed the guidelines within the uwp sample for multiple views.
Thanks in advance, any help would be really appreciated, I only want to force close the secondary view ( If that's possible )
Is this in the editor or the app? If it's in your debug or build of the app, the secondary view is most likely still open but hidden. You may be using a custom close button which doesn't perform its job well enough. Instead of putting down SecondaryViews.Remove you should do what you had originally written and try StopViewInUse. It may not work, I'm not used to this kind of thing.

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

How to handle JavaScript popus with controls using Selenium webdriver C# [duplicate]

So I'm working with selenium firefox webdrivers in c# winform and I have this code below to get the handle of the popup that shows when you click on the "webtraffic_popup_start_button" and it should get the handle of the popup but the popup handle is same as current one.
string current = driver.CurrentWindowHandle;
driver.FindElement(By.XPath("//*[#id='webtraffic_popup_start_button']")).Click();
Thread.Sleep(Sleep_Seconds);
popup = driver.CurrentWindowHandle;
Thread.Sleep(3000);
driver.SwitchTo().Window(current);
Thread.Sleep(1000);
Any help with this would be much appreciated thank you
This is what pop up looks like.
WebDriver does absolutely no tracking whatsoever to detect which window is actually in the foreground in the OS, and does no automatic switching when new browser windows are opened. That means the proper way to get the handle of a newly-opened popup window is a multi-step process. To do so, you would:
Save the currently-focused window handle into a variable so that you
can switch back to it later.
Get the list of currently opened window handles.
Perform the action that would cause the new window to appear.
Wait for the number of window handles to increase by 1.
Get the new list of window handles.
Find the new handle in the list of handles.
Switch to that new window.
In code using the .NET language bindings, that would look something like this:
string currentHandle = driver.CurrentWindowHandle;
ReadOnlyCollection<string> originalHandles = driver.WindowHandles;
// Cause the popup to appear
driver.FindElement(By.XPath("//*[#id='webtraffic_popup_start_button']")).Click();
// WebDriverWait.Until<T> waits until the delegate returns
// a non-null value for object types. We can leverage this
// behavior to return the popup window handle.
WebDriverWait wait = new WebDriverWait(driver, TimeSpan.FromSeconds(5));
string popupWindowHandle = wait.Until<string>((d) =>
{
string foundHandle = null;
// Subtract out the list of known handles. In the case of a single
// popup, the newHandles list will only have one value.
List<string> newHandles = driver.WindowHandles.Except(originalHandles).ToList();
if (newHandles.Count > 0)
{
foundHandle = newHandles[0];
}
return foundHandle;
});
driver.SwitchTo().Window(popupWindowHandle);
// Do whatever you need to on the popup browser, then...
driver.Close();
driver.SwitchTo().Window(currentHandle);
Alternatively, if you're using the .NET bindings, there's a PopupWindowFinder class in the WebDriver.Support assembly that is specifically designed to do these operations for you. Using that class is much simpler.
// Get the current window handle so you can switch back later.
string currentHandle = driver.CurrentWindowHandle;
// Find the element that triggers the popup when clicked on.
IWebElement element = driver.FindElement(By.XPath("//*[#id='webtraffic_popup_start_button']"));
// The Click method of the PopupWindowFinder class will click
// the desired element, wait for the popup to appear, and return
// the window handle to the popped-up browser window. Note that
// you still need to switch to the window to manipulate the page
// displayed by the popup window.
PopupWindowFinder finder = new PopupWindowFinder(driver);
string popupWindowHandle = finder.Click(element);
driver.SwitchTo().Window(popupWindowHandle);
// Do whatever you need to on the popup browser, then...
driver.Close();
// Switch back to parent window
driver.SwitchTo().Window(currentHandle);
If the lastly opened window is your target then simply do the following after the click
driver.SwitchTo().Window(driver.WindowHandles.ToList().Last());
EDIT
//You may need to go back to parent window to perform additional actions;
// to the new window
driver.SwitchTo().Window(driver.WindowHandles.ToList().Last());
// to the new window
driver.SwitchTo().Window(driver.WindowHandles.ToList().First());
//or
driver.SwitchTo().DefaultContent();
I've got some code you might like. The quickest solution is to use Popup Finder, but I've made my own method as well. I would never rely on the order the Window Handles are in to select the appropriate window. Popup Window Finder:
PopupWindowFinder finder = new PopupWindowFinder(driver);
driver.SwitchTo().Window(newWin);
My Custom method. Basically you pass it the element you want to click, your webdriver, and optionally the time to wait before searching after you click the element.
It takes all of your current handles and makes a list. It uses that list to eliminate the previously existing windows from accidentally getting switched to. Then it clicks the element that launches the new window. There should always be some sort of a delay after the click, as nothing happens instantly. And then it makes a new list and compares that against the old one until it finds a new window or the loop expires. If it fails to find a new window it returns null, so if you have an iffy webelement that doesn't always work, you can do a null check to see if the switch worked.
public static string ClickAndSwitchWindow(IWebElement elementToBeClicked,
IWebDriver driver, int timer = 2000)
{
System.Collections.Generic.List<string> previousHandles = new
System.Collections.Generic.List<string>();
System.Collections.Generic.List<string> currentHandles = new
System.Collections.Generic.List<string>();
previousHandles.AddRange(driver.WindowHandles);
elementToBeClicked.Click();
Thread.Sleep(timer);
for (int i = 0; i < 20; i++)
{
currentHandles.Clear();
currentHandles.AddRange(driver.WindowHandles);
foreach (string s in previousHandles)
{
currentHandles.RemoveAll(p => p == s);
}
if (currentHandles.Count == 1)
{
driver.SwitchTo().Window(currentHandles[0]);
Thread.Sleep(100);
return currentHandles[0];
}
else
{
Thread.Sleep(500);
}
}
return null;
}

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

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.

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