C#作业补充(6)

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

C#作业补充(6)C#作业补充(6)

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

     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 = 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控件,然后循环移动,这样既能控制行距,又能很好的显示歌词高亮,后续很多功能都可以再加上去

C#作业补充(6)

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

第三种:针对上面的问题,背景纯色不会闪烁,但是一旦背景更换成图片后闪烁相当厉害,网上有很多都是用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)

上一篇:Win7开启远程桌面——图文详解


下一篇:c# HtmlParser 简单使用