经过十多天的艰苦奋战,MyKTV点歌系统终于成型,从刚开始接到项目的茫然,到完成项目时的喜悦,整个过程的艰辛和付出只有自己知道.虽然这个项目还有许多需要完善的地方,譬如添加歌词信息,实现窗体的美化等,这些在后续时间里我再一一进行一个完善吧!
首先呢,我先将整个项目所能实现的功能做一个简单的介绍,KTV点歌系统包括了前台和后台两大部分,前台的功能就是能够根据客户的需求来实现点歌操作,后台主要是管理员来添加歌手信息和歌手信息
前台
这是前台的主界面,通过主界面上的一系列操作,分别可以进入相对应的界面
这是当我播放歌曲后的主界面,添加歌曲后,主界面就能显示该歌手的图片信息,正在播放和下一首也会显示相应的文本信息,并且,当我切歌后,歌手图片和文本框也能随之变化
这是通过金榜排行进入的界面,歌曲的排列顺序都是通过点击的次数降序排列
这是类型点歌,当客户点击任意一个类型时都会根据客户所选的类型,到数据库中进行筛选,然后将筛选后的结果在歌曲列表中展示出来
这是字数点歌,其中每个文字都是通过代码动态生成的,如何生成将在后面写详细代码.当用户点击相应的字数时也会在数据库中进行筛选,然后在歌曲列表中显示出来,大家可以很清楚的感觉到,歌曲列表这个窗体其实是几个功能块共用的一个窗体.
这是拼音点歌,可以根据用户输入的拼音或者是歌曲名字进行模糊查询.
这是已点列表,这里面我加了一个右键菜单,可以根据用户的需求来进行立即播放和删除
这是歌星点歌,这三张图其实不是三个窗体,而是一个窗体,只是用了三个listview控件,当我需要显示哪一个控件时就将其他两个控件给隐藏了.如何操作也将在后面通过代码进行详细介绍.
最后这就是我在主界面通过娱乐添加的一个猜数小游戏.它能产生一个随机数,跟你输入的数进行一个比较,如果大了或者是小了都会有相应的提示,直到你猜中后,又会显示你一共猜了多少次,用户就可以根据你猜的次数的多少来进行相应的惩罚了.而且界面上的图是可动的,当程序运行起来后就好像一个少女在那儿跳舞.
这些基本上就是我整个前台所能实现的所有功能了,后台的话在后面再跟大家一一展示了.
下面就给大家展示一些相对应的代码了
一:怎样实现无边框窗体的拖动
1 private Point mouseOffset; //记录鼠标指针的坐标 2 private bool isMouseDown = false; //记录鼠标按键是否按下 3 private void MainMenu_MouseDown(object sender, MouseEventArgs e) 4 { 5 int xOffset; 6 int yOffset; 7 if (e.Button == MouseButtons.Left) 8 { 9 xOffset = -e.X - SystemInformation.FrameBorderSize.Width; 10 yOffset = -e.Y - SystemInformation.CaptionHeight - SystemInformation.FrameBorderSize.Height; 11 mouseOffset = new Point(xOffset, yOffset); 12 isMouseDown = true; 13 } 14 15 } 16 17 private void MainMenu_MouseMove(object sender, MouseEventArgs e) 18 { 19 if (isMouseDown) 20 { 21 Point mousePos = Control.MousePosition; 22 mousePos.Offset(mouseOffset.X + 5, mouseOffset.Y + 30); 23 Location = mousePos; 24 } 25 26 } 27 28 private void MainMenu_MouseUp(object sender, MouseEventArgs e) 29 { 30 // 修改鼠标状态isMouseDown的值 31 // 确保只有鼠标左键按下并移动时,才移动窗体 32 if (e.Button == MouseButtons.Left) 33 { 34 isMouseDown = false; 35 } 36 37 }
在窗体中找到相对应的事件,然后copy代码便可实现
二:实现窗体抖动的代码
//实现窗体抖动的效果 Point first = this.Location; for (int i = 0; i < 8; i++) { Random ran = new Random(); Point p = new Point(this.Location.X + ran.Next(20) - 4, this.Location.Y + ran.Next(20) - 4); System.Threading.Thread.Sleep(25);//当前线程挂起15毫秒 this.Location = p; System.Threading.Thread.Sleep(25);//当前线程再挂起15毫秒 } this.Location = first; //将窗体还原为原来的位置
整个KTV点歌系统中,我们需要定义几个辅助类.
1:Help类
public class Help { public static string str = "data source=.;initial catalog=MyKTV;uid=sa;"; public static string ways = ""; //保存歌手图片路径 public static string songurl = ""; //保存歌曲路径 }
2:PlayBackStatus(播放状态类)
public enum PlayBackStatus { PlayBack, //已播 NotBroadcast, //未播 NowBroadcast, //正在播放 Repeat, //重播 baoji, //右键菜单的标记 Cut //切歌 }
3:Song(歌曲类)
public class song { public string SongName; //歌曲名称 public string SongURL; //歌曲存放路径 public PlayBackStatus playback; //歌曲播放状态 public string singerphotourl; //歌手图片路径 }
4:PlayList(播放列表类)
public class PlayList { public static song[] songlist = new song[100]; //定义一个歌曲数组 public static int SongIndex = 0; //当前播放歌曲在数组中的索引 public static string NextSongName = ""; //下一首歌曲 public static string zhuangtai = ""; //保存是切歌还是重播 //将歌曲增加到歌曲数组中去 public static bool AddSong(song song) { bool Result = false; //记录歌曲是否添加成功 for (int i = 0; i < songlist.Length;i++ ) { if (songlist[i] == null) { songlist[i] = song; Result = true; break; } } return Result; } //切歌 public static void CutSong() { //获取到当前播放的歌曲改变播放状态 if (songlist[SongIndex] != null) { songlist[SongIndex].playback = PlayBackStatus.Cut; ChargeInde(); //改变歌曲索引,播放下一首 } } //重唱 public static void ListenAgain() { if (songlist[SongIndex] != null) { songlist[SongIndex].playback = PlayBackStatus.Repeat; //改变歌曲播放状态 } } //获取下一首歌曲的名称 public static string GetNextSongName() { if (songlist[0]!=null && songlist[SongIndex + 1] == null) { NextSongName = "待添加...."; return NextSongName; } else { if (songlist[0] != null) { NextSongName = songlist[SongIndex + 1].SongName; return NextSongName; } else { return string.Empty; } } } //获得当前播放的歌曲 public static song GetPlaySong() { if (songlist[SongIndex] != null) { return songlist[SongIndex]; } else { return null; } } //播放下一首 public static void ChargeInde() { SongIndex++; } //点击重播时根据歌曲名称查找该歌曲在歌曲列表中的位置并改变其状态 public static void SelectFromSongName(string name) { for (int i = 0; i < songlist.Length;i++ ) { if (songlist[i] != null) { if (songlist[i].SongName.Equals(name)) { if (zhuangtai.Equals("重播")) { songlist[i].playback = PlayBackStatus.Repeat; //将该歌曲状态修改成重播 break; } else { if (songlist[i + 1] != null && songlist[i].playback == PlayBackStatus.NowBroadcast) { songlist[i].playback = PlayBackStatus.Cut; //将该歌曲状态修改成切歌 songlist[i + 1].playback = PlayBackStatus.NowBroadcast; //将下一首歌状态改成正在播放 } else { MessageBox.Show("亲,最后一首歌曲和不是正在播放的歌曲不能切哟~~^_^"); } break; } } } else { break; } } } public static bool isRight=false; //记录当isRight等于true时就播放选中歌曲 //点击已点列表中的播放时,根据歌曲名找到索引 public static void SelectIndexBySongName(string name) { for (int i = 0; i < songlist.Length; i++) { if (songlist[i] != null) { if (songlist[i].SongName.Equals(name)) { songlist[i].playback = PlayBackStatus.baoji; //为选中歌曲做一个标记 songlist[SongIndex].playback=PlayBackStatus.PlayBack; //将当前播放歌曲改为已播状态 SongIndex = i; //将歌曲索引改变为所选中的歌曲 isRight = true; break; } } else { break; } } } //删除右键菜单选中的歌曲 public static bool delete(string name) { for (int i = 0; i < songlist.Length; i++) { if (songlist[i] != null) { if (songlist[i].SongName.Equals(name)) { if (songlist[i].playback == PlayBackStatus.NowBroadcast) { return false; } while(true) { if (songlist[i + 1] != null) { songlist[i] = songlist[i + 1]; i++; } else { songlist[i] = null; break; } } for (int l = 0; l < songlist.Length;l++ ) { if (songlist[l] != null) { if (songlist[l].playback == PlayBackStatus.NowBroadcast) { SongIndex = l; } } } } } else { break; } } return true; } }
当我们将这些辅助类创建好之后,我们就可以开始实现其他功能块的书写了.
首先在一进入主界面的时候我们就要保存歌手图片路径和歌曲路径
private void Form1_Load(object sender, EventArgs e) { //保存歌手图片前半部分路径 string sql = "select ways from Ways where id=1"; Help.ways = retunURL(sql); //保存歌曲前半部分路径 string sql2 = "select ways from Ways where id=2"; Help.songurl = retunURL(sql2); } //返回前半部分路径的方法 public string retunURL(string sql) { SqlConnection con = new SqlConnection(Help.str); con.Open(); SqlCommand com = new SqlCommand(sql, con); string URL = com.ExecuteScalar().ToString(); con.Close(); return URL; }
第一步完成后,我们就可以进如歌星点歌界面,实现歌星图片的动态加载了,我们上面歌星点歌的图片中,第一张是手动添加的,第二张半动态添加的(也可跟第一张一样手动添加,在这里是为了熟练动态加载的代码),第三张是全动态添加的,手动添加的部分就是,准备一个listview,imagelist 选择大图标模式,直接添加图片索引和Text文本即可,在这里只展示全动态加载时所写的代码
第一步,在load事件中显示第一个listview隐藏第二第三个listview
//Load事件 private void SingerStar_Load(object sender, EventArgs e) { //隐藏第二第三个窗体 listView2.Visible = false; listView3.Visible = false; }
第二步,当我点击第一个listview时保存所点的记录的文本或Tag并展示第二个listview
public string singertype = ""; //保存用户选择的歌星类型(男,女,组合) public int singerdistrictID; //保存用户选择的歌星地区的ID(香港...大陆...台湾.....) public string singerdistrict; //保存用户选择的歌星地区(香港...大陆...台湾.....) public int Number; //返回时更具Number的值来判断显示哪个控件,隐藏哪个控件 //从第一个控件跳转到第二个控件,第二个控件的文本和图像都是动态加载的 public void ShowListView2() { Number = 2; if (listView1.SelectedItems[0] != null) { listView1.Visible = false; //隐藏第一个控件 listView2.Location = listView1.Location; //让两个控件出现的位置相同 listView2.Visible = true; //显示第二个控件 singertype = listView1.SelectedItems[0].Tag.ToString(); } SqlConnection con = new SqlConnection(Help.str); string sql = "select * from singer_type "; SqlCommand com = new SqlCommand(sql, con); listView2.Items.Clear(); //清空上次点击加载的数据 try { con.Open(); SqlDataReader read = com.ExecuteReader(); if (read != null) { if (read.HasRows) { int index = 0; while (read.Read()) { int TypeId = Convert.ToInt32(read["singertype_id"]); string TypeName = read["singertype_name"].ToString(); ListViewItem list = new ListViewItem(); list.Text = TypeName; list.Tag = TypeId; list.ImageIndex = index; listView2.Items.Add(list); index++; } } } } catch (Exception) { MessageBox.Show("网络错误!"); } finally { con.Close(); } }
第三步,保存第二个listview中用户所选择的记录,并且显示第三个listview
//从第二个控件跳转到第三个控件,第三个控件的歌星图片是从电脑硬盘上加载的 public void ShowListView3() { Number = 3; if (listView2.SelectedItems[0] != null) { listView2.Visible = false; //隐藏第二个控件 listView3.Location = listView2.Location; //两个控件的出现位置相同 listView3.Visible = true; //第三个控件显示 singerdistrictID = Convert.ToInt32(listView2.SelectedItems[0].Tag); //获得用户点击的地区ID singerdistrict = listView2.SelectedItems[0].Text; //获得用户点击的歌手地区 } SqlConnection con = new SqlConnection(Help.str); con.Open(); try { //查询符合用户选择的地区和歌手的类型的歌手名字 string sql = @"select singer_name ,siinger_photo_url from singer_info where singer_sex='" + singertype + "' and singertype_id=" + singerdistrictID + ""; SqlCommand com = new SqlCommand(sql,con); SqlDataReader reader= com.ExecuteReader(); listView3.Items.Clear(); //清除上次点击加载的数据 imageList3.Images.Clear(); //清除imagelist中上一次保存的图片数据 if (reader != null) { if (reader.HasRows) { int index=0; while (reader.Read()) { string singerName = reader["singer_name"].ToString(); string singer_phone = reader["siinger_photo_url"].ToString(); //获得图片名字(后半部分路径) string lujing = Help.ways + singer_phone; //获得图片完整路径 imageList3.Images.Add(Image.FromFile(lujing)); //通过完整路径将图片保存到imagelist3中 ListViewItem list = new ListViewItem(); list.ImageIndex = index; list.Text = singerName; listView3.Items.Add(list); index++; } } else { DialogResult result= MessageBox.Show("没有" + singerdistrict+"地区 " + singertype + "歌手信息!!!!","用户提示",MessageBoxButtons.YesNo,MessageBoxIcon.Information); if (result == DialogResult.Yes) { fanhui(); } } } } catch (Exception) { MessageBox.Show("网络异常!!"); } finally { con.Close(); } }
这样就能将我们的三个listview展示出来,并且能够实现动态加载数据了,到这里然后我们就可以根据用户第一次,第二次的条件来查找数据库中的歌手信息,当用户点击歌手信息的时候就可以在歌曲列表中根据歌手名字显示所有的歌曲信息了
//获取选中歌星的名字,并将其值传递给歌曲列表然后显示歌曲列表 private void listView3_Click(object sender, EventArgs e) { SongList song = new SongList(); song.SingerName = listView3.SelectedItems[0].Text; song.singer = this; song.Show(); this.Hide(); } //这是在歌曲列表中定义的一个歌手名字信息,然后通过窗体传值实现 public string SingerName; //保存从歌星列表中传递过来要查询其歌曲信息的歌手的名字 string sql = @"select siinger_photo_url,song_name,singer_name , song_url from song_info,singer_info where singer_info.singer_id=song_info.singer_id and singer_name='" + SingerName + "'"; //根据歌手的名字将用户选择的歌手信息展示在LIstView控件中 public void ShowUserByName(string sql) { dataGridView1.AutoGenerateColumns = false; SqlConnection con = new SqlConnection(Help.str); try { SqlDataAdapter da = new SqlDataAdapter(sql,con); DataSet dt = new DataSet(); da.Fill(dt,"user"); dataGridView1.DataSource = dt.Tables["user"]; } catch (Exception) { MessageBox.Show("网络异常!!"); } finally { con.Close(); } }
这样,我就可以通过歌曲列表中的歌曲信息来点歌了,当我每点击一首歌,我就将所点的歌增加到我事先定义好的Song类里面的歌曲数组当中去
//添加歌曲的方法 public void AddSong() { if (dataGridView1.SelectedRows[0].Cells[0].Value.ToString() != "") { song Song = new song(); Song.SongName = dataGridView1.SelectedRows[0].Cells["songname"].Value.ToString(); Song.SongURL = dataGridView1.SelectedRows[0].Cells["songurl"].Value.ToString(); Song.playback = PlayBackStatus.NotBroadcast;//播放状态为未播 Song.singerphotourl = dataGridView1.SelectedRows[0].Cells["siinger_photo_url"].Value.ToString(); //添加歌手图片路径 bool result = PlayList.AddSong(Song); //将歌曲增加到播放列表里面 if (result) { MessageBox.Show("添加成功!!"); AddSongCount(dataGridView1.SelectedRows[0].Cells["songname"].Value.ToString()); } else { MessageBox.Show("添加失败!"); } } else { MessageBox.Show("请选择正确的歌曲名称!"); } }
并且,为了实现金榜排行,用户每添加一首歌,都在数据库中将歌曲的点击次数加1
//每次点击歌曲后都在数据库中将该歌曲的点击次数增加1 public void AddSongCount(string song_name) { SqlConnection con = new SqlConnection(Help.str); string sql = "update song_info set song_play_count=song_play_count+1 where song_name='"+song_name+"'"; SqlCommand com = new SqlCommand(sql,con); con.Open(); com.ExecuteNonQuery(); con.Close(); }
一旦当我们的歌曲数组中有了歌曲之后,我们就可以进行歌曲的播放了,在播放器控件所在的界面(我的是主界面)就可以通过获取到歌曲的完整路径来播放歌曲
并且动态的给pictureBox添加歌手图片
public song CurrentSong; //定义当前播放歌曲 //获得当前播放的歌曲 public void PlaySong() { CurrentSong = PlayList.GetPlaySong();//事先定义的类里的方法 if (CurrentSong != null) { CurrentSong.playback = PlayBackStatus.NowBroadcast; //将歌曲改成正在播放状态 Player1.URL = Help.songurl + CurrentSong.SongURL; //获得歌曲的路径 singerimage.Image = Image.FromFile(Help.ways + CurrentSong.singerphotourl); textBox1.Text = CurrentSong.SongName; } else { //给文本框赋值 textBox1.Text = ""; textBox2.Text = ""; } }
为了可以实现连续播放,并且能够自动播放下一首,我们可以加一个计时器控件,每隔一秒扫描一次歌曲信息,判断时候为空,如果为空则进行播放下一首歌曲
public song NextSong; //定义下一首播放的歌曲 //获得下一首播放的歌曲名称 public void NextPlay() { textBox2.Text= PlayList.GetNextSongName(); //事先定义的类 } private void timer1_Tick(object sender, EventArgs e) { NextPlay(); //获得下一首歌曲的名称 if (CurrentSong == null) { PlaySong(); } if (Player1.playState == WMPLib.WMPPlayState.wmppsStopped) //判断歌曲的播放状态是否为快要停止也就是是否快要播放完 { if(CurrentSong!=null) { CurrentSong.playback = PlayBackStatus.PlayBack; //将该歌曲状态改成已播放 } CurrentSong = null; PlayList.ChargeInde(); //Playlist中定义的方法 } }
这样就基本实现了整个播放歌曲的全过程了,并且你的歌曲数组中有多少歌曲都能够依次顺序播放,以上代码实现了如何通过歌星点歌进行歌曲的播放,但是并没有实现切歌和重唱的功能,
切歌和重唱功能是根据歌曲的状态,在计时器控件中来进行判定操作的,根据歌曲状态来执行相对应的方法(这是主界面的切歌和重唱)
//切歌 private void pictureBox2_Click(object sender, EventArgs e) { // PlayList.CutSong(); if (CurrentSong != null) { CurrentSong.playback = PlayBackStatus.Cut; } else { MessageBox.Show("亲~已经没歌了哦~~"); } } //重唱 private void lenago_Click(object sender, EventArgs e) { if (CurrentSong != null) { PlayList.ListenAgain(); } else { MessageBox.Show("亲,还没歌呢~~"); } } //Tick事件 private void timer1_Tick(object sender, EventArgs e) { NextPlay(); //获得下一首歌曲的名称 if (CurrentSong == null) { PlaySong(); } if (Player1.playState == WMPLib.WMPPlayState.wmppsStopped) //判断歌曲的播放状态是否为快要停止也就是是否快要播放完 { if(CurrentSong!=null) { CurrentSong.playback = PlayBackStatus.PlayBack; //将该歌曲状态改成已播放 } CurrentSong = null; PlayList.ChargeInde(); //Playlist中定义的方法 } if (CurrentSong != null) { if (CurrentSong.playback == PlayBackStatus.Repeat) { CurrentSong.playback = PlayBackStatus.PlayBack; //将歌曲改成已播放状态 PlaySong(); } if (CurrentSong.playback == PlayBackStatus.baoji) { CurrentSong.playback = PlayBackStatus.NowBroadcast; //将歌曲改成正在播放状态 PlaySong(); } if (PlayList.isRight) //判断是否播放选中歌曲 { PlaySong(); PlayList.isRight = false; } if (CurrentSong.playback == PlayBackStatus.Cut) { PlayList.CutSong(); CurrentSong.playback = PlayBackStatus.PlayBack; //将歌曲改成播放状态 PlaySong(); } } }
在已点列表中的切歌和重唱,还有相对应的右键菜单
//重唱 private void lenago_Click(object sender, EventArgs e) { if (listView1.SelectedItems.Count != 0) { PlayList.zhuangtai = "重播"; string SongName = listView1.SelectedItems[0].SubItems[0].Text; PlayList.SelectFromSongName(SongName); listView1.Items.Clear(); show(); } else { MessageBox.Show("请选择一项歌曲!"); } } //切歌 private void pictureBox2_Click(object sender, EventArgs e) { if (listView1.SelectedItems.Count!= 0) { PlayList.zhuangtai = "切歌"; string SongName = listView1.SelectedItems[0].SubItems[0].Text; PlayList.SelectFromSongName(SongName); listView1.Items.Clear(); show(); } else { MessageBox.Show("请选择一项歌曲!"); } } //右键菜单点击播放 private void 播放ToolStripMenuItem_Click(object sender, EventArgs e) { if (listView1.SelectedItems.Count != 0) { string SongName = listView1.SelectedItems[0].SubItems[0].Text; PlayList.SelectIndexBySongName(SongName); listView1.Items.Clear(); show(); } } //右键菜单删除 private void 删除ToolStripMenuItem_Click(object sender, EventArgs e) { if (listView1.SelectedItems.Count != 0) { string SongName = listView1.SelectedItems[0].SubItems[0].Text; if( PlayList.delete(SongName)==false) { MessageBox.Show("该歌曲正在播放,不能删除!"); } listView1.Items.Clear(); show(); } }
如何动态产生字数点歌
private void SelectSongFromWords_Load(object sender, EventArgs e) { for (int i = 0; i < 12;i++ ) { ListViewItem li = new ListViewItem(); li.Text = (i + 1) + "个字"; li.Tag=(i+1); listView1.Items.Add(li); } }
猜数小游戏相关代码
public int num = 0; //随机数 public int count = 0; //猜的次数 private void button1_Click(object sender, EventArgs e) { number.Clear(); Random r = new Random(); num = r.Next(1, 100); //返回0~100的整数,包含1不包含100 button2.Enabled = true; } private void button2_Click(object sender, EventArgs e) { if (Convert.ToInt32(number.Text) > num) { MessageBox.Show("大了点.再猜!"); count++; } else if (Convert.ToInt32(number.Text) < num) { MessageBox.Show("小了点,再猜!"); count++; } else { count++; MessageBox.Show("恭喜你,猜中了!!!一共猜了" + count + "次"); } }
到这儿,前台就基本上是写完了,还有一些其他功能都非常的简单,只是一些sql语句的问题了,这里就不一一展示了.
后台的话由于时间关系,我就不一一的展示界面图了,只将一些关键代码做个展示
后台
如何通过button按钮浏览本地资源管理器,并将图片展示到控件中
public string Singer_photo_url; //保存歌手图片的后半部分路径 public string url; //歌手图片的绝对路径 //点击浏览按钮,添加图片 private void button3_Click(object sender, EventArgs e) { openFileDialog1.Filter = " bgtr|*.jpg;*.png;*.gif"; //内部筛选符合条件的文件 DialogResult result = openFileDialog1.ShowDialog(); if (result == DialogResult.OK)//证明用户双击(选中了)一个文件,我就获取路径 { //相对路径 Singer_photo_url = openFileDialog1.SafeFileName; //绝对路径 url = openFileDialog1.FileName; //给PictureBox的Image属性赋值 pictureBox1.Image = Image.FromFile(url); MessageBox.Show("请选择正确的文件类型!"); Singer_photo_url = null; } }
如何选择文件夹目录
//浏览 private void btnselect_Click(object sender, EventArgs e) { DialogResult result = folderBrowserDialog1.ShowDialog(); if (result == DialogResult.OK) { txtNewURL.Text = folderBrowserDialog1.SelectedPath; } }
如何剪切文件到相应文件夹下
Directory.Delete(txtNewURL.Text); //删除事先存在的文件夹 Directory.Move(txtNowURL.Text, txtNewURL.Text); //将原文件夹中的内容剪切到新文件夹中 UpdateSongURl(); //将新路劲保存到数据库
如何复制文件到指定文件夹下
// 移动歌手照片文件到指定的目录 if (!string.IsNullOrEmpty(SongURl)) { if (url != Help.songurl + SongURl) { File.Copy(url, Help.songurl + "\\" + SongURl, true); } }
以上就是整个MyKTV的所有代码了,因为我是初学者第一次写这个算是比较大的项目,所以有写得不好的地方,和未能实现的功能,大家就见谅哈~^_^.