图解使用Win8Api进行Metro风格的程序开发十----音乐播放
我们紧接着上篇,这篇将介绍如何使用Windows.UI.Xaml.Media API
中的MediaControl全部事件和MediaElement控件的功能
-----------------------------------我是华丽的分割线-----------------------------------------
我们紧接着上篇,这篇将使用Windows.UI.Xaml.Media API 中的MediaControl全部事件和MediaElement控件的功能
本篇将介绍如下九个方面:
a)MediaControl.PlayPauseTogglePressed事件
b)MediaControl.PlayPressed事件
c)MediaControl.PausePressed事件
d)MediaControl.StopPressed事件
e)MediaControl.FastForwardPressed事件
f)MediaControl.RewindPressed事件
g)MediaControl.RecordPressed事件
h)MediaControl.ChannelDownPressed事件
i)MediaControl.ChannelUpPressed事件
我们的创建的步骤如下:
1)为了组织文件方便,我们先建一个文件夹MediaButtons
2)向文件夹中添加如下文件:
MediaEvents.xaml
创建方法请参照前一篇.
3)由于我们需要对选择的歌曲进行操作,我们要新建一个Song类:
public class Song { public Windows.Storage.StorageFile File; //注意此处,必须是属性 才能用于数据绑定,如public string Artist;数据绑定不到 public string Artist{get;set;} public string Track { get; set; } public Windows.Storage.FileProperties.StorageItemThumbnail Image{get;set;} public Song(Windows.Storage.StorageFile file) { File = file; } public async Task getMusicPropertiesAsync() { //获得音乐属性 var properties = await this.File.Properties.GetMusicPropertiesAsync(); Image = await this.File.GetThumbnailAsync(Windows.Storage.FileProperties.ThumbnailMode.MusicView); //音乐家姓名 Artist = properties.Artist; //歌曲的标题 Track = properties.Title; } }
4)此时的解决方案结构如下:
5)向我们的DataSource添加导航所需要的信息
修改我们的SampleDataSource.cs文件中的SampleDataSource类中的代码,
代码如下:
public SampleDataSource() { #region Group1 var group1 = new SampleDataGroup("FilePicker", "Use Windows.Storage.Pickers API", "Access and save files using the file picker", "Assets/FilePicker.jpg", ""); group1.Items.Add(new SampleDataItem("FilePicker-PickASinglePhoto", "Pick a single photo", "only one file can selected,file type is jpg,jpeg,png", "Assets/FilePicker.jpg", "only one file can selected ", "", group1, typeof(PickASinglePhoto))); group1.Items.Add(new SampleDataItem("FilePicker-PickMultipleFiles", "Pick multiple files", "you can pick multiple files", "Assets/FilePicker.jpg", "pick multiple files", "", group1, typeof(PickMultipleFiles))); group1.Items.Add(new SampleDataItem("FilePicker-PickAFolder", "Pick a folder", "you can pick a folder", "Assets/FilePicker.jpg", "Pick a folder", "", group1, typeof(PickAFolder))); group1.Items.Add(new SampleDataItem("FilePicker-SaveAFile", "Save a file", "you can save a file", "Assets/FilePicker.jpg", "Save a file", "", group1, typeof(SaveAFile))); this.AllGroups.Add(group1); #endregion #region Group2 var group2 = new SampleDataGroup("FileAceess", "Using Windows.Storage API", "File access", "Assets/FileAccess.jpg", ""); group2.Items.Add(new SampleDataItem("FileAceess-CreatingAFile", "Create a file", "Using CreateFileAsync Create a file", "Assets/FileAccess.jpg", "Using CreateFileAsync", "", group2, typeof(CreatingAFile))); group2.Items.Add(new SampleDataItem("FileAceess-WritingAndReadingText", "Write And Read A Text", "Using WriteTextAsync,ReadTextAsync Write And Read Text", "Assets/FileAccess.jpg", "Using WriteTextAsync,ReadTextAsync", "", group2, typeof(WritingAndReadingText))); group2.Items.Add(new SampleDataItem("FileAceess-WritingAndReadingBytes", "Writing and reading bytes in a file", "Using WriteBufferAsync,ReadBufferAsync Write And Read bytes", "Assets/FileAccess.jpg", "Using WriteBufferAsync,ReadBufferAsync", "", group2, typeof(WritingAndReadingBytes))); group2.Items.Add(new SampleDataItem("FileAceess-WritingAndReadingUsingStream", "Writing and reading using a stream", "Using OpenAsync Writing and reading using a stream", "Assets/FileAccess.jpg", "Using OpenAsync", "", group2, typeof(WritingAndReadingUsingStream))); group2.Items.Add(new SampleDataItem("FileAceess-DisplayingFileProperties", "Displaying file properties", "Using GetBasicPropertiesAsync Get File Properties", "Assets/FileAccess.jpg", "Using GetBasicPropertiesAsync", "", group2, typeof(DisplayingFileProperties))); group2.Items.Add(new SampleDataItem("FileAceess-PersistingAccess", "Persisting access to a storage item for future use", "Using MostRecentlyUsedList", "Assets/FileAccess.jpg", "Using MostRecentlyUsedList", "", group2, typeof(PersistingAccess))); group2.Items.Add(new SampleDataItem("FileAceess-CopyAFile", "Copy a file", "Using CopyAsync Copy a file", "Assets/FileAccess.jpg", "Using CopyAsync", "", group2, typeof(CopyAFile))); group2.Items.Add(new SampleDataItem("FileAceess-DeleteAFile", "Delete a file", "Using DeleteAsync Delete a file", "Assets/FileAccess.jpg", "Using DeleteAsync", "", group2, typeof(DeleteAFile))); this.AllGroups.Add(group2); #endregion #region Group3 var group3 = new SampleDataGroup("AccountPictureName", "Use Windows.System.UserProfile API", "Account Picture Name", "Assets/AccountPictureName.jpg", ""); group3.Items.Add(new SampleDataItem("AccountPictureName-GetUserDisplayName", "Get User DisplayName", "Use UserInformation.GetDisplayNameAsync Get User DisplayName", "Assets/AccountPictureName.jpg", "Use UserInformation.GetDisplayNameAsync", "", group3, typeof(GetUserDisplayName))); group3.Items.Add(new SampleDataItem("AccountPictureName-GetUserFirstLastName", "Get First Last Name", "Use UserInformation.GetFirstNameAsync,GetLastNameAsync Get First Name", "Assets/AccountPictureName.jpg", "Use UserInformation.GetFirstNameAsync ", "", group3, typeof(GetUserFirstLastName))); group3.Items.Add(new SampleDataItem("AccountPictureName-GetAccountPicture", "Get Account Picture", "Use UserInformation.GetAccountPicture Get Account Picture", "Assets/AccountPictureName.jpg", "Use UserInformation.GetAccountPicture", "", group3, typeof(GetAccountPicture))); group3.Items.Add(new SampleDataItem("AccountPictureName-SetAccountPictureAndListen", "Set AccountPicture And Listen", "Use UserInformation.SetAccountPicturesAsync Set AccountPicture", "Assets/AccountPictureName.jpg", "Use UserInformation.SetAccountPicturesAsync", "", group3, typeof(SetAccountPictureAndListen))); this.AllGroups.Add(group3); #endregion #region Group4 var group4 = new SampleDataGroup("ApplicationSettings", "ApplicationSettings", " Use the Windows.UI.ApplicationSettings namespace and WinJS.UI.SettingsFlyout", "Assets/ApplicationSettings.jpg", ""); group4.Items.Add(new SampleDataItem("ApplicationSettings-Default", "Default behavior with no settings integration", "Default behavior ", "Assets/ApplicationSettings.jpg", "Default behavior with no settings integration", "", group4, typeof(Default))); group4.Items.Add(new SampleDataItem("ApplicationSettings-AddSettings", "Add settings commands to the settings charm", "Add settings", "Assets/ApplicationSettings.jpg", "Add settings commands to the settings charm ", "", group4, typeof(AddSettings))); this.AllGroups.Add(group4); #endregion #region Group5 var Group5 = new SampleDataGroup("AssociationLaunching", "Use Windows.System.Launcher API", "Association Launching", "Assets/AssociationLaunching.jpg", ""); Group5.Items.Add(new SampleDataItem("AssociationLaunching-LaunchFile", "Launching a file", "Use Windows.System.Launcher.LaunchFileAsync", "Assets/AssociationLaunching.jpg", "Use Windows.System.Launcher.LaunchFileAsync", "", Group5, typeof(LaunchFile))); Group5.Items.Add(new SampleDataItem("AssociationLaunching-LaunchUri", "Launching a URI", "Use Windows.System.Launcher.LaunchUriAsync", "Assets/AssociationLaunching.jpg", "Use Windows.System.Launcher.LaunchUriAsync", "", Group5, typeof(LaunchUri))); Group5.Items.Add(new SampleDataItem("AssociationLaunching-ReceiveFile", "Receiving a file", "Receiving a file", "Assets/AssociationLaunching.jpg", "Receiving a file", "", Group5, typeof(ReceiveFile))); Group5.Items.Add(new SampleDataItem("AssociationLaunching-ReceiveUri", "Receiving a URI", "Receiving a URI", "Assets/AssociationLaunching.jpg", "Receiving a URI", "", Group5, typeof(ReceiveUri))); this.AllGroups.Add(Group5); #endregion #region Group6 var Group6 = new SampleDataGroup("BackgroundTransfer", "Use Windows.Networking.BackgroundTransfer API", "BackgroundDownloader And BackgroundUploader", "Assets/BackgroundTransfer.jpg", ""); Group6.Items.Add(new SampleDataItem("BackgroundTransfer-DownloadFile", "Download Files", "Use BackgroundDownloader", "Assets/BackgroundTransfer.jpg", "BackgroundDownloader", "", Group6, typeof(DownloadFile))); Group6.Items.Add(new SampleDataItem("BackgroundTransfer-UploadFile", "Upload Files", "Use BackgroundUploader", "Assets/BackgroundTransfer.jpg", "BackgroundUploader", "", Group6, typeof(UploadFile))); this.AllGroups.Add(Group6); #endregion #region Group7 var Group7 = new SampleDataGroup("Clipboard", "Use Windows.ApplicationModel.DataTransfer API", "ClipboardOperation", "Assets/Clipboard.jpg", ""); Group7.Items.Add(new SampleDataItem("Clipboard-CopyAndPasteText", "Copy and paste text", "Use Clipboard.GetContent,Clipboard.SetContent", "Assets/Clipboard.jpg", "Clipboard.GetContent,Clipboard.SetContent", "", Group7, typeof(CopyAndPasteText))); Group7.Items.Add(new SampleDataItem("Clipboard-CopyAndPasteImage", "Copy and paste an image", "Use Clipboard.GetContent,Clipboard.SetContent", "Assets/Clipboard.jpg", "Clipboard.GetContent,Clipboard.SetContent", "", Group7, typeof(CopyAndPasteImage))); Group7.Items.Add(new SampleDataItem("Clipboard-CopyAndPasteFile", "Copy and paste files", "Use Clipboard.GetContent,Clipboard.SetContent", "Assets/Clipboard.jpg", "Clipboard.GetContent,Clipboard.SetContent", "", Group7, typeof(CopyAndPasteFile))); Group7.Items.Add(new SampleDataItem("Clipboard-OtherClipboardOperation", "Other Clipboard operations", "Use Clipboard.GetContent,Clipboard.SetContent", "Assets/Clipboard.jpg", "Clipboard.GetContent,Clipboard.SetContent", "", Group7, typeof(OtherClipboardOperation))); this.AllGroups.Add(Group7); #endregion #region Group8 var Group8 = new SampleDataGroup("Compression", "Use Windows.Storage.Compression API", "Compression And Decompression", "Assets/Compression.jpg", ""); Group8.Items.Add(new SampleDataItem("Compression-CompressionAndDecompression", "Compression And Decompression", "Use Windows.Storage.Compression API", "Assets/Compression.jpg", "Compression And Decompression", "", Group8, typeof(CompressionAndDecompression))); this.AllGroups.Add(Group8); #endregion #region Group9 var Group9 = new SampleDataGroup("MediaButtons", "Use Windows.Media API", "Media Buttons", "Assets/MediaButtons.jpg", ""); Group9.Items.Add(new SampleDataItem("MediaButtons-MediaEvents", "Media Events", "Use Windows.Media API", "Assets/MediaButtons.jpg", "Listening to hardware media transport events", "", Group9, typeof(MediaEvents))); this.AllGroups.Add(Group9); #endregion }
6)我们的导航这样就做好了,效果图:
点击 MediaButtons
7)MediaControl的使用和MediaElement控件的使用
修改MediaEvents.xaml的xaml:
<Page x:Class="Win8Api.MediaButtons.MediaEvents" IsTabStop="false" xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation" xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" xmlns:local="using:Win8Api.MediaButtons" xmlns:d="http://schemas.microsoft.com/expression/blend/2008" xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006" mc:Ignorable="d"> <Page.Resources> <DataTemplate x:Key="SongItems"> <Grid Margin="6"> <Grid.ColumnDefinitions> <ColumnDefinition Width="Auto"/> <ColumnDefinition Width="*"/> </Grid.ColumnDefinitions> <Border Background="{StaticResource ListViewItemPlaceholderBackgroundThemeBrush}" Margin="0,0,0,10" Width="40" Height="40"> <Image Source="{Binding Image}" Stretch="UniformToFill"/> </Border> <StackPanel Grid.Column="1" Margin="10,-10,0,0"> <TextBlock Text="{Binding Track}" Style="{StaticResource BodyTextStyle}" TextWrapping="NoWrap"/> <TextBlock Text="{Binding Artist}" Style="{StaticResource BodyTextStyle}" Foreground="{StaticResource ApplicationSecondaryForegroundThemeBrush}" TextWrapping="NoWrap"/> </StackPanel> </Grid> </DataTemplate> </Page.Resources> <Grid Background="{StaticResource ApplicationPageBackgroundThemeBrush}"> <Grid.RowDefinitions> <RowDefinition Height="Auto"/> <RowDefinition Height="*"/> </Grid.RowDefinitions> <Grid x:Name="Input" Grid.Row="0"> <Grid.RowDefinitions> <RowDefinition Height="Auto"/> <RowDefinition Height="*"/> </Grid.RowDefinitions> <TextBlock x:Name="InputTextBlock1" TextWrapping="Wrap" Grid.Row="0" Style="{StaticResource SubheaderTextStyle}" HorizontalAlignment="Left" > This scenario will demonstrate how to register and handle media transport events for hardware and software buttons. To use select media files and press the play button on the keyboard or in the application </TextBlock> <StackPanel Orientation="Horizontal" Margin="0,10,0,0" Grid.Row="1"> <Button x:Name="PlayButton" Content="Play" Margin="0,0,10,0" /> <Button x:Name="DefaultOption" Content="Select Files" Margin="0,0,10,0" /> </StackPanel> </Grid> <Grid x:Name="Output" HorizontalAlignment="Left" VerticalAlignment="Top" Grid.Row="1"> <Grid.ColumnDefinitions> <ColumnDefinition Width="Auto" /> <ColumnDefinition Width="*" /> </Grid.ColumnDefinitions> <ListView x:Name="SongListView" AutomationProperties.AutomationId="ItemsListView" AutomationProperties.Name="Items" TabIndex="1" Grid.Row="1" Margin="10,10,0,0" IsSwipeEnabled="False" ItemTemplate="{StaticResource SongItems}" MaxHeight="400" /> <MediaElement x:Name="mediaElement" AudioCategory="BackgroundCapableMedia" AutoPlay="False" Grid.Column="1"> </MediaElement> <TextBlock x:Name="OutputTextBlock" Style="{StaticResource SubheaderTextStyle}" TextWrapping="Wrap" Text="Events Not Registered" Grid.Column="1"/> </Grid> </Grid> </Page>
修改后台代码:
public sealed partial class MediaEvents : Page { int currentSongIndex = 0; int playlistCount = 0; bool previousRegistered = false; bool nextRegistered = false; bool wasPlaying = false; ObservableCollection<Song> playlist = new ObservableCollection<Song>(); public MediaEvents() { this.InitializeComponent(); DefaultOption.Click += DefaultOption_Click; MediaControl.PlayPauseTogglePressed += MediaControl_PlayPauseTogglePressed; MediaControl.PlayPressed += MediaControl_PlayPressed; MediaControl.PausePressed += MediaControl_PausePressed; MediaControl.StopPressed += MediaControl_StopPressed; MediaControl.FastForwardPressed += MediaControl_FastForwardPressed; MediaControl.RewindPressed += MediaControl_RewindPressed; MediaControl.RecordPressed += MediaControl_RecordPressed; MediaControl.ChannelDownPressed += MediaControl_ChannelDownPressed; MediaControl.ChannelUpPressed += MediaControl_ChannelUpPressed; PlayButton.Click += new RoutedEventHandler(MediaControl_PlayPauseTogglePressed); mediaElement.CurrentStateChanged += mediaElement_CurrentStateChanged; mediaElement.MediaOpened += mediaElement_MediaOpened; mediaElement.MediaEnded += mediaElement_MediaEnded; SongListView.SelectionChanged += SongListView_SelectionChanged; OutputTextBlock.Text = "Events Registered"; } async void SongListView_SelectionChanged(object sender, SelectionChangedEventArgs e) { Song song = e.AddedItems[0] as Song; if (song != null) { await SetMediaElementSourceAsync(song); } await Dispatcher.RunAsync(Windows.UI.Core.CoreDispatcherPriority.Normal, () => { mediaElement.AutoPlay = true; }); await dispatchMessage("Play/Pause Pressed - Play"); } /// <summary> /// Invoked when this page is about to be displayed in a Frame. /// </summary> /// <param name="e">Event data that describes how this page was reached. The Parameter /// property is typically used to configure the page.</param> protected override void OnNavigatedTo(NavigationEventArgs e) { } async void DefaultOption_Click(object sender, RoutedEventArgs e) { FileOpenPicker openPicker = new FileOpenPicker(); openPicker.ViewMode = PickerViewMode.List; openPicker.SuggestedStartLocation = PickerLocationId.MusicLibrary; openPicker.FileTypeFilter.Add(".mp3"); openPicker.FileTypeFilter.Add(".m4a"); openPicker.FileTypeFilter.Add(".wma"); //选择多个文件 IReadOnlyList<StorageFile> files = await openPicker.PickMultipleFilesAsync(); if (files.Count > 0) { //创建播放列表 await createPlaylist(files); //设置当前播放歌曲 await SetCurrentPlayingAsync(currentSongIndex); } } //播放结束,设置下一首 async void mediaElement_MediaEnded(object sender, RoutedEventArgs e) { if (currentSongIndex < playlistCount - 1) { currentSongIndex++; await SetCurrentPlayingAsync(currentSongIndex); if (wasPlaying) { mediaElement.Play(); } } } //开始播放 void mediaElement_MediaOpened(object sender, RoutedEventArgs e) { if (wasPlaying) { mediaElement.Play(); } } void mediaElement_CurrentStateChanged(object sender, RoutedEventArgs e) { if (mediaElement.CurrentState == MediaElementState.Playing) { MediaControl.IsPlaying = true; PlayButton.Content = "Pause"; } else { MediaControl.IsPlaying = false; PlayButton.Content = "Play"; } } //设置要播放的歌曲 async Task SetMediaElementSourceAsync(Song song) { var stream = await song.File.OpenAsync(Windows.Storage.FileAccessMode.Read); await Dispatcher.RunAsync(CoreDispatcherPriority.Normal, () => { mediaElement.SetSource(stream, song.File.ContentType); }); MediaControl.ArtistName = song.Artist; MediaControl.TrackName = song.Track; } //创建播放列表 async Task createPlaylist(IReadOnlyList<StorageFile> files) { playlistCount = files.Count; //注销事件关联 if (previousRegistered) { MediaControl.PreviousTrackPressed -= MediaControl_PreviousTrackPressed; previousRegistered = false; } if (nextRegistered) { MediaControl.NextTrackPressed -= MediaControl_NextTrackPressed; nextRegistered = false; } //设置当前播放歌曲 currentSongIndex = 0; //清空播放列表 playlist.Clear(); if (files.Count > 0) { if (files.Count > 1) { //注册下一曲事件 MediaControl.NextTrackPressed += MediaControl_NextTrackPressed; nextRegistered = true; } // 创建播放列表 foreach (StorageFile file in files) { Song newSong = new Song(file); await newSong.getMusicPropertiesAsync(); playlist.Add(newSong); } SongListView.ItemsSource = playlist; } } async Task SetCurrentPlayingAsync(int playlistIndex) { string errorMessage = null; wasPlaying = MediaControl.IsPlaying; Windows.Storage.Streams.IRandomAccessStream stream = null; try { stream = await playlist[playlistIndex].File.OpenAsync(Windows.Storage.FileAccessMode.Read); await Dispatcher.RunAsync(CoreDispatcherPriority.Normal, () => { mediaElement.SetSource(stream, playlist[playlistIndex].File.ContentType); }); } catch (Exception e) { errorMessage = e.Message; } if (errorMessage != null) { await dispatchMessage("Error" + errorMessage); } MediaControl.ArtistName = playlist[playlistIndex].Artist; MediaControl.TrackName = playlist[playlistIndex].Track; } async void MediaControl_ChannelUpPressed(object sender, object e) { await dispatchMessage("Channel Up Pressed"); } async void MediaControl_ChannelDownPressed(object sender, object e) { await dispatchMessage("Channel Down Pressed"); } async void MediaControl_RecordPressed(object sender, object e) { await dispatchMessage("Record Pressed"); } async void MediaControl_RewindPressed(object sender, object e) { await dispatchMessage("Rewind Pressed"); } async void MediaControl_FastForwardPressed(object sender, object e) { await dispatchMessage("Fast Forward Pressed"); } async void MediaControl_PreviousTrackPressed(object sender, object e) { await dispatchMessage("Previous Track Pressed"); if (currentSongIndex > 0) { if (currentSongIndex == (playlistCount - 1)) { if (!nextRegistered) { MediaControl.NextTrackPressed += MediaControl_NextTrackPressed; nextRegistered = true; } } currentSongIndex--; if (currentSongIndex == 0) { MediaControl.PreviousTrackPressed -= MediaControl_PreviousTrackPressed; previousRegistered = false; } await SetCurrentPlayingAsync(currentSongIndex); } } async void MediaControl_NextTrackPressed(object sender, object e) { await dispatchMessage("Next Track Pressed"); if (currentSongIndex < (playlistCount - 1)) { currentSongIndex++; await SetCurrentPlayingAsync(currentSongIndex); if (currentSongIndex > 0) { if (!previousRegistered) { MediaControl.PreviousTrackPressed += MediaControl_PreviousTrackPressed; previousRegistered = true; } } if (currentSongIndex == (playlistCount - 1)) { if (nextRegistered) { MediaControl.NextTrackPressed -= MediaControl_NextTrackPressed; nextRegistered = false; } } } } async void MediaControl_StopPressed(object sender, object e) { await Dispatcher.RunAsync(Windows.UI.Core.CoreDispatcherPriority.Normal, () => { mediaElement.Stop(); }); await dispatchMessage("Stop Pressed"); } async void MediaControl_PausePressed(object sender, object e) { await dispatchMessage("Pause Pressed"); await Dispatcher.RunAsync(Windows.UI.Core.CoreDispatcherPriority.Normal, () => { mediaElement.Pause(); }); } async void MediaControl_PlayPressed(object sender, object e) { await Dispatcher.RunAsync(Windows.UI.Core.CoreDispatcherPriority.Normal, () => { mediaElement.Play(); }); await dispatchMessage("Play Pressed"); } async void MediaControl_PlayPauseTogglePressed(object sender, object e) { if (MediaControl.IsPlaying) { await Dispatcher.RunAsync(Windows.UI.Core.CoreDispatcherPriority.Normal, () => { mediaElement.Pause(); }); await dispatchMessage("Play/Pause Pressed - Pause"); } else { await Dispatcher.RunAsync(Windows.UI.Core.CoreDispatcherPriority.Normal, () => { mediaElement.Play(); }); await dispatchMessage("Play/Pause Pressed - Play"); } } async Task dispatchMessage(string message) { await Dispatcher.RunAsync(Windows.UI.Core.CoreDispatcherPriority.Normal, () => { OutputTextBlock.Text = getTimeStampedMessage(message); }); } //播放进度 string getTimeStampedMessage(string eventText) { return eventText + " " + DateTime.Now.Hour.ToString() + ":" + DateTime.Now.Minute.ToString() + ":" + DateTime.Now.Second.ToString(); } }
修改Package.appxmanifest:
8)请看效果图:
选择文件后:
点击 play或者点击歌曲,将播放音乐.按下音乐键:
未完待续,敬请期待...
转载请注明出处:http://www.cnblogs.com/refactor/