这个控件的需求和存储方式在很久前就写好了,只不过因为汉字数据的录入花费了不少时间,所以现在才开始写实现方式,先设置我们要的属性,在这里为了界面的美观加入了可以设置画笔的线宽,代码如下:
readonly FontFamily FONT_FAMILY; //字体
Brush FLASH_BRUSH1 = Brushes.White; //闪烁的颜色
Brush FLASH_BRUSH2 = Brushes.Red; //闪烁的颜色
string m_strPlayChar="";//默认汉字为空
string m_strCharPinYin; //汉字拼音
int m_nFontSize = 135; //默认字体大小
Rectangle m_DrawArea; //绘图区域
int penWidth = 5; //默认画笔线宽
int m_nCurrentPos = 0; //汉字笔画索引,用以指示当前为第几笔画
int m_nStrokeCount = 0; //汉字总笔画数
int m_nFlashCount = 6; //闪烁的次数
int m_nFlashNum = 0; //已经闪烁的次数
int m_nPlayFreq = 3; //频率
SolidBrush m_FilledBrush = new SolidBrush(Color.Black); //已画笔画的笔刷
SolidBrush m_EmptyBrush = new SolidBrush(Color.White);//未画笔画的笔刷
SolidBrush m_FlashBrush = new SolidBrush(Color.Red); //绘制闪烁效果的笔刷
SolidBrush m_LastBrush = new SolidBrush(Color.Blue);//当前笔画的笔刷
List<GraphicsPath> m_CharPathList = new List<GraphicsPath>();//用以存储单笔画路径
有部分属性是可设置的,为此添加以下代码:
[DefaultValue("")]
public string PlayChar
{
set
{
m_strPlayChar = value;
GetStrokeInfo(m_strPlayChar);
Invalidate(m_FontRect);
}
get { return m_strPlayChar; }
}
[DefaultValue(5)]
public int PenWidth
{
get { return penWidth; }
set
{
penWidth = value;
Invalidate(m_FontRect);
}
}
public string CharPinYin
{
get { return m_strCharPinYin; }
}
[DefaultValue(3)]
public int PlayFreq
{
get { return m_nPlayFreq; }
set
{
if (value >= 30) value = 30; //频率最大值为
if (value == m_nPlayFreq)
return;
m_nPlayFreq = value;
tmrStokeCtrl.Interval = 1000 / value;
}
}
[DefaultValue(6)]
public int FlashCount
{
get { return m_nFlashCount; }
set { m_nFlashCount = value; }
}
public Color FilledColor
{
get { return m_FilledBrush.Color; }
set
{
m_FilledBrush.Color = value;
Invalidate(m_FontRect);
}
}
public Color EmptyColor
{
get { return m_EmptyBrush.Color; }
set
{
m_EmptyBrush.Color = value;
Invalidate(m_FontRect);
}
}
public Color FlashColor
{
get { return m_FlashBrush.Color;}
set
{
m_FlashBrush.Color = value;
Invalidate(m_FontRect);
}
}
public int CurrentPos
{
get
{
return m_nCurrentPos;
}
}
因为字体的依赖性,所以我们这里需要内嵌一个字体文件资源标准楷体简.ttf并将其生成操作设置为嵌入的资源,另外还有个就是我们的汉字笔画信息的存储文件StrokeInfo.txt操作同上作为嵌入资源。
接下来第一个问题我们要解决的是怎样去获取汉字的笔画路径,这个可以参照《基于GDI+路径技术的汉字笔顺和部件自动绘制》这篇文章,换成C#方法实现代码如下:
void GetStrokeInfo(string chineseChar)
{
if(null == chineseChar || chineseChar.Length <= 0) //判断汉字的合法性
return;
m_CharPathList.Clear();
m_nFontSize = GetFontSize(chineseChar);
m_strCharPinYin = Chinese2Spell.Convert(chineseChar); //获取汉字的拼音,此方法可以在网络上搜索到很到,这里给出的方法不是很好,所以如果需要的话可以自己去网上下载代码,相信会比我的方法好很多
GraphicsPath charPath = new GraphicsPath();
charPath.AddString(chineseChar.Substring(0, 1), FONT_FAMILY, 0,
(int)(m_nFontSize * 96f / 72f), new Point(0, 0), StringFormat.GenericDefault);
List<int> strokeList = GetStrokeList(chineseChar); //此方法下面将给出实现代码
GraphicsPathIterator iter = new GraphicsPathIterator(charPath);
m_nStrokeCount = iter.SubpathCount; //获取汉字总笔画数
bool isClosed = false;
//存储此汉字所有单笔画路径
for (int i = 0; i < iter.SubpathCount; i++ )
{
iter.Rewind();
GraphicsPath subPath = new GraphicsPath();
for (int j = 0; j < strokeList[i]; j++)
{
iter.NextSubpath(subPath, out isClosed);
}
m_CharPathList.Add(subPath);
}
}
关于GetStrokeList(chineseChar)的实现代码:
List<int> GetStrokeList(string chineseChar)
{
//根据当前汉字来定位读取内嵌资源StrokeInfo.txt获得笔画信息
List<int> strokeList = new List<int>();
byte[] order = new byte[100];
string NameSpc = Assembly.GetExecutingAssembly().GetName().Name.ToString();
using (Stream stream = Assembly.GetExecutingAssembly().GetManifestResourceStream(NameSpc + ".StrokeInfo.txt"))
{
stream.Seek(102 * ((int)chineseChar[0] - 19968) + 2, SeekOrigin.Begin);
stream.Read(order, 0, 100);
stream.Close();
}
for (int i = 0; i < 100; i++)
{
if (order[i] == 0)
{
break;
}
strokeList.Add(order[i]);
}
return strokeList;
}
为了切断对系统字体的依赖性这里我们使用内嵌的字体文件,代码如下:
private Font GetSpecialFont()
{
//通过内嵌的字体资源文件来获取对应字体
System.Drawing.Text.PrivateFontCollection m_pfc;
int size = 20;
Font fnt = null;
if (null == m_pfc)
{
string NameSpc = Assembly.GetExecutingAssembly().GetName().Name.ToString();
Stream stmFont = Assembly.GetExecutingAssembly().GetManifestResourceStream(
NameSpc + ".标准楷体简.ttf");
if (null != stmFont)
{
byte[] rgbyt = new Byte[stmFont.Length];
stmFont.Read(rgbyt, 0, rgbyt.Length);
IntPtr pbyt = Marshal.AllocCoTaskMem(rgbyt.Length);
if (null != pbyt)
{
Marshal.Copy(rgbyt, 0, pbyt, rgbyt.Length);
m_pfc = new PrivateFontCollection();
m_pfc.AddMemoryFont(pbyt, rgbyt.Length);
Marshal.FreeCoTaskMem(pbyt);
}
}
}
if (m_pfc.Families.Length > 0)
{
fnt = new Font(m_pfc.Families[0], size);
}
return fnt;
}
汉字的绘制功能代码既Form_Paint事件实现:
private void ChineseStrokePlay_Paint(object sender, PaintEventArgs e)
{
Graphics g = e.Graphics;
g.SmoothingMode = SmoothingMode.AntiAlias;
//绘制田字框
Pen wordBox = new Pen(Color.FromArgb(100, Color.Black));
wordBox.Width = penWidth;
wordBox.DashStyle = DashStyle.DashDot;
wordBox.Alignment = PenAlignment.Center;
g.DrawLine(wordBox, m_FontRect.Left + m_FontRect.Width / 2, m_FontRect.Top, m_FontRect.Left + m_FontRect.Width / 2, m_FontRect.Height + m_FontRect.Top);
g.DrawLine(wordBox, m_FontRect.Left, m_FontRect.Top + m_FontRect.Height / 2, m_FontRect.Left + m_FontRect.Width, m_FontRect.Top + m_FontRect.Height / 2);
wordBox.Color = Color.Black;
wordBox.DashStyle = DashStyle.Solid;
wordBox.Alignment = PenAlignment.Inset;
g.DrawRectangle(wordBox, m_FontRect.Left, m_FontRect.Top, m_FontRect.Width, m_FontRect.Height);
//绘制汉字笔画路径
wordBox.Alignment = PenAlignment.Center;
GraphicsPath frame = new GraphicsPath();
foreach (GraphicsPath gp in m_CharPathList)
{
g.FillPath(Brushes.White, gp);
frame.AddPath(gp, true);
}
wordBox.DashStyle = DashStyle.Solid;
g.DrawPath(wordBox, frame);
frame.Dispose();
wordBox.Dispose();
//填充笔画
if (m_nStrokeCount <= 0 || m_nCurrentPos < 0)
return;
//绘制不闪烁的笔画
if (m_nCurrentPos > 0 && m_nStrokeCount > 0)
{
for (int i = 0; i < m_nCurrentPos; i++)
{
g.FillPath(m_FilledBrush, m_CharPathList[i]);
}
}
//绘制闪烁的笔画
if (null == m_CharPathList[m_nCurrentPos]) return;
g.FillPath(m_LastBrush, m_CharPathList[m_nCurrentPos]);
}
至此,所有的辅助方法都已经写好,接下来按顺序来调用,先是构造函数
public ChineseStrokePlay()
{
InitializeComponent();
FONT_FAMILY = GetSpecialFont().FontFamily; //初始化字体
m_FilledBrush = new SolidBrush(Color.Black);
m_EmptyBrush = new SolidBrush(Color.White);
m_FlashBrush = new SolidBrush(Color.Red);
m_LastBrush = m_EmptyBrush;
}
然后播放、暂停、停止、上一笔、下一笔功能
public void Start()
{
if (m_nCurrentPos >= m_nStrokeCount - 1)
m_nCurrentPos = -1;
m_nCurrentPos++;
Invalidate(m_FontRect);
tmrStokeCtrl.Start();
}
public void Pause()
{
tmrStokeCtrl.Stop();
}
public void Stop()
{
m_nCurrentPos = -1;
tmrStokeCtrl.Stop();
Invalidate(m_FontRect);
}
public void Previous()
{
if (m_nCurrentPos >= 0)
{
tmrStokeCtrl.Stop();
m_nCurrentPos--;
Invalidate(m_FontRect);
}
}
public void Next()
{
if(m_nCurrentPos < m_nStrokeCount - 1)
{
tmrStokeCtrl.Stop();
m_nCurrentPos++;
Invalidate(m_FontRect);
}
}
最后,为了使汉字播放时能不停的闪烁一吸引人眼球在定时器加了闪烁的代码:
private void tmrStokeCtrl_Tick(object sender, EventArgs e)
{
if (m_nStrokeCount <= 0 || m_nCurrentPos >= m_nStrokeCount - 1)
{
tmrStokeCtrl.Stop();
m_LastBrush = m_FilledBrush;
Invalidate(m_FontRect);
return;
}
m_nFlashNum++;
if (m_nFlashNum % 2 == 0)
m_LastBrush = m_EmptyBrush;
else
m_LastBrush = m_FlashBrush;
if(m_nFlashNum >= m_nFlashCount)
{
m_nCurrentPos ++;
m_nFlashNum = 0;
}
Invalidate(m_FontRect);
}
至此,本控件已完成,谢谢观赏!
本人第一次写这样的技术文章,写的很生疏,希望大家对我提提意见,谢谢!
另外附上代码下载地址:http://download.csdn.net/detail/xiarenwang/4352294