ScrollViewer is not working in WPF WindowsFormHost

前端 未结 6 770
半阙折子戏
半阙折子戏 2020-12-14 23:00

\"enterI have WindowsFormHost with a RichTextBox in my WPF form, i have given ScrollViewer for

相关标签:
6条回答
  • 2020-12-14 23:40

    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.

    0 讨论(0)
  • 2020-12-14 23:41

    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
    }
    
    0 讨论(0)
  • 2020-12-14 23:52

    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>
    

    0 讨论(0)
  • 2020-12-14 23:52

    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);
    }
    
    0 讨论(0)
  • 2020-12-14 23:54

    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;
            }
        }
    }
    
    0 讨论(0)
  • 2020-12-15 00:01

    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..));

    0 讨论(0)
提交回复
热议问题