首页 > 代码库 > C#作业补充(6)

C#作业补充(6)

昨天没事看了一下源程序中的歌词部分,不是很难,可是今天我在写的时候却在一个问题上面卡了很长时间,我没有用源程序里每次显示一句歌词,而是列表显示,并且当前句加粗显示,思路其实很简单,问题出现在列表移动时候的闪烁,对这个闪烁我先是用LABEL控件GDI+重绘,后来是PictureBox控件重绘,效果都不是很好,闪烁依旧存在,这直接让我怀疑GDI+的性能,很多人都说它不如GDI。后来我直接像我MFC里面那样,完全自己绘制,闪烁解决了,整整搞了一个下午,我也是醉了,也有人建议用DX2D来绘制,可能会好很多,毕竟DX绘制3D是很强悍的,我没有去试。先给出运行界面

当然源程序在歌词这块还是存在问题的,这不是重点,现在先分析一下源程序里面获取歌词这块 

     string exc=@"[a-zA-z]+://[^\s]*[a-zA-z]";        //用于匹配歌词连接的正则表达式           //[a-zA-z]  任意字母   + 多个    [^\s] 非制表符空格之类        string HTML;//保存网页源码        string LrcText;                            //歌词文本        string lrcAPI="http://geci.me/api/lyric/";//取歌词文件的API        string fileName;                          //保存歌词路径        public string getLrc(string mp3Name)        {            lrcAPI = "http://geci.me/api/lyric/";//初始化            //此处做本地歌词判断 如果存在 就不需要下载 不存在 就下载            if (File.Exists(string.Format(".\\Lrc\\{0}.Lrc", mp3Name)) == true)            {                fileName =mp3Name;                return "正在解析歌词...";            }            else            {                lrcAPI = lrcAPI + mp3Name;                WebClient wc = new WebClient();                wc.Credentials = CredentialCache.DefaultCredentials;    // 获取或设置用于对向 Internet 资源的请求进行身份验证的网络凭据。                Encoding enc = Encoding.GetEncoding("UTF-8");           // 如果是乱码就改成 utf-8 / GB2312                Byte[] pageData =http://www.mamicode.com/ wc.DownloadData(lrcAPI);              //获取数据                HTML = enc.GetString(pageData);                MatchCollection matchs = Regex.Matches(HTML, exc);//开始对歌词进行匹配                if (matchs.Count == 0)                {                        return "没有找到对应的歌词!";                }                else                {                        DownloadLrc(matchs[0].Value, mp3Name);  //这里使用第一个匹配的   可能不对  这块我没看                           return "歌词找到并下载成功!";                }            }                    }        public void DownloadLrc(string url,string FileName)        {            WebClient wc = new WebClient();            wc.Credentials = CredentialCache.DefaultCredentials; // 获取或设置用于对向 Internet 资源的请求进行身份验证的网络凭据。            Encoding enc = Encoding.GetEncoding("UTF-8");        // 如果是乱码就改成 utf-8 / GB2312            try            {                Byte[] pageData = wc.DownloadData(url);                // 从资源下载数据并返回字节数组。                LrcText = enc.GetString(pageData);                if (Directory.Exists(".\\Lrc") == false)                {                    Directory.CreateDirectory(".\\Lrc");                }                StreamWriter sw = new StreamWriter(String.Format(".\\Lrc\\{0}.Lrc", FileName), false, Encoding.UTF8);                sw.Write(LrcText);                sw.Flush();   //写入文件                 sw.Close();                fileName = FileName;            }            catch (Exception)            {                            }                     }

 

 

 

在分析歌词这块,我先给出LRC文件的一些例子,然后对应程序来看

[00:04.56]作词:刘德华&徐继宗 作曲:徐继宗 编曲:Billy Chan[03:44.21][00:10.78][00:16.44]十七岁那日不要脸 参加了挑战[00:22.43]明星也有训练班 短短一年太新鲜[00:27.98]记得四哥 发哥 都已见过面[00:34.26]后来 荣升主角太突然
//下面是我自己的程序 没有使用源程序里面 对时间字符串匹配 直接计算时间反而更准更有效 txtclass txt
= new txtclass(); string excTime = @"(?<=\[).*?(?=\])"; //匹配时间的正则 // (?<=\[) 匹配 ‘[‘ 中间是任意多字符 string excText = @"(?<=\])(?!\[).*"; //匹配歌词的正则 // 寻找最后一个 ‘]’

string[] lrcText = new string[100]; //保存歌词文字 int[] lrcIndex = new int[100]; //保存顺序索引 int[] lrcNumTime = new int[100]; //保存计数时间 在后面判断时间 寻找对应为歌词文本 int total1 = 0; int total2 = 0; public void getLrc(string FileName) { total1 = 0; total2 = 0; string zj; int[] tpNumTime = new int[100]; int numTime; string[] strs = System.IO.File.ReadAllLines(FileName); int hasline = strs.Length; MatchCollection match1; MatchCollection match2; for (int i = 0; i <= hasline; i++) { match1 = Regex.Matches(txt.txtRead(FileName, i), excTime); //匹配集合 match2 = Regex.Matches(txt.txtRead(FileName, i), excText); foreach (var v in match1) { zj = v.ToString(); //获取字符串 try { numTime = int.Parse(zj.Substring(0, 2)) * 60 + int.Parse(zj.Substring(3, 2)); //只是计算分和秒 后面发现歌词偶尔出现偏差 快一格慢一格 lrcNumTime[total1] = numTime; tpNumTime[total1] = numTime; lrcIndex[total1] = total1; foreach (var t in match2) { lrcText[total2] = t.ToString(); } total1++; //递增 total2++; } catch (Exception) { } } } //排序 直接就比较交换了 当然数量少 没有优化的必要了 int tmp1; for(int i=0;i<total1-1;i++) { for(int j=i+1;j<total1;j++) { if (tpNumTime[j] < tpNumTime[i]) { //交换 tmp1 = tpNumTime[j]; tpNumTime[j] = tpNumTime[i]; tpNumTime[i] = tmp1; tmp1 = lrcIndex[j]; lrcIndex[j] = lrcIndex[i]; lrcIndex[i] = tmp1; } } } }

 

好了,歌词基本获得了,下面我先说一下我的思路。

第一种:使用Panel控件里面加上一个Label控件,然后一次性输出文本,然后移动就行,相当简单,但是实际证明这样效果很差,必须控制行距,这点,一个C#新手是真的不会怎样设置LABEL控件的行距,这个方案就Pass了

第二种:仿照以前的抽奖程序,放上几个Label控件,然后循环移动,这样既能控制行距,又能很好的显示歌词高亮,后续很多功能都可以再加上去

我就手绘一下,其实就是一个数组交换和整体的移动,也是相当简单,我就不贴代码了

第三种:针对上面的问题,背景纯色不会闪烁,但是一旦背景更换成图片后闪烁相当厉害,网上有很多都是用GDI+绘制,当然我也做了相关的,但是效果不是很理想

改变思路  其实就是在一张固定的图片上面绘制文字,不需要添加控件(控件透明属性在每次移动的时候会计算绘制,这是闪烁的根本原因),于是我删掉所有的控件,自己计算文本的

位置,绘制文本,实验证明这种方法速度很快,比单纯使用控件好多了(当然如果能很好的实现效果使用控件是最好的,其实像MFC做界面那完全就是自己控制绘制,要求很高,但是相对的

比C#开放多了,可控性很高,灵活性更好)

这里给出我PictureBox控件的一些代码

     //绘制      Bitmap tpBit = new Bitmap(this.Width, this.Height);   //建立图片     Graphics tpBitG = Graphics.FromImage(tpBit);          //获取绘制GDI     //绘制图片     if (bkBitmap != null)     tpBitG.DrawImage(bkBitmap, 0, 0, getMainWndRect(), GraphicsUnit.Pixel);  //这里需要自己计算位置矩形            //绘制文字           Graphics g = pe.Graphics;
if (text != "") { if (text_bold) tpBitG.DrawString(text, new Font("微软雅黑", text_size, FontStyle.Bold), new SolidBrush(Color.White), new Point(0, 0)); else tpBitG.DrawString(text, new Font("微软雅黑", text_size, FontStyle.Regular), new SolidBrush(Color.White), new Point(0, 0)); } g.DrawImage(tpBit, 0, 0); //绘制 将缓存里面的图片一次性绘制到界面上面来 //释放资源 tpBit.Dispose(); tpBitG.Dispose();

 

上面就是最简单的双缓存绘制,对比GDI,其实都差不多,但是我不知道最后的效果为什么差那么多,原理是相同的,双缓存就是先在缓存里面绘制好图片,然后一次性绘制到界面上面,DX里面还有三缓存,多缓存,都是这样的道理。

给大家个地址,介绍的很详细    http://blueve.me/archives/633

再来看看后面自己绘制,其实差不多,自己写一个类,保存绘制的相关信息,和原来LABEL控件的内容差不多

        private string text;                //文本内容          private bool text_bold = false;     //文本加粗        private int text_size = 9;          //字体大小        private Point location;             //保存当前位置        public string Text        {            get { return text; }            set { text = value; }        }        public bool Text_bold        {            get { return text_bold; }            set { text_bold = value; }        }        public int Text_size        {            get { return text_size; }            set { text_size = value; }        }        public Point Location        {            get { return location; }            set { location = value; }        }

没写相关的方法,就这些了,然后就是在Panel里面的绘制过程  

            Graphics g = e.Graphics;            g.DrawImage(m_PanelLcrBit, 0, 0); //绘制背景图片            for (int i = 0; i < 8; i++)            {              if (m_lableLrc[i].Text!="")    //绘制文本              {                  Font tpFont;                  Point locatePos;                  if (m_lableLrc[i].Text_bold)    //设置字体                      tpFont=new Font("微软雅黑",m_lableLrc[i].Text_size, FontStyle.Bold);                  else                      tpFont = new Font("微软雅黑", m_lableLrc[i].Text_size, FontStyle.Regular);                  SizeF sizeF = g.MeasureString(m_lableLrc[i].Text, tpFont);  //字符串的宽度                  locatePos = new Point((int)(panel_LRC.Width - sizeF.Width) / 2, m_lableLrc[i].Location.Y);  //居中显示                  g.DrawString(m_lableLrc[i].Text, tpFont, new SolidBrush(Color.White), locatePos);  //绘制              }            }

 

当然做完之后最好是关掉Panel控件檫除背景,我在MFC里面都是禁掉檫除背景这块,没有必要。

看上面的代码是不是相当简单,只是我一开始忽略了本质的问题,一味的使用控件来减少自己写代码的行数,这样反而使得程序变得更加冗余,虽然我是个新手,但是很多情况下我都是试着去考虑如何才能是代码看上去更加简洁,逻辑更加的清晰,我现在感觉在写这些的时候定时器是个坏东西,他让你的程序整个的分散了,而且你无法预料到什么时候会出现什么错误,很莫名的错误,以前在写DX3D的时候都直接C++在绘制窗体的里面来控制,没有定时器这一说法,当然里面存在时间控制,这样而言比定时器来触发效率更高,也更准确,当然这必须得靠自己慢慢去写了。

说了一大堆废话,下面在贴一些里面的控制代码

            //寻找当前时间索引            try            {                string time = this.axWindowsMediaPlayer1.Ctlcontrols.currentPositionString;                int currentPlayTime = int.Parse(time.Substring(0, 2)) * 60 + int.Parse(time.Substring(3, 2));                //解决头部开始情况                if (currentPlayTime >= 0 && currentPlayTime < LNumtime[Lindex[0]])                    return 0;                for (int i = 0; i < lrcCount - 1; i++)                {                    if (LNumtime[Lindex[i]] <= currentPlayTime && currentPlayTime < LNumtime[Lindex[i + 1]])                    {                        return i;                    }                }                //解决最后情况                return lrcCount - 1;            }            catch            {                   //解决没开始播放 产生错误的情况                 return 0;            }

 

            //移动4次 每次移动10            currentMove += lHangDis/4;            //首先变更字体            if (currentMove==lHangDis/4)            {                m_lableLrc[3].Text_size = 9;                m_lableLrc[3].Text_bold= false;                m_lableLrc[4].Text_size = 10;                m_lableLrc[4].Text_bold = true;            }            for(int i=0;i<8;i++)                 m_lableLrc[i].Location = new Point(6, m_lableLrc[i].Location.Y - lHangDis / 4);            panel_LRC.Invalidate(false);            if (currentMove==lHangDis)  //移动结束            {                //更换位置                LabelLcr toubu = m_lableLrc[0];                toubu.Location = new Point(6, 280);                if (lCurrentIndex + 4 < lrcCount)                    toubu.Text = Ltext[Lindex[lCurrentIndex + 4]];                else                    toubu.Text = "";                //更换                  for (int i = 0; i < 7; i++)                    m_lableLrc[i] = m_lableLrc[i + 1];                m_lableLrc[7] = toubu;                timer6.Enabled = false;            }

 

基本的就这些了,准备睡觉,明天还要看高频,蛋疼。。。。。。。。。。。。

 

C#作业补充(6)