ScrollViewer 在 WPF WindowsFormHost 中不起作用
声明:本页面是StackOverFlow热门问题的中英对照翻译,遵循CC BY-SA 4.0协议,如果您需要使用它,必须同样遵循CC BY-SA许可,注明原文地址和作者信息,同时你必须将它归于原作者(不是我):StackOverFlow
原文地址: http://stackoverflow.com/questions/14080580/
Warning: these are provided under cc-by-sa 4.0 license. You are free to use/share it, But you must attribute it to the original authors (not me):
StackOverFlow
ScrollViewer is not working in WPF WindowsFormHost
提问by Avinash Singh
I 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...
我的 WPF 表单中有带有 RichTextBox 的 WindowsFormHost,我已经为该 WindowsFormHost 提供了 ScrollViewer 但它不起作用,WindowsFormHost 在 ScrollViewer 之外......
My XAML is..
我的 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>
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://www.mycsharp.de/wbb2/thread.php?threadid=76625
http://www.mycsharp.de/wbb2/thread.php?threadid=76625
Thanks in advance..
提前致谢..
回答by Avinash Singh
Finally got the solution
终于得到解决方案
Create this Class in your solution for above problem, and take new class control (ScrollViewerWindowsFormsHost) instead of WindowsFormsHost
在上述问题的解决方案中创建此类,并采用新的类控件 (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 Code:
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>
回答by Marlon
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.
以防万一其他人有我的边缘情况,我有一个 WinForms UserControl 托管在 WPF UserControl 中,它本身托管在 WinForms 表单中(不要问......) - Avinash 提供的类没有修复我的剪辑问题。
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);
}
回答by CSharpie
We are using multiple ScrollViewersand also a ViewBoxso none of the mentioned solutions worked for us. So here is our soloution which can also deal with different DPI settings.
我们正在使用多个ScrollViewers和一个,ViewBox所以提到的解决方案都不适合我们。所以这是我们的解决方案,它也可以处理不同的 DPI 设置。
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;
}
}
}
回答by sbrett
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.
我发现 Marlon 的答案是最好的,但是如果用户有不同的 DPI 设置,它根本不起作用。这是 Marlon 的答案,但已针对 DPI 进行了扩展。我还添加了一个位置更改事件,因为我需要与滚动查看器中的 WindowsFormsHost 一起移动位于 WindowsFormsHost 内容顶部的弹出窗口。
#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
}
回答by VivekDev
If your WindowsFormsHost is to be placed inside a UserControl, then the answer presented by Avinashmay not work. So I had to tweak the ScrollViewerWindowsFormsHostclass as follows.
如果您的 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);
}
Thats it. Every thing remains the same.
就是这样。每件事都保持不变。
回答by Erti-Chris Eelmaa
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:
那是因为 ScrollViewer 不知道它必须滚动。如果您的鼠标在 RichTextBox 上,它将拦截所有键。您可以继承 RichTextBox(即 WndProc)并侦听鼠标滚轮事件,然后使用 RaiseEvent 将它们发送到 scrollViewer。不要忘记 WndProc 在与 WPF 不同的线程上运行,因此您需要执行以下操作:
case WM_MOUSEWHEEL: Dispatcher.BeginInvoke(new Action(() => VisualHelper.FindParent(richTextBox).RaiseEvent(..mouse wheel event with correct parameters..));
case WM_MOUSEWHEEL: Dispatcher.BeginInvoke(new Action(() => VisualHelper.FindParent(richTextBox).RaiseEvent(..mouse wheel event with正确的参数..));

