WPF 和 Silverlight 支持 Style 机制,它允许我们把控件的属性值封装成可重用的资源。我们可以把这些样式声明保存在独立于页面的其他文件中(当然本文件也可以),然后就可以在一个应用程序中跨控件和页面重用(甚至跨多个应用程序重用)。在做一些基本定制的场景下,概念上类似于在 HTML 中重用 CSS。
一个样式的简单例子
我们给一个园定义一个名叫 EllipseStyle1 的样式,然后应用这个样式。
Xaml 文件
<UserControl
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
x:Class="SilverlightApplication_Style.MainPage"
Width="300" Height="300">
<UserControl.Resources>
<Style x:Key="EllipseStyle1" TargetType="Ellipse">
<Setter Property="Fill">
<Setter.Value>
<LinearGradientBrush EndPoint="0.9,1" StartPoint="0.1,0.1">
<GradientStop Color="#FF42B34A" Offset="0"/>
<GradientStop Color="#FFC03535" Offset="1"/>
</LinearGradientBrush>
</Setter.Value>
</Setter>
<Setter Property="Margin" Value="10,0,0,0"/>
<Setter Property="Cursor" Value="Hand"/>
<Setter Property="StrokeThickness" Value="20"/>
<Setter Property="VerticalAlignment" Value="Center"/>
<Setter Property="HorizontalAlignment" Value="Center"/>
</Style>
</UserControl.Resources>
<Grid x:Name="LayoutRoot" Background="White">
<Ellipse Height="100" Width="100" Style="{StaticResource EllipseStyle1}" />
</Grid>
</UserControl>
效果图:
这里简单期间把样式跟控件放在一个文件中了, 实际我们还可以放在另外一个文件。
使用 Blend 设计样式时,我们会看得更清楚,
使用菜单在 对象 –> 编辑样式 –> 创建空项 打开创建 Style 资源时,我们就可以看到我们可以在多个地方保存样式:
之后在 Blend 中编辑时,可以在资源中选择对应的样式名字,然后如下图所示进行编辑:
编辑可以在属性窗口修改任何想修改的内容。
在编辑完毕后,在对象和时间线中,点击
按钮就可以回到正常状态。
参考资料:
Silverlight教程第四部分:使用 Style 元素更好地封装观感
http://blog.joycode.com/scottgu/archive/2008/02/29/114915.joy
一步一步学Silverlight 2系列(8):使用样式封装控件观感
http://www.cnblogs.com/Terrylee/archive/2008/03/08/Silverlight2-step-by-step-part8-styles.html
解说:Silverlight Style,Setter(XAML/Design)换肤机制
http://blog.csdn.net/bobby96333/archive/2008/10/22/3126363.aspx
Silverlight2 边学边练 之六 设定风格
http://www.cnblogs.com/gnielee/archive/2009/08/02/1537122.html
Silverlight 2 : 关于ListBox的一个Layout Bug及其解决方法
http://www.cnblogs.com/sun/archive/2009/03/04/1402648.html
Silverlight项目中"自定义控件开发/Style"学习笔记
http://www.cnblogs.com/yjmyzz/archive/2009/11/11/1600791.html
Quick Silverlight Tip: Define control style dynamically
http://blogs.microsoft.co.il/blogs/alex_golesh/archive/2008/09/09/quick-silverlight-tip-define-control-style-dynamically.aspx
SQL Server 的 ltrim 和 rtrim 函数只会取消 char(32) 的字符, char(9) 之类的字符则不会剔除。今天在处理论坛的一个bug时,发现数据多了空格,就是char(9)在作怪。
一个演示SQL代码:
declare @s nvarchar(50)
select @s = char(32)+char(9)+'*'
print('%'+@s+'%')
select @s = ltrim(rtrim(@s))
print('%'+@s+'%')
print (str(len(@s)))
而 C# 中则没有类似问题,它剔除了很多非 char(32) 的空格。
演示代码:
static void Main(string[] args)
{
string s = string.Format("{0}{1}*", (char)32, (char)9);
Console.WriteLine("%" + s + "%");
Console.WriteLine(s.Length);
s = s.Trim();
Console.WriteLine("%" + s + "%");
Console.WriteLine(s.Length);
Console.ReadLine();
}
MSDN上一些空格的资料:
下表列出了被 Trim 方法移除的空白字符。第一列列出了字符的 Unicode 名称,第二列列出了标识该字符的 Unicode 码位的十六进制表示法。
(请注意,尽管传递特定字符时静态 Char.IsWhiteSpace(Char) 方法返回了 true,但该字符不一定被 Trim 方法移除。(作者注:MSDN上这句话很让我困惑,下面2个表中,Char.IsWhiteSpace中的空格都出现在Trim 中的空格了呀?))
| Unicode 名称 |
Unicode 码位 |
| CHARACTER TABULATION |
U+0009 |
| LINE FEED |
U+000A |
| LINE TABULATION |
U+000B |
| FORM FEED |
U+000C |
| CARRIAGE RETURN |
U+000D |
| SPACE |
U+0020 |
| NEXT LINE |
U+0085 |
| NO-BREAK SPACE |
U+00A0 |
| OGHAM SPACE MARK |
U+1680 |
| EN QUAD |
U+2000 |
| EM QUAD |
U+2001 |
| EN SPACE |
U+2002 |
| EM SPACE |
U+2003 |
| THREE-PER-EM SPACE |
U+2004 |
| FOUR-PER-EM SPACE |
U+2005 |
| SIX-PER-EM SPACE |
U+2006 |
| FIGURE SPACE |
U+2007 |
| PUNCTUATION SPACE |
U+2008 |
| THIN SPACE |
U+2009 |
| HAIR SPACE |
U+200A |
| ZERO WIDTH SPACE |
U+200B |
| LINE SEPARATOR |
U+2028 |
| PARAGRAPH SEPARATOR |
U+2029 |
| IDEOGRAPHIC SPACE |
U+3000 |
| ZERO WIDTH NO-BREAK SPACE |
U+FEFF |
会被 Char.IsWhiteSpace( 认为是空白字符的包括以下Unicode 字符:
- SpaceSeparator 类别的成员,该类别包括 SPACE 字符 (U+0020)。
- LineSeparator 类别的成员,该类别只包括 LINE SEPARATOR 字符 (U+2028)。
- ParagraphSeparator 类别的成员,该类别只包括 PARAGRAPH SEPARATOR 字符 (U+2029)。
- 字符 CHARACTER TABULATION (U+0009)、LINE FEED (U+000A)、LINE TABULATION (U+000B)、FORM FEED (U+000C)、CARRIAGE RETURN (U+000D)、NEXT LINE (U+0085) 和 NO-BREAK SPACE (U+0000A0)。
我们以前要实现朗读一段文本,需要如何做呢?
首先在项目中引用 Microsoft Speech Object Library 。 如下Com组件:
然后是一堆复杂的代码。这部分的实现可以采看以下几篇文章:
.Net平台下开发中文语音应用程序
http://www.microsoft.com/china/community/program/originalarticles/TechDoc/Cnspeech.mspx
C#中实现语音朗读(短信 & 语音短信)sms
http://www.cnblogs.com/joyyuan97/archive/2009/02/26/1398716.html
当然 由于 .Net Framework 3.0 中增加了一个命名空间:System.Speech。 实际上从 .Net Framework 3.0 开始,我们要实现语音朗读要简单多了,具体如何实现可以参看我之前的博客:用.net 编码实现朗读文本的方法。 但是我们操作其他微软没有封装的 Com 组件,仍然跟之前一样超级复杂,而且需要相当的技巧。
在 .Net 4.0 中,我们只需要三行代码就可以实现上面的功能,而且项目不需要引用 Microsoft Speech Object Library 。超简单,这是利用了 C# 4.0 的动态查找新特性, 即 dynamic 类型。有关 dynamic 的基础知识,请参看我前篇博客: C# 4.0 特性: dynamic 和 ExpandoObject 。
C# 4.0 的项目不需要引用任何其他组件,只需要下面简单的几行代码,完成阅读文字的功能。
using System;
namespace ConsoleApplication1
{
class Program
{
static void Main(string[] args)
{
Type type = Type.GetTypeFromProgID("SAPI.SpVoice");
dynamic spVoice = Activator.CreateInstance(type);
spVoice.Speak("你好,郭红俊,欢迎使用 CSharp 4.0!");
}
}
}
这个例子引用了 Speech API 中的 SAPI.SpVoice 对象,并调用了其 Speak() 方法。
Type.GetTypeFromProgID 用于获取与指定程序标识符 (ProgID) 关联的类型。
ProgID 是程序员给某个CLSID指定一个易记的名字。在注册表的 HKey_Classes_Root 下,有版本号的ProgID和无版本号的ProgID都会列出,一般无版本号的除了有CLSID子键外会还有CurVer子键来标明版本。
Type.GetTypeFromProgID 是 .NET Framework 1.0 就具有的功能, 但为何到了 .NET Framework 4.0 我们才可以写出这么简单的代码, 这关键就是 dynamic 类型的引用。
任何直接声明为 dynamic 类型的变量,或者从函数中返回 dynamic 类型的值,都将自动地视为后期绑定。即在运行时才会被初始化绑定。 由于这个原因,我们才会用这么简单的代码实现对Com的调用。
参考资料:
C# 4.0 特性: dynamic 和 ExpandoObject
http://blog.joycode.com/ghj/archive/2009/12/29/115832.joy
ProgID是什么
http://kb.cnblogs.com/a/1218548/
C# 4.0引入了一个新类型:dynamic,简单来说,任何直接声明为这种类型的变量,或者从函数中返回这种类型的值,都将自动地视为后期绑定。即在运行时才会被初始化绑定。这类似于在Visual Basic中把变量声明为“object”,不过它现在可以支持任何类型系统了,不仅仅是CTS(通用类型规范)和COM。
C# 4.0 的主打特性就是动态类型。也就在这里体现。
在 New features in CSharp 4.docx 中提到 dymanic主要应用于下面的场景也是这样:
- 自动反射
- COM组件互操作
- 混合编程,例如IronRuby和IronPython
- 处理Html DOM对象
一个简单例子:
static void Main(string[] args)
{
dynamic person = new System.Dynamic.ExpandoObject();
person.Name = "cary";
person.Age = 25;
person.ShowDescription = new Func<string>(() => person.Name + person.Age);
Console.WriteLine(person.Name + person.Age + person.ShowDescription());
Console.ReadLine();
}
这里的 System.Dynamic.ExpandoObject 是个特殊的对象,简单地说它的行为可以被“扩展”——是如动态语言般真正的扩展,而非静态的多态。当我们使用dynamic 饰变量后,在它之上的方法调用会由编译器和DLR配合出不一样的行为。例如,我们在调用一个方法的时候,DLR会先检查这个动态对象上是否存在符合这个签名的方法,存在则最好,否则便会调用TryInvokeMember来“执行”一个动态方法,而它的参数便是此次调用的全部信息。这样的做法被称为“Method Missing”操作。
DLR 和 CLR 以及编程语言的关系看下图:
注意点:
使用 Dynamic 将导致只能在运行时才能被测试。这就是为什么当你用dynamic类型时,单元测试在你的代码中变得很重要,很重要!!
参考资料:
C#4.0新特性:可选参数,命名参数,Dynamic
http://www.cnblogs.com/carysun/archive/2009/12/09/CSharp4.html
翻译:Visual C# 4.0的新特性-第三部分-Dynamic ExpendoObject
http://www.cnblogs.com/codeyu/archive/2009/12/08/1616839.html
C# 4.0中dynamic的用法
http://space.itpub.net/740297/viewspace-600670
介绍C# 4.0新特性dynamic
http://developer.51cto.com/art/200908/144976.htm
二十行C#代码打造Ruby Markup Builder
http://www.cnblogs.com/JeffreyZhao/archive/2009/10/27/implement-ruby-markup-builder-in-20-lines-of-c-sharp-codes.html
Dynamic in C# 4.0: Introducing the ExpandoObject
http://blogs.msdn.com/csharpfaq/archive/2009/10/01/dynamic-in-c-4-0-introducing-the-expandoobject.aspx
C#4.0的dynamic用法(一)——巧用反射
http://www.cnblogs.com/architect/archive/2009/03/13/1410921.html
C#动静结合编程之三:Duck Typing
http://www.cnblogs.com/weidagang2046/archive/2009/03/26/1421943.html
也谈.NET反射的封装
http://www.cnblogs.com/weidagang2046/archive/2009/04/30/1446928.html
C#动态特性的更多消息
http://www.infoq.com/cn/news/2009/04/Dynamic-CSharp
Fun With Method Missing and C# 4
http://haacked.com/archive/2009/08/26/method-missing-csharp-4.aspx
我们先来看一段超简单的使用了这个特性的代码例子:
public static void ShowMessage(string msg = "")
{
Console.WriteLine("Hello {0}", msg);
}
static void Main(string[] args)
{
// 使用默认的 msg = ""
ShowMessage();
// 使用指定的参数值
ShowMessage("ghj1976");
// 指定参数值的又一种写法
ShowMessage(msg: "郭红俊");
Console.ReadLine();
}
为什么需要开放命名参数和可选参数呢?
- 这是出于动态语言运行时兼容性的要求。动态语言中存在动态绑定的参数列表,有时候并不是所有的参数值都需要指定(有些语言可能没有重载决策);
- 另外,在一些 COM 互操作时,往往 COM Invoke 的方法参数列表非常的长(例如 ExcelApplication.Save,可能需要 12 个参数),但 COM 暴露的参数的实际值往往为 null,只有很少一部分参数需要指定植,如 ExcelApplication.Save(),可能不需要指定任何参数值,或者仅仅一个值,例如 fileType。为了精简书写的代码,就有着这个特性。
一些注意点:
上面 ShowMessage 函数写法的可选参数(Optional Parameters)必须放在最后,否则将引发 "Optional parameters must appear after all required parameters" 编译错误。
比如这样的写法就是不允许的:
private static void Test2(int x = 0x7b, double y = 45.3, string w)
{
Console.WriteLine("{0},{1},{2}", s, x, y);
}
但是换种写法就可以了:
如下:
private static void Test(
[System.Runtime.InteropServices.Optional, System.Runtime.InteropServices.DefaultParameterValue(0x7b)] int x,
[System.Runtime.InteropServices.Optional, System.Runtime.InteropServices.DefaultParameterValue(45.3)] double y,
string s
)
{
Console.WriteLine("{0},{1},{2}", s, x, y);
}
最初的这种写法在编译时,就是编译生成了 System.Runtime.InteropServices.Optional, System.Runtime.InteropServices.DefaultParameterValue 这样的参数属性。
Optional Parameters 的实质就是:
编译器为可选参数增加了 OptionalAttribute 和 DefaultParameterValueAttribute 特性,以便于引用编译和反射调用时能获取默认值。
至于不用 OptionalAttribute 和 DefaultParameterValueAttribute 特性的写法只不过是种语法糖而已。
当你的方法有多个同一类型的可选参数(optional parameters)时,命名参数(Named parameters)特别有用。如果不用命名参数,编译器就不知道传递的是哪个参数。
仍然是上面的 private static void Test 函数,我们调用时, 就必须这么写:
Test(s: "蝈蝈俊.net");
否则如果我们写成 Test("蝈蝈俊.net"); 就会报错误: No overload for method 'Test' takes 1 arguments
Named Parameters 的实质就是:
命名参数,无非是变相告知参数键值而已,最终编译结果还是按照原有的规则和顺序生成方法调用。这只不过是中语法糖而已。
参考资料:
C# 4.0 新特性 -- 命名参数和可选参数
http://blog.csdn.net/hustorochi/archive/2009/03/20/4007260.aspx
C#特性聚焦:可选和命名参数、COM互操作性
http://www.infoq.com/cn/news/2008/11/CSharp-Optional
C#4.0新特性:可选参数,命名参数,Dynamic
http://www.cnblogs.com/carysun/archive/2009/12/09/CSharp4.html
C# 4.0 - Named and Optional Parameters - Behind the Scenes
http://codebetter.com/blogs/matthew.podwysocki/archive/2008/10/29/c-4-0-named-and-optional-parameters-behind-the-scenes.aspx
[C# 4.0] 4. Named & Optional Parameters
http://www.rainsts.net/article.asp?id=879
C# 4.0 Optional Parameters 和Named Parameters
http://blog.csdn.net/shanyou/archive/2009/10/27/4735970.aspx
Silverlight数据绑定有三种模式(没有WPF的 OneWayToSource 模式),如下:
- OneTime:一次绑定,在绑定创建时使用源数据更新目标,适用于只显示数据而不进行数据的更新。 这是绑定的默认情况。
- OneWay:单向绑定,在绑定创建时或者源数据发生变化时更新到目标,适用于显示变化的数据。
- TwoWay:双向绑定,在任何时候都可以同时更新源数据和目标。
下面是这三种的代码演示:
OneTime 绑定
这种数据绑定模式,只是显示数据而不对数据做任何修改,默认的绑定模式就是OneTime绑定。
绑定到一个实体的一个属性:
Xaml 文件
<TextBlock x:Name="tb_1" Text="{Binding I}" Margin="100,50,100,200" />
或者可以稍稍复杂的写法:
<TextBlock x:Name="tb_1" Text="{Binding Path=I}" Margin="100,50,100,200" />
CS 代码文件
using System;
using System.Collections.Generic;
using System.Linq;
using System.Net;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Documents;
using System.Windows.Input;
using System.Windows.Media;
using System.Windows.Media.Animation;
using System.Windows.Shapes;
namespace SilverlightApplication_Binding
{
public partial class MainPage : UserControl
{
public MainPage()
{
InitializeComponent();
}
private void UserControl_Loaded(object sender, RoutedEventArgs e)
{
BindingDemoObject o = new BindingDemoObject();
o.I = 15;
tb_1.DataContext = o;
}
}
public class BindingDemoObject
{
public int I {get;set;}
}
}
如果要绑定到一个实体,则演示代码如下:
<TextBlock x:Name="tb_1" Text="{Binding}" Margin="100,50,100,200" />
private void UserControl_Loaded(object sender, RoutedEventArgs e)
{
int w = 30;
tb_1.DataContext = w;
// 当然也可以写成:
// tb_1.DataContext = 30;
}
OneWay 绑定
如果需要在数据源发生变化时能够通知UI进行相应的更新,则需要使用单向绑定OneWay或者双向绑定TwoWay。
若要支持 OneWay 或 TwoWay 绑定,从而使绑定目标属性能够自动反映绑定源的动态更改(例如,用户编辑窗体后,预览窗格会自动更新),类需要提供相应的属性更改通知。即创建实现 INotifyPropertyChanged 的类。
下面要演示的例子是当点击按钮时,实体的一个属性发生变化,继而前台显示也发生变化。
Xaml 文件
<Grid x:Name="LayoutRoot" Background="White">
<TextBlock x:Name="tb_1" Text="{Binding I, Mode=OneWay}" Margin="100,50,100,200" />
<Button Content="变化" Margin="222,112,0,0" Name="button1" Width="75" Click="button1_Click" />
</Grid>
CS 代码文件
using System;
using System.Collections.Generic;
using System.Linq;
using System.Net;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Documents;
using System.Windows.Input;
using System.Windows.Media;
using System.Windows.Media.Animation;
using System.Windows.Shapes;
namespace SilverlightApplication_Binding
{
public partial class MainPage : UserControl
{
public MainPage()
{
InitializeComponent();
}
BindingDemoObject o = new BindingDemoObject();
private void UserControl_Loaded(object sender, RoutedEventArgs e)
{
o.I = 15;
tb_1.DataContext = o;
}
private void button1_Click(object sender, RoutedEventArgs e)
{
o.I = 58;
}
}
public class BindingDemoObject : System.ComponentModel.INotifyPropertyChanged
{
private int _i = 0;
public int I
{
get
{
return _i;
}
set
{
_i = value;
OnPropertyChanged("I");
}
}
/// <summary>
/// Declare the event
/// </summary>
public event System.ComponentModel.PropertyChangedEventHandler PropertyChanged;
/// <summary>
/// Create the OnPropertyChanged method to raise the event
/// </summary>
/// <param name="name"></param>
protected void OnPropertyChanged(string name)
{
System.ComponentModel.PropertyChangedEventHandler handler = PropertyChanged;
if (handler != null)
{
handler(this, new System.ComponentModel.PropertyChangedEventArgs(name));
}
}
}
}
TwoWay 绑定
网上很多例子没有 Twoway的演示,下面就是一个 Twoway的演示。
Xaml 文件
<UserControl
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008" xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
x:Class="SilverlightApplication_TwoWayBinding.MainPage"
Width="400" Height="200" mc:Ignorable="d">
<Grid x:Name="LayoutRoot" Background="White">
<Ellipse Fill="#FF2F70C0" Stroke="Black" Height="57" HorizontalAlignment="Left" Margin="52,28,0,0" VerticalAlignment="Top" Width="{Binding Value, ElementName=silderSelf, Mode=OneWay}" />
<Slider x:Name="silderSelf" Height="30" Margin="52,103,48,0" VerticalAlignment="Top" Maximum="300" Minimum="10" LargeChange="10" SmallChange="1" Value="100" Width="300"/>
<TextBox Height="30" HorizontalAlignment="Left" Margin="52,150,0,0" VerticalAlignment="Top" Width="150" Text="{Binding Value, ElementName=silderSelf, Mode=TwoWay}" TextWrapping="Wrap"/>
</Grid>
</UserControl>
演示效果:
解释:
这里我们把椭圆的宽度和 TextBox 的值捆绑到 Slider 的值。
对椭圆的捆绑是 OneWay 模式, 表示只是读取Slider 的值。
TextBox 的捆绑则是 TwoWay, 表示我们即可以接受 Slider 的值,也可以设置 Slider 的值。 当 TextBox 中输入了值,按 Tab 离开焦点后,我们就可以看到 Slider 和 椭圆的值都发生变化了。
当我们移动 Slider 时, TextBox 的值和 椭圆的宽度都发生变化。
参考资料:
一步一步学Silverlight 2系列(11):数据绑定
http://www.cnblogs.com/Terrylee/archive/2008/03/08/Silverlight2-step-by-step-part11-Data-Binding.html
如何在Blend设置数据绑定
http://msdn.microsoft.com/zh-cn/library/cc295161(Expression.30).aspx
silverlight数据绑定模式TwoWay,OneWay,OneTime的研究
http://www.cnblogs.com/yjmyzz/archive/2009/11/09/1599058.html
OAuth 协议是现在众多网站提供API服务所选择的认证方式,是由Blaine Cook、Chris Messina、Larry Halff 及David Recordon共同发起,目的是为API服务提供一个安全、统一和开放的标准。
官方网站对 OAuth 的一句话介绍是:
An open protocol to allow secure API authentication in a simple and standard method from desktop and web applications.
大概意思是说 OAuth 是一种开放的协议,为桌面程序或者基于BS的web应用提供了一种简单的,标准的方式去访问需要用户授权的API服务。OAUTH认证授权具有以下特点:
1. 简单:不管是 OAuth 服务提供者还是应用开发者,都很容易于理解与使用;
2. 安全:没有涉及到用户密钥等信息,更安全更灵活;
3. 开放:任何服务提供商都可以实现 OAuth ,任何软件开发商都可以使用 OAuth;
OAUTH认证授权流程:
下面这个图就是演示了认证流程。这个图来自: http://wiki.opensocial.org/index.php?title=OAuth_Use_Cases
上述5个步骤解释如下:
- A user wishes to use their social network data inside of a third party website or application. The application server contacts the social network, but does not have the user's account information, and does not have permission to access the user's data.
用户希望在第三网站和应用上使用他在SNS网站上的用户信息,这些第三方网站联系SNS网站,但是由于没有用户认证信息,这时这些用户信息是不允许访问的。
更技术点说法:
第三方应用程序向SNS网站授权服务发出获取request token的请求。
- The social network responds with information that the application server uses to redirect the user's web browser to a special login page on the social network's domain.
SNS网站把用户的浏览器重定向到SNS网站的登录页面。
更技术点说法:
SNS授权服务响应请求,返回一个尚未认证的request token。
第三方应用获取响应中包含的request token,按照协议规范,附带这个request token,将其重定向到SNS提供的授权页面(User Authorization URL)。
- If the user is logged out of the social network, they input their username and password as if they were normally logging into the site. After they log in, or if they were already logged in (with a cookie), they are asked by the social network to share data with the application.
如果用户没有登录,用户向普通登录一样,输入用户名和密码完成登录。如果用户已经登录(使用记录Cookie的方式),会出现一个页面,问用户是否允许共享他的SNS信息给第三方网站。
更技术点说法:
在Google提供的授权页面上,用户按照提示输入用户账号信息。
- Once permission has been granted, the social network redirects the user's web browser to a predefined URL on the application server, along with a token that can be used to access the user's information.
一旦用户选择信任第三方网站,SNS网站将把Web浏览器重定向到第三方网站,同时把SNS的用户信息传递过去。
更技术点说法:
用户决定允许或拒绝授权给第三方应用,如果用户拒绝授权给此第三方应用,则被重定向到SNS的页面,而不会再回到第三方应用的页面上。
如果用户授权给第三方应用,那么,SNS授权服务接收此请求,将用户重定向到第三方应用提供的页面上,并传递被认证了的request token。
- Using the token as described in the OAuth specification, the application server is now able to access the user's data on the social network.
这样第三方网站就可以访问SNS网站的用户信息了。
更技术点说法:
第三方应用接收到认证的request token后,再次向SNS账号服务发起一次HTTP请求,以换取access token。
SNS 账户授权服务接收请求,验证是否合法。如果合法,则返回一个access token。
从上面的步骤可以看出,用户始终没有将其用户名与密码等信息提供给使用者(第三方软件),从而更安全。
OAUTH相关术语
明白了流程,再看技术术语就会简单很多。在上面流程中,在更技术说法中提到了一些术语,这些OAuth的术语解释如下:
OAUTH相关的三个URL:
- Request Token URL: 获取未授权的Request Token服务地址;
- User Authorization URL: 获取用户授权的Request Token服务地址;
- Access Token URL: 用授权的Request Token换取Access Token的服务地址;
OAUTH相关的参数定义:
- oauth_consumer_key: 使用者的ID,OAUTH服务的直接使用者是开发者开发出来的应用。所以该参数值的获取一般是要去OAUTH服务提供商处注册一个应用,再获取该应用的oauth_consumer_key。如Yahoo该值的注册地址为:https://developer.yahoo.com/dashboard/
- oauth_consumer_secret:oauth_consumer_key对应的密钥。
- oauth_signature_method: 请求串的签名方法,应用每次向OAUTH三个服务地址发送请求时,必须对请求进行签名。签名的方法有:HMAC-SHA1、RSA-SHA1与PLAINTEXT等三种。
- oauth_signature: 用上面的签名方法对请求的签名。
- oauth_timestamp: 发起请求的时间戳,其值是距1970 00:00:00 GMT的秒数,必须是大于0的整数。本次请求的时间戳必须大于或者等于上次的时间戳。
- oauth_nonce: 随机生成的字符串,用于防止请求的重放,防止外界的非法攻击。
- oauth_version: OAUTH的版本号,可选,其值必须为1.0。
OAUTH HTTP响应代码:
- HTTP 400 Bad Request 请求错误
- Unsupported parameter 参数错误
- Unsupported signature method 签名方法错误
- Missing required parameter 参数丢失
- Duplicated OAuth Protocol Parameter 参数重复
- HTTP 401 Unauthorized 未授权
- Invalid Consumer Key 非法key
- Invalid / expired Token 失效或者非法的token
- Invalid signature 签名非法
- Invalid / used nonce 非法的nonce
参考资料:
OAuth
http://en.wikipedia.org/wiki/OAuth
Developer's Guide: Data API Protocol – OAuth for Web Applications
http://code.google.com/intl/zh-CN/apis/youtube/2.0/developers_guide_protocol_oauth.html
OAUTH协议简介
http://blog.csdn.net/hereweare2009/archive/2009/03/08/3968582.aspx
An OAuth library for .NET consumers and service providers
http://code.google.com/p/oauth-dot-net/
OAuth不断获得动力
http://www.infoq.com/cn/news/2008/06/oauth_spring
豆瓣 API OAuth认证
http://www.douban.com/service/apidoc/auth
OAuth Use Case
http://wiki.opensocial.org/index.php?title=OAuth_Use_Cases
动画和定时器是紧密相关的,下面的三个例子既可以说是定时器,也可以说是动画。
编码使用定时器
编码方式使用定时器,最简单的就是使用 System.Windows.Threading 命名空间的 DispatcherTimer 类。虽然也可以用 System.Timers.Timer,但是实现就会复杂多了,不推荐使用System.Timers.Timer,原因如下:
在每个 Dispatcher (管理线程工作项队列的服务)循环的顶端重新计算 DispatcherTimer。 这样 DispatcherTimer 不能保证会正好在时间间隔发生时执行计时器,但能够保证不会在时间间隔发生之前执行计时器。 这是因为 DispatcherTimer 操作与其他操作一样被放置到 Dispatcher 队列中。 何时执行 DispatcherTimer 操作取决于队列中的其他作业及其优先级。
如果 System.Timers.Timer 用于 WPF/Silverlight 应用程序,则值得注意的是 System.Timers.Timer 运行于不同于用户界面 (UI) 线程的其他线程上。 为了访问用户界面 (UI) 线程上的对象,需要使用 Invoke 或 BeginInvoke 将操作发布到用户界面 (UI) 线程的 Dispatcher 上。
使用 DispatcherTimer 而不是使用 System.Timers.Timer 的原因是 DispatcherTimer 与 Dispatcher(管理线程工作项队列的服务) 运行于相同的线程,并且可以在 DispatcherTimer 上设置 DispatcherPriority。
使用 DispatcherTimer 的例子
XAML 文件
<Grid x:Name="LayoutRoot" Background="White">
<TextBlock Loaded="StartTimer" x:Name="myTextBlock" />
</Grid>
代码文件
public void StartTimer(object o, RoutedEventArgs sender)
{
System.Windows.Threading.DispatcherTimer myDispatcherTimer = new System.Windows.Threading.DispatcherTimer();
myDispatcherTimer.Interval = new TimeSpan(0, 0, 0, 0, 100); // 100 Milliseconds
myDispatcherTimer.Tick += new EventHandler(Each_Tick);
myDispatcherTimer.Start();
}
// A variable to count with.
int i = 0;
// Raised every 100 miliseconds while the DispatcherTimer is active.
public void Each_Tick(object o, EventArgs sender)
{
myTextBlock.Text = "Count up: " + i++.ToString();
}
使用故事版来做的定时器
我们在 Storyboard 的 Completed 事件中再次把Storyboard 起来: myTimer.Begin(); 这就相当于一个定时器。
XAML文件
<UserControl
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
x:Class="MySilverlight3Study_Animation3.MainPage"
Width="180" Height="120">
<UserControl.Resources>
<Storyboard x:Name="myTimer" Duration="00:00:01" Completed="myTimer_Completed" />
</UserControl.Resources>
<Grid x:Name="LayoutRoot" Background="White">
<Grid.RowDefinitions>
<RowDefinition/>
<RowDefinition/>
</Grid.RowDefinitions>
<Button x:Name="btn" Height="36" Margin="20,5" Content="开始" Grid.Row="1"
Click="Button_Click"/>
<TextBlock x:Name="txt" Grid.Row="0" Height="40" Width="60" Text="0"
TextWrapping="Wrap" Foreground="#FFA72222" FontSize="26.667"
FontFamily="Comic Sans MS" FontWeight="Bold" TextAlignment="Center"
LineHeight="9.333"/>
</Grid>
</UserControl>
代码文件
using System;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Documents;
using System.Windows.Ink;
using System.Windows.Input;
using System.Windows.Media;
using System.Windows.Media.Animation;
using System.Windows.Shapes;
namespace MySilverlight3Study_Animation3
{
public partial class MainPage : UserControl
{
public MainPage()
{
// 为初始化变量所必需
InitializeComponent();
}
private bool isStop = true;
private void Button_Click(object sender, System.Windows.RoutedEventArgs e)
{
if(isStop)
{
btn.Content = "暂停";
myTimer.Begin();
}
else
{
btn.Content = "继续";
myTimer.Stop();
}
isStop = !isStop;
}
private int i = 0;
private void myTimer_Completed(object sender, EventArgs e)
{
i++;
txt.Text = string.Format("{0}",i);
myTimer.Begin();
}
}
}
用 CompositionTarget.Rendering 来做动画和定时器
除了之前提到的几种动画方式,还有一种只能使用代码创建的基于帧的动画方式。需要做的全部工作是响应静态的 CompositionTarget.Rendering 事件,触发该事件是为了为每帧获取内容。这是一种非常低级的方法,除非使用标准的基于属性的动画模型不能满足需要(例如,构建一个简单的游戏、创建一个基于物理的动画,或者构建粒子效果模型(如火焰、雪以及气泡)),否则不会希望使用这种方法。
构建基于帧的动画的基本技术很容易。只需要简单地为静态的CompositionTarget.Rendering事件关联事件处理程序。一旦关联了事件处理程序,Silverlight就会开始不断地调用这个事件处理程序(只要渲染代码执行得足够快,每秒将会调用60次)。
下面是一个简单的演示代码:
using System;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Documents;
using System.Windows.Ink;
using System.Windows.Input;
using System.Windows.Media;
using System.Windows.Media.Animation;
using System.Windows.Shapes;
namespace MySilverlight3Study_Animation3
{
public partial class MainPage : UserControl
{
public MainPage()
{
// 为初始化变量所必需
InitializeComponent();
System.Windows.Media.CompositionTarget.Rendering += new EventHandler(CompositionTarget_Rendering);
}
void CompositionTarget_Rendering(object sender, EventArgs e)
{
txt.Text = DateTime.Now.ToString();
}
}
}
Xaml 文件
<UserControl
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
x:Class="MySilverlight3Study_Animation3.MainPage"
Width="180" Height="120">
<TextBlock x:Name="txt" Text="1" Margin="20" Foreground="Red" TextAlignment="Center" />
</UserControl>
一般游戏,为了性能,都是使用 CompositionTarget.Rendering 事件来做的。而不是之前的2种方式。
参考资料:
SilverLight Timers, what to use?
http://advertboy.wordpress.com/2007/08/11/silverlight-timers-what-to-use/
在Silverlight中使用定时器(Timer)
http://blog.csdn.net/SilverlightShanghai/archive/2007/09/02/1768874.aspx
Silverlight 2 手把手(之六) 使用故事版实现定时器
http://blogs.msdn.com/jijia/archive/2008/04/30/silverlight2-timer-storyboard.aspx
Silverlight 2 手把手(之五) 编程实现定时器
http://blogs.msdn.com/jijia/archive/2008/04/29/programming-timer.aspx
How to: Create a Timer
http://msdn.microsoft.com/en-us/library/cc189084(VS.95).aspx
Make a Silverlight Timer (Silverlight 2)
http://blogs.msdn.com/silverlight_sdk/archive/2008/03/27/make-a-silverlight-timer-silverlight-2.aspx
21.4.5 基于帧的动画
http://book.51cto.com/art/200908/145560.htm
Silverlight: How to use CompositionTarget.Rendering event to synchronize discrete and non-discrete animations
http://blogs.msdn.com/marlat/archive/2009/05/24/silverlight-use-compositiontarget-rendering-event-to-synchronize-discrete-and-non-discrete-animations.aspx
CompositionTarget.Rendering事件
http://blog.etvalley.com/index.php/archives/tag/compositiontargetrendering
Silverlight制作逐帧动画 v2 - part1
http://www.cnblogs.com/nasa/archive/2009/09/11/imageBrush-Animate.html
Silverlight制作逐帧动画 v2 - part2
http://www.cnblogs.com/nasa/archive/2009/09/12/imageBrush-Animate-part2.html
Silverlight制作逐帧动画 v2 - part3
http://www.cnblogs.com/nasa/archive/2009/09/13/imageBrush-Animate-part3.html
Blend基础-动画
http://www.cnblogs.com/nasa/archive/2009/11/03/Expression_Blend_Essentials_Animation.html
System.Data.SqlClient 命名空间下有个类: SqlConnectionStringBuilder ,可以帮助我们维护链接字符串。 这个类对我们下面的需求场景很有帮助。
对于同一个数据库,我们可能有正常和比较耗时的统计工作两种需求,为何互相不干扰,我们一般可以配置2个数据库链接字符串,类似如下的配置:
Persist Security Info=False;Integrated Security=true;Initial Catalog=Northwind;server=(local);
Persist Security Info=False;Integrated Security=true;Initial Catalog=Northwind;server=(local);Connect Timeout=0;
注: 在没有设置 Connect Timeout 时, Connect Timeout 默认为15秒,如果 Connect Timeout 设置为0,则表示永远不超时。这个设置是以秒为单位的。
其实我们完全可以只配置一个数据库链接字符串,然后使用 SqlConnectionStringBuilder 类来帮助我们实现不同 Connect Timeout 的需求。
演示代码如下:
// connString 原汁原味的配置的链接字符串
SqlConnectionStringBuilder connStr = new SqlConnectionStringBuilder(connString);
// 不论之前是否设置了Connect Timeout,这里都强制设置为用不超时
connStr.ConnectTimeout = 0;
// 使用新组合后的链接字符串
SqlConnection conn = new SqlConnection(connStr.ConnectionString);
参考资料:
SqlConnectionStringBuilder 类
http://msdn.microsoft.com/zh-cn/library/system.data.sqlclient.sqlconnectionstringbuilder(VS.80).aspx
按照动画的形成方式,Silverlight 动画可以分为两种:
- 渐变风格方式(也可以叫From/To/By 动画)(确定开始和结束,然后按照一个固定的频率完成渐变) ;
- 关键帧生成方式 (设定若干中间帧,可以按照指定的节奏来变化,节奏可以忽快忽慢);
渐变式动画的讲解请看博客: Silverlight学习笔记--动画效果-- 渐变风格方式动画 本文介绍关键帧动画:
与渐变(From/To/By )动画类似,关键帧动画以动画形式显示了目标属性的值。它通过定义其持续时间(Duration属性)创建其目标值之间的过渡。但是,与From/To/By 动画创建两个值之间的过渡不同,单个关键帧动画可以创建任意数量的目标值之间的过渡。
与 From/To/By 动画不同,关键帧动画没有设置其目标值所需的 From、To 或 By 属性。而是使用关键帧对象描述关键帧动画的目标值。若要指定动画的目标值,需要创建关键帧对象并将其添加到动画的 KeyFrames 属性。动画运行时,将在您指定的帧之间过渡。
几种关键帧动画的动画过度方法(Interpolation Methods)
关键帧动画从一帧到另外一帧,是如何过度的,这个过度策略就是 Interpolation Methods ,MSDN上的翻译竟然叫“内插方法”,我晕,翻译真垃圾。这个过度策略有三种方式:线性(linear interpolation 线性内插)、离散(Discrete Interpolation 离散内插)和样条(Splined Interpolation 样条内插)。
linear interpolation 线性
使用线性过度,指定时间段内,动画的播放速度将是固定的。比如,如果关键帧段在 5 秒内, 从 0 过渡到 10,则该动画会在指定的时间产生如下表所示的值。
| 时间 | 输出值 |
| 0 | 0 |
| 1 | 2 |
| 2 | 4 |
| 3 | 6 |
| 4 | 8 |
| 4.25 | 8.5 |
| 4.5 | 9 |
| 5 | 10 |
Discrete Interpolation 离散
使用离散过度,动画函数将从一个值跳到下一个没有内插的值。如果关键帧段在 5 秒内从 0 过渡到 10,则该动画会在指定的时间产生如下表所示的值。动画在持续期间恰好结束之前不会更改其输出值,一直到到了时间点,才会修改。
| 时间 | 输出值 |
| 0 | 0 |
| 1 | 0 |
| 2 | 0 |
| 3 | 0 |
| 4 | 0 |
| 4.25 | 0 |
| 4.5 | 0 |
| 5 | 10 |
Splined Interpolation 样条
样条过度可用于达到更现实的计时效果。由于动画通常用于模拟现实世界中发生的效果,因此您可能需要精确地控制对象的加速和减速,并且需要严格地对计时段进行操作,这时你就会用到样条关键帧。样条关键帧比起其他关键帧,多一个 KeySpline 属性,用于获取或设置用于定义此关键帧的动画进度的两个控制点。
若要了解 KeySpline 的工作原理,首先需要了解三次方贝塞尔曲线。一条三次方贝塞尔曲线由一个起点、一个终点和两个控制点来定义。
KeySpline 中的两个坐标定义这两个控制点。在描述关键样条时,贝塞尔曲线的起点始终为 0,终点始终为 1,这也就是只定义两个控制点的原因。所生成的曲线指定如何在一个时间段内内插动画;也就是说,该曲线表示该时间段内动画的目标属性的变化速率。
下面是来自MSDN的演示:
<SplineDoubleKeyFrame Value="500"
KeyTime="0:0:7" KeySpline="0.0,1.0 1.0,0.0" />
控制点为 (0.0, 1.0) 和 (1.0, 0.0) 的关键样条
上面的关键帧在开始时快速运动,再减速,然后再次加速,直到结束。
更多演示参看: http://msdn.microsoft.com/zh-cn/library/cc189038(VS.95).aspx 以及微软的演示 KeySpline 的例子,在这里可以看到: 运行此示例
例子
下面我们先看一个简单例子:
<UserControl
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
x:Class="MySilverlight3Study_Animation2.MainPage"
Width="640" Height="480">
<Canvas>
<Canvas.Resources>
<Storyboard x:Name="myStoryboard">
<!-- 总共10秒钟的动画 -->
<DoubleAnimationUsingKeyFrames
Storyboard.TargetName="MyAnimatedTranslateTransform"
Storyboard.TargetProperty="X"
Duration="0:0:10">
<!-- 0到3秒内,平滑过渡到 500 -->
<LinearDoubleKeyFrame Value="500" KeyTime="0:0:3" />
<!-- 在第4秒时,突然过度到 400 -->
<DiscreteDoubleKeyFrame Value="400" KeyTime="0:0:4" />
<!-- 从第4秒开始,逐步加速,一直到第8秒 -->
<SplineDoubleKeyFrame KeySpline="0.6,0.0 0.9,0.00" Value="0" KeyTime="0:0:8" />
<!-- 第8秒到结束的第10秒,没有做任何事情 -->
</DoubleAnimationUsingKeyFrames>
</Storyboard>
</Canvas.Resources>
<Rectangle MouseLeftButtonDown="Mouse_Clicked" Fill="BlueViolet"
Width="50" Height="50">
<Rectangle.RenderTransform>
<TranslateTransform x:Name="MyAnimatedTranslateTransform" X="20" Y="20" />
</Rectangle.RenderTransform>
</Rectangle>
</Canvas>
</UserControl>
事件代码
private void Mouse_Clicked(object sender, System.Windows.Input.MouseButtonEventArgs e)
{
myStoryboard.Begin();
}
例子的演示效果如下,点击方块,开始动画:
代码说明:
我们对一个正方形的进行二维坐标平移来实现动画,动画是通过分帧的方式来定义实现的。
前3秒,平滑过渡,然后停下来,一直到第4秒,突然变化,然后加速运动。
LinearDoubleKeyFrame : 通过使用线性内插,可以在前一个关键帧的 Double 值及其自己的 Value 之间进行动画处理。
DiscreteDoubleKeyFrame : 离散关键帧,在值之间产生突然"跳跃"(无内插算法)。换言之,已经过动画处理的属性在到达此关键帧的关键时间后才会更改,此时已经过动画处理的属性会突然转到目标值。
SplineDoubleKeyFrame : 通过 贝塞尔 曲线方式来定义动画变化节奏。详细看前面的介绍。
通过上面的代码,我们可以看到完成关键帧动画有以下3个步骤:
- 首先声明一个Duration对象
- 在每个节奏点上加入一个KeyFrame
- 把动画效果和元素进行绑定
不同的属性类型有不同的动画类型。关键帧动画也是类似,如下是关键帧对应的分类。
ObjectAnimationUsingKeyFrames 很强大,我可以可以修改元素的任何属性,下面就是一个简单的例子:
例子使用了 ObjectAnimationUsingKeyFrames 对 Rectangle 的 Fill 属性进行动画处理。此动画按如下方式使用两个关键帧:
1.通过使用 DiscreteObjectKeyFrame,Rectangle 的 Fill 属性会在动画的前两秒之后突然更改为 LinearGradientBrush。
2.在动画的第三秒之后,Fill 属性会突然更改为另一个 LinearGradientBrush,然后一直保持到动画结束(总共四秒)。
<UserControl
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
x:Class="MySilverlight3Study_Animation2.MainPage"
Width="120" Height="120">
<StackPanel>
<StackPanel.Triggers>
<EventTrigger RoutedEvent="StackPanel.Loaded">
<BeginStoryboard>
<Storyboard x:Name="myStoryboard">
<ObjectAnimationUsingKeyFrames
Storyboard.TargetName="animatedRectangle"
Storyboard.TargetProperty="Fill"
Duration="0:0:4" RepeatBehavior="Forever">
<ObjectAnimationUsingKeyFrames.KeyFrames>
<!-- 2秒时突变 //-->
<DiscreteObjectKeyFrame KeyTime="0:0:2">
<DiscreteObjectKeyFrame.Value>
<LinearGradientBrush>
<LinearGradientBrush.GradientStops>
<GradientStop Color="Yellow" Offset="0.0" />
<GradientStop Color="Orange" Offset="0.5" />
<GradientStop Color="Red" Offset="1.0" />
</LinearGradientBrush.GradientStops>
</LinearGradientBrush>
</DiscreteObjectKeyFrame.Value>
</DiscreteObjectKeyFrame>
<!-- 3秒时再次突变 -->
<DiscreteObjectKeyFrame KeyTime="0:0:3">
<DiscreteObjectKeyFrame.Value>
<RadialGradientBrush GradientOrigin="0.75,0.25">
<RadialGradientBrush.GradientStops>
<GradientStop Color="White" Offset="0.0" />
<GradientStop Color="MediumBlue" Offset="0.5" />
<GradientStop Color="Black" Offset="1.0" />
</RadialGradientBrush.GradientStops>
</RadialGradientBrush>
</DiscreteObjectKeyFrame.Value>
</DiscreteObjectKeyFrame>
</ObjectAnimationUsingKeyFrames.KeyFrames>
</ObjectAnimationUsingKeyFrames>
</Storyboard>
</BeginStoryboard>
</EventTrigger>
</StackPanel.Triggers>
<!-- 我们将对这个正方形的 Fill 属性来做动画. -->
<Rectangle x:Name="animatedRectangle" Width="100" Height="100" Fill="Aqua" Margin="10" />
</StackPanel>
</UserControl>
执行效果如下:
参考资料:
Animations
http://silverlight.net/learn/quickstarts/animations/
Silverlight 使用 XAML 和 Expression Blend 创建动画
http://msdn.microsoft.com/zh-cn/magazine/cc721608.aspx
关键帧动画
http://msdn.microsoft.com/zh-cn/library/cc189038(VS.95).aspx
稳扎稳打Silverlight(11) - 2.0动画之ColorAnimation, DoubleAnimation, PointAnimation, 内插关键帧动画
http://www.cnblogs.com/webabcd/archive/2008/11/06/1327758.html
使用 XAML 和 Expression Blend 创建动画
http://msdn.microsoft.com/zh-cn/magazine/cc721608.aspx
ObjectAnimationUsingKeyFrames 类
http://msdn.microsoft.com/zh-cn/library/system.windows.media.animation.objectanimationusingkeyframes(VS.95).aspx
按照动画的形成方式,Silverlight 动画可以分为两种:
- 渐变风格方式(确定开始和结束,然后按照一个固定的频率完成渐变) ;
- 关键帧生成方式 (设定若干中间帧,可以按照指定的节奏来变化,节奏可以忽快忽慢);
本文重点演示渐变风格方式。关键帧生成方式下篇再涉及。
要实现一个渐变风格动画,一般需要下面4步,我们下面的演示是实现一个小球从左到右来回移动的动画效果:
建立动画的4个步骤:
<Ellipse x:Name="ellipse" Height="20" Width="20"
Canvas.Left="31" Canvas.Top="31" Fill="#FFBE4343" >
</Ellipse>
- 创建一个EventTrigger.
EventTrigger 响应事件的一组触发器。 EventTrigger 对象将在发生指定的路由事件时启动一组 Actions。例如,当Silverlight加载成功后启动一组动画。
EventTrigger 最重要的属性就是 RoutedEvent, 用于获取或设置将激活此触发器的 RoutedEvent。
如果包含此 EventTrigger 的模板或样式没有指定 TargetType 属性,则需要使用 ClassName.EventName 语法通过类名限定事件名称。
目前Silverlight只支持一个事件:Element.Loaded,Element是包含trigger的对象的名称(这里是Canvas)。
<Canvas>
<Canvas.Triggers>
<EventTrigger RoutedEvent="Canvas.Loaded">
</EventTrigger>
</Canvas.Triggers>
<Ellipse x:Name="ellipse" Height="20" Width="20"
Canvas.Left="31" Canvas.Top="31" Fill="#FFBE4343" >
</Ellipse>
</Canvas>
- 使用 BeginStoryboard 和 Storyboard
BeginStoryboard 是一个包含 Storyboard 对象的触发器操作,该操作可启动 Storyboard 并将其动画分发给动画的目标对象和属性。
Storyboard 对象包含动画定义。 当定义动画时,您只需在 EventTrigger 定义内嵌入这些对象。
<Canvas>
<Canvas.Triggers>
<EventTrigger RoutedEvent="Canvas.Loaded">
<BeginStoryboard>
<Storyboard AutoReverse="True" RepeatBehavior="Forever">
</Storyboard>
</BeginStoryboard>
</EventTrigger>
</Canvas.Triggers>
<Ellipse x:Name="ellipse" Height="20" Width="20"
Canvas.Left="31" Canvas.Top="31" Fill="#FFBE4343" >
</Ellipse>
</Canvas>
上面例子中的两个参数的意思分别如下:
AutoReverse: 获取或设置一个值,该值指示时间线在完成向前迭代后是否按相反的顺序播放。
RepeatBehavior : 获取或设置此时间线的重复行为。默认情况下,时间线的重复次数为 1.0,即播放一次时间线,不进行重复。但是,如果将 Timeline 的 RepeatBehavior 属性设置为 Forever,则时间线将会无限重复。
如果 AutoReverse 属性设置为 true 且 RepeatBehavior 值为 2x 的时间线将向前播放,然后向后播放,再向前播放,然后再向后播放,这样才算完。
除了指定时间线播放的次数,还可以指定希望时间线播放的总时间长度。如果 RepeatBehavior 的值为: 0:0:4 则表示要播放4秒钟。
在 Storyboard 中增加下面代码:
<DoubleAnimation
Storyboard.TargetName="ellipse"
Storyboard.TargetProperty="(Canvas.Left)"
To="500" Duration="0:0:3" />
这里的例子创建了一个DoubleAnimation动画,DoubleAnimation用于为包含双精度值的属性(例如,Canvas.Left 等维度属性或不透明度等可视化属性)设置动画效果。
上面例子中的参数的意思分别如下:
Storyboard.TargetName : 动画元素的名称(为此需要赋一个name属性),即第一步我们建立的那个元素。
Storyboard.TargetProperty : 动画元素的属性。
提示:Storyboard.TargetProperty的值是接收动态值的属性的名称,如果属性包含点(如Canvas.Left或Canvas.Top),
需要在圆括号内使用完整名称,如(Canvas.Left)或(Canvas.Top)。
Duration : 获取或设置此时间线播放的时间长度,而不是计数重复。语法为:hh:mm:ss(小时,分钟,秒)
To : 获取或设置动画的结束值。
设置双精度值动画效果时,可以在动画开始时使用 From 值指定该值,然后将其更改为 To 值(绝对目标),
或更改为 By 值(相对目标)。例如,如果要将某个项目的 Canvas.Left 属性从 100(接近屏幕的左端)移动至 500,
您可以将 From 值设置为 100,将 To 值设置为 500 或将 By 值设置为 400。请注意,如果您同时设置了 To 值和 By 值,
则优先采用 To 属性,而忽略 By 属性。同样,如果此矩形已经位于 From 的目标位置,则不需要再指定 From 属性。
除了 DoubleAnimation 渐变风格动画我们还可以使用 ColorAnimation ,PointAnimation 。ColorAnimation ,PointAnimation 动画中的 By ,From ,To ,Duration,Storyboard.TargetName,Storyboard.TargetProperty含义类似。就不再重复含义介绍了。下面就是一个完整演示这三种动画的代码,演示效果看本文最后的演示:
<UserControl
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
x:Class="MySilverlight3Study_Animation1.MainPage"
Width="640" Height="300">
<Canvas>
<Canvas.Triggers>
<EventTrigger RoutedEvent="Canvas.Loaded">
<BeginStoryboard>
<Storyboard AutoReverse="True" RepeatBehavior="Forever">
<DoubleAnimation
Storyboard.TargetName="ellipse"
Storyboard.TargetProperty="(Canvas.Left)"
To="500" Duration="0:0:3" />
<ColorAnimation
Storyboard.TargetName="rectangle"
Storyboard.TargetProperty="(Fill).(Color)"
To="Black" Duration="0:0:3" />
<PointAnimation
Storyboard.TargetName="ellipseGeometry"
Storyboard.TargetProperty="Center"
By="400,-150" Duration="0:0:3" />
</Storyboard>
</BeginStoryboard>
</EventTrigger>
</Canvas.Triggers>
<Ellipse x:Name="ellipse" Height="20" Width="20"
Canvas.Left="31" Canvas.Top="31" Fill="#FFBE4343" />
<Rectangle x:Name="rectangle" Fill="#FFC7CFE6" Height="31" Width="74" Canvas.Left="31" Canvas.Top="87"/>
<Path Fill="Blue">
<Path.Data>
<EllipseGeometry x:Name="ellipseGeometry" Center="36,250" RadiusX="10" RadiusY="10" />
</Path.Data>
</Path>
</Canvas>
</UserControl>
当然上面所有的代码都可以通过编程实现,但更多时候,我们只是编程实现 EventTrigger 和 BeginStoryboard 这部分,动画元素,故事板,动画定义我们是通过 Blend来编写 Xaml 文件来实现的。
参考资料:
Silverlight QuickStart:动画
http://www.cftea.com/docs/Silverlight/quickstart/animations-frames.html
Silverlight 使用 XAML 和 Expression Blend 创建动画
http://msdn.microsoft.com/zh-cn/magazine/cc721608.aspx
运行 Storyboard 时进行控制
http://msdn.microsoft.com/zh-cn/library/cc295328.aspx
触发器概述
http://msdn.microsoft.com/zh-cn/library/cc294856.aspx
silverlight 动画
http://www.cnblogs.com/njnudt/archive/2008/05/20/1203347.html
简单的Silverlight动画
http://www.cnblogs.com/nasa/archive/2009/06/04/silverlight-mpeo.html
稳扎稳打Silverlight(11) - 2.0动画之ColorAnimation, DoubleAnimation, PointAnimation, 内插关键帧动画
http://www.cnblogs.com/webabcd/archive/2008/11/06/1327758.html
程序效果如下:
其实反编译Silverlight 应用非常容易,下面就以反编译 http://www.joerassic.ch/ 的街机对战的Silverlight应用为例来说明这个过程。http://www.joerassic.ch/ 有一个非常酷的,用Silverlight开发的恐龙街机对战的游戏,下面是一副截图。
下载Silverlight XAP文件:
由于 Silverlight 是在客户端浏览器中执行的,我们下载了 Silverlight 文件后,也就得到了完整的 Silverlight 执行文件。
当我们用 Internet Explorer 浏览器浏览了 Silverlight 应用后, 在Internet Explorer的临时文件夹中就有这个Silverlight的文件。我们可以直接来使用这个文件作为反编译的基础。Silverlight的文件是一个 xap 文件, 其实它就是一个 zip 文件。有关这部分的详细信息可以参看我之前写的博客: Silverlight项目中的文件 。
Internet Explorer 的临时文件夹可以通过下面菜单途径到达:
工具 --》 Internet 选项 打开 Internet 选项 设置Tab页。如下图:
在其中的 浏览历史记录中, 我们点击“设置” 按钮,进入了 Internet 临时文件和历史记录设置窗体,如下图:
在这个窗体中我们点击“查看文件”按钮,就可以进入临时文件目录。
临时文件目录下文件很多,由于silverlight 应用默认是 xap后缀的文件,我们通过过滤只看这类文件,如下图:
我们把上述 xap 文件另外复制一个目录,作为我们进一步反编译的基础。
注意,这个复制应该在 Internet Explorer 没有关闭 http://www.joerassic.ch/ 页面之前,同时Silverlight 加载之后进行,否则就会报错误:
我们把 xap 文件名的后缀修改为 zip , 然后解压缩到一个目录下。以这个街机对战的为例,解压缩后文件如下图,有关这些文件的用途,请参看我之前的博客:Silverlight项目中的文件 :
反编译:
由于Silverlight 2.0 以后, 大家几乎都是用 .net 来开发Siverlight 应用,我们也就可以使用 Reflector 打开其中的dll文件。
我们用 Reflector 打开 JurassicCombat.dll 文件后,查看Dll 反编译后的源代码很简单,如下图:
查看对应的XAML文件,则需要查看它的资源文件,如下图:
选中一个资源,然后右击,会出现“Save As” 菜单项,把这个资源另存在一个目录下,打开就可以看到 Xaml文件了, 当然上面资源文件中的图片文件,字体文件都可以看到。这个街机对战的恐龙动画特效文件并没有被编译到 Xap 文件中,而是另外有文件。我们在 Fiddler 辅助下就可以分析到这些图片文件的地址,这里使用的是 http://www.joerassic.ch/ClientBin/images.zip http://www.joerassic.ch/ClientBin/images2.zip 的图片对应不同等级的对战特效。由于这些图片文件的巨大, 我们在访问 http://www.joerassic.ch/ 的初始化时间才需要这么久。
我们来看例子,下面的椭圆显示的将不是完整的一个椭圆。
Xaml 文件:
<UserControl
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008" xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
x:Class="MySilverlight3StudyApplication.MainPage"
Width="640" Height="480" mc:Ignorable="d">
<Ellipse Stroke="Black" StrokeThickness="10" Height="64"
Margin="313,256,200,30"
VerticalAlignment="Top" HorizontalAlignment="Left" Width="197"/>
</UserControl>
执行效果如下图:
出现椭圆被劫了一部分的原因是 Margin="313,256,200,30" 在作怪。
Margin:获取或设置元素的外边距。上面的313,256,200,30 依次是 left,top,right,bottom
上面的右边距X坐标是通过下面算出来的 640(父控件宽度)-200(right Margin) = 440。 而正常椭圆的最右边点的X坐标为 313(left Margin)+10(left StrokeThickness)+197(Ellipse Width)+10(right StrokeThickness)= 530。
由于椭圆的最右边已经在 计算出来的右边距之外了,在之外的部分就没有显示,所以就会有上面的情况发生。
这里通过简单的一个播放器代码来学习如何在silverlight中使用多媒体。代码是参考的 http://www.cnblogs.com/webabcd/archive/2009/11/10/1344632.html 的例子进行稍加改造完成的。执行效果见下图:视频播放的是我家宝宝7个月的时候的视频。
代码如下:
Xaml 文件:
说明:这里使用了 Grid 来控制布局。
<UserControl
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
x:Class="SilverlightStudy.MainPage" Loaded="UserControl_Loaded">
<Grid Height="500" Width="800">
<Grid.RowDefinitions>
<RowDefinition Height="*" />
<RowDefinition Height="45" />
<RowDefinition Height="90" />
</Grid.RowDefinitions>
<Grid.ColumnDefinitions>
<ColumnDefinition/>
<ColumnDefinition/>
<ColumnDefinition/>
<ColumnDefinition/>
<ColumnDefinition Width="200"/>
</Grid.ColumnDefinitions>
<MediaElement x:Name="mediaElement" Grid.Row="0" Grid.ColumnSpan="4" Stretch="Fill" AutoPlay="False"
Source="http://localhost:41454/SilverlightStudySite/MVI_0122.mp4"
BufferingProgressChanged="mediaElement_BufferingProgressChanged" MediaEnded="mediaElement_MediaEnded"
DownloadProgressChanged="mediaElement_DownloadProgressChanged" MediaOpened="mediaElement_MediaOpened"
CurrentStateChanged="mediaElement_CurrentStateChanged" />
<Button x:Name="play" Content="播放" Margin="5" Grid.Row="1" Grid.Column="0" Click="play_Click" />
<Button x:Name="pause" Content="暂停" Margin="5" Grid.Row="1" Grid.Column="1" Click="pause_Click" />
<Button x:Name="stop" Content="停止" Margin="5" Grid.Row="1" Grid.Column="2" Click="stop_Click" />
<Button x:Name="mute" Content="静音" Margin="5" Grid.Row="1" Grid.Column="3" Click="mute_Click" />
<StackPanel Grid.Row="2" Grid.ColumnSpan="4">
<Slider x:Name="playSlider" Minimum="0" Maximum="1" Margin="5" ToolTipService.ToolTip="播放进度" ValueChanged="playSlider_ValueChanged" />
<Slider x:Name="volumeSlider" Minimum="0" Maximum="1" Margin="5" ToolTipService.ToolTip="音量大小" ValueChanged="volumeSlider_ValueChanged" />
<Slider x:Name="balanceSlider" Minimum="-1" Maximum="1" Margin="5" ToolTipService.ToolTip="音量平衡" ValueChanged="balanceSlider_ValueChanged" />
</StackPanel>
<StackPanel Grid.Column="4" Grid.RowSpan="3">
<TextBlock x:Name="lblPlayTime" Margin="5" />
<TextBlock x:Name="lblVolume" Margin="5" />
<TextBlock x:Name="lblBalance" Margin="5" />
<TextBlock x:Name="lblDownloadProgress" Margin="5" />
<TextBlock x:Name="lblBufferingProgress" Margin="5" />
<TextBlock x:Name="lblDroppedFramesPerSecond" Margin="5" />
<TextBlock x:Name="lblState" Margin="5" />
<TextBlock x:Name="lblWidth" Margin="5" />
<TextBlock x:Name="lblHeight" Margin="5" />
<TextBlock x:Name="lblTotalTime" Margin="5" />
<TextBlock x:Name="lblBufferingTime" Margin="5" />
</StackPanel>
</Grid>
</UserControl>
对应的CS 文件,即事件处理代码。这里用了定时器,定时显示媒体的一些状态信息。
using System;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Documents;
using System.Windows.Ink;
using System.Windows.Input;
using System.Windows.Media;
using System.Windows.Media.Animation;
using System.Windows.Shapes;
using System.Windows.Threading;
namespace SilverlightStudy
{
public partial class MainPage : UserControl
{
public MainPage()
{
// Required to initialize variables
InitializeComponent();
}
/// <summary>
/// 需要播放的文件的长度
/// </summary>
private TimeSpan _duration;
/// <summary>
/// 定时器,定时显示播放状态信息
/// </summary>
private DispatcherTimer _timer = new DispatcherTimer();
private void UserControl_Loaded(object sender, RoutedEventArgs e)
{
// 每 500 毫秒调用一次指定的方法
_timer.Interval = TimeSpan.FromMilliseconds(500);
_timer.Tick += new EventHandler(_timer_Tick);
_timer.Start();
}
void _timer_Tick(object sender, EventArgs e)
{
// CurrentState - 播放状态 [System.Windows.Media.MediaElementState枚举]
// Position - 媒体的位置(单位:秒)
if (mediaElement.CurrentState == MediaElementState.Playing)
{
lblPlayTime.Text = string.Format(
"{0}{1:00}:{2:00}:{3:00}",
"播放进度:",
mediaElement.Position.Hours,
mediaElement.Position.Minutes,
mediaElement.Position.Seconds);
}
// DroppedFramesPerSecond - 媒体每秒正在丢弃的帧数
lblDroppedFramesPerSecond.Text = string.Format(
"每秒丢弃帧数:{0}",
mediaElement.DroppedFramesPerSecond);
}
private void mediaElement_BufferingProgressChanged(object sender, RoutedEventArgs e)
{
// BufferingProgress - 缓冲进度(0 - 1 之间)
lblBufferingProgress.Text = string.Format(
"缓冲进度:{0:##%}",
mediaElement.BufferingProgress);
}
private void mediaElement_MediaEnded(object sender, RoutedEventArgs e)
{
mediaElement.Stop();
}
private void mediaElement_DownloadProgressChanged(object sender, RoutedEventArgs e)
{
// DownloadProgress - 下载进度(0 - 1 之间)
lblDownloadProgress.Text = string.Format(
"下载进度:{0:##%}",
mediaElement.DownloadProgress);
}
private void mediaElement_MediaOpened(object sender, RoutedEventArgs e)
{
/*
* NaturalVideoWidth - 媒体文件的宽
* NaturalVideoHeight - 媒体文件的高
* HasTimeSpan - 是否可取得媒体文件的时长
* NaturalDuration - 媒体文件的时长
* Volume - 音量大小(0 - 1 之间)
* Balance - 音量平衡(-1 - 1 之间)
* BufferingTime - 需要缓冲的时间的长度
*/
lblWidth.Text = "媒体文件的宽:" + mediaElement.NaturalVideoWidth.ToString();
lblHeight.Text = "媒体文件的高:" + mediaElement.NaturalVideoHeight.ToString();
_duration = mediaElement.NaturalDuration.HasTimeSpan ? mediaElement.NaturalDuration.TimeSpan : TimeSpan.FromMilliseconds(0);
lblTotalTime.Text = string.Format(
"时长:{0:00}:{0:00}:{0:00}",
_duration.Hours,
_duration.Minutes,
_duration.Seconds);
// 音量
mediaElement.Volume = 0.8;
volumeSlider.Value = 0.8;
lblVolume.Text = "音量大小:80%";
// 音量平和
mediaElement.Balance = 0;
balanceSlider.Value = 0;
lblBalance.Text = "音量平衡:0%";
// 缓存大小
mediaElement.BufferingTime = TimeSpan.FromSeconds(30);
lblBufferingTime.Text = "缓冲长度:30秒";
}
private void mediaElement_CurrentStateChanged(object sender, RoutedEventArgs e)
{
/*
* CurrentState - 播放状态 [System.Windows.Media.MediaElementState枚举]
* MediaElementState.Closed - 无可用媒体
* MediaElementState.Opening - 尝试打开媒体(此时Play(),Pause(),Stop()命令会被排进队列,等到媒体被成功打开后再依次执行)
* MediaElementState.Buffering - 缓冲中
* MediaElementState.Playing - 播放中
* MediaElementState.Paused - 被暂停(显示当前帧)
* MediaElementState.Stopped - 被停止(显示第一帧)
*/
lblState.Text = string.Format("播放状态:{0}", mediaElement.CurrentState);
}
private void play_Click(object sender, RoutedEventArgs e)
{
// Play() - 播放媒体(在当前 Position 处播放)
mediaElement.Play();
}
private void stop_Click(object sender, RoutedEventArgs e)
{
// Stop() - 停止媒体的播放
mediaElement.Stop();
}
private void pause_Click(object sender, RoutedEventArgs e)
{
// CanPause - 媒体是否可暂停
// Pause() - 暂停媒体的播放
if (mediaElement.CanPause)
mediaElement.Pause();
}
private void mute_Click(object sender, RoutedEventArgs e)
{
// IsMuted - 是否静音
if (mediaElement.IsMuted == true)
{
mute.Content = "静音";
mediaElement.IsMuted = false;
}
else
{
mute.Content = "有声";
mediaElement.IsMuted = true;
}
}
private void playSlider_ValueChanged(object sender, RoutedPropertyChangedEventArgs<double> e)
{
// CanSeek - 是否可以通过设置 Position 来重新定位媒体
// Position - 媒体的位置(单位:秒)
if (mediaElement.CanSeek)
{
mediaElement.Pause();
mediaElement.Position = TimeSpan.FromSeconds(_duration.TotalSeconds * playSlider.Value);
mediaElement.Play();
}
}
private void volumeSlider_ValueChanged(object sender, RoutedPropertyChangedEventArgs<double> e)
{
// Volume - 音量大小(0 - 1 之间)
mediaElement.Volume = volumeSlider.Value;
lblVolume.Text = string.Format(
"音量大小:{0:##%}",
volumeSlider.Value);
}
private void balanceSlider_ValueChanged(object sender, RoutedPropertyChangedEventArgs<double> e)
{
// Balance - 音量平衡(-1 - 1 之间)
mediaElement.Balance = balanceSlider.Value;
lblBalance.Text = string.Format(
"音量平衡:{0:##%}",
balanceSlider.Value);
}
}
}
一些播放多媒体的知识点:
Silverlight 并非支持所有媒体格式和协议。
详细支持的媒体请参看: http://msdn.microsoft.com/zh-cn/library/cc189080(VS.95).aspx
播放媒体的关键是 MediaElement 控件:这是一个表示包含音频和/或视频的控件。
MediaElement 的几个媒体特定的属性:
AutoPlay:指定 MediaElement 是否应自动开始播放。默认值为 true。
IsMuted:指定 MediaElement 是否静音。true 值将使 MediaElement 静音。默认值为 false。
Stretch:指定如何拉伸视频以填充 MediaElement 对象。可能值为 None、Uniform、UniformToFill 和 Fill。默认值为 Fill。
Balance: 音量平横,立体声扬声器的音量比。Double 类型属性,扬声器的音量比的范围为 -1 到 1。 默认值为 0。 值为 -1 表示左侧扬声器达到 100% 音量,而值为 1 表示右侧扬声器达到 100% 音量。0 表示在左右扬声器之间平均分布音量。
Volume: 音量大小。Double 类型属性,在 0 与 1 之间的线性标尺上所表示的媒体音量。 默认值为 0.5。
音量可能难以量化,因为每个媒体文件对于其录制或察觉到的音频级别都具有不同的基线。此外,真实的音量(以分贝标度表示)本质上是对数值。您可能希望用 Volume 属性来表示音频混频器滑块控件的位置,0 表示最低音量,而 1 表示最高音量。
Position : 自媒体开始播放之后经历的时间量。默认值是 TimeSpan 值"00:00:00"。可以设置的最大值是 MediaElement 对象的 NaturalDuration(媒体的自然持续时间)。
如果可以设置 Position,但该值被设置为某一负值或高于该媒体的 NaturalDuration 的值,则该属性值将相应设置为 00:00:00 或 NaturalDuration。
通常不应在 XAML 中设置此值,因为不能保证在加载媒体源之前可以定位该媒体。在引发 MediaOpened 后,检查 CanSeek 的值。如果该值为 true,则可以定位该媒体,并且可以设置 Position。
MediaElement 的事件说明:
MediaOpened: 当媒体流已被验证和打开且已读取文件头时发生。如果 AutoPlay 属性设置为 false,则当发生 MediaOpened 事件时,将暂停媒体。另外要注意: Position 属性在发生 MediaOpened 事件之前为 null。虽然 Position 是可设置的。但在引发 MediaOpened 事件之前,不应尝试设置 Position。包括不应在 XAML 中设置初始 Position 值。
MediaEnded : 当 MediaElement 对象不再播放音频或视频时发生。 如果媒体文件包含多个流,则在最后一个流结束后将发生 MediaEnded 事件。 如果以交互方式控制媒体播放,则不会发生 MediaEnded 事件。即上述演示代码中, mediaElement.Stop(); 不会触发 MediaEnded 事件。
CurrentStateChanged : 当 CurrentState 属性的值更改时发生。
尽管在 CurrentState 失效时会发生该事件,但这并不一定意味着 CurrentState 属性具有新值。例如,CurrentState 属性可能会从 Playing 切换为 Buffering 并很快又切换回 Playing,以致只引发了单个 CurrentStateChanged 事件,在这种情况下,该属性将并不表现为具有变化的值。此外,您的应用程序不应采用事件发生的顺序,尤其是针对"缓冲"之类的瞬态。在事件报告中可能会跳过某一瞬态,因为该瞬态发生得太快了。
DownloadProgressChanged : 在 DownloadProgress (MediaElement) 属性更改后发生。
对于渐进式下载,会发生 DownloadProgressChanged 事件(通常会发生多次)。只要下载的内容总量按照 0.05 或更高的量(作为 1.0 的系数,1.0 指示完成)增加或达到 1.0, 事件就会发生。
这里的 0.05, 是指 DownloadProgress 属性的值, DownloadProgress 属性是一个 Double 类型的属性,指示位于远程服务器上的内容的已下载内容的总量。此值介于 0 到 1 之间。乘以 100 可得到百分比。此属性为只读。默认值为 0。
BufferingProgressChanged : 当 BufferingProgress 属性更改时发生。
通常在下载过程中,BufferingProgressChanged 事件会发生多次。当 BufferingProgress 值按照 0.05 或更大的量增加(与上一次引发事件时相比)时,以及当该值达到 1.0 时,将发生该事件。
这里的 0.05, 是指 BufferingProgress 属性的值, BufferingProgress 属性是一个 Double 类型的属性,指示当前缓冲进度的值。此值介于 0 到 1 之间。乘以 100 可得到百分比。此属性为只读。默认值为 0。
参考资料:
稳扎稳打Silverlight(18) - 2.0视频之详解MediaElement, 开发一个简易版的全功能播放器
http://www.cnblogs.com/webabcd/archive/2009/11/10/1344632.html
音频和视频概述
http://msdn.microsoft.com/zh-cn/library/cc189078(VS.95).aspx
文本的处理
TextBlock 是 Silverlight 中的一个常用又有用的控件。我们可以通过TextBlock呈现只读的文本。
下面是演示代码
<UserControl
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
x:Class="SilverlightStudy.MainPage"
Width="640" Height="480">
<TextBlock FontFamily="Arial Black" FontSize="16"
Foreground="Red" Width="500">
欢迎
<Run FontSize="34" FontStyle="Italic" Foreground="Blue">您</Run>
<Run FontSize="22" FontStretch="ExtraCondensed">来到</Run>
<Run>这里!</Run>
<LineBreak />
<Bold>哈哈哈哈</Bold>
<Italic>我是谁?</Italic>
<Underline>太阳出来了</Underline>
<LineBreak />
</TextBlock>
</UserControl>
演示效果:
其中一些属性如下:
FontFamily: Silverlight内置了十几种常见拉丁字体,如Arial,Comic Sans MS,Courier New,Lucida Sans Unicode等。默认使用的字体是 Portable User Interface。这是一种复合字体,它使用若干字体来实现 Silverlight 所支持的一组国际语言。这些字体包括针对许多西方书写系统的"Lucida Sans Unicode"和"Lucida Grande",以及针对东亚书写系统的更多字体。Silverlight 会根据文字的语言选择最佳的字体。
如果您指定的 FontFamily 不存在,即使它是唯一可用的字体系列值,Silverlight 仍将为 Portable User Interface 值提供备用值以用于呈现。
上面例子中,我把字体设置成 Arial Black,由于其中的中文无法找到对应字体,就使用了 Portable User Interface 来显示的。由于是中文,上述文字就是用宋体的样式来显示的。
如果我们直接把字体修改为其他中文字体,比如: 微软雅黑, 程序就会报警告:
The font, 微软雅黑, is not a built-in Silverlight font and will not be displayed to users who run your Silverlight application. To display the font, either install the font on your computer and then use Expression Blend to embed the font in your project, or add the font files to your project.
如何解决这个问题以及关于字体的更多信息,你可以看下面几篇文章:
文本和字体: http://msdn.microsoft.com/zh-cn/library/cc189010(VS.95).aspx
解决Silverlight引用中文字体的问题
http://blog.joycode.com/joy/archive/2007/05/28/103441.aspx
Silverlight Tip of the Day #46 – Font Support in Silverlight
FontSize:指定所需字体大小(以像素为单位)。值必须为非负数。 默认值为 11 像素。
FontStretch: 指定所需的字体标志符号宽度。
默认值为 Normal。FontStretch 的效果依赖于正使用的特定字体系列,并且只能指定字体系列中已经存在的字体。该属性不会导致以编程方式拉伸标志符号。(注意:如果 FontStretch 映射不可用,您可以考虑将某一变换应用于文本。)
压缩或者拉长的比率请参看下面文档
http://msdn.microsoft.com/zh-cn/library/system.windows.fontstretches(VS.95).aspx
FontStyle: 指定所需字形为普通还是斜体。
可设置两种值: Normal, Italic(斜体)。默认值为 Normal。FontWeight 的效果依赖于正使用的特定字体系列,并且通常只能指定字体系列中已经存在的字体。Silverlight 不以编程方式创建修剪变形来模拟斜体。
FontWeight: 指定所需的字体标志符号粗细。即:文字的胖瘦。可设置为Thin, ExtraLight, Light, Normal, Medium, SemiBold, Bold, ExtraBold, Black, ExtraBlack.这些值是否起作用还要取决于你所选择的字体。默认值为 Normal。
FontWeight 的效果依赖于正使用的特定字体系列,并且通常只能指定字体系列中已经存在的字体。修剪不会以编程方式创建备用粗细值,除非字体系列中包含普通粗细字体,而不包含粗体字体。在这种情况下,Silverlight 将通过增加使用二维图形算法的笔画宽度来模拟粗体字体。
TextDecorations: 对文字的修饰,默认设置为无修饰。若要指定下划线修饰,请将 TextDecorations 属性设置为 Underline。
Foreground: 通过这个属性可以设置文字的前景色填充。不但可以使用颜色值,还可以通过设置solid color, gradient, image 及 video笔刷进行填充。
比如下面代码就是使用的图片做的文字前景刷子
<TextBlock Text="SHRUBBERY">
<TextBlock.Foreground>
<ImageBrush ImageSource="forest.jpg"/>
</TextBlock.Foreground>
</TextBlock>
执行效果:
Run: 你可以在TextBlock中使用Run标签创建内联元素,每个Run都可以设置上面提到的属性。这样一篇文档,就可以每处显示的风格都不一样。
参考资料:
了解Silverlight提供的TextBlock(文字区块)元素对象
http://silverlight.cn/node/356
[Silverlight]TextBlock控件全攻略
http://www.cnblogs.com/024hi/archive/2008/12/04/1347337.html
文本和字体
http://msdn.microsoft.com/zh-cn/library/cc189010(VS.95).aspx
WPF4数据绑定应用之创建具有多种显示效果的字串
http://www.cnblogs.com/bitfan/archive/2009/11/12/1601857.html
如下Xaml文件, 在 Silverlight 4 中会报错。
<UserControl
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
x:Class="SilverlightStudy.MainPage"
Width="640" Height="480">
<TextBlock>
<Hyperlink NavigateUri="http://www.msn.com" >MSN Home</Hyperlink>
</TextBlock>
</UserControl>
错误信息:
Hyperlink 不支持将 nothing 作为内容。 [Line: 7 Position: 66]
在 Microsoft Expression Blend Preview for .NET 4 中,我们会看到错误信息:
参考资料:
Hyperlink should be able to be inserted in a textblock
http://connect.microsoft.com/VisualStudio/feedback/ViewFeedback.aspx?FeedbackID=511958
Bugs fixed (or not) in Silverlight 4 Beta
http://www.olsonsoft.com/blogs/stefanolson/post/Bugs-fixed-(or-not)-in-Silverlight-4-Beta.aspx
最近在处理一批下载的评书mp3文件时,需要把它们的一些属性做修改为有规律的样式,以便自己播放时知道是播放的那首。
要修改的属性如下:
修改的方法我是使用的 http://www.cnblogs.com/TianFang/archive/2009/09/27/1574722.html 介绍的 使用 WindowsAPICodePack 的方法来修改。
但是上述地址给出的函数有个小bug,且具体如何用代码实现也没有说明,所以整理了这篇博客,
WindowsAPICodePack 在下面地址可以下载:
http://code.msdn.microsoft.com/WindowsAPICodePack
我下载的是 Windows API Code Pack 1.0.1 中的
WindowsAPICodePack.zip
source code, 6927K, uploaded Nov 19
这个是源文件, 下载后打开可以看到 WindowsAPICodePack.sln 文件, 用 Visual Studio 编译后,我们可以得到下面2个文件:
Microsoft.WindowsAPICodePack.dll
Microsoft.WindowsAPICodePack.Shell.dll
这就是我们项目中要引用的两个文件。
引用这两个文件后,再在项目中增加一个文件,内容如下(注意把namespace修改为你自己的):
这个文件中也就是我说的http://www.cnblogs.com/TianFang/archive/2009/09/27/1574722.html 介绍的有小bug的文件,有错误的部分看下面我的注释。文件内容如下:
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using Microsoft.WindowsAPICodePack.Shell;
using Microsoft.WindowsAPICodePack.Shell.PropertySystem;
using System.Reflection;
namespace _52PS_WpfApplication
{
public class MediaTags
{
#region Mp3文件属性
//// <summary>
/// 标题
/// </summary>
[MediaProperty("Title")]
public string Title { get; set; }
/// <summary>
/// 子标题
/// </summary>
[MediaProperty("Media.SubTitle")]
public string SubTitle { get; set; }
/// <summary>
/// 星级
/// </summary>
[MediaProperty("Rating")]
public uint? Rating { get; set; }
/// <summary>
/// 备注
/// </summary>
[MediaProperty("Comment")]
public string Comment { get; set; }
/// <summary>
/// 艺术家
/// </summary>
[MediaProperty("Author")]
public string Author { get; set; }
/// <summary>
/// 唱片集
/// </summary>
[MediaProperty("Music.AlbumTitle")]
public string AlbumTitle { get; set; }
/// <summary>
/// 唱片集艺术家
/// </summary>
[MediaProperty("Music.AlbumArtist")]
public string AlbumArtist { get; set; }
/// <summary>
/// 年
/// </summary>
[MediaProperty("Media.Year")]
public uint? Year { get; set; }
/// <summary>
/// 流派
/// </summary>
[MediaProperty("Music.Genre")]
public string Genre { get; set; }
/// <summary>
/// #
/// </summary>
[MediaProperty("Music.TrackNumber")]
public uint? TrackNumber { get; set; }
/// <summary>
/// 播放时间
/// </summary>
[MediaProperty("Media.Duration")]
public string Duration { get; private set; }
/// <summary>
/// 比特率
/// </summary>
[MediaProperty("Audio.EncodingBitrate")]
public string BitRate { get; private set; }
#endregion
public MediaTags(string mediaPath)
{
//var obj = ShellObject.FromParsingName(mp3Path); //缩略图,只读
//obj.Thumbnail.Bitmap.Save(@"R:\2.jpg");
Init(mediaPath);
}
void Init(string mediaPath)
{
using (var obj = ShellObject.FromParsingName(mediaPath))
{
var mediaInfo = obj.Properties;
foreach (var properItem in this.GetType().GetProperties())
{
var mp3Att = properItem.GetCustomAttributes(typeof(MediaPropertyAttribute), false).FirstOrDefault();
var shellProper = mediaInfo.GetProperty("System." + mp3Att);
var value = shellProper == null ? null : shellProper.ValueAsObject;
if (value == null)
{
continue;
}
if (shellProper.ValueType == typeof(string[])) //艺术家,流派等多值属性
{
properItem.SetValue(this, string.Join(";", value as string[]), null);
}
else if (properItem.PropertyType != shellProper.ValueType) //一些只读属性,类型不是string,但作为string输出,避免转换 如播放时间,比特率等
{
properItem.SetValue(this, value == null ? "" : shellProper.FormatForDisplay(PropertyDescriptionFormat.Default), null);
}
else
{
properItem.SetValue(this, value, null);
}
}
}
}
public void Commit(string mp3Path)
{
var old = new MediaTags(mp3Path);
using (var obj = ShellObject.FromParsingName(mp3Path))
{
var mediaInfo = obj.Properties;
foreach (var proper in this.GetType().GetProperties())
{
var oldValue = proper.GetValue(old, null);
var newValue = proper.GetValue(this, null);
if (oldValue == null && newValue == null)
{
continue;
}
// 这里做了修改 郭红俊 20091202
// 原先在旧值存在,新值没有给出时,会有空对象引用的bug
//if (oldValue == null || !oldValue.Equals(newValue))
// 新的逻辑 新值存在时, 则替换旧值
if ((newValue != null) && (newValue.ToString().Trim().Length > 0) && (newValue != oldValue))
{
var mp3Att = proper.GetCustomAttributes(typeof(MediaPropertyAttribute), false).FirstOrDefault();
var shellProper = mediaInfo.GetProperty("System." + mp3Att);
Console.WriteLine(mp3Att);
if (newValue == null) newValue = string.Empty;
SetPropertyValue(shellProper, newValue);
}
}
}
}
#region SetPropertyValue
static void SetPropertyValue(IShellProperty prop, object value)
{
if (prop.ValueType == typeof(string[])) //只读属性不会改变,故与实际类型不符的只有string[]这一种
{
string[] values = (value as string).Split(new char[] { ';' }, StringSplitOptions.RemoveEmptyEntries);
(prop as ShellProperty<string[]>).Value = values;
}
if (prop.ValueType == typeof(string))
{
(prop as ShellProperty<string>).Value = value as string;
}
else if (prop.ValueType == typeof(ushort?))
{
(prop as ShellProperty<ushort?>).Value = value as ushort?;
}
else if (prop.ValueType == typeof(short?))
{
(prop as ShellProperty<short?>).Value = value as short?;
}
else if (prop.ValueType == typeof(uint?))
{
(prop as ShellProperty<uint?>).Value = value as uint?;
}
else if (prop.ValueType == typeof(int?))
{
(prop as ShellProperty<int?>).Value = value as int?;
}
else if (prop.ValueType == typeof(ulong?))
{
(prop as ShellProperty<ulong?>).Value = value as ulong?;
}
else if (prop.ValueType == typeof(long?))
{
(prop as ShellProperty<long?>).Value = value as long?;
}
else if (prop.ValueType == typeof(DateTime?))
{
(prop as ShellProperty<DateTime?>).Value = value as DateTime?;
}
else if (prop.ValueType == typeof(double?))
{
(prop as ShellProperty<double?>).Value = value as double?;
}
}
#endregion
#region MediaPropertyAttribute
[AttributeUsage(AttributeTargets.Property, Inherited = false, AllowMultiple = true)]
sealed class MediaPropertyAttribute : Attribute
{
public string PropertyKey { get; private set; }
public MediaPropertyAttribute(string propertyKey)
{
this.PropertyKey = propertyKey;
}
public override string ToString()
{
return PropertyKey;
}
}
#endregion
}
}
使用这个文件的一个简单范例如下:
这个范例修改程序运行时目录下指定规范名字mp3文件的标题属性,当然其他属性也是类似的。
private void btn_2_Click(object sender, RoutedEventArgs e)
{
MediaTags mt = new MediaTags(Environment.CurrentDirectory);
for (int i = 1; i <= 16; i++)
{
string fileName = string.Format("{0}\\童林传_{1:000}.mp3", Environment.CurrentDirectory, i);
mt.Title = string.Format("童林传 {0:000}",i);
mt.Author = "单田芳";
mt.Commit(fileName);
}
}
参考资料:
用C#修改Mp3文件属性
http://www.cnblogs.com/TianFang/archive/2009/09/27/1574722.html
.NET Framework Client Profile是.NET Framework的裁剪版本。它面向客户端应用程序。它提供 Windows Presentation Foundation (WPF)、Windows 窗体、Windows Communication Foundation (WCF) 和 ClickOnce 功能的简化子集。这可以为面向 .NET Framework Client Profile 的 WPF、Windows 窗体、WCF 和控制台应用程序实现快速部署方案。
参看:http://msdn.microsoft.com/zh-cn/library/cc656912.aspx
在VS2010 中, 你默认建立的 WPF 程序就是 .Net Framework 4 Client Profile ,而不是 .Net Framework 4。昨天下午到今天早上, 我在为 System.Web 无法引用而苦恼的问题,就是它导致的。
VS2010中,我们在项目的属性下,可以看到项目有以下多种 Target framework 属性。
我今天碰到的 Sytem.Web 引用问题如下:
在用 VS2010 Beta2 书写一个WPF应用的时候,要使用 System.Web.HttpUtility.UrlEncode() 方法时,发现竟然无法增加对 System.Web 的引用。如下图:
.NET Reference 中没有 System.Web 组件。
使用 Browse 方式打开指定目录的文件 C:\Windows\Microsoft.NET\Framework\v4.0.21006\System.Web.dll 也不行。
如下图:
GAC 中也没有 4.0 版本的 System.Web 组件,如下图:
强制添加 C:\Windows\Microsoft.NET\Framework\v4.0.21006\System.Web.dll 到GAC 时会报错误:
---------------------------
Cannot Add Assembly
---------------------------
Unable to add the selected assembly. The assembly must have a strong name (name, version and public key).
---------------------------
确定
---------------------------
这个文件的信息如下:
但是VS2010 Beta2 建立的 Web 项目, 默认就有 System.Web 的引用, 这里没有任何问题,如下图:
查看这时候的 System.Web 属性,如下:
把 C:\Program Files\Reference Assemblies\Microsoft\Framework\.NETFramework\v4.0\System.Web.dll 放入 GAC也 会报错误:
---------------------------
Cannot Add Assembly
---------------------------
Unable to add the selected assembly. The assembly must have a strong name (name, version and public key).
---------------------------
确定
---------------------------
WPF程序 单独指向这个文件的引用, 也是跟上面的情况一样。也报错。
上面我碰到的这个问题,只需要简单的把项目的 Target framework 修改为 .Net Framework 4 就搞定了。