论道WP(三):应用程序栏
作者通过具体翔实的例子介绍了Winodws Phone 7中应用程序栏的使用方式。
该内容已经被标记书签!
标记书签错误,请重试!

作者 Edd Morgan 译者 王波 发布于 2010年9月21日
我曾在早期的博文中介绍过IronRuby。在文章中,我扩展了IronRuby的基础知识,来解释需要在Rail应用程序所做的额外工作,好让大家继续深入.NET所实现Ruby语言,但这方面的内容并不够。所以现在我想深入地谈谈IronRuby与项目的兼容性,以便开发全新的应用程序来说明IronRuby和.NET之间的互操作性。实际上,我们会使用WPF(Windows Presentation Foundation),它是.NET Framework的组件,我们可以用它创建富媒体和图形界面。
再次申明,WPF是.NET Framework组件之一,负责呈现富用户界面和其他媒体。它不是.NET Framework中唯一可完成该功能的函数库集,Window Form也可以完成类似工作,在我们需要创建炫目效果的时候,WPF会显得十分有用。无论是演示文档、视频、数据录入表格、某些类型的数据可视化(这是我最希望做的,尤其用IronRuby完成,后面的故事更精彩)抑或用动画把以上的都串联起来,你很可能会发现在给Windows开发这些应用程序的时候WPF可以满足你的需求。
举例说明。某一天午饭时间,我创建了基于WPF的类似于时钟的应用程序——我喜欢参考WPF的“Hello,Wold”应用程序——于是决定使用IronRuby。
注:学习本示例的过程中,需要参考WPF文档。
require 'WindowsBase'
require 'PresentationFramework'
require 'PresentationCore'
require 'System.Core, Version=3.5.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089'
class Clock
CLOCK_WIDTH = 150
CLOCK_HEIGHT = 150
LABEL_HEIGHT = CLOCK_HEIGHT / 7
LABEL_WIDTH = CLOCK_WIDTH / 7
RADIUS = CLOCK_WIDTH / 2
RADS = Math::PI / 180
MIN_LOCATIONS = {}
HOUR_LOCATIONS = {}
def run!
plot_locations
# build our window
@window = System::Windows::Window.new
@window.background = System::Windows::Media::Brushes.LightGray
@window.width = CLOCK_WIDTH * 2
@window.height = CLOCK_HEIGHT * 2
@window.resize_mode = System::Windows::ResizeMode.NoResize
@canvas = System::Windows::Controls::Canvas.new
@canvas.width = CLOCK_WIDTH
@canvas.height = CLOCK_HEIGHT
# create shapes to represent clock hands
@minute_hand = System::Windows::Shapes::Line.new
@minute_hand.stroke = System::Windows::Media::Brushes.Black
@minute_hand.stroke_thickness = 1
@minute_hand.x1 = CLOCK_WIDTH / 2
@minute_hand.y1 = CLOCK_HEIGHT / 2
@hour_hand = System::Windows::Shapes::Line.new
@hour_hand.stroke = System::Windows::Media::Brushes.Black
@hour_hand.stroke_thickness = 3
@hour_hand.x1 = CLOCK_WIDTH / 2
@hour_hand.y1 = CLOCK_HEIGHT / 2
# .. and stick them to our canvas
@canvas.children.add(@minute_hand)
@canvas.children.add(@hour_hand)
plot_face # draw a clock face
plot_labels # draw clock numbers
plot_hands # draw minute / hour hands
@window.content = @canvas
app = System::Windows::Application.new
app.run(@window)
# the Application object handles the lifecycle of our app
# including the execution loop
end
# determine 2 sets of equidistant points around the circumference of a circle
# of CLOCK_WIDTH and CLOCK_HEIGHT dimensions.
def plot_locations
for i in (0..60) # 60 minutes, and 12 hours
a = i * 6
x = (RADIUS * Math.sin(a * RADS)).to_i + (CLOCK_WIDTH / 2)
y = (CLOCK_HEIGHT / 2) - (RADIUS * Math.cos(a * RADS)).to_i
coords = [x, y]
HOUR_LOCATIONS[i / 5] = coords if i % 5 == 0 # is this also an 'hour' location (ie. every 5 minutes)?
MIN_LOCATIONS[i] = coords
end
end
# draws a circle to represent the clock's face
def plot_face
extra_x = (CLOCK_WIDTH * 0.15) # pad our circle a little
extra_y = (CLOCK_HEIGHT * 0.15)
face = System::Windows::Shapes::Ellipse.new
face.fill = System::Windows::Media::Brushes.White
face.width = CLOCK_WIDTH + extra_x
face.height = CLOCK_HEIGHT + extra_y
face.margin = System::Windows::Thickness.new(0 - (extra_x/2), 0 - (extra_y/2), 0, 0)
face.stroke = System::Windows::Media::Brushes.Gray # give it a slight border
face.stroke_thickness = 1
System::Windows::Controls::Canvas.set_z_index(face, -1) # send our circle to the back
@canvas.children.add(face) # add the clock face to our canvas
end
# at each point along the hour locations, put a number
def plot_labels
HOUR_LOCATIONS.each_pair do |p, coords|
unless p == 0
lbl = System::Windows::Controls::Label.new
lbl.horizontal_content_alignment = System::Windows::HorizontalAlignment.Center
lbl.width = LABEL_WIDTH
lbl.height = LABEL_HEIGHT
lbl.content = p.to_s
lbl.margin = System::Windows::Thickness.new(coords[0] - (LABEL_WIDTH / 2), coords[1] - (LABEL_HEIGHT / 2), 0, 0)
lbl.padding = System::Windows::Thickness.new(0, 0, 0, 0)
@canvas.children.add(lbl)
end
end
end
def plot_hands
time = Time.now
hours = time.hour
minutes = time.min
if !@minutes || minutes != @minutes
@hours = hours >= 12 ? hours - 12 : hours
@minutes = minutes == 0 ? 60 : minutes
# Dispatcher.BeginInvoke() is asynchronous, though it probably doesn't matter too much here
@minute_hand.dispatcher.begin_invoke(System::Windows::Threading::DispatcherPriority.Render, System::Action.new {
@minute_hand.x2 = MIN_LOCATIONS[@minutes][0]
@minute_hand.y2 = MIN_LOCATIONS[@minutes][1]
@hour_hand.x2 = HOUR_LOCATIONS[@hours][0]
@hour_hand.y2 = HOUR_LOCATIONS[@hours][1]
})
end
end
end
clock = Clock.new
timer = System::Timers::Timer.new
timer.interval = 1000
timer.elapsed { clock.plot_hands }
timer.enabled = true
clock.run!
查看GitHub站点的实例效果
世上没有完美的事物,但我认为本实例使用数据可视化来说明IronRuby与WPF间的互操作。我相信你会细心研究以上代码,但我仍要逐步解析它的关键之处。(顺便提一下,通过ir来运行本实例可第一时间看到效果)。
现在,我们使用的是IronRuby,并非我之前提到的那样纯使用Ruby代码并用ir(IronRuby解析器)运行代码来以证明它的兼容性。本文的主旨在于说明.NET命名空间和Ruby模块,.NET类和Ruby类之间的明显相似性。在这方面我觉得无需多说,你也许已经能够熟练地应用Ruby的绘图函数。
以上例子中,我们实例化.NET对象,但使用的是标准的Ruby对象的.new方法,即Object#new。我们调用这些对象(和类)的方法(例如,对System.Windows.Controls.Canvas.SetZIndex()调用)可为Ruby语言建立相应的小写规则。无缝集成让我们可在.NET CLR之上运行动态语言(公共语言运行时需要动态语言运行时来支持动态语言)。这对于我们来说是完全抽象的,仅用于创建软件。
注:使用IronRuby的时候,.NET堆栈确实在各级别上集成。有一个地方要注意的是所有的IronRuby对象并非真正意义上的Object而是System.Object。
事件是开发.NET客户端应用程序的重要一环,在其它开发环境下也同样如此。万一你没有注意到这一点,事件驱动编程实质上也需要在不可预知的情况下调用方法或者其它代码块(比如:委托)。你永远无法预测用户什么时候点击按钮,敲击按键或者执行任何输入,所以事件驱动编程必须处理GUI事件。
我最喜欢Ruby语言的原因之一就在于它的“blocks”确实能够帮助我们。例如在传统的C#语言中,你需要通过以下一种或两种方式来订阅事件(即在事件发生时执行所分配的代码块):把引用传递给指定的方法,或者提供匿名代码块。你正好可以看到Ruby中的类似概念“block”“Proc”和“lambda”。最后在相对简单的代码中说明这些概念,我们会使用.NET的System.Timers.Timer来尝试每秒钟更新该时钟(我知道这并非最佳做法,仅用于示范)
注:和我之前说的稍有不同,时钟的运行是可预期的,然而我们仍使用Timer事件进行更新,这是在主线程之外完成任务的众多方式的一种。
接下来,你会看到为处理事件所需编写的代码仅是向CLR提供处理事件的函数名。这种方式的缺点在于它对每个事件仅允许委托一个代码块。我们需要使用add方法让该事件订阅多个处理程序,即把处理函数放到队列的末端。如下所示:
def tick
puts "tick tock"
end
timer.elapsed.add method(:tick)
timer.elapsed.add proc { puts "tick tock" }
tick_handler = lambda { puts "tick tock" }
timer.elapsed.add(tick_handler)
创建代码块作为事件处理程序的能力使得IronRuby向优秀的动态语言又迈进了一步。小写规范减少了样板代码的数量。当然,匿名方法在其它传统的.NET语言——像C#和VB——中也可用,但是在IronRuby则让人感觉更加优雅和自然。
注:无论方法是已命名还是匿名,处理事件的委托代码都可以接收参数,一般来说,参数会包括一个sender 对象和一些args。
XAML是微软用于定义CLR对象及其属性的类XML语言,主要在WPF和Silverlight应用程序中使用。有了它,我们可以用描述的方式来创建整个UI,在程序性代码中关联事件并在运行时绑定数据、创建图形、甚至为那些图形创建具有故事情节的动画。我不准备深入探讨XAML的架构,如果你有任何使用基于XML语言的经验的话,你就会了解其中发生的事情。
<Window xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation" xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml">
<StackPanel>
<Rectangle x:Name="mySquare" Width="50" Height="50">
<Rectangle.Fill>
<SolidColorBrush Color="Green" />
</Rectangle.Fill>
</Rectangle>
<TextBlock Text="Hello, world">
<TextBlock.Foreground>
<SolidColorBrush Color="Red" />
</TextBlock.Foreground>
</TextBlock>
</StackPanel>
</Window>
注:Window、StackPanel、TextBlock、SolidColorBrush和Rectangle 都是WPF类。XAML代码可以轻松地用C#、VB或者IronRuby编程实现。
以上代码会显示一个中等尺寸的独立窗体。该窗体中有StackPanel对象,它是WPF控件,用于定义其子控件采取流布局样式。在StackPanel中有两个不同对象:一个文本框和一个矩形。在XAML定义的对象皆可被命名以供后续引用亦可匿名(我们的Rectangle对象就命名为mySquare,尽管TextBlock未被命名)。这些对象的属性可以通过两种方式进行赋值:利用XML元素属性(例如:Width="50"),或者所期望的值非初级类型它们的子元素(例如:预期<Rectangle.Fill> 为Brush或者派生自Brush)。
不要陷入WPF和XAML的谜团当中,因为任何人都可以轻松地编写大量代码,让我们用IronRuby运行这些代码。
require 'PresentationFramework'
require 'PresentationCore'
@window = System::Windows::Markup::XamlReader.parse(File.open('my_xaml.xaml', 'r').read)
System::Windows::Application.new.run(@window)
WPF方法Application.Run需要Window作为其中一个参数。如果我们回头看之前的XAML代码,就会发现根元素其实就是Window,那也是语法分析后所返回的对象。所有在XAML中定义的控件都会作为反射XAML文档结构的控件树返回,Window是根元素,StackPanel作为Window的唯一子元素,Rectangle和TextBlock则作为StackPanel的子元素等等。我们可以通过以下方式添加控件:
@window.find_name("mySquare").class # => "System::Windows::Shapes::Rectangle"
我们提到兼容性、互操作性却忽略了可扩展性。我已经清楚解释了IronRuby与.NET间如何无缝继承,甚至你可以用继承来扩展CLR类。以下是一个示例,让我们再来看一看之前写的文章中用C#创建的Person类。
namespace MyClassLibrary
{
public class Person
{
public string Name { get; set; }
public string Introduce()
{
return String.Format("Hi, I'm {0}", Name);
}
}
}
让我们用Ruby来扩展它,并借此培养程序员的思维习惯。
require 'MyClassLibrary.dll'
class Programmer < MyClassLibrary::Person
ACCEPTABLE_DRINKS = [:coffee, :tea, :cola, :red_bull]
def drink(liquid)
if ACCEPTABLE_DRINKS.include? liquid
puts "Mmm... #{name} likes code juice!"
else
raise "Need caffeine!"
end
end
end
me = Programmer.new
me.name = "Edd"
puts me.introduce
me.drink(:coffee)
老实说,我不介意使用繁琐的代码引用,只要它不影响程序的性能即可,就像在之前的代码显示的那样。我喜欢简洁的代码,冗长的寻址和对象描述的简化会产生某种安全感,尽管这和本句形成了鲜明的对比。然而,在使用IronRuby的时候,我已厌倦输入System::Whatever::Something。不管使用何种语言,总有一些开发人员喜欢设定命名空间并忘掉它们。不用担心,IronRuby也有这种人。
由于.NET命名空间在IronRuby中是模块,所以在调用include后,完全可以把.NET命名空间引入IronRuby代码,就像要引入一个Ruby组织模块一样。
class Clock
include System::Windows::Shapes
include System::Windows::Media
include System::Windows::Threading
# and so on...
这样做可以减少调用 System::Windows::Shapes::Ellipse.new,代之以Ellipse.new,或通过System::Windows::Threading::DispatcherPriority.Render引用DispatcherPriority.Render。
在.NET Framework中,另一个简化IronRuby代码以及处理这些冗长代码的方法就是通过给命名空间取别名来完成。
require 'System.Windows.Forms' WinForms = System::Windows::Forms WinForms::Form.new WinForms::Label.new
到此为止,我希望你能更好的了解IronRuby与.NET间的互操作,以及如何利用.NET Framework的动态属性和Ruby的优雅语法。
Ruby的风格和用法让数据处理变成一种乐趣,当然,在IronRuby也一样,它结合了WPF生成图像的功能。我希望大家能具体看到使用这两种技术进行数据可视化的可能性。使用IronRuby来创建数据和信息图的视觉描述是多么的振奋人心。尽管在这个项目中,我们仅展现了一些简单的信息——时间——但潜在的可能性是巨大的。
感谢侯伯薇对本文的审校。
给InfoQ中文站投稿或者参与内容翻译工作,请邮件至editors@cn.infoq.com。也欢迎大家加入到InfoQ中文站用户讨论组中与我们的编辑和其他读者朋友交流。
在多线程并发编程中Synchronized一直是元老级角色,很多人都会称呼它为重量级锁,但是随着Java SE1.6对Synchronized进行了各种优化之后,有些情况下它并不那么重了,本文详细介绍了Java SE1.6中对于锁的性能优化,以及锁的存储结构及升级过程。
本次分享将首先介绍现代富文本编辑器的组成和实现,然后结合UEditor的开发过程,与参会者分享UEditor在设计和实现的过程中,所涉及到的核心功能的细节实现。
本次演讲视频录制于百度技术沙龙。
我们所开发的应用程序大多都需要提供一个图形用户界面(GUI)。关于GUI应用的架构设计,已经有了Form & Control、MVC,、MVP、 Passive View等多种模式。模式可以帮助我们建立优雅的架构,但前提是弄清楚模式的应用场景。弄清楚GUI应用面临的设计上的问题,有助于我们正确的挑选设计方案。
MongoDB是一种非常易用的NoSQL方案,Brian C. Dilley在这篇文章里介绍了MongoDB的优劣势,并介绍了MJORM项目。MJORM用于MongoDB,是一个没有注解的Java ORM库。
随着网络基础设施的逐步成熟,从RPC进化到Web Service,并在业界开始普遍推行SOA,再到后来的RESTful平台以及云计算中的PaaS与SaaS概念的推广,分布式架构在企业应用中开始呈现出不同的风貌,然而殊途同归,这些分布式架构的目标仍然是希望回到建造巴别塔的时代,系统之间的交流不再为不同语言与平台的隔阂而产生障碍。
精益软件开发方法因其对市场和交付的重视和在各种场景下体现出的适应能力正在获得广泛的关注。特别是在精益创业(Lean Startup)渐渐兴起和技术日新月异的今天,其"极端"的思想也变得越来越必要和可行。 InfoQ就此主题对他做了深入的采访。
没有回复
关注此讨论 回复