I've extended WinForms TreeView in C# in order to draw my own text (I'm drawing a shadowed font with black under white) and everything looks okay apart from when I expand a node with a large number of children (by large I'm only talking 20+). I've tried to capture what happens but it's so quick I've been unsuccessful so I'll try to explain it.
The TreeView has around 15 1st level nodes in it and when I open one of the 20+ children nodes, I get a glitch where it displays the node under the one I've clicked multiple times (looks like the same amount of times as the children nodes that subsequently appear) for an instant and then displays all the child nodes. It's very frustrating, mostly because I can't find anything online similar to this.
I've set the TreeView to use double buffering but although it covers lots of flicking, it doesn't cover this glitch.
Here's my TreeView class:
using System;
using System.Drawing;
using System.Windows.Forms;
using System.Runtime.InteropServices;
namespace Liq.CommonControls.Tree
{
public partial class LiqTreeView : TreeView
{
private Brush backBrush, highlightBrush, blackBrush, textBrush;
private Font drawFont;
private const int TVM_SETEXTENDEDSTYLE = 0x1100 + 44;
private const int TVM_GETEXTENDEDSTYLE = 0x1100 + 45;
private const int TVS_EX_DOUBLEBUFFER = 0x0004;
private const int WM_ERASEBKGND = 0x0014;
[DllImport("user32.dll")]
private static extern IntPtr SendMessage(IntPtr hWnd, int msg, IntPtr wp, IntPtr lp);
public LiqTreeView()
{
InitializeComponent();
this.DrawNode += LiqTreeView_DrawNode;
BackColor = Color.FromArgb(255, 105, 105, 105);
backBrush = new SolidBrush(this.BackColor);
highlightBrush = new SolidBrush(Color.LightGray);
blackBrush = new SolidBrush(Color.Black);
textBrush = new SolidBrush(Color.WhiteSmoke);
drawFont = new Font("Arial", 14, FontStyle.Bold, GraphicsUnit.Pixel);
}
protected override void OnHandleCreated(EventArgs e)
{
SendMessage(this.Handle, TVM_SETEXTENDEDSTYLE, (IntPtr)TVS_EX_DOUBLEBUFFER, (IntPtr)TVS_EX_DOUBLEBUFFER);
base.OnHandleCreated(e);
}
protected override void WndProc(ref Message m)
{
if (m.Msg == WM_ERASEBKGND)
{
m.Result = IntPtr.Zero;
return;
}
base.WndProc(ref m);
}
private void LiqTreeView_DrawNode(object sender, DrawTreeNodeEventArgs e)
{
if (e.Bounds.Height < 1 || e.Bounds.Width < 1) return;
e.DrawDefault = false;
if (!e.Node.IsVisible)
return;
if (e.Node.IsSelected)
e.Graphics.FillRectangle(highlightBrush, e.Bounds);
Rectangle r = e.Bounds;
StringFormat sf = new StringFormat();
sf.Alignment = StringAlignment.Near;
sf.LineAlignment = StringAlignment.Near;
sf.Trimming = StringTrimming.Character;
r.Y -= 2;
r.X += e.Node.Level * 10;
TextRenderer.DrawText(e.Graphics, e.Node.Text, drawFont, (Rectangle)r, Color.Black, TextFormatFlags.Left|TextFormatFlags.EndEllipsis);
r.Y -= 1;
r.X += 1;
TextRenderer.DrawText(e.Graphics, e.Node.Text, drawFont, (Rectangle)r, Color.WhiteSmoke, TextFormatFlags.Left | TextFormatFlags.EndEllipsis);
}
}
}
Related
The basic TabControl doesn't fit my needs design wise and needed a redesign.
The things that i needed were:
No borders
Gray background with white text for the selected tab
Black background with gray text for not selected tab
No doted line inside the selected tab (this one is a bit low priority)
I managed to fix some of these issues by creating a new class except for the last two. Seeing my limited experience (first time doing something like this) I was hoping somebody here could point me in the right direction.
Below an illustration of what it currently is and how I would want it to look. And of course the code of the class I used to do this.
What it looks like:
What i want it to look like:
The code:
class CustomTabControl : TabControl
{
public CustomTabControl(): base()
{
this.DrawMode = TabDrawMode.OwnerDrawFixed;
this.DrawItem += new DrawItemEventHandler(tabControl1_DrawItem);
}
private void tabControl1_DrawItem(object sender, DrawItemEventArgs e)
{
Font fntTab;
Brush bshBack;
Brush bshFore;
if (e.Index == this.SelectedIndex)
{
fntTab = new Font(e.Font, FontStyle.Bold);
bshBack = new System.Drawing.Drawing2D.LinearGradientBrush(e.Bounds, Color.FromArgb(64,64,64), Color.FromArgb(0, 0, 0), System.Drawing.Drawing2D.LinearGradientMode.BackwardDiagonal);
bshFore = Brushes.White;
}
else
{
fntTab = new Font(e.Font, FontStyle.Bold);
bshBack = new System.Drawing.Drawing2D.LinearGradientBrush(e.Bounds, Color.FromArgb(0,0,0), Color.FromArgb(0, 0, 0), System.Drawing.Drawing2D.LinearGradientMode.BackwardDiagonal);
bshFore = Brushes.Gray;
}
string tabName = this.TabPages[e.Index].Text;
StringFormat sftTab = new StringFormat();
e.Graphics.FillRectangle(bshBack, e.Bounds);
Rectangle recTab = e.Bounds;
recTab = new Rectangle(recTab.X +3 , recTab.Y+3, recTab.Width, recTab.Height );
e.Graphics.DrawString(tabName, fntTab, bshFore, recTab, sftTab);
Rectangle r = this.GetTabRect(this.TabPages.Count - 1);
RectangleF tf = new RectangleF(r.X + r.Width, r.Y - 2, this.Width - (r.X + r.Width) + 0, r.Height + 4);
Brush b = Brushes.Black;
e.Graphics.FillRectangle(b, tf);
}
protected override void WndProc(ref Message m)
{
if (m.Msg == 0x0005)
{
int Width = unchecked((short)m.LParam);
int Height = unchecked((short)((uint)m.LParam >> 16));
Region = new Region(new Rectangle(4, 2, Width - 8, Height - 6));
}
base.WndProc(ref m);
}
}
(And suggestions on improving this class are welcome.)
I've drawn on a Tool Tip control - The code is below.
private void toolTip1_Popup(object sender, PopupEventArgs e)
{
ToolTip tt = (sender as ToolTip);
string toolTipText = tt.GetToolTip(e.AssociatedControl);
e.ToolTipSize = TextRenderer.MeasureText(toolTipText, new Font("Arial", 16.0f));
}
private void toolTip1_Draw(object sender, DrawToolTipEventArgs e) => DrawToolTip(sender, e);
[System.Runtime.InteropServices.DllImport("User32.dll")]
static extern bool MoveWindow(IntPtr h, int x, int y, int width, int height, bool redraw);
private void DrawToolTip(object sender, DrawToolTipEventArgs e)
{
ToolTip tt = (sender as ToolTip);
string toolTipText = tt.GetToolTip(e.AssociatedControl);
Font f = new Font("Arial", 16.0f);
e.DrawBackground();
e.DrawBorder();
toolTipText = e.ToolTipText;
e.Graphics.DrawString(e.ToolTipText, f, Brushes.Black, new PointF(2, 2));
}
And then to set the ToolTip text:
toolTip1.SetToolTip(btnLogin, "Some text.....");
Additionally, here is an image of what is happening.
It looks alright, but it has come to my attention that if the text is a certain length, the tooltip will go off-screen. Is there anyway to prevent that? I would rather not have to add Environment.NewLine(); or \n etc, since I would need to do that for MANY strings.
If I understand your question correctly, you are trying to combine the two solutions posted here and here to mainly resize the ToolTip window with the screen width, and move it to Point(2, 2), screen coordinates.
If that is what you need, you need to modify the source codes a bit to set the right e.ToolTipSize in the Popup event, and as the gentlemen commented above, draw the string in a rectangle, the e.Bounds property in the Draw event.
using System;
using System.Drawing;
using System.Reflection;
using System.Runtime.InteropServices;
using System.Windows.Forms;
public partial class Form1 : Form
{
TextFormatFlags flags = TextFormatFlags.VerticalCenter
| TextFormatFlags.Left
| TextFormatFlags.LeftAndRightPadding
| TextFormatFlags.NoClipping
| TextFormatFlags.WordBreak;
public Form1()
{
InitializeComponent();
}
private void toolTip1_Popup(object sender, PopupEventArgs e)
{
var tt = sender as ToolTip;
var toolTipText = tt.GetToolTip(e.AssociatedControl);
var screen = Screen.FromControl(e.AssociatedControl).WorkingArea;
using (var g = e.AssociatedControl.CreateGraphics())
using (var font = new Font("Arial", 16))
{
var sz = TextRenderer.MeasureText(
g, toolTipText, font, screen.Size, flags);
e.ToolTipSize = new Size(screen.Width - 2, sz.Height + 10);
}
}
private void toolTip1_Draw(object sender, DrawToolTipEventArgs e)
{
var t = sender as ToolTip;
var h = (IntPtr)t.GetType().GetProperty("Handle",
BindingFlags.NonPublic | BindingFlags.Instance).GetValue(t);
MoveWindow(h, 2, 2, e.Bounds.Width - 2, e.Bounds.Height, false);
e.DrawBackground();
e.DrawBorder();
using (var font = new Font("Arial", 16))
TextRenderer.DrawText(e.Graphics, e.ToolTipText, font,
e.Bounds, Color.Black, flags);
}
[DllImport("User32.dll")]
static extern bool MoveWindow(IntPtr h, int x, int y,
int width, int height, bool redraw);
}
I created the custom combobox on .net framework 1.1, i can custom draw dropdown items, but i can't set or draw the combobox text on Middle Left , combobox text always render top left , but i need text should be render on middle left.
[ToolboxBitmap(typeof(ComboBox))]
public class MenComboBox :ComboBox
{
private Image _image = Image.FromFile("Expand.png");
public MenComboBox()
{
this.DrawMode = DrawMode.OwnerDrawFixed;
this.BackColor = Color.White;
this.ItemHeight = 18;
this.Font = new Font("Arial",12f,FontStyle.Regular);
}
protected override void OnDrawItem(DrawItemEventArgs e)
{
if (!DesignMode)
{
if (e.Index > -1)
{
int textHeight = (int)e.Graphics.MeasureString(this.Items[e.Index].ToString(), e.Font).Height;
Point textPos = new Point(e.Bounds.X + 4, e.Bounds.Y + ((this.ItemHeight - textHeight) / 2));
if ((e.State & DrawItemState.Selected) == DrawItemState.Selected)
{
e.Graphics.FillRectangle(Brushes.Blue, e.Bounds);
e.Graphics.DrawString(this.Items[e.Index].ToString(),e.Font,Brushes.White,textPos);
}
else
{
e.Graphics.FillRectangle(Brushes.White, e.Bounds);
e.Graphics.DrawString(this.Items[e.Index].ToString(),e.Font,Brushes.Black,textPos);
}
}
}
}
protected override void WndProc(ref Message m)
{
base.WndProc(ref m);
if (m.Msg == 0x000F)
{
using (Graphics g = this.CreateGraphics())
{
g.FillRectangle(new SolidBrush(BackColor), this.ClientRectangle);
g.DrawRectangle(Pens.Blue, new Rectangle(this.ClientRectangle.X, this.ClientRectangle.Y, this.ClientRectangle.Width - 1, this.ClientRectangle.Height - 1));
Rectangle rect = new Rectangle(this.Width - 15, 3, 12, this.Height - 6);
g.FillRectangle(new SolidBrush(BackColor), rect);
g.DrawImage(this._image, this.Width - 16, (this.Height - 8) / 2);
g.Dispose();
}
}
}
}
In an owner draw ComboBox the text of the Edit part of the control will always be shown at top left, regardless of the height of the ItemHeight.
To position the Edit part vertically in middle, you can find the Edit element using GetComboBoxInfo and then using SetWindowPos set a new position for it to stand vertically in middle of the ComboBox.
You need to reposition it when the control size changes. Also you need to fill the background of ComboBox with a Color.
Here is the code that I used:
using System;
using System.Drawing;
using System.Runtime.InteropServices;
using System.Windows.Forms;
public class MyComboBox : ComboBox
{
public MyComboBox()
{
SetStyle(ControlStyles.ResizeRedraw, true);
DrawMode = DrawMode.OwnerDrawFixed;
ItemHeight = 40;
}
[StructLayout(LayoutKind.Sequential)]
public struct RECT
{
public int Left;
public int Top;
public int Right;
public int Bottom;
public int Width { get { return Right - Left; } }
public int Height { get { return Bottom - Top; } }
}
private const int SWP_NOSIZE = 0x0001;
private const int SWP_NOZORDER = 0x0004;
private const int SWP_SHOWWINDOW = 0x0040;
[DllImport("user32.dll", SetLastError = true)]
static extern bool SetWindowPos(IntPtr hWnd, IntPtr hWndInsertAfter,
int X, int Y, int cx, int cy, int uFlags);
[DllImport("user32.dll")]
public static extern bool GetComboBoxInfo(IntPtr hWnd, ref COMBOBOXINFO pcbi);
[StructLayout(LayoutKind.Sequential)]
public struct COMBOBOXINFO
{
public int cbSize;
public RECT rcItem;
public RECT rcButton;
public int stateButton;
public IntPtr hwndCombo;
public IntPtr hwndEdit;
public IntPtr hwndList;
}
protected override void OnResize(EventArgs e)
{
base.OnResize(e);
SetupEdit();
Invalidate();
}
private int buttonWidth = SystemInformation.HorizontalScrollBarArrowWidth;
protected override void WndProc(ref Message m)
{
if (m.Msg == 0xF)
{
using (var g = this.CreateGraphics())
{
var r = new Rectangle(2, 2,
ClientRectangle.Width - buttonWidth - 2,
ClientRectangle.Height - 4);
g.FillRectangle(Brushes.White, r);
}
}
base.WndProc(ref m);
}
protected override void OnVisibleChanged(EventArgs e)
{
base.OnVisibleChanged(e);
SetupEdit();
}
private void SetupEdit()
{
var info = new COMBOBOXINFO();
info.cbSize = Marshal.SizeOf(info);
GetComboBoxInfo(this.Handle, ref info);
SetWindowPos(info.hwndEdit, IntPtr.Zero, 3,
(this.Height - Font.Height) / 2,
ClientRectangle.Width - buttonWidth - 3,
ClientRectangle.Height - Font.Height - 4,
SWP_NOZORDER);
}
protected override void OnDrawItem(DrawItemEventArgs e)
{
base.OnDrawItem(e);
e.DrawBackground();
var txt = "";
if (e.Index >= 0)
txt = GetItemText(Items[e.Index]);
TextRenderer.DrawText(e.Graphics, txt, Font, e.Bounds,
ForeColor, TextFormatFlags.Left | TextFormatFlags.VerticalCenter);
}
}
ok, below code doesn't answer the actual question about the Text portion; Hans got it right, as usual.
I keep the answer because I think it does a few things better than OP code..
if (!DesignMode)
{
if (e.Index > -1)
{
using (StringFormat fmt = new StringFormat()
{ Alignment = StringAlignment.Center, LineAlignment = StringAlignment.Center })
{
if ((e.State & DrawItemState.Selected) == DrawItemState.Selected)
{
e.Graphics.FillRectangle(SystemBrushes.MenuHighlight, e.Bounds);
e.Graphics.DrawString(comboBox1.Items[e.Index].ToString(),
e.Font,SystemBrushes.HighlightText, e.Bounds, fmt);
}
else
{
e.Graphics.FillRectangle(SystemBrushes.Window, e.Bounds);
e.Graphics.DrawString(comboBox1.Items[e.Index].ToString(),
e.Font, SystemBrushes.MenuText,e.Bounds, fmt);
}
}
}
}
Instead of calculating a centered position I use the DrawString overload that takes a target rectangle and add a StringFormat to center in both directions. StringFormat was available since .Net 1.1 and indeed is IDisposable, so we should dipose of each we create, best in a using clause..
Note that for drawing controls the use of TextRenderer is encouraged but only came with .Net 2.0.
Also note that I substituted the Brushes for SystemBrushes..
Also: My ComboBox doesn't place the text in its Text portion top-left but middle-left. Maybe the old .Net1.1 control is the culprit?
I have a custom TextBox in which I draw some place holder text when it's empty.
It works pretty well, but it flickers when the mouse enters and leaves the TextBox. It seems related to the border becoming blue when the mouse hovers the control (I'm on Windows 8.1).
Any idea how I could fix this ?
I've tried various SetStyles flags without success.
class MyTextBox : TextBox
{
public string PlaceHolder { get; set; }
static readonly Brush sPlaceHolderBrush = new SolidBrush(Color.FromArgb(70, 70, 78));
static readonly StringFormat sFormat = new StringFormat
{
Alignment = StringAlignment.Near,
LineAlignment = StringAlignment.Center
};
private Font mPlaceHolderFont;
[DllImport("user32")]
private static extern IntPtr GetWindowDC(IntPtr hwnd);
protected override void WndProc(ref Message m)
{
base.WndProc(ref m);
if (m.Msg == 0x0F)
{
if (string.IsNullOrEmpty(Text) && !Focused)
{
IntPtr dc = GetWindowDC(Handle);
using (Graphics g = Graphics.FromHdc(dc))
{
if (mPlaceHolderFont == null)
mPlaceHolderFont = new Font(Font, FontStyle.Italic);
var rect = new RectangleF(2, 2, Width - 4, Height - 4);
g.FillRectangle(Brushes.White, rect);
g.DrawString(PlaceHolder, mPlaceHolderFont, sPlaceHolderBrush, rect, sFormat);
}
}
}
}
}
I had other problems with overriding OnPaint. Here is the best solution I came up with :
class MyTextBox : TextBox
{
public string PlaceHolder { get; set; }
static readonly Brush sPlaceHolderBrush = new SolidBrush(Color.FromArgb(70, 70, 78));
static readonly StringFormat sFormat = new StringFormat
{
Alignment = StringAlignment.Near,
LineAlignment = StringAlignment.Near
};
private Font mPlaceHolderFont;
private Brush mForegroundBrush;
protected override void OnHandleCreated(EventArgs e)
{
base.OnHandleCreated(e);
SetStyle(ControlStyles.OptimizedDoubleBuffer | ControlStyles.DoubleBuffer | ControlStyles.AllPaintingInWmPaint | ControlStyles.UserPaint, true);
}
protected override void OnPaint(PaintEventArgs e)
{
var bounds = new Rectangle(-2, -2, Width, Height);
var rect = new RectangleF(1, 0, Width - 2, Height - 2);
e.Graphics.FillRectangle(Brushes.White, rect);
if (string.IsNullOrEmpty(Text) && !Focused)
{
if (mPlaceHolderFont == null)
mPlaceHolderFont = new Font(Font, FontStyle.Italic);
if (mForegroundBrush == null)
mForegroundBrush = new SolidBrush(ForeColor);
e.Graphics.DrawString(PlaceHolder, mPlaceHolderFont, sPlaceHolderBrush, rect, sFormat);
}
else
{
var flags = TextFormatFlags.Default | TextFormatFlags.TextBoxControl;
if (!Multiline)
flags |= TextFormatFlags.SingleLine | TextFormatFlags.NoPadding;
TextBoxRenderer.DrawTextBox(e.Graphics, bounds, Text, Font, flags, TextBoxState.Selected);
}
}
}
Is there a special reason for using WM_PAINT instead of OnPaint? In WM_PAINT you obtain a drawing context from the handle, which is always a direct access to the control. In OnPaint you already have a Graphics in the event args, which can be either a buffer or a direct context, depending on the styles.
You mentioned that you have tried a few styles with no success. Firstly I would say try these and move your paint logic into OnPaint:
SetStyle(ControlStyles.OptimizedDoubleBuffer | ControlStyles.DoubleBuffer | ControlStyles.AllPaintingInWmPaint | ControlStyles.UserPaint, true);
If it does not work (a focused control may behave strangely in Windows) and you must stick to WM_PAINT, then create a buffer manually. Your original code draws a white rectangle first, then some text, which causes flickering. You can avoid this by using a buffer:
IntPtr dc = GetWindowDC(Handle);
using (Graphics g = Graphics.FromHdc(dc))
{
// creating a buffered context
using (BufferedGraphicsContext context = new BufferedGraphicsContext())
{
// creating a buffer for the original Graphics
using (BufferedGraphics bg = context.Allocate(e.Graphics, ClientRectangle))
{
if (mPlaceHolderFont == null)
mPlaceHolderFont = new Font(Font, FontStyle.Italic);
var gBuf = bg.Graphics;
var rect = ClientRectangle;
rect.Inflate(-1, -1);
gBuf.FillRectangle(Brushes.White, rect);
gBuf.DrawString(PlaceHolder, mPlaceHolderFont, sPlaceHolderBrush, rect, sFormat);
// copying the buffer onto the original Graphics
bg.Render(e.Graphics);
}
}
}
I am working on creating a simple notebook application. I have been asked to make the input area look like a sheet of notebook paper, with the text sitting on light blue lines. I am trying to make this work, but it seems to be failing miserably.
So far, I have created a transparent RichTextBox that sits on top of a panel. The Text Box is:
using System;
using System.Windows.Forms;
public class TransparentTextBox : RichTextBox
{
public TransparentTextBox()
{
this.SetStyle(ControlStyles.Opaque, true);
this.SetStyle(ControlStyles.OptimizedDoubleBuffer, false);
}
protected override CreateParams CreateParams
{
get
{
CreateParams parms = base.CreateParams;
parms.ExStyle |= 0x20; // Turn on WS_EX_TRANSPARENT
return parms;
}
}
}
The paint code for the panel:
private void paper_Paint(object sender, PaintEventArgs e)
{
Graphics g = e.Graphics;
g.Clear(Color.White);
g.DrawLine(new Pen(Brushes.LightPink, 2), 20, 0, 20, paper.Height);
int h = TextRenderer.MeasureText("Testj", txtBody.Font).Height;
for (int x = 2 + h; x < paper.Height; x += h)
{
g.DrawLine(new Pen(Brushes.LightSkyBlue, 2), 0, x, paper.Width, x);
}
}
The lines are static, and they will grow to fit any font size/family that is chosen. The problem is when the text box is scrolled. The lines won't move with the text. I have tried to link the handle of the scroll bar to the lines, but they don't seem to be linking properly.
The code to get the current scroll position:
[StructLayout(LayoutKind.Sequential)]
public struct SCROLLINFO
{
public int cbSize;
public uint fMask;
public int nMin;
public int nMax;
public uint nPage;
public int nPos;
public int nTrackPos;
}
public enum ScrollBarDirection
{
SB_HORZ = 0,
SB_VERT = 1,
SB_CTL = 2,
SB_BOTH = 3
}
public enum ScrollInfoMask
{
SIF_RANGE = 0x1,
SIF_PAGE = 0x2,
SIF_POS = 0x4,
SIF_DISABLENOSCROLL = 0x8,
SIF_TRACKPOS = 0x10,
SIF_ALL = SIF_RANGE + SIF_PAGE + SIF_POS + SIF_TRACKPOS
}
...
public partial class Form1 : Form
{
[DllImport("User32.dll", EntryPoint = "GetScrollInfo")]
[return: MarshalAs(UnmanagedType.Bool)]
private static extern bool GetScrollInfo([In]IntPtr hwnd, [In]int fnBar, [In, Out]ref SCROLLINFO lpsi);
...
private void txtBody_VScroll(object sender, EventArgs e)
{
inf.cbSize = Marshal.SizeOf(inf);
inf.fMask = (int)ScrollInfoMask.SIF_ALL;
GetScrollInfo(txtBody.Handle, 1, ref inf);
Console.WriteLine(inf.nTrackPos + ":" + inf.nPos + ":" + TextRenderer.MeasureText("Testj", txtBody.Font).Height);
paper.Invalidate();
}
Then the paint above was modified to use this:
for (int x = inf.nPos % h; x < paper.Height; x += h)
{
g.DrawLine(new Pen(Brushes.LightSkyBlue, 2), 0, x, paper.Width, x);
}
I also tried to use nTrackPos, but neither seemed to follow the text like I want it to. I'm not too familiar with C#, so I wanted to know what I am missing/could do better. I am using Visual Studio 2008, with Visual C# 2008. .Net framework 3.5 SP1
So, here is what I came up with after some intensive googling. I decided to follow more into Gusman's comment on my question and look into drawing on the textbox again. After some playing, I realized I was improperly calculating the position of the start line. So, I reconfigured my custom RichTextBox to look like:
using System;
using System.Drawing;
using System.Windows.Forms;
using System.Runtime.InteropServices;
namespace Journal
{
class CustomRichTextBox : RichTextBox
{
private const int WM_HSCROLL = 0x114;
private const int WM_VSCROLL = 0x115;
private const int WM_MOUSEWHEEL = 0x20A;
private const int WM_PAINT = 0x00F;
private const int EM_GETSCROLLPOS = 0x4DD;
public int lineOffset = 0;
[DllImport("user32.dll")]
public static extern int SendMessage(
IntPtr hWnd,
int Msg,
IntPtr wParam,
ref Point lParam
);
protected override void WndProc(ref Message m)
{
base.WndProc(ref m);
if (m.Msg == WM_PAINT)
{
using (Graphics g = base.CreateGraphics())
{
Point p = new Point();
//get the position of the scrollbar to calculate the offset
SendMessage(this.Handle, EM_GETSCROLLPOS, IntPtr.Zero, ref p);
//draw the pink line on the side
g.DrawLine(new Pen(Brushes.LightPink, 2), 0, 0, 0, this.Height);
//determine how tall the text will be per line
int h = TextRenderer.MeasureText("Testj", this.Font).Height;
//calculate where the lines need to start
lineOffset = h - (p.Y % h);
//draw lines until there is no more box
for (int x = lineOffset; x < Height; x += h)
{
g.DrawLine(new Pen(Brushes.LightSkyBlue, 2), 0, x, Width, x);
}
//force the panel under us to draw itself.
Parent.Invalidate();
}
}
}
public CustomRichTextBox()
{
this.SetStyle(ControlStyles.OptimizedDoubleBuffer, true);
}
}
}
I then set this box inside of a panel to get the padding I want. The panel is forced to redraw itself with the text box.