问题
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...
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..
回答1:
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>
回答2:
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);
}
回答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;
}
}
}
回答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
}
回答5:
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.
回答6:
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..));
来源:https://stackoverflow.com/questions/14080580/scrollviewer-is-not-working-in-wpf-windowsformhost