• 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 = 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;
                }

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

  • 相关阅读:
    为什么使用CWinApp类编译以后的文件会比CWinAppEx类小呢?
    遇到问题——IntelliSense: #error directive: Please use the /MD switch for _AFXDLL builds
    理解C++中复杂的指针声明
    运行没有错,但是窗口没有显示出来——Windows编程中的CreateWindow返回值为空?
    错误argument of type "char *" is incompatible with parameter of type "LPCWSTR"的解决方法
    游戏坐标和窗口坐标的变换公式
    一路风雨走过来:那些我亲密接触过的项目
    10 Fun Things to do with Tessellation
    irrlicht引擎:中文支持
    魔兽世界客户端数据研究(一)
  • 原文地址:https://www.cnblogs.com/fightfuture/p/4172960.html
Copyright © 2020-2023  润新知