按照动画的形成方式,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/ 的初始化时间才需要这么久。
这里通过简单的一个播放器代码来学习如何在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
最近在处理一批下载的评书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
不透明度属性(Opacity)
Opacity属性用于指定刷子的不透明度。
Opacity 属性的值以 0.0 和 1.0 之间的值表示。默认情况下元素的 Opacity 属性为 1.0。
如果 Opacity 值为 0,则表示画笔完全透明;如果值为 1,则表示画笔完全不透明。
如果值为 0.5,则表示画笔的不透明度为 50%;如果值为 0.725,则表示画笔的不透明度为 72.5%,
依此类推。小于 0 的值将被视为 0,而大于 1 的值将被视为 1。
如下图的代码所示:
<Canvas>
<Rectangle Height="100" Width="150" Canvas.Left="30" Canvas.Top="30"
Stroke="Black" StrokeThickness="1" Fill="SkyBlue">
</Rectangle>
<Rectangle Height="100" Width="150" Canvas.Left="100" Canvas.Top="50"
Stroke="Black" StrokeThickness="1" Fill="SkyBlue" Opacity="0.5">
</Rectangle>
</Canvas>
演示效果为:
左上角矩形不透明度为默认的1,右下角矩形的不透明度为50%,所以重叠部分可以看到左上角的矩形。
不透明蒙板(OpacityMask)
有时候,我们对透明度的要求更复杂,并不是希望整个图像都是一个固定的不透明度,这时候就要用到半透明蒙板了。
如下图所示: 我们对一副荷花图片使用不透明蒙板,就可以让荷花图片出现部分区域显示,部分区域不显示的效果。
一段演示Xaml代码如下:
<Canvas>
<Rectangle Height="100" Width="150" Canvas.Left="30" Canvas.Top="30"
Stroke="Azure" StrokeThickness="1" Fill="SeaGreen">
<Rectangle.OpacityMask>
<LinearGradientBrush>
<GradientStop Offset="0.56" Color="#00000000" />
<GradientStop Offset="0.25" Color="#FF000000" />
</LinearGradientBrush>
</Rectangle.OpacityMask>
</Rectangle>
</Canvas>
演示效果如下图:
这里当然也可以是可以使用 前一篇中提到的 径向渐变刷子(RadialGradientBrush)。如下面例子:
<Canvas>
<Rectangle Height="100" Width="150" Canvas.Left="30" Canvas.Top="30"
Stroke="Azure" StrokeThickness="1" Fill="SeaGreen">
<Rectangle.OpacityMask>
<RadialGradientBrush GradientOrigin="0.7,0.5" Center="0.5,0.5"
RadiusX="0.5" RadiusY="0.4">
<GradientStop Color="#00000000" Offset="0" />
<GradientStop Color="#FF000000" Offset="1" />
</RadialGradientBrush>
</Rectangle.OpacityMask>
</Rectangle>
</Canvas>
执行效果如下图:
修剪图像(Clip)
有时候我们希望把一个图像作部分修剪,比如下面一副荷花的照片,我们希望修剪成椭圆形。
(没有经过剪切的图像)
(带有椭圆型的剪辑区域的图像)
它的剪切代码如下:
<Image
Source="sampleImages\Waterlilies.jpg"
Width="200" Height="150" HorizontalAlignment="Left">
<Image.Clip>
<EllipseGeometry
RadiusX="100"
RadiusY="75"
Center="100,75"/>
</Image.Clip>
</Image>
当然除了 EllipseGeometry(椭圆剪切类), 我们还可以使用其它剪切类,比如下面的 RectangleGeometry
<Canvas>
<Ellipse Height="200" Width="200" Canvas.Left="30" Canvas.Top="30" Stroke="Black" StrokeThickness="10" Fill="SlateBlue">
<Ellipse.Clip>
<RectangleGeometry Rect="0,0,100,100"/>
</Ellipse.Clip>
</Ellipse>
</Canvas>
上述代码执行效果:
注意,剪切是取得两个的重叠部分。
上面代码我们可以看到是通过设置Geometry的子类来做实现剪切的, Geometry 的子类如下:
System.Object
System.Windows.Threading.DispatcherObject
System.Windows.DependencyObject
System.Windows.Freezable
System.Windows.Media.Animation.Animatable
System.Windows.Media.Geometry
System.Windows.Media.CombinedGeometry
System.Windows.Media.EllipseGeometry
System.Windows.Media.GeometryGroup
System.Windows.Media.LineGeometry
System.Windows.Media.PathGeometry
System.Windows.Media.RectangleGeometry
System.Windows.Media.StreamGeometry
图像变形
所有的UI元素都可以变形,包括以下几种变形:
- 旋转变形(RotateTransform): 根据特定的角度进行物体旋转;
- 倾斜变形(SkewTransform):根据指定的x-y刻度进行倾斜;
- 刻度变形(ScaleTransform):根据横向和纵向进行放大和缩小;
- 翻转变形(TranslateTransform): 水平或横向移动物体;
旋转变形(RotateTransform)
在 二维 x-y 坐标系内围绕指定点按照顺时针方向旋转对象。
演示代码:
<Canvas>
<Rectangle Height="100" Width="100" Canvas.Left="70" Canvas.Top="10" Fill="Black">
<Rectangle.RenderTransform>
<RotateTransform Angle="27" />
</Rectangle.RenderTransform>
</Rectangle>
</Canvas>
演示效果:
倾斜变形(SkewTransform)
二维扭曲
演示代码
<Canvas>
<Rectangle Height="100" Width="100" Canvas.Left="70" Canvas.Top="10"
Fill="Red">
<Rectangle.RenderTransform>
<SkewTransform AngleX="15" />
</Rectangle.RenderTransform>
</Rectangle>
</Canvas>
演示效果
刻度变形(ScaleTransform)
在 二维 x-y 坐标系内缩放对象。
演示代码
<Canvas>
<Rectangle Height="100" Width="100" Canvas.Left="70" Canvas.Top="10"
Fill="Red">
<Rectangle.RenderTransform>
<ScaleTransform ScaleX="1.3" ScaleY=".5" />
</Rectangle.RenderTransform>
</Rectangle>
</Canvas>
演示效果
翻转变形(TranslateTransform)
在 二维 x-y 坐标系中平移(移动)对象。
演示代码
<Canvas>
<Rectangle Height="50" Width="50"
Fill="#CCCCCCFF" Stroke="Blue" StrokeThickness="2"
Canvas.Left="100" Canvas.Top="100">
<Rectangle.RenderTransform>
<TranslateTransform X="50" Y="50" />
</Rectangle.RenderTransform>
</Rectangle>
</Canvas>由于是位移,演示效果截图会不明显,就不提供演示效果图了。
组合应用
我们当然可以把上面的变形组合应用,比如下面的例子
演示代码
<Canvas>
<Rectangle Height="100" Width="100" Canvas.Left="70" Canvas.Top="10"
Fill="Green">
<Rectangle.RenderTransform>
<TransformGroup>
<RotateTransform Angle="45" />
<ScaleTransform ScaleX=".5" ScaleY="1.2" />
<SkewTransform AngleX="30"/>
</TransformGroup>
</Rectangle.RenderTransform>
</Rectangle>
</Canvas>
演示效果

下面是刷子的一些内容:
纯色刷子(SolidColorBrush)
Silverlight中使用纯色刷子可以有多种方式如下,
<object property="predefinedColor"/>
- or -
<object property="#rgb"/>
- or -
<object property="#argb"/>
- or -
<object property="#rrggbb"/>
- or -
<object property="#aarrggbb"/>
- or -
<object property="sc# scA,scR,scG,scB"/>
分别代表的意思如下:
predefinedColor Colors 类预定义的颜色之一。
rgb 一个三位数的十六进制数字。第一位数指定颜色的 R 值,第二位数指定 G 值,第三位数指定 B 值。例如 00F。
argb 一个四位数的十六进制数字。第一位数指定颜色的 A 值,第二位数指定其 R 值,下一位数指定 G 值,最后一位数指定其 B 值。例如 F00F。
rrggbb 一个六位数的十六进制数字。前两位数指定颜色的 R 值,下两位数指定其 G 值,最后两位数指定其 B 值。例如 0000FF。
aarrggbb 一个八位数的十六进制数字。前两位数指定颜色的 A 值,下两位数指定其 R 值,接下来的两位指定其 G 值,最后两位数指定其 B 值。例如 FF0000FF。
scA System.Single 颜色的 ScA 值。
scR System.Single 颜色的 ScR 值。
scG System.Single 颜色的 ScG 值。
scB System.Single 颜色的 ScB 值。
演示代码如下:
<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">
<Canvas>
<Ellipse Height="90" Width="90" Canvas.Left="10" Canvas.Top="10" Fill="black"/>
<Ellipse Height="90" Width="90" Canvas.Left="110" Canvas.Top="10" Fill="#000000"/>
<Ellipse Height="90" Width="90" Canvas.Left="210" Canvas.Top="10" Fill="#000" />
<Ellipse Height="90" Width="90" Canvas.Left="310" Canvas.Top="10" Fill="sc# 0.0 0.0 1.0" />
<Ellipse Height="90" Width="90" Canvas.Left="10" Canvas.Top="110" Fill="#ff000000"/>
<Ellipse Height="90" Width="90" Canvas.Left="110" Canvas.Top="110">
<Ellipse.Fill>
<SolidColorBrush Color="Black"/>
</Ellipse.Fill>
</Ellipse>
<Ellipse Height="90" Width="90" Canvas.Left="210" Canvas.Top="110" Fill="#f000"/>
</Canvas>
</UserControl>
演示效果如下:
需要注明的是 WPF 中支持的另外一种颜色定义方式,如下,Silverlight 是不支持的:
<!-- This rectangle fill uses the sRGB color profile and values to create a complete opaque blue. -->
<Rectangle Width="50" Height="50" Margin="10">
<Rectangle.Fill>
<SolidColorBrush Color="ContextColor
file://C:/WINDOWS/system32/spool/drivers/color/sRGB%20Color%20Space%20Profile.icm
1.0,0.0,0.0,1.0"/>
</Rectangle.Fill>
</Rectangle>
线性渐变刷子(LinearGradientBrush)
默认的线性渐变是沿对角方向进行的。默认情况下,线性渐变的 StartPoint 是被绘制区域的左上角 (0,0),其 EndPoint 是被绘制区域的右下角 (1,1)。所得渐变的颜色是沿着对角方向路径插入的。如下图显示了对角方向的渐变。图中添加了一条线,用于突出显示渐变从起点到终点的插值路径。
.png)
具有突出显示的渐变停止点的对角线性渐变,如下图:
.png)
演示代码
<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">
<Canvas>
<!-- 水平渐变 //-->
<Rectangle Width="140" Height="70" Canvas.Left="155" Canvas.Top="10">
<Rectangle.Fill>
<LinearGradientBrush StartPoint="0,0" EndPoint="1,0">
<GradientStop Color="Red" Offset="0.0" />
<GradientStop Color="LimeGreen" Offset="1.0" />
</LinearGradientBrush>
</Rectangle.Fill>
</Rectangle>
<!-- 垂直渐变 //-->
<Rectangle Width="140" Height="70" Canvas.Left="300" Canvas.Top="10">
<Rectangle.Fill>
<LinearGradientBrush StartPoint="0,0" EndPoint="0,1">
<GradientStop Color="Blue" Offset="0.0" />
<GradientStop Color="LimeGreen" Offset="1.0" />
</LinearGradientBrush>
</Rectangle.Fill>
</Rectangle>
<!-- 具有突出显示的渐变停止点的对角线性渐变 //-->
<Rectangle Width="140" Height="70" Canvas.Left="450" Canvas.Top="10">
<Rectangle.Fill>
<LinearGradientBrush>
<GradientStop Color="Yellow" Offset="0.0" />
<GradientStop Color="Red" Offset="0.25" />
<GradientStop Color="Blue" Offset="0.75" />
<GradientStop Color="LimeGreen" Offset="1.0" />
</LinearGradientBrush>
</Rectangle.Fill>
</Rectangle>
</Canvas>
</UserControl>
执行效果
其中的一些属性说明
Offset 渐变停止点在渐变向量中的位置。值 0.0 指定停止点位于渐变向量的开始处;而值 1.0 指定停止点位于渐变向量的末尾处。如果没有设置这个值,则默认是0.0。
径向渐变刷子(RadialGradientBrush)
焦点定义渐变的开始,而圆定义渐变的终点。
范例代码:
<!-- This rectangle is painted with a diagonal linear gradient. -->
<Rectangle Width="200" Height="100">
<Rectangle.Fill>
<RadialGradientBrush
GradientOrigin="0.5,0.5" Center="0.5,0.5"
RadiusX="0.5" RadiusY="0.5">
<GradientStop Color="Yellow" Offset="0" />
<GradientStop Color="Red" Offset="0.25" />
<GradientStop Color="Blue" Offset="0.75" />
<GradientStop Color="LimeGreen" Offset="1" />
</RadialGradientBrush>
</Rectangle.Fill>
</Rectangle>
效果图如下:
.png)
其中一些属性的意思如下
GradientOrigin : 定义渐变开始的二维焦点的位置。(下面演示图中的红点),可以在径向渐变最外面的圆外面,渐变的二维焦点的位置。默认值为 (0.5, 0.5)。
Center :径向渐变最外面的圆的圆心,渐变圆的默认中心点位置是 (0.5, 0.5)。
RadiusX:径向渐变最外面的圆的水平半径,默认值为 0.5。
RadiusY:径向渐变最外面的圆的垂直半径,默认值为 0.5。
注意,上面坐标系是相对于边界框的:0 表示边界框的 0%,1 表示边界框的 100%。可以通过将 MappingMode 属性设置为值 Absolute 来更改此坐标系。绝对坐标系不是相对于边界框的。值直接在本地坐标系中解释。
我们可以看下面的例子来理解具有不同 GradientOrigin、Center、RadiusX 和 RadiusY 设置的 RadialGradientBrush。
.gif)
图像绘制刷子(ImageBrush)
实例代码:
<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">
<Canvas>
<Rectangle Height="180" Width="90" Canvas.Left="110" Canvas.Top="36"
Stroke="Bisque" StrokeThickness="1">
<Rectangle.Fill>
<ImageBrush ImageSource="7116_2004321149418456.jpg" Stretch="Uniform"/>
</Rectangle.Fill>
</Rectangle>
</Canvas>
</UserControl>
效果如下:这里的原始图片是一个380*486 的大尺寸图片。
其中的 Stretch 属性用于控制图像的拉伸、对齐和平铺方式。默认值为 Fill。
Stretch 属性可以设置为以下几种值:
- None:内容不拉伸以填充图块。
- Uniform:内容进行缩放,以适合图块尺寸。但保留该内容的纵横比。
- UniformToFill:对内容进行缩放,从而可以完全填充输出区域,但保持其原始纵横比。
- Fill:内容进行缩放,以适合图块。由于内容的高度和宽度独立进行缩放,因此可能不会保持该内容的原始纵横比。也就是说,为了完全填充输出区域,内容可能会失真。
下面的图像阐释了不同的 Stretch 设置。
参考资料:
Color 结构
http://msdn.microsoft.com/zh-cn/library/system.windows.media.color.aspx
LinearGradientBrush 类
http://msdn.microsoft.com/zh-cn/library/system.windows.media.lineargradientbrush.aspx
使用纯色和渐变进行绘制概述
http://msdn.microsoft.com/zh-cn/library/ms754083.aspx
最近通过看WebCase的录像来学习Silverlight,为了避免学了后面忘了前面,把录像中的重点整理成笔记记录下来。
通过录像来学习Silverlight,我推荐看苏鹏讲的Silverlight探秘系列课程,这个系列课程已经讲到50多讲了。由浅入深,而且连绵不断,确实很有帮助。
这篇博客是对 苏鹏讲解的“Silverlight探秘系列课程(3):绘制与着色”的笔记,网上有很多地方都有这个课程下载,我就不给链接地址了。不过推荐使用 iReaper 来下载讲座视频。
椭圆
<Ellipse
Height="200" Width="200"
Stroke="Black" StrokeThickness="10" Fill="SlateBlue" />
- Stroke 属性来指定用于绘制椭圆轮廓的 Brush。
- StrokeThickness 属性指定椭圆轮廓的粗细。
- Fill 属性来指定用于绘制椭圆内部区域的 Brush。
相对于父控件的定位可以有下面2种:
情况1:
<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">
<Ellipse
Height="200" Width="250"
Stroke="Black" StrokeThickness="10" Fill="SlateBlue"
HorizontalAlignment="Left" VerticalAlignment="Top" Margin="10,12,0,0" />
</UserControl>
情况2:
<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">
<Canvas xmlns="http://schemas.microsoft.com/client/2007"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml">
<Ellipse
Height="200" Width="250"
Stroke="Black" StrokeThickness="10" Fill="SlateBlue"
Canvas.Left="50" Canvas.Top="30" />
</Canvas>
</UserControl>
图像的遮挡
如果没有指定ZIndex, 则后画的遮挡了先画的;如果指定了ZIndex :则给定 element 的 value 越大,element 在前景中出现的可能性就越大。同样,如果 element 具有一个相对较低的 value,则 element 将可能出现在背景中。例如,具有 value 为 5 的 element 将出现在具有 value 为 4 的 element 的上方,后者又将出现在具有值为 3 的 element 的上方,依此类推。允许负值,并且这些负值也适用此优先级模式。 如下图,
<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">
<Canvas >
<Ellipse
Canvas.ZIndex="3"
Canvas.Left="5" Canvas.Top="5"
Height="200" Width="200"
Stroke="Black" StrokeThickness="10" Fill="Silver" />
<Ellipse
Canvas.ZIndex="2"
Canvas.Left="50" Canvas.Top="50"
Height="200" Width="200"
Stroke="Black" StrokeThickness="10" Fill="DeepSkyBlue" />
<Ellipse
Canvas.ZIndex="-1"
Canvas.Left="95" Canvas.Top="95"
Height="200" Width="200"
Stroke="Black" StrokeThickness="10" Fill="Lime" />
</Canvas>
</UserControl>
效果图如下:

线条
<Line X1="280" Y1="10" X2="10" Y2="280" Stroke="Black" StrokeThickness="5"/>
使用 Line 元素的 X1 和 Y1 属性设置线条起点,并使用 Line 元素的 X2 和 Y2 属性来设置线条终点。最后,设置 Line 元素的 Stroke 和 StrokeThickness,因为没有笔画的线条是看不见的。为线条设置 Fill 元素将毫无意义,因为线条没有内部区域。
矩形
<Rectangle
Width="200"
Height="100"
Fill="Blue"
Stroke="Black" StrokeThickness="4"
RadiusX="20" RadiusY="20"/>
若要绘制矩形圆角,请指定可选的 RadiusX 和 RadiusY 属性。RadiusX 和 RadiusY 属性设置用于使矩形的角变圆的椭圆的 x 轴和 y 轴半径。这两个的默认值为0,即,没有圆角。
封闭多边形
<Polygon Points="10,10 10,110 110,110 110,85 45,10"
Stroke="Black" StrokeThickness="10" Fill="LightBlue"/>

注意,在可扩展应用程序标记语言 (XAML) 中,我们使用由逗号分隔的 x 和 y 坐标对组成的空格分隔列表来设置 Points 属性。即使用简单表示法, x1,y1 x2,y2 ... xn,yn。
未封闭多边形
我们还是使用上面的点,注意,这时候 Fill 意义不大,我们就没设置,这个值也是可设置的
<Polyline Points="10,10 10,110 110,110 110,85 45,10"
Stroke="Black" StrokeThickness="10" />
效果如下:
增加 Fill="LightBlue" 带来的效果如下:
路径
<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">
<Canvas>
<Path Data="M0,0 L11.5,0 11.5,30 5.75,40 0,30z"
Stroke="Black" Fill="SlateBlue" Canvas.Left="118" Canvas.Top="57"
UseLayoutRounding="False" />
<Path Data="M 10,100 C 10,300 300,-200 250,100z"
Stroke="Red" Fill="Orange" Canvas.Left="54" Canvas.Top="167"
UseLayoutRounding="False" />
<Path Data="M 0,200 L100,200 50,50z"
Stroke="Black" Fill="Gray" Canvas.Left="397" Canvas.Top="66"
UseLayoutRounding="False" />
</Canvas>
</UserControl>
上面 Path 元素的 Data 属性描述的是路径,这里的路径描述使用几何迷你语言命令,这个语言命令规范如下图:

参考资料:
WPF 中的形状和基本绘图概述
http://msdn.microsoft.com/zh-cn/library/ms747393.aspx
前一篇文章 “WPF,Silverlight 中的 xmlns,xmlns:x” 中提到了: 目前对于大多数的 WPF 应用程序以及 SDK 的 WPF 部分中给出的几乎所有示例版本的WPF,Silverlight 2.0 (含)以后的开发工具, 默认的 xmlns 命名空间均映射到 WPF 命名空间 http://schemas.microsoft.com/winfx/2006/xaml/presentation。
对于Silverlight来说,从 Silverlight 2 (含)以及更高版本的 Silverlight 开始,大多数工具都默认使用 http://schemas.microsoft.com/winfx/2006/xaml/presentation 命名空间来生成 Silverlight 的 XAML 文件。不过,Silverlight 仍然支持 http://schemas.microsoft.com/client/2007 XAML 命名空间(这是用于 Silverlight 1.0 的 XAML 命名空间)。
比起WPF,Silverlight 对设置 xmlns 值加了以下限制:
比起WPF,Silverlight 对映射 xmlns 值的程序集和命名空间加了以下限制:
- 该程序集必须是 mscorlib(以支持 sys: 情况)或 XAP 文件中某个程序集的名称。(该程序集不能是未使用 XAP 包部署的程序集。)
- 映射中的该程序集名称末尾不能包含".dll"。
参考资料:
Silverlight 和 WPF 之间的 XAML 处理差异
http://msdn.microsoft.com/zh-cn/library/cc917841(VS.95).aspx (中文)
http://msdn.microsoft.com/en-us/library/cc917841(VS.95).aspx (英文)
我们在每一个XAML文件中都可以看到下面的信息:
Silverlight应用
<UserControl
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" >
</UserControl>
WPF应用
<Window x:Class="WpfApplication1.Window1"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
Title="Window1" Height="300" Width="300">
<Grid>
</Grid>
</Window>
XAML文件首先是一个XML文件。为了验证自己和子元素的合法性,即保证在XML文档中的元素和属性名字的合法性,XML规范了XML命名空间,参看下面文档:
http://www.opendl.com/openxml/w3/TR/xml-names/xml-names-gb.html
上面例子中,xmlns 属性专门指默认的 xmlns 命名空间。在默认的 xmlns 命名空间中,可以不使用前缀指定标记中的对象元素。目前对于大多数的 WPF 应用程序以及 SDK 的 WPF 部分中给出的几乎所有示例版本的WPF,Silverlight 2.0 (含)以后的开发工具, 默认的 xmlns 命名空间均映射到 WPF 命名空间 http://schemas.microsoft.com/winfx/2006/xaml/presentation。
xmlns:x 属性指示另外一个 xmlns 命名空间,该命名空间映射 XAML 语言命名空间 http://schemas.microsoft.com/winfx/2006/xaml 。在具有此映射的文件的标记中引用时,XAML 规范定义的所需语言组件带有 x: 前缀。
如下图所示:
| <Page |
根元素的开始对象元素 |
| xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation" |
默认 (WPF) 命名空间
就类似于如下的C#代码:
using System.Windows;
using System.Windows.Automation
using System.Windows.Controls;
using System.Windows.Controls.Primitives
using System.Windows.Data;
using System.Windows.Documents;
using System.Windows.Input;
using System.Windows.Media;
using System.Windows.Media.Imaging;
using System.Windows.Navigation;
using System.Windows.Shapes;
using System.Windows.Forms.Integration
using System.Windows.Ink
using System.Windows.Media.Animation
using System.Windows.Media.Effects
using System.Windows.Media.Media3D
using System.Windows.Media.TextFormatting
|
| xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" |
这是XAML语言命名空间,用于映射System.Windows.Markup命名空间中的类型,而且它也定义了XAML编译器或解析器中的一些特殊的指令。这些指令通常是作为XML元素的特性出现的,因此它们看上去像宿主元素的属性,但实际上并不是如此。 |
| x:Class="MyNamespace.MyPageCode" |
分部类声明,它将标记连接到此分部类中定义的任何代码隐藏 |
| > |
根的对象元素的末尾,但由于页面包含子元素,因此尚未结束 |
参考资料:
WPF学习笔记1:XAML之NameSpace
http://www.cnblogs.com/jacksonyin/archive/2008/02/25/1080336.html
HTML xmlns 属性
http://www.w3school.com.cn/tags/tag_prop_xmlns.asp
XAML 概述
http://msdn.microsoft.com/zh-cn/library/ms752059.aspx
《WPF揭秘》 2.3 命名空间
http://book.csdn.net/bookfiles/677/10067721293.shtml
上周五CSDN的论坛发布了一个新的版本,发布后发现有个奇怪的bug。一个WebService的调用,调用方传递的信息一切正常,接受方接受到的就是null。整个过程没有任何异常发生。
想尽方法调试进去后,发现如下的怪异情况:
message 变量明明不是空,但是它却执行到里面了。这个问题跟我之前的一篇Blog提到的一个情况几乎一样。
代码优化出的问题
采用同样的方法,把 Release 时默认选中的 Optimize code 功能取消,就解决了这个问题。如下图鼠标的位置。
这次碰到的情况跟上次 “代码优化出的问题” 还是有差异的, 这次没有任何提醒,要不是我以前碰到过这样的问题,真是不知道啥时候才能解决这个问题。
这几种行为型设计模式分别为:
- Template Method 模板方法模式
- Command 命令模式
- Interpreter 解释器模式
- Mediator 中介者模式
- Iterator 迭代器模式
- Observer 观察者模式
- Chain Of Responsibility 职责链模式
- Memento 备忘录模式
- State 状态模式
- Strategy 策略模式
- Visitor 访问者模式
对比:
- Template Method模式封装算法结构,支持算法子步骤变化
- Strategy 策略模式注重封装算法,支持算法的变化
- State模式注重封装与状态相关的行为,支持状态的变化
- Memento备忘录模式注重封装对象状态变化,支持状态保存/恢复
- Mediator 中介者模式注重封装对象间的交互,支持对象交互的变化
- Chain Of Responsibility 模式注重封装对象责任,支持责任的变化
- Command 模式注重将请求封装为对象,支持请求的变化
- Iterator 迭代器模式注重封装集合对象内部结构,支持集合的变化
- Interpreter 解释器模式注重封装特定领域变化,支持领域问题的频繁变化
- Observer 模式注重封装对象通知,支持通信对象的变化
- Visitor 模式注重封装对象操作变化,支持在运行时为类层次结构动态添加新的操作。
参考:
学习笔记:7种结构型设计模式简单对比
http://blog.joycode.com/ghj/archive/2009/06/08/115607.joy
学习笔记:5种创建型设计模式简单对比
http://blog.joycode.com/ghj/archive/2009/05/08/115570.joy
Gof 23 中模式关系图
http://www.blogjava.net/images/blogjava_net/fantasyamin/design_pattern_relation.png
看下面代码,编译会不会报错,如果不报错,执行的结果是啥?
using System;
class Program
{
static void Main(string[] args)
{
Console.WriteLine(GetIP());
Console.WriteLine(GetIP("a"));
Console.WriteLine(GetIP("a", "b"));
Console.WriteLine(GetIP("a", "b","c"));
Console.ReadLine();
}
public static string GetIP(params string[] ipArr)
{
return "3";
}
public static string GetIP(string ip)
{
return "2";
}
public static string GetIP()
{
return "1";
}
}
答案:
编译不会报错,执行结果:
1
2
3
3
使用ILdasm察看,显然,params 是一个语法糖,编译时,如果发现有完全匹配的函数,就优先调用完全匹配的函数,否则就调用带params 参数的函数。
这个逻辑是编译时确定的,执行时根本不考虑这个问题。
最近在做多线程处理数据库的程序时,这个程序总是会报如下错误:
超时时间已到。超时时间已到,但是尚未从池中获取连接。出现这种情况可能是因为所有池连接均在使用,并且达到了最大池大小。
System.Data
在 System.Data.ProviderBase.DbConnectionFactory.GetConnection(DbConnection owningConnection)
在 System.Data.ProviderBase.DbConnectionClosed.OpenConnection(DbConnection outerConnection, DbConnectionFactory connectionFactory)
在 System.Data.SqlClient.SqlConnection.Open()
仔细用 SQL Server Management Studio 中的 Activity Monitor 查看数据库链接,竟然是只有2,3个数据库链接时,就报上述错误,很是怪异。
一步步删除掉代码,反复试验后,竟然是数据库链接字符串中的 Connect Timeout=0 来作怪的。
比如下述数据库链接字符串就会出现上述问题,
Persist Security Info=False;Integrated Security=SSPI;Initial Catalog=DB1;server=(local);Connect Timeout=0
而把数据库链接字符串修改为
Persist Security Info=False;Integrated Security=SSPI;Initial Catalog=DB1;server=(local)
就不会有问题了。
这个bug在微软的反馈中可以看到,如下:
https://connect.microsoft.com/VisualStudio/feedback/ViewFeedback.aspx?FeedbackID=331882&wa=wsignin1.0
其中微软的官方反馈是:
The fix was submitted to the source branch of the next major .Net release.
由 Microsoft 在 2008/8/15 11:31 发送
照这么说,估计.net 4.0 中会修复这个bug。
参考资料:
Why Does a Connection Pool Overflow?
http://msdn.microsoft.com/en-us/library/aa175863%28SQL.80%29.aspx
Trace a SqlConnection - Connection Pooling issues
http://www.experts-exchange.com/Software/Server_Software/Web_Servers/Q_22589733.html
Fixing connection pooling timeout exceptions on third-party code
http://www.cuttingedge.it/blogs/steven/pivot/entry.php?id=14
Connection Pooling and the "Timeout expired" exception FAQ
http://blogs.msdn.com/angelsb/archive/2004/08/25/220333.aspx
ADO.NET Connection Pooling at a Glance
http://blog.csdn.net/tuwen/archive/2008/05/28/2490299.aspx
FIX: Sp_reset_connection Does Not Reset the Rowcount Settings for the DELETE and UPDATE Statements
http://support.microsoft.com/kb/310617/ ADO.NET数据连接池
http://tech.it168.com/db/s/2006-10-18/200610181013413.shtml
关于ADO.Net连接池(Connection Pool)的一些个人见解
http://www.cnblogs.com/rickie/archive/2004/10/02/48546.aspx
How to: Open Activity Monitor (SQL Server Management Studio)
http://msdn.microsoft.com/en-us/library/ms175518.aspx
Connect Timeout = 0 / Connection Pool Timeout
https://connect.microsoft.com/VisualStudio/feedback/ViewFeedback.aspx?FeedbackID=331882&wa=wsignin1.0
昨天花了2个小时调试有时候启动了定时轮询,有时候没有启用定时轮询的bug,最后发现竟然是作用域的问题,比较汗颜,下面就是有bug的这个代码的简单写法:
using System;
using System.Timers;
using System.Threading;
class Program
{
static void Main(string[] args)
{
Begin();
Console.ReadLine();
}
public static void Begin()
{
TimerCallback callbackMethod = new TimerCallback(Program.Check);
int pollCycleInMilliseconds = 180000;
System.Threading.Timer pollTimer = new System.Threading.Timer(
callbackMethod, null, pollCycleInMilliseconds, pollCycleInMilliseconds);
}
public static void Check(object notUsed)
{
Console.Write(DateTime.Now);
}
}
出bug是因为这里的 System.Threading.Timer pollTimer 是一个局部变量,这个变量出了他的作用域,可能会被销毁以及垃圾回收,这时候,自然就没有了定时轮询的触发了。
实际的代码比这里要复杂很多,是个WEB应用。一直没想到是作用域的问题才导致花了这么长时间找bug。把这个我碰到的bug记录下来,让碰到类似问题的人也好有思路提醒。
我做的一个Windows Form 程序碰到一个很怪异的多线程情况,最后检查进去竟然是部分代码的数据库链接没有关闭导致的。
我的这个程序是多线程程序,每个线程不间断的从数据库中取得数据,然后对取出的数据进行处理,一直循环到没有需要处理的数据为至。每个线程的循环是上万次的,即,每个线程上万次的数据库链接打开操作。
这个程序碰到怪异的现象是:
在A服务器上,没有任何问题,在B服务器上程序开一个线程没有任何问题,开多个线程则只有一个线程没问题,其他线程都有问题。
对每一个线程都作 try catch 拦截,就会看到出错线程报如下错误:
超时时间已到。超时时间已到,但是尚未从池中获取连接。出现这种情况可能是因为所有池连接均在使用,并且达到了最大池大小。
System.Data
在 System.Data.ProviderBase.DbConnectionFactory.GetConnection(DbConnection owningConnection)
在 System.Data.ProviderBase.DbConnectionClosed.OpenConnection(DbConnection outerConnection, DbConnectionFactory connectionFactory)
在 System.Data.SqlClient.SqlConnection.Open()
从这个异常就可以看到,是数据库链接对象池中没有可用的链接了。
再分析下去,就是其中一部分从数据库中读数据的代码,没有调用 SqlConnection.Close。
分析我这个程序出现的规律:
单线程,不主动调用 SqlConnection.Close
这时候竟然数据库链接池不会报没有可用资源:
数据库链接对象池的默认最大容量为100,单线程对数据库链接打开的请求超过10000的。
显然这种情况下,虽然没有手工SqlConnection.Close,但是这些数据库链接SqlConnection还是回到了数据库链接对象池中了。
多线程时,不主动调用 SqlConnection.Close。
只有一个线程在跑,其他线程都报上述错误。
显然,多线程时,数据库链接对象池都被一个线程占住了,其他线程获得不了可用分数据库链接。
这个多线程怪异的特征,之前一直没想到会是SqlConnection.Close导致的,一直在其他方面想可能的问题。这次经验教训后,心得就是:
- try catch 只能拦截当前线程的异常,它拦截不到其他线程的。
- 多人协作开发,并且程序经过多次修改bug后,完全有可能出现某些情况下,没有关闭数据库链接的情况,这种情况如何避免,是必须深思的一个问题。
- 单线程,不主动关闭SqlConnection,有可能仍然不报错误,但是多线程下,就可能报错误了。
参考资料:
FIX: Error message when a "System.Data" thread tries to open a pooled connection in the .NET Framework 2.0: "Timeout expired. The timeout period elapsed prior to obtaining a connection from the pool
http://support.microsoft.com/kb/948868
http://support.microsoft.com/kb/948868/en-us
使用连接池
http://msdn.microsoft.com/zh-cn/library/8xx3tyca(VS.80).aspx
Connection Pooling for the .NET Framework Data Provider for SQL Server
http://msdn.microsoft.com/en-us/library/8xx3tyca(vs.71).aspx
知识点:
- Singleton 模式解决的是实体对象个数的问题。
- 整个创建型设计模式,除了Singleton,其他4个都是解决“如何创建易变类的实体对象”的问题。
- Factory Method,Abstract Factory ,Builder 都需要一个额外的工厂类来负责实例化“易变对象”
- Factory Method 解决“单个对象”的需求变化。
- Abstract Factory 解决“系列对象”的需求变化。
- Builder 解决“对象部分”的需求变化。构造的算法骨架稳定,各个步骤易变。
- Prototype 采用的“原型克隆”的方法来解决这个问题。(可以认为是一个特殊的工厂类)
- Factory Method,Abstract Factory ,Prototype 这三个创建型模式相互转换的难度很低。能用Abstract Factory实行的,我们肯定也可以用Factory Method和Prototype 。如何区分他们的呢?
- 容易 克隆的类才方便用 Prototype ;是否这个类可用MemberwiseClone,是否可序列化,反序列化?
- 单个对象和多个对象来区分Factory Method,Abstract Factory
我们如何选择那个设计模式来创建“易变类”呢?
如果遇到“易变类”,起初的设计通常是从Factory Method 开始,当遇到更多更复杂的变化时,再考虑重构为其他三种工厂模式(Abstract Factory ,Builder ,Prototype ),后这三种区别看上面的知识点。
参考资料:
李建忠老师Webcast讲解的“C#面向对象设计模式纵横谈”
http://msevents.microsoft.com/CUI/EventDetail.aspx?EventID=1032328034&Culture=zh-CN
MemberwiseClone 方法创建一个浅表副本,具体来说就是创建一个新对象,然后将当前对象的非静态字段复制到该新对象。如果字段是值类型的,则对该字段执行逐位复制。如果字段是引用类型,则复制引用但不复制引用的对象;因此,原始对象及其复本引用同一对象。
下面的代码就是演示这个问题:
using System;
using System.IO;
using System.Runtime.Serialization.Formatters.Binary;
namespace CloneDemo
{
[Serializable]
class DemoClass
{
public int i = 0;
public int[] iArr = { 1, 2, 3 };
public DemoClass Clone1()
{
return this.MemberwiseClone() as DemoClass;
}
public DemoClass Clone2()
{
MemoryStream stream = new MemoryStream();
BinaryFormatter formatter = new BinaryFormatter();
formatter.Serialize(stream, this);
stream.Position = 0;
return formatter.Deserialize(stream) as DemoClass;
}
}
class Program
{
static void Main(string[] args)
{
DemoClass a = new DemoClass();
a.i = 10;
a.iArr = new int[] { 8, 9, 10 };
DemoClass b = a.Clone1();
DemoClass c = a.Clone2();
// 更改 a 对象的iArr[0], 导致 b 对象的iArr[0] 也发生了变化
a.iArr[0] = 88;
Console.WriteLine("MemberwiseClone");
Console.WriteLine(b.i);
foreach (var item in b.iArr)
{
Console.WriteLine(item);
}
Console.WriteLine("Clone2");
Console.WriteLine(c.i);
foreach (var item in c.iArr)
{
Console.WriteLine(item);
}
Console.ReadLine();
}
}
}
Singleton模式是最简单的模式,比较汗颜的是自己一直以来使用的是单线程的Singleton模式,最近在听了李建忠老师的模式讲座录像后,才发现自己一直没注意到这点。这个录像讲座在后面给出了链接地址: C#面向对象设计模式纵横谈(2):Singleton 单件(创建型模式)
下面内容整理自李建忠老师的讲课内容:
单线程的Singleton模式
public class Singleton
{
private static Singleton instance;
private Singleton() { }
public static Singleton Instance
{
get
{
if (instance == null)
instance = new Singleton();
return instance;
}
}
}
要点:
- Singleton模式中的实例构造器可以设置为protected以允许子类派生。
- Singleton模式一般不要支持ICloneable接口,因为这可能会导致多个对象实例,与Singleton模式的初衷违背。
- Singleton模式一般不要支持序列化,因为这也有可能导致多个对象实例,同样跟Singleton模式的初衷违背。
- Singleton模式只考虑到了对象创建的管理,并没有考虑对象销毁的管理。就支持垃圾回收的平台和对象的开销来讲,我们一般没必要对其销毁进行特殊的管理。
- 上述代码不支持多线程环境,上述代码在多线程下,仍然有可能得到Singleton类的多个实例。
多线程的Singleton模式
public class Singleton
{
private static volatile Singleton instance = null;
private static object lockHelper = new object();
private Singleton() { }
public static Singleton Instance
{
get
{
if (instance == null)
{
lock (lockHelper)
{
if (instance == null)
{
instance = new Singleton();
}
}
}
return instance;
}
}
}
代码说明:
- volatile C#关键字作用,简单来说,编译器编译我们的代码时候,会对代码作一些优化,进而对代码进行了微调,使用volatile关键字就可以避免这个微调。继而严格意义上保证不会产生多线程。更详细的关于volatile 的说明,请看参考资料。
- 双检查加锁模式。在lock之外和之内,我们做了instance是否为空的检查。这叫双检查。因为同步控制的时间太长了。双检查能够最高效地实现多线程安全的访问。
使用.net特有的支持多线程的单件模式代码
public sealed class Singleton
{
public static readonly Singleton Instance = new Singleton();
private Singleton() { }
}
代码说明:
- sealed 修饰符表示该类是密封类,不能被继承。你可以按需修改。
- 这里readonly关键字只是不希望客户程序将Instance字段设置为null等不合理的值。
- *** static *** = new Singleton(); 是使用了内联初始化技术,这部分初始化其实是在static Singleton()中执行的。即上面的代码相当于:
public sealed class Singleton
{
public static readonly Singleton Instance;
static Singleton()
{
Instance = new Singleton();
}
private Singleton() { }
}
上述代码在编译时,会使用一个名为beforefieldinit元数据标志。此标志使得运行库能够在任何时候执行类型构造函数方法,只要该方法在第一次访问该类型的静态字段之前执行即可。换句话说,beforefieldinit 为运行库提供了一个执行主动优化的许可。如果没有 beforefieldinit,运行库就必须在某个精确时间运行类型构造函数,即,恰好在第一次访问该类型的静态或实例字段和方法之前。
静态构造器自身就可以保证,多线程情况下,系统就可以保证只有一个执行。
缺点:
- 不支持参数化单件构造器。即静态构造器不支持参数,就导致我们无法利用静态构造器实现传参数的单件。
参考资料:
讲讲volatile的作用
http://blog.21ic.com/user1/2949/archives/2007/35599.html
Implementing the Singleton Pattern in C#
http://www.yoda.arachsys.com/csharp/singleton.html
初始化内联引用类型静态字段
http://msdn.microsoft.com/zh-cn/library/ms182275.aspx
通过七个关键编程技巧得益于静态内容
http://www.microsoft.com/china/MSDN/library/enterprisedevelopment/softwaredev/us0501StaticsinNET.mspx?mfr=true
模式设计c#--创建型--Singleton
http://www.cppblog.com/mzty/archive/2006/01/03/2384.html
今天在看 EnterpriseLibrary 源文件时,看到如下的代码,这个代码可以比较经典的解释Thread.Sleep(0)的用途。代码如下:
Hashtable inMemoryCache;
CacheItem cacheItemBeforeLock = null;
// ..... 一些其他代码
// 通过循环,以获得 cacheItemBeforeLock 的控制权。
bool lockWasSuccessful;
do
{
lock (inMemoryCache.SyncRoot)
{
// .....
lockWasSuccessful = Monitor.TryEnter(cacheItemBeforeLock);
}
if (lockWasSuccessful == false)
{
Thread.Sleep(0);
}
} while (lockWasSuccessful == false);
try
{
// 对 cacheItemBeforeLock 作一些操作
// .....
}
finally
{
Monitor.Exit(cacheItemBeforeLock);
}
Thread.Sleep(0); 线程挂起0毫秒。表面看来这个代码是没啥意义的。
其实不然,挂起0毫秒的意义不在于0毫秒,而在于挂起2个字,线程挂起,其他线程就有机会优先执行。
上面的代码中,在Thread.Sleep(0);之前,这个线程跟其他线程争抢cacheItemBeforeLock的情况,就会因为这个挂起而得到解决。
参考:
关于Thread.Sleep(0)
http://www.cnblogs.com/zzgfly/archive/2007/07/15/818426.html
Thread.Sleep 方法 (Int32)
http://msdn.microsoft.com/zh-cn/library/d00bd51t(VS.80).aspx
设置具体数据库启动Service Broker服务,如下图:
我这里试例数据库的名字为“ghj_Demo”,修改 Broker Enabled 属性为 true。
你也可以用SQL 语句来修改,修改的SQL语句如下:
ALTER DATABASE ghj_Demo SET ENABLE_BROKER
确保你将使用的数据库帐户具有必需的权限
你在后面连接这个数据库的帐户,要确保对这个数据库具有 SUBSCRIBE、 QUERY 、NOTIFICATIONS 的权限。
下面就是一个简单的代码例子,来演示查询通知。这里用到一个我自己建立的表:
这个表结构如下:
CREATE TABLE [dbo].[User](
[UserName] [nvarchar](20) NOT NULL,
[Email] [nvarchar](50) NULL
) ON [PRIMARY]
GO
演示的控制台代码如下:
using System;
using System.Data;
using System.Data.SqlClient;
namespace Demo
{
class Program
{
public static string connectionstring = "Data Source=.;Initial Catalog=ghj_Demo;Integrated Security=True";
public void DoDependency()
{
using (SqlConnection conn = new SqlConnection(connectionstring))
{
// sql is a local procedure that returns
// a paramaterized SQL string. You might want
// to use a stored procedure in your application.
string sql = "select [UserName] ,[Email] from dbo.[User]";
SqlCommand cmd = new SqlCommand(sql, conn);
// Ensure the command object does not have a notification object.
cmd.Notification = null;
//Notification specific code
SqlDependency dep = new SqlDependency(cmd);
dep.OnChange += new OnChangeEventHandler(TestEvent);
conn.Open();
SqlDataReader r = cmd.ExecuteReader();
//Read the data here and close the reader
r.Close();
conn.Close();
Console.WriteLine("DataReader Read...");
}
}
void TestEvent(Object o, SqlNotificationEventArgs args)
{
// 注意:
// 如果Server端在很短的时间内发生了大量的改动(比如用了一个循环Update了好多行),
// OnChanged必须能迅速处理事件,否则它只会被触发一次。这个不是缺陷,
// 因为一般OnChanged事件处理函数内都要执行类似刷新缓存的操作,它只触发一次,
// 不会影响程序逻辑,却能提高程序性能。
Console.WriteLine("======================");
Console.WriteLine("Event Recd");
Console.WriteLine("Info:" + args.Info);
Console.WriteLine("Source:" + args.Source);
Console.WriteLine("Type:" + args.Type);
}
static void Main(string[] args)
{
// In order to use the callback feature of the
// SqlDependency, the application must have
// the SqlClientPermission permission.
try
{
SqlClientPermission perm = new SqlClientPermission(
System.Security.Permissions.PermissionState.Unrestricted);
perm.Demand();
}
catch
{
throw new ApplicationException("No permission");
}
try
{
SqlDependency.Stop(connectionstring);
//Start the listener infrastructure on the client
SqlDependency.Start(connectionstring);
Program q = new Program();
q.DoDependency();
Console.WriteLine("Wait for Notification Event...");
Console.Read();
}
finally
{
//Optional step to clean up dependency else it will fallback to automatic cleanup
SqlDependency.Stop(connectionstring);
}
}
}
}
一些注意事项:摘自MSDN文档
使用查询通知功能的应用程序需要考虑下列特殊注意事项。
|
注意事项
|
说明
|
|
SQL Server 的服务帐户
|
对于使用本地系统帐户作为服务帐户的 SQL Server 实例,应用程序不会从其接收通知。
|
|
接收通知
|
无法在运行 Windows 95 或 Windows 98 的计算机上接收通知。
|
|
查询通知和事务
|
如果在某一事务内进行了多项影响具有已注册通知请求的一组数据的修改,则仅会发送单个通知事件。
|
|
快速更新和查询通知
|
使用查询通知的应用程序必须考虑到立即出现通知的情况。服务器上的数据更改时,通知消息将发送到相应的服务中介程序队列。 应用程序需要注册才能接收其他通知。 因此,如果多个应用程序快速更新某个数据集,应用程序在缓存刷新后,立即可以接收通知,检索数据,然后获取另一个更新通知。 编写使用查询通知的应用程序时必须考虑到此情况。 如果应用程序使用不断更新的数据,则可能更适合使用另一种数据缓存策略。
|
|
设置选项设置
|
在通知请求下执行 SELECT 语句时,提交请求的连接必须设置以下选项:
· ANSI_NULLS ON
· ANSI_PADDING ON
· ANSI_WARNINGS ON
· CONCAT_NULL_YIELDS_NULL ON
· QUOTED_IDENTIFIER ON
· NUMERIC_ROUNDABORT OFF
· ARITHABORT ON
|
编写通知查询语句的约束
您可以为 SELECT 和 EXECUTE 语句设置通知。 使用 EXECUTE 语句时,SQL Server 会为执行的命令而不是 EXECUTE 语句本身注册通知。 该命令必须满足 SELECT 语句的要求和限制。 当注册通知的命令包含多个语句时,数据库引擎会为批处理中的每个语句创建一个通知。
对满足以下要求的 SELECT 语句支持查询通知:
-
SELECT 语句中的提取的列必须显式声明,且表名称必须用由两部分组成的名称进行限定。 请注意,这意味着语句中引用的所有表都必须位于同一个数据库中。
-
语句不能使用星号 (*) 或 table_name.* 语法来指定列。
-
语句不能使用未命名的列或重复的列名称。
-
语句必须引用一个基表。
-
SELECT 语句中的提取的列不能包含聚合表达式,除非该语句使用 GROUP BY 表达式。 在提供 GROUP BY 表达式的情况下,选择列表可以包含聚合函数 COUNT_BIG() 或 SUM()。 不过,不能为可以为 null 的列指定 SUM()。 语句不能指定 HAVING、CUBE 或 ROLLUP。
-
SELECT 语句中的提取的列用作简单表达式时不能出现多次。
-
语句不能包含 PIVOT 或 UNPIVOT 运算符。
-
语句不能包含 INTERSECT 或 EXCEPT 运算符。
-
语句不能引用视图。
-
语句不能包含以下任一项: DISTINCT、COMPUTE、COMPUTE BY 或 INTO。
-
语句不能引用服务器全局变量 (@@variable_name)。
-
语句不能引用派生表、临时表或表变量。
-
语句不能引用其他数据库或服务器中的表或视图。
-
语句不能包含子查询、外部联接或自联接。
-
语句不能引用大型对象类型: text、ntext 和 image。
-
语句不能使用 CONTAINS 或 FREETEXT 全文谓词。
-
语句不能使用行集合函数,包括 OPENQUERY 和 OPENROWSET。
-
语句不能使用以下任一集合函数: AVG、COUNT(*)、MAX、MIN、STDEV、STDEVP、VAR 或 VARP。
-
语句不能使用任何不确定性函数,包括排名和开窗函数。
-
语句不能包含用户定义的聚合。
-
语句不能引用系统表或视图,包括目录视图和动态管理视图。
-
语句不能包含 FOR BROWSE 信息。
-
语句不能引用队列。
-
语句不能包含无法更改或无法返回结果的条件语句(例如 WHERE 1=0)。
sunmast 对查询通知的注意事情也有很多有价值的整理:
使用SQL Server 2005 Query Notification的几个注意事项
http://blog.joycode.com/sunmast/archive/2006/08/11/sql_2005_query_notification_comments_79814.aspx
参考资料:
剖析SQL Server 2005查询通知之基础
http://www.allwiki.com/wiki/%E5%89%96%E6%9E%90SQL_Server_2005%E6%9F%A5%E8%AF%A2%E9%80%9A%E7%9F%A5%E4%B9%8B%E5%9F%BA%E7%A1%80
Using SqlDependency for data change events
http://www.codeproject.com/KB/database/chatter.aspx
SQL Server 2005 Service Broker 初探
http://msdn.microsoft.com/zh-cn/library/ms345108.aspx
SQL Server 2005数据库开发详解
http://book.csdn.net/bookfiles/24/10024713.shtml
C# Windows 应用程序中实现 SQL Server 2005 查询通知
http://support.microsoft.com/kb/555893/zh-cn
SqlDependency changes for RTM [Sushil Chordia]
http://blogs.msdn.com/dataaccess/archive/2005/09/27/474447.aspx
.NET 2.0 SqlDependency快速上手指南
http://www.cnblogs.com/Xrinehart/archive/2006/07/27/461106.html
在 Windows 应用程序中使用 SqlDependency
http://msdn.microsoft.com/zh-cn/library/a52dhwx7(VS.80).aspx
Using SqlDependency in an ASP.NET Application
http://msdn.microsoft.com/en-us/library/9dz445ks(VS.80).aspx
Minimum Database Permissions Required for SqlDependency
http://www.codeproject.com/KB/database/SqlDependencyPermissions.aspx
使用SQL Server 2005 Query Notification的几个注意事项
http://blog.joycode.com/sunmast/archive/2006/08/11/sql_2005_query_notification_comments_79814.aspx
一个简单问题:如下2种Html写法,那个加载速度快?
<!-- 写法1 -->
<div>
<div>内容代码2</div>
<div>内容代码3</div>
<div>内容代码4</div>
<div>内容代码5</div>
<div>内容代码6</div>
<div>内容代码7</div>
<div>内容代码8</div>
<div>内容代码9</div>
<div>内容代码10</div>
</div>
<!-- 写法2 -->
<div>内容代码2</div>
<div>内容代码3</div>
<div>内容代码4</div>
<div>内容代码5</div>
<div>内容代码6</div>
<div>内容代码7</div>
<div>内容代码8</div>
<div>内容代码9</div>
<div>内容代码10</div>
我的答案,写法2。当然,如果只是上面的写法,实际上这两种写法对性能的差别,可以忽略不计。
但是如果,上图的内容代码区域如果嵌入了一些不可控的因素,比如:外站的一些脚本出现在<div>内容代码7</div>中。写法1需要所有都加载完成才可以正常显示,写法2的内容2-6 不受这个影响。
更具体的来说:浏览器解析Html的规则必然是:
- 当服务器向浏览器输出多少,浏览器就会解释多少,浏览器不可能解析没有给它的内容。
- 源文件Body区域的每个Html标签,如果浏览器找不到这个标签的结束标志(一些html标签没有结束标志,但是需要浏览器去分析结束位置)。这个标签对应的内容,浏览器就难以正常显示。
- 源文件Body区域的多个标签嵌套,需要所有被嵌套标签都加载完成,才能正常显示,这时候加载顺序是倒着的。举例:<div>1<div>2<div>3</div><div></div>这段源代码会先显示3,然后2, 最后1。因为加载div1时并没有找到它的结束标签</div>,于是它不加载继续解析源文件,在找到div2时,和上面一样也没有找到结束位置不做加载。然后是找到div3,div3有结束标签。 浏览器开始加载div3,之后,找到div2的结束标签,加载div2,以次类推,所以这时理论加载顺序为: 3 2 1 。
- 如果浏览器的加载页面元素只是上面这样的工作原理,我们看到的很多页面的效果就是整个页面所有都加载完成才能显示出来。其实浏览器还有由一个特性,它可以去预测没有加载的内容。有些浏览器在打开一些网页的规程中,会把一些元素移位,最后才恢复正常,一部分原因就是这个事先预测在起作用。
再回到最初的问题:
一般美工在作页面时,会喜欢写法1的嵌套Html。如果一个夏姆,对用户体验要求高,同时预测到可能会在一些地方嵌入广告脚本会影响到页面显示,我会要求美工用方法2的方式来书写源文件,以保证用户体验。页面的设计,减少嵌套的层次,对页面的加载会好处多多的。
最后,我家儿子刚过一周岁,文章的最后祝福一下我家小宝贝。
参考资料:
<div>嵌套<div>后的显示速度问题
http://zhidao.baidu.com/question/7892633.html
关于DIV和表格的速度评论
http://x.discuz.net/viewthread-469261.html
把所有东西都放在一个大DIV里,显示速度问题。
http://zhidao.baidu.com/question/52404898.html
整个网页面用一个DIV包含起来好不好
http://zhidao.baidu.com/question/67452079.html
网络负载平衡采用一种完全分布式的算法,根据传入客户端的 IP 地址和端口,以统计方式将其映射到群集主机。此进程的发生不需要主机间进行任何通信。当发现到达的数据包时,所有主机同时执行这种映射,以快速确定哪个主机应当处理这个程序包。这种映射一直保持不变,直到群集主机数发生更改时为止。与集中式负载平衡应用程序相比,网络负载平衡筛选算法处理数据包的效率更高,因为前者必须修改和重新传送数据包。
如下图:
- 请求发送到所有NLB主机
- 只有一台主机会处理,其他主机丢弃这个请求。
- 此进程的发生不需要主机间进行任何通信。
- 不需要修改和重新传送请求数据包。
这个统计算法在 Wlbs.sys 组件中实现。
有关这个算法细节可以搜索关键字:“Load Balancing Algorithm”
下面是一些相关文章链接:
How Network Load Balancing Algorithm works internally
http://www.codedigest.com/Articles/Windows%20and%20Clustering/49_How_Network_Load_Balancing_Algorithm_Works_Internally.aspx
为了协调其操作,网络负载平衡主机在群集内周期性地交换检测信号。IP 多播允许主机监控群集状态。当群集状态更改时(例如当主机发生故障、离开或加入群集时),网络负载平衡将调用称作“聚合”的过程,在该过程中,主机交换数量有限的消息,以确定群集的新的一致状态,并为主机指定最高主机优先级,即作为新的默认主机。当所有群集主机在正确的新群集状态下取得一致后,它们将在 Windows 事件日志中记录聚合的完成。完成这个过程一般用不了 10 秒种。
在聚合过程中,其余主机继续处理传入的网络通信。对工作主机的客户端请求不受影响。完成聚合后,将以故障主机为目标的通信重新分发给仍在工作的主机。经过负载平衡后的通信将在仍在工作的主机间得到重新划分,以便尽可能好地实现特定 TCP 或 UDP 端口的新的负载平衡。
如果向群集添加了一个主机,则聚合允许该主机接收自己那份经过负载平衡的通信。群集的扩展不影响正在进行的群集操作,而且其实现过程对 Internet 客户端和服务器应用程序都是透明的。但是,当选择了“客户端相似性”时,它可能影响跨多个 TCP 连接的客户端会话,因为可能会将客户端重映射到连接间的不同群集主机。有关相似性的详细信息,请参阅网络负载平衡和状态可控的连接。
网络负载平衡假定,主机在群集内正常工作的时间与它同其他群集主机交换检测信号的时间一样长。如果在多次检测信号交换中,其他主机都没有接收到来自任何成员的响应,则它们将启动聚合,重新分发本来应由失败主机处理的负载。
对于消息交换时段以及启动聚合所需的丢失的消息数,您都可以进行控制。默认值设置分别为 1000 毫秒(1 秒)和 5 个丢失的消息交换时段。由于一般都不修改这些参数,所以无法通过“网络负载平衡属性”对话框配置它们。必要时,可以在注册表中手动调整它们。调整聚合参数对完成此操作的过程进行了描述。
如下图所示发生群机主机变更时的处理。
聚集前的NLB集群
聚集后的NLB集群
_thumb.png)
参考资料:
How Network Load Balancing Technology Works
http://technet.microsoft.com/en-us/library/cc756878.aspx
Appendix B - Network Load Balancing Technical Overview
http://technet.microsoft.com/en-us/library/bb734896.aspx
网络负载平衡的工作原理
http://technet.microsoft.com/zh-cn/library/cc738894.aspx
machineKey的作用在于下述场景:
- ASP.net 使用 forms authentication 时的 cookie 数据的加密和解密。以确保这部分数据不会被篡改。
- viewstate 数据的加密和解密。以确保这部分数据不会被篡改。
- 使用进程外session(out-of-process session)时,对会话状态标识进行验证。
ASP.net 1.0 以及 ASP.net 1.1, 我们都可以在下面地址的文件中找到machineKey的配置信息:
%Windir%\Microsoft.NET\Framework\<version>\config\machine.config
不同的是 ASP.net 1.0 找到的是如下的配置信息
<machineKey
validationKey="AutoGenerate"
decryptionKey="AutoGenerate"
validation="SHA1"
/>
ASP.net 1.1 找到的是如下信息:
<machineKey
validationKey="AutoGenerate,IsolateApps"
decryptionKey="AutoGenerate,IsolateApps"
validation="SHA1"
/>
但是 ASP.net 2.0 , .net Framework 3.0 ,.net Framework 3.5 这些版本中,我们在
%Windir%\Microsoft.NET\Framework\<version>\config\
目录的 machine.config 和 web.config 中找不到machineKey的设置。
这是因为, ASP.net 2.0 中,machineKey 的默认设置没有写在配置文件中。
ASP.net 2.0 中,machineKey 的默认设置如下:
<machineKey
validationKey="AutoGenerate,IsolateApps"
decryptionKey="AutoGenerate,IsolateApps"
validation="SHA1"
decryption="Auto"
/>
我们如果要修改machineKey的默认设置,就需要在必要的地方新加machineKey的配置节点。
产生一个可用的 machineKey 配置信息可以使用下面地址提供的工具:
http://www.aspnetresources.com/tools/keycreator.aspx
参考资料:
How To: Configure MachineKey in ASP.NET 2.0
http://msdn.microsoft.com/zh-cn/library/ms998288(en-us).aspx
machineKey 元素(ASP.NET 设置架构)
http://msdn.microsoft.com/zh-cn/library/w8h3skw9(VS.80).aspx
http://msdn.microsoft.com/en-us/library/w8h3skw9.aspx
如果你的try catch代码不在线程A中,则你无法扑捉到线程A的异常。比如下面代码就是无法用try catch 扑捉的。
using System;
using System.Threading;
namespace TryCatchThread
{
class Program
{
static void Main(string[] args)
{
try
{
Thread t = new Thread(new ThreadStart(doVoid));
t.Start();
}
catch (Exception)
{
Console.WriteLine("Exception");
}
Console.ReadLine();
}
public static void doVoid()
{
throw new Exception("haha");
}
}
}
今天在SD2大会上,K2大中华区CTO彭靖灏分享了他们公司在做K2的时候,对使用WPF的心得,我觉得很有启发。整理这部分的听课笔记如下:
K2是一个很流行的工作流设计软件 http://www.k2.com/zh-CN/index.aspx 他们在最新的K2版本中使用了 WPF,WCF,WF。
他们是在做商业软件,商业软件使用WPF的一些心得,对我们作软件时候,很有醒示作用。
他们在做K2时候,界面采用WPF来实现感觉到的优点:
1、动画技术可以让软件更易用,更能引导用户使用软件。可以很明确的告诉用户,你正在干啥,下一步应该干啥。
2、Template技术,让他们的工作流软件更简单。
3、预览图技术,当一个数据变化的时候,你可以看到数据变化的预览图,很直观。
4、sliverlight2.0后,WPF和sliverlight的代码互用性很高了。
他们使用WPF碰到的问题
1、WPF的XAML都是向量图,很多美工对Expression Blend 这个工具不适应。招到能很高的设计XAML的美工很不容易 。至于让程序员来做这部分活,只能说做出来很丑。一个强大的工具,仍然需要有高手来使用。
2、跟他们已有功能的互操作,即跟Com的互操作,Interop
3、WPF是一项翻天覆地的变革,要用好它,就需要新的设计哲学,新的开发模式,这个转换门槛不低。
另外,他们的工作流每一个工作项的配置这里,没用WPF之前是使用得类似向导的方式来实现的,下一步下一步这样的方式。这种方式其实有很多弊端,他们在使用WPF后,则解决了这些问题。
他们的软件有Ajax,winform,WPF,Sliverlight的版本 ,比较下来,还是WPF的更易用。
另外听到的一个数据是:
国内在Sliverlight方面,同比其他国家,做的很好。但是WPF方面,同比其他国家,则很低。
今天在SD2大会上,听了李建忠老师讲的《.NET框架中的几个典型设计模式》课程收益非浅,李建忠老师的课总能给人醍醐灌顶的感觉,去年的《WPF内核机制》让我们可以从根本上理解WPF的革命。今年的设计模式,也是从根本上理解设计模式产生的原因,适用的场景。
下面是我对课程整理的一些笔记和心得,跟大家分享:
软件的需求一直在变化。很变态,但是很多人都碰到过的情况:一直到代码编写完毕前,需求都可能在变化,需求的冻结要到编码完毕时才完成。
为此,软件的开发方法从最初的瀑布开发, 到迭代开发再到敏捷开发。他们都是适应软件的需求、设计迟迟不能冻结定稿的产物。上面提到的开发方法,一个比一个需求、设计定稿的时间要晚。
当然,变化不能一直不稳定,那么我们软件就永远不能发布了。
软件的架构也受到这些影响:
我们预计到会变的因素我们会写在配置文件ini或者XML配置文件中。
甚至我们预计要变得东西单单配置文件都不可以搞定了,我们要把这些预计要变的东西写成解析执行的脚本语言。今天早上蔡学镛讲的《Scriptable Software与DSL的设计》,下午周爱民讲的《JavaScript + Delphi + ErLang = ?》从这里提到的变化角度来说,都是为了适应这种变化对架构的影响采用的方法。
[魔兽世界]编写插件用的LUA语言,[文明4]用的Python语言...都是这方面的典范。
对于设计模式,也同样的是受变化影响的。
我们预计到一个地方是稳定的,就一个选择,我们如果还用设计模式,就是愚蠢的性能低下的做法。设计模式应该用到我们预计到这里会发生变化,为了保证复用性,我们对变化进行抽象,抽象出一个稳定的抽象。抽象出来的这个类,如果时不时都会变化一下,那简直要疯了,这个抽象的类一定要保证稳定性。这才是OO思想,设计模式的思想.
应对变化,提高复用,这是OO设计的基本原则,也是设计模式的基本原则。
晚绑定机制就是对这些变化的支持。.NET的晚绑定有四种方法:虚函数,委托(函数指针),反射,范型。对应的23种设计模式都是用这些来实现的。
MSDN的WebCast中,李建忠老师对C#设计模式有25节课的讲座,详细介绍了23种设计模式。在下面地址可以看到,下载:http://www.microsoft.com/china/msdn/events/webcasts/shared/webcast/consyscourse/CsharpOOD.aspx
这节课对具体的设计模式讲解的时间也不多。但是你理解了变化、稳定、复用的意义,你再看设计模式就会事半功倍。
这些年的开发经验积累下来,我发现我的设计、架构很多时候都是一个四不象,或者是一个性能,可扩展性等中庸折中的取舍方案。一定要想清楚哪些是要变化的,不会变化的,不要画蛇添足的装酷。对于要变化的要做到应对变化,提高复用来抽象,高水平的技术人员,抽象出来的东西不会三天两头就变的。
今天碰到了一个超级怪异的问题, Visual Studio 调试一段代码时,如下图:监视窗口显示这个变量有值,但是代码却执行到这个变量无值的逻辑中了。

为了这个问题,我简直疯了。
后来看到其中一些变量的察看时,会报错误
Cannot obtain value of local or argument 'oo' as it is not available at this instruction pointer, possibly because it has been optimized away.
在装配脑袋的提醒下,觉得可能是Visual Studio的代码优化导致了这个问题。
取消编译时候的代码优化。 再Debug,就没有这个问题。
竟然是代码优化导致了这个让我困扰了一下午的问题。
代码优化取消在下面位置, 取消 Optimize code 之前的 CheckBox 即可。黄线上面的选项

我们在调试GAC中部署的程序时候,很可能碰到下面的报告:
---------------------------
Microsoft Visual Studio
---------------------------
The following module was built either with optimizations enabled or without debug information:
C:\Windows\assembly\GAC_MSIL\CSDN.Community.TopicListDataCenter.EnterpriseComponents\2.5.1.23407__cdde601ea7585548\aaa.dll
To debug this module, change its project build configuration to Debug mode. To suppress this message, disable the 'Warn if no user code on launch' debugger option.
---------------------------
确定
---------------------------
这是因为VS开发工具在C:\Windows\assembly\GAC_MSIL\CSDN.Community.TopicListDataCenter.EnterpriseComponents\目录下没有找到aaa.pdb调试符号文件。当然这个目录是GAC的目录,不可能有的。
如何解决,最笨的方法是吧 PDB 文件 Copy 到那个目录去。
我这里要说的解决方法当然不是这样的了。而是如下:
在Visual Studio 的调试属性中去掉 Enable Just My Code(Managed only)

这时候我们再去调试就不会出现上述提示文本了。也不会找不到 PDB文件了。一切都OK了。
我们在Modules窗口也可以看到成功加载了正确位置的pdb文件。

参考资料:
You Don't Need to Copy PDB Files to Debug in the GAC!
http://www.elumenotion.com/Blog/Lists/Posts/Post.aspx?ID=23
本文概述
StreamWriter 在产生UTF-8编码的内容时候,会在产生的这个UTF-8内容中增加BOM的信息,而这个BOM的信息,会干扰我们在一些情况的使用。
本文描述的情况,就是这种干扰让我们无法正常工作的一种情况。
何为BOM?
BOM(Byte Order Mark),BOM签名。
BOM的内容就可以表示unicode文件是何种编码。BOM签名的意思就是告诉编辑器当前文件采用何种编码,方便编辑器识别。
对于UTF-8 , BOM 信息为 EF BB BF。 我们如果在Google搜索 UTF-8 BOM 就会搜索到很多文章, BOM 在不少情况下,都会给我们添乱子。
下面是我碰到这个问题的描述
我碰到这个问题的场景:在书写一段模拟HTTP Post 请求的时候, 代码如下,但是却无法模拟Post请求:
private void do2()
{
string url = "http://localhost:39749/Default.aspx";
string indata = "__VIEWSTATE=%2FwEPDwUKMTQ2OTkzNDMyMWRkyGd";
indata += "iqWjBKr5rIKmHzSdD9AaojKw%3D&Button1=Button&__EVENTVALIDATION=%";
indata += "2FwEWAgLohfrVDQKM54rGBu49QLoa7JmG9cEfUpTccMrUmJfD";
HttpWebRequest req = (HttpWebRequest)WebRequest.Create(url);
req.ContentType = "application/x-www-form-urlencoded";
req.Method = "Post";
Stream myRequestStream = req.GetRequestStream();
StreamWriter myStreamWriter = new StreamWriter(myRequestStream, Encoding.UTF8);
myStreamWriter.Write(indata);
myStreamWriter.Close();
myRequestStream.Close();
myStreamWriter.Dispose();
myRequestStream.Dispose();
HttpWebResponse res = (HttpWebResponse)req.GetResponse();
StreamReader reader = new StreamReader(res.GetResponseStream(), Encoding.UTF8);
string info = reader.ReadToEnd();
reader.Close();
res.Close();
reader.Dispose();
MessageBox.Show(info);
}
而文中中间的代码修改成下面代码则可以成功模拟。
byte[] bytes = System.Text.Encoding.UTF8.GetBytes(indata);
req.ContentLength = bytes.Length;
Stream myRequestStream = req.GetRequestStream();
myRequestStream.Write(bytes, 0, bytes.Length);
myRequestStream.Close();
为何会这样呢?分析原因,竟然是 UTF-8 BOM 在作怪。
StreamWriter 在产生UTF-8编码的内容时候,会在产生的这个UTF-8内容中增加BOM的信息, 这样他发送的Post信息就比正常多了三个字节 EF BB BF。 就是因为这三个字节导致服务器端无法处理正常的Post请求。
解决方法:
1、自己重写UTF-8类,参看 http://www.19870202.com/?tid=381 。在调用的时候用这个自己写的类。
重写代码:
public class UTF8EncodingNoPreamble : System.Text.UTF8Encoding
{
public override byte[] GetPreamble()
{
return new byte[0];
}
}
2、不要用 StreamWriter ,参看我上面的替代方案。
参考资料:
System.IO.StreamWriter写UTF-8文件不写BOM
http://www.19870202.com/?tid=381
UTF-8, UTF-16, UTF-32 & BOM
http://unicode.org/faq/utf_bom.html#BOM
utf-8 保存文件的 bom 问题
http://www.uuzone.com/blog/tom/101761.htm
我们在使用WEB Service时,需要注意的一点是,传递过程中会丢失一些字符,比较典型的是 /r/n 中 /r 回车字符会被丢弃。这是XML规范所导致的问题。XML规范关于这部分的描述如下:
2.11 行尾处理
为编辑的方便起见,存储XML已析实体的计算机文件经常用行来组织。通常这些行用回车符(#xD)和换行符(#xA)的一些组合来分隔。
为了使应用的工作简单化,对于一个外部已析实体或内部已析实体的常量实体值中包含的任何两字符常量序列"#xD#xA"或单独的常量#xD,XML处理器都应换成#xA传递给应用。(这可以通过在进行语法分析前将所有行分隔符规范成#xA而方便地实现。)
\r 回车(跑到最前面)
\n 换行(下一行)
参考资料:
WebServices eat \r in \r\n
http://vidmar.net/weblog/archive/2005/04/03/1203.aspx
XML规范对此相关的解释
http://www.w3.org/TR/2004/REC-xml-20040204/#sec-line-ends
中文版的介绍看下面地址:
http://xml.coverpages.org/xml10-chinese.html#sec-line-ends
\r\n和\r
http://topic.csdn.net/t/20060317/09/4620216.html
Web Service - Carriage Return
http://forums.msdn.microsoft.com/en-US/netfxremoting/thread/bb0ff1f8-0300-4910-be10-6594dff56de4
比如我们有下面的需求:
三台电脑:A,B,C。
我们在 B 和 C 上部署了同样的一个服务,电脑 A 需要根据客户端的选择,自动的切换到底是调用B的服务,还是C的服务。
要实现这个需求,核心就在客户端的调用上。下面我们用一个简单的演示这个功能的代码来说明如何实现。
首先:服务器段
服务器段逻辑,这是非常简单的,我们按照之前的.net编写规范,编写代码即可。熟悉.net Remoting 的完全可以跳过这部分。
下面是一段简单的服务器段逻辑代码
using System;
using System.Collections.Generic;
using System.Text;
using System.Net;
namespace MyServiceComponent
{
public class MyComponent : MarshalByRefObject
{
public string GetString(short s)
{
// 返回信息中包含服务器IP,这样我们就知道客户端调用的是哪个服务器
if (s <= 10)
return string.Format("<=10 {0}", GetIP());
else
return string.Format("大于10 {0}", GetIP());
}
protected string GetIP() //获取本地IP
{
IPHostEntry ipHost = Dns.GetHostEntry(Dns.GetHostName());
IPAddress ipAddr = ipHost.AddressList[0];
return ipAddr.ToString();
}
}
}
服务器段的配置
<?xml version="1.0" encoding="utf-8" ?>
<configuration>
<system.runtime.remoting>
<application>
<service>
<wellknown mode="Singleton" type="MyServiceComponent.MyComponent, MyServiceComponent"
objectUri="HongjunguoRemotingService" />
</service>
<channels>
<!-- 这里的配置方法,请参看MSDN中 远程处理安全通道技术示例 -->
<channel ref="tcp" port="8088" secure="true" impersonate="true"
protectionLevel="EncryptAndSign" />
<serverProviders>
<formatter href="binary" typeFilterLevel="Full"/>
</serverProviders>
</channels>
</application>
<!--
只有把 customErrors 配置成 Off ,服务器端的详细错误异常,才能传递到客户端
默认是不传递完整的错误异常的。
-->
<customErrors mode ="Off" />
</system.runtime.remoting>
</configuration>
服务器段调用代码
RemotingConfiguration.Configure(
AppDomain.CurrentDomain.SetupInformation.ConfigurationFile, false);
客户端
客户端如果我们用以前常用的,把所有客户端的调用信息都写在一个配置文件中,期望简单的用下面代码就不可以了。
RemotingConfiguration.Configure(configFile, true);
如果我们用上面的方法时,则会收到下面的异常:
远程处理配置失败,异常为“System.Runtime.Remoting.RemotingException:
试图重定向类型“MyServiceComponent.MyComponent, MyServiceComponent”的激活,而该类型已被重定向。
在 System.Runtime.Remoting.RemotingConfigHandler.RemotingConfigInfo.AddWellKnownClientType(WellKnownClientTypeEntry entry)
在 System.Runtime.Remoting.RemotingConfigHandler.RegisterWellKnownClientType(WellKnownClientTypeEntry entry)
在 System.Runtime.Remoting.RemotingConfiguration.RegisterWellKnownClientType(WellKnownClientTypeEntry entry)
在 System.Runtime.Remoting.RemotingConfigHandler.RemotingConfigInfo.StoreRemoteAppEntries(RemotingXmlConfigFileData configData)
在 System.Runtime.Remoting.RemotingConfigHandler.ConfigureRemoting(RemotingXmlConfigFileData configData, Boolean ensureSecurity)”。
参考我在论坛咨询的帖子
远程处理配置失败,异常为“RemotingException: 试图重定向类型“MySC.MyComponent, MyServiceComponent”的激活,而该类型已被重定向
http://topic.csdn.net/u/20080418/10/a9b02fa0-a230-4fb6-abeb-b7407a6729c1.html
使用.net Remoting 客户端调用服务器段时,需要考虑两个东西:
1、信道的问题(Channel)
2、如何创建远程对象,也就是注册类型
先说信道的问题:
上面例子中, B 和 C 服务器,他们完全可能一个开放的是 TCP 信道,一个开放的是 HTTP 信道。 同时,访问他们服务时,身份验证完全可能是不同的。各自服务器自身的验证。
这就有一个需要解决的问题,如何实现客户端多信道。下面这篇博客对此有比较详细的介绍:
Remoting多个信道(Chennel)的注册问题
http://www.cnblogs.com/kriss/archive/2005/11/30/288177.html
创建远程对象的问题:
如果我们把需要创建的信息写在配置文件中,用 RemotingConfiguration.Configure(configFile, true); 来创建远程对象,就会出现下面的错误。
远程处理配置失败,异常为“System.Runtime.Remoting.RemotingException:
试图重定向类型“MyServiceComponent.MyComponent, MyServiceComponent”的激活,而该类型已被重定向。
在 System.Runtime.Remoting.RemotingConfigHandler.RemotingConfigInfo.AddWellKnownClientType(WellKnownClientTypeEntry entry)
在 System.Runtime.Remoting.RemotingConfigHandler.RegisterWellKnownClientType(WellKnownClientTypeEntry entry)
在 System.Runtime.Remoting.RemotingConfiguration.RegisterWellKnownClientType(WellKnownClientTypeEntry entry)
在 System.Runtime.Remoting.RemotingConfigHandler.RemotingConfigInfo.StoreRemoteAppEntries(RemotingXmlConfigFileData configData)
在 System.Runtime.Remoting.RemotingConfigHandler.ConfigureRemoting (RemotingXmlConfigFileData configData, Boolean ensureSecurity)”。
解决方法,就是下面的演示代码,不写在配置文件中,改自己手工创建,如下面客户端演示代码。
编码创建对象可以使用 Activator.GetObject 或者 Activator.CreateInstance 。
下面就是我的演示代码
针对B服务器的配置文件(主要是通道的配置,注意这两个配置文件验证信息不一样)
注意,这个配置文件中我们可没有下面这样的信息:
<client displayName="client01" >
<wellknown
displayName ="Wellknown1"
type="MyServiceComponent.MyComponent,MyServiceComponent"
url="tcp://192.168.5.2:8088/HongjunguoRemotingService" />
</client>
s1.config
<?xml version="1.0" encoding="utf-8" ?>
<configuration>
<system.runtime.remoting>
<application>
<channels>
<!--
这里的配置方法,请参看MSDN中 远程处理安全通道技术示例
这个技术请参看:
http://www.cnblogs.com/zhengyun_ustc/archive/2006/06/09/remoting_InvalidCredentialException.html
http://forums.asp.net/thread/1626741.aspx
-->
<channel name="Channel1" ref="tcp" port="8081" secure="true"
tokenImpersonationLevel="impersonation" protectionLevel="EncryptAndSign"
username="ghj1976" password="*****" domain="*****" />
<clientProviders>
<formatter ref="binary" typeFilterLevel="Full" />
</clientProviders>
</channels>
</application>
<customErrors mode ="Off" />
</system.runtime.remoting>
</configuration>
针对C服务器的配置文件(主要是通道的配置,注意这两个配置文件验证信息不一样)
s2.config
<?xml version="1.0" encoding="utf-8" ?>
<configuration>
<system.runtime.remoting>
<application>
<channels>
<channel name="Channel2" ref="tcp" port="8082" secure="true"
tokenImpersonationLevel="impersonation" protectionLevel="EncryptAndSign"
username="communityserver" password="****" domain="***"/>
<clientProviders>
<formatter ref="binary" typeFilterLevel="Full" />
</clientProviders>
</channels>
</application>
<customErrors mode ="Off" />
</system.runtime.remoting>
</configuration>
客户端程序的调用代码
public partial class ClientForm : Form
{
public ClientForm()
{
InitializeComponent();
}
private Dictionary<string, MyServiceComponent.MyComponent> dict =
new Dictionary<string, MyServiceComponent.MyComponent>(2);
private void button1_Click(object sender, EventArgs e)
{
short ss = 0;
if (!short.TryParse(this.textBox1.Text, out ss))
return;
string key = string.Empty;
string url = string.Empty;
if (radioButton1.Checked)
{
key = "s1.config";
url = "tcp://192.168.5.2:8088/HongjunguoRemotingService";
}
else if (radioButton2.Checked)
{
key = "s2.config";
url = "tcp://192.168.5.7:8088/HongjunguoRemotingService";
}
else
return;
MyServiceComponent.MyComponent com = null;
if (!dict.TryGetValue(key, out com))
{
string configFile = Path.Combine(
Path.GetDirectoryName(AppDomain.CurrentDomain.SetupInformation.ConfigurationFile), key);
RemotingConfiguration.Configure(configFile, true);
//com = new MyServiceComponent.MyComponent();
com = (MyServiceComponent.MyComponent)Activator.GetObject(
typeof(MyServiceComponent.MyComponent), url);
dict.Add(key, com);
}
else
{
if (com == null) return;
}
string www = com.GetString(ss);
MessageBox.Show(www);
}
}
参考资料
客户端提示 信道 http 已注册
http://topic.csdn.net/t/20051219/15/4468126.html
如何取消RemotingConfiguration.RegisterActivatedClientType的注册类型
http://topic.csdn.net/t/20050116/16/3729762.html
RemotingConfiguration.Configure (String, Boolean) 中隐藏的秘密
http://blog.csdn.net/blue_sky_blue_heart/archive/2006/08/28/1130914.aspx
Remoting给远程对象属性赋值
http://topic.csdn.net/u/20070614/15/1d5a7738-e676-489e-978a-b194579d560b.html
.net FrameWork 3.0 后,我们会发现有两个Color数据结构。
一个是:System.Drawing.Color
一个是:System.Windows.Media.Color
这两个结构有啥区别呢?
下面是对这两个类的属性的一个简单比较:
| | System.Drawing.Color Structure | System.Windows.Media.Color Structure |
| 所在组件 | System.Drawing.dll | PresentationCore.dll |
| 支持的版本 | .NET Framework Supported in: 3.5, 3.0 SP1, 3.0, 2.0 SP1, 2.0, 1.1, 1.0 .NET Compact Framework Supported in: 3.5, 2.0, 1.0 XNA Framework Supported in: 1.0 | .NET Framework Supported in: 3.5, 3.0 SP1, 3.0 |
| A | Gets the alpha component value of this Color structure. | Gets or sets the sRGB alpha channel value of the color. |
| B | Gets the blue component value of this Color structure. | Gets or sets the sRGB blue channel value of the color. |
| G | Gets the green component value of this Color structure. | Gets or sets the sRGB green channel value of the color. |
| R | Gets the red component value of this Color structure. | Gets or sets the sRGB red channel value of the color. |
| ScA | 不支持 | Gets or sets the ScRGB alpha channel value of the color. |
| ScB | 不支持 | Gets or sets the ScRGB blue channel value of the color. |
| ScG | 不支持 | Gets or sets the ScRGB green channel value of the color. |
| ScR | 不支持 | Gets or sets the ScRGB red channel value of the color. |
| 获得系统支持的一些颜色 | 在Color中定义了141种系统预定义的颜色 调用方法如下: System.Drawing.Color.AliceBlue | 不在Color中定义,而是Colors中定义,获得方法类似下面写法: System.Windows.Media.Colors.AliceBlue 系统一共预定义了141个颜色。 |
| 小结 | 只支持 sRGB。向下兼容 | 同时支持 sRGB、ScRGB。不兼容3.0以下的版本 |
我们可以在上面看到,关键是sRGB和ScRGB两种颜色表示方法。这两种有啥差别呢?我们来看下面三副图,先来感性的看看:

这幅图的巧妙之外在于它通过“归一化”,用两维平面来表示三个数据。X轴是红色的比例,Y轴是绿色的比例,而Z轴是蓝色的比例,虽然Z轴没有画出来,但它的比例数据可以很方便地计算出来。比方红是0.2,绿是0.3,那么蓝就是0.5。因为它们三者加起来必须等于1,不然怎么叫“归一化”呢!图上任何一点的蓝色分量,你都可以用这个方法计算出来。
图中的“舌形”色域空间,是人眼能够辨别的色彩空间,它的边缘围绕一道从波长从380到700(毫微米)的光谱,中间就是用红、绿、蓝三种颜色按不同比例调配出来的颜色。
而图中的三角的区域,是 sRGB 可以表示的颜色范围。显然有一些我们人类可以看到的颜色,但是sRGB来描述的。
上面这幅图对比了 sRGB、人眼、ScRGB 可以表示的颜色范围。
上面这幅图是sRGB和ScRGB两幅图的比较,注意看放大了的云彩。
sRGB 和 scRGB 的转换
在 System.Windows.Media.Color 结构中,scRGB原色其实是被储存成单精度(single-precision)的浮点数。想要容纳scRGB颜色空间,Color 结构包含四个主要的property,类型都是float,分别为ScA、ScR、ScG、ScB。
这些property和A、R、G、B property 会相互影响,改编G property也会造成ScG property的改变,反之亦然。
当G property 为0,ScG property 也会为0;当G property 为255,ScG property 就会为1。在这个范围之内,
关系并非是线性的,如下表所示。
| scG | G |
| <= 0 | 0 |
| 0.1 | 89 |
| 0.2 | 124 |
| 0.3 | 149 |
| 0.4 | 170 |
| 0.5 | 188 |
| 0.6 | 203 |
| 0.7 | 218 |
| 0.8 | 231 |
| 0.9 | 243 |
| >=1.0 | 255 |
ScR 与 R 之间的关系,ScB与B之间的关系,以及ScG与G之间的关系,也都是一样的。ScG的值可以小于0或者大于1,以容纳超出显示器和sRGB数字范围的颜色。
sRGB和scRGB的比较
sRGB目标是使同一网页在不同计算机上显示时的色彩更一致,但只适用于CRT显示器。微软HD Photo项目负责人克劳说,sRGB的挑战在于它只是完整色彩空间的一个子集,当使用sRGB编码时,我们会丢掉一些色彩。
scRGB色彩空间是sRGB扩展,对于黑色和纯绿色而言,这二者没有任何分别。二者的差别就在于scRGB能够显示人眼无法分辨的颜色,其精细程度也超过了sRGB。
scRGB描述每个点所需要的位数是sRGB 2倍,甚至是4倍。不仅能够使用整数,还能够使用浮点数,提高图像的精细程度。
参考资料:
关于scRGB色彩空间
http://hi.baidu.com/cybo/blog/item/8f24ba38bbb584c1d5622597.html
第二章 基本的Brush画刷类 [App = Code + Markup]
http://www.cnblogs.com/rickiedu/archive/2007/04/04/699529.html
GDI+与WPF中的颜色简析
http://blog.csdn.net/johnsuna/archive/2007/08/27/1761061.aspx
简述WPF中的图像像素格式(PixelFormats)
http://blog.csdn.net/johnsuna/archive/2007/08/28/1762901.aspx
我们先来看一个我们在使用 GridSplitter 时,经常可能会碰到的问题:
把一个窗体分成2部分,这两部分中间是一个GridSplitter来支持这左右两部分的宽度变动。类似下面的效果图:
中间红色就是分割线,这个分割线支持左右两部分的宽度变更,一般我们会把这个需求写成类似下面XAML文件的方式:
<Window
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
x:Class="WpfApplication_GridSplitter.Window1"
Title="Window1" Height="100" Width="300">
<Grid Width="Auto" Height="Auto" >
<Grid.ColumnDefinitions >
<ColumnDefinition Width="*" />
<ColumnDefinition Width="Auto" />
<ColumnDefinition Width="5*" />
</Grid.ColumnDefinitions>
<Grid.RowDefinitions />
<StackPanel Grid.Column="0" Background="DarkBlue" />
<GridSplitter Grid.Column="1" Width="3" Background="Red" />
<StackPanel Grid.Column="2" Background="#FFAEA130" />
</Grid>
</Window>
但是仅仅上面的代码,我们在拖动分割线的时候,会发现,分割线右边的尺寸发生变化了,而左边的尺寸没有发生变化,类似下面效果图:
中间出现了一个空白区域。
这是为何呢??
原因很简单, GridSplitter 默认的 ResizeBehavior 属性为:BasedOnAlignment
ResizeBehavior 属性是一个枚举类型:GridResizeBehavior ,这个枚举类型包含以下枚举项:
BasedOnAlignment 根据VerticalAlignment 和 HorizontalAlignment 属性来确定分割线移动后,有哪些项被移动
CurrentAndNext Grid 当前所在的区域以及下一个区域被影响
PreviousAndCurrent 前一个和当前区域被影响
PreviousAndNext 前一个和后一个区域被影响
我们把这个XAML文件改成下面方式就可以左右联动了。注意,只改动了加粗,加红的这一个属性。
<Window
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
x:Class="WpfApplication_GridSplitter.Window1"
Title="Window1" Height="100" Width="300">
<Grid Width="Auto" Height="Auto" >
<Grid.ColumnDefinitions >
<ColumnDefinition Width="*" />
<ColumnDefinition Width="Auto" />
<ColumnDefinition Width="5*" />
</Grid.ColumnDefinitions>
<Grid.RowDefinitions />
<StackPanel Grid.Column="0" Background="DarkBlue" />
<GridSplitter Grid.Column="1" Width="3" Background="Red"
ResizeBehavior="PreviousAndNext" />
<StackPanel Grid.Column="2" Background="#FFAEA130" />
</Grid>
</Window>
改动后的效果图:
总结:
GridSplitter 的默认 ResizeBehavior 属性导致我们无法实现左右联动,我们只需要简单的修改这个属性就实现左右联动。
这个问题,由于是第一次接触WPF,让我花了好几个星期,才发现是这里简单的问题所导致的。所以特写此篇博客,希望对碰到这个问题人有所帮助。
参考资料:
MSDN GridResizeBehavior Enumeration