ScrollViewer не работает в WPF WindowsFormHost
У меня есть 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://www.mycsharp.de/wbb2/thread.php?threadid=76625
Заранее спасибо..
6 ответов
Наконец-то получил решение
Создайте этот класс в своем решении для вышеуказанной проблемы и возьмите новый элемент управления класса (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>
На всякий случай, если у кого-то еще есть мой крайний случай, когда у меня есть WinForms UserControl, размещенный внутри WPF UserControl, который сам размещен внутри формы 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);
}
Я нашел ответ Марлона лучшим, но он вообще не работал, если у пользователя были другие настройки DPI. Это ответ Марлона, но он решен в масштабе для 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
}
Мы используем несколько 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;
}
}
}
Если ваш 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);
}
Это оно. Все остается неизменным.
Это потому, что ScrollViewer не знает, что он должен прокручивать. Если ваша мышь находится на RichTextBox, она будет перехватывать все клавиши. Вы можете создать подкласс RichTextBox (а именно WndProc) и прослушивать события mousewheel, а затем отправлять их в scrollViewer с помощью RaiseEvent. Не забывайте, что WndProc работает в отдельном потоке, чем WPF, поэтому вам нужно сделать что-то вроде:
case WM_MOUSEWHEEL: Dispatcher.BeginInvoke (new Action (() => VisualHelper.FindParent (richTextBox).RaiseEvent (.. событие колеса мыши с правильными параметрами..));