ScrollViewer не работает в WPF WindowsFormHost

enter image description here У меня WindowsFormHost с RichTextBox в моей форме WPF, я дал ScrollViewer для этого WindowsFormHost, но его не работает, WindowsFormHost выходит за пределы ScrollViewer...

Мой XAML есть..

<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>

Вот две ссылки с решением этой проблемы, но я не в состоянии реализовать это. Пожалуйста, посмотрите также на эти ссылки и решите мою проблему.

ссылки:

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

Спасибо заранее.

Ответ 1

Через 2 дня получим решение

Создайте этот класс в своем решении для вышеуказанной проблемы и возьмите новый класс управления (ScrollViewerWindowsFormsHost) вместо 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:

<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>

Ответ 2

На всякий случай у кого-то есть мой край, где у меня есть WinForms UserControl, размещенный внутри пользовательского элемента управления WPF, который сам размещается внутри формы WinForms (не спрашивайте...) - класс Avinash предоставил не исправить мои проблемы отсечения.

Но была какая-то измененная версия на форуме, где был трюк, поэтому я думал, что разместил его здесь для удобства.

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

Ответ 3

Решение, размещенное Avanash Singh, не работает для меня.

Мой код выглядит следующим образом: Я пробовал с и без холста вокруг ScrollViewerWindowsFormsHost.

<ext:ScrollViewerWindowsFormsHost x:Name="BrowserHost" ClipToBounds="True" Height="230" Width="Auto" Margin="10,5,0,0" Background="Gray">
                    <wf:WebBrowser x:Name="VisualEditor" />
            </ext:ScrollViewerWindowsFormsHost>

В моей реализации у меня есть окно, содержащее ленту, действующую как панель инструментов, заголовок (в красном), содержащийся на границе, а затем раздел сведений, содержащийся в scrollviewer, который имеет панели (которые являются usercontrols), чем вы можете expand/contract. На некоторых из этих панелей есть редактор html, который позволяет вносить изменения в скомпилированное представление или в html.

Вот как выглядит форма перед прокруткой: http://i1377.photobucket.com/albums/ah56/gareth_white1/inside_of_scroll_viewer_zps85098c62.png

Это происходит, когда это происходит неправильно, когда я прокручиваю вверх, и элемент управления WebBrowser/ScrollViewerWindowsFormsHost входит поверх моего заголовка вне средства просмотра прокрутки.

http://i1377.photobucket.com/albums/ah56/gareth_white1/outside_of_scrollviewer_zps35e7fd87.png

То, что я хотел бы сделать, это то, что когда я прокручиваю нижнюю часть, все идет за заголовком (не infront):

Мне интересно, вызвана ли моя проблема типом элемента управления, который я пытаюсь разместить (WebBrowser).

Ответ 4

Я нашел ответ Марлона лучшим, однако он не работал вообще, если у пользователя была другая настройка DPI. Это ответ Марлона, но он решил масштабироваться для ДОИ. Я также добавил событие изменения местоположения, так как мне нужно было переместить всплывающее окно, которое было поверх содержимого WindowsFormsHost, в тандеме с WindowsFormsHost в 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
}

Ответ 5

Это потому, что ScrollViewer не знает, что он должен прокручиваться. Если ваша мышь находится в RichTextBox, она перехватит все ключи. Вы можете подклассифицировать RichTextBox (а именно WndProc) и прослушивать события mousewheel, а затем отправлять их в scrollViewer с помощью RaiseEvent. Не забывайте, что WndProc работает в отдельном потоке, чем в WPF, поэтому вам нужно сделать что-то вроде:

case WM_MOUSEWHEEL: Dispatcher.BeginInvoke(новое действие (() = > VisualHelper.FindParent(richTextBox).RaiseEvent(.. событие колеса мыши с правильными параметрами..));

Ответ 6

Если ваш WindowsFormsHost будет помещен в UserControl, то ответ представленный Avinash, может не сработать. Поэтому мне пришлось настроить класс ScrollViewerWindowsFormsHost следующим образом.

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

Вот оно. Все остается неизменным.

Ответ 7

Мы используем несколько ScrollViewers, а также ViewBox, поэтому ни один из упомянутых решений не работал для нас. Итак, вот наше soloution

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

namespace Something
{
    /// <summary>
    /// </summary>
    public class ClippingWindowsFormsHost : WindowsFormsHost
    {
        private Rect _previousBounds;
        private List<ScrollViewer> _scrollViewers;
        private PresentationSource _source;

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

        [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)
        {
            if (_previousBounds == rcBoundingBox)
            {
                base.OnWindowPositionChanged(rcBoundingBox);
                return; // optimization
            }
            _previousBounds = rcBoundingBox;

            if (_scrollViewers != null && _scrollViewers.Count != 0)
            {
                Rect scrollRect = Rect.Empty;

                double offsetX = 0d;
                double offsetY = 0d;
                // List is in order outer to inner
                for (int index = 0; index < _scrollViewers.Count; index++)
                {
                    ScrollViewer scrollViewer = _scrollViewers[index];

                    MatrixTransform transform = (MatrixTransform) scrollViewer.TransformToAncestor(_source.RootVisual);

                    Rect viewPort = new Rect(0, 0, scrollViewer.ViewportWidth, scrollViewer.ViewportHeight);
                    viewPort = transform.TransformBounds(viewPort);

                    // The ScrollOffset of the innermost Scrollviewer is the one we are interested in, because this is the one that scrolls our content
                    // all others only scroll our position on the screen
                    if (index == _scrollViewers.Count - 1)
                    {
                        Rect scaledScrollOffset = transform.TransformBounds(new Rect(0, 0, scrollViewer.HorizontalOffset, scrollViewer.VerticalOffset));

                        // We substract our position from the offsets because Clipping works with Controlcoordinates, not Screencoordinates.
                        offsetX = scaledScrollOffset.Width - viewPort.TopLeft.X;
                        offsetY = scaledScrollOffset.Height - viewPort.TopLeft.Y;
                    }


                    scrollRect = scrollRect == Rect.Empty ? viewPort : Rect.Intersect(scrollRect, viewPort);
                }

                scrollRect.Offset(offsetX, offsetY);

                // This transformation is needed to account for any scaling that comes by a sorrounding ViewBox. 
                MatrixTransform mytransform = (MatrixTransform) TransformToAncestor(_source.RootVisual);
                rcBoundingBox = mytransform.TransformBounds(rcBoundingBox);


                Rect intersect = Rect.Intersect(new Rect(rcBoundingBox.Size), scrollRect);

                int x1 = (int) Math.Floor(intersect.X);
                int y1 = (int) Math.Floor(intersect.Y);
                int x2 = (int) Math.Floor(intersect.Right);
                int y2 = (int) Math.Floor(intersect.Bottom);

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

            base.OnWindowPositionChanged(rcBoundingBox);
        }

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

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

        private void _sourceChangedEventHandler(object sender, SourceChangedEventArgs e)
        {
            _scrollViewers = new List<ScrollViewer>();
            _source = e.NewSource;
            DependencyObject parent = this;
            while ((parent = VisualTreeHelper.GetParent(parent)) != null)
            {
                ScrollViewer viewer = parent as ScrollViewer;
                if (viewer != null)
                    _scrollViewers.Insert(0, viewer);
            }
        }
    }
}