继承control的自定义TextBox
下面来介绍一下本人写的一个自定义的textbox,首先说一下写这个控件遇到的几个难点:第一、关联输入法;第二、画字符串和焦点线
先随便上两张效果图吧:
下面这三个类是来自于网络某位高人的,不记得名字了,拿来用,代码看了一下,给有些地方没注释的加上了注释。
![](https://images.cnblogs.com/OutliningIndicators/ContractedBlock.gif)
/// <summary>
/// 提供Unmanaged方法处理Windows Message并接收输入法的输入信号。
/// </summary>
public class ImeWinMessageHandler
{
private Control _tarForm = null;
private IntPtr _tarHandle = IntPtr.Zero;
private IntPtr _hIMC = IntPtr.Zero;
/// <summary>
/// 取得一个值。这个值会指示哪一个辅助按键(Modifier Key)(Shift、Ctrl和Alt)处于按下的状态。
/// </summary>
public static Keys ModifierKeys
{
get
{
Keys m_Result = Keys.None;
if (NativeMethods.GetKeyState(0x10) < 0)
m_Result |= Keys.Shift;
if (NativeMethods.GetKeyState(0x11) < 0)
m_Result |= Keys.Control;
if (NativeMethods.GetKeyState(0x12) < 0)
m_Result |= Keys.Alt;
return m_Result;
}
}
/// <summary>
/// 初始化OptimistSutdioDll.Win32APIs.ImeWinMessageHandler的执行个体。
/// </summary>
/// <param name="form">用于处理Windows Message的 System.Windows.Forms.Form 执行个体。</param>
public ImeWinMessageHandler(Control form)
{
_tarForm = form;
}
private Boolean _Enabled = false;
/// <summary>
/// 取得或设置Handler是否启用。
/// </summary>
public Boolean Enabled
{
get { return _Enabled; }
set
{
if (Enabled != value)
{
_Enabled = value;
if (value)
{
_tarHandle = _tarForm.Handle;
_hIMC = NativeMethods.ImmGetContext(_tarHandle);
}
else
{
NativeMethods.ImmReleaseContext(_tarHandle, _hIMC);
_tarHandle = IntPtr.Zero;
_hIMC = IntPtr.Zero;
}
}
}
}
/// <summary>
/// 将Windows Message资料送入处理器进行处理。
/// </summary>
/// <param name="m">要处理 System.Windows.Forms.Message。</param>
public void InputMessage(ref Message m)
{
bool m_handled = false;
WndProc(m.HWnd, m.Msg, m.WParam, m.LParam, ref m_handled);
}
private IntPtr WndProc(IntPtr hwnd, int msg, IntPtr wParam, IntPtr lParam, ref bool handled)
{
if (Enabled)
{
if (msg == NativeContansts.WM_IME_SETCONTEXT && wParam.ToInt32() == 1)
NativeMethods.ImmAssociateContext(_tarHandle, _hIMC);
else if (msg == NativeContansts.WM_CHAR)
{
char m_Reult = (char)(new KeyEventArgs(((Keys)((int)(wParam))) | ImeWinMessageHandler.ModifierKeys).KeyData);
this.OnCharacterInputted(m_Reult);
}
}
return IntPtr.Zero;
}
private void OnCharacterInputted(char c)
{
if (this.CharacterInputted != null)
this.CharacterInputted(this, new CharacterInputtedEventArgs(c));
}
/// <summary>
/// 当Handler收到输入法输入字元进入应用程式时引发此事件。
/// </summary>
public event EventHandler<CharacterInputtedEventArgs> CharacterInputted;
}
/// <summary>
/// 提供 CharacterInputted的事件资料。
/// </summary>
public class CharacterInputtedEventArgs : EventArgs
{
/// <summary>
/// 取得输入法输入的字元。
/// </summary>
public Char Character { get; private set; }
internal CharacterInputtedEventArgs(char c)
{
this.Character = c;
}
}
![](https://images.cnblogs.com/OutliningIndicators/ContractedBlock.gif)
internal static class NativeMethods
{
/// <summary>
/// 获取当前正在输入的窗口的输入法句柄
/// </summary>
/// <param name="hWnd">要获取输入法的窗口句柄</param>
/// <returns></returns>
[DllImport("Imm32.dll")]
public static extern IntPtr ImmGetContext(IntPtr hWnd);
/// <summary>
/// 将指定的窗口和指定的输入法联系起来
/// </summary>
/// <param name="hWnd">指定的窗口</param>
/// <param name="hIMC">指定的输入法</param>
/// <returns></returns>
[DllImport("Imm32.dll")]
public static extern IntPtr ImmAssociateContext(IntPtr hWnd, IntPtr hIMC);
/// <summary>
/// 释放输入法和解锁相关联的内存
/// </summary>
/// <param name="hWnd">指定的有输入法的窗口</param>
/// <param name="hIMC">关联的输入法</param>
/// <returns></returns>
[DllImport("Imm32.dll")]
public static extern Boolean ImmReleaseContext(IntPtr hWnd, IntPtr hIMC);
/// <summary>
/// 指定虚拟键的状态
/// </summary>
/// <param name="keyCode">GetKeyState(VK_SHIFT) > 0 没按下GetKeyState(VK_SHIFT)小于0被按下</param>
/// <returns></returns>
[DllImport("user32.dll", CharSet = CharSet.Auto, ExactSpelling = true, CallingConvention = CallingConvention.Winapi)]
public static extern short GetKeyState(int keyCode);
}
internal static class NativeContansts { public const int WM_IME_SETCONTEXT = 0x0281; public const int WM_IME_COMPOSITION = 0x010F; public const int WM_CHAR = 0x0102; public const int GCS_COMPSTR = 0x0008; }
这三个类帮助我们把输入法跟控件关联起来,其中调用的四个API我都有注释,应该不难理解!
代码基本上该注释的地方都有注释,有不理解的大家可以回复,有时间给大家讲解!
接下看控件代码:
第一、如何调用输入法的三个类跟控件关联
![](https://images.cnblogs.com/OutliningIndicators/ContractedBlock.gif)
ImeWinMessageHandler ImeHandler;
public TxtBox()
{
InitializeComponent();
ImeHandler = new ImeWinMessageHandler(this);
ImeHandler.CharacterInputted += new EventHandler<CharacterInputtedEventArgs>(ImeHandler_CharacterInputted);
ImeHandler.Enabled = true;
}
private void ImeHandler_CharacterInputted(object sender, CharacterInputtedEventArgs e)
{
if (e.Character == '\b' && this.Text.Length > 0)
{
if (this.Text != prompt)
{
this.Text = this.Text.Remove(this.Text.Length - 1);
}
if (this.Text.Length == 0)
{ this.Text = prompt; }
}
else
{
if (e.Character != (char)3)//双击会产生3的ASCII码
{
if (this.Text.Length >= maxLength && maxLength != 0)
{
//this.Text = this.Text.Substring(this.Text.Length - 16, 16);
this.Text = this.Text;
}//这里可以设置最多输入多少个字符
else
{ this.Text += e.Character; }
}
}
}
第二、设置控件的属性
![](https://images.cnblogs.com/OutliningIndicators/ContractedBlock.gif)
#region property
private int alpha;
[CategoryAttribute("A参数设置"), Description("设置控件的透明度(0-255)!")]
public int Alpha
{
get { return alpha; }
set
{
if (value >= 0 && value <= 255)
{
if (alpha == value) return;
alpha = value;
this.Invalidate();
}
else
{ MessageBox.Show("透明度应该是0到255的整数"); }
}
}
private Color alphaBackColor;
[CategoryAttribute("A参数设置"), Description("设置控件的透明颜色!")]
public Color AlphaBackColor
{
get { return alphaBackColor; }
set {
if (alphaBackColor == value) return;
alphaBackColor = value;
this.Invalidate();
}
}//没用背景色,原因是用背景色会出现运行后一直是一种颜色,没找到原因,故自己自定义颜色属性
private int maxLength;
[CategoryAttribute("A参数设置"), Description("设置控件可输入的最大字符数!")]
public int MaxLength
{
get { return maxLength; }
set {
if (maxLength == value) return;
maxLength = value;
}
}
private Char passwordChar;
[CategoryAttribute("A参数设置"), Description("设置控件密码输入显示字符!")]
public Char PasswordChar
{
get { return passwordChar; }
set {
if (passwordChar == value) return;
passwordChar = value;
}
}
private string prompt;
[CategoryAttribute("A参数设置"), Description("设置控件Text!")]
public string Prompt
{
get
{
return this.prompt;
}
set
{
if (prompt == value) return;
prompt = value;
this.Text = prompt;
this.Invalidate();
}
}
private float cornerRadius;
[CategoryAttribute("A参数设置"), Description("设置控件圆角半径大小!")]
public float CornerRadius
{
get { return cornerRadius; }
set {
if (cornerRadius == value) return;
cornerRadius = value;
this.Invalidate();
}
}
[Browsable(false)]
public new Color BackColor
{
get { return base.BackColor; }
set { base.BackColor = value; }
}
[Browsable(false)]
public new string Text
{
get { return base.Text; }
set { base.Text = value; }
}
#endregion
第三、重写某些事件
![](https://images.cnblogs.com/OutliningIndicators/ContractedBlock.gif)
protected override void OnClick(EventArgs e)
{
this.Focus();
isFocus = true;
if (this.Text == prompt)
{ this.Text = ""; }
this.Invalidate(validRect);//为了只在矩形内显示文字
base.OnClick(e);
}
protected override void OnCreateControl()
{
Thread threadInvalidate = new Thread(methodInvalidate);
threadInvalidate.IsBackground = true;
threadInvalidate.Start();
base.OnCreateControl();
}
protected override void OnLostFocus(EventArgs e)
{
isFocus = false;
if (this.Text == "")
{ this.Text = prompt; }
this.Invalidate();
base.OnLostFocus(e);
}
private void methodInvalidate()
{
try
{
while (true)
{
if (isFocus)
{
this.Invalidate(validRect);//为了只在矩形内显示文字
if (isTwinkle)
{ isTwinkle = false; }
else
{ isTwinkle = true; }
}
Thread.Sleep(500);//每隔500ms就更新一下isTwinkle的布尔值,画焦点线
}
}
catch
{ }
}
第四、重写onpaint,重点
![](https://images.cnblogs.com/OutliningIndicators/ContractedBlock.gif)
protected override void OnPaint(PaintEventArgs e)
{
//---------------显示的区域------------------------
GraphicsPath gp = new GraphicsPath();
if (cornerRadius == 0)
{
//gp.AddArc(new Rectangle(0, 0, this.Height, this.Height), 90.0f, 180.0f);
//gp.AddArc(new Rectangle(this.Width - this.Height, 0, this.Height, this.Height), -90.0f, 180.0f);
//gp.CloseFigure();
gp.AddRectangle(new Rectangle(0,0,this.Width,this.Height));
}
else
{
gp = GetRoundedCorner(cornerRadius, new Rectangle(0, 0, this.Width, this.Height));
}
//-------------------------------------------------
//--------------画笔属性---------------------------
Graphics g = e.Graphics;
g.SmoothingMode = SmoothingMode.HighQuality;
g.CompositingQuality = CompositingQuality.HighQuality;
g.PageUnit = GraphicsUnit.Pixel;
//-------------------------------------------------
Brush b = new SolidBrush(Color.FromArgb(alpha, AlphaBackColor));//用指定的透明度和颜色定义brush
Pen p=new Pen(b);//用指定的brush定义画笔
g.FillPath(b,gp);//用指定的brush填出要显示的区域
//SizeF sf = g.MeasureString(this.Text, this.Font);//获取字符串的宽度高度
Size s = TextRenderer.MeasureText(this.Text, this.Font);
string tempText = this.Text;//把文本信息赋值给临时变量,画密码符号时用到并且要获取控件的文本信息用Text属性获取也不会变成了密码符号
//---------------------更改文本信息为密码符号----------------------
if (passwordChar != '\0'&&tempText!=prompt)//\0为空字符 char
{
foreach (Char c in tempText.ToCharArray())
{
tempText = tempText.Replace(c, passwordChar);
}
}
//--------------------------------------------------------------
//------------------画字符串-------------------------------------
if (s.Width > this.Width - cornerRadius*2)//如果字符串宽度大于要显示的矩形区域,那么重新计算画字符串的起始坐标点。
{
g.DrawString(tempText, this.Font, Brushes.Ivory,
new PointF(-(s.Width-(this.Width-cornerRadius/2)),(Convert.ToSingle(this.Height)-this.Font.Height)/2));//
}
else
{
g.DrawString(tempText, this.Font, Brushes.Ivory, new PointF(cornerRadius/2, (Convert.ToSingle(this.Height) - this.Font.Height) / 2));
}
//---------------------------------------------------------------
if (this.Focused && isTwinkle)
{
if (s.Width > this.Width - cornerRadius)//此时只在矩形最右侧画线
{
g.DrawLine(Pens.Ivory, new PointF(this.Width - cornerRadius/2-1, 6), new PointF(this.Width - cornerRadius/2-1, this.Height - 6));
}
else
{
g.DrawLine(Pens.Ivory, new PointF(cornerRadius / 2 + s.Width, 6), new PointF(cornerRadius / 2 + s.Width, this.Height - 6));
}
}
base.OnPaint(e);
}
总结:其中画字符串的时候当超过矩形区域,隐藏前面的,显示刚写上去的字符的时候,用SizeF sf = g.MeasureString(this.Text, this.Font);测长度画会随着字符串的长度变化而与焦点线之间的距离越来越大,Size s = TextRenderer.MeasureText(this.Text, this.Font);这种测量没有发现什么问题。
代码中有些地方大家看了肯定有觉得不美或者有需要修改改进的地方,希望大家提出,我们共同进步。
附上总的工程:
http://my.csdn.net/wll929187348 由于不知道博客园如何上传资源,想要资源的到这里面可以下载