WPF : Move and resize window at once time - wpf

In Win32 API, function SetWindowPos provided an easy way to move and resize window at once.
However, in WPF class Window doesn't have a method like SetWindowPos. So I must code like the following:
this.Left += e.HorizontalChange;
this.Top += e.VerticalChange;
this.Width = newWidth;
this.Height = newHeight;
Of course, it works well, but it's not simple. And it looks dirty.
How can i move a window and resize at once?
Is there an API?

I know you've already solved your problem, but I'll post a solution that I found in case it helps others.
Basically, You must declare that SetWindowsPos as an imported function from Win32 this is the signature
[DllImport("user32.dll", CharSet = CharSet.Auto)]
[return: MarshalAs(UnmanagedType.Bool)]
private static extern bool SetWindowPos(IntPtr hWnd, IntPtr hWndInsertAfter, int X, int Y, int cx, int cy, SetWindowPosFlags uFlags);
The function needs the hWnd of your window, in order to get it you can add an handler on the initialization of your windows (for example you could listen for the "SourceInitialized" event) and store that value in a private member of the class:
hwndSource = PresentationSource.FromVisual((Visual)sender) as HwndSource;
WPF manages device independent pixels, so you needs even a converter from dip to real pixel for your screen. This is done with these lines:
var source = PresentationSource.FromVisual(this);
Matrix transformToDevice = source.CompositionTarget.TransformToDevice;
Point[] p = new Point[] { new Point(this.Left + e.HorizontalChange, this.Top), new Point(this.Width - e.HorizontalChange, this.Height) };
transformToDevice.Transform(p);
Finally you can call SetWindowsPos:
SetWindowPos(this.hwndSource.Handle, IntPtr.Zero, Convert.ToInt32(p[0].X), Convert.ToInt32(p[0].Y), Convert.ToInt32(p[1].X), Convert.ToInt32(p[1].Y), SetWindowPosFlags.SWP_SHOWWINDOW);
Sources:
Win32 SetWindowPos
WPF Graphics Rendering

You could wrap your code in a helper method. Just like this:
public static class WindowExtensions {
public static void MoveAndResize( this Window value, double horizontalChange, double verticalChange, double width, double height ) {
value.Left += horizontalChange;
value.Top += verticalChange;
value.Width = width;
value.Height = height;
}
}
So your calling code looks like this:
this.MoveAndResize( 10, 10, 1024, 768 );
I've left off namespace and using declaration, keep that in mind when copying.
Edit:
You could also use the API. Personally I stick with the managed code unless I really need to use the API. But that is up to you.

Related

How to set WPF window size to fit the screen?

I want to shrink the window so it fits on a screen.
Normally my window has size of 1000x800 so there is enough space in most scenarios but I'm also checking screen area to make sure it's not bigger. However my boss gave me his pc where he has screen scale set to 150% and the window becomes bigger then available space. I thought I solved this by dividing screen size by it's current dpi but turns out this does not work in all scenarios.
This is the method I'm using to set the size based on current DPI:
private void setMainWindowDimensions() {
//set window size and position with respect to current screen
var windowInteropHelper = new WindowInteropHelper(nativeWindow);
var screen = System.Windows.Forms.Screen.FromHandle(windowInteropHelper.Handle);
var dpi = screen.getScale();
nativeWindow.Width = Math.Min(screen.WorkingArea.Width / dpi.x, 1000);
nativeWindow.Height = Math.Min(screen.WorkingArea.Height / dpi.y, 800);
}
And here is the getScale() extension method I'm using to get the scale factor:
//https://stackoverflow.com/questions/29438430/how-to-get-dpi-scale-for-all-screens
public static (uint x, uint y) GetDpi(this S screen, DpiType dpiType = default) {
var pnt = new System.Drawing.Point(screen.Bounds.Left + 1, screen.Bounds.Top + 1);
var mon = MonitorFromPoint(pnt, 2/*MONITOR_DEFAULTTONEAREST*/);
GetDpiForMonitor(mon, dpiType, out var dpiX, out var dpiY);
return (dpiX, dpiY);
}
//https://msdn.microsoft.com/en-us/library/windows/desktop/dd145062(v=vs.85).aspx
[DllImport("User32.dll")]
private static extern IntPtr MonitorFromPoint([In]System.Drawing.Point pt, [In]uint dwFlags);
//https://msdn.microsoft.com/en-us/library/windows/desktop/dn280510(v=vs.85).aspx
[DllImport("Shcore.dll")]
private static extern IntPtr GetDpiForMonitor([In]IntPtr hmonitor, [In]DpiType dpiType, [Out]out uint dpiX, [Out]out uint dpiY);
This is is form the How to get DPI scale for all screens? answer.
Above code worked when I changed the scale of main monitor on my work pc, it correctly returned dpi of (1.5, 1.5). Few months later and my boss is back with he's computer saying it still don't work.
I figured out the DPI is taken form main screen, even if the window is opened on the other and here I still can't figure out how to get correct values.
I was following this article But it doesn't work at all. The methods form the NativeHelpers project are returning some values but the result is the same as with original method. Basically the SetPerMonitorDPIAware fails and I don't know why. I used slightly modified version and I thought that there is something wrong with my project configuration, but even when I create new WPF project, extending the window from PerMonitorDPIWindow class, the result is the same - it throws "Enabling Per-monitor DPI Failed."
private void createWindow() {
var c = logCtx("Creating main applicaton window", ONE_TIME_LOG);
var pma = PerMonitorDPIHelper.SetPerMonitorDPIAware();
if (pma == 0) log("Could not set per monitor DPI awareness.", WARNING_LOG);
else log("Per monitor awareness was set.", SUCCESS_LOG);
window = new CWindow();
nativeWindow.AllowDrop = true;
setMainWindowDimensions();
c.close();
}
private void setMainWindowDimensions() {
//set window size and position with respect to current screen
var windowInteropHelper = new WindowInteropHelper(nativeWindow);
var screen = System.Windows.Forms.Screen.FromHandle(windowInteropHelper.Handle);
var dpi = screen.getScale();
var sdpi = PerMonitorDPIHelper.GetSystemDPI();
var wdpi = PerMonitorDPIHelper.GetDpiForWindow(windowInteropHelper.Handle);
log($#"DPI awareness: {PerMonitorDPIHelper.getPerMonitorDPIAware()}");
log($#"system DPI: {sdpi}, window DPI: {wdpi}, screen DPI: {dpi}");
nativeWindow.Width = Math.Min(screen.WorkingArea.Width / dpi.x, 1000);
nativeWindow.Height = Math.Min(screen.WorkingArea.Height / dpi.y, 800);
}
private void onLoaded(object sender, RoutedEventArgs e) {
var c = logCtx("Main window loaded", ONE_TIME_LOG);
setMainWindowDimensions();
c.close();
}
Here you can see returned DPI is 1.5 even if the window is opened on screen that is not scaled.
Since I have a drawing app, I'm also using the following method to determine real scale coordinates:
private (double x, double y) density {
get {
var m = PresentationSource.FromVisual(Application.Current.MainWindow).CompositionTarget
.TransformToDevice;
return (96 / m.M11 / 25.4, 96 / m.M22 / 25.4);
}
}
This solution is from this question, and it also don't work in various dpi scenarios.
I've also read in one of the comments that shcore is available only since Windows 8. Our PCs have Win 10 but we also want to support Windows 7.
There is also on more strange thing. My boss PC is actually a laptop with only one, main screen, yet it still doesn't get proper DPI. I don't have access to it right now so don't have logs I've just added...
Is there any method which works in all scenarios? It's seem that such such thing should require 2 lines of code yet it's became so convoluted I've already wasted 2 day on this and I'm out of ideas :(

WM_HELP stops being send when vtk is started or spy++ is running

In our software we occasionally use sending WM_HELP via SendMessage api to a control. Normally the "HelpRequested" event is then fired (or up in the parent hierarchy until an event handler is registered).
We included an external complex 3d visualization library called "VTK" and after that, this Message passing does not work anymore. In trying to track down the problem I used Spy++ to see whether the message is shown there and realized that running spy++ is generating the same problem! (Also without any vtk stuff). It can be shown with this little Program:
using System.Diagnostics;
using System.Runtime.InteropServices;
using System.Windows.Forms;
namespace TestHelp
{
public partial class Form1 : Form
{
[System.Runtime.InteropServices.StructLayout(System.Runtime.InteropServices.LayoutKind.Sequential)]
struct HelpInfo
{
public uint cbSize;
public int iContextType;
public int iCtrlID;
public int hItemHandle;
public int dwContextID;
public int MouseX;
public int MouseY;
}
[DllImport("user32.DLL", EntryPoint = "SendMessage", SetLastError = true)]
private static extern int SendHelpMessage(int hWnd, uint Msg, uint wparam, ref HelpInfo helpinfo);
public static void RaiseHelp(Control ctrl)
{
HelpInfo helpInfo = new HelpInfo
{
cbSize = 48,
iContextType = 1,
iCtrlID = 0,
hItemHandle = ctrl.Handle.ToInt32(),
dwContextID = 0,
MouseX = 10,
MouseY = 10,
};
var res = SendHelpMessage(ctrl.Handle.ToInt32(), 0x053, 0, ref helpInfo);
Debug.WriteLine($"SendMessage returns:{res}");
}
public Form1()
{
InitializeComponent();
button1.HelpRequested += (sender, hlpevent) => { Trace.WriteLine("HelpRequested called"); };
timer = new Timer() {Interval = 1000, Enabled = true};
timer.Tick += (sender, args) => RaiseHelp(button1);
}
private Timer timer;
}
}
The form only contains a single button named "button1".
When you start in debugger you see "HelpRequested called" in Output window every second. When you start Spy++, nothing more, just start, it will stop that! When closing spy++ it continues to work again. Does everyone have an explanation for this behaviour? What is Spy++ doing with my application? My hope is that the same mechanism is responsible for the same problem with vtk (there only in-process, though).
Of course, using win32 api SendMessage may seem inappropriate for a WinForms application, but we don't have time now to refactor all that stuff, and I nevertheless want to understand whats happening here!
Btw.: user window message are not affected (WM_USER to 0x7FFF), checked by overriding WndProc. WM_HELP also does not show in WndProc while spy++ is running, btw.
Problem was wrong size for HelpInfo.cbSize. In 64-bit mode it is 40, in 32-bit it is 28. Yes I should use sizeof(), but this is only allowed in "unsafe" mode.
But how the hell spy++ or VTK interfere with this?

Showing part of UserControl like CroppedBitmap

I can't English very well yet. please understand even if you can't understand me clearly.
I have huge data table in UserControl.xaml, but downscale this UserControl object showing whole in MainWindow.
I want same size datatable showing of partially UserControl in MainWindow.
Like this image display way:
<Image>
<Image.Source>
<CroppedBitmap Source="<path to source image>" SourceRect="20,20,50,50"/>
</Image.Source>
</Image>
Showing UserControl in MainWindow like a SourceRect.
If I understand you correctly, you have several options. The first way, and I think the easiest is to use ViewBox control.
1. ViewBox
The Viewbox control inherited from Decorator is used to stretch or scale a child element, but it is scaled proportionally, ie you can not set the him size such 300x100.
Example
<Viewbox Width="300"
Height="300">
<DataGrid>
...
</DataGrid>
</ViewBox>
The second way is to use a screen capture of your control, that you would like to show, and then if you want to use CroppedBitmap.
2. Capturing screen
I found a great article by Pete Brown on this subject here:
Capturing Screen Images in WPF using GDI, Win32 and a little WPF Interop Help
In this article is an example, and it looks like this:
ScreenCapture
class ScreenCapture
{
public static BitmapSource CaptureFullScreen(bool addToClipboard)
{
return CaptureRegion(
User32.GetDesktopWindow(),
(int)SystemParameters.VirtualScreenLeft,
(int)SystemParameters.VirtualScreenTop,
(int)SystemParameters.VirtualScreenWidth,
(int)SystemParameters.VirtualScreenHeight,
addToClipboard);
}
// capture a window. This doesn't do the alt-prtscrn version that loses the window shadow.
// this version captures the shadow and optionally inserts a blank (usually white) area behind
// it to keep the screen shot clean
public static BitmapSource CaptureWindow(IntPtr hWnd, bool recolorBackground, Color substituteBackgroundColor, bool addToClipboard)
{
Int32Rect rect = GetWindowActualRect(hWnd);
Window blankingWindow = null;
if (recolorBackground)
{
blankingWindow = new Window();
blankingWindow.WindowStyle = WindowStyle.None;
blankingWindow.Title = string.Empty;
blankingWindow.ShowInTaskbar = false;
blankingWindow.AllowsTransparency = true;
blankingWindow.Background = new SolidColorBrush(substituteBackgroundColor);
blankingWindow.Show();
int fudge = 20;
blankingWindow.Left = rect.X - fudge / 2;
blankingWindow.Top = rect.Y - fudge / 2;
blankingWindow.Width = rect.Width + fudge;
blankingWindow.Height = rect.Height + fudge;
}
// bring the to-be-captured window to capture to the foreground
// there's a race condition here where the blanking window
// sometimes comes to the top. Hate those. There is surely
// a non-WPF native solution to the blanking window which likely
// involves drawing directly on the desktop or the target window
User32.SetForegroundWindow(hWnd);
BitmapSource captured = CaptureRegion(
hWnd,
rect.X,
rect.Y,
rect.Width,
rect.Height,
true);
if (blankingWindow != null)
blankingWindow.Close();
return captured;
}
// capture a region of the full screen
public static BitmapSource CaptureRegion(int x, int y, int width, int height, bool addToClipboard)
{
return CaptureRegion(User32.GetDesktopWindow(), x, y, width, height, addToClipboard);
}
// capture a region of a the screen, defined by the hWnd
public static BitmapSource CaptureRegion(
IntPtr hWnd, int x, int y, int width, int height, bool addToClipboard)
{
IntPtr sourceDC = IntPtr.Zero;
IntPtr targetDC = IntPtr.Zero;
IntPtr compatibleBitmapHandle = IntPtr.Zero;
BitmapSource bitmap = null;
try
{
// gets the main desktop and all open windows
sourceDC = User32.GetDC(User32.GetDesktopWindow());
//sourceDC = User32.GetDC(hWnd);
targetDC = Gdi32.CreateCompatibleDC(sourceDC);
// create a bitmap compatible with our target DC
compatibleBitmapHandle = Gdi32.CreateCompatibleBitmap(sourceDC, width, height);
// gets the bitmap into the target device context
Gdi32.SelectObject(targetDC, compatibleBitmapHandle);
// copy from source to destination
Gdi32.BitBlt(targetDC, 0, 0, width, height, sourceDC, x, y, Gdi32.SRCCOPY);
// Here's the WPF glue to make it all work. It converts from an
// hBitmap to a BitmapSource. Love the WPF interop functions
bitmap = System.Windows.Interop.Imaging.CreateBitmapSourceFromHBitmap(
compatibleBitmapHandle, IntPtr.Zero, Int32Rect.Empty,
BitmapSizeOptions.FromEmptyOptions());
if (addToClipboard)
{
//Clipboard.SetImage(bitmap); // high memory usage for large images
IDataObject data = new DataObject();
data.SetData(DataFormats.Dib, bitmap, false);
Clipboard.SetDataObject(data, false);
}
}
catch (Exception ex)
{
throw new ScreenCaptureException(string.Format("Error capturing region {0},{1},{2},{3}", x, y, width, height), ex);
}
finally
{
Gdi32.DeleteObject(compatibleBitmapHandle);
User32.ReleaseDC(IntPtr.Zero, sourceDC);
User32.ReleaseDC(IntPtr.Zero, targetDC);
}
return bitmap;
}
// this accounts for the border and shadow. Serious fudgery here.
private static Int32Rect GetWindowActualRect(IntPtr hWnd)
{
Win32Rect windowRect = new Win32Rect();
Win32Rect clientRect = new Win32Rect();
User32.GetWindowRect(hWnd, out windowRect);
User32.GetClientRect(hWnd, out clientRect);
int sideBorder = (windowRect.Width - clientRect.Width)/2 + 1;
// sooo, yeah.
const int hackToAccountForShadow = 4;
Win32Point topLeftPoint = new Win32Point(windowRect.Left - sideBorder, windowRect.Top - sideBorder);
//User32.ClientToScreen(hWnd, ref topLeftPoint);
Int32Rect actualRect = new Int32Rect(
topLeftPoint.X,
topLeftPoint.Y,
windowRect.Width + sideBorder * 2 + hackToAccountForShadow,
windowRect.Height + sideBorder * 2 + hackToAccountForShadow);
return actualRect;
}
}
Example of using:
private void CaptureRegionButton_Click(object sender, RoutedEventArgs e)
{
CapturedImage.Source = ScreenCapture.CaptureRegion(100, 100, 500, 500, true);
}
private void CaptureScreenButton_Click(object sender, RoutedEventArgs e)
{
CapturedImage.Source = ScreenCapture.CaptureFullScreen(true);
}

How do I get the current mouse screen coordinates in WPF?

How to get current mouse coordination on the screen?
I know only Mouse.GetPosition() which get mousePosition of element, but I want to get the coordination without using element.
Or in pure WPF use PointToScreen.
Sample helper method:
// Gets the absolute mouse position, relative to screen
Point GetMousePos() => _window.PointToScreen(Mouse.GetPosition(_window));
To follow up on Rachel's answer.
Here's two ways in which you can get Mouse Screen Coordinates in WPF.
1.Using Windows Forms. Add a reference to System.Windows.Forms
public static Point GetMousePositionWindowsForms()
{
var point = Control.MousePosition;
return new Point(point.X, point.Y);
}
2.Using Win32
[DllImport("user32.dll")]
[return: MarshalAs(UnmanagedType.Bool)]
internal static extern bool GetCursorPos(ref Win32Point pt);
[StructLayout(LayoutKind.Sequential)]
internal struct Win32Point
{
public Int32 X;
public Int32 Y;
};
public static Point GetMousePosition()
{
var w32Mouse = new Win32Point();
GetCursorPos(ref w32Mouse);
return new Point(w32Mouse.X, w32Mouse.Y);
}
Do you want coordinates relative to the screen or the application?
If it's within the application just use:
Mouse.GetPosition(Application.Current.MainWindow);
If not, I believe you can add a reference to System.Windows.Forms and use:
System.Windows.Forms.Control.MousePosition;
If you try a lot of these answers out on different resolutions, computers with multiple monitors, etc. you may find that they don't work reliably. This is because you need to use a transform to get the mouse position relative to the current screen, not the entire viewing area which consists of all your monitors. Something like this...(where "this" is a WPF window).
var transform = PresentationSource.FromVisual(this).CompositionTarget.TransformFromDevice;
var mouse = transform.Transform(GetMousePosition());
public System.Windows.Point GetMousePosition()
{
var point = Forms.Control.MousePosition;
return new Point(point.X, point.Y);
}
This works without having to use forms or import any DLLs:
using System.Windows;
using System.Windows.Input;
/// <summary>
/// Gets the current mouse position on screen
/// </summary>
private Point GetMousePosition()
{
// Position of the mouse relative to the window
var position = Mouse.GetPosition(Window);
// Add the window position
return new Point(position.X + Window.Left, position.Y + Window.Top);
}
You may use combination of TimerDispatcher (WPF Timer analog) and Windows "Hooks" to catch cursor position from operational system.
[DllImport("user32.dll")]
[return: MarshalAs(UnmanagedType.Bool)]
public static extern bool GetCursorPos(out POINT pPoint);
Point is a light struct. It contains only X, Y fields.
public MainWindow()
{
InitializeComponent();
DispatcherTimer dt = new System.Windows.Threading.DispatcherTimer();
dt.Tick += new EventHandler(timer_tick);
dt.Interval = new TimeSpan(0,0,0,0, 50);
dt.Start();
}
private void timer_tick(object sender, EventArgs e)
{
POINT pnt;
GetCursorPos(out pnt);
current_x_box.Text = (pnt.X).ToString();
current_y_box.Text = (pnt.Y).ToString();
}
public struct POINT
{
public int X;
public int Y;
public POINT(int x, int y)
{
this.X = x;
this.Y = y;
}
}
This solution is also resolving the problem with too often or too infrequent parameter reading so you can adjust it by yourself. But remember about WPF method overload with one arg which is representing ticks not milliseconds.
TimeSpan(50); //ticks
If you're looking for a 1 liner, this does well.
new Point(Mouse.GetPosition(mWindow).X + mWindow.Left, Mouse.GetPosition(mWindow).Y + mWindow.Top)
The + mWindow.Left and + mWindow.Top makes sure the position is in the right place even when the user drags the window around.
Mouse.GetPosition(mWindow) gives you the mouse position relative to the parameter of your choice.
mWindow.PointToScreen() convert the position to a point relative to the screen.
So mWindow.PointToScreen(Mouse.GetPosition(mWindow)) gives you the mouse position relative to the screen, assuming that mWindow is a window(actually, any class derived from System.Windows.Media.Visual will have this function), if you are using this inside a WPF window class, this should work.
I wanna use this code
Point PointA;
private void Button_PreviewMouseUp(object sender, MouseButtonEventArgs e) {
PointA = e.MouseDevice.GetPosition(sender as UIElement);
}
private void Button_Click(object sender, RoutedEventArgs e) {
// use PointA Here
}

Autofit WinForms RichTextBox to its contents

Does anybody know how can I dynamically resize a RichTextBox control to its contents?
I guess I am far too late but take a look at this
It's just two code lines:
private void rtb_ContentsResized(object sender, ContentsResizedEventArgs e)
{
((RichTextBox)sender).Height = e.NewRectangle.Height + 5;
}
Again assuming a fixed font could you do something like:
using (Graphics g = CreateGraphics())
{
richTextBox.Height = (int)g.MeasureString(richTextBox.Text,
richTextBox.Font, richTextBox.Width).Height;
}
It's kind of a pain - the C# RichTextBox is often frustrating to work with. Are you trying to size the box big enough to hold its contents without any scrollbar?
If the RichTextBox has a constant font, you can use TextRenderer.MeasureText to simply measure the required size, and pass in the box's width as a constraint.
The ContentsResized event gives you a ContentsResizedEventsArgs, which gives you a NewRectangle which tells you how big the text area is. But it only fires when the text changes, which isn't as useful if you simply want to measure an existing richtextbox (although you could probably just do something hacky like set the box's text to itself, triggering this event).
There are also a bunch of Win32 api calls, like using EM_GETLINECOUNT (http://ryanfarley.com/blog/archive/2004/04/07/511.aspx), etc.
A really cheap solution (one that is potentially fraught with problems) is to simultaneously fill an autofit label with text using the same font and size, then just copy the width of the label to the width of the RTB.
So, like this:
RichTextBox rtb = new RichTextBox();
rtb.Text = "this is some text";
rtb.Font = new Font("Franklin Gothic Medium Cond", 10, FontStyle.Regular);
Label fittingLabel = new Label();
fittingLabel.Text = rtb.Text;
fittingLabel.Font = rtb.Font;
fittingLabel.AutoSize = true;
//Not sure if it's necessary to add the label to the form for it to autosize...
fittingLabel.Location = new Point(-1000,-1000);
this.Controls.Add(fittingLabel);
rtb.Width = fittingLabel.Width;
this.Controls.Remove(fittingLabel);
I found a solution for the Rich text box height issues.. i have modified it a for general use..
Create following structs in your application....
[StructLayout(LayoutKind.Sequential)]
public struct RECT {
public Int32 left;
public Int32 top;
public Int32 right;
public Int32 bottom;
}
[StructLayout(LayoutKind.Sequential)]
public struct SCROLLBARINFO {
public Int32 cbSize;
public RECT rcScrollBar;
public Int32 dxyLineButton;
public Int32 xyThumbTop;
public Int32 xyThumbBottom;
public Int32 reserved;
[MarshalAs(UnmanagedType.ByValArray, SizeConst = 6)]
public Int32[] rgstate;
}
Create following private variables in your class for form (where ever you need to calculate rich text height)
private UInt32 SB_VERT = 1;
private UInt32 OBJID_VSCROLL = 0xFFFFFFFB;
[DllImport("user32.dll")]
private static extern
Int32 GetScrollRange(IntPtr hWnd, UInt32 nBar, out Int32 lpMinPos, out Int32 lpMaxPos);
[DllImport("user32.dll")]
private static extern
Int32 GetScrollBarInfo(IntPtr hWnd, UInt32 idObject, ref SCROLLBARINFO psbi);
Add following method to your Class for form
private int CalculateRichTextHeight(string richText) {
int height = 0;
RichTextBox richTextBox = new RichTextBox();
richTextBox.Rtf = richText;
richTextBox.Height = this.Bounds.Height;
richTextBox.Width = this.Bounds.Width;
richTextBox.WordWrap = false;
int nHeight = 0;
int nMin = 0, nMax = 0;
SCROLLBARINFO psbi = new SCROLLBARINFO();
psbi.cbSize = Marshal.SizeOf(psbi);
richTextBox.Height = 10;
richTextBox.ScrollBars = RichTextBoxScrollBars.Vertical;
int nResult = GetScrollBarInfo(richTextBox.Handle, OBJID_VSCROLL, ref psbi);
if (psbi.rgstate[0] == 0) {
GetScrollRange(richTextBox.Handle, SB_VERT, out nMin, out nMax);
height = (nMax - nMin);
}
return height;
}
You may need to modify above method to make it work as per your requirement...
Make sure to send Rtf string as parameter to method not normal text and also make sure to assign available width and height to the Richtextbox variable in the method...
You can play with WordWrap depending on your requirement...
It's much easier to use GetPreferredSize, as described in this answer. Then you don't need to wait for a ContentsResized event.

Resources