/// <summary>
/// 自动适应文本大小的文本框控件
/// 继承自TextBox
/// 2007 9 30 bug:键盘输入时,并不能保持中心点不变,原因:前面一个实现时,在每次变化时去求前一次的
/// 保持的位置,长久下来累计误差比较大,因为中间涉及到整数除2的操作,可能会舍入。修改为开始就保存好不变的位置,
/// 解决了这一问题
///
/// 2007 10 20 终于解决当字体设置为Times New Roman, 10pt时的,并不能完全适应文本大小的问题。
/// 原因在于TextRenderer.MeasureText使用的Times New Roman, 10pt实际上设置到textbox时为Times New Roman, 9.75pt
/// 解决方法:获得LOGFONT,并创建一个临时的font来测量大小。
/// 同时发现textbox和label的处理font的舍入方式并不一样,也即对LOGFONT.lfHeight的处理。
/// 从而造成最后有时相同的font,label字体的效果和textbox不一样。
///
/// 进一步研究发现:
/// textbox 使用 -MulDiv(PointSize, GetDeviceCaps(hDC, LOGPIXELSY), 72) 计算lfHeight,只有lfheight超过了0.5才进入下一个整数
/// 也即 -13.4=-13 -13.5=-14
/// 然而 label不是这样处理的,label一旦发现比-13大一点点就会认为lfheight=-14
/// 然后,用发射工具看了label的源代码,发现原来是TextRender.DrawText的问题,里面使用了WindowsFont,
/// 而Winfont去创建font句柄的时候,使用了
/// int num = (int) Math.Ceiling((double) ((WindowsGraphicsCacheManager.MeasurementGraphics.DeviceContext.DpiY * size) / 72f));
/// this.logFont.lfHeight = -num;
/// Math.Ceiling是求得 返回大于或等于指定的小数的最小整数。没有四舍五入,所以会发生以上的情况。
///
/// 而Control.FontHandle采用Font创建句柄时却采用了四舍五入,不过我没有看到明确的代码,因为返回LOGFONT在api
/// GdipGetLogFontA中,不过对textbox实际测试时的结果证明了这一点。
///
/// 我想这应该是微软的bug?
///
/// 为了灵活起见,我设定了一个成员变量,表示时候对字体的高度进行四舍五入。
/// 如果选true那么进行四舍五入,如果选false那么只是取大于等于的整数,类似于label。
/// </summary>
using System;
using System.Collections.Generic;
using System.Text;
using System.ComponentModel;
using System.ComponentModel.Design;
using System.Windows.Forms;
using System.Windows.Forms.Design;
using System.Runtime.InteropServices;
using System.Drawing;
namespace Hob.Toolbox.Controls
{
/// <summary>
/// 锁定的位置
/// </summary>
public enum LockPosition
{
LockTopLeft,
LockTopHCenter,
LockTopRight,
LockVCenterRight,
LockBottomRight,
LockBottomHCenter,
LockBottomLeft,
LockVCenterLeft,
LockVCenterHCenter
}
public class AutoSizeTextBox : TextBox
{
#region windows api
[StructLayout(LayoutKind.Sequential, CharSet = CharSet.Auto)]
public class LOGFONT
{
public int lfHeight = 0;
public int lfWidth = 0;
public int lfEscapement = 0;
public int lfOrientation = 0;
public int lfWeight = 0;
public byte lfItalic = 0;
public byte lfUnderline = 0;
public byte lfStrikeOut = 0;
public byte lfCharSet = 0;
public byte lfOutPrecision = 0;
public byte lfClipPrecision = 0;
public byte lfQuality = 0;
public byte lfPitchAndFamily = 0;
[MarshalAs(UnmanagedType.ByValTStr, SizeConst = 32)]
public string lfFaceName = string.Empty;
}
private const uint WM_NCPAINT = 0x0085;
private const uint WM_PAINT = 0x000F;
private const int SWP_NOSIZE = 0x0001;
private const int SWP_NOMOVE = 0x0002;
private const int SWP_NOZORDER = 0x0004;
private const int SWP_NOACTIVATE = 0x0010;
private const int SWP_FRAMECHANGED = 0x0020; /* The frame changed: send WM_NCCALCSIZE */
public const uint WM_SETFONT = 0x0030;
[DllImport("user32.dll", EntryPoint = "GetWindowDC", CharSet = CharSet.Auto, ExactSpelling = true)]
private static extern IntPtr GetWindowDC(IntPtr hwnd);
[DllImport("user32.dll", EntryPoint = "ReleaseDC", CharSet = CharSet.Auto, ExactSpelling = true)]
private static extern int ReleaseDC(IntPtr hwnd, IntPtr hdc);
[DllImport("user32.dll", CharSet = CharSet.Auto, ExactSpelling = true)]
private static extern bool SetWindowPos(IntPtr hwnd, IntPtr hWndInsertAfter, int x, int y, int cx, int cy, int wFlags);
[DllImport("gdi32.dll", EntryPoint = "CreateFontIndirect", CharSet = CharSet.Auto, SetLastError = true)]
public static extern IntPtr CreateFontIndirect(LOGFONT lf);
[DllImport("gdi32.dll", EntryPoint = "DeleteObject", CharSet = CharSet.Auto, SetLastError = true, ExactSpelling = true)]
public static extern bool DeleteObject(IntPtr hObject);
[DllImport("user32.dll", CharSet = CharSet.Auto)]
public static extern bool SendMessage(IntPtr hwnd, uint msg, IntPtr wparam, IntPtr lparam);
#endregion
public AutoSizeTextBox()
{
//默认多行
base.Multiline = true;
base.WordWrap = false;
base.TextAlign = HorizontalAlignment.Center;
base.BorderStyle = BorderStyle.FixedSingle;
m_LockPosition = LockPosition.LockTopLeft;
m_FrameColor = Color.Gray;
m_unchangleLocationInit = false;
m_RoundFontHeight=true;
m_FontHandleWhenNotRound = IntPtr.Zero;
}
/// <summary>
/// 不变化的位置坐标
/// </summary>
private Point m_unchangeLocation;
bool m_unchangleLocationInit;//是否已经初始化m_unchangeLocation
/// <summary>
/// 调整大小
/// </summary>
private void AdjustSize()
{
ClientSize = GetRightClientSize();
//调整大小
if (!m_unchangleLocationInit) return;
switch (m_LockPosition)//保持位置
{
case LockPosition.LockTopLeft:
Location = m_unchangeLocation;
break;
case LockPosition.LockTopHCenter:
Location = new Point(m_unchangeLocation.X - Size.Width / 2, m_unchangeLocation.Y);
break;
case LockPosition.LockTopRight:
Location = new Point(m_unchangeLocation.X - Size.Width, m_unchangeLocation.Y);
break;
case LockPosition.LockVCenterRight:
Location = new Point(m_unchangeLocation.X - Size.Width, m_unchangeLocation.Y - Size.Height / 2);
break;
case LockPosition.LockBottomRight:
Location = new Point(m_unchangeLocation.X - Size.Width, m_unchangeLocation.Y - Size.Height);
break;
case LockPosition.LockBottomHCenter:
Location = new Point(m_unchangeLocation.X - Size.Width / 2, m_unchangeLocation.Y - Size.Height);
break;
case LockPosition.LockBottomLeft:
Location = new Point(m_unchangeLocation.X, m_unchangeLocation.Y - Size.Height);
break;
case LockPosition.LockVCenterLeft:
Location = new Point(m_unchangeLocation.X, m_unchangeLocation.Y - Size.Height / 2);
break;
case LockPosition.LockVCenterHCenter:
Location = new Point(m_unchangeLocation.X - Size.Width / 2, m_unchangeLocation.Y - Size.Height / 2);
break;
}
InvalidateFrame();
}
/// <summary>
/// 重绘边框
/// </summary>
private void InvalidateFrame()
{
if (BorderStyle == BorderStyle.Fixed3D)
SetWindowPos(Handle, (IntPtr)0, 0, 0, 0, 0,
SWP_NOSIZE | SWP_NOMOVE | SWP_NOZORDER | SWP_NOACTIVATE | SWP_FRAMECHANGED);
}
/// <summary>
/// 得到合适的客户区大小
/// </summary>
/// <returns></returns>
private Size GetRightClientSize()
{
Size size = GetRightTextSize();
int borderwidth = GetBorderWidth();
size.Width += borderwidth * 2;
size.Height += borderwidth * 2;
return size;
}
/// <summary>
/// 得到合适的大小(客户区和边框一起)
/// </summary>
/// <returns></returns>
private Size GetRightSize()
{
Size size = GetRightClientSize();
int borderwidth = GetBorderWidth();
size.Width += borderwidth * 2;
size.Height += borderwidth * 2;
return size;
}
/// <summary>
/// 当文本改变时
/// </summary>
/// <param name="e"></param>
protected override void OnTextChanged(EventArgs e)
{
//call base
base.OnTextChanged(e);
//set auto size
AdjustSize();
}
/// <summary>
/// 字体变化
/// </summary>
/// <param name="e"></param>
protected override void OnFontChanged(EventArgs e)
{
base.OnFontChanged(e);
AdjustSize();
}
/// <summary>
/// 得到合适的尺寸
/// </summary>
/// <returns></returns>
private Size GetRightTextSize()
{
String str = Text;
int len = str.Length;
if (len<2) str = "t";//至少一个宽度
int count = Lines.Length;
if (count >= 1)
if (Lines[count - 1] == "")
str += "t";
Size size;
if(m_RoundFontHeight)
{
//采用了四舍五入,但是TextRenderer.MeasureText不是四舍五入来计算的
//所以我们要传给他正确的font
LOGFONT logfont = new LOGFONT();
Font.ToLogFont(logfont);
Font measureFont = Font.FromLogFont(logfont);
size = TextRenderer.MeasureText(str, measureFont);
measureFont.Dispose();
}
else
size=TextRenderer.MeasureText(str, Font);
return size;
}
//border改变
protected override void OnBorderStyleChanged(EventArgs e)
{
base.OnBorderStyleChanged(e);
AdjustSize();
}
/// <summary>
/// 得到边框宽度
/// </summary>
/// <returns></returns>
private int GetBorderWidth()
{
if (BorderStyle == BorderStyle.Fixed3D)
return 2;
else if (BorderStyle == BorderStyle.FixedSingle)
return 1;
else //none
return 0;
}
//隐藏Multiline
[Browsable(false)]
public override bool Multiline
{
get
{
return base.Multiline;
}
set
{
base.Multiline = value;
}
}
//隐藏WordWrap
[Browsable(false)]
public new bool WordWrap
{
get
{
return base.WordWrap;
}
set
{
base.WordWrap = value;
}
}
[DefaultValue(HorizontalAlignment.Center)]//默认值设为Center,并且构造函数为center
public new HorizontalAlignment TextAlign
{
get
{
return base.TextAlign;
}
set
{
base.TextAlign = value;
}
}
[DefaultValue(BorderStyle.FixedSingle)]
public new BorderStyle BorderStyle
{
get
{
return base.BorderStyle;
}
set
{
base.BorderStyle = value;
}
}
private Color m_FrameColor;
[Category("Appearance"), Description("边框的颜色"), DefaultValue(typeof(Color), "Gray")]
public Color FrameColor
{
get
{
return m_FrameColor;
}
set
{
m_FrameColor = value;
InvalidateFrame();
}
}
/// <summary>
/// 重载wndproc
/// </summary>
/// <param name="m"></param>
protected override void WndProc(ref Message m)
{
//字体不采用四舍五入时,那么我们纠正字体
if (!m_RoundFontHeight && m.Msg == WM_SETFONT)
m.WParam = CreateFontHandleWhenNotRound();
base.WndProc(ref m);
if (m.Msg == WM_PAINT)
DrawFixedSingle();
if (m.Msg == WM_NCPAINT)
DrawFixed3D();
}
/// <summary>
/// 绘制非客户区边框
/// </summary>
private void DrawFixed3D()
{
if (BorderStyle == BorderStyle.Fixed3D)
{
Pen pen = new Pen(this.m_FrameColor);
IntPtr windc = GetWindowDC(Handle);
Graphics borderG = Graphics.FromHdc(windc);
borderG.DrawRectangle(pen, 0, 0, Size.Width - 1, Size.Height - 1);
ReleaseDC(Handle, windc);
borderG.Dispose();
pen.Dispose();
}
}
/// <summary>
/// 绘制客户区边框
/// </summary>
private void DrawFixedSingle()
{
if (BorderStyle == BorderStyle.FixedSingle)
{
Pen pen = new Pen(this.m_FrameColor);
Graphics dc = Graphics.FromHwnd(Handle);
dc.DrawRectangle(pen, 0, 0, Size.Width - 1, Size.Height - 1);
dc.Dispose();
pen.Dispose();
}
}
//锁定的不变位置
private LockPosition m_LockPosition;
[Category("Layout"), Description("锁定的不变位置"), DefaultValue(LockPosition.LockTopLeft)]
//修改名字为TextLockPosition,使得InitializeComponent在text改变后加载TextLockPosition属性
public LockPosition TextLockPosition
{
get
{
return m_LockPosition;
}
set
{
m_LockPosition = value;
UpdateunchangeLocation();
}
}
/// <summary>
/// 更新不变的位置
/// </summary>
private void UpdateunchangeLocation()
{
//求不变的位置坐标
switch (m_LockPosition)//求保持的位置
{
case LockPosition.LockTopLeft:
m_unchangeLocation = Location;
break;
case LockPosition.LockTopHCenter:
m_unchangeLocation = new Point(Location.X + Size.Width / 2, Location.Y);
break;
case LockPosition.LockTopRight:
m_unchangeLocation = new Point(Location.X + Size.Width, Location.Y);
break;
case LockPosition.LockVCenterRight:
m_unchangeLocation = new Point(Location.X + Size.Width, Location.Y + Size.Height / 2);
break;
case LockPosition.LockBottomRight:
m_unchangeLocation = new Point(Location.X + Size.Width, Location.Y + Size.Height);
break;
case LockPosition.LockBottomHCenter:
m_unchangeLocation = new Point(Location.X + Size.Width / 2, Location.Y + Size.Height);
break;
case LockPosition.LockBottomLeft:
m_unchangeLocation = new Point(Location.X, Location.Y + Size.Height);
break;
case LockPosition.LockVCenterLeft:
m_unchangeLocation = new Point(Location.X, Location.Y + Size.Height / 2);
break;
case LockPosition.LockVCenterHCenter:
m_unchangeLocation = new Point(Location.X + Size.Width / 2, Location.Y + Size.Height / 2);
break;
}
m_unchangleLocationInit = true;
}
//重载禁止调整高度
protected override void SetBoundsCore(int x, int y, int width, int height, BoundsSpecified specified)
{
Size size = GetRightSize();
if (width != size.Width && ((specified & BoundsSpecified.Width) == BoundsSpecified.Width))
width = size.Width;
if (height != size.Height && ((specified & BoundsSpecified.Height) == BoundsSpecified.Height))
height = size.Height;
//call base
base.SetBoundsCore(x, y, width, height, specified);
}
/// <summary>
/// 覆盖Location
/// </summary>
public new Point Location
{
get
{
return base.Location;
}
set
{
base.Location = value;
UpdateunchangeLocation();
}
}
private bool m_RoundFontHeight;
/// <summary>
/// 是否对字体高度进行四舍五入
/// </summary>
[Category("Appearance"), Description("是否对字体高度进行四舍五入"), DefaultValue(true)]
public bool RoundFontHeight
{
get { return m_RoundFontHeight; }
set
{
m_RoundFontHeight = value;
if (!value)
SetWindowFont();
//set auto size
AdjustSize();
}
}
//不采用四舍五入时的font句柄
private IntPtr m_FontHandleWhenNotRound;
private IntPtr CreateFontHandleWhenNotRound()
{
if(m_FontHandleWhenNotRound==IntPtr.Zero)
{
LOGFONT logfont=new LOGFONT();
Font.ToLogFont(logfont);
Graphics dc=Graphics.FromHwnd(Handle);
//求字体高度
int num = (int)Math.Ceiling(Font.Size*dc.DpiX/ 72f);
dc.Dispose();
logfont.lfHeight=-num;
m_FontHandleWhenNotRound=CreateFontIndirect(logfont);
}
return m_FontHandleWhenNotRound;
}
private void DeleteFontHandleWhenNotRound()
{
if (m_FontHandleWhenNotRound != IntPtr.Zero)
{
DeleteObject(m_FontHandleWhenNotRound);
m_FontHandleWhenNotRound = IntPtr.Zero;
}
}
private void SetWindowFont()
{
SendMessage(Handle,WM_SETFONT, (IntPtr)0,(IntPtr)0);
}
/// <summary>
/// 重载dispose,释放非托管资源
/// </summary>
/// <param name="disposing"></param>
protected override void Dispose(bool disposing)
{
//do something
if (!IsDisposed)
{
if(disposing)
{
//free managed resource
}
//free unmanaged resource
DeleteFontHandleWhenNotRound();
}
base.Dispose(disposing);
}
}
}