RSS 2.0 Feed

Wednesday, September 24, 2008

接上篇继续说。

如果调查一个问题:正版 Windows 和盗版 Windows 的区别是什么?我觉得会有如下的回答:

“正版的包装精良”“正版的光盘质量好,不容易坏”(这估计是普通老百姓的说法)

“正版 Windows 有微软的正版贴签”(这估计是软件店促销MM的说法)

“正版 Windows 不用担心激活的问题”(这估计是经常帮人装机的“高手”的说法)

“盗版 Windows 是不是会有错别字啊?”(这估计是深受盗版书打击的读者的说法)

“正版有彩盒包装、光盘,盗版不齐全。如果上面来检查,让他们看到这些盒子就没事儿了。”(这估计是部分老板的理解)

上面的理解其实也没啥特别的错误,不过都没答到实质。IT 行业内的人都已经很清楚了,正版 Windows 和盗版 Windows 运行起来还真是差别不大,那到底相差到哪儿了?

我的理解是,正版和盗版的差别实际上就是那一纸协议:“最终用户许可协议”,也就是常说的“授权协议”,英文的“Licence”(这个词还可以当“驾驶执照”“营业执照”等意思),从法律意义上看这是一个格式合同。它详细记载了许可的内容和范围,记载了你的权利和义务。

你买正版软件,真正花钱买的并不是那张光盘或盒子本身,而是微软给你的一份授权,或者说是你和微软之间的一份合同。判断是否是正版软件,关键不是那张光盘或盒子。你的光盘坏了,或者干脆没有光盘(比如下载的试用版,后来转正),都无所谓,关键的是你有没有那份协议、你的行为有没有超出协议书规定的许可范围。

当前的正版宣传下,有很多人不理解,我买了你的软件,我爱装几台机器就装几台机器,这有什么错?为什么政府机关或者大企业,也是一张光盘就可以同时装很多台,而我才装两三台就不可以了?实际上,虽然介质光盘及包装都是类似的,但授权范围是不同的,“合同”内容是不同的,这就是原因。

因此,我认为微软等大企业应该逐渐转向以“契约精神”“合同”“服务”“权利”“义务”“许可范围”为核心的等正版化宣传,弱化介质的作用。

我认为这么做有很多好处:

(1)很多人不能理解为什么同样是一张光盘,盗版卖4元,正版要上千块。如果转向强调“契约”“合同”“服务”“权利义务”,会比较容易理解和接受,花钱买的是“服务”,是买“执照”(就类似开店需要办营业执照)。盗版不会有售后服务,而且是“无照经营”。

(2)很多人不理解也不重视研究微软的授权体系,举几个例子:

(a) 比如 Windows Server 2003 除了软件本身的授权外,还需要 CAL (User CAL 或 Device CAL)授权。软件一般带 5 个 CAL,如果需要连接更多设备或者让更多用户使用,需要额外购买 CAL,相信很多人并不了解吧。
(b) 比如 MSDN 订阅软件里有几乎全部微软的软件 Windows, Office, Visual Studio 等等,比如这里面的 Windows Server 软件能不能作为正式服务器向外提供服务呢?应该说软件本身是没特别限制的,不过按照 MSDN 的授权协议,这么做是超出许可范围内的,也就是说违反协议、也可以称为“盗版”。(这种“盗版”跟普通人理解的“盗版”差距真大。)

只有强调“契约精神”强调“授权范围”,才能让人们重视这些授权细节,才能吸引人们去研究那复杂的授权体系,根据实际需求选择最适合的授权体系。(顺便说一句,微软的授权体系也应该简化简化,看起来很费劲。) 此处举个例子,比如说 Windows Server 2003 Enterprise Edition R2 版(注意是 R2 版)有一个细节,就是在一台安装此系统的机器上,如果使用虚拟机软件(Visual PC 或 VMware 等)安装虚拟机的话,微软许可在最多四个虚拟机里安装这同一个系统而不需要额外购买授权。这个细节对某些软件开发企业来说,还是很有用的。

希望大家读后能对“正版”“盗版”有个新的认识。

posted @ | Feedback (0) |

最近有一条争议不小的新闻:微软(中国)在国庆节期间的促销活动,Office家庭版降价到199元。

这让我想到了我三年多前的一篇博文,梦呓:微软在中国的新定价策略——比例折算法。这个价格可以说比“比例折算法”还要实惠。不过遗憾的是,网上投票情况显示,半数以上的网民对此促销活动并不买账,认为只要比盗版价格高就绝不买正版。这也如实反映了国民对“正版”的认识程度。

很多人拒绝正版的理由是没钱。但另一方面却发现,大家对于硬件选择相当的“阔绰”“奢侈”,全部组件都要选择最高等级,CPU要市场上最快的,硬盘要转速高容量大的,要外加两三千的专业级独立显卡,再加杜比5.1的家庭影院,呵呵,无论自己的实际需求是否真的必需那么高等级的硬件,都要配到气派、爽心悦目。
(其实,这个“奢侈”或者说“不管实际需求”的毛病,甚至还表现在对“盗版软件”的选择上,Windows XP 全部要 Professional(用盗版 Home 的还真没见过),Vista 全部要 Ultimate,Office 最起码要是 Professional(很多人安装之后根本就不会用到其中的 Publisher, Access 等),Visual Studio 也是最高的 Team 版最受欢迎。)

简单想想,“反正盗版软件不花钱,我就可以把省下的钱升级更好的硬件。何乐而不为呢?”这道理也很好理解。不过,这却让“因为没钱所以拒绝正版”的理由站不住脚了,实际上你不是没钱,而只是不愿把钱“分配”给软件,这才是真正的理由吧?实际上,买电脑之前做预算时,就应该把软件的费用考虑在内,量力而行,根据实际需求、合理分配金钱投入。只有当你把软件当作像 CPU 那样必不可少的组成部分时,你才不会“挪用”本应分配给软件的“专项资金”,对吧。举个不恰当的例子,某些地方政府挪用救灾专款、教育专项资金,去搞地产开发或者其他用途,大家都咬牙切齿,想想道理其实是一样的,只要不重视,就会发生“挪用”,缺钱只是个不高明的借口。

下面简单给出我的观点:
(1)盗版率高的问题,肯定有定价的原因,把发达国家的定价直接搬到中国是不合理的。定价应该考虑到中国的实际收入水平。长远来说,这并不必然会损害产商利益,毕竟中国市场是以人多为特点的市场。如果软件正版化的理念能得到社会承认,以数量取胜同样可以取得丰厚的利润。
(2)如果定价和收入基本协调的情况下,还有人死硬拒绝,那就不是“缺钱”这样的借口可以说得过去的了。

建议所有从事软件开发的人员都应该支持正版化,并力所能及的身体力行。这不仅关系微软这些外国产商,同样也关系我们自己的生存质量。比如,那些死硬拒绝正版的网民的留言:“除非微软降到 39 元(甚至19元、4元等数字),否则坚决不买。” 像 Windows 或 Office 这样规模庞大、功能强大的软件、只值这个价钱的话,那么我们诸位或公司开发的软件、应该卖啥价格呢?是不是要比白菜还便宜呢?所以,没得说,大家都该知道怎么做。

P.S.

新闻中所谓的“Office家庭版”应该是微软专为中国市场推出的吧,包含 Word, Excel, PowerPoint, OneNote 四个组件,在其他地区并没有发现这样的组合方式,正式的版本组合有:个人版 = Word + Excel + Outlook; 标准版 = 个人版 + PowerPoint; 专业版 = 标准版 + Access + Publisher;...
个人以为此次“家庭版”将使用最广泛的 Word, Excel, PowerPoint 组合在一起还是很好的。我在日本就只能选择标准版才能用上这三个组件,而 Outlook 可以用 Windows Live Mail 等替代,并非必需品。希望这样的组合方式能够向其他地区市场推广,呵呵。

posted @ | Feedback (0) |

Monday, June 16, 2008

最近似乎不太顺利,总是一钻进 Reflector 就 N 久时间找不到问题所在,一点一点琢磨那些可疑的、没有头绪的、没有注释的 BCL (.net 的基础类库)源代码,以确认到底是我错了,还是微软错了。

这不,又发现一个疑似bug,如标题所写。

XmlDataSource 控件一般是和 TreeView 组合使用的,如果是静态的 XML 数据是不会碰到什么问题的,但一变成动态数据,就总碰到一些怪异的现象。(虽然大部分最终还是被克服了。)

想让 TreeView 显示动态数据,第一条,可以不用绑定,直接一个 TreeNode 一个 TreeNode 的添加,保证 100% 符合要求。

第二条,在画面上放上多个 XmlDataSource 控件,根据情况将 TreeView 的 DataSourceID 属性指到相应的 XmlDataSource 控件。但局限也很明显,总不能一下子放上 100个,1000个 XmlDataSource 控件吧?

第三条,XmlDataSource 只用一个,但改变它的 DataFile 或者 Data 属性,以改变数据。

改变 DataFile 属性的方案,经试验是可行的。不过相对于 Data 属性,局限就是必须有硬盘上存在的 XML 文件做源。虽然你可以选择动态生成 XML 文件,然后绑上去,但还是有些麻烦,还得考虑考虑这些临时文件的废弃处理措施。

Data 属性相对就比较合我的口味,不过,我就在这个方案上栽了跟斗:我发现无论怎么改变 Data 值,TreeView 的显示总是不变。(只有页面初始化那一次有效。)

先是怀疑是不是 TreeView 没有自动去 XmlDataSource 去取最新数据?对 BCL 的 debug 是有点麻烦的,好像是有办法下载微软公开的源代码,然后进行 debug 的,不过我这会儿没功夫去调。就用 Reflection 将我看到的那些 private 变量的值弄出来看,结果没发现 TreeView 有“偷工”的迹象。

然后把矛头掉转到 XmlDataSource,看看是不是它有问题。一钻去,首先就发现这个家伙跟其他的 DataSource 控件相比,有一点很与众不同,它默认的 Cache.Enabled = true。这个默认设置,也算可以理解,毕竟 XML 文件相对于数据库那些数据源来说,还是很稳定的,而且加载很多节点的 XML 文档也是很费劲的。

进一步的追踪发现,问题的原因应该就是出在缓存上。Data 属性变化后,缓存没有自动失效,导致了问题。

下面是我使用 Reflection 的 Hack:

    public static void XmlDataSourceCacheHack(XmlDataSource dataSource)
    {
        try
        {
            Type t = typeof(XmlDataSource);
            MethodInfo m = t.GetMethod("CreateCacheKey", 
                BindingFlags.Instance | BindingFlags.NonPublic);
            string key = (string)m.Invoke(dataSource, null);
            PropertyInfo p = t.GetProperty("Cache",
                BindingFlags.Instance | BindingFlags.NonPublic);
            object cache = p.GetValue(dataSource, null);

            Type t2 = t.Assembly.GetType("System.Web.UI.DataSourceCache");
            MethodInfo m2 = t2.GetMethod("Invalidate", 
                BindingFlags.Instance | BindingFlags.Public);
            m2.Invoke(cache, new object[] { key });
        }
        catch
        {
        }
    }

如果你有不使用 Reflection 就能解决的方法,敬请不吝赐教,那就多谢了!

如果你希望看点示例代码,请到 codeproject 下面的页面去下载:

http://www.codeproject.com/KB/webforms/XmlDataSource_Cache_Hack.aspx

补充:

当然,还有个简单的做法,就是直接禁用 XmlDataSource 的 cache 功能。不过,像我的实际情况中,切换数据源的可能性要远远低于其他 PostBack 的几率,缓存要比 reflection 更重要一些。你可以根据你的实际情况决定。

posted @ | Feedback (0) | Filed Under [ 技术类 ]

Tuesday, June 10, 2008

痛苦了debug了一个多钟头,后来终于在网络上找到了这篇“救星”文章:

http://columns.chicken-house.net/blogs/chicken/archive/2007/04/06/system-net-mail-bug.aspx

立此存照,如果您也碰到同样问题,希望能够能比我更幸运些,更早找到问题所在。

症状是:调用 SmtpClient.Send 方法后,出现 System.FormatException,

英文消息为“An invalid character was found in header value.

中文消息是:“邮件标头中找到无效字符”。

原因是在 SmtpClient.Send 之前曾经调用过该 MailMessage 对象的 From, To, Cc 等字段的 ToString 方法。很有可能的情形是,你尝试在发信前留下日志时,“无意间”调用到了。而微软的工程师在此处出现了一些失误,最终产生了该错误消息,具体情况请参看上面链接中的文章。

P.S. 当然还会有其他原因可能导致此问题,比如微软知识库里给出的一种原因是因为收件人显示名称中包含有引号

posted @ | Feedback (0) | Filed Under [ 技术类 ]

Friday, April 18, 2008

I suppose you were searching the keywords in the title before entering this page. The problem may be:

In a GridView (ASP.NET 2.0), you want to use a HyperLinkField, but you find it doesn't support UrlEncode, while you are planning to pass some variables via URLs. The bug report shows that Microsoft doesn't have any plan on adding such a property, because their policy on backward compatibility between different version of .net frameworks.

I also made a lot of searching and browsing. The popular way to solve this problem always is, to tranform your HyperLinkField into TemplateField, and do UrlEncode by yourself via HttpUtility.UrlEncode method.

Following codes give you another choice, avoiding such a tranformation, and working for your UrlEncode needs. Just have a try!

 

    public static void HyperLinkFieldUrlEncodeHack(GridView gridView)
    {
        if (gridView == null)
        {
            return;
        }
        gridView.RowDataBound += delegate(object sender, GridViewRowEventArgs e)
        {
            if (e.Row.RowType != DataControlRowType.DataRow)
            {
                return;
            }
            for (int i = 0; i < gridView.Columns.Count; i++)
            {
                DataControlField field = gridView.Columns[i];
                if (field is HyperLinkField)
                {
                    log.Debug(e.Row.RowType.ToString());
                    TableCell td = e.Row.Cells[i];
                    if (td.Controls.Count > 0 && td.Controls[0] is HyperLink)
                    {
                        HyperLink hyperLink = (HyperLink)td.Controls[0];
                        HyperLinkField hyperLinkField = (HyperLinkField)field;
                        if (!String.IsNullOrEmpty(
                            hyperLinkField.DataNavigateUrlFormatString))
                        {
                            string[] dataUrlFields = new string
                                [hyperLinkField.DataNavigateUrlFields.Length];
                            for (int j = 0; j < dataUrlFields.Length; j++)
                            {
                                object obj = DataBinder.Eval(e.Row.DataItem,
                                    hyperLinkField.DataNavigateUrlFields[j]);
                                dataUrlFields[j] = HttpUtility.UrlEncode(
                                    (obj == null ? "" : obj.ToString()));
                            }
                            hyperLink.NavigateUrl = String.Format(
                                hyperLinkField.DataNavigateUrlFormatString,
                                dataUrlFields);
                        }
                    }
                }
            }
        };
    }

 

Pleased if you find it useful.

posted @ | Feedback (0) | Filed Under [ English articles 技术类 ]

我假定你是碰到了和我相同的问题,搜索标题中这几个关键字来到这篇文章的。

简单的描述一下这个有点挠头的问题,就是对于 GridView 中的 HyperLinkField 列,MS 并没有像 BoundField 那样提供 UrlEncode/HtmlEncode 之类相关的属性设置。可实际运用中,你很可能碰到需要在 URL 中传中文参数的问题。Google 出来的网页表明,西方人也会因为一些特殊字符(比如 &)碰到同样的问题。当然相比西方人,CJK 圈子里更普遍一些。

目前看到的文章给出的方案大都是将 HyperLinkField 转化为 TemplateField 之后,手动用 HttpUtility.UrlEncode 方法处理数据绑定。代码会增多,看起来也不太雅观。而且如果你反悔或者想调整字段时,从 TemplateField 也不能自动转回 HyperLinkField。

Google 出来的网页中,有一些网友跟我同样的想法,希望 MS 能够在 .net 2.0 下个版本(也就是 .net 3.x)中提供一个这样的常用属性,并且已经有人向 MS 提交了“bug 报告”。MS 的答复应该令不少人失望:为了在版本升级中保持 backward-compatibility,MS 不会添加这样的属性。

(顺便多句嘴,backward-compatibility 这个词,国内有人译作“向前兼容”,也有人译作“向后兼容”,呵呵,很有意思的一件事,都有点道理,“以前”是指 before now,“向前看”却是 look forward。)

MS 说明 backward-compatiblity 的 blog 上,很多网友表达了跟我类似的疑惑:既然 .net 各个版本之间可以互不影响的独立工作,那就没有必要去“过分”地追求 100% backward-compatibility。当然,为了大家不至于重新学习,做到尽可能兼容就可以了,但以前写的程序,就还让它们在原来的 .net framework 上运行就好了。

而至于版本迁移,决策者准备迁移之前,就应该考虑好迁移的优点和缺点,就像 PetShop 从 .net 1.x 迁移到 2.0 改动不可谓之不大,但为了利用 2.0 最新的技术,它那样去做了,到底值不值得,那就是决策者的算盘了。

好了,上面对于多数人来说都是废话,来主要的吧:

下面这段代码给你一个新选择,避免将 HyperLinkField 转换为 TemplateField ,但同时实现了 UrlEncode。

 

    public static void HyperLinkFieldUrlEncodeHack(GridView gridView)
    {
        if (gridView == null)
        {
            return;
        }
        gridView.RowDataBound += delegate(object sender, GridViewRowEventArgs e)
        {
            if (e.Row.RowType != DataControlRowType.DataRow)
            {
                return;
            }
            for (int i = 0; i < gridView.Columns.Count; i++)
            {
                DataControlField field = gridView.Columns[i];
                if (field is HyperLinkField)
                {
                    log.Debug(e.Row.RowType.ToString());
                    TableCell td = e.Row.Cells[i];
                    if (td.Controls.Count > 0 && td.Controls[0] is HyperLink)
                    {
                        HyperLink hyperLink = (HyperLink)td.Controls[0];
                        HyperLinkField hyperLinkField = (HyperLinkField)field;
                        if (!String.IsNullOrEmpty(
                            hyperLinkField.DataNavigateUrlFormatString))
                        {
                            string[] dataUrlFields = new string
                                [hyperLinkField.DataNavigateUrlFields.Length];
                            for (int j = 0; j < dataUrlFields.Length; j++)
                            {
                                object obj = DataBinder.Eval(e.Row.DataItem,
                                    hyperLinkField.DataNavigateUrlFields[j]);
                                dataUrlFields[j] = HttpUtility.UrlEncode(
                                    (obj == null ? "" : obj.ToString()));
                            }
                            hyperLink.NavigateUrl = String.Format(
                                hyperLinkField.DataNavigateUrlFormatString, 
                                dataUrlFields);
                        }
                    }
                }
            }
        };
    }

 

我想你该知道怎么来使用这段代码吧?

posted @ | Feedback (0) | Filed Under [ 技术类 ]

Monday, March 31, 2008

(第一部分)

样式和控件模板

创建界面时,我们经常重复性地为很多控件设置外观属性,比如,我们希望所有的 Label 都显示为“Bold Trebuchet 12px”的字体,如果是 HTML,我们很容易用 CSS 实现,但如果是 WinForm,则会比较麻烦。WPF 引入了 Style 标签来满足这一常见的需求。先来看一个例子:

<StackPanel>
   <StackPanel.Resources>
      <Style TargetType="{x:Type Label}">
         <Setter Property="FontFamily" Value="Trebuchet" />
         <Setter Property="FontSize" Value="12" />
         <Setter Property="FontWeight" Value="Bold" />
      </Style>
   </StackPanel.Resources>

   <Label>Here is some text.</Label>
   <Label>More text.</Label>
   <Label>The last bit of text.</Label>
</StackPanel>

Style 标签中最常用的是 Setter 语法,从示例中你看到的,Setter 标签指定了样式相关的属性名和我们赋给该属性的值。是不是很简单?上一节中,我们讨论过的数据模板,它可以根据设定的数据类型来匹配,也可以根据指定的 key 来精确匹配,另外,数据模板的书写位置决定了它的作用范围。Style 标签同样适用类似的规则。这个例子中,Style 标签处在 StackPanel 的资源中,匹配类型为 Label 的标签,则此 StackPanel 内的所有标签都将适用此 Style 标签指定的样式。当然如果需要,你也可以为其中一些 Label 单独设定样式。另外一个有用的属性是 BaseOn,它允许 Style 标签间的“继承”关系:

<StackPanel>
  <StackPanel.Resources>
    <Style x:Key="baseStyle" TargetType="{x:Type Control}">
      <Setter Property="FontFamily" Value="Trebuchet" />
      <Setter Property="FontSize" Value="12" />
      <Setter Property="FontWeight" Value="Bold" />
    </Style>
    <Style BasedOn="{StaticResource baseStyle}" TargetType="{x:Type Label}">
      <Style.Triggers>
        <Trigger Property="IsMouseOver" Value="True">
          <Setter Property="Foreground" Value="Red" />
        </Trigger>
      </Style.Triggers>
    </Style>
  </StackPanel.Resources>

  <Label>Here is some text.</Label>
  <Label>More text.</Label>
  <Label>The last bit of text.</Label>
</StackPanel>

此例中定义了两个 Style:一个将对所有 Control 类型有效,第二个只对所有的 Label 有效。我在此例中展示了另一个很有用的语法,Trigger。示例的效果将是,此 StackPanel 内的 Label,在鼠标悬停其上时,背景变为红色。

有了 Style 标签,你可以很容易地让你的界面代码变得风格统一、易于维护。它有很多好处。不过有时候,你或许需要更强大的。比如,你希望界面中的 Button 多一点立体感,或者卡通一些。仅使用基本的 Setter 语法,你无法做到这些,而需要使用控件模板(ControlTemplate)。先试着把控件想得抽象一些,WPF 中,比如一个 Button 控件,它只是一个抽象意义上的按钮,你在不同的软件中见过各种各样、不同外观的按钮,它们都算得上 Button,它们外观各异,有些还在鼠标滑过时变化一些效果,按下它们后会触发一些事件。WPF 中,你可以将这些按钮都用 Button 来实现,分别给它们指定不同的控件模板,它们就将显示出不同的外观效果。一些控件的模板写法简单,另一些控件的模板写法或许复杂一些。下面的例子来自 SDK:

<StackPanel>
  <StackPanel.Resources>
    <Style TargetType="{x:Type Button}">
      <Setter Property="Foreground" Value="white" />
      <Setter Property="Margin" Value="1" />
      <Setter Property="Template">
        <Setter.Value>
          <ControlTemplate TargetType="{x:Type Button}">
            <Grid>
              <Rectangle x:Name="GelBackground"
                Opacity="1" RadiusX="9"
                RadiusY="9"
                Fill="{TemplateBinding Background}"
                StrokeThickness="0.35">
                <Rectangle.Stroke>
                  <LinearGradientBrush StartPoint="0,0" EndPoint="0,1">
                    <GradientStop Color="White" Offset="0" />
                    <GradientStop Color="#666666" Offset="1" />
                  </LinearGradientBrush>
                </Rectangle.Stroke>
              </Rectangle>
              <Rectangle x:Name="GelShine"
                Margin="2,2,2,0"
                VerticalAlignment="Top"
                RadiusX="6"
                RadiusY="6"
                Opacity="1"
                Stroke="Transparent"
                Height="15px">
                <Rectangle.Fill>
                  <LinearGradientBrush StartPoint="0,0" EndPoint="0,1">
                    <GradientStop Color="#ccffffff" Offset="0"/>
                    <GradientStop Color="Transparent" Offset="1"/>
                  </LinearGradientBrush>
                </Rectangle.Fill>
              </Rectangle>
              <ContentPresenter VerticalAlignment="Center" HorizontalAlignment="Center"/>
            </Grid>
            <ControlTemplate.Triggers>
              <Trigger Property="IsMouseOver" Value="True">
                <Setter Property="Fill" TargetName="GelBackground">
                  <Setter.Value>
                    <RadialGradientBrush>
                      <GradientStop Color="Lime" Offset="0" />
                      <GradientStop Color="DarkGreen" Offset="1" />
                    </RadialGradientBrush>
                  </Setter.Value>
                </Setter>
              </Trigger>
              <Trigger Property="IsPressed" Value="true">
                <Setter Property="Fill" TargetName="GelBackground">
                  <Setter.Value>
                    <RadialGradientBrush>
                      <GradientStop Color="#ffcc00" Offset="0"/>
                      <GradientStop Color="#cc9900" Offset="1"/>
                    </RadialGradientBrush>
                  </Setter.Value>
                </Setter>
              </Trigger>
            </ControlTemplate.Triggers>
          </ControlTemplate>
        </Setter.Value>
      </Setter>
      <Style.Triggers>
        <Trigger Property="IsMouseOver" Value="True">
          <Setter Property="Foreground" Value="Black"/>
        </Trigger>
        <Trigger Property="IsPressed" Value="True">
          <Setter Property="Foreground" Value="Black"/>
        </Trigger>
      </Style.Triggers>
    </Style>
  </StackPanel.Resources>

  <Button Height="35" Width="125" Background="Black">Normal</Button>
  <Button Height="35" Width="125" Background="Black">Mouse Over</Button>
</StackPanel>

图10:使用模板的 Button

此例中,ControlTemplate 标签写在 Style 标签的内部(也可以单独写,在不同的 Style 间共享),定义了 Button 的外观效果,包含了若干个叠放着的矩形,它们有着不同的渐变效果。从这个例子中,你多少看到了控件模板的威力了吧,如果你乐意,你还可以在模板中加入 3D 形状或者动画效果。类似地,控件模板也使用类型匹配和 key 匹配,当然要注意不要将类型匹配错,驴唇是装不到马嘴上的。编写控件模板或许不太容易,因此最好先从好用的示例代码开始学习。SimpleStyles 的示例就是不错的资源,Kaxaml 编辑器(一个不错的 XAML 编辑器,或许你该试试)中就提供了此链接,Blend 中也有类似的资源。更多的探讨就超出了这篇入门教程的范畴了,我们进入下一个话题。

动画和 3D

动画和 3D 同样是很大的范畴,本文中我只能简单的介绍一些,并提出一些资源供你进一步的学习。

开始学习 WPF 动画之前,你应该对 DependencyProperties 有一个深入的理解,这样的属性都支持动画。前文已经提到过,Charles Petzold 的那本书在这些话题上有深入的解释,很有助于你的理解。

初步的印象是,WPF 使用 Storyboard 标签来组织动画效果。这种规则便于用 XAML 形式来记述,也能够承载越来越多的各种复杂动画效果。典型的动画以 BeginStoryboard 标签作为它的容器,用于容纳一个或多个 Storyboard:

<BeginStoryboard> 
  <Storyboard TargetProperty="Opacity"> 
    <DoubleAnimation From="1" To="0" Duration="0:0:1" /> 
  </Storyboard> 
</BeginStoryboard>

这个例子中的动画效果将是,Opacity 不透明度属性的值从 1 变化到 0,动画效果的时长是 1 秒钟,也就是在一秒钟内逐渐“消失”掉。哪些控件可以应用这个动画效果呢?又如何指定该动画效果的触发时机呢?请看完整的写法:

<Button Height="40" Width="125"> 
  <Button.Triggers> 
    <EventTrigger RoutedEvent="Button.Click"> 
      <BeginStoryboard> 
        <Storyboard TargetProperty="Opacity"> 
          <DoubleAnimation From="1" To="0" Duration="0:0:1" /> 
        </Storyboard> 
      </BeginStoryboard> 
    </EventTrigger> 
  </Button.Triggers> 
</Button>

我们让这个 Button 在被点击的时候逐渐“消失”掉。触发器定义使用的是 Button 控件的 Click 事件这个 RoutedEvent。下面的例子中,这个动画效果作为样式定义的一部分出现,而触发方式则换了另一种方式:

<Window.Resources> 
  <Style TargetType="{x:Type RadioButton}"> 
    <Style.Triggers> 
      <Trigger Property="IsChecked" Value="True"> 
        <Trigger.EnterActions> 
          <BeginStoryboard> 
            <Storyboard TargetProperty="Opacity"> 
              <DoubleAnimation From="1" To="0" Duration="0:0:1" /> 
            </Storyboard> 
          </BeginStoryboard> 
        </Trigger.EnterActions> 
      </Trigger> 
    </Style.Triggers> 
  </Style> 
</Window.Resources> 
<RadioButton Height="40" Width="125"/>

嗯,它将在 IsChecked 属性变为 True 值时启动该动画效果,这是 Trigger.EnterActions。相对地,还有 Trigger.ExitActions,如果上例改为 ExitActions,则将在 IsChecked 属性从 True 值变为其他值时启动该动画。请注意,上面两例中的动画效果的类型为 DoubleAnimation,这里的“Double”是指被逐渐变化的属性(例子中的 Opacity)的类型为 Double 数字类型。WPF 中定义了 22 中类型的动画效果,如 ColorAnimation, VectorAnimation, PointAnimation 等。这些动画效果都提供了很多可设定的属性选项,用于动画效果的细节控制,比如 AccelerationRatio, DecelerationRatio, SpeedRatio, RepeatBehavior 等。此外,还可以使用 AnimationUsingPath(路径动画),先定义一个“路径”(比如一个正弦曲线),然后让动画的变化规则根据“路径”指示的规则来变化,比如让一个“灯泡”的亮度变化呈正弦曲线的规律,比如让一个“小球”的位置变化沿着一条抛物线的轨迹移动,等等。如果你希望更大的自定义能力,WPF 中还有 AnimationUsingKeyFrames (关键帧动画),可以定义复杂的、缺乏规则的、缺乏连贯性的变化效果。如果你对 WPF 中的动画有兴趣,我建议你读一读这两个博客,Charles Petzold (在他的文章里检索一下)和 theWPFblog,那里还有不少 3D 方面的示例。开始介绍 3D 之前,我得提示你,WPF 动画的结构并不限于平面的动画,它同样适用于 3D 物体的动画。

在这个教程即将收尾之前,我们来看一看 3D。前文提到的 Daniel Lehenbauer 写的那本“Windows Presentation Foundation Unleashed”书中,关于 3D 的那一章写的很精彩,应该是截至目前最好的 WPF 3D 介绍。Petzold 正在写的一本书,将全部用于介绍 WPF 3D。当然还有上面介绍的两个博客,还有以下的几个站点:The WPF3D Team Blog, Five Great WPF 3D Nuggets, 3D Tutorial,都是很好的资源。关于 3D,第一件事必须知道的是,所有的 3D 内容都必须写在 Viewport3D 控件内部,它好像一个窥视 3D 世界的窗口,你可以在这个 3D 世界里放上 Camera,调好角度,再放上光源,这样你就在 Viewport3D 控件里看到那个 Camera “拍”到的图像。WPF 提供了两种主要的 Camera,PerspectiveCamera 和 OrthographicCamera,关于这两者的差别,请看这里的讨论。光源你可以选择 AmbientLight 和/或 DirectionalLight。

<Viewport3D> 
   <Viewport3D.Camera> 
      <PerspectiveCamera   
         FarPlaneDistance="20" 
         LookDirection="5,-2,-3" 
         UpDirection="0,1,0" 
         NearPlaneDistance="1" 
         Position="-5,2,3" 
         FieldOfView="45" /> 
   </Viewport3D.Camera> 
   <ModelVisual3D> 
      <ModelVisual3D.Content> 
         <Model3DGroup> 
            <DirectionalLight Color="White" Direction="-3,-4,-5" /> 
            <GeometryModel3D> 
               <GeometryModel3D.Geometry> 
                  <MeshGeometry3D   
                     Positions="-1 -1 0  1 -1 0  -1 1 0  1 1 0" 
                     Normals="0 0 1  0 0 1  0 0 1  0 0 1" 
                     TextureCoordinates="0 1  1 1  0 0  1 0" 
                     TriangleIndices="0 1 2  1 3 2" /> 
               </GeometryModel3D.Geometry> 
               <GeometryModel3D.Material> 
                  <DiffuseMaterial> 
                     <DiffuseMaterial.Brush> 
                        <SolidColorBrush Color="Blue"/> 
                     </DiffuseMaterial.Brush> 
                  </DiffuseMaterial> 
               </GeometryModel3D.Material> 
            </GeometryModel3D> 
         </Model3DGroup> 
      </ModelVisual3D.Content> 
   </ModelVisual3D> 
</Viewport3D>

在这里例子里,我只放了一个平面,你可以添加自己的 3D 物体。和前面介绍的那样,这里用了 PerspectiveCamera,有光源 DirectionalLight,使用 MeshGeometry3D 定义了平面的位置,然后还给这个平面指定了 Material 材质。例子中使用了 DiffuseMaterial 材质,WPF 中还提供有 EmmisiveMaterial 和 SpecularMaterial 供你选用。如果你之前使用过其他 3D 软件,你应该了解材质的重要性。例子中的 DiffuseMaterial 使用了 Brush 来“粉刷”,如果你善于联想,你说我们是否可以把软件界面也放在某个 3D 物体的表面上?来看看这个网页吧,来看看 WPF 3D 潜在的神奇。

 

WPF 现状和未来

WPF 是一个威力十足的用户界面框架,我向 WPF 的设计团队致意,我相信它是到目前为止最棒的界面框架,无论在任何平台。它还只是一个 Version 1,它还有成长的空间。下面,我将以我的一些想法来总结本文,我希望在未来版本中看到以下的功能:

  • 对话框是常见的需求,我希望看到和 WPF 普通窗口同样风格的对话框;
  • 数据绑定是我的最爱,不过当我用 Reflector 查看其内部结构时发现,它使用反射发出功能来实现(并不是很意外),我希望 WPF 团队应该尝试更快的简单属性存取(setter/getter)和 LCG(译注:不知道此处的 LCG 具体是什么的缩写),我不确定这样做是否有可行性,也不确定是否能提高运行效率,不过我想这是值得去深入研究的;
  • 希望有更多通用的控件,如 DatePicker, Calendar, MaskedTextBox, DataGrid, PropertyGrid 等;
  • 还有,3D 的运行效率希望能够得到改善,也希望更多材质类型,对 pixel shader 的支持;
  • 如果是 XBAP 方式运行,希望有“最小化 WPF”的功能,希望能有更多的 WPF 特性被移植到 XBAP 中来。

随着 Orcas 发布的临近,我希望其中的一部分能够尽早的看到。

展望未来,Microsoft 通过 Silverlight 已经开始将 WPF 和 .NET Framework 的触角伸向其他的操作系统平台。在 MIX07 展示时,Microsoft 声称 Silverlight 1.1 将包含一个跨平台的 CLR 和 BCL(基础类库)的一个子集版本,对于 .NET 开发者来说,这是一个很好的消息,我们将有可能使用喜欢的编程语言,为 Mac 和 PC 机编写基于浏览器的 RIA 程序。Silverlight 1.0 不包含 CLR,如果你希望使用 .NET 语言来编写,那你得用 Silverlight 1.1(译注:目前已改称 2.0 版本),其中包含的 BCL 子集包含了多线程、数据绑定、Web服务等重要功能。我看好这项技术,也坚信 Microsoft 正引领我们走向正确的未来方向。

结语

我希望我的这篇简介能够帮助你了解 WPF 的全貌。这是一个全新的、丰富功能的界面框架,针对 Windows 平台,并且有一个“子集”面向浏览器。它从此前的各种界面框架中获取了很多灵感,它不露痕迹的整合了那些框架的优点,为开发者提供了强大的能量来构建新一代的用户界面。

(作者 Rob Eisenberg 的博客)

posted @ | Feedback (0) |

Friday, March 14, 2008

感觉一年多时间以来,状态遭透了,荒芜了这片自留地,不过还好,这只是自留地,不会有太多人时常关注。这次为了避免被开心开除,先发几篇译文。

这篇译文是我为了学习 WPF 找到的,我认为是我见到最好的 WPF 入门教程。我不知道是否有其他人翻译过,我简单的 Google 了一圈,似乎是没有,因此贴出来给大家共享。另外,从这篇文章开始,我的文章原则上不再发布到博客堂首页了,最近很喜欢清静。

原文地址: http://dotnetslackers.com/articles/silverlight/WPFTutorial.aspx  因为原文很长,我可能将它分两三次来发。另外,如果你发现已经有人译过的话,也告诉我,那我就不用献丑了,呵呵。

WPF Tutorial

Published: 16 May 2007
By: Rob Eisenberg  (作者的博客)

微软发布新技术的速度总是快得难以追赶上,WPF就是这样的例子。WPF和以往构建用户界面的任何框架都不一样。本文尝试着给您一幅WPF的全景鸟瞰图,向您介绍WPF技术中最关键也最革新的思维方式,以期使您充分地了解它的威力,并能够顺利地展开进一步的学习。

简介

Windows Presentation Foundation (通常被我们简称做WPF),是Windows平台上创建用户界面的全新框架。它是WinForm窗体技术的换代品,它能够充分发挥现代PC在多媒体支持方面的潜力。WinForm通过包装底层的经典Win32 GDI+接口实现用户界面,WPF则抛弃了这一路线,使用.NET技术自下而上构建了全新的API框架。它的强大威力在于基于矢量的绘图能力,支持硬件加速,以及不依赖于设备分辨率。

或许你在WPF中找到了那些你在WinForm开发中常用的控件:Button, ComboBox, ListBox等。但在WPF开发中,你的UI设计器完全不同于以往,不再有设计器生成的构建画面控件及布局的代码文件,不再有设计器生成的资源文件。WPF使用XML,也就是XAML。千万别被这个新特点吓跑,你将发现它并不如想象中的困难,它很符合直觉,或许你还会认为充满乐趣。WPF拥有许多全新的功能,下图展示了其中最重要的一部分:

图 1: WPF中重要的功能

Important WPF Features

表的右列中,列出了一些WPF之前的技术,你或许会觉得它们有相似点。希望会对你认识左列中的新术语有所帮助,但请不要把它们想得太像,差别还是很大的。

除了上面介绍到的功能,WPF引入了一种新的程序运行方式。你仍然可以做成标准的Windows应用程序,新的选择是XBAP: the XAML Browser Application。你将XBAP程序放置到Web服务器上,把地址链接告诉你的使用者,使用者通过浏览器访问,XBAP程序就可以在浏览器的沙盒中运行了。这种体验有点类似于Flash,但仅限于IE用户(版本6以上)和其他一些支持WebControl的浏览器(如Firefox可以通过它的IE-tab)。你可以设想到,这很适合于为企业、公司创建应用程序,如果他们的职员主要工作在Windows XP或Windows Vista上的话。但是,影响选择XBAP的一个问题或许是,需要在客户端安装.NET Framework 3.0(译注:或3.5)。微软正在研发中的Silverlight(之前称为WPF/E),将是一个跨平台的、跨浏览器的新选择,它将包含.NET Framework的一个子集和WPF技术的子集。

你准备开始了吗?你需要准备好开发工具。WPF是.NET Framework 3.0(或3.5)的一部分,首先,你得知道,.NET 3.0应安装在Windows XP Service Pack 2以上、Windows Server 2003 Service Pack 1以上,或者Windows Vista操作系统上,你可以从这里下载最新的.NET Framework(注:3.5版)。安装3.0的同时,.NET 2.0也会被安装,如果你的机器上没有2.0的话,这是因为3.0是2.0的超集。我猜你更希望使用Visual Studio的集成开发环境来学习,你可以下载一个试用版的Visual Studio 2005。(注意那文件可不小,得花点时间的。),和对.NET 3.0的扩展支持包。(译注:建议直接用Visual Studio 2008)另外,我还建议你去WPF的官方站点逛逛。

打开开发环境,WPF的画面设计器和WinForm的很相像,但它们还是差别很大。你可以通过标签切换画面效果和XAML代码。而更为专业的工具是Expression Blend(之前称为Expression Interactive Designer),你可以从这里下载它的试用版。如果你希望界面有特别的外观效果,你应该去试试它。它有些像Adobe Illustrator,不过它用于XAML的编辑,并且和VS集成得很好。设计师可以以他们熟悉的方式工作,并且产生的XAML代码直接用于产品,设计师的参与度大大加强了。以往的工作模式中,设计师将设计出的效果图转交给开发队伍后,开发人员需要用编程的方式将画面重新实现出来,并且还会因为难以实现而放弃某些设计效果。你还可以考虑第三方的Mobiform's Aurora,我强烈建议你试一下。除此以外,还有很多插件用于将其他流行软件制作的2D/3D图形转化为XAML。或许你希望我能推荐一些书籍,目前我所知的最棒的两本书是Charles Petzold著的Applications = Code + Markup 和Adam Nathan著的Windows Presentation Foundation Unleashed

XAML

XAML是eXtensible Application Markup Language的缩写。你可以将它想成用类似HTML的方式来定义Windows应用程序的界面,不过XAML的功能更强大一些。或许代码狂们会认为XAML不过是一堆CLR对象被序列化后的结果,不过XAML除了可以被逆序列化之外,还比较适合阅读,程序员们可以直接编辑它,图形化的设计器可以方便生成它。包含XAML文件的项目,编译后对应的是BAML,这是二进制表示的XAML,它被当作资源存储在程序exe或dll中,当运行需要时被快速加载到程序中来,BAML比XAML的加载效率要高一些。

学习XAML最好的方式就是通过一些例子,不再絮叨,首先来比较两段代码:

HTML 代码片段

<div style="border: solid 5px black; margin: 10px; padding: 5px">

 <input type="button" value="Click Me!" />

</div>

XAML 代码片段

<Border BorderBrush="Black" BorderThickness="5" Margin="10" Padding="5">

 <Button>Click Me!</Button>

</Border>

图 2: 带边框的按钮

A Button with Border.

这两段代码将各自实现相似的效果:一个按钮以及环绕其一圈的边框。(注意:将样式直接声明在控件上,多数时候并不太好。此处只是为了演示目的。)这个例子大家可以看到XAML和HTML的写法有很像的地方。或许你也会认可,XAML的写法比HTML更直观些。(译注:如果你的英语很poor,或许你不会这么认为。)多解释一些XAML的基本写法:标签名(Border, Button)直接表示WPF类库中的类(主要在System.Windows.Controls命名空间中),标签的属性(BorderBrush, Margin等)直接对应该类的同名属性。因为继承关系,很多控件都有一些相同的属性,这将便于学习。

使用XAML,你可以标记出大量和界面相关的设定,除了控件,还有面板布局,基本图形(圆形、方形等),3D图形,甚至还包括动画。来看下一个例子,我们画一个椭圆:

<Ellipse Width="200" Height="150">

  <Ellipse.Fill>

      <LinearGradientBrush>

         <GradientStop Offset="0" Color="Teal"/>

         <GradientStop Offset="1" Color="Aqua"/>

      </LinearGradientBrush>

   </Ellipse.Fill>

</Ellipse>

图 3: 椭圆

An Ellipse

我们来简单分析一下:第一行代码很简单,设置了椭圆的高度和宽度。第二行Ellipse的子节点,有些跟刚才看到的不一样,从图3的效果看,我们是打算为这个椭圆设置一个渐变的填充颜色,也就是打算给Fill属性赋值。如果我们只是给Fill属性设置一种单一的颜色,就可以仿照Width和Height那样子声明即可,可现在要声明一个渐变,是不可能以刚才那样的简单方式的。这是一个很常见的场景,从示例代码中你已经知道解决方法了,我们可以称之为Property Element Syntax(元素属性语法),在该标签的内部,创建一个“类型名.属性名”的子节点,然后再在此子节点内部定义我们希望的东西。此例中就是Ellipse.Fill节点。再往下看第三行、第四行,LinearGradientBrush类型属于System.Windows.Media命名空间,你可以去查看这个类的GradientStops属性,该属性用于设置渐变的起止颜色。如果你足够仔细,你会发现我们关于其GradientStops属性的设置不符合刚刚的“元素属性语法”,它并没有使用LinearGradientBrush.GradientStops的子节点,而在LinearGradientBrush内直接写了两个GradientStop标签。你以后会发现类似的例外很普遍。XAML规定,每个类型可以指定一个属性作为它的默认内容属性(default content property),该默认内容属性的子节点可以直接写在类型节点的内部。此例中,GradientStops属性是LinearGradientBrush类的默认内容属性。其实,刚才的第一个例子已经出现了这种情况:

<Button>Click Me!</Button>

这里,Button类型的默认内容属性是Content,“Click Me!”将作为Content的值。下一个例子,我们为Button的Content属性指定稍微“复杂”一些的内容:

<Button>

   <Ellipse Width="200" Height="150">

      <Ellipse.Fill>

         <LinearGradientBrush>

            <GradientStop Offset="0" Color="Teal"/>

            <GradientStop Offset="1" Color="Aqua" />

         </LinearGradientBrush>

      </Ellipse.Fill>

   </Ellipse>

</Button>

图 4: 内部包含一个椭圆的按钮

A Button with Ellipse content.

看明白了吗?Button(还有其他一些控件)继承自ContentControl类,该类定义了一个Content属性,你可以将任意的CLR对象放进Content中。不过对于大部分不可显示的对象,WPF将直接的调用其ToString()方法,将所得的字符串作为实际的Content。但是除此之外,还有很多可以显示的对象,这意味着Button中可能嵌套有另外的控件,或者图片、图形,甚至3D、动画、视频等。这个有些奇妙的特性其实不是XAML带来的,而是WPF的设计理念之一,有时被称为Content Model(内容模型)。这个开放性的架构允许了很多想得到或想不到的组合与嵌套,蕴藏着无限的可能性。

如果你还没有想通这个道理,不用着急,我们将要看到更多的示例,保证你越看越着迷。你应该将这些示例运行起来,这样效果更好,我推荐你使用XamlPad,可以直接显示XAML效果的小编辑器,它属于SDK的一部分。当然你也可以放到Visual Studio里面运行。

UI 布局

创建用户界面时,或许第一件事就是如何布局,如何将各种各样的界面元素合理的安排到它们应在的位置。在WPF之前的MS技术里,我们只有有限的支持,.NET 2.0提供的布局模型或许是多数人等待已久的,然后它仍然有很多不便之处。但在WPF,布局成为了一等公民,提供了一系列的布局控件,满足各种不同场合的需要。这里,我只能介绍其中的很少一部分,因此建议你读完本文后,去查看SDK中关于其他布局控件的介绍。来看这个例子:

<StackPanel>

   <TextBlock>My UI</TextBlock>

   <ListBox>

      <ListBoxItem>Item 1</ListBoxItem>

      <ListBoxItem>Item 2</ListBoxItem>

   </ListBox>

   <RichTextBox/>

</StackPanel>

图 5: StackPanel 布局

A StackPanel layout.

 

StackPanel是最简单的布局选择,就像它的名字那样,它将放在其中的控件叠放在一起,从上到下或者从左到右。下面是DockPanel:

<DockPanel>

   <TextBlock DockPanel.Dock="Top">My UI</TextBlock>

   <ListBox DockPanel.Dock="Right">

      <ListBoxItem>Item 1</ListBoxItem>

      <ListBoxItem>Item 2</ListBoxItem>

   </ListBox>

   <RichTextBox/>

</DockPanel>

图 6: DockPanel 布局

A DockPanel layout.

DockPanel有些类似WinForm中最初提供的Dock布局模式,你可以将元素紧贴上边缘、下边缘、左边缘或右边缘进行布局,最后一个元素将充满剩余的空间。关于这些特点,想必大家都不陌生。此处重点看一下示例XAML中的新语法。注意看TextBlock和ListBox标签中的DockPanel.Dock属性,而实际上,这两个类型是不包含这样的属性的。这是一个新语法,“DependencyProperty”:DockPanel类型中声明了一个名为DockProperty的DependencyProperty。对这个特例来说,这个DependencyProperty是一个AttachedProperty,即允许父级控件将设定信息存储在子级控件上。(我们目前没有时间来讨论DependencyProperty的细节,如果你有兴趣,请阅读上面提到过的Charles Petzold的Applications = Code + Markup。)在这个示例中,DockPanel将它所需的Dock设定存储到了它的子级控件上,语法为“父级标签名.AttachedProperty名”。OK,我们看一个Grid布局的示例:

<Grid>

   <Grid.RowDefinitions>

      <RowDefinition Height="*"/>

      <RowDefinition Height="3*"/>

   </Grid.RowDefinitions>

 

   <Grid.ColumnDefinitions>

      <ColumnDefinition/>

      <ColumnDefinition/>

   </Grid.ColumnDefinitions>

 

   <TextBlock Grid.ColumnSpan="2">My UI</TextBlock>

   <ListBox Grid.Row="1" Grid.Column="0">

      <ListBoxItem>Item 1</ListBoxItem>

      <ListBoxItem>Item 2</ListBoxItem>

   </ListBox>

   <RichTextBox Grid.Row="1" Grid.Column="1"/>

</Grid>

图 7: Grid 布局

A Grid layout.

Grid布局或许是最经常使用的,它有些类似HTML中的TABLE布局模式。它的语法或许最复杂。示例代码中的开始部分,定义了行、列。你或许注意到此处行的高度定义写法,它的意思是第二行是第一行高度的3倍,也就是第一行占Grid总高度的1/4,第二行占3/4。我们没有定义列宽,因此两列各占一半宽度。这个示例中,你仍然看得到AttachedProperty,诸如“Grid.Column”“Grid.ColumnSpan”。你注意到第一行第一列的TextBlock没有明确指定Grid.Column和Grid.Row,这两个属性默认为零,因此显示是正常的。

在实际的应用程序中,界面或许使用多种不同的布局控件相互嵌套在一起,元素的宽度、高度最好避免使用绝对的数字,而让它们自己协调,以充分的利用屏幕空间。经过一段时间练习,相信你很快会熟悉WPF的布局模型并感受到它的灵活性。除了上面介绍的三种外,还有很多布局选择,以下是一些常用的,本文没有时间来介绍了:

  1. WrapPanel: 类似于StackPanel,但当一行不够显示时会转到下一行。
  2. GroupBox: 类似于WinForm的GroupBox。
  3. Viewport: 可以自动放大或缩小,以适应空间的大小。
  4. Border: 经常用于表示边框、标题条等。
  5. Canvas: 使用坐标位置布局的Panel,坐标相对于Canvas。

当然,你还可以创建自己的Panel,实现特殊的功能。网上有一些自定义Panel的示例,比如Kevin's Bag-O-Tricks,你可以跟他学习很多自定义Panel的方法,当然还包括自定义其他控件的示例。另外,也建议你在适当的时候使用Margin、Padding的方式来布局,以避免过多的嵌套。其他一些可能用到的属性,如VerticalAlignment和HorizontalAlignment,都很有用,帮助你实现实际程序中的界面布局。

数据绑定

数据绑定(Databinding)和数据模板(DataTemplates)或许是WPF中最具威力的特性了。无论你以往的经历是MS系的技术,还是其他技术,WPF的数据绑定都会让你印象深刻。数据绑定功能在WPF底层的DependencyObject类型里实现,WPF里绝大多数类型都从此类型衍生,因而都具有该基类实现的数据绑定、样式、动画等功能。本文不再此展开讨论,简单说,DependencyObject的子类型都支持DependencyProperty,而大多数属性,像Text, Content, Width, Height 等都是DependencyProperty,任何一个DependencyProperty都具有实现动画、样式、数据绑定的能力。WPF在数据绑定方面有着丰富的支持,本文只能选择其中最基本的示例来演示,你应该去看看Beatriz Costa的博客,那里有更深入的探讨,我建议你从他的第一篇文章开始,一直看到最近的文章,我认为在那里花些时间是值得的。

第一个例子通常都很简单,也很普遍的场景。假定我们有一些对象,是Person, Customer, Employee等类型,下面这个XAML用于显示这些对象的数据,用于编辑模式:

<Grid>

   <Grid.RowDefinitions>

      <RowDefinition/>

      <RowDefinition/>

      <RowDefinition/>

      <RowDefinition/>

      <RowDefinition/>

   </Grid.RowDefinitions>

 

   <Grid.ColumnDefinitions>

      <ColumnDefinition Width="75"/>

      <ColumnDefinition/>

   </Grid.ColumnDefinitions>

 

   <Label>First Name:</Label>

   <TextBox Grid.Column="1" Text="{Binding Path=FirstName}"/>

 

   <Label Grid.Row="1">Last Name:</Label>

   <TextBox Grid.Row="1" Grid.Column="1" Text="{Binding LastName}"/>

 

  <Label Grid.Row="2">Street:</Label>

   <TextBox Grid.Row="2" Grid.Column="1" Text="{Binding Address.Street}"/>

 

   <Label Grid.Row="3">City:</Label>

   <TextBox Grid.Row="3" Grid.Column="1" Text="{Binding Address.City}"/>

 

   <Label Grid.Row="4">State:</Label>

   <TextBox Grid.Row="4" Grid.Column="1" Text="{Binding Address.State}"/>

</Grid>

图 8: 简单的数据绑定

A basic databinding example.

截图很直观。我得说明一下对象的成员组成:Person类有字符串类型的FirstName和LastName属性,此外还有一个Address类型的Address属性。Address类包含Street, City和State三个字符串属性。上例中,我在后台代码中把Person对象赋值给Window的DataContext属性(每个FrameworkElement的子类型都有这个属性)。数据绑定后,WPF沿着控件树,从末端向根部逐级查询,直到找到一个它可以绑定的DataContext属性。注意绑定的语法,使用了大括号,并且以“Binding”开头。注意到FirstName和LastName的绑定写法稍有不同,后者多了“Path”,但两者效果相同。另外,还要注意关于Address的绑定写法。如果运行这个程序,你会发现在界面上改变Person对象的值,会影响到后台的Person对象;而相反的,后台Person对象的改变不会自动反映到界面上。你可以选择实现INotifyPropertyChanged接口来实现这一过程,一项较为琐碎的工作。

接下来我们考虑一个普通的场景,我们想在不同的画面表示相同的信息,比如类似一张“联系人卡片”那样子。在WinForm中,你或许考虑创建一个自定义控件或者用户控件来实现。在WPF当然也可以如此去做,不过你最好看看数据模板功能:

<Grid>

 <Grid.Resources>

    <DataTemplate DataType="{x:Type local:Person}">

      <Grid Margin="3">

        <Grid.BitmapEffect>

          <DropShadowBitmapEffect />

        </Grid.BitmapEffect>

        <Rectangle Opacity="1" RadiusX="9" RadiusY="9" Fill="Blue" StrokeThickness="0.35">

          <Rectangle.Stroke>

            <LinearGradientBrush StartPoint="0,0" EndPoint="0,1">

              <GradientStop Color="White" Offset="0" />

              <GradientStop Color="#666666" Offset="1" />

            </LinearGradientBrush>

          </Rectangle.Stroke>

        </Rectangle>

      <Rectangle Margin="2,2,2,0"

        VerticalAlignment="Top"

        RadiusX="6"

        RadiusY="6"

        Stroke="Transparent"

        Height="15px">

        <Rectangle.Fill>

          <LinearGradientBrush StartPoint="0,0" EndPoint="0,1">

            <GradientStop Color="#ccffffff" Offset="0" />

            <GradientStop Color="transparent" Offset="1" />

          </LinearGradientBrush>

        </Rectangle.Fill>

      </Rectangle>

      <Grid Margin="5">

        <Grid.RowDefinitions>

          <RowDefinition Height="auto"/>

          <RowDefinition Height="auto"/>

          <RowDefinition Height="auto"/>

        </Grid.RowDefinitions>

        <Grid.ColumnDefinitions>

          <ColumnDefinition />

        </Grid.ColumnDefinitions>

 

        <StackPanel Grid.Row="0" Orientation="Horizontal">

          <TextBlock FontSize="16" Foreground="White" Text="{Binding LastName}" />

          <TextBlock FontSize="16" Foreground="White" xml:space="preserve">, </TextBlock>

          <TextBlock FontSize="16" Foreground="White" Text="{Binding FirstName}" />

        </StackPanel>

 

        <TextBlock Grid.Row="1" FontSize="16" Foreground="White"

          Text="{Binding Address.Street}" />

        <StackPanel Orientation="Horizontal" Grid.Row="2">

          <TextBlock FontSize="16" Foreground="White" Text="{Binding Address.City}" />

          <TextBlock FontSize="16" Foreground="White" xml:space="preserve">, </TextBlock>

          <TextBlock FontSize="16" Foreground="White" Text="{Binding Address.State}" />

        </StackPanel>

      </Grid>

    </Grid>

 </DataTemplate>

 </Grid.Resources>

 

 <ListBox Name="personList" />

 

</Grid>

图 9: 数据模板示例:联系人卡片

A contact card DataTemplate sample.

数据模板是让原本不可视的对象变得可视化的模板。此例中,Person类本身是不可视的,它应该显示成什么样子?例子中,我们创建了一个DataTemplate,并指定它的DataType为Person,现在WPF知道如何来显示一个Person类的对象了,在数据绑定发生时,它就那么去显示了。你也可以给模板指定一个key,避免自动的模板套用,只在你指定的地方让它套用模板。这跟HTML中CSS有些类似,当然数据模板要更强大。数据模板写在资源中,你可以把它放置到不同级别节点的资源中,它的作用范围也就不同了,此例中放在Grid.Resources中,向上还可以放在Window.Resources或Page.Resources,或者顶级的Application.Resources。另外,类型的继承关系也会影响数据模板,比如Person的子类型Employee, Manager等也会套用这个模板,不过如果单独的Manager模板也存在的话,WPF会套用Manager的模板也不用Person的模板。神奇吧?

本文中关于数据绑定和数据模板的介绍只能到此了,我很惭愧没有能力覆盖更多的特性,但我的文章必须得进入下一个特性了。不过,留下如下的思考题给你: You can have conditional binding based on business rule validation, conditional template selection based on business rules, converters that shape data when binding to and from the UI, DataTriggers that change the appearance of the template based on values in the data, binding between different UI elements based on name or relative position in the UI hierarchy, a choice of four different binding modes and a choice of what type of event triggers the binding, and bindings to data sources that pass in other bindings as parameters, etc. (译注:此段保留未翻译,暂时理解还不够深刻。)此外还有很多可能的思路。再一次地,向你推荐Beatriz Costa的博客,等你有了更深的理解后,再来读这些文字。

。。。(第二部分

posted @ | Feedback (1) |

Thursday, October 12, 2006

我关于 BuildProvider 的系列介绍还没有写新的文章,是因为我最近在研究 NDoc 的代码,已经将核心部分的代码理清了思路。然后我做了一个决定:捡起 Kevin 丢下的接力棒,将 NDoc 进行下去!

我的一个短期计划是:在十一假期之后的一个时间,发布第一个 Beta 2.0 版本,在这个版本中,预定的 features 大致如下:

1)文档引擎:暂时缩编为 XML 和 MSDN 两种,以便于集中精力。

2).NET 2.0 支持:希望实现对泛型类型和泛型方法的全面支持,对可空类型的支持等。

3)全球化:NDoc 现有的版本很少考虑全球化的问题,此次整理将梳理核心代码,将区域化文本提取到资源文件中。第一个发布版本可能只有英语,但已经 ready to localize。(如果进展顺利、时间许可的情况下,也可能在第一个发布版本中就同步发布简体中文资源。)

较远期的计划:

1)文档引擎方面:

将主要完善 XML 和 MSDN 两种文档引擎。其中,MSDN 文档引擎,将同时支持 .NET 1.x / 2.0 / Mono 三种类库文档样式;另一方面,同时支持网页,HTML Help 1.x (*.CHM)和 Microsoft Help 2 三种输出格式。

其他的文档引擎,将交给其他有兴趣的开发者维护。如果没有足够的关注,则可能会被废弃。

2)实现在 .NET 1.x / 2.0 / Mono 不同平台下的编译;

3)全球化支持,将包括两层含义:

一方面是程序界面的全球化,一方面是文档样式(比如那些静态文本等)的全球化。希望支持九种语言:en (英语), fr (法语), de (德语), it (意大利语), es (西班牙语), ko (朝鲜语), ja (日语), zh-CHS (简体中文), zh-CHT (繁体中文),分别对应 .NET Framework SDK 所支持的九种本地化版本。当然,这个愿望还需要其他国家/地区的开发者来完成。

4)其他需要完善的细节。(比如 NDoc 当前版本对数组的支持不够好,等等)

此项目在 SourceForge.net 站点注册,项目名定为 NDoc Reloaded (嗯,现在还在 Reloading,呵呵)。如果可以的话,请给我一些鼓励和耐心:

http://sourceforge.net/projects/ndoc-reloaded/

http://ndoc-reloaded.sourceforge.net/

 

由于换工作的原因,没能按原定计划完成第一个版本,请各位原谅!

posted @ | Feedback (20) |

这一篇记录一些学习 Atlas (不包含 Control Toolkit 的核心框架部分)碰到的问题点:

1)String.format 函数

Atlas 的客户