How to simulate multimedia key press (in C)? - c

Modern keyboards have special multimedia keys, e.g. 'Pause/Play' or 'Open Web Browser'. Is it possible to write a program that "presses" these keys?
I would prefer solution in C, but I would accept a language agnostic solution, too.

Use the SendInput Windows API, if you are talking about programming under Win32.
You need to build INPUT structures, setting the type member to INPUT_KEYBOARD. In the ki member (KEYBDINPUT type), you can set vk (virtual key) to your desired VK code (for example, VK_MEDIA_NEXT_TRACK, VK_MEDIA_STOP).
Virtual key codes: http://msdn.microsoft.com/en-us/library/dd375731(v=VS.85).aspx
SendInput Function: http://msdn.microsoft.com/en-us/library/ms646310(v=VS.85).aspx
I've not tested the following, but should be like this:
KEYBDINPUT kbi;
kbi.wVk = VK_MEDIA_STOP; // Provide your own
kbi.wScan = 0;
kbi.dwFlags = 0; // See docs for flags (mm keys may need Extended key flag)
kbi.time = 0;
kbi.dwExtraInfo = (ULONG_PTR) GetMessageExtraInfo();
INPUT input;
input.type = INPUT_KEYBOARD;
input.ki = kbi;
SendInput(1, &input, sizeof(INPUT));

Even more easier than the accepted answer is keybd_event. No structs just a call with numeric parameters.
// C# example:
public const int KEYEVENTF_EXTENDEDKEY = 1;
public const int KEYEVENTF_KEYUP = 2;
public const int VK_MEDIA_NEXT_TRACK = 0xB0;
public const int VK_MEDIA_PLAY_PAUSE = 0xB3;
public const int VK_MEDIA_PREV_TRACK = 0xB1;
[DllImport("user32.dll", SetLastError = true)]
public static extern void keybd_event(byte virtualKey, byte scanCode, uint flags, IntPtr extraInfo);
keybd_event(VK_MEDIA_PREV_TRACK, 0, KEYEVENTF_EXTENDEDKEY, IntPtr.Zero);

Related

How to get keyboard scancode of pressed Key?

I make an WPF app where hotkeys are chosen for their physical position on the keyboard, rather than for what letter they represent.
Because different users use different layout (qwerty, azerty, etc), my app must be smart enough to be layout-agnostic.
I designated QWERTY as the layout of reference.
So for example a French user uses AZERTY. Therefore what the Q key is in QWERTY is A in AZERTY.
So in order to be agnostic I imagine I should do the following operation:
// Represents, in QWERTY, the top-left letter key.
Key myKey = Key.Q;
// ScanCode represent the physical key on the keyboard.
var scanCode = GetScanCodeFromKey(myKey, new CultureInfo("en-US"));
// Would be "A" if the user uses AZERTY.
Key agnosticKey = getKeyFromScanCode(scanCode, CultureInfo.CurrentCulture);
This seems feasible, but I cannot find a function that does GetScanCodeFromKey and getKeyFromScanCode.
The only relevant post I've found is Is it possible to create a keyboard layout that is identical to the keyboard used?
but unfortunately it seems to be made for win32, and not WPF. MapVirtualKeyEx is not accessible from WPF.
I have to remark that writing high level code (a WPF appliacation) that has to depend on low level concepts (hardware/machine details) is never a good idea. High level applications should depend on high level abstractions (input) of the low level data output. You always want to add an extra abstraction layer/interface between high level and low level or software and hardware.
Custom mapping
You can of course do the mapping yourself. Using Win32 API allows you to get the current active input language and deal with the low level concept of a keyboard layout.
First chose the base layout of your application e.g. en-GB. Then create a lookup table based on the supported layout languages.
Each entry of this table is another table used as conversion table: from current layout to application's internal base layout.
To simplify lookup table creation you can create a minimal table by focusing on the supported keys only.
You can create the conversion table from a file(s) e.g XML or JSON to the lookup table. This provides simple extensibility to an already deployed application.
private Dictionary<int, Dictionary<Key, Key>> LanguageLayouts { get; set; }
public MainWindow()
{
InitializeComponent();
this.LanguageLayouts = new Dictionary<int, Dictionary<Key, Key>>
{
{
CultureInfo.GetCultureInfo("fr-FR").LCID, new Dictionary<Key, Key>
{
{Key.A, Key.Q},
{Key.Z, Key.W},
{Key.E, Key.E},
{Key.R, Key.R},
{Key.T, Key.T},
{Key.Y, Key.Y}
}
}
};
}
private void OnPreviewKeyUp(object sender, KeyEventArgs e)
{
int lcid = GetCurrentKeyboardLayout().LCID;
if (this.LanguageLayouts.TryGetValue(lcid, out Dictionary<Key, Key> currentLayoutMap))
{
if (currentLayoutMap.TryGetValue(e.Key, out Key convertedKey))
{
HandlePressedKey(convertedKey);
}
}
}
[DllImport("user32.dll")] static extern IntPtr GetForegroundWindow();
[DllImport("user32.dll")] static extern uint GetWindowThreadProcessId(IntPtr hwnd, IntPtr proccess);
[DllImport("user32.dll")] static extern IntPtr GetKeyboardLayout(uint thread);
public CultureInfo GetCurrentKeyboardLayout()
{
IntPtr foregroundWindow = GetForegroundWindow();
uint foregroundProcess = GetWindowThreadProcessId(foregroundWindow, IntPtr.Zero);
int keyboardLayout = GetKeyboardLayout(foregroundProcess).ToInt32() & 0xFFFF;
return new CultureInfo(keyboardLayout);
}
Since keyboard scancodes are a low-level concept (hardware <-> OS), you have to use Win32 API to hook onto the Windows message loop and filter keystroke messages.
Windows keyboard drivers usually use the scancode set 1 code table to convert scancodes to actual key values.
Windows API scancodes are decimal based i.e presented as integer, while codes in reference tables are documented hex based.
Scancode Based Solution #1: System.Windows.Interop.HwndSource (Recommended)
HwndSource is a WPF ready wrapper that provides access to the Win32 window handle.
Note that HwndSource implements IDisposable.
You can listen to the Windows message loop by registering a callback of type HwndSourceHook by calling HwndSource.AddHook.
Also don't forget to unregister the callback by calling HwndSource.RemoveHook.
To handle keystroke messages, you have to monitor the loop for WM_KEYUP and WM_KEYDOWN messages. See Keyboard Input Notifications for a list of keyboard input messages.
See System-Defined Messages for an overview of all system messages e.g., Clipborad messages.
private async void OnLoaded(object sender, RoutedEventArgs e)
{
var hwndSource = PresentationSource.FromVisual(this) as HwndSource;
if (hwndSource != null)
{
hwndSource.AddHook(MainWindow.OnWindowsMessageReceived);
}
// Define filter constants (see docs for message codes)
private const int WM_KEYDOWN = 0x0100;
private const int WM_KEYUP = 0x0101;
private static IntPtr OnWindowsMessageReceived(IntPtr hwnd, int msg, IntPtr wParam, IntPtr lParam, ref bool handled)
{
switch (msg)
{
// Equivalent of Keyboard.KeyDown event (or UIElement.KeyDown)
case MainWindow.WM_KEYDOWN:
{
// Parameter 'wParam' is the Virtual-Key code.
// Convert the Win32 Virtual-Key into WPF Key
// using 'System.Windows.Input.KeyInterop'.
// The Key is the result of the virtual code that is already translated to the current layout.
// While the scancode remains the same, the Key changes according to the keyboard layout.
Key key = KeyInterop.KeyFromVirtualKey(wParam.ToInt32());
// TODO::Handle Key
// Parameter 'lParam' bit 16 - 23 represents the decimal scancode.
// See scancode set 1 for key to code mapping (note: table uses hex codes!): https://www.win.tue.nl/~aeb/linux/kbd/scancodes-10.html).
// Use bit mask to get the scancode related bits (16 - 23).https://www.win.tue.nl/~aeb/linux/kbd/scancodes-10.html)
var scancode = (lParam.ToInt64() >> 16) & 0xFF;
//TODO::Handle scancode
break;
}
// Equivalent of Keyboard.KeyUp event (or UIElement.KeyUp)
case MainWindow.WM_KEYUP:
{
// Parameter 'wParam' is the Virtual-Key code.
// Convert the Win32 Virtual-Key into WPF Key
// using 'System.Windows.Input.KeyInterop'.
// The Key is the result of the virtual code that is already translated to the current layout.
// While the scancode remains the same, the Key changes according to the keyboard layout.
Key key = KeyInterop.KeyFromVirtualKey(wParam.ToInt32());
// TODO::Handle Key
// Parameter 'lParam' bit 16 - 23 represents the decimal scancode.
// See scancode set 1 for key to code mapping (note: table uses hex codes!): https://www.win.tue.nl/~aeb/linux/kbd/scancodes-10.html).
// Use bit mask to get the scancode related bits (16 - 23).
var scancode = (lParam.ToInt64() >> 16) & 0xFF;
//TODO::Handle scancode
break;
}
}
return IntPtr.Zero;
}
Scancode based solution #2: SetWindowsHookExA
private void Initialize()
{
// Keep a strong reference to the delegate.
// Otherwise it will get garbage collected (Win32 API won't keep a reference to the delegate).
this.CallbackDelegate = OnKeyPressed;
// Argument '2' (WH_KEYBOARD) defines keystroke message monitoring (message filter).
// Argument 'OnKeyPressed' defines the callback.
SetWindowsHookEx(2, this.CallbackDelegate, IntPtr.Zero, AppDomain.GetCurrentThreadId());
}
protected delegate IntPtr HookProc(int code, IntPtr wParam, IntPtr lParam);
private HookProc CallbackDelegate { get; set; }
[DllImport("user32.dll")]
protected static extern IntPtr SetWindowsHookEx(int code, HookProc func, IntPtr hInstance, int threadID);
[DllImport("user32.dll")]
static extern IntPtr CallNextHookEx(IntPtr hhk, int nCode, IntPtr wParam, IntPtr lParam);
private IntPtr OnKeyPressed(int code, IntPtr wParam, IntPtr lParam)
{
// Parameter 'wParam' is the Virtual-Key code.
// Convert the Win32 Virtual-Key into WPF Key
// using 'System.Windows.Input.KeyInterop'
Key key = KeyInterop.KeyFromVirtualKey(wParam.ToInt32());
// TODO::Handle Key
// Parameter 'lParam' bit 16 - 23 represents the decimal scancode.
// See scancode set 1 for key to code mapping (note: table uses hex codes!): https://www.win.tue.nl/~aeb/linux/kbd/scancodes-10.html).
// Use bit mask to get the scancode related bits (16 - 23).
var scancode = (lParam.ToInt64() >> 16) & 0xFF;
// TODO::Handle scancode
// Let other callbacks handle the message too
return CallNextHookEx(IntPtr.Zero, code, wParam, lParam);
}

needing to emulate Win key in a batch file

I have been trying to find the answer, but my research has not come up with much that is helpful to my situation. I am needing to emulate the Windows Key in my batch file that I am creating. This is to help me Maximize the window, as well as easily move it between monitors without the use of a mouse. The workstation this batch will autorun on, will not have access to a mouse or physical Keyboard.
This is the code I am working with.
#if (#CodeSection == #Batch) #then
#echo off
rem Use %SendKeys% to send keys to the keyboard buffer
set SendKeys=CScript //nologo //E:JScript "%~F0"
rem Start the other program in the same Window
Start "Progam"
rem the script only works if the application in question is the active window. Set a timer
timeout /t 10
rem use the tab key to move the cursor to the login and password inputs.
%SendKeys% "This would be the username{TAB}"
%SendKeys% "this would be the password{ENTER}"
rem I then have a timer to let the login happen
timeout /t 10
rem this where I am now trying to maximize and move the active window around.
goto :EOF
#end
// JScript section
var WshShell = WScript.CreateObject("WScript.Shell");
WshShell.SendKeys(WScript.Arguments(0));
Great question! Yeah, I had to battle this myself some time ago. I wanted to auto launch an application installed from the Microsoft Store on startup, and for reasons the easiest way for me to do that was to pin it to my Taskbar then simulate hitting ⊞+1 to launch it. I assume the case is now as it was back then, but there's no way to SendKeys() the Windows meta key. But thanks to LandOfTheLostPass on reddit, I discovered an alternative to SendKeys(): sending the keyboard scan code.
I modified the code from reddit to allow me to simulate pressing multiple keys, then release them. If I recall correctly, the code on reddit would only simulate press then release a single key at a time. I only modified the scan code function for multi-key combos. Re-examining the code now, it appears I might've broken the SendChars() function. But I never used it anyway.
<# : win+1.bat -- Batch portion
#echo off & setlocal
powershell -window minimized -noprofile "iex (${%~f0} | out-string)"
goto :EOF
: end batch / begin PowerShell chimera #>
# https://www.reddit.com/r/PowerShell/comments/3qk9mc/keyboard_keypress_script/
Add-Type #"
using System;
using System.Collections.Generic;
using System.Runtime.InteropServices;
public static class KBEmulator {
public enum InputType : uint {
INPUT_MOUSE = 0,
INPUT_KEYBOARD = 1,
INPUT_HARDWARE = 3
}
[Flags]
internal enum KEYEVENTF : uint
{
KEYDOWN = 0x0,
EXTENDEDKEY = 0x0001,
KEYUP = 0x0002,
SCANCODE = 0x0008,
UNICODE = 0x0004
}
[Flags]
internal enum MOUSEEVENTF : uint
{
ABSOLUTE = 0x8000,
HWHEEL = 0x01000,
MOVE = 0x0001,
MOVE_NOCOALESCE = 0x2000,
LEFTDOWN = 0x0002,
LEFTUP = 0x0004,
RIGHTDOWN = 0x0008,
RIGHTUP = 0x0010,
MIDDLEDOWN = 0x0020,
MIDDLEUP = 0x0040,
VIRTUALDESK = 0x4000,
WHEEL = 0x0800,
XDOWN = 0x0080,
XUP = 0x0100
}
// Master Input structure
[StructLayout(LayoutKind.Sequential)]
public struct lpInput {
internal InputType type;
internal InputUnion Data;
internal static int Size { get { return Marshal.SizeOf(typeof(lpInput)); } }
}
// Union structure
[StructLayout(LayoutKind.Explicit)]
internal struct InputUnion {
[FieldOffset(0)]
internal MOUSEINPUT mi;
[FieldOffset(0)]
internal KEYBDINPUT ki;
[FieldOffset(0)]
internal HARDWAREINPUT hi;
}
// Input Types
[StructLayout(LayoutKind.Sequential)]
internal struct MOUSEINPUT
{
internal int dx;
internal int dy;
internal int mouseData;
internal MOUSEEVENTF dwFlags;
internal uint time;
internal UIntPtr dwExtraInfo;
}
[StructLayout(LayoutKind.Sequential)]
internal struct KEYBDINPUT
{
internal short wVk;
internal short wScan;
internal KEYEVENTF dwFlags;
internal int time;
internal UIntPtr dwExtraInfo;
}
[StructLayout(LayoutKind.Sequential)]
internal struct HARDWAREINPUT
{
internal int uMsg;
internal short wParamL;
internal short wParamH;
}
private class unmanaged {
[DllImport("user32.dll", SetLastError = true)]
internal static extern uint SendInput (
uint cInputs,
[MarshalAs(UnmanagedType.LPArray)]
lpInput[] inputs,
int cbSize
);
[DllImport("user32.dll", CharSet = CharSet.Unicode, SetLastError = true)]
public static extern short VkKeyScan(char ch);
}
internal static short VkKeyScan(char ch) {
return unmanaged.VkKeyScan(ch);
}
internal static uint SendInput(uint cInputs, lpInput[] inputs, int cbSize) {
return unmanaged.SendInput(cInputs, inputs, cbSize);
}
public static void SendScanCodeCombo(short[] scanCodes) {
lpInput[] KeyInputs = new lpInput[1];
lpInput KeyInput = new lpInput();
Enum[] actions = { KEYEVENTF.KEYDOWN, KEYEVENTF.KEYUP };
// Generic Keyboard Event
KeyInput.type = InputType.INPUT_KEYBOARD;
KeyInput.Data.ki.wScan = 0;
KeyInput.Data.ki.time = 0;
KeyInput.Data.ki.dwExtraInfo = UIntPtr.Zero;
// Press and release the correct key combination
foreach (KEYEVENTF action in actions) {
foreach (short scanCode in scanCodes) {
KeyInput.Data.ki.wVk = scanCode;
KeyInput.Data.ki.dwFlags = action;
KeyInputs[0] = KeyInput;
SendInput(1, KeyInputs, lpInput.Size);
}
}
return;
}
public static void SendChars(char[] keys) {
lpInput[] KeyInputs = new lpInput[1];
lpInput KeyInput = new lpInput();
Enum[] actions = { KEYEVENTF.KEYDOWN, KEYEVENTF.KEYUP };
// Generic Keyboard Event
KeyInput.type = InputType.INPUT_KEYBOARD;
KeyInput.Data.ki.wScan = 0;
KeyInput.Data.ki.time = 0;
KeyInput.Data.ki.dwExtraInfo = UIntPtr.Zero;
foreach (KEYEVENTF action in actions) {
foreach (char ch in keys) {
// Press the key
KeyInput.Data.ki.wVk = VkKeyScan(ch);
KeyInput.Data.ki.dwFlags = KEYEVENTF.KEYDOWN;
KeyInputs[0] = KeyInput;
SendInput(1, KeyInputs, lpInput.Size);
// Release the key
KeyInput.Data.ki.dwFlags = KEYEVENTF.KEYUP;
KeyInputs[0] = KeyInput;
SendInput(1, KeyInputs, lpInput.Size);
}
}
return;
}
}
"# # end Add-Type
# Send LWin+1
[KBEmulator]::SendScanCodeCombo(#(0x5B, 0x31))
See Virtual-Key Codes on Microsoft Docs to find the scan codes for other keys.
By the way, the Windows API provides other ways to maximize and move windows without simulating keyboard shortcuts. (Please don't judge too harshly. I wrote that hack before learning how to write Batch + PowerShell hybrid scripts or to include .NET code in PowerShell.)
you can use http://nircmd.nirsoft.net/
nircmd sendkeypress key

Drag & Drop for SpecialFolders

I have an application that allows users to drag and drop files or entire folders into a special "drop area," at which point all files are processed. The application is being developed using WPF, and this particular XAML view sets "AllowDrop" to true and handles the Drop event in code-behind.
Everything is working for normal files and standrard Windows folders. However, if the user drops a special Windows folder (e.g., Pictures, Videos), then the functionality does not work. It would appear this is because the contents of DragEventArgs.Data are not a DataFormats.FileDrop enum. That's not the case with other folders or files.
My code for handling the drop, in part, is:
private void OnDrop(object Sender, DragEventArgs E)
{
if (E.Data.GetDataPresent(DataFormats.FileDrop))
{
var _droppedFilePaths = E.Data.GetData(DataFormats.FileDrop, true) as string[];
// Process the files....
}
}
Is there any way to identify that the drop data contains the Windows 7 pictures library and map back to its actual path?
Using the solution described here, I wrote the following method:
const string ShellIdListArrayName = "Shell IDList Array";
static IEnumerable<string> GetPathsFromShellIDListArray(IDataObject data)
{
if (data.GetDataPresent(ShellIdListArrayName))
{
var ms = (MemoryStream)data.GetData(ShellIdListArrayName);
byte[] bytes = ms.ToArray();
IntPtr p = Marshal.AllocHGlobal(bytes.Length);
Marshal.Copy(bytes, 0, p, bytes.Length);
uint cidl = (uint)Marshal.ReadInt32(p, 0);
int offset = sizeof(uint);
IntPtr parentpidl = (IntPtr)((int)p + (uint)Marshal.ReadInt32(p, offset));
StringBuilder path = new StringBuilder(256);
SHGetPathFromIDList(parentpidl, path);
for (int i = 1; i <= cidl; ++i)
{
offset += sizeof(uint);
IntPtr relpidl = (IntPtr)((int)p + (uint)Marshal.ReadInt32(p, offset));
IntPtr abspidl = ILCombine(parentpidl, relpidl);
if (SHGetPathFromIDList(abspidl, path) != 0)
{
yield return path.ToString();
}
ILFree(abspidl);
}
}
}
[DllImport("shell32.dll")]
public static extern int SHGetPathFromIDList(IntPtr pidl, StringBuilder pszPath);
[DllImport("shell32.dll")]
public static extern IntPtr ILCombine(IntPtr pidl1, IntPtr pidl2);
[DllImport("shell32.dll")]
public static extern void ILFree(IntPtr pidl);
You can just pass e.Data from your event handler to this method, and you will get a sequence of paths (assuming the items do have a path of course... for instance, "My computer" doesn't have a path)

How to sort Windows by z-index?

If I'm enumerating windows in Application.Current.Windows, how can I tell, for any two windows, which one is "closer" (i.e. has greater z-index)?
Or, to say the same in other words, how can I sort those windows by z-index?
You cannot get a Window's Z Order information from WPF so you must resort to Win32.
Something like this ought to do the trick:
var topToBottom = SortWindowsTopToBottom(Application.Current.Windows.OfType<Window>());
...
public IEnumerable<Window> SortWindowsTopToBottom(IEnumerable<Window> unsorted)
{
var byHandle = unsorted.ToDictionary(win =>
((HwndSource)PresentationSource.FromVisual(win)).Handle);
for(IntPtr hWnd = GetTopWindow(IntPtr.Zero); hWnd!=IntPtr.Zero; hWnd = GetWindow(hWnd, GW_HWNDNEXT)
if(byHandle.ContainsKey(hWnd))
yield return byHandle[hWnd];
}
const uint GW_HWNDNEXT = 2;
[DllImport("User32")] static extern IntPtr GetTopWindow(IntPtr hWnd);
[DllImport("User32")] static extern IntPtr GetWindow(IntPtr hWnd, uint wCmd);
The way this works is:
It uses a dictionary to index the given windows by window handle, using the fact that in the Windows implementation of WPF, Window's PresentationSource is always HwndSource.
It uses Win32 to scan all unparented windows top to bottom to find the proper order.
Ah this was a really fun one:
[DllImport("user32.dll")]
static extern IntPtr GetActiveWindow();
public static Window ActiveWindow
{
get
{
return HwndSource.FromHwnd(GetActiveWindow()).RootVisual as Window;
}
}
It will give you the active window in your app (which is usually the foremost).

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