2008年5月13日
#
摘要:
阅读全文
2008年5月10日
#
动态加载控件貌似给很多程序员都带来了困扰,经常收到这样的邮件,干脆就写下面这个示例来演示如何解决那些常见的问题吧。
其实常见的问题通常有这样两个:
1、通常他们都通过一个按钮来添加一个UserControl并将它们加入PlaceHolder容器的Controls中。然后页面上就会有一个另外一个按钮,这个按钮什么相关的事也没做,就是做了一次回发。这样的情况动态添加的控件就不翼而飞了。
2、今天收到了一封邮件说是要追加控件,和上面的情况看上去好像不一样,但实质就是同一回事。
原因:
其实网上有很多帖子都不约而同地解释了这个问题,这里我还是不厌其烦地解释一下:
首先,要提到大家所熟知很多人一知半解的页面生命周期,以至于很多居然还停留在将ASP.NET和Winform一样处理的层次上,因此就会有人试图将变量存在实例字段中,然后一如既往地指望它能够用来共享数据,结果总是无功而返,以我所知这样的人居然还不在少数,当然了,咱博客园的素质相对偏高,这种问题一般不在话下。事实上每次页面PostBack都会从Aspnet线程池中返回一个空闲的用户线程,用于处理用户本次的请求。摆弄一下那种浏览器进度条会动的控件基本也都算是回发事件了。两次回发之间可以当作没有什么关联的。但是你总能看到很多控件等在回发之后还能保持状态比如文本框边上有个按钮。你填写完了文本后狂点那个按钮,你会发现文本框中的文字还是你填写的那些而不会被清空。这就不得不说到ViewState这种神奇的双刃剑了。它的原理在MSDN上讲的很清楚,找不到的留言或发邮件给我我再慢慢给你找……
然后呢?还是查MSDN,关键字“TemplateControl.LoadControl ”我们在用PlaceHolder中动态添加控件的时候就会用到这个方法了。我们注意到这里有一句:“在将控件加载到容器控件时,该容器引发所添加控件的所有事件,直到所添加控件参与当前事件为止。但是,所添加控件不参与回发数据处理。”因为所添加的控件是不参与回发数据处理的,因此就会出现问题1中所遇到的按另一个按钮就消失的现象了。问题2其实也是一样的问题,因为事实上它们遇到的现象是一样的,只不过它的需求有所不同罢了。(可以理解成一个是i=1;另一个是i+=1;)
综上所述,问题的关键就是原本在页面加载的时候所有的控件初始化操作都应该完成,动态加载将加载的过程延迟到了事件被触发之后,因此在页面回发后,因为会有一次新的页面加载过程,显然这时候动态加载的控件是不存在的,但是用户预期的答案是显示已经加载的信息。这时候如果可能我们最好在加载的过程中进行控件的重新加载和数据绑定。常见的方法中我们呢通常通过LoadControl来动态加载控件,因此只要在页面输出之前的所有事件节点上我们都可以加载我们的控件。但是推荐的则是Init事件。在Load事件的时候进行数据绑定。
解决:
既然问题的原因找到了,我们就应该解决它,现在关键就是在回发后PlaceHolder.Controls的子集数量为0,也就是没有子控件,也就是很明显地控件跑没了。那么我们就应该在我们在他们还在的时候将其存放起来。在经典的回发模型中,ViewState通过将所有控件/其子控件的各个属性字段等都存放到ViewState中了,在最后Render的时候都一并丢给了用户。数据包括数据状态都一并发到了客户端,现在客户点击了一个能够引起回发的按钮或者下拉框按钮,所有这些数据状态以及客户修改(也许没有修改,但我们假定客户篡改过了)的数据都传回客户端。因为回发发生了,因此在加载数据的阶段IPostBackEventHandler和IPostBackDataHandler接口所定义的方法(通常由服务器控件实现)都将被调用,然后就是一系列的数据回填工作。用户的数据又被重新做成了新的ViewState放在页面里面又丢给了客户端。我曾经用一个比喻(相当拙劣的比喻,当时好像不是这样比喻的)是白衬衫(花花公子正版)被蓝笔画后,送去洗衣店,人家新拿了一件一样的白衬衫(花花公子高仿),然后用蓝笔划了一下还给你,事实上白衬衫不是你原来的那件了,但看上去还是无法分辨。因此我们这里也可以用类似的办法来解决。但是真的可以吗?用ViewState不仅有众所周知的性能问题,因为ViewState的存储介质(其实是指它的内容存储,可以理解成持久层)是页面,而页面是指接受文本的一种载体(正如网页事实上都是文本一样的道理)因此会有序列化的问题。这就给用户控件的开发带来了极大的不便。更关键的原因是不仅如此,因为UserControl压根没有支持序列化,因此你的控件即使精简到没有字段方法(就声明了个名字够精简了吧)再加上序列化特性,只要你继承自UserControl,就必然面临无法序列化的尴尬。况且它的性能问题确实也很值得关注。和ViewState有类似性质的常见的还有Session和HttpContext.Current.Cache等缓存,或者自己实现一个静态字典用于存储也是一个不错的选择。用它们是可以解决问题的,在下面的代码中将会用到。但这样的方案事实上是存在很多问题的。大家都知道Session是有超时时间的,默认长度也就是几十分钟,而且Session也有诸多其他方面的限制,因此用它来做容量如此之大的控件存储其实是非常不适合的。HttpContext.Current.Cache是一个高级的缓存对象,因为有完善的内部机制来限制其膨胀以及管理其内容,但也正因为这种管理比如大小限制等原因会导致在生产环境中可能会遭遇严重的性能问题。缓存应该用来存取较小的常用的数据,比如用户名/密码这样的常用数据,而不是这种大个头的东西。但是与ViewState相似的性质让它们有了承担这份责任的义务。(家里的大人都死光了,孩子也只好来当家了)这让我们想到了存储介质,事实上磁盘文件,数据库等都具有了同样的性质。另一条思路是来自简单地加载思路,因为对动态添加的控件来说,它有一个很明显的特征,它是动态添加的。因此既然可以在按钮事件处理程序中添加,同样也就可以在页面初始化事件处理程序中添加。按照页面的生命周期动态添加最好写在Init这时候理应做丰富的添加(不过不适合那种需要用按钮添加的用户需求了)[另外一点有点郁闷的是在MSDN中也是说应该在Init而不是Load中动态添加,但是同样是在MSDN的《如何:以编程方式创建 ASP.NET 用户控件的实例》居然就用了Load事件来处理,因此这种区分对页面开发人员事实上并不是那么严谨的,事实上也不会出现什么问题,因此也就没有人吹毛求疵了,而且Google出来的答案估计90%以上都是在Load中写的,一传十十传百的结果可能这个数值还在上升,所以就更没必要计较了]。刚刚打算帮发邮件的兄弟直接找一个答案发现了有网友说在每个页面都要做判断搞加载,很烦很烦……所以如果您的需求不是那种追求打开一个页面两天后再来点一下要追加或则重新加载控件的朋友,我的方案还是可以考虑的。当然如果你比较追求那种近乎变态的需求或者您的页面和淘宝有一样大的访问量的话,不凡试试我的方案,更好的解释是,我的方案可以当作理解控件动态加载原理解释的一个入口罢了。
我的例子,因为代码比较多,我就贴出如何调用的部分(也就是“如何用”的代码)源码可以在后面的链接中下载。
扩展性:虽然是为我那位邮友给出的答案,但是还是考虑了扩展性,我们可以尝试扩展用磁盘文件、网络、或者数据库的方式来作为存储介质,当然,您必须为此实现部分接口。局限性,因为有存储介质一说,因此不同容器托管方面不允许同时使用多种存储介质,否则将会出现两个集合,因此就带来了另一个扩展性,您可以自行实现扩展存储之间的数据同步,不过做此之前提醒您一下,不同的存储介质可能存在不同的存储能力,比如Session有大小限制,而数据库简直就是容量大王,这些数据之间的同步可能会引发新的问题,另者就是这样的同步除了看上去很酷之外并没有什么好处,将数据乱存的结果可能导致程序显得混乱,更尴尬的是数据同步所白白消耗掉的性能。当然如果您只是练练手的话您确实可以这么做,做完记得告诉我一下,哈哈,我也想不劳而获。哈哈。下面贴一下代码就不多做解释了,因为如果你理解了上面这些,看懂那些代码就不可能有问题了。
public partial class _Default : System.Web.UI.Page

{
public ContainerManager.ContainerManager cm = new ContainerManager.ContainerManager();

protected void Page_Load(object sender, EventArgs e)

{
//重载控件(HttpContext.Current.Cache作为存储介质)
cm.ReloadControls(HttpContext.Current.Cache, "PlaceHolder_DynamicUserControlContainer", PlaceHolder_DynamicUserControlContainer.Controls);
}

protected void btnInsertDynamicUserControl_Click(object sender, EventArgs e)

{
//Control c1 = LoadControl("DynamicUserControl.ascx");
//PlaceHolder_DynamicUserControlContainer.Controls.Add(c1);

int displayCount;
int.TryParse(txtNumber.Text, out displayCount);
if (displayCount == 0)

{
//追加控件(Session作为存储介质)
Control c1 = LoadControl("DynamicUserControl.ascx");
cm.AppendControl(this.Session, "PlaceHolder_DynamicUserControlContainer", PlaceHolder_DynamicUserControlContainer.Controls, c1);
}
else if (displayCount == 1)

{
//追加控件(HttpContext.Current.Cache作为存储介质)
Control c1 = LoadControl("DynamicUserControl.ascx");
Control c2 = LoadControl("WebUserControl.ascx");
cm.AppendControl(HttpContext.Current.Cache, "PlaceHolder_DynamicUserControlContainer", PlaceHolder_DynamicUserControlContainer.Controls, c1);
cm.AppendControl(HttpContext.Current.Cache, "PlaceHolder_DynamicUserControlContainer", PlaceHolder_DynamicUserControlContainer.Controls, c2);
}
else

{
//常见的动态加载控件后点击其他回发事件就导致控件丢失
PlaceHolder_DynamicUserControlContainer.Controls.Clear();
Control c1 = LoadControl("DynamicUserControl.ascx");
PlaceHolder_DynamicUserControlContainer.Controls.Add(c1);
cm.CacheControls(HttpContext.Current.Cache, "PlaceHolder_DynamicUserControlContainer", PlaceHolder_DynamicUserControlContainer.Controls);
}
}

protected void btnUnloadStorage_Click(object sender, EventArgs e)

{
cm.Remove(HttpContext.Current.Cache, "PlaceHolder_DynamicUserControlContainer");
}
}
源码地址:
http://www.cnblogs.com/Files/volnet/WebAppPlaceHolder.zip
2008年5月7日
#
摘要: 一直都在做asp.net的东西,WinForm好久没碰过了,近乎陌生。今天同事说他的Winform中的ListBox无法上下移动项,让我感觉好奇怪,怎么可能,不就是交替选项么,换换位置应该就可以搞定。看了同事的代码,只觉得一片混沌,实在不忍心再读下去,就自己操刀写一下了。(下面的代码使用了扩展方法,需要编译器版本>=3.0,也可以根据相关语法自行修改成2.0以下的版本)
代码功能:比较简单...
阅读全文
2008年4月19日
#
准备工作
其实没有什么准备工作,不过可以看看《关于打开ILDASM的方法(2种)》,或许对您有用。
前言
本来没有这个前言的,刚才自己看了标题,感觉有属性大全的味道,顺便改了标题,很可惜这里不讲那些你需要基础知识,那些知识您可以从MSDN获取。本文究竟讲些什么呢?本文其实没讲什么,就想看看属性被“编译”(变异)后的样子……
关于属性
get、set访问器
在使用了get、set访问器后,同类中不能够定义名称为get_[PropertyName]()与set_[PropertyName](TypeOfProperty value)这样的方法,否则将遭遇编译错误,如下图所示:
通过ILDASM工具查看后方可知道,get和set访问器会被编译器编译成依照上述规则而转换的公有方法供外部调用,这时我们若定义了相同签名的方法体将导致编译器的编译结果违背最基本的定义规则。
自动实现的属性
众所周知在.NET 3.0的新版本中增加了自动实现的属性(automatically implemented property)语言特性。这将带来形如下面这样的属性声明方式:
public int DotNet3Field1
{
get;
set;
}
那么它将如何编译呢?同样用ILDASM查看:
在图中我们可以很容易看到它通过了'<PropertyName>k__BackingField'的方式进行了声明。关于自动实现的属性的限制,请参看《自动实现的属性》而这样的方式避免了之前编译错误的尴尬,因为由编译器生成的字段名不符合我们的语言规范。
索引器
为了说明问题以下仅依照索引器的定义规范实现索引器,但不保证索引器能够正常工作:
public int this[int index]
{
get {
return 0;
}
set {
}
}
仔细对比就可以看见多了
.custom instance void [mscorlib]System.Reflection.DefaultMemberAttribute::.ctor(string) = (01 00 04 49 74 65 6D 00 00)
get_Item : int32(int32)
set_Item : void(int32,int32)
Item : instance int32(int32)
以下分别是get_Item和set_Item的IL代码
.method public hidebysig specialname instance int32
get_Item(int32 index) cil managed
{
// 代码大小 7 (0x7)
.maxstack 1
.locals init ([0] int32 CS$1$0000)
IL_0000: nop
IL_0001: ldc.i4.0
IL_0002: stloc.0
IL_0003: br.s IL_0005
IL_0005: ldloc.0
IL_0006: ret
} // end of method BaseClass::get_Item
.method public hidebysig specialname instance void
set_Item(int32 index,
int32 'value') cil managed
{
// 代码大小 2 (0x2)
.maxstack 8
IL_0000: nop
IL_0001: ret
} // end of method BaseClass::set_Item因此其实索引器与属性之间的唯一差别仅在于参数的个数上,当然外部表现还有那个中括号。
关于打开ILDASM的方法(2种)(推荐方法2):
1、通过VisualStudio在开始菜单下的Microsoft Visual Studio 2008\Visual Studio Tools\中的命令提示符中输入ildasm即可
2、将其添加至Visual Studio的外部工具中。获取它地址的方法简要描述为:先通过方法1启动ildasm,打开任务管理器,在进程中找到ildasm,右键选择调试,用VisualStudio或其他开发工具打开,通过属性可获得该工具的本地磁盘位置。我机器上它存在于C:\Program Files\Microsoft SDKs\Windows\v6.0A\bin\ILDASM.EXE,若您机器上它不是位于这个位置,按我的方式即可找到它,复制这个地址,并打开VisualStudio,在“工具”中找到“外部工具”,点击“添加”,填写相应信息即可。关于参数一栏可以参考http://msdn2.microsoft.com/zh-cn/library/f7dy01k1(VS.80).aspx中的相关说明。我这里添加了最基本的参数filename,将其添加到参数一栏即可:$(ProjectDir)bin\Debug\$(TargetName)$(TargetExt)
![image_thumb[9]](http://www.cnblogs.com/images/cnblogs_com/volnet/WindowsLiveWriter/ILDASM2_E52D/image_thumb%5B9%5D_thumb.png)
2008年4月8日
#
试试下面这个功能吧:(选中之后是利用“博客园”的博问搜索进行搜索……)
IE8中添加Activity的JavaScript脚本(这是一个来自淘宝网的例子)(本文最后脚本有做修改)
<script type="text/javascript">
function addActivity() {
if (navigator.userAgent.indexOf('MSIE 8') == -1) {
alert('请首先安装IE8.');
} else {
window.external.addService('activities/taobao-search.xml');
}
}
</script>相关XML描述文件:http://ie8.taobao.com/activities/taobao-search.xml
<?xml version="1.0" encoding="UTF-8" ?>
<openServiceDescription xmlns="http://www.microsoft.com/schemas/openservicedescription/1.0">
<homepageUrl>http://www.taobao.com</homepageUrl>
<display>
<name>淘宝搜索</name>
<icon>http://www.taobao.com/favicon.ico</icon>
</display>
<activity category="Find">
<activityAction context="selection">
<preview action="http://ie8.taobao.com/activities/preview.php?query={selection}" />
<execute method="get" action="http://ie8.taobao.com/list.php?query={selection}" />
</activityAction>
</activity>
</openServiceDescription>
写了一个简单的Google搜索的Activity XML:
<?xml version="1.0" encoding="UTF-8" ?>
<openServiceDescription xmlns="http://www.microsoft.com/schemas/openservicedescription/1.0">
<homepageUrl>http://www.google.com</homepageUrl>
<display>
<name>Google Search</name>
<icon>http://www.google.com/favicon.ico</icon>
</display>
<activity category="Find">
- <activityAction context="selection">
<preview action="http://www.google.com/search?q={selection}" />
<execute method="get" action="http://www.google.com/search?q={selection}" />
</activityAction>
</activity>
</openServiceDescription>
其中有句用于描述homepageUrl节点的说明:The main URL of the Activity where the user can access the service through browsing. All URLs expressed in this format must match the domain of the homepageUrl.也就是后续的所有的URLs都必须与主页Url在相同的域中。比如上例中就必须都是google.com域中。
openServiceDescription是专门用于Activity的xml节点名。
形如selection的位置,若在尾部加“?”则表示若该标记为空,将以空格代替。如:
<execute method="get" action="http://example.com/done.aspx?url={documentUrl}&title={documentTitle?}" />
其中documentUrl是必选项,而documentTitle因为带了“?”,所以是可选项。
另外以下几种简单的标记在“预览”窗体中是不会有效的。
· <select>
· <input type="text">
· <text area>
关于脚本:
//JavaScript API for Activities
//检查服务是否已经安装了
//参数:Service XML,name
window.external.IsServiceInstalled("http://maps.live.com/GetMap.xml", "map");
//添加服务
//参数:Service XML
window.external.AddService("http://maps.live.com/GetMap.xml");
下面是白皮书中所给出的示例,相对元素更齐全一些:
<?xml version="1.0" encoding="UTF-8"?>
<openServiceDescription
xmlns="http://www.microsoft.com/schemas/openservicedescription/1.0">
<homepageUrl>http://maps.live.com</homepageUrl>
<display>
<name>Map with Live Maps</name>
<icon>http://www.live.com/favicon.ico</icon>
</display>
<activity category="map">
<activityAction context="selection" >
<preview action=" http://maps.live.com/geotager.aspx">
<parameter name="b" value="{selection}" />
<parameter name="clean" value="true" />
<parameter name="w" value="320" />
<parameter name="h" value="240" />
<parameter name="format" value="full" />
</preview>
<execute action=" http://maps.live.com/default.aspx">
<parameter name="where1" value="{selection}" type="text" />
</execute>
</activityAction>
</activity>
</openServiceDescription>
试试下面这个功能吧:(选中之后是利用“博客园”的博问搜索进行搜索……)
下面是上面这个按钮的代码:
<div>
<script type="text/javascript">
function addActivity(xml) {
if (navigator.userAgent.indexOf('MSIE 8') == -1) {
alert('请首先安装IE8.');
} else {
window.external.addService(xml);
}
}
</script>
试试下面这个功能吧:(选中之后是利用“博客园”的博问搜索进行搜索……) </div>
<div><input onclick="addActivity('http://www.cnblogs.com/Files/volnet/cnblogsactivity.xml');" value="将博客园添加到IE8的活动中" type="button"> </div><?xml version="1.0" encoding="UTF-8" ?>
<openServiceDescription xmlns="http://www.microsoft.com/schemas/openservicedescription/1.0">
<homepageUrl>http://www.cnblogs.com</homepageUrl>
<display>
<name>博客园</name>
<icon>http://www.cnblogs.com/favicon.ico</icon>
</display>
<activity category="Blog">
- <activityAction context="selection">
<preview action="http://space.cnblogs.com/search/q?all={selection}" />
<execute method="get" action="http://www.cnblogs.com/" />
</activityAction>
</activity>
</openServiceDescription>
添加后若不喜欢,可以在IE8的“管理活动”中进行移除。
因为IE8为了做兼容IE7以及过去的网页,添加了一个“模仿IE7(Emulate IE7)”的按钮,这导致了当使用了该按钮的网页在使用以上所述的脚本的时候,navigator.userAgent的值显示的是IE7内核的相关代码,因此我们的判断可能会不准确,但是Activity功能事实上并不依赖于IE内核而存在,也就是如果你用的是IE8但你仅仅只是使用了模仿IE7的功能,那么你实质上还是可以使用Activity功能的。因此上面一直使用的脚本可以修改为下面的样子,以兼容这种特殊的伪装行为。
function addActivity(xml){
try
{
window.external.addService(xml);
}
catch(e)
{
//alert(e.toString());
alert('请首先安装IE8');
}
}
于此同时我也记录下了navigator.userAgent在常见浏览器中的一些值,以让大家更好地了解之前所使用的脚本所进行的内部工作。
//IE7(或IE8中使用了兼容IE7模式)
Mozilla/4.0 (compatible; MSIE 7.0; Windows NT 5.1; .NET CLR 2.0.50727; .NET CLR 3.0.04506.648; .NET CLR 3.5.21022; .NET CLR 1.1.4322)
//IE8
Mozilla/4.0 (compatible; MSIE 8.0; Windows NT 5.1; .NET CLR 2.0.50727; .NET CLR 3.0.04506.648; .NET CLR 3.5.21022; .NET CLR 1.1.4322)
//Firefox/2.0.0.13
Mozilla/5.0 (Windows; U; Windows NT 5.1; zh-CN; rv:1.8.1.13) Gecko/20080311 Firefox/2.0.0.13
//Safari3
Mozilla/5.0 (Windows; U; Windows NT 5.1; zh-CN) AppleWebKit/523.15(KHTML,like Gecko) Version/3.0 Safari/523.15
2008年4月7日
#
今天看到周兄的【全面解析DeepZoom 之一】酷!Deep Zoom的介绍,觉得实在是酷,就开始动手做了一下,做的不好,凑合着看吧。周兄是给大家全面解析,我只是做着玩的,所以叫做简单示范……
准备
1.Visual Studio 2008
2.Deep Zoom Composer(http://blogs.msdn.com/expression/archive/2008/03/05/download-the-preview-of-the-deep-zoom-composer.aspx)
3.Silverlight beta2
4.图片若干
步骤
1.打开Deep Zoom Composer,新建Project,Import图片,图片将如图显示在Deep Zoom Composer的右侧
2.点击Compose,然后将右侧的图片摆放到中间的面板上,注意到下方有一些布局工具,大家可以用来做图片对齐,摆放完的图片将如图所示。
3.点击Export,导出图片,给它一个命名,点Export按钮即可。我这里起名为Demo1Export
4.打开Visual Studio 2008,新建一个Silverlight项目,选择一个测试项目,我这里选择一个Web Application Project作为测试项目。
5.我们之前用Deep Zoom Composer导出的文件的文件夹内有个source images\OutputSdi的文件夹,将它Copy到我们用VS创建的Web项目的ClientBin文件夹下(可能需要先编译一次Web项目,注意不是Sliverlight项目,而是它的测试工程中)

6.导入文件后的项目如下所示。注意ClientBin文件夹。
7.点击Page.xaml,修改代码如下:
<UserControl x:Class="SA_DeepZoomComposerDemo1.Page"
xmlns="http://schemas.microsoft.com/client/2007"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" >
<Grid
x:Name="LayoutRoot"
Background="AliceBlue">
<MultiScaleImage
x:Name="msi"
ViewportWidth="1.0"
Source="/demo1export/info.bin" MouseLeftButtonDown="msi_MouseLeftButtonDown" MouseLeftButtonUp="msi_MouseLeftButtonUp" MouseMove="msi_MouseMove"/>
</Grid>
</UserControl>
这里因为我暂时没研究出怎么控制鼠标滚轮,所以没法做出那种滚动的效果(时间太迟了,不想去找了)……注意MultiScaleImage节点下的相关设置。我用于取代鼠标滚轮的做法是使用一个按钮来激活是否放大缩小。代码有点生硬,大家自己改造……
运行效果:
试着将ViewportWidth修改为2.0,再次运行,效果将变为:
8.点击Page.xmal.cs文件,修改代码如下:
using System;
using System.Collections.Generic;
using System.Linq;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Documents;
using System.Windows.Input;
using System.Windows.Media;
using System.Windows.Media.Animation;
using System.Windows.Shapes;
namespace SA_DeepZoomComposerDemo1
{
public partial class Page : UserControl
{
public Page()
{
InitializeComponent();
new MouseWheelHelper(msi).Moved += new EventHandler<MouseWheelEventArgs>(msi_Scroll);
}
Mouse _m = new Mouse();
private void msi_MouseLeftButtonDown(object sender, MouseButtonEventArgs e)
{
_m.DownPosition = e.GetPosition(msi);
}
private void msi_MouseLeftButtonUp(object sender, MouseButtonEventArgs e)
{
_m.UpPosition = e.GetPosition(msi);
msi.ViewportOrigin = new Point(msi.ViewportOrigin.X + _m.UpInterval.X, msi.ViewportOrigin.Y + _m.UpInterval.Y);
}
private void msi_MouseMove(object sender, MouseEventArgs e)
{
_m.MovePosition = e.GetPosition(msi);
}
private void msi_Scroll(object sender, MouseWheelEventArgs e)
{
Point p = GetCenter();
if (e.Delta > 0)
{
msi.ZoomAboutLogicalPoint(1.1, p.X, p.Y);
}
else
{
msi.ZoomAboutLogicalPoint(0.9, p.X, p.Y);
}
}
private Point GetCenter()
{
double centreX = msi.Width / 2;
double centreY = (msi.Width / msi.AspectRatio) / 2; //
return msi.ElementToLogicalPoint(new Point(centreX, centreY));
}
}
}
值得注意的是ZoomAboutLogicalPoint方法(试一下吧,试了你就知道……)
这里用到了MouseWheelHelper类,大家可以从http://blois.us/Silverlight/Scrolling2/MouseWheelHelper.cs下载,并添加到项目中,具体用法我就不再赘述了……
9.另外需要你添加一个Mouse类,
using System;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Documents;
using System.Windows.Ink;
using System.Windows.Input;
using System.Windows.Media;
using System.Windows.Media.Animation;
using System.Windows.Shapes;
namespace SA_DeepZoomComposerDemo1
{
public class Mouse
{
public Point DownPosition
{
get;
set;
}
public Point UpPosition
{
get;
set;
}
public Point MovePosition
{
get;
set;
}
public Point UpInterval
{
get
{
return new Point((DownPosition.X - UpPosition.X) / 1000, (DownPosition.Y - UpPosition.Y) / 1000);
}
}
}
}
10.运行程序,得到下面的效果:
总结
有许多需要修改的地方,不过通过这个简单的示例你应该知道http://memorabilia.hardrock.com/的效果并不至于难道哪里去,Deep Zoom Composer为我们封装的API太强大了,让我们不需要花费多少体力就可以做出很酷的效果了。还犹豫什么?快点开始用Silverlight2吧……
PS
因为包括了大量的图片,所以没法打包上传了,有需要Source Code的同志留下邮箱吧(放一个无图片版本的给大家下载添加图片请参考步骤1到5。),我会尽快发送的……
2008年4月4日
#
今天有一个网友通过“发送短消息
”向我提了一个题为《关于“C#重定向问题”的探讨》问题。本着对网友的负责,我决定写下完整示例。
关于网友所提的问题,因为描述的信息不够充足,我很难告知具体是哪里出了问题,直觉判断是masm.exe中的程序可能有点问题,但也不排除其他可能。希望能够提供更多的信息进行排错。
既然我写下了示例,而且自己也不曾做过相关的内容,就博留个脚印。
基本思路
应用程序的重定向问题基本上是通过启动另外一个进程运行另外的程序来执行一些处理,如果处理结果有返回值,主进程可以通过输出流的方式来进行获取。
准备
本示例采用WPF作为主进程的宿主程序,并通过构建控制台程序作为后台处理的程序进行处理。虽然采用WPF进行构建,但并不代表该文章所描述的内容仅限于此,WPF只是作为一个可视界面方便查看。如果您熟悉Winform也可以采用Winform编程的方式进行构建,甚至您仅用控制台应用程序也是可以的。(需要做一些小的改动)
基本步骤(简略)
1.添加如图所示的项目文件和程序界面:
2.分别为两个按钮添加事件处理程序:
(
左侧:其中白色文本框Name=txtInput,蓝色文本框Name=txtOutput,RunProcessSync执行同步调用进程的操作,RunProcessAsync执行异步调用进程的操作
右侧:CA_Processer用于处理具体操作的一个简单的控制台应用程序,WpfAppProcess即为左侧所示宿主程序
)
同步:
同步的操作相对比较简单,也就是当按下按钮后,启动另外一个进程进行处理,我们可以在处理完毕后从StandardOutput对象中获取处理进程输出流。
代码如下:
#region Sync
private void RunProcessSync_Click(object sender, RoutedEventArgs e)
{
string input = txtInput.Text;
ProcessStartInfo info = InitializeProcessStartInfo(input);
txtOutput.Text = DoProcessSync(info);
}
/// <summary>
/// 同步处理COMMAND
/// </summary>
/// <param name="info"></param>
/// <param name="input"></param>
/// <returns></returns>
&