Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

How to force line numbers IWpfTextViewMargin (WpfLineNumberMargin) to repaint? #445

Closed
Denis535 opened this issue Dec 11, 2024 · 1 comment

Comments

@Denis535
Copy link

Denis535 commented Dec 11, 2024

Hi!
I'm making an extension that smoothly scrolls a text view and records video (now gif animation).
But there is one problem, line numbers are not repainted when scrolling.
The text is scrolled, but line numbers are frozen.
Please tell me how I can solve this problem?

[VisualStudioContribution]
internal class TakeSnapshotCommand : Microsoft.VisualStudio.Extensibility.Commands.Command {

    private TraceSource Logger { get; }
    private AsyncServiceProviderInjection<DTE, DTE2> DTE { get; }
    private MefInjection<IVsEditorAdaptersFactoryService> EditorAdaptersFactoryService { get; }
    private AsyncServiceProviderInjection<SVsTextManager, IVsTextManager> TextManager { get; }

    public override CommandConfiguration CommandConfiguration => new CommandConfiguration( "%Snapshot.Pro.TakeSnapshotCommand.DisplayName%" ) {
        Icon = new CommandIconConfiguration( ImageMoniker.KnownValues.Extension, IconSettings.IconAndText ),
        Placements = [ CommandPlacement.KnownPlacements.ToolsMenu ],
    };

    public TakeSnapshotCommand(
        VisualStudioExtensibility extensibility,
        TraceSource logger,
        AsyncServiceProviderInjection<DTE, DTE2> dte,
        MefInjection<IVsEditorAdaptersFactoryService> editorAdaptersFactoryService,
        AsyncServiceProviderInjection<SVsTextManager, IVsTextManager> textManager
        ) : base( extensibility ) {
        Logger = logger;
        DTE = dte;
        EditorAdaptersFactoryService = editorAdaptersFactoryService;
        TextManager = textManager;
    }

    public override Task InitializeAsync(CancellationToken cancellationToken) {
        return base.InitializeAsync( cancellationToken );
    }

    public override async Task ExecuteCommandAsync(IClientContext context, CancellationToken cancellationToken) {
        await ThreadHelper.JoinableTaskFactory.SwitchToMainThreadAsync();
        try {
            var textViewSnapshot = await context.GetActiveTextViewAsync( cancellationToken ) ?? throw new NullReferenceException( "ITextViewSnapshot is null" );
            //var textDocumentSnapshot = view.Document ?? throw new NullReferenceException( "ITextDocumentSnapshot is null" );

            var editorAdaptersFactoryService = await EditorAdaptersFactoryService.GetServiceAsync();

            var textManager = await TextManager.GetServiceAsync();
            ErrorHandler.ThrowOnFailure( textManager.GetActiveView( 1, null, out var activeTextView ) );

            var wpfTextViewHost = editorAdaptersFactoryService.GetWpfTextViewHost( activeTextView ) ?? throw new NullReferenceException( "IWpfTextViewHost is null" );
            var wpfTextView = wpfTextViewHost.TextView ?? throw new NullReferenceException( "IwpfTextView is null" );
            var wpfTextViewMargin = wpfTextViewHost.GetTextViewMargin( PredefinedMarginNames.LineNumber ) ?? throw new NullReferenceException( "IWpfTextViewMargin is null" );

            var path = $"D:/Snapshots/{DateTime.UtcNow.Ticks}-{Path.GetFileNameWithoutExtension( textViewSnapshot.FilePath ).Replace( ".", "_" )}.gif";
            TakeSnapshot( path, wpfTextView, wpfTextViewMargin );
            await Extensibility.Shell().ShowPromptAsync( $"Snapshot was saved: " + path, PromptOptions.OK, cancellationToken );
        } catch (Exception ex) {
            Logger.TraceInformation( "Can not save snapshot: " + ex );
        }
    }

Unfortunately, margin.VisualElement.UpdateLayout(); doesn't work!!!

    private static void TakeSnapshot(string path, IWpfTextView view, IWpfTextViewMargin margin) {
        ThreadHelper.ThrowIfNotOnUIThread();
        Directory.CreateDirectory( Path.GetDirectoryName( path ) );
        using (var stream = File.Create( path )) {
            var encoder = new GifBitmapEncoder();
            TakeSnapshot( encoder, view, margin );
            encoder.Save( stream );
        }
    }
    private static void TakeSnapshot(BitmapEncoder encoder, IWpfTextView view, IWpfTextViewMargin margin) {
        ThreadHelper.ThrowIfNotOnUIThread();
        var element = GetRoot( view.VisualElement );
        {
            view.ViewportLeft = 0;
            view.DisplayTextLineContainingBufferPosition( new SnapshotPoint( view.TextSnapshot, 0 ), 0, ViewRelativePosition.Top );
            view.Caret.MoveTo( new SnapshotPoint( view.TextSnapshot, 0 ), PositionAffinity.Predecessor );
            element.UpdateLayout();
            margin.VisualElement.UpdateLayout();
            TakeSnapshot2( encoder, element );
        }
        while (view.TextViewLines.LastVisibleLine.End.Position < view.TextSnapshot.Length) {
            view.ViewScroller.ScrollViewportVerticallyByPixels( -10 );
            element.UpdateLayout();
            margin.VisualElement.UpdateLayout();
            TakeSnapshot2( encoder, element );
        }
    }
    private static void TakeSnapshot2(BitmapEncoder encoder, FrameworkElement element) {
        ThreadHelper.ThrowIfNotOnUIThread();
        var renderTargetBitmap = new RenderTargetBitmap( (int) element.ActualWidth, (int) element.ActualHeight, 96, 96, PixelFormats.Pbgra32 );
        renderTargetBitmap.Render( element );
        encoder.Frames.Add( BitmapFrame.Create( renderTargetBitmap ) );
    }
    private static FrameworkElement GetRoot(FrameworkElement element) {
        ThreadHelper.ThrowIfNotOnUIThread();
        while (element.GetVisualOrLogicalParent() != null) {
            element = (FrameworkElement) element.GetVisualOrLogicalParent();
        }
        return element;
    }

}
@Denis535 Denis535 changed the title How to force line numbers margin to repaint? How to force line numbers IWpfTextViewMargin to repaint? Dec 11, 2024
@Denis535 Denis535 changed the title How to force line numbers IWpfTextViewMargin to repaint? How to force line numbers IWpfTextViewMargin (WpfLineNumberMargin) to repaint? Dec 11, 2024
@Denis535
Copy link
Author

Denis535 commented Dec 11, 2024

It seems to work.

private static void UpdateLineNumbers(IWpfTextViewMargin margin) {
    var method = margin.GetType().GetMethod( "UpdateLineNumbers", System.Reflection.BindingFlags.NonPublic | System.Reflection.BindingFlags.Instance );
    method.Invoke( margin, new object[ 0 ] );
}

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

1 participant