蛙蛙推荐:winform入门
蛙蛙推荐:winform入门
摘要:现在web应用的发展大大快于桌面应用,但桌面应用在某些场合确实也有很大的有事,其实.net 2.0在windows form方面做了不少的改进,大家也许也零零散散的知道一些,本文来综合这些技术点来做一个模仿outlook2003的桌面应用程序框架。
outlook的界面早已深入人心(当然有些人打死也不用outlook),其实outlook在UI和用户体验多方面的好多考虑是有理论根据的,比如手风琴式的按钮条会让用打户使用起来很方便,三栏式的布局让人的视觉不用左右来回移动,全键盘操作等等方面,也许大家天天用没在意这些,可当初的设计者一定在用户体验上做了好多考虑和取舍的,所以我们这些开发者一般要做的就是模仿。限于篇幅,本文只讲到一些基础的东西,算是一个入门,最终我们会模仿一个outlook界面的大概样子,如果更深入学习outlook的界面,请参考以下链接
Outlook 2003 Look and Feel
http://windowsclient.net/downloads/folders/applications/entry1338.aspx
【窗体设计】
首先窗体的高宽比例要考虑的,不是没有讲究的,这里用932和561,要整体看起来美观,其实这个更具体应用有关的,不能一概而论。再次就是窗体的Icon要做一个能体现你的应用的,一般为了灵活可以在窗体加载事件里从资源文件里读取图标,如下:
Icon = Icon.FromHandle(Resources.Outlook.GetHicon());
然后就是标题要修改一下,就是Text属性。还有就是有时候界面太大或者太小的时候影响窗体内的显示元素,这时候可以用MaximumSize和MinimumSize来设置窗体的最大大小和最小大小。还有的时候我们希望窗体没有最大化和最小化的按钮,分别应该设置MinimizeBox和maximizeBox属性,甚至有时候我们不想让用户能调整窗体的大小,我们可以把FormBorderStyle设置为FixedDialog。如果要想让应用程序一打开就全屏,应该设置WindowState属性为Maximized。
好多应用程序关闭后能记住上次用户拖动设置的窗体大小,下次打开的时候还是上次的窗口大小,这些就需要编程来实现了,后面会介绍到,关于整个窗体设计就说这么多,别的属性大家慢慢看MSDN。
【总体布局】
顶部是菜单栏,接着是工具按钮条,再下面是主工作区,最下面是状态栏,其中工作区又分为左右两部分。以此往界面上拖动MenuStrip,ToolStrip,StatusStrip和一个SplitContainer,然后适当调整SplitContainer左右面板的大小就可以了,然后把FixPanel设置为左边的面板,这样窗体大小改动后左边的面板不会改变。默认SplitContainer放上去,运行的时候拖动手柄会显示一条虚线,我觉得这是一个bug,自动获取焦点了,要把TabStop属性设置为false就可以了,但是你拖动手柄调整大小后,虚线不会自动去掉,所以我就在它的鼠标释放的时候把焦点转给了菜单,就没虚线了,这是我的解决方案,大家有别的方案可以告诉我。我的如下
private void splitContainer1_MouseUp(object sender, MouseEventArgs e)
{
menuStrip1.Focus();
}
SplitContainer会自动填充剩余的空间,并且没有任何边框。但是不要紧,.net 2.0的所有windows控件新增了magrin和padding属性,用来设置外空白和内填充,和CSS差不多,我们可以把左右两个面板设置背景色,然后padding设置为1,最后在左右两个面板里再分别拖进去两个面板,Dock设置为fill,背景色设置为Control,这样就会显示1像素的边框了,这种技巧在网页设计里很常见,这里也适用。
【菜单】
菜单排放的顺序依次是 文件,编辑,视图,转到,工具,操作和帮助,这些排列虽然没有硬性规定,却是约定俗成的,你随便排列会给用户带来不便,包括子菜单的排列等。然后菜单项的文本里可以设置alt键的热键,比如文件的文本可以设置成“文件(&F)”这样用户按住alt键盘和f键就会激活文件菜单。然后菜单的左边可以设置一个表达菜单项意义的图片,还可以设置一个全局快捷键,比如新建联系人用ctrl+shift+C来设置shortcutkeys属性。要想让菜单项的左边显示一个对勾的话需要设置CheckState属性。还有就是如果点击菜单项会弹出对话框的话,菜单项的文本后面要加一个省略号,如果菜单有子菜单项的话右边显示一个小三角,后者是自动的。菜单一般要能用纯键盘操作,关于键的设计,参考如下链接,虽然文章比较早,但仍有意义。
键盘用户界面设计指南
http://wz.cnblogs.com/detail/1873/
关于菜单的基础就是这些。
【工具条】
ToolStrip是个功能很强大的控件,可以在上面添加按钮,下拉按钮,分隔按钮,标签,文本框,组合文本框,甚至进度条等。工具条里演示了这个强大的控件,不过这里只是单纯的用任务提示栏添加各种子控件就可以了,有些应用有多个工具栏,比如标准工具栏,插入工具栏等等,而且还能拖动和隐藏。那是吧ToolStrip放到了ToolStripContainer里了,本文暂不演示,只是提一下,本文只演示最基本的布局和用户体验考虑。ToolStrip的表现力非常强,可以参考如下链接管中窥豹一番:
使用 Windows Forms 2.0 创建智能应用程序布局
http://msdn.microsoft.com/zh-cn/library/aa730847(VS.80).aspx
【状态栏】
状态栏向用户反馈信息的一个重要的工具,很少有程序没有状态栏的。winform 2.0的状态栏比以前版本也强大很多,但缺少了分隔栏,不过没关系,我们在上面添加了label后把右边框显示出来就显示出风格条了,这应该设置BorderSides属性。然后我们有时候想让状态栏上有三个元素,两边的靠在两边,中间的自动填充满,这时候要把中间的label的Spring属性设置为True。再有就是我们有时候想把ToolStripDrowdownButton右下角的小三角去掉,这通常是为了美观,可以把ShowDropDownArrow设置为False。当然这些都是很细小的设置,但是如果你不知道的话,会郁闷半天的。
【手风琴式的功能按钮条】
这个称呼不是我起的,好多人都这么叫,这个是本文的重点,outlook的功能条看起来很炫,其实用ToolStrip控件加上一些自绘就可以模仿出来。
首先在主分割面板的左面板里再放置一个SplitContainer,把Horizontal设置为Horizontal,这样两个面板就成为上下布局了,然后把下面的面板设置为FixPanel,这是因为我们这里要放按钮条,按钮条的高度我们不希望随着窗口的大小而变动。先在这个面板里放一个ToolStrip,为了让按钮从上到下一次排列,我们把LayoutStyle 的值改为 VerticalStackWithOverflow,为了不显示按钮条的是拖动手柄,我们把GripStyle 设置为Hidden,为了显示office式的漂亮效果,我们把RenderMode设置为Professional,还有就是我们把字体设置为宋体,加粗和8号,关于字体的设置可以讨论的有很多,不同情况用不同的字体,这里暂不讨论。为了让按钮条不至于太窄,我们把ImageScalingSize设置为24×24,默认是16*16的有些小,然后把TabStop设置为false,margin和padding都设置为0。
接下来就可以往ToolStrip上添加按钮了,首先把magrin设置为0,padding设置为2,这样按钮上的文字不会挨到按钮的边儿了,这样好看一些然后设置图像,默认设置的透明图像可能会有白变,但把ImageTransparentColor设置为238,238,238就没那个白边了,其中的道理我也不明白,反正直接设置成Transparent白边去不了。然后把DispayStyle设置为ImageAndText,ImageAlign设置为MidleLelt,TextAlign设置为MiddleRight,TextImageRelation设置为Overlay(这部可做可不做),,如果字体已经是粗体就不用单独设置了,否则在按钮上单独设置字体加粗,整体效果就出来了。
然后默认的ToolStrip的背景和边框不是很好看,outlook的按钮条有渐变色,这些就需要用到重绘了,大致原理是创建一个自定义的控件,继承自ToolStrip,然后在OnRendererChanged和构造函数里挂接ToolStrip的背景颜色绘制事件和按钮的背景颜色绘制事件,然后在事件处理函数里重新绘制它们的边框和背景填充色,这里要用到gdi来做2D的渲染,不是本文讲的范围,大家可以搜索下相关资料。另外可以参考以下链接来了解如何写出专业化的ToolStrip,可以根据用户的操作系统样式来改变菜单条的样式。
演练:创建具有专业样式的 ToolStrip 控件
http://msdn.microsoft.com/zh-cn/library/ms233664(VS.80).aspx
{
protected ToolStripProfessionalRenderer _pr;
public WawaToolStrip()
{
// Check Dock
Dock = DockStyle.Fill;
GripStyle = ToolStripGripStyle.Hidden;
Margin = new Padding(0);
CanOverflow = false;
AutoSize = false;
LayoutStyle = ToolStripLayoutStyle.VerticalStackWithOverflow; //垂直排列按钮
SetRenderer();
}
private void SetRenderer()
{
if ((Renderer is ToolStripProfessionalRenderer) && (Renderer != _pr))
{
if (_pr == null)
{
_pr = new ToolStripProfessionalRenderer();
_pr.RoundedEdges = false;
//重绘ToolStrip的背景颜色
_pr.RenderToolStripBackground += StackStrip_RenderToolStripBackground;
OnSetRenderer(_pr);
}
Renderer = _pr;
}
}
private void StackStrip_RenderToolStripBackground(object sender, ToolStripRenderEventArgs e)
{
if (_pr != null)
{
// Setup colors from the provided renderer
Color start = _pr.ColorTable.ToolStripGradientMiddle;
Color end = _pr.ColorTable.ToolStripGradientEnd;
// Size to paint
Rectangle bounds = new Rectangle(Point.Empty, e.ToolStrip.Size);
// Make sure we need to do work
if ((bounds.Width > 0) && (bounds.Height > 0))
{
using (Brush b = new LinearGradientBrush(bounds, start, end, LinearGradientMode.Vertical))
{
e.Graphics.FillRectangle(b, bounds);
}
}
// Draw border
//e.Graphics.DrawRectangle(SystemPens.ControlDarkDark, bounds);
e.Graphics.DrawLine(SystemPens.ControlDarkDark, bounds.X, bounds.Y, bounds.Width - 1, bounds.Y);
e.Graphics.DrawLine(SystemPens.ControlDarkDark, bounds.X, bounds.Y, bounds.X, bounds.Height - 1);
e.Graphics.DrawLine(SystemPens.ControlDarkDark, bounds.X + bounds.Width - 1, bounds.Y,
bounds.X + bounds.Width - 1, bounds.Height - 1);
}
}
protected virtual void OnSetRenderer(ToolStripProfessionalRenderer pr)
{
pr.RenderButtonBackground += _pr_RenderItemBackground;
}
private void _pr_RenderItemBackground(object sender, ToolStripItemRenderEventArgs e)
{
Graphics g = e.Graphics;
Rectangle bounds = new Rectangle(Point.Empty, e.Item.Size);
Color gradientBegin = Color.White;
Color gradientEnd = Color.FromArgb(198, 194, 188);
ToolStripButton button = e.Item as ToolStripButton;
if (button.Pressed || button.Checked)
{
gradientBegin = _pr.ColorTable.ButtonPressedGradientBegin;
gradientEnd = _pr.ColorTable.ButtonPressedGradientEnd;
}
else if (button.Selected)
{
gradientBegin = _pr.ColorTable.ButtonSelectedGradientBegin;
gradientEnd = _pr.ColorTable.ButtonSelectedGradientEnd;
}
// Draw Button Background
using (Brush b = new LinearGradientBrush(bounds, gradientBegin, gradientEnd, LinearGradientMode.Vertical))
{
g.FillRectangle(b, bounds);
}
// Draw Outilne
e.Graphics.DrawRectangle(SystemPens.ControlDarkDark, bounds);
}
protected override void OnRendererChanged(EventArgs e)
{
base.OnRendererChanged(e);
// Work around bug with setting renderer in the constructor
SetRenderer();
}
}
【关于用户设置的保存】
前面提到有些应用会记住自己上次关闭时的窗口大小,其实在winform2.0里做到这个很简单,winform2.0里有一个Settings.settings的文件,它用来添加一些用户的设置,和app.config不同的是,他可以应用于用户范围或者整个应用范围,而且还可以跟随windows漫游文件来走,只要你登录在域里,无论使用哪台机器,你的个性化配置都会跟着你走。在Setting里加入main_form_width和main_form_height两个int型的配置,范围选择user。我们在窗口关闭的时候保存用户配置,打开的时候应用配置,就可以做到窗口大小自动记忆了,代码如下。
{
if (Settings.Default.main_form_height != 0 && Settings.Default.main_form_width != 0)
{
Width = Settings.Default.main_form_width;
Height = Settings.Default.main_form_height;
}
}
private void MainForm_FormClosing(object sender, FormClosingEventArgs e)
{
Settings.Default.main_form_height = Height;
Settings.Default.main_form_width = Width;
Settings.Default.Save();
}
【小节】
本文只是抛砖引玉,希望大家能做出更专业,用户体验更好的桌面应用来。有了这个入门,大家有时间也可以根据自己的需求系统的学习下windows forms编程,光做web应用有时候思路就受局限了,windows上桌面应用才是王道,只要解决了部署的问题,web程序相对windows程序也没什么优势了,可喜的是.net1.0就支持自动部署了,后来又支持click one,部署和更新真的不是问题,而且界面体验比web强的多,还有本地存储等优势,能写出很强大很丰富的界面来,现在web应用,你要用离线存储得安装ms sync framework,google gears等,要想用丰富的界面得用exts,yui等,要想访问网络得自己解析rest服务返回的数据,还得考虑浏览器兼容性等,而winform这些东西都是天生具备的,大家都围着浏览器,flash,severlight的时候,其实可以写一个插件式的应用程序浏览器,发布的时候只是一个空架子,相当于浏览器,广大开发者可以根据SDK开发各种应用,相当于用HTML写网页,然后用应用程序浏览器去浏览各种应用,相当于浏览器去浏览网页,这得咱们自己去做,自己干等着完全支持html5的浏览器发布,不知道等到哪辈子了,到那时候浏览器能直接画矢量图,使用嵌入式Sql,有脱机缓存方面的API,内置多媒体播放器及富文本编辑器而且有好多开放的接口,那时候和winform也差不多了,本文也就没用了,大家也该忘了winform了。关于HTML5的新特性,参考如下链接
揭开HTML 5工作草稿的神秘面纱
http://www.infoq.com/cn/news/2008/02/html5draft
忘了放代码了,下载路径如下
https://files.cnblogs.com/onlytiancai/WindowsApplication1.7z
补充:
关于winform 2.0的新的功能改进可以参考一下两个链接
构造用户界面
http://msdn.microsoft.com/zh-cn/library/aa730862(VS.80).aspx
Ground Rules for Building Enhanced Windows Forms Support into Your .NET App
http://msdn.microsoft.com/en-us/magazine/cc163793.aspx
http://www.cnblogs.com/onlytiancai/archive/2008/09/14/begin_winform.html
代码我也简单贴一下吧,均出自OLAF,我做了一些精简,很容易理解,GDI的一些API如果不理解可以根据其名字来猜测一些含义,不影响对本文的理解。