Highlight Current Line of RichTextBox - c#

This highlights the entire width of each line by painting a transparent color as the backcolor on the current line. When the line switches, the original background color is restored.
So, what we want to do is:
Verify the previous and current rectangles don't match, so not to paint the same area twice
Replace the last line's highlight using the controls backcolor
Highlight the current line using a transparent color
Set mLastHighlight with the index and rectangle for each applied line
However, when removing the highlight, the text is painted over. This does not occur when applying the highlight.
One solution would be to repaint the text back on the control after resetting the back color. though the text formatting, selection colors, font styles, hyperlinks, etc would be tedious to filter. Not very elegant.
This leads to a simpler solution, refreshing the control. Though that would cause massive flickering. Not acceptable either.
Is there an elegant solution? I'm completely baffled why this occurs.
EDIT: Edited to reflect Code Gray's response.
using System;
public class RTBHL : RichTextBox
{
private LastHighlight mLastHighlight = new LastHighlight(0, Rectangle.Empty);
private class LastHighlight
{
public int mCharIndex;
public Rectangle mRectangle;
public LastHighlight(int index, Rectangle r)
{
mCharIndex = index;
mRectangle = r;
}
}
public void PaintLineHighlight()
{
using (Graphics g = this.CreateGraphics)
{
// highlight color
Color c = Color.Beige;
// current pen color
Pen cp = new Pen(Color.Beige);
// color for removing highlight
Pen lp = new Pen(this.BackColor);
// brush for removing highlight
SolidBrush lb = new SolidBrush(this.BackColor);
// brush for applying highlight
SolidBrush cb = new SolidBrush(Color.FromArgb(64, c.R, c.G, c.B));
// index of the current line
int index = this.GetFirstCharIndexOfCurrentLine;
// rectangle to specify which region to paint too
Rectangle r = new Rectangle();
// specify dimensions
r.X = 0;
r.Y = this.GetPositionFromCharIndex(index).Y;
r.Width = this.HorizontalScrollBarWidth;
r.Height = Convert.ToInt32(this.Font.Height * this.ZoomFactor);
// this will always be true unless the current line remains the same
if (!(mLastHighlight.mCharIndex == index) && !(mLastHighlight.mRectangle == r))
{
// remove the last highlight. regardless of the brush specified, white is always applied, and the text is painted over
g.DrawRectangle(lp, mLastHighlight.mRectangle);
g.FillRectangle(lb, mLastHighlight.mRectangle);
// apply highlight to the current line
g.DrawRectangle(cp, r);
g.FillRectangle(cb, r);
}
mLastHighlight = new LastHighlight(index, r);
}
}
#region RichScrollBars
[DllImport("user32.dll")]
[return: MarshalAs(UnmanagedType.Bool)]
private static extern bool GetScrollInfo(IntPtr hWnd, int fnBar, ref SCROLLINFO si);
[StructLayout(LayoutKind.Sequential)]
public class SCROLLINFO
{
public int cbSize;
public int fMask;
public int nMin;
public int nMax;
public int nPage;
public int nPos;
public int nTrackPos;
public SCROLLINFO()
{
this.cbSize = Marshal.SizeOf(typeof(SCROLLINFO));
}
public SCROLLINFO(int mask, int min, int max, int page, int pos)
{
this.cbSize = Marshal.SizeOf(typeof(SCROLLINFO));
this.fMask = mask;
this.nMin = min;
this.nMax = max;
this.nPage = page;
this.nPos = pos;
}
}
private const int SIF_ALL = 0X17;
private const int SB_HORZ = 0;
private const int SB_VERT = 1;
public int HorizontalScrollBarWidth()
{
SCROLLINFO si = new SCROLLINFO() {fMask = SIF_ALL};
GetScrollInfo(this.Handle, SB_HORZ, si);
return Math.Max(si.nMax, this.Width);
}
public int VerticalScrollBarHeight()
{
SCROLLINFO si = new SCROLLINFO() {fMask = SIF_ALL};
GetScrollInfo(this.Handle, SB_VERT, si);
return Math.Max(si.nMax, this.Height);
}
#endregion
}

The problem here is that the code you're copying is designed for Scintilla. The SCI_* constants are defined internally by the Scintilla headers, and the messages they refer to only have meaning to the Scintilla controls.
Sending those messages to the native Win32 rich edit control isn't going to do anything because it wasn't designed to process those messages. (Or worse, one or more of the SCI_* constants happen to clash with one or more of the message identifiers that the rich edit control does recognize, producing some potentially interesting behavior.)
Unless you're actually using a Scintilla edit control in your project (which you said you don't want to do), that code isn't going to do anything interesting. It isn't written for the Win32 rich edit control, it's written to interface with the Scintilla control.
The Scintilla control is far more than just a wrapper around the Win32 rich edit control. It has to do a lot of custom drawing to make its magic, and all of that code is hard to get right on your own. That's why so many people use Scintilla in the first place. If you need its feature set, I highly recommend that you follow suit.
Anyway, I don't actually know if this is possible with the Win32 rich edit control. I don't think it is, but I couldn't swear to that fact. I guess you could hack it by setting the selection color, but that doesn't seem like a very good solution. Something like Daniel suggests here. I'm not a Scintilla expert, but to my untrained eyes, this looks kind of like the moral equivalent of your Scintilla-based code, but written for the Win32 rich edit control (through the .NET WinForms wrapper thereof).

Related

How to get position of c# console application

I am a beginner at coding and I want to create an application that says the width, height, and position of the window.
The problem is that I don't know how to GET the position of the window.
I searched the internet but couldn't find the answer to my question.
Here is the code I have:
using System;
namespace WindowSizeChecker
{
class Program
{
const bool alwaysTrue = true;
static void Main(string[] args)
{
while (alwaysTrue == true)
{
Console.Write("Set your console window to your prefered size and position. Then press Enter");
Console.ReadLine();
screenSizeAndPosition();
Console.WriteLine("\n\nPress enter to repeat\n\n");
Console.ReadLine();
}
}
public static void screenSizeAndPosition()
{
int consoleWidth = Console.WindowWidth;
int consoleHeight = Console.WindowHeight;
string consoleWidthString = consoleWidth.ToString();
string consoleHeightString = consoleHeight.ToString();
Console.WriteLine("\nThe width of the window is: {0}\nAnd the height of the window is: {1}", consoleWidthString, consoleHeightString);
int largestWindowWidth = Console.LargestWindowWidth;
int largestWindowHeight = Console.LargestWindowHeight;
string largestWindowWidthString = largestWindowWidth.ToString();
string largestWindowHeightString = largestWindowHeight.ToString();
Console.WriteLine("\nThe largest width of the window is: {0}\nAnd the largest height of the window is: {1}", largestWindowWidthString, largestWindowHeightString);
}
}
}
Here is the program running:
enter image description here
I am a beginner at coding and I want to create an application that says the width, height, and position of the window. The problem is that I don't know how to GET the position of the window. I searched the internet but couldn't find the answer to my question.
The information is out there, but I admit, it's not necessarily presented in the easiest to understand manner, especially for a beginner.
IMHO, two of the most relevant Stack Overflow questions you probably should read are these:
Position a small console window to the bottom left of the screen?
DwmGetWindowAttribute returns 0 with PInvoke
They aren't really duplicates of your question, and for a beginner it's probably hard to see how they answer it. But they do in fact contain almost all of the information you would need.
There are a couple of things you need to understand, besides the "how":
The Console properties you're looking at now are not pixel dimensions, but rather are in terms of character columns and rows. That is, how many characters can fit across the window in a single row, and how many rows of those characters can fit vertically.
When it comes to pixels, there are actually (at least) two different ways to look at the window size: raw screen coordinates, and "DPI-adjusted". The latter is IMHO a misnomer, because it's not taking into account any actual screen resolution (i.e. "dots per inch"), but rather the scaling factor that is set for your desktop. It's considered "DPI-adjusted" because setting the scaling factor is the Windows mechanism for attempting to keep the visual presentation of a program consistent across displays of different resolution.
As you already have seen, you can get the character-oriented dimensions straight from the .NET Console class. But to get the pixel information, you need to use .NET's native interop support to call the Windows API directly. Here are some helper classes I put together, based on available documentation and Stack Overflow posts, to do that for your scenario:
[StructLayout(LayoutKind.Sequential)]
struct Rect
{
public int Left;
public int Top;
public int Right;
public int Bottom;
public int Width => Right - Left;
public int Height => Bottom - Top;
}
class NativeConsole
{
[DllImport("kernel32")]
public static extern IntPtr GetConsoleWindow();
}
class Winuser
{
[DllImport(#"user32.dll", SetLastError = true)]
[return: MarshalAs(UnmanagedType.Bool)]
private static extern bool GetWindowRect(IntPtr hWnd, out Rect lpRect);
public static Rect GetWindowRect(IntPtr handle)
{
if (!GetWindowRect(handle, out Rect rect))
{
throw Marshal.GetExceptionForHR(Marshal.GetLastWin32Error());
}
return rect;
}
}
class DwmApi
{
private const int DWMWA_EXTENDED_FRAME_BOUNDS = 9;
[DllImport(#"dwmapi.dll")]
private static extern int DwmGetWindowAttribute(IntPtr hwnd, int dwAttribute, out Rect pvAttribute, int cbAttribute);
public static Rect GetExtendedFrameBounds(IntPtr hwnd)
{
int hresult = DwmGetWindowAttribute(hwnd, DWMWA_EXTENDED_FRAME_BOUNDS, out Rect rect, Marshal.SizeOf(typeof(Rect)));
if (hresult != 0)
{
throw Marshal.GetExceptionForHR(hresult);
}
return rect;
}
}
You could, of course, lump all of the above together in a single class, but I prefer to keep things organized. The above groups the various parts of the API into the same organization used in the native Win32 API itself.
With those pieces in hand, now we can put together a program similar to the one you have above, except that it will display the window position (which is what you want), along with the width and height as well (since those come for free from the native API anyway).
That looks like this:
using static System.Console;
class Program
{
static void Main(string[] args)
{
Clear();
string prompt = "Set your console window to your preferred size and position. Press X to exit";
while (true)
{
if (KeyAvailable && ReadKey(intercept: true).Key == ConsoleKey.X)
{
break;
}
ScreenSizeAndPosition(prompt);
System.Threading.Thread.Sleep(TimeSpan.FromSeconds(0.25));
}
}
static void ScreenSizeAndPosition(string prompt)
{
string format = $"{{0, {-WindowWidth}}}";
SetCursorPosition(0, 0);
Write(format, prompt);
Write(format, $"Window is {WindowWidth} columns wide and {WindowHeight} rows high");
Write(format, $"The largest window that can fit on the screen is {LargestWindowWidth} columns wide and {LargestWindowHeight} rows high");
IntPtr consoleHwnd = NativeConsole.GetConsoleWindow();
Rect winuserRect = Winuser.GetWindowRect(consoleHwnd),
dwmRect = DwmApi.GetExtendedFrameBounds(consoleHwnd);
Write(format, $"DPI-adjusted screen values: location is {{{winuserRect.Left}, {winuserRect.Top}}}, window is {winuserRect.Width} pixels wide, {winuserRect.Height} pixels high");
Write(format, $"Desktop Window Manager values: location is {{{dwmRect.Left}, {dwmRect.Top}}}, window is {dwmRect.Width} pixels wide, {dwmRect.Height} pixels high");
for (int i = 0; i < WindowHeight - 5; i++)
{
Write(format, "");
}
}
}
I did change your basic logic in the program a bit, so that it just checks every quarter second rather than waiting for the user to press a key, displaying whatever the current values are as the user changes the window size and position.
For more details on the Windows functions used above, you should read the documentation:
GetConsoleWindow()
GetWindowRect()
DwmGetWindowAttribute()
There are some additional subtleties in the ways that GetWindowRect() and DwmGetWindowAttribute() work, so it's worth checking out the docs so that you understand better what they are returning in terms of the window dimensional values.
You should call a Win32 API DwmGetWindowAttribute via PInovke to the current window position. Refer to the document and see how to use it.

Windows forms MenuStrip DropDown inside a desktop application bar

This is a duplicate of a question I posted at Windows Dev Center. Still awaiting replies, so I thought I would try it here. My apologies if the formatting goes awry.
This is currently being written and debugged using Windows 7 Professional, SP1.
The application is located at the top of the desktop, and the working area is appropriately resized via a hook into the SystemParametersInfo function. The MenuStrip appears as it should, with the exception that any dropdown from the MenuStrip shows as detached from the MenuStrip itself (as if it is being drawn on the new working area, as opposed to the form containing the MenuStrip). For example:
Application TopLevel: true
Application Height: 150
Application Location: 0,0 on Desktop (prior to working area's resize)
MenuStrip Height: 25
MenuStrip Location: 0,0 inside Parent Form
MenuStrip DropDown Location: x,2 (where x is a valid and acceptable value) this is being drawn on the resized working area (i.e. beneath the form)
I have attempted correcting this with a custom Renderer to no present avail. I tried to override WndProc (as follows) so as to see what exactly was occurring, but that resulted in a stackoverflow halfway through drawing the application.
protected override void WndProc(ref Message m)
{
if (mainForm.Visible)
{
MessageBox.Show("ID: " + m.Msg.ToString() + "\n" + m.ToString());
}
base.WndProc(ref m);
}
I suspect I've run this into the ground by now, but if you require any further explanation, just let me know. Just hoping someone can point me in the appropriate direction as to where I should look.
Hopefully this will answer the question regarding why I used SystemParametersInfo:
#region TEMPORARY FIX TO ACHIEVE APPBAR
public RECT normalWorkingRECT = new RECT();
public RECT newWorkingRECT = new RECT();
public struct RECT
{
public int left;
public int top;
public int right;
public int bottom;
};
[DllImport("user32.dll", EntryPoint="SystemParametersInfo")]
public static extern bool SystemParametersInfo(uint uiAction, uint uiParam, ref RECT pvParam, uint fWinIni);
//call after scale is set
private void setAppBar()
{
//make the parent = the desktop
if (!this.GetTopLevel())
{
this.SetTopLevel(true);
}
this.Width = SystemInformation.PrimaryMonitorSize.Width;
//set old Working Area
SystemParametersInfo(0x0030, 0, ref normalWorkingRECT, 0);
//change work area based on size of main form
newWorkingRECT.left = normalWorkingRECT.left;
newWorkingRECT.top = normalWorkingRECT.top + this.DesktopBounds.Height + 4;
newWorkingRECT.right = normalWorkingRECT.right;
newWorkingRECT.bottom = normalWorkingRECT.bottom;
SystemParametersInfo(0x002F, 0, ref newWorkingRECT, 0x0002);
}
//called on close
private void unsetAppBar()
{
//get current work area to compare
RECT testRECT = new RECT();
SystemParametersInfo(0x0030, 0, ref testRECT, 0);
//if no change, resize to normal working rect
if (newWorkingRECT.top == testRECT.top &&
newWorkingRECT.bottom == testRECT.bottom &&
newWorkingRECT.left == testRECT.left &&
newWorkingRECT.right == testRECT.right)
{
SystemParametersInfo(0x002F, 0, ref normalWorkingRECT, 0x0002);
}
//if there is a change, resize to current working rect - this.height
else
{
testRECT.top -= this.DesktopBounds.Height + 4;
SystemParametersInfo(0x002F, 0, ref testRECT, 0x0002);
}
}
#endregion
EDIT: Added image as requested and code to show reason for SystemParametersInfo.

Combobox Drop Down list

I have a Form that has a combobox on it.
The combobox is set DropDownList. These drop down items are a descriptive form of an object. This means they can get quite long. The position of the combobox on the screen means when the drop down list is displayed it doesn't all fit on the screen. Some of it is chopped off by the right edge of the screen.
I can't move the combobox.
Is their some way I can move the drop down list part of the control. Perhaps centre it under the control ?
Update
I've attached a screenshot. You can see the form here -
When entering transactions the user fills in the form and clicks Save. A number of the transactions that will be entered for any client though will be recurring transactions. These can be saved to the favourites. The drop down box lists the currently saved favourites and when one is selected the program automatically fills in the transaction fields.
Screenshot 2 showing the whole program and the combobox list running out of space.
I realise from the screenshot I could move the form but I like to keep the forms for entering transactions centered on the screen.
I may have to look at other options for the interface.
Thanks,
maybe you should create your own comboBox, as shown here:
http://msdn.microsoft.com/en-us/library/ms996411
Sorry for late posting :-). Yes You can do that. But you need to create a custom ComboBox and override the WndProc method of base ComboBox;
Its like this;
System.Runtime.InteropServices
private const int SWP_NOSIZE = 0x1;
private const int WM_CTLCOLORLISTBOX = 0x0134;
[DllImport("user32.dll")]
static extern bool SetWindowPos(IntPtr hWnd, IntPtr hWndInsertAfter, int
X, int Y, int cx, int cy, uint uFlags);
protected override void WndProc(ref Message m)
{
if (m.Msg == WM_CTLCOLORLISTBOX)
{
// Make sure we are inbounds of the screen
int left = this.PointToScreen(new Point(0, 0)).X;
//Only do this if the dropdown is going off right edge of screen
if (this.DropDownWidth > Screen.PrimaryScreen.WorkingArea.Width - left)
{
// Get the current combo position and size
Rectangle comboRect = this.RectangleToScreen(this.ClientRectangle);
int dropHeight = 0;
int topOfDropDown = 0;
int leftOfDropDown = 0;
//Calculate dropped list height
for (int i = 0; (i < this.Items.Count && i < this.MaxDropDownItems); i++)
{
dropHeight += this.ItemHeight;
}
//Set top position of the dropped list if
//it goes off the bottom of the screen
if (dropHeight > Screen.PrimaryScreen.WorkingArea.Height -
this.PointToScreen(new Point(0, 0)).Y)
{
topOfDropDown = comboRect.Top - dropHeight - 2;
}
else
{
topOfDropDown = comboRect.Bottom;
}
//Calculate shifted left position
leftOfDropDown = comboRect.Left - (this.DropDownWidth -
(Screen.PrimaryScreen.WorkingArea.Width - left));
//when using the SWP_NOSIZE flag, cx and cy params are ignored
SetWindowPos(m.LParam,
IntPtr.Zero,
leftOfDropDown,
topOfDropDown,
0,
0,
SWP_NOSIZE);
}
}
base.WndProc(ref m);
}
The code is obtain from a MSDN article Building a Better ComboBox
Did you try setting in the designer
Combobox.Anchor = Left | Right
Try setting the DropdownWidth of the combo.

C# Change ListView Item's/Row's height

I want to change the Item's/Row's height in listview.
I searched every where and I figured that in order to change the height I need to use LBS_OWNERDRAWFIXED or MeasureItem or something like that.
The problem is that I dont know exactly what to do and how to use it..
Can anyone help me with it?
Edit:
I cant use the ImageList hack because I am using the SmallImageList for real and I need different line height from the ImageList images size.
Thanks!
For the people that are still struggling with this, here is the code I use:
private void SetHeight(ListView listView, int height)
{
ImageList imgList = new ImageList();
imgList.ImageSize = new Size(1, height);
listView.SmallImageList = imgList;
}
To use this, just do:
SetHeight(lvConnections, 25);
You have to use a bit of a hack. The trick is to use an image list in the StateImageList property. The ListView will adjust its item height, based on the height of the ImageList's ImageSize property. You do not have to specify an image for your items, but just using the StateImageList will force the ListView to adjust. In the example below, I had set the image list size to 32x32, thus resulting in a 32px height ListViewItem(s).
It can be done using the SmallImageList trick -- you just have to be careful. ObjectListView -- an open source wrapper around a standard .NET ListView -- uses that trick to successfully implement a RowHeight property.
If you want 32 pixels for each row, allocate an ImageList that is 16x32 (width x height), and then position each of your images in the vertical middle of the 32-pixel height.
This screen shot shows 32-pixel rows and the word wrapping that is possible because of the extra space:
ObjectListView does all this work for you. In fact, if you are trying to do anything with a ListView, you should seriously looked at using an ObjectListView instead. It makes many difficult things (e.g. sorting by column type, custom tooltips) trivial, and several impossible things (e.g. overlays, groups on virtual lists) possible.
Sadly nobody answered your original question how to use LBS_OWNERDRAWFIXED in all these years.
The answer that you have accepted is integrating a huge project (with demos and documentation 3,3MB). But just for setting the line height of a ListView this is overbloated.
The other workaround suggested here (adding an ImageList) works only to increase the row height. But it does not allow to really set the RowHeight independent of the image height. Additionally the default row height depends on the operating system. For example on Windows 7 the rows are much higher than on XP. You cannot chose to make them tighter, only higher.
But with very few lines you can do what you want.
Just copy and paste the following class:
using System;
using System.Drawing;
using System.Diagnostics;
using System.ComponentModel;
using System.Runtime.InteropServices;
using System.Windows.Forms;
namespace ExtendedControls
{
public class ListViewEx : ListView
{
#region Windows API
/*
struct MEASUREITEMSTRUCT
{
public int CtlType; // Offset = 0
public int CtlID; // Offset = 1
public int itemID; // Offset = 2
public int itemWidth; // Offset = 3
public int itemHeight; // Offset = 4
public IntPtr itemData;
}
*/
[StructLayout(LayoutKind.Sequential)]
struct DRAWITEMSTRUCT
{
public int ctlType;
public int ctlID;
public int itemID;
public int itemAction;
public int itemState;
public IntPtr hWndItem;
public IntPtr hDC;
public int rcLeft;
public int rcTop;
public int rcRight;
public int rcBottom;
public IntPtr itemData;
}
// LVS_OWNERDRAWFIXED: The owner window can paint ListView items in report view.
// The ListView control sends a WM_DRAWITEM message to paint each item. It does not send separate messages for each subitem.
const int LVS_OWNERDRAWFIXED = 0x0400;
const int WM_SHOWWINDOW = 0x0018;
const int WM_DRAWITEM = 0x002B;
const int WM_MEASUREITEM = 0x002C;
const int WM_REFLECT = 0x2000;
#endregion
bool mb_Measured = false;
int ms32_RowHeight = 14;
/// <summary>
/// Constructor
/// </summary>
public ListViewEx()
{
SetStyle(ControlStyles.OptimizedDoubleBuffer | ControlStyles.AllPaintingInWmPaint, true);
}
/// <summary>
/// Sets the row height in Details view
/// This property appears in the Visual Studio Form Designer
/// </summary>
[Category("Appearance")]
[Description("Sets the height of the ListView rows in Details view in pixels.")]
public int RowHeight
{
get { return ms32_RowHeight; }
set
{
if (!DesignMode) Debug.Assert(mb_Measured == false, "RowHeight must be set before ListViewEx is created.");
ms32_RowHeight = value;
}
}
protected override CreateParams CreateParams
{
get
{
CreateParams k_Params = base.CreateParams;
k_Params.Style |= LVS_OWNERDRAWFIXED;
return k_Params;
}
}
/// <summary>
/// The messages WM_MEASUREITEM and WM_DRAWITEM are sent to the parent control rather than to the ListView itself.
/// They come here as WM_REFLECT + WM_MEASUREITEM and WM_REFLECT + WM_DRAWITEM
/// They are sent from Control.WmOwnerDraw() --> Control.ReflectMessageInternal()
/// </summary>
protected override void WndProc(ref Message k_Msg)
{
base.WndProc(ref k_Msg); // FIRST
switch (k_Msg.Msg)
{
case WM_SHOWWINDOW: // called when the ListView becomes visible
{
Debug.Assert(View == View.Details, "ListViewEx supports only Details view");
Debug.Assert(OwnerDraw == false, "In ListViewEx do not set OwnerDraw = true");
break;
}
case WM_REFLECT + WM_MEASUREITEM: // called once when the ListView is created, but only in Details view
{
mb_Measured = true;
// Overwrite itemHeight, which is the fifth integer in MEASUREITEMSTRUCT
Marshal.WriteInt32(k_Msg.LParam + 4 * sizeof(int), ms32_RowHeight);
k_Msg.Result = (IntPtr)1;
break;
}
case WM_REFLECT + WM_DRAWITEM: // called for each ListViewItem to be drawn
{
DRAWITEMSTRUCT k_Draw = (DRAWITEMSTRUCT) k_Msg.GetLParam(typeof(DRAWITEMSTRUCT));
using (Graphics i_Graph = Graphics.FromHdc(k_Draw.hDC))
{
ListViewItem i_Item = Items[k_Draw.itemID];
Color c_BackColor = i_Item.BackColor;
if (i_Item.Selected) c_BackColor = SystemColors.Highlight;
if (!Enabled) c_BackColor = SystemColors.Control;
using (SolidBrush i_BackBrush = new SolidBrush(c_BackColor))
{
// Erase the background of the entire row
i_Graph.FillRectangle(i_BackBrush, i_Item.Bounds);
}
for (int S=0; S<i_Item.SubItems.Count; S++)
{
ListViewItem.ListViewSubItem i_SubItem = i_Item.SubItems[S];
// i_Item.SubItems[0].Bounds contains the entire row, rather than the first column only.
Rectangle k_Bounds = (S>0) ? i_SubItem.Bounds : i_Item.GetBounds(ItemBoundsPortion.Label);
// You can use i_Item.ForeColor instead of i_SubItem.ForeColor to get the same behaviour as without OwnerDraw
Color c_ForeColor = i_SubItem.ForeColor;
if (i_Item.Selected) c_ForeColor = SystemColors.HighlightText;
if (!Enabled) c_ForeColor = SystemColors.ControlText;
TextFormatFlags e_Flags = TextFormatFlags.NoPrefix | TextFormatFlags.EndEllipsis | TextFormatFlags.VerticalCenter | TextFormatFlags.SingleLine;
switch (Columns[S].TextAlign)
{
case HorizontalAlignment.Center: e_Flags |= TextFormatFlags.HorizontalCenter; break;
case HorizontalAlignment.Right: e_Flags |= TextFormatFlags.Right; break;
}
TextRenderer.DrawText(i_Graph, i_SubItem.Text, i_SubItem.Font, k_Bounds, c_ForeColor, e_Flags);
}
}
break;
}
}
}
} // class
} // namespace
After adding a ListViewEx to your Form you will see a new property in the Visual Studio Forms Designer which allows to set the row height in pixels:
The value you enter there will be the row height in pixels and it will be respected exatctly on all operating systems. I tested it on Windows XP, 7 and 10:
Additionally my class has two more advantages over the original ListView: It draws flicker-free and it respects the ForeColor and Font set in ListViewSubItem which is ignored by the original Microsoft ListView. So you can draw each cell with a different color and font.
IMPORTANT: As the MSDN says LBS_OWNERDRAWFIXED has been designed only for Details view (Report view). My code works only for this mode and this is because Microsoft has designed it like that.
Additionally please note that setting ListView.OwnerDraw = true is a completely different thing than using LVS_OWNERDRAWFIXED.
I did not implement drawing icons, because I don't need that. But you can easily add this.
The default line height of a ListView (in report view mode) is computed based on the control's font size.
So to select the line height, choose a font with the right height in the ListView properties.
For example, select MS Sans Serif 18.
Then you can change the font used by all items:
when you insert a new item, set its font property.
To optimize font assignment you should declare the item font as a private member of the form:
Private Font stdfont = new Font( "Consolas", 9.0f, FontStyle.Regular );
Then when adding items :
ListViewItem i = new ListViewItem( "some text" );
i.Font = stdfont;
MyListView.Items.Add( i );
This trick is the only easy one allowing to have SMALLER line height ;)
i.E. set control's font size to 7 and set items' font size to 10.
(Tested with VS 2008 )
Plasmabubble has the right idea. This expands on that and is what I use to use a narrow line-width for the items.
The linespacing in a ListView is dependent on the ListView's font and can't be changed. However, you can set the font for the items in the ListView to something larger than the ListView's font.
If you want it to be proportional, create a font based on the item's font.
I want the item height to be 90% of normal, whatever the font chosen.
When I populate the list I used a font stored in settings but you could also use a literal font like "Consolas".
lvResults.Font =
new Font(Properties.Settings.Default.usrHookFont.FontFamily,
(float)(Properties.Settings.Default.usrHookFont.Size * .9));
foreach (HookSet item in resultSet)
{
ListViewItem lvi = new ListViewItem();
lvi.Font = Properties.Settings.Default.usrHookFont;
<dot><dot><dot>
}
After reading the answers for so many years, one is to use ListView based extension controls and the other is to use fonts or icons for extensions.
If your project already uses a ListView -- as in my case, where you need to extend the column height while keeping the original icon and font size -- I suggest
You can roughly calculate the column height you need, and the ratio of the normal icon size, and thus use the transparent border to extend the icon size, e.g. if the 24x24 icon is actually 35 in height, you can use windows paint 3D to extend the icon to 35x35 using the canvas to keep the original ratio of the icon, I think this is probably the most time and cost effective way.

Autoscale Font in a TextBox Control so that its as big as possible and still fits in text area bounds

I need a TextBox or some type of Multi-Line Label control which will automatically adjust the font-size to make it as large as possible and yet have the entire message fit inside the bounds of the text area.
I wanted to see if anyone had implemented a user control like this before developing my own.
Example application: have a TextBox which will be half of the area on a windows form. When a message comes in which is will be approximately 100-500 characters it will put all the text in the control and set the font as large as possible. An implementation which uses Mono Supported .NET libraries would be a plus.
If know one has implemented a control already... If someone knows how to test if a given text completely fits inside the text area that would be useful for if I roll my own control.
Edit: I ended up writing an extension to RichTextBox. I will post my code shortly once i've verified that all the kinks are worked out.
I had to solve the same basic problem. The iterative solutions above were very slow. So, I modified it with the following. Same idea. Just uses calculated ratios instead of iterative. Probably, not quite as precise. But, much faster.
For my one-off need, I just threw an event handler on the label holding my text.
private void PromptLabel_TextChanged(object sender, System.EventArgs e)
{
if (PromptLabel.Text.Length == 0)
{
return;
}
float height = PromptLabel.Height * 0.99f;
float width = PromptLabel.Width * 0.99f;
PromptLabel.SuspendLayout();
Font tryFont = PromptLabel.Font;
Size tempSize = TextRenderer.MeasureText(PromptLabel.Text, tryFont);
float heightRatio = height / tempSize.Height;
float widthRatio = width / tempSize.Width;
tryFont = new Font(tryFont.FontFamily, tryFont.Size * Math.Min(widthRatio, heightRatio), tryFont.Style);
PromptLabel.Font = tryFont;
PromptLabel.ResumeLayout();
}
I haven't seen an existing control to do this, but you can do it the hard way by using a RichTextBox and the TextRenderer's MeasureText method and repeatedly resizing the font. It's inefficient, but it works.
This function is an event handler for the 'TextChanged' event on a RichTextBox.
An issue I've noticed:
When typing, the text box will scroll to the current caret even if scrollbars are disabled. This can result in the top line or left side getting chopped off until you move back up or left with the arrow keys. The size calculation is correct assuming you can get the top line to display at the top of the text box. I included some scrolling code that helps sometimes (but not always).
This code assumes word wrap is disabled. It may need modification if word wrap is enabled.
The code:
[DllImport("user32.dll")]
public static extern int SendMessage(IntPtr hWnd, uint wMsg, int wParam, uint lParam);
private static uint EM_LINEINDEX = 0xbb;
private void richTextBox1_TextChanged(object sender, EventArgs e)
{
// If there's no text, return
if (richTextBox1.TextLength == 0) return;
// Get height and width, we'll be using these repeatedly
int height = richTextBox1.Height;
int width = richTextBox1.Width;
// Suspend layout while we mess with stuff
richTextBox1.SuspendLayout();
Font tryFont = richTextBox1.Font;
Size tempSize = TextRenderer.MeasureText( richTextBox1.Text, richTextBox1.Font);
// Make sure it isn't too small first
while (tempSize.Height < height || tempSize.Width < width)
{
tryFont = new Font(tryFont.FontFamily, tryFont.Size + 0.1f, tryFont.Style);
tempSize = TextRenderer.MeasureText(richTextBox1.Text, tryFont);
}
// Now make sure it isn't too big
while (tempSize.Height > height || tempSize.Width > width)
{
tryFont = new Font(tryFont.FontFamily, tryFont.Size - 0.1f, tryFont.Style);
tempSize = TextRenderer.MeasureText(richTextBox1.Text, tryFont);
}
// Swap the font
richTextBox1.Font = tryFont;
// Resume layout
richTextBox1.ResumeLayout();
// Scroll to top (hopefully)
richTextBox1.ScrollToCaret();
SendMessage(richTextBox1.Handle, EM_LINEINDEX, -1, 0);
}
The solution i came up with was to write a control which extends the standard RichTextBox control.
Use the extended control in the same way you would a regular RichTextBox control with the following enhancements:
Call the ScaleFontToFit() method after resizing or text changes.
The Horizontal Alignment field can be used to center align the text.
The Font attributes set in the designer will be used for the entire region. It is not possible to mix fonts as they will changed once the ScaleFontToFit method is called.
This control combines several techniques to determine if the text still fits within it's bounds. If the text area is multiline, it detects if scrollbars are visible. I found a clever way to detect whether or not the scrollbars are visible without requiring any winapi calls using a clever technique I found on one of Patrick Smacchia's posts.. When multiline isn't true, vertical scrollbars never appear so you need to use a different technique which relies on rendering the text using a the Graphics object. The Graphic rendering technique isn't suitable for Multiline boxes because you would have to account for word wrapping.
Here are a few snippets which shows how it works (link to source code is provided below). This code could easily be used to extend other controls.
/// <summary>
/// Sets the font size so the text is as large as possible while still fitting in the text
/// area with out any scrollbars.
/// </summary>
public void ScaleFontToFit()
{
int fontSize = 10;
const int incrementDelta = 5; // amount to increase font by each loop iter.
const int decrementDelta = 1; // amount to decrease to fine tune.
this.SuspendLayout();
// First we set the font size to the minimum. We assume at the minimum size no scrollbars will be visible.
SetFontSize(MinimumFontSize);
// Next, we increment font size until it doesn't fit (or max font size is reached).
for (fontSize = MinFontSize; fontSize < MaxFontSize; fontSize += incrementDelta)
{
SetFontSize(fontSize);
if (!DoesTextFit())
{
//Console.WriteLine("Text Doesn't fit at fontsize = " + fontSize);
break;
}
}
// Finally, we keep decreasing the font size until it fits again.
for (; fontSize > MinFontSize && !DoesTextFit(); fontSize -= decrementDelta)
{
SetFontSize(fontSize);
}
this.ResumeLayout();
}
#region Private Methods
private bool VScrollVisible
{
get
{
Rectangle clientRectangle = this.ClientRectangle;
Size size = this.Size;
return (size.Width - clientRectangle.Width) >= SystemInformation.VerticalScrollBarWidth;
}
}
/**
* returns true when the Text no longer fits in the bounds of this control without scrollbars.
*/
private bool DoesTextFit()
{
if (VScrollVisible)
{
//Console.WriteLine("#1 Vscroll is visible");
return false;
}
// Special logic to handle the single line case... When multiline is false, we cannot rely on scrollbars so alternate methods.
if (this.Multiline == false)
{
Graphics graphics = this.CreateGraphics();
Size stringSize = graphics.MeasureString(this.Text, this.SelectionFont).ToSize();
//Console.WriteLine("String Width/Height: " + stringSize.Width + " " + stringSize.Height + "form... " + this.Width + " " + this.Height);
if (stringSize.Width > this.Width)
{
//Console.WriteLine("#2 Text Width is too big");
return false;
}
if (stringSize.Height > this.Height)
{
//Console.WriteLine("#3 Text Height is too big");
return false;
}
if (this.Lines.Length > 1)
{
//Console.WriteLine("#4 " + this.Lines[0] + " (2): " + this.Lines[1]); // I believe this condition could be removed.
return false;
}
}
return true;
}
private void SetFontSize(int pFontSize)
{
SetFontSize((float)pFontSize);
}
private void SetFontSize(float pFontSize)
{
this.SelectAll();
this.SelectionFont = new Font(this.SelectionFont.FontFamily, pFontSize, this.SelectionFont.Style);
this.SelectionAlignment = HorizontalAlignment;
this.Select(0, 0);
}
#endregion
ScaleFontToFit could be optimized to improve performance but I kept it simple so it'd be easy to understand.
Download the latest source code here. I am still actively working on the project which I developed this control for so it's likely i'll be adding a few other features and enhancements in the near future. So, check the site for the latest code.
My goal is to make this control work on Mac using the Mono framework.
I had a similar requirement for a text box in a panel on a windows form hosted window. (I injected the panel onto the existing form). When the size of the panel changes (in my case) the text would resize to fit the box. Code
parentObject.SizeChanged += (sender, args) =>
{
if (textBox1.Text.Length > 0)
{
int maxSize = 100;
// Make a Graphics object to measure the text.
using (Graphics gr = textBox1.CreateGraphics())
{
for (int i = 1; i <= maxSize; i++)
{
using (var test_font = new Font(textBox1.Font.FontFamily, i))
{
// See how much space the text would
// need, specifying a maximum width.
SizeF text_size =
TextRenderer.MeasureText(
textBox1.Text,
test_font,
new Size(textBox1.Width, int.MaxValue),
TextFormatFlags.WordBreak | TextFormatFlags.TextBoxControl);
try
{
if (text_size.Height > textBox1.Height)
{
maxSize = i - 1;
break;
}
}
catch (System.ComponentModel.Win32Exception)
{
// this sometimes throws a "failure to create window handle" error.
// This might happen if the TextBox is invisible and/or
// too small to display a toolbar.
// do whatever here, add/delete, whatever, maybe set to default font size?
maxSize = (int) textBox1.Font.Size;
}
}
}
}
// Use that font size.
textBox1.Font = new Font(textBox1.Font.FontFamily, maxSize);
}
};

Categories