ASP.NET Page 指令
一些重要的Page指令
虽然Page公开了很多属性,让我们可以在运行时调整它的状态与行为,但是,还有些重要的参数却是以“指令”方式提供的,需要在设计时就指定。
下面是我整理的一些我认为 比较重要并且经常需要使用 的指令:
@ Page 指令 | |
Async | 指示页面要不要以异步页的方式运行。默认值为 false。 注意:如果需要开发一个异步页,必须要设置这个指令属性,以便在编译页面时实现IHttpAsyncHandler接口。 |
AsyncTimeOut | 定义在处理异步任务时使用的超时时间间隔(以秒为单位)。默认值为 45 秒。 该值必须是整数。 此属性仅对Page.RegisterAsyncTask()方法有效。 |
AutoEventWireup | 指示页的事件是否自动绑定。如果启用了事件自动绑定,则为 true;否则为 false。默认值为 true。 所谓的事件自动绑定就是识别Page_Load这些类型的页面事件处理程序。 事件自动绑定发生在页面第一次请求时,在后续请求中会使用缓存的委托调用列表,因此对性能的轻微影响也仅发生在第一次请求时。 |
CodeBehind | 用于指定包含代码隐藏类的文件。 |
EnableEventValidation | 如果为true,将验证POST请求,尝试阻止恶意创建的请求 |
EnableSessionState | 定义页的会话状态要求。如果启用了会话状态,则为 true;如果可以读取会话状态但不能进行更改,则为 ReadOnly;否则为 false。默认值为 true。 建议不要保留默认值: 1. 如果只是部分页面需要使用Session,那么不需要使用Session的页面请显式设置为关闭状态, 2. 对于不需要修改Session的页面请设置为ReadOnly。 页面编译时就是根据这个指令来决定生成对IRequiresSessionState,IReadOnlySessionState接口的实现关系。 |
EnableViewState | 指示是否在页请求之间保持视图状态。如果要保持视图状态,则为 true;否则为 false。默认值为 true。 强烈建议不要使用视图状态,因为它解决的问题比引用的问题更多。 |
EnableViewStateMac | 确定ASP.NET是否会使用消息验证代码(MAC)来验证视图状态数据的完整性。 |
ErrorPage | 指定在处理Web窗体发生错误时应向用户显示的页面。 |
MasterPageFile | 设置内容页的母版页或嵌套母版页的路径。支持相对路径和绝对路径。 可以在页面的PreInit事件中动态设置同名的属性实现动态切换母版页的功能。 |
Trace | 指示是否启用跟踪。如果启用了跟踪,则为 true;否则为 false。默认值为 false。 启用跟踪对页面调试非常有用,我们可以调用Page.Trace对象的方法输出一些有价值的诊断信息。 Page.Trace.Write()采用默认字体输出,Page.Trace.Warn()采用红色字体输出。 如果你总是记不住页面生命周期,启用跟踪后,一切就摆在你眼前。 |
ValidateRequest | 指示是否应发生请求验证。如果为true,请求验证将根据具有潜在危险的值的硬编码列表检查所有输入数据。默认值为 true。 如果希望用户输入HTML代码,请设置为false 。 注意:在ASP.NET 2.0中不验证ashx请求,但是ASP.NET 4.0默认会验证, 如果希望在4.0中兼容2.0的行为,请在web.config中配置<httpRuntime requestValidationMode="2.0"/> |
Inherits | 指定与Web窗体关联的代码隐藏类。 |
ViewStateMode | 用于启用或禁用视图状态功能。 |
ViewStateEncryptionMode | 用于启用或禁用视图状态加密功能。 |
其它指令 | |
@ Import | 导入允许您指定要在代码中引用的命名空间。 |
@ OutputCache | 设置页面或者用户控件的输出缓存。 |
@ Register | 声明控件的标记前缀和控件程序集的位置。如果要向页面添加用户控件或自定义 ASP.NET 控件,可以使用此指令。 |
web.config的全局设置
前面我介绍了一些常用的Page指令,考虑到方便性,ASP.NET还允许我们在web.config中为一些常用的指令配置默认值。 下面我就一些常用的场景来说明这些全局配置的方便性。
1. 通常,我在创建一个网站项目时,肯定会决定不使用ViewState和Session的。 那么如果为每个页面设置EnableViewState,EnableSessionState指令属性,那就显得太麻烦了,而且还容易遗漏。 此时,我们可以直接在web.config中为这些参数指定一个全局的默认值:
<pages enableViewState="false" enableSessionState="false"></pages>
补充说明一下:全局禁用Session的彻底方法是把Session对应的HttpModule从httpModules列表中移除。
web.config允许我们设置Page默认参数的具体配置节如下:
2. 为了代码重用,设计用户控件也是很常用的方法。
我们可以使用 @ Register指令 在页面注册需要使用的UserControl或者WebControl。 然而,有些控件比较通用,许多页面都会使用它,那么就不要再使用 @ Register指令了,可以在web.config中统一注册。例如:
<pages>
<controls>
<add tagPrefix="fish" tagName="MainMenu" src="~/Controls/MainMenu.ascx" />
<add tagPrefix="fish" tagName="PageHeader" src="~/Controls/PageHeader.ascx" />
</controls>
</pages>
有了这个定义后,我就可以在任何页面中直接使用:
<fish:PageHeader runat="server" ID="PageHeader1" />
3. 对于喜欢使用页面内联代码的人来说,可能经常需要使用自己定义的类型。 如果这些类型定义在某个命名空间中,那么就需要在内联代码中采用完整命名空间的写法。 虽然这样做没有什么问题,但就是麻烦,于是,我们可以在页面中使用 @ Import指令 来导入我们需要使用的命名空间, 但是这个指令每次只能导入一个命名空间,而且每个页面还得重复导入,显然不够方便。
为了方便使用一些常用的命名空间,我们可以在web.config中统一指定,例如:
<pages>
<namespaces>
<add namespace="MyMVC" />
<add namespace="WebSiteCommonLib" />
<add namespace="WebSiteModel" />
</namespaces>
</pages>
这样设置后,所有页面就可以直接使用这些命名空间下的类型了。
不知道有些人想过:为什么在页面中使用某些微软提供的类型就不需要导入命名空间?
答案是:其实ASP.NET已经将一些微软认为常用的命名空间在web.config中配置好了:
4. 现在,有越来越多的人为了方便而使用扩展方法。使用扩展方法的好处是:可以让我们不去关心这些扩展方法定义在那个类中, 只要在支持扩展方法的对象上调用就可以了,就像下面的代码这样:
当前用户已登录,登录名:<%= Context.User.Identity.Name.HtmlEncode() %>
然而,在页面中使用扩展方法时,也必须先导入扩展方法的定义类的命名空间。
因此,为了方便,我们可以在web.config中为我们定义的扩展方法导入相应的命名空间:
<pages>
<namespaces>
<add namespace="FishDemoCodeLib" />
</namespaces>
</pages>
换个方式使用 Page
在传统的WEB开发模式下,我们通常会设计一些页面(Page)响应来自用户浏览器的请求, 在这种模式下,Page会将最后生成的整页HTML代码直接发送给用户浏览器。 然而,在某些时候,我们只需要生成一个HTML片段:
1. 在AJAX请求中,客户端为了局部刷新,只要求服务端返回一个HTML片段。
2. BigPipe方式下,为了能分块输出,每次也只需要输出一个HTML片段。
如果只是为了得到一段简单的HTML代码,可能有些人会选择采用代码来拼接,但是如果那段HTML还有些复杂呢? 显然拼接方法肯定是不行的。
对于第一个问题,可能有人说:我可以创建一个页面,只放部分代码到页面上。
的确,这种方法可以勉强解决第一个问题,但是,很有可能那部分代码在整页输出时也会用到,怎么办?
做成UserControl,然后放在一个单独的页面中!
其实这种做法很无奈,因为那个容器页面的意义不大(仅仅是个容器),最后搞得项目中一大堆页面文件!
事实上,这种方法仅适用于使用简单服务端控件的场合,如果想使用一些高级的服务端控件,它根本就不行。
为了能实现前面说到的二个需求,我们就不能再按照传统的方式来使用Page了。 因为我们希望能得到(返回)一段HTML。
有二种方法可以让我们继续使用页面模板代码的方式生成HTML代码:
1. Server.Execute()方法。
2. Page.RenderControl()方法。
下面这段代码来源于 MyMVC框架, 这个方法可以根据指定的用户控件以及控件显示所需的数据得到控件的输出结果(一段HTML代码)。
/// <summary>
/// 用指定的用户控件以及视图数据呈现结果,最后返回生成的HTML代码。
/// 用户控件应从MyUserControlView<T>继承
/// </summary>
/// <param name="ucVirtualPath">用户控件的虚拟路径</param>
/// <param name="model">视图数据</param>
/// <returns>生成的HTML代码</returns>
public static string Render(string ucVirtualPath, object model)
{
if( string.IsNullOrEmpty(ucVirtualPath) )
throw new ArgumentNullException("ucVirtualPath"); Page page = new Page();
Control ctl = page.LoadControl(ucVirtualPath);
if( ctl == null )
throw new InvalidOperationException(
string.Format("指定的用户控件 {0} 没有找到。", ucVirtualPath)); if( model != null ) {
MyBaseUserControl myctl = ctl as MyBaseUserControl;
if( myctl != null )
myctl.SetModel(model);
} // 将用户控件放在Page容器中。
page.Controls.Add(ctl); StringWriter output = new StringWriter();
HtmlTextWriter write = new HtmlTextWriter(output, string.Empty);
page.RenderControl(write); // 用下面的方法也可以的。
//HttpContext.Current.Server.Execute(page, output, false); return output.ToString();
}
整段代码分为以下几个步骤(我已用空行分隔开了):
1. 检查参数。
2. 创建页面容器并加载用户控件。
3. 设置页面(视图)所需的显示数据。
4. 将用户控件添加到Page的Controls集合中。
5. 调用RenderControl或者Execute让Page输出HTML代码。
6. 返回结果。
这段代码很简单,唯独值得介绍的就是第5步,调用它们就可以得到控件输出的HTML代码。
RenderControl或者Execute的差别在于:
RenderControl不支持服务器控件,原因在于它利用了页面的一种独特编译方式,我已在以前的博客中分析过了。
Execute可以支持服务器控件,因为它会执行一次完整的页面生命周期。
注意:上面这段代码就算使用Execute,也只能支持部分简单的服务器控件,因为一些复杂的服务器控件需要在HtmlForm中才能运行。 因此,如果需要支持所有的服务器控件,那么还必须创建HtmlForm对象,并调整包含关系,还有就是还需要去掉产生的多余HTML代码。
如果你需要生成整个页面生成的HTML代码,可以参考 MyMVC框架, 那里有实现这个功能的完整代码。
重新认识Eval()方法
我想很多人都写过类似下面的代码:
<asp:Repeater ID="repeater1" runat="server">
<HeaderTemplate><ul></HeaderTemplate>
<FooterTemplate></ul></FooterTemplate>
<ItemTemplate>
<li><%# Eval("OrderID")%>,<%# Eval("OrderDate")%>,<%# Eval("SumMoney")%>
</li>
</ItemTemplate>
</asp:Repeater>
在这里我要说的是 Eval() 的调用,还不是Repeater控件。
Eval()不仅仅可以读取一个绑定数据项的属性,还可以去读取DataTable中的一个数据列。而且还能完成更复杂的绑定计算:
<li><%# Eval("OrderID")%>,<%# Eval("OrderDate")%>,<%# Eval("SumMoney")%>
,订单中的第一个商品:<%# Eval("Detail[0].ProductName") %>
</li>
当然了,对于页面上的数据绑定,用Eval()的确不是性能最好的方法,建议还是使用强类型转换的方法。
有时候,尤其是在写反射应用时,时常会有从字符串解析并实现求值计算的需求。 那么,前面这个示例中,Eval()的功能是不是值得挖掘呢? 我认为答案是肯定的。
通过分析ASP.NET的绑定代码,我发现Eval在内部会调用DataBinder.Eval这个静态方法,这个方法的签名如下:
// 在运行时计算数据绑定表达式。
//
// 参数:
// container:
// 表达式根据其进行计算的对象引用。此标识符必须是以页的指定语言表示的有效对象标识符。
//
// expression:
// 从 container 到要放置在绑定控件属性中的公共属性值的导航路径。
// 此路径必须是以点分隔的属性或字段名称字符串,如 C# 中的 "Tables[0].DefaultView.[0].Price"
// 或 Visual Basic 中的 "Tables(0).DefaultView.(0).Price"。
//
// 返回结果:
// System.Object,它是数据绑定表达式的计算结果。
public static object Eval(object container, string expression);
通过这个签名的注释,我们可以很容易地看出它的用法。
下面我来举个例子把它应用在非绑定的应用中:
我有一个类:
public class TestEvalClass
{
public List<Order> Orders { get; set; } // Order以及OrderDetail的定义就省略了,我想大家能想像得出来。
}
那么下面的代码是可以运行的:
static void Main()
{
TestEvalClass testObject = GetTestEvalClassInstance(); string productName = (string)System.Web.UI.DataBinder.Eval(testObject, "Orders[0].Detail[0].ProductName");
Console.WriteLine(productName);
}
对于这个示例,我想输出什么结果,并不重要。
我只想说:如果让你去解析那个表达式,会不会比较麻烦,现在有现成的,用起来是不是很方便?
不用基类也能扩展
在一个ASP.NET网站中,如果想为所有的页面添加某个功能,我们通常会想到使用基类的方式去实现。 这的确是一种很有效的方法,但不并唯一的方法,还有一种方法也能容易实现这个需求,那就是使用PageAdapter的方式。
在我写博客的过程中,我写了很多示例页面,页面中包含一些提交按钮是少不了的事情, 然而,为了能让示例代码看起来比较原始(简单),我尽量不使用服务器控件,因此就要面临提交按钮的事件处理问题。 在博客【细说 ASP.NET Cache 及其高级用法】的示例代码中, 我开始采用PageAdapter这种方法,它可以让代码很简单,而且以后也方便以后重用(只需要复制几个文件即可)。
或许有些人认为:扩展所有页面的功能,还是使用基类比较好。
对于这个观点,我完全不反对。
但是,PageAdapter的好处在于它的可插拔性(类似HttpModule的优点)。
不过,我当时设计这种扩展方式只是想再换个方法尝试一下而已。
其实微软设计PageAdapter的本意是为了处理各种浏览器的兼容问题,但是我把这个功能用到扩展Page的功能上去了。 HttpModule可以进入到ASP.NET请求管线的任何阶段,但它就是进入不了页面的生命周期中, 有了这个方法,我们就可以采用HttpModule这种【外挂】式的方法进入到页面生命周期中, 我认为是很有意义的。
方法多了,我想不是件坏事。每种方法都有适合它们的应用场合,了解更多的方法,以后就能做出更优秀的设计。
这次想到这个话题是因为前面的博客【细说ASP.NET Forms 身份认证】中的示例代码。 有些人看到那些代码,发现代码的运行方式比较特别,所以,今天我就打算着重介绍这种方法。
我们再来回顾一下以前博客中的示例代码,首先从页面代码开始:
<fieldset><legend>普通登录</legend><form action="<%= Request.RawUrl %>" method="post">
登录名:<input type="text" name="loginName" style="width: 200px" value="Fish" />
<input type="submit" name="NormalLogin" value="登录" />
</form></fieldset> <fieldset><legend>包含【用户信息】的自定义登录</legend> <form action="<%= Request.RawUrl %>" method="post">
<table border="0">
<tr><td>登录名:</td>
<td><input type="text" name="loginName" style="width: 200px" value="Fish" /></td></tr>
<tr><td>UserId:</td>
<td><input type="text" name="UserId" style="width: 200px" value="78" /></td></tr>
<tr><td>GroupId:</td>
<td><input type="text" name="GroupId" style="width: 200px" />
1表示管理员用户
</td></tr>
<tr><td>用户全名:</td>
<td><input type="text" name="UserName" style="width: 200px" value="Fish Li" /></td></tr>
</table>
<input type="submit" name="CustomizeLogin" value="登录" />
</form></fieldset>
在这段页面代码中,我定义了二个表单,它们包含各自的提交按钮(其实这也只是部分代码)。
再来看后台处理代码是如何响应提交请求的:
public partial class _Default : System.Web.UI.Page
{
[SubmitMethod(AutoRedirect = true)]
public void NormalLogin()
{
// 省略登录处理代码。
// 如果需要知道这段代码可以浏览下面的网址:
// http://www.cnblogs.com/fish-li/archive/2012/04/15/2450571.html
} [SubmitMethod(AutoRedirect = true)]
public void CustomizeLogin()
{
// 省略登录处理代码。
// 如果需要知道这段代码可以浏览下面的网址:
// http://www.cnblogs.com/fish-li/archive/2012/04/15/2450571.html
}
注意观察,这二个C#方法的名称与页面二个submit按钮的name属性相同,因此可以猜测到这二个C#方法可以处理那二个submit按钮的提交请求。 那么这二段代码是如何运行起来的呢?有些人或许看到了[SubmitMethod]的使用,认为与它们有关。 其实这种说法并不正确,我也可以完全不使用它们。请记住:Attribute永远只是一个标记,它不可能让代码自动运行起来。
前面的代码能运行起来,与App_Browsers目录下的Page.browser文件有关,此文件的代码如下:
<browsers>
<browser refID="Default">
<controlAdapters>
<adapter controlType="System.Web.UI.Page"
adapterType="FishDemoCodeLib.MyPageAdapter, FishDemoCodeLib" />
</controlAdapters>
</browser>
</browsers>
这里定义了一个MyPageAdapter,它用于Page控件的请求过程。 refID="Default" 表示是对ASP.NET定义的Default.browser文件补充一些配置,它将能匹配来自所有浏览器的请求。
我再来看一下MyPageAdapter的代码:
[AttributeUsage(AttributeTargets.Method, AllowMultiple = false)]
public class SubmitMethodAttribute : Attribute
{
public bool AutoRedirect { get; set; }
} internal sealed class MethodInvokeInfo
{
public MethodInfo MethodInfo;
public SubmitMethodAttribute MethodAttribute;
} public class MyPageAdapter : System.Web.UI.Adapters.PageAdapter
{
private static readonly Hashtable s_table = Hashtable.Synchronized(new Hashtable()); private static MethodInvokeInfo[] GetMethodInfo(Type type)
{
MethodInvokeInfo[] array = s_table[type.AssemblyQualifiedName] as MethodInvokeInfo[];
if( array == null ) {
array = (from m in type.GetMethods(BindingFlags.Instance | BindingFlags.Public)
let a = m.GetCustomAttributes(
typeof(SubmitMethodAttribute), false) as SubmitMethodAttribute[]
where a.Length > 0
select new MethodInvokeInfo {
MethodInfo = m, MethodAttribute = a[0] }).ToArray(); s_table[type.AssemblyQualifiedName] = array;
}
return array;
} protected override void OnLoad(EventArgs e)
{
base.OnLoad(e); if( Page.Request.Form.AllKeys.Length == 0 )
return; // 没有提交表单 MethodInvokeInfo[] array = GetMethodInfo(Page.GetType().BaseType);
if( array.Length == 0 )
return; foreach( MethodInvokeInfo m in array ) {
if( string.IsNullOrEmpty(Page.Request.Form[m.MethodInfo.Name]) == false ) {
m.MethodInfo.Invoke(Page, null); if( m.MethodAttribute.AutoRedirect
&& Page.Response.IsRequestBeingRedirected == false )
Page.Response.Redirect(Page.Request.RawUrl); return;
}
}
}
}
这段代码并不长,核心代码更是比较少。
代码中,最重要的一块是MyPageAdapter的实现,它继承了System.Web.UI.Adapters.PageAdapter, 并重写了OnLoad方法(相当是在重写Page的OnLoad方法),也正是由于这个重写, 代码才有机会在页面的生命周期中被执行,这一点是HttpModule做不到的。
在OnLoad方法中做了以下事情:
1. 检查是不是发生了表单提交的操作。
2. 获取当前页面类型的所有[SubmitMethod]修饰过的方法。
3. 检查提交的表单数据中,是否存在与name对应的C#方法名。
4. 如果找到一个匹配的方法名,则调用。
5. 如果在[SubmitMethod]中设置了AutoRedirect=true,则引发重定向。
注意:如果不调用base.OnLoad(e); 那么页面的Load事件根本不会发生。
也就是说:PageAdapter.OnLoad的调用时间要早于Page.Onload方法。
由于这段代码仅供我写示例代码时使用,因此并没有检查要调用的方法的参数是否满足条件,也没有优化刻意去优化它的性能。 在我的设计中,被调用的方法应该是无参的,因此是容易判断的,而且可以使用一个固定签名的委托去优化它的,这些细节留着以后再去完善它吧!
ASP.NET Page 指令的更多相关文章
- ASP.NET @Page指令属性(vs2010)
最近看一篇好文章,摘抄下来. 原文出处:http://www.cnblogs.com/zhaozhan/archive/2010/05/01/1725819.html @Page指令位于每个ASP.N ...
- Asp.net Page指令
Page指令为编译器编译页面时使用的指令 指令的格式为 <%@ [Directive] [Attribute=value]%> <%@ Page Language="C#& ...
- 【转】asp.net中@page指令的属性Inherits、Src、CodeBehind区别
Inherits.Src.CodeBehind 在 ASP.NET 中使用代码隐藏方法来设计Web 窗体,可使页代码能够更清晰地从 HTML 内容中分离到完全单独的文件中. 通常一个 @page 指令 ...
- IIS部署ASP.NET网站后提示只有在配置文件或 Page 指令中将 enableSessionState 设置为 true 时,才能使用会话状态...
今天,在IIS上部署网站后,出现了下面错误: 只有在配置文件或 Page 指令中将 enableSessionState 设置为 true 时,才能使用会话状态.还请确保在应用程序配置的 <sy ...
- 合法提交Html标签 Page指令
3.2.1 提交合法的HTML标签(1) 有时候我们需要让我们提交的文本展示出来的效果非常美观,通常会对服务器提交一些HTML标签来控制文本或内容的样式. HTML标签可能包含了很多不安全的因素,所以 ...
- C# @Page指令中的AutoEventWireup,CodeBehind,Inherits
AutoEventWireup 如果 Page 指令的 AutoEventWireup 属性被设置为 true(或者如果缺少此属性,因为它默认为 true) ,该页框架将自动调用页事件,即 Page_ ...
- 在做了 BasePage 时: 只有在配置文件或 Page 指令中将 enableSessionState 设置为 true 时,才能使用会话状态。还请确保在应用程序配置的 / / 节中包括
摘自: http://lichengguizy.blog.163.com/blog/static/11771858620122342749552/ 只有在配置文件或 Page 指令中将 enableS ...
- (2)ASP.NET 页面指令
页面指令 一共有12个指令,这些指令用来控制APS.NET页面的行为. Assembly,Control,Implements,Import,Master,MasterTpye,OutputCache ...
- aspx页面@Page指令解析
@Page指令位于每个ASP.NET页面的顶部,告诉ASP.NET这个具体页面使用什么属性,以及该页面继承的用户控件.ASP.NET页面@Page指令属性有:AspCompat.Async.Async ...
随机推荐
- poj1039 Pipe【计算几何】
含[求直线交点].[判断直线与线段相交]模板 Pipe Time Limit: 1000MS Memory Limit: 10000K Total Submissions:11940 Ac ...
- python之sys.stdout、sys.stdin以及设置打印到日志文件等
转自:https://www.cnblogs.com/BigFishFly/p/6622784.html python之sys.stdout.sys.stdin 转自:http://www.cnblo ...
- 洛谷P4799 世界冰球锦标赛 CEOI2015 Day2 meet-in-the-middle
正解:折半搜索 解题报告: 先放个传送门QAQ 想先说下部分分?因为包含了搜索背包两个方面就觉得顺便复习下?QwQ 第一档部分分 爆搜 就最最普通的爆搜鸭,dfs(第几场,钱),然后每次可以看可以不看 ...
- 【Loadrunner】使用LR录制HTTPS协议的三种方法
使用LR录制HTTPS协议的三种方法 一.最简单的方法:浏览器配置打开浏览器,安装证书,配置完成后直接用http协议录制即可(配置完成的标识就是打开网页,不显示安全提示) 二.LR配置修改操作步骤如下 ...
- java-mybaits-00102-mybatis框架原理
1.mybatis是什么? mybatis是一个持久层的框架,是apache下的顶级项目.是一个不完全的ORM框架. mybatis托管到goolecode下,再后来托管到github下(https: ...
- node核心:异步流程控制
Node.js的异步是整个学习Node.js过程中重中之重. 1)异步流程控制学习重点 2)Api写法:Error-first Callback 和 EventEmitter 3)中流砥柱:Promi ...
- 11Qt样式表
Qt样式表 Qt样式表的思想很大程度上是来自原HTML的层叠式样式表(CSS),通过调用Qwdiget::setStyleSheet()或是Qapplication::setStyleSheet(), ...
- Linux Makefile
动态库: gcc getmaxlen.c –fPIC –shared –o libtest.so ldd -r libtest.so 静态库: ar crv libfirst.a testlib ...
- Java开发之JDK配置
windows下配置JDK环境变量: 1.安装JDK,安装过程中可以自定义安装目录等信息,例如我们选择安装目录为D:/java/jdk1.5.0_08: 2.安装完成后,右击“我的电 ...
- 微信 audio 获取 duration 为 NaN 的解决方法
先加load() myaudio.load(); myaudio.oncanplay = function () { alert(myaudio.duration); } load() 方法用于在更改 ...