locked
QuickInfoSession is dismissed prematurely when using UserControls in quickInfoContent RRS feed

  • Question

  • I have a standard QuickInfoSourceProvider:

    [Export(typeof(IIntellisenseControllerProvider))]
    internal sealed class QuickInfoControllerProvider : IIntellisenseControllerProvider
    {
    	[Import]
    	private IQuickInfoBroker _quickInfoBroker = null;
    
    	public IIntellisenseController TryCreateIntellisenseController(ITextView textView, IList<ITextBuffer> subjectBuffers)
    	{
    		return new QuickInfoController(textView, subjectBuffers, this._quickInfoBroker);
    	}
    }

    And a standard QuickInfoController:

    internal sealed class QuickInfoController : IIntellisenseController
    {
    	private readonly IList<ITextBuffer> _subjectBuffers;
    	private readonly IQuickInfoBroker _quickInfoBroker;
    	private ITextView _textView;
    
    	internal QuickInfoController(
    		ITextView textView,
    		IList<ITextBuffer> subjectBuffers,
    		IQuickInfoBroker quickInfoBroker)
    	{
    		this._textView = textView;
    		this._subjectBuffers = subjectBuffers;
    		this._quickInfoBroker = quickInfoBroker;
    		this._textView.MouseHover += (o, e) => {
    			SnapshotPoint? point = GetMousePosition(new SnapshotPoint(this._textView.TextSnapshot, e.Position));
    			if (point.HasValue)
    			{
    				ITrackingPoint triggerPoint = point.Value.Snapshot.CreateTrackingPoint(point.Value.Position, PointTrackingMode.Positive);
    				if (!this._quickInfoBroker.IsQuickInfoActive(this._textView))
    				{
    					this._quickInfoBroker.TriggerQuickInfo(this._textView, triggerPoint, false);
    				}
    			}
    		};
    	}
    	private SnapshotPoint? GetMousePosition(SnapshotPoint topPosition)
    	{
    		return this._textView.BufferGraph.MapDownToFirstMatch(
    			topPosition,
    			PointTrackingMode.Positive,
    			snapshot => this._subjectBuffers.Contains(snapshot.TextBuffer),
    			PositionAffinity.Predecessor
    		);
    	}
    }

    The QuickInfoSourceProvider is also standaard:

    [Export(typeof(IQuickInfoSourceProvider))]
    internal sealed class QuickInfoSourceProvider : IQuickInfoSourceProvider
    {
    	public IQuickInfoSource TryCreateQuickInfoSource(ITextBuffer buffer)
    	{
    		Func<QuickInfoSource> sc = delegate () {
    			return new QuickInfoSource(buffer);
    		};
    		return buffer.Properties.GetOrCreateSingletonProperty(sc);
    	}
    }

    Now things become interesting: instead of adding a plain string into quickInfoContent I pass in a UserControl; but still, pretty standard:

    internal sealed class QuickInfoSource : IQuickInfoSource
    {
    	private readonly ITextBuffer _sourceBuffer;
    	public AsmQuickInfoSource(ITextBuffer buffer)
    	{
    		this._sourceBuffer = buffer;
    	}
    	public void AugmentQuickInfoSession(IQuickInfoSession session, IList<object> quickInfoContent, out ITrackingSpan applicableToSpan)
    	{
    		var snapshot = this._sourceBuffer.CurrentSnapshot;
    		var triggerPoint = (SnapshotPoint)session.GetTriggerPoint(snapshot);
    		applicableToSpan = snapshot.CreateTrackingSpan(new SnapshotSpan(triggerPoint, triggerPoint), SpanTrackingMode.EdgeInclusive);
    		quickInfoContent.Add(new BugWindow());
    	}
    }

    The BugWindow.xaml contains an expander, and it is with this expander that the trouble starts. First the code:

    <UserControl x:Class="BugWindow"
                 xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
                 xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
                 x:Name="MainWindow">
    	<Expander GotMouseCapture="GotMouseCapture_Click" IsExpanded="False">Blah</Expander>
    </UserControl>

    The BugWindow.xaml.cs contains some code to help me understand the problem, but it did not help...

    public partial class BugWindow : UserControl
    {
    	public BugWindow()
    	{
    		InitializeComponent();
    
    		this.MainWindow.MouseLeftButtonDown += (o, i) => 
    		{
    			//i.Handled = true; // dont let the mouse event from inside this window bubble up to VS
    		}; 
    
    		this.MainWindow.PreviewMouseLeftButtonDown += (o, i) =>
    		{
    			//i.Handled = true; // if true then no event is able to bubble to the gui
    		};
    	}
    
    	private void GotMouseCapture_Click(object sender, RoutedEventArgs e)
    	{
    		//e.Handled = true;
    	}
    }

    What works: if you hover the mouse over a keyword, my BugWindow appears and the expander is closed (because it is closed by default). If you click the expander, it opens and the text "Blah" appears. 

    Bug: if you leave open the expander and scroll down, the tooltip will disappear when the span the tooltip is applicable to (applicableSpan) is outside the visible window. We are now in the "bug state". If you hover the mouse over any keyword, my BugWindow will appear. If you click the expander, the window will incorrectly disappear. In fact, the quickInfoSession will be dismissed, as if you were clicking where no keywords are, which is interpreted as dismissing the session. Buttons have the same bug behaviour. 

    Question: I like my exander to behave as expected. How to do that?


    Tuesday, October 17, 2017 2:03 PM