ScrollViewer is not working in WPF WindowsFormHost
Asked Answered
L

6

13

enter image description hereI have WindowsFormHost with a RichTextBox in my WPF form, i have given ScrollViewer for that WindowsFormHost but its not working, WindowsFormHost going outside of ScrollViewer...

My XAML is..

<ScrollViewer Background="DarkOrange" VerticalScrollBarVisibility="Auto" Height="80" MaxHeight="85" Margin="11,243,12,218" Width="756">
        <Canvas Height="100" Name="canvas1" Width="auto" >
            <WindowsFormsHost ClipToBounds="True" Height="120" Width="715" Margin="10,5,0,0" Name="winHostTEst" Background="Gray">
                <wf:RichTextBox BackColor="Cornsilk" Text="RichTextBox" x:Name="richTbTest" BorderStyle="None" Enabled="True" ForeColor="Black" Width="550" Multiline="True" ReadOnly="True" />
            </WindowsFormsHost>
        </Canvas>
    </ScrollViewer>

Here are two links with solution of this problem, but i am not able to implement that.. Please have a look to these links also and solve my problem..

links are:

http://blogs.msdn.com/b/ryanvog/archive/2009/01/20/clipping-legacy-content-hosted-inside-a-wpf-scrolling-region.aspx

http://www.mycsharp.de/wbb2/thread.php?threadid=76625

Thanks in advance..

Leaving answered 29/12, 2012 at 10:2 Comment(0)
L
14

Finally got the solution

Create this Class in your solution for above problem, and take new class control (ScrollViewerWindowsFormsHost) instead of WindowsFormsHost

using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Diagnostics;
using System.Linq;
using System.Text;
using System.Windows.Forms.Integration;
using System.Windows.Media;
using System.Runtime.InteropServices;
using System.Windows;
using System.Windows.Controls;

namespace WPFRichTextBox
{
class ScrollViewerWindowsFormsHost: WindowsFormsHost
{

    protected override void OnWindowPositionChanged(Rect rcBoundingBox)
    {
        base.OnWindowPositionChanged(rcBoundingBox);

        if (ParentScrollViewer == null)
            return;

        GeneralTransform tr = ParentScrollViewer.TransformToAncestor(MainWindow);
        var scrollRect = new Rect(new Size(ParentScrollViewer.ViewportWidth, ParentScrollViewer.ViewportHeight));
        scrollRect = tr.TransformBounds(scrollRect);

        var intersect = Rect.Intersect(scrollRect, rcBoundingBox);
        if (!intersect.IsEmpty)
        {
            tr = MainWindow.TransformToDescendant(this);
            intersect = tr.TransformBounds(intersect);
        }

        SetRegion(intersect);
    }

    protected override void OnVisualParentChanged(DependencyObject oldParent)
    {
        base.OnVisualParentChanged(oldParent);
        ParentScrollViewer = null;

        var p = Parent as FrameworkElement;
        while (p != null)
        {
            if (p is ScrollViewer)
            {
                ParentScrollViewer = (ScrollViewer)p;
                break;
            }

            p = p.Parent as FrameworkElement;
        }
    }

    private void SetRegion(Rect intersect)
    {
        using (var graphics = System.Drawing.Graphics.FromHwnd(Handle))
            SetWindowRgn(Handle, (new System.Drawing.Region(ConvertRect(intersect))).GetHrgn(graphics), true);
    }

    static System.Drawing.RectangleF ConvertRect(Rect r)
    {
        return new System.Drawing.RectangleF((float)r.X, (float)r.Y, (float)r.Width, (float)r.Height);
    }

    private Window _mainWindow;
    Window MainWindow
    {
        get
        {
            if (_mainWindow == null)
                _mainWindow = Window.GetWindow(this);

            return _mainWindow;
        }
    }

    ScrollViewer ParentScrollViewer { get; set; }

    [DllImport("User32.dll", SetLastError = true)]
    public static extern int SetWindowRgn(IntPtr hWnd, IntPtr hRgn, bool bRedraw);
}

}

XAML Code:

<Window x:Class="WPFRichTextBox.MainWindow"
    xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
    xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
    xmlns:wf="clr-namespace:System.Windows.Forms;assembly=System.Windows.Forms"
    xmlns:swfh="clr-namespace:WPFRichTextBox"
    Title="MainWindow" Height="600" Width="800" Background="LightBlue">
<Grid Loaded="Grid_Loaded">

    <ScrollViewer  Background="DarkOrange" VerticalScrollBarVisibility="Auto" Height="100"  Margin="11,160,12,301" Width="756" Name="scrollViewer1">
        <Canvas Height="200" Name="canvas1" Width="auto" >
      <swfh:ScrollableWindowsFormsHost ClipToBounds="True" Height="194" Width="715" Margin="10,5,0,0" Background="Gray">
                <wf:RichTextBox BackColor="Cornsilk" Text="RichTextBox" x:Name="richTbTest" BorderStyle="None" Enabled="True" ForeColor="Black" Width="550" Multiline="True" ReadOnly="True" />
      </swfh:ScrollableWindowsFormsHost>
        </Canvas>
    </ScrollViewer>
</Grid>

Leaving answered 31/12, 2012 at 9:40 Comment(5)
i used it without the parent ScrollViewer, but it's great and solved my problem. i wonder how didn't you get any credit for that :)Joannejoannes
Thanks it helped me alot. I know i'm late but you might want to add a way to manage the dpi factor which may cause incorrect dimensionCloraclorinda
I replaced the references to MainWindow with Window.GetWindow(this) but otherwise I think this is excellent. Thank you!Tami
f your WindowsFormsHost is to be placed inside a UserControl, then the answer may not work. See [below] (https://mcmap.net/q/850814/-scrollviewer-is-not-working-in-wpf-windowsformhost) for a tweak to get that work.Impassable
In my case, I needed to use VisualTreeHelper.GetParent() to get the ScrollContentPresenter. Then you can access the ScrollContentPresenter.ScrollOwner property to get the ScrollViewer. Similarly, it appears that @Marlon also needed a different approach to getting the `ScrollViewer1.Metz
R
9

Just in case someone else has my edge case, where I have a WinForms UserControl hosted inside a WPF UserControl, which itself is hosted inside a WinForms Form (don't ask...) - the class Avinash provided didn't fix my clipping issues.

But there was a modified version on a forum thread somewhere, which did the trick - so I thought I'd post it here for ease.

class WindowsFormsHostEx : WindowsFormsHost
{
    private PresentationSource _presentationSource;

    public WindowsFormsHostEx()
    {
        PresentationSource.AddSourceChangedHandler(this, SourceChangedEventHandler);
    }

    protected override void OnWindowPositionChanged(Rect rcBoundingBox)
    {
        base.OnWindowPositionChanged(rcBoundingBox);

        if (ParentScrollViewer == null)
            return;

        GeneralTransform tr = RootVisual.TransformToDescendant(ParentScrollViewer);
        var scrollRect = new Rect(new Size(ParentScrollViewer.ViewportWidth, ParentScrollViewer.ViewportHeight));

        var intersect = Rect.Intersect(scrollRect, tr.TransformBounds(rcBoundingBox));
        if (!intersect.IsEmpty)
        {
            tr = ParentScrollViewer.TransformToDescendant(this);
            intersect = tr.TransformBounds(intersect);
        }
        else
            intersect = new Rect();

        int x1 = (int)Math.Round(intersect.Left);
        int y1 = (int)Math.Round(intersect.Top);
        int x2 = (int)Math.Round(intersect.Right);
        int y2 = (int)Math.Round(intersect.Bottom);

        SetRegion(x1, y1, x2, y2);
    }

    protected override void Dispose(bool disposing)
    {
        base.Dispose(disposing);

        if (disposing)
            PresentationSource.RemoveSourceChangedHandler(this, SourceChangedEventHandler);
    }

    private void SourceChangedEventHandler(Object sender, SourceChangedEventArgs e)
    {
        ParentScrollViewer = FindParentScrollViewer();
    }

    private ScrollViewer FindParentScrollViewer()
    {
        DependencyObject vParent = this;
        ScrollViewer parentScroll = null;
        while (vParent != null)
        {
            parentScroll = vParent as ScrollViewer;
            if (parentScroll != null)
                break;

            vParent = LogicalTreeHelper.GetParent(vParent);
        }
        return parentScroll;
    }

    private void SetRegion(int x1, int y1, int x2, int y2)
    {
        SetWindowRgn(Handle, CreateRectRgn(x1, y1, x2, y2), true);
    }

    private Visual RootVisual
    {
        get
        {
            if (_presentationSource == null)
                _presentationSource = PresentationSource.FromVisual(this);

            return _presentationSource.RootVisual;
        }
    }

    private ScrollViewer ParentScrollViewer { get; set; }

    [DllImport("User32.dll", SetLastError = true)]
    static extern int SetWindowRgn(IntPtr hWnd, IntPtr hRgn, bool bRedraw);

    [DllImport("gdi32.dll")]
    static extern IntPtr CreateRectRgn(int nLeftRect, int nTopRect, int nRightRect, int nBottomRect);
}
Rumpf answered 6/8, 2013 at 15:33 Comment(2)
This has a drawback - scrolling of the hosted control is not as smooth as with the standard WindowsFormsHost. Apart from that, it works.Daven
This worked for me where the accepted solution did not. specifically I'm hosting a winforms control in a WPF UserControl in a scrollregion.Tullius
S
4

I found Marlon's answer to be the best, however it did not work at all if the user had a different DPI setting. This is Marlon's answer, but solved to scale for DPI. I also added a location changed event since I needed to move a popup that was on top of the WindowsFormsHost content in tandem with the WindowsFormsHost in the scrollviewer.

#region Using Declarations

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

#endregion

public class WindowsFormsHostEx : WindowsFormsHost
{
    #region DllImports
    [DllImport("User32.dll", SetLastError = true)]
    static extern int SetWindowRgn(IntPtr hWnd, IntPtr hRgn, bool bRedraw);

    [DllImport("gdi32.dll")]
    static extern IntPtr CreateRectRgn(int nLeftRect, int nTopRect, int nRightRect, int nBottomRect);

    #endregion

    #region Events
    public event EventHandler LocationChanged;
    #endregion

    #region Members
    private PresentationSource _presentationSource;
    #endregion

    #region Properties
    private ScrollViewer ParentScrollViewer { get; set; }
    private bool Scrolling { get; set; }
    public bool Resizing { get; set; }
    private Visual RootVisual
    {
        get
        {
            _presentationSource = PresentationSource.FromVisual(this);
            return _presentationSource.RootVisual;
        }
    }
    #endregion

    #region Constructors
    public WindowsFormsHostEx()
    {
        PresentationSource.AddSourceChangedHandler(this, SourceChangedEventHandler);
    }
    #endregion

    #region Methods

    protected override void OnWindowPositionChanged(Rect rcBoundingBox)
    {
        DpiScale dpiScale = VisualTreeHelper.GetDpi(this);

        base.OnWindowPositionChanged(rcBoundingBox);

        Rect newRect = ScaleRectDownFromDPI(rcBoundingBox, dpiScale);
        Rect finalRect;
        if (ParentScrollViewer != null)
        {
            ParentScrollViewer.ScrollChanged += ParentScrollViewer_ScrollChanged;
            ParentScrollViewer.SizeChanged += ParentScrollViewer_SizeChanged;
            ParentScrollViewer.Loaded += ParentScrollViewer_Loaded;
        }

        if (Scrolling || Resizing)
        {
            if (ParentScrollViewer == null)
                return;
            MatrixTransform tr = RootVisual.TransformToDescendant(ParentScrollViewer) as MatrixTransform;

            var scrollRect = new Rect(new Size(ParentScrollViewer.ViewportWidth, ParentScrollViewer.ViewportHeight));
            var c = tr.TransformBounds(newRect);

            var intersect = Rect.Intersect(scrollRect, c);
            if (!intersect.IsEmpty)
            {
                tr = ParentScrollViewer.TransformToDescendant(this) as MatrixTransform;
                intersect = tr.TransformBounds(intersect);
                finalRect = ScaleRectUpToDPI(intersect, dpiScale);
            }
            else
                finalRect = intersect = new Rect();

            int x1 = (int)Math.Round(finalRect.X);
            int y1 = (int)Math.Round(finalRect.Y);
            int x2 = (int)Math.Round(finalRect.Right);
            int y2 = (int)Math.Round(finalRect.Bottom);

            SetRegion(x1, y1, x2, y2);
            this.Scrolling = false;
            this.Resizing = false;

        }
        LocationChanged?.Invoke(this, new EventArgs());
    }

    private void ParentScrollViewer_Loaded(object sender, RoutedEventArgs e)
    {
        this.Resizing = true;
    }

    private void ParentScrollViewer_SizeChanged(object sender, SizeChangedEventArgs e)
    {
        this.Resizing = true;
    }

    private void ParentScrollViewer_ScrollChanged(object sender, ScrollChangedEventArgs e)
    {
        if (e.VerticalChange != 0 || e.HorizontalChange != 0 || e.ExtentHeightChange != 0 || e.ExtentWidthChange != 0)
            Scrolling = true;
    }

    protected override void Dispose(bool disposing)
    {
        base.Dispose(disposing);

        if (disposing)
        {
            PresentationSource.RemoveSourceChangedHandler(this, SourceChangedEventHandler);
            _presentationSource = null;
        }
    }

    private void SourceChangedEventHandler(Object sender, SourceChangedEventArgs e)
    {
        if (ParentScrollViewer != null)
        {
            ParentScrollViewer.ScrollChanged -= ParentScrollViewer_ScrollChanged;
            ParentScrollViewer.SizeChanged -= ParentScrollViewer_SizeChanged;
            ParentScrollViewer.Loaded -= ParentScrollViewer_Loaded;
        }
        ParentScrollViewer = FindParentScrollViewer();
    }

    private ScrollViewer FindParentScrollViewer()
    {
        DependencyObject vParent = this;
        ScrollViewer parentScroll = null;
        while (vParent != null)
        {
            parentScroll = vParent as ScrollViewer;
            if (parentScroll != null)
                break;

            vParent = LogicalTreeHelper.GetParent(vParent);
        }
        return parentScroll;
    }

    private void SetRegion(int x1, int y1, int x2, int y2)
    {
        SetWindowRgn(Handle, CreateRectRgn(x1, y1, x2, y2), true);
    }

    public static  Rect ScaleRectDownFromDPI(Rect _sourceRect, DpiScale dpiScale)
    {
        double dpiX = dpiScale.DpiScaleX;
        double dpiY = dpiScale.DpiScaleY;
        return new Rect(new Point(_sourceRect.X / dpiX, _sourceRect.Y / dpiY), new System.Windows.Size(_sourceRect.Width / dpiX, _sourceRect.Height / dpiY));
    }

    public static Rect ScaleRectUpToDPI(Rect _toScaleUp, DpiScale dpiScale)
    {
        double dpiX = dpiScale.DpiScaleX;
        double dpiY = dpiScale.DpiScaleY;
        return new Rect(new Point(_toScaleUp.X * dpiX, _toScaleUp.Y * dpiY), new System.Windows.Size(_toScaleUp.Width * dpiX, _toScaleUp.Height * dpiY));
    }
    #endregion
}
Superintendency answered 21/10, 2017 at 0:13 Comment(0)
C
3

We are using multiple ScrollViewers and also a ViewBox so none of the mentioned solutions worked for us. So here is our soloution which can also deal with different DPI settings.

using System;
using System.Collections.Generic;
using System.Linq;
using System.Runtime.InteropServices;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Forms.Integration;
using System.Windows.Media;
using System.Windows.Threading;


namespace XYZ
{

    public class ClippingWindowsFormsHost : WindowsFormsHost
    {
        private readonly DispatcherTimer _updateTimer;

        private Rect _bounds;

        private PresentationSource _source;

        public ClippingWindowsFormsHost()
        {
            PresentationSource.AddSourceChangedHandler(this, _sourceChangedEventHandler);

            _updateTimer = new DispatcherTimer(DispatcherPriority.Render);
            _updateTimer.Tick += _updateTick;
            _updateTimer.Interval = TimeSpan.FromMilliseconds(100);
        }

        private void _updateTick(object sender, EventArgs e)
        {
            _updateTimer.Stop();

            if (_source == null)
                return;


            // Get the Rect of the scrollviewer on screen.
            Rect scrollRect = _getScrollRect();

            // apply dpi settings
            scrollRect = _scaleDpi(scrollRect);

            if (scrollRect.Width > 0 && scrollRect.Height > 0) // if the rect is valid...
            {
                int x1 = (int) Math.Ceiling(scrollRect.X);
                int y1 = (int) Math.Ceiling(scrollRect.Y);
                int x2 = (int) Math.Ceiling(scrollRect.Right);
                int y2 = (int) Math.Ceiling(scrollRect.Bottom);

                SetWindowRgn(Handle, CreateRectRgn(x1, y1, x2, y2), true);
            }
            else
                SetWindowRgn(Handle, CreateRectRgn(0, 0, 0, 0), true);

        }

        private Rect _scaleDpi(Rect rect)
        {
            if (_source.CompositionTarget != null)
            {
                Matrix transformToDevice = _source.CompositionTarget.TransformToDevice;
                if (!transformToDevice.IsIdentity)
                {
                    Point scaledSize = transformToDevice.Transform(new Point(rect.Width, rect.Height));
                    rect = new Rect(rect.X, rect.Y, scaledSize.X, scaledSize.Y);
                }
            }

            return rect;
        }

        [DllImport("User32.dll", SetLastError = true)]
        private static extern int SetWindowRgn(IntPtr hWnd, IntPtr hRgn, bool bRedraw);

        [DllImport("gdi32.dll")]
        private static extern IntPtr CreateRectRgn(int nLeftRect, int nTopRect, int nRightRect, int nBottomRect);

        protected override void OnWindowPositionChanged(Rect rcBoundingBox)
        {
            base.OnWindowPositionChanged(rcBoundingBox);
            _updateClipping(rcBoundingBox);
        }

        private void _updateClipping(Rect bounds)
        {
            if (_source == null || _bounds == bounds)
                return;

            _bounds = bounds;

            // Only update clipping in certain intervals, otherwise splitpanels can create huge cpu load
            _updateTimer.Stop();
            _updateTimer.Start();
        }

        private Rect _getScrollRect()
        {
            ScrollViewer scrollViewer = _getTopScrollViewer();

            // Get the screenposition of the scrollviewer
            Point topLeft = scrollViewer.PointToScreen(new Point(0, 0));
            Point bottomRight = scrollViewer.PointToScreen(new Point(scrollViewer.ViewportWidth, scrollViewer.ViewportHeight));

            Rect scrollRect = new Rect(topLeft, bottomRight);



            // Get "this" position and use it to offset the scrollrect
            // because that is basically the scrolled distance
            Point myPosition = PointToScreen(new Point());
            scrollRect.Offset(-myPosition.X, -myPosition.Y);

            return scrollRect;
        }

        private ScrollViewer _getTopScrollViewer()
        {
            DependencyObject parent = this;
            ScrollViewer lastViewer = null;
            while ((parent = VisualTreeHelper.GetParent(parent)) != null)
            {
                ScrollViewer viewer = parent as ScrollViewer;
                if (viewer != null)
                    lastViewer = viewer;
            }

            return lastViewer;
        }

        protected override void Dispose(bool disposing)
        {
            base.Dispose(disposing);

            if (disposing)
            {
                _updateTimer.Stop();
                PresentationSource.RemoveSourceChangedHandler(this, _sourceChangedEventHandler);
            }
        }

        private void _sourceChangedEventHandler(object sender, SourceChangedEventArgs e)
        {
            _updateTimer.Stop();
            _source = e.NewSource;
        }
    }
}
Codie answered 20/12, 2017 at 13:55 Comment(0)
I
1

If your WindowsFormsHost is to be placed inside a UserControl, then the answer presented by Avinash may not work. So I had to tweak the ScrollViewerWindowsFormsHost class as follows.

    public class ScrollViewerWindowsFormsHost : WindowsFormsHost
    {
        protected override void OnWindowPositionChanged(Rect rcBoundingBox)
        {
            base.OnWindowPositionChanged(rcBoundingBox);

            if (ParentScrollViewer == null)
                //return; // Instead, you set the ParentScrollViewr by calling the following method.
                SetParentScrollViewer();

            GeneralTransform tr = ParentScrollViewer.TransformToAncestor(MainWindow);

            var scrollRect = new Rect(new Size(ParentScrollViewer.ViewportWidth, ParentScrollViewer.ViewportHeight));
            scrollRect = tr.TransformBounds(scrollRect);

            var intersect = Rect.Intersect(scrollRect, rcBoundingBox);
            if (!intersect.IsEmpty)
            {
                tr = MainWindow.TransformToDescendant(this);
                intersect = tr.TransformBounds(intersect);
            }

            SetRegion(intersect);
        }

        // This is new a new method. This is called from the above method.
        private void SetParentScrollViewer()
        {
            if (ParentScrollViewer is ScrollViewer)
                return; // that means its already set;

            var p = Parent as FrameworkElement;
            while (p != null)
            {
                if (p is ScrollViewer)
                {
                    ParentScrollViewer = (ScrollViewer)p;
                    break;
                }

                p = p.Parent as FrameworkElement;
            }
        }
        // Just comment out this method, you dont need this any more. You set the parent Scroll Viewer by calling SetParentScrollViewer Method.
        //protected override void OnVisualParentChanged(DependencyObject oldParent)
        //{
        //    base.OnVisualParentChanged(oldParent);
        //    ParentScrollViewer = null;

        //    var p = Parent as FrameworkElement;
        //    while (p != null)
        //    {
        //        if (p is ScrollViewer)
        //        {
        //            ParentScrollViewer = (ScrollViewer)p;
        //            break;
        //        }

        //        p = p.Parent as FrameworkElement;

        //    }
        //}

        private void SetRegion(Rect intersect)
        {
            using (var graphics = System.Drawing.Graphics.FromHwnd(Handle))
                SetWindowRgn(Handle, (new System.Drawing.Region(ConvertRect(intersect))).GetHrgn(graphics), true);
        }

        static System.Drawing.RectangleF ConvertRect(Rect r)
        {
            return new System.Drawing.RectangleF((float)r.X, (float)r.Y, (float)r.Width, (float)r.Height);
        }

        private Window _mainWindow;
        Window MainWindow
        {
            get
            {
                if (_mainWindow == null)
                    _mainWindow = Window.GetWindow(this);

                return _mainWindow;
            }
        }

        ScrollViewer ParentScrollViewer { get; set; }

        [DllImport("User32.dll", SetLastError = true)]
        public static extern int SetWindowRgn(IntPtr hWnd, IntPtr hRgn, bool bRedraw);
    }

Thats it. Every thing remains the same.

Impassable answered 14/5, 2016 at 12:50 Comment(0)
N
0

That's because ScrollViewer does not know it has to scroll. If your mouse is on RichTextBox, it will intercept all keys. You can subclass the RichTextBox(namely WndProc) and listen for mousewheel events and then send them to scrollViewer using RaiseEvent. Don't forget that WndProc runs on a seperate thread than WPF so you need to do something like:

case WM_MOUSEWHEEL: Dispatcher.BeginInvoke(new Action(() => VisualHelper.FindParent(richTextBox).RaiseEvent(..mouse wheel event with correct parameters..));

Nappie answered 29/12, 2012 at 10:9 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.