Laden...

Ableitung von WindowsFormsHost - korrektes Clipping innerhalb ScrollViewer

Erstellt von dIeGoLi vor 14 Jahren Letzter Beitrag vor 11 Jahren 12.911 Views
D
dIeGoLi Themenstarter:in
28 Beiträge seit 2005
vor 14 Jahren
Ableitung von WindowsFormsHost - korrektes Clipping innerhalb ScrollViewer

Hallo zusammen,

Aktuell besteht in WPF das Problem, dass ein WindowsFormsHost innerhalb eines ScrollViewer nicht zu gebrauchen ist, da der WindowsFormsHost in einem eigenen Fenster über dem WPF Inhalt "sitzt" und beim scrollen nicht verschwindet sondern über die WPF Oberfläche gezeichnet wird.

Das Problem ist im Blog von Ryan (Nachname nicht bekannt) mit Beispiel beschrieben und beschreibt auch den Ansatz nachdem ich meine Lösung geschrieben habe: Ryan's Blog.

Meine Lösung ist eine Ableitung des WindowsFormsHost welche das Clipping bei registriertem ScrollViewer übernimmt.

/** Code editiert, neue Version im nächsten Post **/

Es war nicht einfach und zeitaufwendig für mich diese Lösung zu schreiben und ich denke sie ist deswegen auch noch nicht perfekt. Ich würde mich über Kritik sowie Verbesserungsvorschläge freuen.

Liebe Grüsse
Diego

D
dIeGoLi Themenstarter:in
28 Beiträge seit 2005
vor 11 Jahren

Weil mir einige Leute Emails geschrieben habe, möchte ich den code hier aktualisieren.
Because there were some people mailing me, i'd like to update the class with some minor changes:


using System;
using System.Diagnostics;
using System.Runtime.InteropServices;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Forms.Integration;
using System.Windows.Interop;
using System.Windows.Media;

// Creation date: 24.09.2009
// Author: Diego Frehner (fdi@root.ch)

// This class is based on the explanation of http://blogs.msdn.com/ryanvog/archive/2009/01/20/clipping-legacy-content-hosted-inside-a-wpf-scrolling-region.aspx

namespace fdi {
  
  /// <summary>This class is a Windows Forms host which supports being inside a scroll container.</summary>
  /// <remarks>When you nest Windows Forms controls inside a WPF visual container the Windows Forms control actually 
  /// sits on top of the WPF window and maintains its own HWND. If the container is a scroll container (ScrollView),
  /// the clipping will not be done correctly. This windows forms sub class does the clipping.</remarks>
  public class ScrollableWindowsFormsHost : WindowsFormsHost {

    /// <summary>Top left x position of window region</summary>
    private Int32 _topLeftX = -1;

    /// <summary>Top left y position of window region</summary>
    private Int32 _topLeftY = -1;

    /// <summary>Bottom right x position of window region</summary>
    private Int32 _bottomRightX = -1;

    /// <summary>Bottom right x position of window region</summary>
    private Int32 _bottomRightY = -1;

    /// <summary>Scrollviewer on which scrollchanged event this instance listens.</summary>
    private ScrollViewer _scrollViewer;

    /// <summary>Track whether Dispose has been called.</summary>
    private Boolean _disposed = false;

    //--------------------------------------------------------------------------------
    /// <summary>Creates a scrollabel Windows Forms host.</summary>
    public ScrollableWindowsFormsHost() {
    }

    //--------------------------------------------------------------------------------
    #region Methods
    //--------------------------------------------------------------------------------

    //--------------------------------------------------------------------------------
    /// <summary>Creates a handle to a region specified through upper left and bottom right corner.</summary>
    /// <param name="x1">Upper left X</param>
    /// <param name="y1">Upper left Y</param>
    /// <param name="x2">Bottom right X</param>
    /// <param name="y2">Bottom right Y</param>
    /// <returns>Returns a handle to the created region.</returns>
    [DllImport("GDI32.DLL", EntryPoint = "CreateRectRgn")]
    private static extern IntPtr CreateRectRgn(Int32 x1, Int32 y1, Int32 x2, Int32 y2);

    //--------------------------------------------------------------------------------
    /// <summary>Sets the specified region to the specified window. That means the specified window can paint
    /// itself only in the specified region.</summary>
    /// <param name="hWnd">Handle to the window</param>
    /// <param name="hRgn">Hanlde to the region</param>
    /// <param name="bRedraw">Boolean indicating whether the specified region should be redrawn.</param>
    /// <returns>Returns nonzero if success, otherwise zero.</returns>
    [DllImport("User32.dll", SetLastError = true)]
    private static extern Int32 SetWindowRgn(IntPtr hWnd, IntPtr hRgn, Boolean bRedraw);

    //--------------------------------------------------------------------------------
    /// <summary>Performs application-defined tasks associated with freeing, releasing, or resetting unmanaged resources.</summary>
    /// <param name="bDisposing"><b>true</b> if the method has been called by applicaton code.</param>
    protected override void Dispose(Boolean bDisposing) {
      if (bDisposing) {
        try {
          if (!_disposed) {
            if (_scrollViewer != null) {
              _scrollViewer.ScrollChanged -= new ScrollChangedEventHandler(ScrollHandler);
            }
          }
          _disposed = true;
        }
        finally {
          base.Dispose(bDisposing);
        }
      }
    }
    
    //--------------------------------------------------------------------------------
    /// <summary>Register a scrollviewer on which this instance listens.</summary>
    /// <param name="scrollViewer">ScrollViewer</param>
    public void RegisterScrollViewer(ScrollViewer scrollViewer) {
      if (scrollViewer == null) {
        throw new ArgumentNullException("scollViewer");
      }
      if (_scrollViewer != null) {
        UnregisterScrollViewer(_scrollViewer);
      }
      scrollViewer.ScrollChanged += new ScrollChangedEventHandler(ScrollHandler);
      _scrollViewer = scrollViewer;

      // If you move focus inside a scrollviewer the scrollviewer adjusts its scroll value that the focused element is visible.
      // Unfortunately with a windows forms host this doesn't work.
      // So we register a got focus event and adjust the scrol value ourself.
      GotFocus += new RoutedEventHandler(ScrollableWindowsFormsHost_GotFocus);
    }

    //--------------------------------------------------------------------------------
    /// <summary>Unregister a scrollviewer.</summary>
    /// <param name="scrollViewer">ScrollViewer</param>
    public void UnregisterScrollViewer(ScrollViewer scrollViewer) {
      if (scrollViewer == null) {
        throw new ArgumentNullException("scollViewer");
      }
      scrollViewer.ScrollChanged -= new ScrollChangedEventHandler(ScrollHandler);
      GotFocus -= new RoutedEventHandler(ScrollableWindowsFormsHost_GotFocus);
      _scrollViewer = null;
    }
    #endregion

    //--------------------------------------------------------------------------------
    #region Events
    //--------------------------------------------------------------------------------

    //--------------------------------------------------------------------------------
    /// <summary>Scroll handler manages the clipping of this windows forms host.</summary>
    /// <param name="sender">Sender</param>
    /// <param name="ea">Event argument</param>
    private void ScrollHandler(Object sender, ScrollChangedEventArgs ea) {
      PresentationSource presentationSource = HwndSource.FromVisual(this);
      if (presentationSource == null) {
        return;
      }
      Visual rootVisual = presentationSource.RootVisual;
      if (rootVisual == null) {
        return;
      }
      ScrollViewer scrollViewer = (ScrollViewer)sender;
      if (!scrollViewer.IsDescendantOf(rootVisual)) {
        return;
      }

      // calculate the rect of scrollview with 0/0 at upper left corner of root visual
      GeneralTransform transform = scrollViewer.TransformToAncestor(rootVisual);
      Rect scrollRect = transform.TransformBounds(new Rect(0, 0, scrollViewer.ViewportWidth, scrollViewer.ViewportHeight));

      // calculate the rect of the scrollable windows forms host instance with 0/0 at upper left corner of root visual
      transform = this.TransformToAncestor(rootVisual);
      Rect hostRect = transform.TransformBounds(new Rect(this.Padding.Left, this.Padding.Right,
        this.RenderSize.Width, this.RenderSize.Height));

      // calculate the intersection of the two rect
      Rect intersectRect = Rect.Intersect(scrollRect, hostRect);

      Int32 topLeftX = 0;
      Int32 topLeftY = 0;
      Int32 bottomRightX = 0;
      Int32 bottomRightY = 0;
      if (intersectRect != Rect.Empty) {
        // calculate the HRGN points with 0/0 at upper left corner of scrollable windows forms host instance
        topLeftX = (Int32)(intersectRect.TopLeft.X - hostRect.TopLeft.X);
        topLeftY = (Int32)(intersectRect.TopLeft.Y - hostRect.TopLeft.Y);
        bottomRightX = (Int32)(intersectRect.BottomRight.X - hostRect.TopLeft.X);
        bottomRightY = (Int32)(intersectRect.BottomRight.Y - hostRect.TopLeft.Y);
      }

      // because the CreateRectRgn / SetWindowRgn api calls are slow we call them only if it has a visual effect
      if (_topLeftX != topLeftX || _topLeftY != topLeftY || _bottomRightX != bottomRightX || _bottomRightY != bottomRightY) {
        _topLeftX = topLeftX;
        _topLeftY = topLeftY;
        _bottomRightX = bottomRightX;
        _bottomRightY = bottomRightY;
        // create HRGN object and set it to the windows forms host instance
        IntPtr hrgn = CreateRectRgn(_topLeftX, _topLeftY, _bottomRightX, _bottomRightY);
        SetWindowRgn(this.Handle, hrgn, true);
      }
    }

    //--------------------------------------------------------------------------------
    /// <summary>Occurs on got focus and adjusts the scroll value of the registered scrollviewer that this is visible.</summary>
    /// <param name="sender">Sender</param>
    /// <param name="e">Event argument</param>
    private void ScrollableWindowsFormsHost_GotFocus(Object sender, RoutedEventArgs e) {
      Debug.Assert(_scrollViewer != null);
      // calculate the rect of this windows forms host instance with 0/0 at upper left corner of scrollviewer
      GeneralTransform transform = this.TransformToAncestor(_scrollViewer);
      Rect hostRect = transform.TransformBounds(new Rect(this.Padding.Left, this.Padding.Right,
        this.RenderSize.Width, this.RenderSize.Height));

      // if this element is not visible scroll to an offset which makes this windows forms host visible
      if (hostRect.Bottom > _scrollViewer.ViewportHeight) {
        _scrollViewer.ScrollToVerticalOffset(hostRect.Bottom - _scrollViewer.ViewportHeight + _scrollViewer.VerticalOffset);
      }
    }
    #endregion
  }
}

Ergänzung zur Verwendung des abgeleiteten Hosts aus xaml.

  1. Namespace / Referenz hinzufügen
xmlns:swfh="clr-namespace:fdi"

  1. Ableitung verwenden
<swfh:ScrollableWindowsFormsHost... 
  1. Anstelle der Register/Unregister Funktionen kann im Konstruktor des ScrollableWindowsFormsHost ein Klassen Eventhandler verwendet werden (Gruss an Avinash Singh)

//--------------------------------------------------------------------------------------
/// <summary>Creates a scrollabel Windows Forms host.</summary>
public ScrollableWindowsFormsHost() {
  EventManager.RegisterClassHandler(typeof(ScrollViewer), ScrollViewer.ScrollChangedEvent, new ScrollChangedEventHandler(ScrollHandler));
}