一.实验要求
(1) 参考现有图片浏览软件的功能,实现一个自己的图片浏览器。
(2) 该图片浏览器,至少完成以下功能:
l 文件操作:目录打开,指定文件打开;
l 图片显示方式:正常,拉伸;
l 图片旋转操作;
l 图片浏览功能:幻灯片演示;
(3) 界面美观,操作方便。
(4)重点检查:缩略图效果(显示方式、速度等);幻灯片效果;间传递文本信息、文件等功能。
二. 设计思路
本次的课程设计,是做一个图片浏览器,参考了Microsoft Office 2010 Picture Manager和WindowsEssential 照片库的图片浏览部分,根据使用这些软件的用户体验,加入了自己一些觉得有用的功能。该软件主要分为4大部分,分别是本地路径显示,图片显示(目录浏览和图片浏览),这两大功能的分别通过PathClass和ImageClass类实现;而菜单模块和图片功能模块在MainForm(UI窗体)的代码中实现。
除了菜单模块,其他3大模块均采用了异步线程的功能。
1.本地路径显示
为了保持软件使用过程的畅顺与稳定,该功能使用了线程的异步动能,增加了委托,当软件加载时,使用异步调用PathClass中的GetPathList方法得到,当获取当前就绪的磁盘,再调用GetFilePathList获取磁盘下当前用户有权限访问两级的文件目录。例如有一个路径C:\A\B\C\D\E,第一次去的调用的时候只会调用获取到A、B的两级的目录信息,当用户展开A文件夹的时候,软件会再次调用GetFilePathList获取到A下的B、C两级的目录信息。软件调用这两个方法的使用都会使用异步线程调用,当异步线程执行完毕后,再调用UI线程(主线程)将目录信息更新到本地路径(PathTreeView)中。
/*在MainForm的构造方法中调用获取本地路径信息的方法*/
AsyncOpenDirectory();
/*MainForm中异步打开本地路径的方法*/
public void AsyncOpenDirectory()
{
PathClass myPath = new PathClass();
//异步调用方法GetPathList
GetPathListDelegate gpld = new GetPathListDelegate(myPath.GetPathList);
//异步线程执行完后调用方法SetPathList
IAsyncResult ar = gpld.BeginInvoke(new AsyncCallback(SetPathList), gpld);
}
/*异步线程执行完后调用的方法获取异步线程返回的目录信息*/
public void SetPathList(IAsyncResult ar)
{
GetPathListDelegate gpld = (GetPathListDelegate)ar.AsyncState;
TreeNode rootTreeNode = gpld.EndInvoke(ar);
MyDelegate d = new MyDelegate(SetTreeView);
this.BeginInvoke(d, rootTreeNode);//UI线程执行修改界面的本地路径的目录信息
}
/*修改界面的本地路径的目录信息*/
public void SetTreeView(TreeNode rootTreeNode)
{
PathTreeView.Nodes.Add(rootTreeNode);
}
/*异步展开子目录模块*/
//声明异步委托
public delegate void MyFileDelegate(TreeViewCancelEventArgs e, TreeNode fileTreeNode);
private void PathTreeView_BeforeExpand(object sender, TreeViewCancelEventArgs e)
{
if (e.Node.Text.Length > 0)//在状态栏上显示提示“打开某某目录”
{
StatusLabel.Text =
String.Format("{0}“{1}”", Properties.Resources.ExpandingString, e.Node.Text);
}
PathClass myPath = new PathClass();
//异步调用MainFormGetFilePathList方法
MainFormGetFilePathListDelegate gfpld =
new MainFormGetFilePathListDelegate(MainFormGetFilePathList);
IAsyncResult ar = gfpld.BeginInvoke(e, null, gfpld);
}
public void MainFormGetFilePathList(TreeViewCancelEventArgs e)
{
PathClass myPath = new PathClass();
if (e.Node.Name.Length > 0)
{
TreeNodeCollection expandingTreeNode = e.Node.Nodes;
foreach (TreeNode x in expandingTreeNode)
{
if (x.Nodes.Count <= 0)//如果子目录的文件夹数大于0则不重新更新文件目录
{
TreeNode fileTreeNode = myPath.GetFilePathList(x.Name, 0);
MyFileDelegate myFileDelegate = new MyFileDelegate(SetFileTreeView);
this.Invoke(myFileDelegate,e,fileTreeNode); //UI线程更新该目录信息
……
/*更新该目录信息*/
public void SetFileTreeView(TreeViewCancelEventArgs e,TreeNode fileTreeNode)
{
……
}
/*PathClass.cs中的代码*/
public delegate TreeNode GetPathListDelegate();//声明异步线程的委托
public TreeNode GetPathList()//获取就绪磁盘的前两级目录信息
{
TreeNode RootTreeNode = new TreeNode();
……
RootTreeNode.Text = Properties.Resources.ComputerName;
DriveInfo[] Drivers = DriveInfo.GetDrives();
foreach (DriveInfo driver in Drivers)
{
if (driver.IsReady)//如果该磁盘就绪
{
TreeNode driverTreeNode = GetFilePathList(driver.Name, 0);
RootTreeNode.Nodes.Add(driverTreeNode);
……
//显示子文件的文件夹
public TreeNode GetFilePathList(string parentPath, int pathDepth)
{
TreeNode parentTreeNode = new TreeNode(parentPath);
DirectoryInfo parentDir = new DirectoryInfo(parentPath);
DirectoryInfo[] childDirs = parentDir.GetDirectories();
……
DirectorySecurity s = new DirectorySecurity(childDir.FullName, AccessControlSections.Access);
if (!s.AreAccessRulesProtected)//判断是否权限访问该目录
{
if (pathDepth < 1)//访问深度,如果还没有到第二级就递归该方法获取
{
TreeNode childNode =
GetFilePathList(childDir.FullName, pathDepth + 1);
parentTreeNode.Nodes.Add(childNode);
}
……
2.图片浏览
该功能主要分为目录浏览和图片浏览两个部分。该部分主要参照的对象为Windows自带的资源管理器(Explorer)并模仿手机显示时的淡出特效。为了实现更加炫酷的功能,本次综合性实验并没有使用ListView和PictureBox等控件,而是使用了WebBrowser空间实现图片的目录浏览和图片的原图浏览。图片的显示内容主要是HTML,特效主要通过CSS和Javascript实现。为了保证UI线程(主线程)稳定和畅顺,这个模块还是使用了异步线程功能。
/*在MainForm中的代码*/
///
///目录浏览模块
/判断浏览器是否加载中,防止同时访问多个网页
private bool BrowserIsOk = false;//是否加载中的标识
//异步线程执行完,UI线程填充目录浏览WebBrowser的委托声明
public delegate void WriteFileOverDelegate(String innerHTML);
//异步线程:文件夹被选中事件
private void PathTreeView_AfterSelect(object sender, TreeViewEventArgs e)
{
if (e.Node.Name.Trim().Length > 0&&e.Node.Name.Trim()!=currentPath)
{
currentPath = e.Node.Name.Trim();//修改全局变量“当前目录”的路径
if (BrowserIsOk)//当上一次目录浏览加载完毕
{
BrowserIsOk = false;//上锁
ImageClass myImage = new ImageClass();
//异步读入该目录下的图片信息:图片绝对地址与图片名称
WriteFileDelegate wfg = new WriteFileDelegate(myImage.WriteFile);
//异步线程完成后,回调函数读入异步线程的返回值
IAsyncResult ar =
wfg.BeginInvoke(e.Node.Name, new AsyncCallback(WriteFileOver), wfg);
}
}
}
//异步线程返回值,并调用UI线程显示
public void WriteFileOver(IAsyncResult ar)
{
……
}
//UI线程修改目录浏览WebBrowser的内容
public void ChangeUri(string innerHTML)
{
SmallViewBrowser.Document.InvokeScript("PicMargin");//显示特效
SmallViewBrowser.Document.Body.InnerHtml = innerHTML;
BrowserIsOk = true;//解锁
}
///
/// 图片浏览模块
///
int CurrentPic = 0;//当前图片的索引
string currentPath = System.IO.Directory.GetCurrentDirectory();//图片当前目录路径
private bool BigBrowserIsOK = false;//图片浏览WebBrowser是否就绪的标识
//图片浏览WebBrowser是否就绪的监听事件
private void BigImageWebBrowser_DocumentCompleted(object sender,
WebBrowserDocumentCompletedEventArgs e)
//用户点击图片事件
private void SmallViewBrowser_NewWindow(object sender, CancelEventArgs e)
{
e.Cancel = true;//取消目录浏览器中发生的事件
//在图片浏览WebBrowser中显示图片
ShowBigPic(SmallViewBrowser.Document.ActiveElement.FirstChild.GetAttribute("src"),
SmallViewBrowser.Document.ActiveElement.FirstChild.GetAttribute("alt"));
//获取当前图片的索引
……
}
//图片切换事件
private void ShowBigPic(string url, string name)
{
BigBrowserIsOK = false;//上锁
object[] myObject = new object[] { (object)url, (object)name };
//在WebBrowser切换图片
BigImageWebBrowser.Document.InvokeScript("ChangePic", myObject);
FunctionTab.SelectedIndex = 1;
BigBrowserIsOK = true;//解锁
}
/*图片类ImageClass中的代码*/
public delegate string WriteFileDelegate(string path);
//图片类型的集合
HashSet<string> hashSet = new HashSet<string>();
public ImageClass()
{
hashSet.Add(Properties.Resources.JPGFileString);//jpg
……
}//增加图片类型
//获取Path目录下所有图片并组成HTML语言
public string WriteFile(string Path)
{
……
int count = 0;
StringBuilder imageHTML = new StringBuilder();
DirectoryInfo imageFile = new DirectoryInfo(Path);
FileSystemInfo[] imageInfo = imageFile.GetFileSystemInfos();
……
foreach (FileSystemInfo x in imageInfo)
{
if (IsPicture(x.FullName))//如果该文件是图片
{
count++;
……
imageHTML.Append(x.Name);
……//组装HTML代码
……
if (count == 0)
{
imageHTML.Append("<p>该文件夹没有图片。</p>");
}
return imageHTML.ToString();//返回HTML代码
}catch(Exception ex){
return "<h1>错误提示:</h1><hr><p>"+ex.Message+"</p>";
}//返回错误提示
}
//判断文件类型,通过文件的前两位判断,防止打开错误后缀名的文件
//例如,就算任何文件改名为*.jpg都会提示错误文件
//相反如果图片文件修改过后续名,都能正常打开
public bool IsPicture(string filePath)//filePath是文件的完整路径
{
……
FileStream fs = new FileStream(filePath, FileMode.Open, FileAccess.Read);
BinaryReader reader = new BinaryReader(fs);
string fileClass;
byte buffer;
byte[] b = new byte[2];
buffer = reader.ReadByte();
b[0] = buffer;
fileClass = buffer.ToString();
buffer = reader.ReadByte();
b[1] = buffer;
fileClass += buffer.ToString();
reader.Close();
fs.Close();
if (hashSet.Contains(fileClass))//判断该文件类型是否在hashSet中
{
return true;
}
else
{
return false;
……
3.菜单模块
菜单模块由4个部分组成:“开始”菜单、“地址栏”菜单、“打印”菜单和“关于”菜单。这些功能是参加其他图片浏览器之后提取出来一些比较实用的功能。四个部分的功能有:打开文件、显示/隐藏地址栏、刷新地址栏、打印/打印设置/打印语言和关于图片库。
///
///菜单->开始 模块
///
//菜单->开始->打开文件
private void StartOpenFileItem_Click(object sender, EventArgs e)
{
OpenFileDialog myDialog = new OpenFileDialog();
if (myDialog.ShowDialog() == DialogResult.OK)//打开文件框
{
string fileName = myDialog.SafeFileName;
string fullName = myDialog.FileName;
ImageClass myImage = new ImageClass();
if (myImage.IsPicture(fullName))//判断选定的是否为图片
{
……
ShowBigPic(fullName, fileName);//显示图片
}
else//提示选择正确的图片
{
MessageBox.Show("请打开正确的图片格式", "提示",
System.Windows.Forms.MessageBoxButtons.OK);
……
//菜单->开始->退出
private void StartCloseItem_Click(object sender, EventArgs e)
{
this.Close();
}
///
///菜单->地址栏 模块
//菜单->地址栏->隐藏地址栏
private void HidePathItem_Click(object sender, EventArgs e)
……
HidePathItem.Text = "隐藏地址栏";
……
//菜单->地址栏->刷新地址栏
private void RefreshPath_Click(object sender, EventArgs e)
{
……//移除当前本地路径的节点信息
AsyncOpenDirectory();//异步获得本地路径节点信息
}
///
///菜单->打印 模块
///
//菜单->打印->打印
private void PrintItem_Click(object sender, EventArgs e)
{
WebBrowser myWB;
……//获得当前选项卡
myWB.Print();
}
//菜单->打印->打印设置
private void PrinterSettingItem_Click(object sender, EventArgs e)
……
myWB.ShowPrintDialog();
……
//菜单->打印->打印预览
private void PrintPreviewItem_Click(object sender, EventArgs e)
……
myWB.ShowPrintPreviewDialog();
……
///
///菜单->关于 模块
///
//菜单->关于->关于
private void AboutItem_Click(object sender, EventArgs e)
{
AboutForm myAboutForm = new AboutForm();
……
//菜单->关于->关于Windows
private void AboutWindows_Click(object sender, EventArgs e)
……
System.Diagnostics.Process.Start(Properties.Resources.Winver);
……
4.图片功能模块
图片功能模块包括了图片缩放大小设置、旋转角度设置、图片切换设置。图片缩放设置是通过修改WebBrowser的显示比例(zoom值)实现的,可以通过拉动显示比例的滚动条设置。而旋转角度的设置则是通过JavaScript修改图片样式中的滤镜的值实现,可以通过点击向左/右旋转90°的图片或通过拉动旋转角度的滚动条设置。而图片切换则是利用JavaScript切换图片的路径实现,而通过点击上/下一张图片实现。
///
///图片缩放模块
///
//缩放控制
private int value1 = 100;//目录浏览的缩放值
private int value2 = 100;//图片浏览的缩放值
//图片缩放值改变事件
private void ImageTrackBar_ValueChanged(object sender, EventArgs e)
{
ImagePercent.Text = String.Format("{0}{1}%",
Properties.Resources.ImagePercentTips, ImageTrackBar.Value);
ChangeZoom();//改变浏览器显示比例事件
}
//当鼠标滚动时,发生显示比例缩小事件
private void MainForm_Scroll(object sender, ScrollEventArgs e)
{
ImageTrackBar.Value--;
}
//目录浏览和图片浏览选项卡改变时,保存被切换的WebBrowser的缩放值
private void FunctionTab_SelectedIndexChanged(object sender, EventArgs e)
{
if (FunctionTab.SelectedIndex == 0)
{
value2 = ImageTrackBar.Value;
ImageTrackBar.Value = value1;
SmallViewBrowser.Focus();
}
else
{
……
}
ChangeZoom();
}
//改变当前浏览器的缩放值
private void ChangeZoom()
{
string zoom=
"zoom:" + Math.Round((double)ImageTrackBar.Value / 100, 2).ToString();
if (FunctionTab.SelectedIndex == 0)
{
//修改浏览器的显示比例
SmallViewBrowser.Document.Body.Style = zoom;
SwapTrackBar.Visible = false;
SwapTipLabel.Visible = false;
}
else
{
……
}
}
///
///功能键模块
///
//功能键提示语
private void FunctionPanel_MouseHover(object sender, EventArgs e)
{
Label myLabel = (Label)sender;
switch (myLabel.Name)
{
case "Play"://当鼠标移动到“幻灯片播放图片”时,显示提示语
this.toolPlay.SetToolTip(Play, "幻灯片播放图片");
break;
……
///
///图片缩放角度模块
///
//旋转角度
private int SwapCount = 0;
//向左或向右旋转90°
private void Swap_Click(object sender, EventArgs e)
{
Label myLabel = (Label)sender;
if (myLabel.Name == "SwapLeft")//计算向左旋转角度
{
SwapCount = (SwapCount + 4 - 1) % 4;
}
else
……
}
//旋转值改变的时候,旋转图片
private void SwapTrackBar_ValueChanged(object sender, EventArgs e)
{
SwapTipLabel.Text = String.Format("{0}{1}°",
Properties.Resources.SwapTipString, SwapTrackBar.Value);
object[] myobject = new object[1];
myobject[0] = (object)(SwapTrackBar.Value.ToString());
//在WebBrowser中旋转图片
BigImageWebBrowser.Document.InvokeScript("SwapChange",myobject);
}
//切换图片,下一张图片或上一张图片
private void NextPic_Click(object sender, EventArgs e)
{
……
ShowBigPic(SmallViewBrowser.Document.Images[CurrentPic].GetAttribute("src"),
SmallViewBrowser.Document.Images[CurrentPic].GetAttribute("alt"));
……
}
/*幻灯片播放模块*/
//幻灯片播放
private void Play_Click(object sender, EventArgs e)
{
Label myPanel = (Label)sender;
if (myPanel.Name == "Play")//全屏显示
{
……
}
else//退出全屏
{
……
}
}
//计时器触发切换图片
private void PicDisplayTimer_Tick(object sender, EventArgs e)
{
Label myLabel = new Label();
myLabel.Name = "NextPic";
NextPic_Click((object)myLabel, e);
}
//幻灯片播放速度设置
private void HowTimeFly_Scroll(object sender, EventArgs e)
{
int value = 1000 + HowTimeFly.Value * 1000;
HowTimeFlyLabel.Text = String.Format("{0}{1}秒/张",
Properties.Resources.HowTimeFlyString, value / 1000 - 1);
PicDisplayTimer.Interval = value;
}
//键入“ESC”时退出幻灯片播放
private void MainForm_KeyPress(object sender, KeyPressEventArgs e)
{
……
myLabel.Name = "Pause";
Play_Click((object)myLabel, e);
……
}
//当软件的大小改变的时候,改变图片的显示大小
private void BigImageWebBrowser_Resize(object sender, EventArgs e)
{
BigImageWebBrowser.Document.InvokeScript("PicHeight");
}
三. 程序运行效果图
本次综合实验的生成的程序,是基于.Net Framework 4.0开发的,在Windows7和Windows8.x下顺利地通过了测试。为了保证软件能够正常地配置,使用了InstallShield2013对本程序进行了封装。
下面是在安装了VirtualStudio 2013 Professional 和.Net Framework 4.5的Windows 7 x64 with sp1 环境下,程序的使用效果。
双击快捷链接,就会显示该软件的主界面,如图3所示。可以看到,软件上方是开始菜单。界面中间左边显示的是本地路径的节点信息,右边则是显示目录浏览和图片浏览的选项卡。界面底部则是状态信息栏和调整显示比例的滚动条。目录浏览默认显示的本软件的简单使用说明。而图片浏览默认显示“示例图片”,如图4所示。在图片浏览选项卡中,显示的还有图片向左/右旋转90°、上/下一张图片、和播放图片幻灯片的按钮。
图5 菜单栏中“开始”的功能
点击菜单栏的“开始”,可以看到有“打开文件”和“退出”两个功能,如图5所示。“打开文件”功能是通过Windows自带的打开文件窗口打开图片,选中图片后,图片会在图片浏览的选项卡中显示,而该图片目录下的所有图片将会在目录浏览的选项卡中显
示。点击“退出”功能则可以退出程序。
图6 菜单栏中“地址栏”的功能
点击菜单栏的“地址栏”,可以看到有“刷新地址栏”和“隐藏地址栏”两个功能,如图6。点击“刷新地址栏”,可以刷新本地路径下的节点信息。点击“隐藏地址栏”,可以隐藏地址栏,如图7所示。
图7 点击“隐藏地址栏”功能后的界面效果
点击菜单栏的“打印”,可以看到有“打印”、打印设置”和“打印预览”三个功能,如图8。点击“打印”,可以直接打印选中选项卡的内容。点击“打印设置”,可以直接对打印进行设置。点击“打印预览”,可以对打印信息进行预览,如图9、10所示。
图8 菜单栏中“打印”的功能
图9 “打印预览”目录浏览截图
图10“打印预览”图片浏览截图
点击菜单栏的“关于”,可以看到有“关于”本软件的信息。点击“关于祥琳望晴图片库2014”,可以查看本软件的版本号等信息,如图11所示。
图11 关于“祥琳望晴 图片库2014”
如图12所示,在本地路径下,展开“这台电脑”,再依次展开“娱乐与文档”、“图片收藏”,点击文件夹“WindowsVista”可以在“目录浏览”的选项卡中显示文件夹“WindowsVista”里的所有图片文件。用户可以通过拉动该程序右下角的“显示比例”滚动条,对“目录浏览”显示的图片进行缩放显示。如图13所示,是将“显示比例”滚动条拉到80%处,也就是将“目录浏览”选项卡中的图片缩小为原先80%后的效果。相同地,在“图片浏览”的选项卡中,也能使用同一条滚动条对“图片浏览”选项卡中的图片进行按比例缩放。值得一提的是,虽然两个选项卡使用了同一条滚动条,但程序会在选项卡切换的时候分别记录和还原每个卡选项上次的缩放记录。也就是说,选项卡之间的缩放比例并不相互影响。例如用户将“目录浏览”的显示比例调整为80%,当切换到“图片浏览”时,显示比例仍未上次的显示比例,设置“图片浏览”的显示比例为110%。再次切换到“目录浏览”,显示比例将会仍为80%。
图12显示比例为100%下的“目录浏览”
图13 显示比例为80%下的“图片浏览”
在“目录浏览”的选项卡中,选中任意一张图片,程序会自动切换到“图片浏览”的卡选项,并淡出显示该图片,如图14所示。如图14所示,与“目录浏览”不同,“图片浏览”多了5个功能键和1条滚动条,分别是:向左旋转90°、上一张图片、幻灯片播放图片、下一张图片、向右旋转90°以及“旋转角度”滚动条。点击“上一张图片”可以切换到图片目录下上一张图片,点击“下一张图片”则可以切换到图片目录下下一张图片。通过拉动旋转角度,可以将“图片浏览”选项卡中的图片进行0~360°的任意旋转,角度之间的最小值为1°。如图15所示,图片旋转270°的效果图。可以在旋转角度为0的情况下,点击1次向左旋转90°实现,或者通过点击3次向右旋转90°实现,还可以拉动“旋转角度”的滚动条到270°实现。
图14 选中图片并在“图片浏览”中显示
图15旋转图片270°
点击播放幻灯片,程序将会全屏显示,并播放图片,如图16所示。用户通过拉动幻灯片底下的“播放速度”滚动条,改变幻灯片的播放速度。本程序的幻灯片播放的特效是随机的,包括淡入、淡出、伸展进入和缩小退出等等。
图16 幻灯片播放图片