Posted on 2007-03-29 03:18
Jeffrey Zhao 阅读(5554)
评论(78) 编辑 收藏 所属分类:
Ajax & Atlas相关 、
ASP.NET AJAX
这是一个很常见的使用场景,尤其是出现了UpdatePanel之后:页面中有一排菜单,点击菜单中的每一项,都会使某个UpdatePanel中出现不同的控制面板。在开发时,往往会将每个的控制面板写成不同的用户控件,点击菜单时事实上就是在UpdatePanel中放入不同的用户控件。
如果要开发这样的功能,从理论上来说并不困难,但是如果要能够在控件之间灵活切换,甚至要从控件A的某个操作中切换到控件B,可能就需要增加控件之间的耦合度了。因此,如何控制这样的切换似乎需要细细考虑一下。
在这里,我选择使用一个第三方的控件来进行统一处理,这个控件就是SwitchPartManager。在了解这个控件的实现之前,我们先来看一下一个简单的使用示例吧。
使用效果
首先,在页面中,会使用两个按钮在两个用户控件之间进行切换。在SwitchPartManager的PlaceHolderUpdatePanelID属性指定了作为容器的UpdatePanel,而点击不同的按钮,则会调用ScriptManager的SwitchTo方法切换至不同的控件。如下:
在ControlA中有一个按钮,点击它之后将会切换到ControlB。如下:
而在ControlB中,它自身含有一个UpdatePanel和一个按钮,点击按钮则可以刷新时间:
编写代码,控件之间的切换都是非常的容易。大家可以点击这里查看使用效果。
SwitchPartManager控件实现
实现这么一个控件其实比想象中容易许多。处理这个问题的关键在于如何在(同步或异步)PostBack后正确地恢复当前已经加载的控件。只要能够正确恢复了控件的状态,剩下的问题都是由ASP.NET自身的机制来完成了,例如触发事件等等。
我们来看一下SwitchPartManager的关键实现代码:
SwitchTo方法是用于切换用户控件的方法,它会将UpdatePanel内已有的控件(例如从ControlA切换到ControlB时,UpdatePanel中已经有了ControlA)清除,然后再向UpdatePanel中添加新的控件。
我在这里将当前当前的控件标识记录在私有变量partTypeToSave中,它会在Page的PreRenderComplete时作为<input type="hidden" />的形式输出。我在这里使用了HiddenField作为保留控件选择状态的方式,这样可以避免因为ViewState被禁用而导致的数据丢失。
这样,我们的页面在PostBack之后,就能够通过接受到的信息来恢复UpdatePanel的控件了。我在在Page的InitComplete时恢复UpdatePanel中的控件——很容易,直接使用SwitchTo方法就可以了。
这几乎就是SwitchPartManager的完整代码。其实这个类相当的简单,但是它的价值却不小。但是根据我的经验,似乎为了自己的项目开发Custom Control的朋友不多,大家大都是在写用户控件(ascx)。其实在有些时候,为自己的应用编写一个Custom Control,尤其是用于“管理”的控件,其实能够使页面的逻辑变得清晰许多。
点击这里下载SwitchPartManager的代码。
Feedback
不错,收藏了!
看样子老赵是不把UpdatePanel玩烂了不罢休了,嘿嘿。。。
靠,我就是自己写CustomControl的傻子了这么说 -__-。可以稍微改进一下,把自己的input hidden改为ControlState,纯粹为了好看 :P 。
你说的这种需求,有时候干脆iframe最干净 -_-
我目前有类似的应用,确实控件状态的问题用UpdatePanel和你这种方式容易很多,问题是假设我自己有能力处理,并假设我水平是正好就我的特殊需求可以优化到传输量、服务器运算量最小,那么UpdatePanel比这种极端的优化开销会大多少,这是我想请教的问题 :)。
@怪怪
我没有听懂您的意思。
至于传输量,这样看您的时机情况。
用iframe的话无法与页面其他部分交互,往往是无法替换现在的做法的。
@Jeffrey Zhao
我没有听懂您的意思。
至于传输量,这样看您的时机情况。
用iframe的话无法与页面其他部分交互,往往是无法替换现在的做法的。
采用这种方式, 对于用户控件是否第一次加载还是Postback不容易区分, 因为每次Page.IsPostBack都为True....
@MK2
您可以自己修改代码,穿参数也好,辨认第一次加载一也好。
@Clark Zheng
老赵不止把UpdatePanel玩烂,还能把烂的东西从组。呵呵
佩服老赵的钻研和你的勤奋。
@Artech
其实我现在是想尝试着把一些常见的应用场景总结成UpdatePanel使用上的Pattern。
@Jeffrey Zhao
这是一个好的想法,这样可以消除很多人对Pattern的神秘感。Pattern就在我们身边, Coding is design.
trade.igfire.com是一个全新的交易平台,它可以让供货商们把自己的虚拟产品直接出售到国外玩家的手里或直接联系国外的收购商,让利润翻上几翻.让这个梦想由我们来给您实现!
同样,收货商们也可以多一个平台来做好自己的销售.
我们看不懂英文,如何和老外交易?
相信很多人都会提出这样的问题,不用担心,这在我们网站完全不是问题.我们有优秀的客服团队在时刻为您服务,您只要每个月支付RMB 500就可以享受到贵宾的待遇,专业的服务.我们的客服对您的交易全程跟踪,让您与老外的交流没有障碍.生意越做越红火!
我们没有paypal,怎么收美金?
Paypal注册的全过程在我们网站上有详细的中文注解,你可以很轻松的注册成功!同时paypal对中国推出了中文界面,只要重设语言,您立即可以轻松使用他的一切功能!
paypal也推出了电汇美金(手续费20$一次)到国内银行的业务,如果你拥有香港银行,无须电汇就能可以把美金转入银行.
心动了吗?那么就赶快来体验一线卖家的精彩吧~~~
@Artech
哦,这个“模式”的意思只是“使用方法”:)
很好啊,实用.自定义控件确实用处很大.
一个合格的程序员应该会开发自定义控件,看来我不是很合格.要好好学习了.
请问象chinaren首页上的登录模块可以用此控件实现不?
SwitchPartManager 里不放置任何内容
ControlA.ascx (放未登录时视图),点login提交事件切换到
ControlB.ascx (登录成功信息“如欢迎你。。。。”)
ControlA.ascx.cs :
protected void Login_Click(object sender, EventArgs e)
{
if( dal.member.checklogin(name,pw) )
{
SwitchPartManager.GetCurrent(this.Page).SwitchTo("ControlB");
}
}
@guozili
我不知道Chinaren到底什么样的,不过您的代码是可以的。
@12456
没错,这样做的确不太妥当。您可以修改一下。:)
如果那个user control有验证控件的话,那么这样做就有点麻烦了...
This is Control A.
<asp:LinkButton ID="LinkButton1" runat="server" OnClick="LinkButton1_Click">Switch To Control B</asp:LinkButton>
<asp:TextBox ID="TextBox1" runat="server"></asp:TextBox>
<asp:RequiredFieldValidator ID="RequiredFieldValidator1" runat="server" ControlToValidate="TextBox1"
ErrorMessage="RequiredFieldValidator"></asp:RequiredFieldValidator>
假如我加了个textbox必须输才能提交...,那么如果load Control A的时候,那个验证控件就不起作用了...
@kakin
您是使用了微软提供的新的Validator吗?
您好,在使用时发现一些问题
1. IsPostBack一直是True
2. 用户控件中的ViewState无法保存,就是加载的用户控件,然后这个和户控件又提交了,ViewState无法获取,但里面的textbox的Text倒是有改变
3. 就是传递参数给用户控件,我重载了SwitchTo方法,传递一个Control过来,这个Control已经Load并设置了相应属性,属性放到了ViewState,但一提交ViewState就没有了,不知道有没有其他好的方法。
谢谢。
kakin 的问题提的很好,我也遇到了这样的问题,不过赵大哥真的很棒,能自己写这样的控件 了不起!~~~
This is Control A.
<asp:LinkButton ID="LinkButton1" runat="server" OnClick="LinkButton1_Click">Switch To Control B</asp:LinkButton>
<asp:TextBox ID="TextBox1" runat="server"></asp:TextBox>
<asp:RequiredFieldValidator ID="RequiredFieldValidator1" runat="server" ControlToValidate="TextBox1"
ErrorMessage="RequiredFieldValidator"></asp:RequiredFieldValidator>
假如我加了个textbox必须输才能提交...,那么如果load Control A的时候,那个验证控件就不起作用了...
@hdz
才看到这些。我想知道你们用的是兼容了UpdatePanel的Validator还是普通的Validator呢?
@llinzzi
全是些简单的组件,谈不上工具包。
配服,我用了一整个下午的时间在研究怎么切换用户控件,要是早看到这个控件,也不用花那么长时间了:(
不过,时间也没有白花,至少对这种控件的细节有了比较深的了解。谢谢楼主的辛勤劳动和无私奉献!!!
老赵,在UpdatePanel如何给GridView添加全选/取消并做删除呢.
@spc920
删除就是最普通的作法,全选/取消是什么意思呢?
错误 2 找不到类型或命名空间名称“UpdatePanel”(是否缺少 using 指令或程序集引用?) F:\gsjy\SourVer.Component\SwitchPartManager.cs 69 17 SourVer.Component
编译时出现这样的提示:
不知使用updatepanel需要怎么样命名空间?
我试到用了,不过还是有些问题,好像简单的用户控件正确运行,对于有提交的出现找不到switchpartmanage之类的提示,估计是updatepanel在提交时,viewstate方面出现了差错,我反复试了就是不行,自己干脆简单地用button 的onclick加载用户控件,并在saveviewsate中保存"xxx.ascx" 然后在loadviewstate中加载,这样也可切换,但有小问题,updatepanel中动态加载的所有控件,首次提交数据丢失,第二次没问题,或动态加载后,整页提交/refresh一下(保存),之后updatepanel中的首次提交不会出问题,我只能认为是updatepanel提交的机制方面的可能.
而非updatepanel中动态加载,如上所用的方法,不会出现问题,现在对此updatepanel中此现象待研究,哪位大侠给出正解,多谢!!!!
有与我一样困惑的朋友否? 不知大家能否一起研究一下ajax的document(官司),里面有server reference/client reference,内容详解的确不错的很,我想一定可能找到解决updatepanel动态加载用户控件关于viewstate方面的问题
@xpycy
我觉得这一点上没有什么问题,都比较清晰,您遇到的究竟是什么问题呢?
请问一下,当点击"ButtonA"的时候,如何让网页先在客户端用javascript清空UpdatePanel1内的控件,再向服务端申请创建ControlA.ascx,我主要的目的是,想当点击"ButtonA"的时候,提交的数据,尽量少,不知道这个想法能不能实现?
@myfriend253
只有页面上的Hidden Input才会被Post,显示在页面的东西和传输的数据无关。您可以用一些嗅探器看一下。:)
非常好,我一直在苦苦寻求解决办法, 但就是不支持Viewstate啊
呵,感谢老赵的回复
1.能否介绍一款好的嗅探器,可以方便查看网页提交的数据情况?
2.再问一下,你说只有页面上的的Hidden Input才会被Post,那么我可以理解成:当点击"ButtonB"的时候,原先在updatepanel1中生成的用户控件ControlA.ascx将会以Hidden Input进行提交?
3.接上一个问题,ButtonB生成的网页,从 源代码看ControlA.ascx这一部分,怎么在源代码中找不到吗?是不是存在其他的地方?
再次期待您的回复,感谢感谢
在第二个问题提到的ControlA.ascx的意思是,在ButtonB之前 ,已经点击过ButtonA,所以已经在网页中生成ControlA控件了
@myfriend253
1、Fiddler
2、当然不会……只有各种Input/TextArea/Options之类的会被提交(我刚才说Hidden Input说错了),ControlA里有再多普通HTML也不会被提交。
3、HTML肯定是在页面上,否则浏览器是无法显示出来的。
// 其实这些是HTML基础,建议有一定HTML基础后再学习ASP.NET会比较合适。:)
@myfriend253
纠正一点ControlA是服务器端的概念,客户端只有ControlA生成的HTML。:)
这个控件确实不错,不过使用中发现了问题:
ScriptManager sm = ScriptManager.GetCurrent(this);
sm.RegisterAsyncPostBackControl(this.ButtonA);
sm.RegisterAsyncPostBackControl(this.ButtonB);
在page_load里面把按钮注册为异步提交,这么做会造成用户控件里的javascript脚本不能执行,验证控件也都不能用,把上面那几句注释掉就没问题了,不过这样做会造成整个页面刷新,不知道老赵有没好的解决方案呢,期待您的答复!
@xunlong
RegisterAsyncPostBackControl是必须的。
控件里的JavaScript自然不能执行,因为它会替换innerHTML,您要使用ScirptManager注册的方式来执行脚本。
验证控件的话,您要使用官方提供的兼容UpdatePanel的控件。
搞啊搞?深夜了,没出来,应在WEBCONFIG还要配置吧??默认系统只认得SCRIPTMANAGER,快告诉我啊!
@彭良生
按照ASP.NET AJAX Enabled Site模板生成的web.config文件配一下吧。
protected void btnSave_Click1(object sender, EventArgs e)
{
....
ScriptManager.RegisterStartupScript(this.Page, this.Page.GetType(), "upsc", "alert('保存成功');__doPostBack('" + LinkButton1.ClientID + "','');", true);
}
protected void LinkButton1_Click(object sender, EventArgs e)
{
SwitchPartManager.GetCurrent(this.Page).SwitchTo("U1");
}
目的:在btnSave_Click1中执行保存代码后弹出一个提示,再重新加载另一个ascx。
以上代码运行结果:弹出了“保存成功”提示,但仍然在当前的ascx里,没有成功加载U1.ascx
跟踪代码发现进入了LinkButton1_Click事件,但未出现预想的结果。
如果直接点击LinkButton1,能够成功切换到U1.ascx。
还请博主指点一下。
使用window.setTimeout来让__doPostBack方法延迟执行。不过这里建议不要通过__doPostBack来做,可以获得按钮后执行它客户端的click方法。
可以获得按钮后执行它客户端的click方法。
-----------------------------------
没懂,还请帮助
@sorke
$get("id").click()
老赵你好.
我在我的程序里用了你开发的SwitchPartManager, 是很好用, 但也碰到个妖怪的问题, 我在2个控件里各加入了一个Grid, 每次切换控件的时候, Grid的Columns的Width老是跟着上个控件上的Grid. 感觉是控件上的JavaScript没有被更新. 你有什么办法把Client端的JavaScript也清除呢.
@Alan
Client端的JavaScript是没法清除的。
@Jeffrey Zhao
你说到IsPostBack中都为True的解决方法是:
可以自己修改代码,穿参数也好,辨认第一次加载一也好。
可以详细说说辨认第一次加载这方法怎样实现吗?
@samuelchoi
就是摆脱IsPostBack的判断,使用自定义的判断方式,例如cookie等。
@samuelchoi
其实,我这个SwitchPartManager只是展示了一种做法,要写的时候最好还是理解后自己写。
老赵,我不知道可不可以这样称呼你,看了你的视屏,读了你的博客,感觉你真
的是个牛人。
你的影响力就不多说了,反正大家都是有目共睹的...
我这里想请教一下,在控件里实现跳转控件,而不是在页面里进行A控件和B控件的跳转。那个linkbutton(也就是导航到B控件的按钮)是写在A控件里面,再把A控件拖到到页面
不知道可不可以实现???
xiaoliyx@gmail.com
@肖力
看情况,应该也是可以的,这里其实只是UpdatePanel的简单使用吧。
先谢了!
这麽快,就回复了。
我去试一下,到时候,碰到问题还要请教你
我怎样才能把那个SwitchPartManager放到我的VS工具里作永久的控件,要不要把.cs文件生成DLL形式的程序集,再把它放入全局程序集里,然后用vs来引用?
先谢了
有个地方不妥。每次加载新的用户控件前都先加载前一个用户控件。
private void Page_InitComplete(object sender, EventArgs e)
{
this.initialized = true;
string partType = this.Page.Request.Params[SwitchPartManager.HiddenElementName];
if (partType != null) //除了第一次,parType都不为空
{
this.SwitchTo(partType);
}
}
@Eve
这就是设计,否则以前的控件怎么响应事件?
“就是摆脱IsPostBack的判断,使用自定义的判断方式,例如cookie等。”
我试着做了,但没做成,可否说得详细些?。
我的做法这样:
在SwitchTo(string partType)里判断Hidden Input "__PartType__"里的用户控件名和形参传递进来的是否相等。如果相等,就表示是页面回发的,反之则是第一次加载。这样该用户控件的“IsPostBack”可以很好判断。可如果连续点击两次加载该用户控件,此时"IsPostBack"又一直为"true"了。郁闷中。
请赵大哥赐予正确的做法。
人人都叫你做老赵,那我也叫你老赵了
我有个问题,我加载的ascx控件里有个<asp:ListView>的控件,但我用你的SwitchTo()来加载那个ascx控件后,处理结果是把<asp:ListView>给过滤掉的样子,其它的内容会显示出来的,就是<asp:ListView>控件内容给没了,不知为什么?
@zsensi
检查一下你的代码逻辑,一步一步调试,估计是你在PostBack以后ListView的内容没有重新绑定。
老赵,您好!我是一个.Net新手,使用了您的这个组件,确实可以实现动态的加载用户控件。但是有个问题:
我用DevExpress做了一个测试用户控件,里面只有一个菜单控件,设置了File菜单及New和Open两个子菜单。使用您的组件后确实加载了这个用户控件,但是只显示了File菜单,原本鼠标移到File菜单时会显示子菜单的,现在子菜单出不来,IE提示脚本错误。看DOM模型好像也没有找到这个子菜单所在的DIV标签。不知道这是怎么回事?