艾伟:简单探照灯遮照效果的Silverlight程序-阿里云开发者社区

开发者社区> 狼人2007> 正文

艾伟:简单探照灯遮照效果的Silverlight程序

简介:    最近做SonySource项目时实现了几个很小的Silverlight程序,分别是Clock、HomePeoplePicker和ManageMentPeoplePicker。实际上这三个silverlight程序都非常简单,主要特点有以下几个方面:   1.
+关注继续查看

   最近做SonySource项目时实现了几个很小的Silverlight程序,分别是Clock、HomePeoplePicker和ManageMentPeoplePicker。实际上这三个silverlight程序都非常简单,主要特点有以下几个方面:

  1. Silverlight程序和页面上的HTML元素混合在一起,且在特定事件触发后要动态改变Silverlight程序在页面中占的位置及大小,但给用户的感觉是无缝连接;
  2. Javascript和Silverlight相互调用;
  3. 简单的探照灯遮照效果;

      下面就分别对我认为比较不好处理的地方或者一些我费了很多周折才实现的地方做一简要说明:

  一、使Silverlight浮动在Html元素中,并动态改变大小

  或许我这个小标题描述得还不是很准确,不能直观表达我的意思。举个例子,假设我们要用Silverlight做个下拉菜单,并将他放在html页面上使用。我们希望这个silverlight菜单所占的大小只是980px宽和30px高,因为在紧挨菜单的上面和下面的地方我们要放置一起其他的html元素。但当用户点击某个菜单项时,这个子菜单就展开,假设子菜单的大小是100px款和200px高,那就要求Siverlight所占的位置至少高为230px。由于Silverlight菜单只有30px高,所以下拉菜单就被截断而不能完整显示。我做的这个项目里三个Silverlight都遇到类似问题。例如PeoplePicker是在一个表格框里显示很多人的图像,当用户点击一个人的图像的时候弹出一个窗口以显示人的详细信息,在某种情况下,这个弹出窗口会超出包含所有人物图像的表格从而部分被截断。在《Silverlight嵌入到HTML之windowless属性及运用AjaxControlToolKit时出现虚线边框的问题》所描述的问题就是基于这种需求。

  上述问题是否可以简单的描述为:Silverlight程序在页面上只在指定的Silverlight plug in(<object/>元素)中显示,当超过Silverlight Plug in时就会被截除;当Silverlight程序的宽和高在运行时不确定时,就要求Silverlight Plug in的大小和位置随之改变以使所有silverlight内容都能完整正确的显示出来。

      我在这个项目里的解决办法就是基于以上的描述,动态改变Silverlight plug in(object元素)的大小,并时silverlight plug in以绝对定位的方式浮动于其他元素之上,且让silverlight plug in的背景色为透明以不至于让他遮盖所有的底层元素。

  首先,我们在页面上定义一个<div>元素,我们的silverlight程序就放在这个<div>里,并以它作为silverlight的定位基准。即正常情况下silverlight和包含它的<div>的位置和大小完全一致。当需要改变silverlight的大小和位置时,也以该<div>为参考。在页面布局时,我们只用关注这个<div>应该放到哪就行了。HTML代码大致如下:

Code
<div id="silverlightHomePeoplePickerHost" style="width:275px;height:324px;background-color:transparent;float:left">
        
<object data="data:application/x-silverlight-2," type="application/x-silverlight-2" style="width:100%;height:100%;position:absolute">
            
<param name="source" value="../ClientBin/SEL.SonySource.Silverlight.HomePeoplePicker.xap"/>
            
<param name="onerror" value="onSilverlightError" />
            
<param name="onload" value="onSilverlightHomePeoplePickerLoaded" />
            
<param name="background" value="transparent" />
            
<param name="windowless" value="true" />
            
<param name="minRuntimeVersion" value="2.0.31005.0" />
            
<param name="autoUpgrade" value="true" />
            
<a href="http://go.microsoft.com/fwlink/?LinkID=124807" style="text-decoration: none;">
                 
<img src="http://go.microsoft.com/fwlink/?LinkId=108181" alt="Get Microsoft Silverlight" style="border-style: none"/>
            
a>
        
object>
        
<iframe style='visibility:hidden;height:0;width:0;border:0px'>iframe>
    
div>

     那么怎么初始化silverlight的位置和大小呢?就在onload事件里处理: 

Code
var silverlightHomePeoplePickerInstance = null;

// When loaded the silverlight HomePeoplePicke in html page
function onSilverlightHomePeoplePickerLoaded(sender, args) {            
      silverlightHomePeoplePickerInstance 
= new SilverlightHomePeoplePicker(sender);
}

     在onSilverlightHomePeoplePickerLoaded事件中,首先通过参数sender创建一个SilverlightHomePeoplePicker的实例,SilverlightHomePeoplePicker对象的代码大致如下:

Code
// the class of SilverlightHomePeoplePicker
function SilverlightHomePeoplePicker(sender) {
    
this.slApp = sender;
    
this.objElement = this.slApp.getHost();
    
this.divContainer = this.objElement.parentNode;
    
this.page = this.objElement.Content.Page;

    
// the left and top offset to the directly parent element of silverlight object tag
    this.leftOffsetToContainer = 0;
    
this.topOffsetToContainer = 0;

    
this.page.addEventListener("SilverlightScopeChanged", SilverlightHomePeoplePicker.createDelegate(thisthis.handleSilverlightScopeChangedEvent));
    addEvent(window, 
"onresize", SilverlightHomePeoplePicker.createDelegate(thisthis.setPosition));

    
with (this.objElement.style) {
        position 
= "absolute";
        zIndex 
= 999;
        width 
= this.divContainer.offsetWidth + "px";
        height 
= this.divContainer.offsetHeight + "px";
    }

    
this.setPosition();
}

SilverlightHomePeoplePicker.prototype 
= {
    
// change scope of silverlight object tag
    handleSilverlightScopeChangedEvent: function(s, e) {
        
this.leftOffsetToContainer = e.Left;
        
this.topOffsetToContainer = e.Top;
        
this.setPosition();

        
with (this.objElement.style) {
            width 
= e.Width + "px";
            height 
= e.Height + "px";
        }
    },

    
// set left and top positions
    setPosition: function() {
        setSilverlightObjectPosition(
this.objElement, this.divContainer, this.leftOffsetToContainer, this.topOffsetToContainer);
    }
}

SilverlightHomePeoplePicker.createDelegate 
= function(instance, method) {
    
return function() {
        
return method.apply(instance, arguments);
    }
}

 

      可以看到,在构造SilverlightHomePeoplePicker的实例时就设置了 <object/>元素的位置和大小。

  leftOffsetToContainer和topOffsetToContainer是指silverlight plug in ( object 元素 )左上相对于包含它的<div>的左上角的偏移量,一般正常情况下,这个偏移量为0,即silverlight与包含它的<div>左上角位置重叠。初始化时同时设置了silveright插件的宽和高,即等于包含它的<div>的宽和高。

  那么当Silverlight的位置和大小需要改变时怎么办呢?谁来负责处理这个变化呢?首先,在Silverlight程序里应该最先知道这个Silverlight程序的范围是否改变了,是否需要改变silverlight plug in的位置和大小来正确显示整个Silverlight的内容。例如,当弹出或关闭详细信息窗口时,Silverlight程序应该做这个检查,如果需要改变silverlight plugin的位置和大小,就通知javascript程序。

  Silverlight的启动xmal文件Page.xaml大概是这个样子:

Code
<UserControl x:Class="SEL.SonySource.Silverlight.HomePeoplePicker.Page"
    xmlns
="http://schemas.microsoft.com/winfx/2006/xaml/presentation" 
    Width
="275" Height="324">   
    
    
<Grid x:Name="LayoutRoot" RenderTransformOrigin="0,0">        

        


        

        
<Grid.RenderTransform>
            
<TranslateTransform x:Name="LayoutRootTranslate" X="0" Y="0" />
        
Grid.RenderTransform>        
    
Grid>
UserControl>

  然后定义一个SilverlightScopeChangeHandler:

Code
/// 
    
/// When the scope of the app changed, call the handler to invoke js to resize the position of the plug-in object
    
/// Most time, this will be triggered by popup or close the detail window
    
/// 
    
/// 
    
/// 
    public delegate void SilverlightScopeChangedHandler(Object sender, ScopeEventArgs e);

    
/// 
    
/// Indicate the scope of the silverlight app
    
/// 
    public class ScopeEventArgs : EventArgs
    {
        
/// 
        
/// Relative to the orginal left position, it will always less than 0
        
/// 
        [ScriptableMember]
        
public double Left
        {
            
get;
            
internal set;
        }

        
/// 
        
/// Relative to the orginal top position, it will always less than 0
        
/// 
        [ScriptableMember]
        
public double Top
        {
            
get;
            
internal set;
        }

        
/// 
        
/// The actual width of the app
        
/// 
        [ScriptableMember]
        
public double Width
        {
            
get;
            
internal set;
        }

        
/// 
        
/// The height width of the app
        
/// 
        [ScriptableMember]
        
public double Height
        {
            
get;
            
internal set;
        }

        
public ScopeEventArgs(double left, double top, double width, double height) : base()
        {
            
this.Left = left;
            
this.Top = top;
            
this.Width = width;
            
this.Height = height;
        }

        
public static bool operator ==(ScopeEventArgs e1, ScopeEventArgs e2)
        {
            
return Object.Equals(e1, e2);
        }

        
public static bool operator !=(ScopeEventArgs e1, ScopeEventArgs e2)
        {
            
return !Object.Equals(e1, e2);
        }

        
public override bool Equals(object obj)
        {
            
if (obj == null)
                
return false;
            
if (GetType() != obj.GetType())
                
return false;
            ScopeEventArgs e 
= (ScopeEventArgs)obj;            
            
return (this.Left==e.Left && this.Top == e.Top && this.Width == e.Width && this.Height == e.Height );
        }

    }

  在Page.cs页面中,当捕获到需要改变silverlight plugin(object元素)的位置和大小时,就触发相应的SilverlightScopeChange事件:

Code
    // scope changed event
        [ScriptableMember]
        
public event SilverlightScopeChangedHandler SilverlightScopeChanged;

        
// save current silverlight scope
        private ScopeEventArgs silverlightScope = null;

   /// 
        
/// detect if the size of silverlight app is larged than orgianl size or if it's return to the origianl size
        
/// This will invoke the js to reposition the silverlight plug-in on the html page
        
/// 
        private void DetectSilverlightScopeChange()
        {
            ........

            ScopeEventArgs se 
= new ScopeEventArgs(left, top, width - left, height - top );
            
if (silverlightScope != se)
            {
                silverlightScope 
= se;
                
LayoutRootTranslate.X = -silverlightScope.Left;
                LayoutRootTranslate.Y 
= -silverlightScope.Top;

                
if (SilverlightScopeChanged != null)
                    SilverlightScopeChanged(
this, silverlightScope);
            }
        }

  当Silverlight应用程序检查到需要改变 <object>元素的大小时,就触发SilverlightScopeChanged事件,告知javascript来处理,同时SilverlightScopeChangeEventArgs参数还告诉了silverlight plug in需要的宽、高以及相对于上级<div>的偏移量。

  到这里,大家就会看到上述SilverlightHomePeoplePicker代码中

this.page.addEventListener("SilverlightScopeChanged", SilverlightHomePeoplePicker.createDelega
te(
this, this.handleSilverlightScopeChangedEvent));
的意义所在了。

二、Silverlight程序和Javascript程序的相互调用 

  1. javascript注册silverlight的事件

  其实上面的代码已经体现了这点,即通过javascript的代码

this.page.addEventListener("SilverlightScopeChanged", SilverlightHomePeoplePicker.createDeleg
ate(
this, this.handleSilverlightScopeChangedEvent));

来注册silverlight端的事件。这里要注意几点:
     a. Silverlight事件必须是Scriptable的,即事件的声明上加上[ScriptableMember];
     b. 事件原型必须有两个参数, sender和e。sender是object类型的,e需要时继承自EventArgs类型的。我在这里走了些弯路。
               c. 必须在Silverlight程序中注册整个类以供javascript访问,例如:HtmlPage.RegisterScriptableObject("Page", this);
     d. silverlight程序在LayoutUpdated事件中是无法访问或触发任何javascript方法的。

  2. silverlight注册html element的事件
    在silverlight中可以注册html元素的事件。例如在clock程序中,要求当用户点击网页上任何位置时都要关闭timezonelist下拉列表。这时可以在silverligh中注册document.click事件:
  

Code
HtmlPage.Document.AttachEvent("onclick", Document_Click);
...

private void Document_Click(object sender, HtmlEventArgs e)
{
    
if (ListBoxMaskLayer.Visibility == Visibility.Visible)
        HideTimezoneList();
}

   但后来由于firefox下点击silverlight时也触发document.click事件,所以没有采用这种方式。

  3. Javascript调用Silverlight的方法9
   由于没有采用第2点所示的方法,所以document.click事件还是由javascript来处理:
   但是,在handleClickDocumentEvent事件里,javascript调用了silverlight的方法:
  this.page.HideTimezoneList();
  与第1条所述类似,silverlight中HideTimezoneList这个方法必须标记为[ScriptableMember],且要注册Page类到js:
  HtmlPage.RegisterScriptableObject("Page", this);  

Code
// the class of SilverlightClock
function SilverlightClock(slClock) {
  ...............

    
this.page = this.objElement.Content.Page
    addEvent(document, "onclick", SilverlightClock.createDelegate(thisthis.handleClickDocumentEvent));
  .................
}

SilverlightClock.prototype 
= {    
    
// when click on the document, shrink the timezone listbox
    handleClickDocumentEvent: function(e) {
        e 
= window.event || e;
        
var srcE = e.srcElement || e.target;
        
if (!srcE.source || srcE.source.indexOf("SEL.SonySource.Silverlight.Clock.xap"== -1) {
            
if (this.objElement.style.zIndex > 0) {
                
try{                                   //BUG: there will be an error in FF
                    this.page.HideTimezoneList();
                }
                
catch(err){}                
            }
        }
    }
}

 

   三、简单探照灯效果的实现

   这里的探照灯的实现,主要运用了RadialGradientBrush。在整个Silverlight的内容上面放一个Rectangle,且将其Fill为RadialGradientBrush:

Code
 <Grid x:Name="LayoutRoot" RenderTransformOrigin="0,0" MouseMove="LayoutRoot_MouseMove" MouseEnter="LayoutRoot_MouseEnter">
        
        

        
<local:UniformGrid x:Name="PicturesContainerGrid" Width="275" Height="324" Columns="5" Rows="4" MinWidth="55" MinHeight="81" />
     
        

        
<Canvas x:Name="LamplightCanvas" Width="275" Height="324" MouseLeftButtonDown="LamplightCanvas_MouseLeftButtonDown" Cursor="Hand">
            
<Rectangle x:Name="LamplightRect" Width="700" Height="644" Stretch="Fill" RenderTransformOrigin="0.5,0.5" Canvas.Left="-208" Canvas.Top="-199" >
                
<Rectangle.Fill>
                    
<RadialGradientBrush x:Name="LamplightGradient" RadiusX="0.5" RadiusY="0.666667" Center="0,0" GradientOrigin="0,0">
                        
<GradientStop Color="#00000000" Offset="0.08"/>
                        
<GradientStop Color="#7F000000" Offset="0.143"/>
                        
<GradientStop Color="#7F000000" Offset="1"/>
                        
<GradientStop Color="#00FFFFFF" Offset="0"/>
                    
RadialGradientBrush>
                
Rectangle.Fill>
            
Rectangle>
       

            
<Canvas.Clip>
                
<GeometryGroup x:Name="LamplightClipGroup">                    
                    
<RectangleGeometry Rect="0,0,275,324"  />
                    
<RectangleGeometry Rect="0,0,0,0" x:Name="MouseOverRectGemo" />
                
GeometryGroup>
            
Canvas.Clip>
        
Canvas>            
    
Grid>

  当鼠标移动时,就动态改变RadialGradientBrush的CenterGradientOrigin属性。大致代码如下:

Code
private void LayoutRoot_MouseMove(object sender, MouseEventArgs e)
{
      HanleMouseEventOnLayoutRoot(e);
}

 
private void HanleMouseEventOnLayoutRoot(MouseEventArgs e)
 {
      
// if the mouse move in the Main Grid
      if (IsCaptureMouse(PicturesContainerGrid, e))
       {
            
// if no people has been selected ,move the lamplight
            if (dicPersonDetails.Count == 0)
            {
                    
// move lamplight 
                    SetLamplight(e);

                    
// open a small rect to enable triger the mouseover and click of HomePerson control
                    Point ptMouse = e.GetPosition(LamplightCanvas);
                    MouseOverRectGemo.Rect 
= new Rect(ptMouse.X - 2, ptMouse.Y - 244);
             }
                
else
                {
                    
// Don't move lamplight because one HomePerson has alreay get the lamplight, 
                    
// but a big rect should open to trigger MouseOver of HomePerson Control
                    HomePerson homePerson = GetHomePerson(e);
                    
if (homePerson != null)
                    {
                        
// check if click on any detail window
                        foreach (KeyValuePair<HomePerson, HomeDetails> kv in dicPersonDetails)
                        {
                            
if (kv.Value.IsOpened && IsCaptureMouse(kv.Value, e))
                            {
                                MouseOverRectGemo.Rect 
= new Rect(-1-100);
                                
return;
                            }
                        }

                        RemoveLamplightMask(homePerson);
                    }
                }
            }
        }

 
private void SetLamplight(Point pointRelativeToLamplightRec)
{
     Point ptCenter 
= new Point(pointRelativeToLamplightRec.X / LamplightRect.ActualWidth, pointRelativeToLamplightRec.Y / LamplightRect.ActualHeight);
     LamplightGradient.Center 
= LamplightGradient.GradientOrigin = ptCenter;
}

        
/// 
        
/// set the focus lamplight to the mouse position
        
/// 
        
/// 
        private void SetLamplight(MouseEventArgs e)
        {
            Point pointRelativeToLamplightRec 
= e.GetPosition(LamplightRect);
            SetLamplight(pointRelativeToLamplightRec);
        }

  另外还有一个问题,由于在所有内容上遮照了一个Rectangle,那么当鼠标移到某个位置时,虽然灯亮了,那怎么触发Rectangle下层元素的事件呢?我这里主要运用了包含Rectangle的Canvas的Clip属性,在Clip里我定义了两个Geometry,动态改变Geometry的位置,就相当于在Rectangle上动态打一些窟窿。

  四、其他问题 

  当然还有一些需要改进的地方,例如Clock的点"edit"按钮后弹出的下拉框的宽和高应该动态自适应,可以在TimezoneList的Curstom Control的MeasureOverride事件中获取应该分配的宽和高,进而计算其他数据达到自适应的效果,简单伪代码如下:

Code
public event RequestMeasuredSizeHandler RequestMeasuredSize;

protected override Size MeasureOverride(Size availableSize)
 {
      Size desireSize 
= base.MeasureOverride(new Size(double.MaxValue,double.MaxValue));
      
if (RequestMeasuredSize != null)
         RequestMeasuredSize(desireSize);

       
this.Width = desireSize.Width;
       
this.Height = desireSize.Height;
       
return desireSize;
 }


     还有,在探照灯已经固定在某一个人的头像上后,当鼠标滑动时还可以使效果更加柔和。

版权声明:本文内容由阿里云实名注册用户自发贡献,版权归原作者所有,阿里云开发者社区不拥有其著作权,亦不承担相应法律责任。具体规则请查看《阿里云开发者社区用户服务协议》和《阿里云开发者社区知识产权保护指引》。如果您发现本社区中有涉嫌抄袭的内容,填写侵权投诉表单进行举报,一经查实,本社区将立刻删除涉嫌侵权内容。

相关文章
silverlight:DeepZoom版的图片局部放大效果
先上演示地址: http://images.24city.com/jimmy/DeepZoom/  步骤:1.先启动PhotoShop,利用自动等分切片功能把图片等分成若干小图片,并导出,参考下图: 2.
697 0
阿里云服务器端口号设置
阿里云服务器初级使用者可能面临的问题之一. 使用tomcat或者其他服务器软件设置端口号后,比如 一些不是默认的, mysql的 3306, mssql的1433,有时候打不开网页, 原因是没有在ecs安全组去设置这个端口号. 解决: 点击ecs下网络和安全下的安全组 在弹出的安全组中,如果没有就新建安全组,然后点击配置规则 最后如上图点击添加...或快速创建.   have fun!  将编程看作是一门艺术,而不单单是个技术。
3956 0
一起谈.NET技术,Silverlight 游戏开发小技巧:技能冷却效果2(Cooldown)2
  可能会奇怪为什么有来一个第2号版本,其实,这是改进版本,而这个改进版本实现起来更加容易,更加方便,但是问题也是很明显的,因为会加上一个100多KB的DLL,对于网页游戏来说,任何1KB都是宝贵的资源,就为了这个小效果而平白无故增加XAP的容量,也验证了有得必有失道理,鱼与熊掌应该如何取舍呢,先来看看这个实现方式吧。
723 0
阿里云服务器安全组设置内网互通的方法
虽然0.0.0.0/0使用非常方便,但是发现很多同学使用它来做内网互通,这是有安全风险的,实例有可能会在经典网络被内网IP访问到。下面介绍一下四种安全的内网互联设置方法。 购买前请先:领取阿里云幸运券,有很多优惠,可到下文中领取。
9307 0
阿里云ECS云服务器初始化设置教程方法
阿里云ECS云服务器初始化是指将云服务器系统恢复到最初状态的过程,阿里云的服务器初始化是通过更换系统盘来实现的,是免费的,阿里云百科网分享服务器初始化教程: 服务器初始化教程方法 本文的服务器初始化是指将ECS云服务器系统恢复到最初状态,服务器中的数据也会被清空,所以初始化之前一定要先备份好。
2901 0
silverlight ListBox 多列图片效果
这个功能之前用wpf写过一次这次用Silverlight写一次 这两种写法上基本上没有太大的差别 这个Demo并不完美,只是给大家提供一个思路 源码:SilverLightListPricture.
547 0
+关注
狼人2007
个人对技术的追求:代码少而精捍;思路清晰美观;可扩展好维护;技术驱动商业; 人生格言:只要你有信念,有追求,并且坚持,那你一定比随波逐流,行得远行得正...
3528
文章
0
问答
文章排行榜
最热
最新
相关电子书
更多
文娱运维技术
立即下载
《SaaS模式云原生数据仓库应用场景实践》
立即下载
《看见新力量:二》电子书
立即下载