WindowOpenedEvent Working for Normal Windows, But Not Push Notifications - windows

I'm trying to use the WindowOpenedEvent in System.Windows.Automation to detect when a new window is opened. This works fine for almost every window that I'm interested in. For example, when I open a notepad window when running the app I get this written to the console:
New Window opened: Notepad
But when I send a web push notification (using this site to test them), it is not detected and nothing is written to the console when the window appears:
Here's the code I'm using in my app. I've used all the various values for the scope field (TreeScope.Subtree, TreeScope.Children, etc)
using System.Windows.Automation;
using System.Diagnostics;
using System.Windows.Forms;
namespace FormWatcherAutomation
{
public partial class Form1 : Form
{
public Form1()
{
InitializeComponent();
System.Windows.Automation.Automation.AddAutomationEventHandler(
eventId: WindowPattern.WindowOpenedEvent,
element: AutomationElement.RootElement,
scope: TreeScope.Subtree,
eventHandler: OnWindowOpened);
}
private static void OnWindowOpened(object sender, AutomationEventArgs automationEventArgs)
{
try
{
var element = sender as AutomationElement;
if (element != null)
Debug.WriteLine("New Window opened: " + element.Current.Name);
}
catch (ElementNotAvailableException)
{
}
}
}

Related

Show tooltip on hover over text

I want to create extension that allows to show custom message when I hover over a text.
E.g. "test-text" should give tooltip "OK" instead of current "ITrackin..."
I tried to follow https://learn.microsoft.com/en-us/visualstudio/extensibility/walkthrough-displaying-quickinfo-tooltips?view=vs-2019
but people are stating that it is not working and it's quite long way of doing this.
I cannot find any more docs on this. I know how to display it in on-click window/get currently selected text.
The sample send by Lance Li-MSFT was really helpful, but in order to get this working I had to spend some time.
Important steps:
Import LineAsyncQuickInfoSourceProvider.cs and LineAsyncQuickInfoSource.cs
Add reference to System.ComponentModel.Composition by add reference dialog (right click on the project name)
Get missing references by installing them using NuGet Package Manager
To initialize MEF components, you’ll need to add a new Asset to source.extension.vsixmanifest.
<Assets>
...
<Asset Type = "Microsoft.VisualStudio.MefComponent" d:Source="Project" d:ProjectName="%CurrentProject%" Path="|%CurrentProject%|" />
</Assets>
LineAsyncQuickInfoSourceProvider.cs
It's just used to display quick info/tooltip.
using Microsoft.VisualStudio.Language.Intellisense;
using Microsoft.VisualStudio.Text;
using Microsoft.VisualStudio.Utilities;
using System.ComponentModel.Composition;
namespace JSONExtension
{
[Export(typeof(IAsyncQuickInfoSourceProvider))]
[Name("Line Async Quick Info Provider")]
[ContentType("any")]
[Order]
internal sealed class LineAsyncQuickInfoSourceProvider : IAsyncQuickInfoSourceProvider
{
public IAsyncQuickInfoSource TryCreateQuickInfoSource(ITextBuffer textBuffer) //creates instance of LineAsyncQuickInfoSource for displaying Quick Info
{
return textBuffer.Properties.GetOrCreateSingletonProperty(() => new LineAsyncQuickInfoSource(textBuffer)); //this ensures only one instance per textbuffer is created
}
}
}
LineAsyncQuickInfoSource.cs
Here you can customize what you want to display.
using Microsoft.VisualStudio.Language.Intellisense;
using Microsoft.VisualStudio.Language.StandardClassification;
using Microsoft.VisualStudio.Text;
using Microsoft.VisualStudio.Text.Adornments;
using Newtonsoft.Json;
using Newtonsoft.Json.Linq;
using System.Collections.Generic;
using System.IO;
using System.Threading;
using System.Threading.Tasks;
namespace JSONExtension
{
internal sealed class LineAsyncQuickInfoSource : IAsyncQuickInfoSource
{
private ITextBuffer _textBuffer;
public LineAsyncQuickInfoSource(ITextBuffer textBuffer)
{
_textBuffer = textBuffer;
}
// This is called on a background thread.
public Task<QuickInfoItem> GetQuickInfoItemAsync(IAsyncQuickInfoSession session, CancellationToken cancellationToken)
{
var triggerPoint = session.GetTriggerPoint(_textBuffer.CurrentSnapshot);
if (triggerPoint != null)
{
var line = triggerPoint.Value.GetContainingLine();
var lineSpan = _textBuffer.CurrentSnapshot.CreateTrackingSpan(line.Extent, SpanTrackingMode.EdgeInclusive);
var text = triggerPoint.Value.GetContainingLine().GetText(); //get whole line of current cursor pos
ContainerElement dataElm = new ContainerElement(
ContainerElementStyle.Stacked,
new ClassifiedTextElement(
new ClassifiedTextRun(PredefinedClassificationTypeNames.Keyword, "MESSAGE TO EDIT: " + text.ToString())
));
return Task.FromResult(new QuickInfoItem(lineSpan, dataElm)); //add custom text from above to Quick Info
}
return Task.FromResult<QuickInfoItem>(null); //do not add anything to Quick Info
}
public void Dispose()
{
// This provider does not perform any cleanup.
}
}
}

Wikitude Image recognized reset

So I have an app that uses Wikitude to recognize images. When an image is recognized I show a popup that gives the name of the image back. When the user clicks on done in the popup the image should be trackable again. But now everything works except for the image to be trackable again (unless i go off the image and go back on the image)
from the js file:
imageRcognized: function(name) {
AR.platform.sendJSONObject(name);
},
from C# delegate
public class PopUpdel : WTArchitectViewDelegate
{
VC _presentingVC;
public PopUpdel(VC presentingVC)
{
_presentingVC = presentingVC;
}
public override void ReceivedJSONObject(WTArchitectView architectView, NSDictionary jsonObject)
{
WikitudeScanResult result = new WikitudeScanResult()
{
name = jsonObject.ValueForKey(new NSString("name")).ToString()
};
_presentingVC.Tracked(result);
}
public void Tracked(WikitudeScanResult result)
{
//show popup
}
Now only thing that rest is reset the image so it can be scanned again
You could set ImageTracker.enabled to false when it is recognized and to true once you want to recognize it again.

VSTO MS Project 2007 Add-In Button Events Stop firing after a few minutes

I'm writing an MS Project 2007 Add-in (VS 2010, WinXP) that creates a toolbar w/button and assigns a HelloWorld onclick eventhandler to said button.
The problem
After being installed, the plugin creates the button, wires up the click event, and everything works just fine. However, after a few minutes, the onclick event inexplicably stop firing.
Everything I've read says that I need to define my toolbar/button in the global scope, which I've done. However, the onclick event still gets unhooked after a few minutes of runtime.
One other weird symptom I'm experiencing is that when I toggle it in the COM Add-ins Dialog box (after its stopped working), I get this weird message:
"com object that has been seperated from its underlying RCW cannot be used"
...which is strange, because in this simple application, I'm not releasing any COM objects.
Any suggestions?
The Code
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Xml.Linq;
using MSProject = Microsoft.Office.Interop.MSProject;
using Office = Microsoft.Office.Core;
namespace Test_Project2007_Addin
{
public partial class ThisAddIn
{
private Office.CommandBar cmdBar; // Hello World Toolbar
private Office.CommandBarButton cmdBtn01; // Hellow World Button
private string cmdBarName = "Hello World Toolbar";
private string cmdBtn01Name = "HelloWorld";
private void ThisAddIn_Startup( object sender, System.EventArgs e ) {
// Define the toolbar
cmdBar = this.Application.CommandBars.Add(
cmdBarName, Office.MsoBarPosition.msoBarTop, false, true );
cmdBar.Visible = true;
// Define the button
cmdBtn01 = cmdBar.Controls.Add( Office.MsoControlType.msoControlButton, missing, missing, missing, true ) as Office.CommandBarButton;
cmdBtn01.FaceId = 422;
cmdBtn01.Caption = "Hello World";
cmdBtn01.Tag = cmdBtn01Name;
cmdBtn01.DescriptionText = "Hello World";
cmdBtn01.TooltipText = "Hello World";
cmdBtn01.Click += new Microsoft.Office.Core._CommandBarButtonEvents_ClickEventHandler( HelloWorld );
}
private void ThisAddIn_Shutdown( object sender, System.EventArgs e ) {
}
private void HelloWorld( Microsoft.Office.Core.CommandBarButton barButton, ref bool someBool ) {
System.Windows.Forms.MessageBox.Show( "Hello, World!" );
}
#region VSTO generated code
/// <summary>
/// Required method for Designer support - do not modify
/// the contents of this method with the code editor.
/// </summary>
private void InternalStartup()
{
this.Startup += new System.EventHandler(ThisAddIn_Startup);
this.Shutdown += new System.EventHandler(ThisAddIn_Shutdown);
}
#endregion
}
}
Try adding the CommandBars as a private member variable. Maybe CommandBars is getting garbage collected. See if this fixes your RCW issue - if not it may be another plugin.
If this doesn't work, maybe try making Application a local member. Sorry - I don't have MS Project to test this out.
private CommandBars cmdBars; // app command bars
private void ThisAddIn_Startup( object sender, System.EventArgs e ) {
//..
cmdBars = this.Application.CommandBars;
cmdBar = cmdBars.Add(cmdBarName, Office.MsoBarPosition.msoBarTop, false, true );
//..
}
Try this one:
cmdBar = this.Application.ActiveExplorer().CommandBars.Add(
instead of:
cmdBar = this.Application.CommandBars.Add(

Modal MessageBox on another Process' Handle may lock-up target Process

If I show a MessageBox as modal of a window on another process, it works just fine as long as my program remains responding. If it is closed or terminated while the MessageBox is showing the windows that received the MessageBox will be locked (but still responding) and it will have to be finalized via Task Manager.
Here is a sample code to demonstrate that:
using System;
using System.Windows.Forms;
using System.Diagnostics;
using System.Threading;
namespace TestMessageBox
{
class Program
{
private WindowWrapper notepad;
Program(IntPtr handle)
{
notepad = new WindowWrapper(handle);
}
static void Main(string[] args)
{
Process[] procs = Process.GetProcessesByName("notepad");
if (procs.Length > 0)
{
Console.WriteLine("Notepad detected...");
Program program = new Program(procs[0].MainWindowHandle);
Thread thread = new Thread(new ThreadStart(program.ShowMessage));
thread.IsBackground = true;
thread.Start();
Console.Write("Press any key to end the program and lock notepad...");
Console.ReadKey();
}
}
void ShowMessage()
{
MessageBox.Show(notepad, "If this is open when the program ends\nit will lock up notepad...");
}
}
/// <summary>
/// Wrapper class so that we can return an IWin32Window given a hwnd
/// </summary>
public class WindowWrapper : System.Windows.Forms.IWin32Window
{
public WindowWrapper(IntPtr handle)
{
_hwnd = handle;
}
public IntPtr Handle
{
get { return _hwnd; }
}
private IntPtr _hwnd;
}
}
How to avoid that?
The act of showing a modal dialog disables the parent window of the dialog (Notepad's window in your example). When the modal dialog is closed, the parent window gets re-enabled.
If your program dies before it re-enables the window, that window will never get re-enabled - it's up to the thread that's showing the dialog to re-enable the parent. (In your example, it happens within MessageBox.Show(), after the user clicks OK or whatever.)
The only way to make this work would be to have a second process whose responsibility it was to put things back as they should be if the process creating the modal dialog dies prematurely, but that's horrible. And it's still not bulletproof - what if the watcher process dies too?

How do I write to the Visual Studio Output Window in My Custom Tool?

I am writing a custom tool and I currently have it doing what I want as far as functionality. I would like to be able to write to Visual Studio if something goes wrong. (Incorrectly formatted code or whatever).
Are there any standards for this? Right now I basically can force the tool to fail and Visual Studio puts in a warning that it has done so. I'd like a category in the Output window with any resulting messages I want to send. I could also live with a more descriptive task/warning in the Error list window.
Output Window
To write to the "General" output window in Visual Studio, you need to do the following:
IVsOutputWindow outWindow = Package.GetGlobalService( typeof( SVsOutputWindow ) ) as IVsOutputWindow;
Guid generalPaneGuid = VSConstants.GUID_OutWindowGeneralPane; // P.S. There's also the GUID_OutWindowDebugPane available.
IVsOutputWindowPane generalPane;
outWindow.GetPane( ref generalPaneGuid , out generalPane );
generalPane.OutputString( "Hello World!" );
generalPane.Activate(); // Brings this pane into view
If, however, you want to write to a custom window, this is what you need to do:
IVsOutputWindow outWindow = Package.GetGlobalService( typeof( SVsOutputWindow ) ) as IVsOutputWindow;
// Use e.g. Tools -> Create GUID to make a stable, but unique GUID for your pane.
// Also, in a real project, this should probably be a static constant, and not a local variable
Guid customGuid = new Guid("0F44E2D1-F5FA-4d2d-AB30-22BE8ECD9789");
string customTitle = "Custom Window Title";
outWindow.CreatePane( ref customGuid, customTitle, 1, 1 );
IVsOutputWindowPane customPane;
outWindow.GetPane( ref customGuid, out customPane);
customPane.OutputString( "Hello, Custom World!" );
customPane.Activate(); // Brings this pane into view
Details on IVsOutputWindow and IVsOutputWindowPane can be found on MSDN.
Error List
For adding items to the error list, the IVsSingleFileGenerator has a method call void Generate(...) which has a parameter of the type IVsGeneratorProgress. This interface has a method void GeneratorError() which lets you report errors and warnings to the Visual Studio error list.
public class MyCodeGenerator : IVsSingleFileGenerator
{
...
public void Generate( string inputFilePath, string inputFileContents, string defaultNamespace, out IntPtr outputFileContents, out int output, IVsGeneratorProgress generateProgress )
{
...
generateProgress.GeneratorError( false, 0, "An error occured", 2, 4);
...
}
...
}
The details of GeneratorError() can be found on MSDN.
There is another way using Marshal.GetActiveObject to grab a running DTE2 instance.
First reference EnvDTE and envdte80. This currently works in VisualStudio 2012, I haven't tried the others yet.
using System;
using System.Runtime.InteropServices;
using EnvDTE;
using EnvDTE80;
internal class VsOutputLogger
{
private static Lazy<Action<string>> _Logger = new Lazy<Action<string>>( () => GetWindow().OutputString );
private static Action<string> Logger
{
get { return _Logger.Value; }
}
public static void SetLogger( Action<string> logger )
{
_Logger = new Lazy<Action<string>>( () => logger );
}
public static void Write( string format, params object[] args)
{
var message = string.Format( format, args );
Write( message );
}
public static void Write( string message )
{
Logger( message + Environment.NewLine );
}
private static OutputWindowPane GetWindow()
{
var dte = (DTE2) Marshal.GetActiveObject( "VisualStudio.DTE" );
return dte.ToolWindows.OutputWindow.ActivePane;
}
}
If you want anything to appear in the Output window, it has to come from stdout. To do this, your app needs to be linked as a "console" app. Set the /SUBSYSTEM:CONSOLE flag in the project's property page, under Linker/System set the SubSystem property to CONSOLE.
Once you have your output in the window, if you include the text "Error:" it will appear as an error, or if you set "Warning:" it will appear as a warning. If your error text begins with a path/filename, followed by a line number in parenthesis, the IDE will recognize it as a "clickable" error, and navigate you automatically to the faulting line.
This is demonstrated in the following helper class from a Microsoft sample project:
https://github.com/microsoft/VSSDK-Extensibility-Samples/blob/df10d37b863feeff6e8fcaa6f4d172f602a882c5/Reference_Services/C%23/Reference.Services/HelperFunctions.cs#L28
The code is as follows:
using System;
using System.Diagnostics;
using Microsoft.VisualStudio.Shell.Interop;
namespace Microsoft.Samples.VisualStudio.Services
{
/// <summary>
/// This class is used to expose some utility functions used in this project.
/// </summary>
internal static class HelperFunctions
{
/// <summary>
/// This function is used to write a string on the Output window of Visual Studio.
/// </summary>
/// <param name="provider">The service provider to query for SVsOutputWindow</param>
/// <param name="text">The text to write</param>
internal static void WriteOnOutputWindow(IServiceProvider provider, string text)
{
// At first write the text on the debug output.
Debug.WriteLine(text);
// Check if we have a provider
if (null == provider)
{
// If there is no provider we can not do anything; exit now.
Debug.WriteLine("No service provider passed to WriteOnOutputWindow.");
return;
}
// Now get the SVsOutputWindow service from the service provider.
IVsOutputWindow outputWindow = provider.GetService(typeof(SVsOutputWindow)) as IVsOutputWindow;
if (null == outputWindow)
{
// If the provider doesn't expose the service there is nothing we can do.
// Write a message on the debug output and exit.
Debug.WriteLine("Can not get the SVsOutputWindow service.");
return;
}
// We can not write on the Output window itself, but only on one of its panes.
// Here we try to use the "General" pane.
Guid guidGeneral = Microsoft.VisualStudio.VSConstants.GUID_OutWindowGeneralPane;
IVsOutputWindowPane windowPane;
if (Microsoft.VisualStudio.ErrorHandler.Failed(outputWindow.GetPane(ref guidGeneral, out windowPane)) ||
(null == windowPane))
{
if (Microsoft.VisualStudio.ErrorHandler.Failed(outputWindow.CreatePane(ref guidGeneral, "General", 1, 0)))
{
// Nothing to do here, just debug output and exit
Debug.WriteLine("Failed to create the Output window pane.");
return;
}
if (Microsoft.VisualStudio.ErrorHandler.Failed(outputWindow.GetPane(ref guidGeneral, out windowPane)) ||
(null == windowPane))
{
// Again, there is nothing we can do to recover from this error, so write on the
// debug output and exit.
Debug.WriteLine("Failed to get the Output window pane.");
return;
}
if (Microsoft.VisualStudio.ErrorHandler.Failed(windowPane.Activate()))
{
Debug.WriteLine("Failed to activate the Output window pane.");
return;
}
}
// Finally we can write on the window pane.
if (Microsoft.VisualStudio.ErrorHandler.Failed(windowPane.OutputString(text)))
{
Debug.WriteLine("Failed to write on the Output window pane.");
}
}
}
}
You can use the Debug and/or Trace classes. There is some information here:
http://msdn.microsoft.com/en-us/library/bs4c1wda(VS.71).aspx
Best of luck.
use System.Diagnostics.Debugger.Message

Resources