In both cases, I was able to install the component by removing the following updates:
- Hotfix for Microsoft Windows (KB110806)
- Hotfix for Microsoft Windows (KB930264)
- Hotfix for Microsoft Windows (KB929300)
Anyone who has hooked into the resize event of the window object in Internet Explorer knows the pain - the event is fired twice for every single movement (once for horizontal and once for vertical). To make matters worse, if you drag the window size it doesn't get fried twice at the end, it gets fired numerous times throughout the resize. The result of this is that if you are hooked into the event and are resizing some other elements (and probably re-rendering them too), performance suffers and the page seems unresponsive.
Luckily there is a relatively straight-forward resolution to this:
Sys.Application.add_load(function(sender, args) {
$addHandler(window, 'resize', window_resize);
});
var resizeTimeoutId;
function window_resize(e) {
window.clearTimeout(resizeTimeoutId);
resizeTimeoutId = window.setTimeout('doResizeCode();', 10);
}
Essentially you queue up the resize handling code, and then if the window resize occurs again, you cancel it, and re-queue it. This keeps happening until the resize is complete, at which point, your doResizeCode() function is actually executed.
I love hearing about bugs and problems in components I have authored. Most people hate hearing about bugs (I assume because they like to think they are perfect), but I like it because it lets me know that people are actually using components I have written, which is always rewarding to know.
I had a report on the CodeProject in response to the article I authored, Persisting the Scroll Position of child DIV's using MS AJAX about a client-side InvalidOperationException during page initialization. The particular user was embedding my control in a user-control placing two of the controls on the page. Because my control is an "extender"-type control, one of its properties is the control ID of the other control. I then use a recursive FindControl method to locate the desired control:
protected virtual Control FindControlRecursive(Control root, string id)
{
if(root.ID == id)
{
return root;
}
foreach(Control c in root.Controls)
{
Control t = FindControlRecursive(c, id);
if(t != null)
{
return t;
}
}
return null;
}
The call to FindControlRecursive looks like:
protected internal Control Control
{
get
{
if(_control == null)
{
Page page = Page;
if(page == null)
throw new InvalidOperationException("Page cannot be null");
_control = (Control)FindControlRecursive(Page, ControlToPersist);
if(_control == null)
throw new InvalidOperationException("Could not located the specified control");
}
return _control;
}
}
Both DIV's in the user-control were named "div1" - can you spot the problem?
_control = (Control)FindControlRecursive(Page, ControlToPersist);
How about now? The problem was I was starting to scan from the Page and all of its children controls - since both DIV's were named "div1", it was always picking up the first control! To resolve this issue I changed Page to Parent:
_control = (Control)FindControlRecursive(Parent, ControlToPersist);
In this case, the Parent is the web-control (or page) hosting the extender control. Lesson Learned: Always start scanning controls from your parent control, not anything higher up in the chain.
I've been mucking around with (D)LINQ all week (this will probably be its own series of posts if I get around to it) and as a part of this, I've been reading a lot of blogs (no-thanks to Microsoft's lack of implementation demos). Something I've noticed in a few entries is storing a variable available in the Requests body in the pages ViewState.
public partial class _Default : System.Web.UI.Page
{
protected void Page_Load(object sender, EventArgs e)
{
if (!Page.IsPostBack)
{
if (!string.IsNullOrEmpty(Request["id"]))
Id = Convert.ToInt32(Request["Id"]);
}
}
protected void Button1_Click(object sender, EventArgs e)
{
Controls.Add(new LiteralControl(string.Format("Id is {0}", Id)));
}
protected int Id
{
get
{
object o = ViewState["Id"];
return (o == null ? -1 : (int)o);
}
set { ViewState["Id"] = value; }
}
}
I can think of a few reasons why this is unnecessary:
Since it's available on every request, don't bother yourself with the viewstate, save a few lines of code and just grab it from the Request during the pages Load event:
public partial class _Default : System.Web.UI.Page
{
int Id;
protected void Page_Load(object sender, EventArgs e)
{
if (!string.IsNullOrEmpty(Request["id"]))
Id = Convert.ToInt32(Request["Id"]);
if (!Page.IsPostBack)
{
// Do stuff...
}
}
protected void Button1_Click(object sender, EventArgs e)
{
Controls.Add(new LiteralControl(string.Format("Id is {0}", Id)));
}
}
As an added bonus, the HttpRequest is available much earlier in the page lifecycle (before the viewstate) permitting initial events access to this (you would of course need to parse it much earlier too).
I feel like every time I open my mouth about Apple something negative comes out. I try to be positive, I really do, but the reality is that experience after experience, I just don't have anything good today.
I came into my office today to find my MacBook revving its fan's at high speed displaying a folder with a question mark. Rebooting only yielded a "click-click-click," classic HD failure symptom. Luckily nothing is important on this machine. This raises the first myth I'd like to dispel: "Apple hardware is of higher quality than others." FALSE! The truth is, Apple, Dell, HP, Lenovo, etc., all use pretty much the same hardware. At the end of the day, they all have Intel processors, ATI/NVidia/Intel video cards, and Maxtor/Segate/WD hard drives, and they all share the same failure rates.
I was actually somewhat excited about this failure as it'd let me test Apple's support first hand.
My initial call was answered by a routing agent within a few rings, but upon routing, I waited on hold for 20 minutes. I rarely wait more then 5 minutes with Dell. Upon reaching a tech, I was asked for my first name, phone number, and serial number. I was advised that I have a 1yr hardware warranty and that my 90 phone support warrantee had expired. "It's definitely a hardware issue," I said. The Apple rep kindly replied "OK, well what I can do then is start a support incident [requiring your credit card], and if it turns out to be hardware, we'll refund your money." I then asked what the next step would be and it turned out that for MacBooks, Apple does not offer advanced replacements or on-site technicians, rather you have to take it to an authorized depot. This regardless of warranty type.
My experience at the depot (MIAD) wasn't much better - A tech came to take my computer, but upon trying to enter in the information into his system, he was unable to do so because of a system problem. I waited for 20 minutes only to be told that "we'll enter it in later and send you the info." Not wanted to leave my laptop without any type of receipt, I had the tech write down all his info on a business card. I'm told I they will have a part within 24-72 hours, and fixed within 48 hours after that, depending on their volume (ie: it could be longer).
So all in all, I'm not overly impressed. Just for the sake of it, let's take a look at a "standard" warranty on a MacBook Pro versus a Dell Latitude:
Standard Features | Dell Latitude | Apple MacBook Pro |
Phone Support | Lifetime | 90 days |
Hardware Warranty | 3yrs | 1yr |
Hardware Replacement | Next Business Day advanced swap, 4hr and 2yr available | 24-72 hours for parts + 24-72 hours for replacement, return to depot, no advanced swap available |
Onsite Technician | For parts deemed not "customer replaceable units" (CRU) | Not offered |
If you've ever reviewed the JavaScript source code for some MS AJAX-based components like the AJAX Control Toolkit, you might have noticed the use of the Sys.UI.DomEvent.keyCode property in a number of places to handle keyboard input in controls. I had recalled that there was also a charCode property on the same class, so interested in what the difference was, I headed over to http://asp.net/AJAX/Documentation/Live/ClientReference/Sys.UI/DomEventClass/default.aspx to take a look at the official documentation, but to my surprise, there was no keyCode property listed.
My initial reaction was that this was either an internal field or something that had been deprecated in the RTM, yet there are references to it in the target property too. What I ultimately discovered is that this is a simple omission in the documentation.
Now back to why I was looking it up in the first place - what exactly is the difference between charCode and keyCode? Patrick Long has a blog entry at http://blogs.charteris.com/blogs/patl/archive/2007/07/04/keycodes-charcodes-and-asp-net-ajax.aspx that sums it all up nicely; charCode will return a value related to the character being pressed whereas keyCode is related to the key being pressed. For example, 7 on the number pad and 7 on the QWERTY pad will return different keyCode values but the same charCode values.
One of our helpdesk reps reported an odd problem trying to setup a local workstation with 2 widescreens - when running in DVI, he couldn't get the horizontal resolution any higher then 1280 even though the monitor is optimized for 1400x900.
We're all used to fiddling with video card drivers to resolve these types of issues, but this one was a bit different because it required a monitor driver (when was the last time you had to install one of those?). Slight side note, but generally speaking you should install the monitor driver because it usually ships with an color profile for the monitor which helps with replicating color consistently.
Before I attempted to install the driver for our Acer 1916W's, I figured I'd take a look at the drivers INF file to see if there was anything of interest to help resolve this issue, and sure enough, there was.
[DEL_CURRENT_REG]
HKR,MODES
HKR,,MaxResolution
[1280]
HKR,,MaxResolution,,"1440,900"
[AL1916W.AddReg]
HKR,"MODES\1440,900",Mode1,,"30.0-82.0,56.0-76.0,+,+"
The interesting line of course is the one proceeding [AL1916W.AddReg]. HKR is an identifier for "relative root", which in the case of the Acer driver is HKLM\System\CurrentControlSet\Control\Class\{4D36E96E-E325-11CE-BFC1-08002BE10318}\XXXX\MODES (the GUID is found at the ClassGuid property of the INF file). You can see that the INF is adding in the supported resolution to the Modes key, allowing us to choose the correct resolution now!
Finally, a Microsoft Blog publishing tool that works!
I have been trying to get Microsoft Word 2007 to talk with Blogger for some time now to no avail and to further frustrate matters, the beta versions of Windows Live Writer would not install on x64, but that has all changed now.
Microsoft has just released Windows Live Writer, and keeping up with everything else coming out of the Live BU lately, it seems just great!
If you're interested in trying Windows Live Writer, along with the rest of the Live software packages (Messenger, Photo Gallery, etc), you can grab them at http://www.windowslive.com/.
While I could probably write entire articles on each of these, I'm going to concentrate on the fill factors.
First, it's important to know that SQL Server itself does not have any timeouts; rather timeouts are implemented by the data provider (the .NET SQL providers) or the client (query analyzer, etc).
So what exactly is the fill factor? The fill factor is a parameter you set on indexes (both clustered and non-clustered); it controls the percentge of free space to leave on each page to leave room for future growth. Lower fill factors leads to less page splits. So why is this important? Well, page splits are expensive - they take a lot of CPU and disk resources to complete. So if you have a busy DB (lots of inserts into a table), and a high fill factor, then SQL server will have to constantly split pages to accomodate the growth, which might take longer then your timeout, resulting in a timeout error!
According to MSDN documentation (http://msdn2.microsoft.com/en-us/library/aa933139(SQL.80).aspx):
"Fill factor value is a percentage from 0 to 100 that specifies how much to fill the data pages after the index is created. A value of 100 means the pages will be full and will take the least amount of storage space. This setting should be used only when there will be no changes to the data, for example, on a read-only table. A lower value leaves more empty space on the data pages, which reduces the need to split data pages as indexes grow but requires more storage space. This setting is more appropriate when there will be changes to the data in the table."
In my particular case, I was able to resolve the timeout issue by changing the fill factor from 90% to 40%. Why 40% you might ask? The customer did not have overly large amounts of storage and decreasing the fill factor increases storage requirements - I could have set this to 0 but I didn't want to have an out-of-space condition to deal with, so 40% seemed reasonable to me.
Setting the fill factor via script is relatively easy and involves dropping the constraint and re-adding it with the new fill factor.
ALTER TABLE dbo.MyTableName
DROP CONSTRAINT PK_MyTableName
GO
ALTER TABLE dbo.MyTableName ADD CONSTRAINT
PK_MyTableName PRIMARY KEY CLUSTERED (
MyTableNameID
) WITH FILLFACTOR = 40 ON [PRIMARY]
Just something to watch out for when designing your DB's!
Mbccs.Psp.Web.Manager.Hashtable = function() {
this._hash = new Array();
this._keys = new Array();
this._getEnum = new Array();
this._count = 0;
}
Mbccs.Psp.Web.Manager.Hashtable.prototype = {
getAt : function(index) {
return this._hash[this._getEnum[index]];
},
get : function (key) {
return this._hash[key];
},
remove : function (key) {
for (var i = this._keys.length - 1; i >= 0; i--) {
if (this._keys[i] == key) {
this._keys.splice(i, 1);
this._getEnum.splice(i, 1);
this._hash[key] = null;
this._count = this._keys.length;
}
}
},
put : function (key, value) {
if (value == null)
return null;
if (this._hash[key] == null) {
this._keys[this._keys.length] = key;
this._count = this._keys.length;
this._getEnum[this._count - 1] = key;
}
this._hash[key] = value;
}
}
Mbccs.Psp.Web.Manager.Hashtable.registerClass('Mbccs.Psp.Web.Manager.Hashtable');
if(typeof(Sys) !== 'undefined')
Sys.Application.notifyScriptLoaded();
While the ScriptManager
and UpdatePanel
found in Microsoft AJAX do a good job of persisting your pages scroll position during partial post back operations, you might be surprised to find out the same is not for scrollable child DIV's contained within an UpdatePanel
.
The PersistentScrollPosition
control presented in this article seeks to remedy this issue using a client-side behavior and ASP.NET server control implemented using Microsoft AJAX.
While it is certainly not my intention to review the internals of the UpdatePanel
and PageRequestManager
or implementing any of the client-side components (Sys.Component
, Sys.UI,Behavior
, Sys.UI.Control
), a quick understanding can go a long way into understanding and resolving this particular problem. There are two key items to keep in mind for this control:
The HTML output of the UpdatePanel
is completely replaced during a partial post back (assuming it was triggered) through the innerHTML
property, which is why the scroll position problem exists in the first place.
For those who just want the solution, using the code is straight forward. The control has one property you need to set named ControlToPersist
. This is a string
which takes the ID of the server-side container DIV (it must have runat="server"
).
<asp:UpdatePanel runat="server" ID="UpdatePanel" UpdateMode="always">
<ContentTemplate>
<asp:Button runat="server" ID="btnPostBack" Text="Post Back" OnClick="btnPostBack_Click" />
<br />
<div style="width:590px;height:400px;overflow-y:scroll;overflow-x:hidden;" runat="server" id="persistMe">
<p>
Lorem ipsum dolor sit amet, consectetuer adipiscing elit...</p>
</div>
<mbc:PersistentScrollPosition runat="server" ID="psf1" ControlToPersist="persistMe" />
</ContentTemplate>
</asp:UpdatePanel>
The control consists of two parts, both of which for the most part are very "cookie cutter". On the server side, we inhert from Control
and implement IScriptControl
and INamingContainer
and create a HiddenField
during initialization to store our scroll position in between partial post backs.
public class PersistentScrollPosition : Control, IScriptControl, INamingContainer
{
protected override void OnInit(EventArgs e)
{
base.OnInit(e);
// Create hidden control for storage
storage = new HiddenField();
storage.ID = "storage";
Controls.Add(storage);
}}
When creating the script descriptors for the client-side initiation, we pass through the scrollable DIV's ClientID
as the controls ElementID
and we pass in a reference to the HiddenField's DOM element using the AddElementProprety
method of the ScriptComponentDescriptor
class.
public IEnumerable<scriptdescriptor /> GetScriptDescriptors()
{
ScriptComponentDescriptor scd =
new ScriptBehaviorDescriptor("Mbccs.WebControls.PersistentScrollPosition", Control.ClientID);
scd.AddElementProperty("storage", storage.ClientID);
yield return scd;
}
On the client-side, the control inherits from the Sys.UI.Behavior
base class. Upon control intialization, it hooks into two events:
The scroll
DOM event of the DIV, and the EndRequest
event of the Sys.WebForms.PageRequestManager
class. The EndRequest
event is when the scroll state is restored, but I'll get to that shortly.
initialize : function() {
Mbccs.WebControls.PersistentScrollPosition.callBaseMethod(this, 'initialize');
this._scrollDelegate = Function.createDelegate(this, this._onScroll);
this._endRequestDelegate = Function.createDelegate(this, this._onEndRequest);
var prm = Sys.WebForms.PageRequestManager.getInstance();
prm.add_endRequest(this._endRequestDelegate);
$addHandler(this.get_element(), 'scroll', this._scrollDelegate);
}
When the DIV is scrolled, the x,y scroll position is serialized and stored in the HiddenField
server-side control we created earlier.
_onScroll : function(e) {
this._storage.value =
Sys.Serialization.JavaScriptSerializer.serialize(this._getScrollPosition());
},
_getScrollPosition : function() {
var el = this.get_element();
if (el) {
return {
x: el.scrollLeft 0,
y: el.scrollTop 0
};
}
}
To prevent null's from floating around, the x,y coordinates are coerced into 0's if either the scrollLeft
or scrollTop
properties are null.
The EndRequest
event is fired when "an asyncronous postback is finished and control has been returned to the browser," (http://asp.net/AJAX/Documentation/Live/ClientReference/Sys.WebForms/PageRequestManagerClass/default.aspx) so it's a perfect time to restore our scroll state.
_onEndRequest : function(sender, args) {
var o = null;
if(this._storage.value !== '')
o = Sys.Serialization.JavaScriptSerializer.deserialize(this._storage.value);
if (o) {
var el = this.get_element();
el.scrollLeft = o.x;
el.scrollTop = o.y;
this._storage.value = '';
}
}
The dispose
method isn't usually a place of any particular interest, but its worthy of noting that it you need to unhook the DIV's scroll
event prior to calling dispose
on the base class.
dispose : function() {
$removeHandler(this.get_element(), 'scroll', this._scrollDelegate);
Mbccs.WebControls.PersistentScrollPosition.callBaseMethod(this, 'dispose');
var prm = Sys.WebForms.PageRequestManager.getInstance();
prm.remove_endRequest(this._endRequestDelegate);
delete this._endRequestDelegate;
delete this._scrollDelegate;
}
Only one gotcha - in a batch file, you need to double up your % identifiers.
REM This is the start of the batch file
for /f "tokens=*" %%a in ('Program.exe "argument1"') do set __VARIABLENAME__=%%a
echo Result is %__VARIABLENAME__%
REM This is the end of the batch file
I can always count on my clients for some last minute urgent changes - I’ve come to expect it - in fact, I even plan for it (similarly to how I tell all my late friends that things start an hour earlier than they actually do.) I had a change come up on Thursday afternoon that needed to be completed for Monday morning if possible. It involved a color scheme update, new menu items and a few additional design elements. My first reaction was “great, this is going to take a few days…” but after opening up the project to take a look at what actually needed to be changed I was pleasantly surprised – it didn’t look like I needed to make any markup changes (with the exception of changing a few words – Fundraising to Fundraise, that kind of stuff).
Four hours later (most of it spent in Photoshop slicing images and testing in various browsers), the update was complete. No HTML changes, just a bunch of new images and about 20 lines of CSS to change, amazing!
One thing that really saved me was having an extra wrappre-DIV around the main content.
This allowed me to easily add the circles that appear in the bottom right corner.
Without the wrapper-DIV I likely would have to either added it to the markup or gotten really creative with the CSS on the mainbody DIV. Not to say that it wouldn’t be possible to do, just that having it there (it wasn’t really necessary for the original design) made my life that much easier.
The final results:
Before:
After:
To top everything off, the homepage comes down in about 3.7kb of HTML markup which means it loads wicked-fast even on my Blackberry. And of course it gracefully renders to down-level clients without CSS support.
NameValueCollection answers = new NameValueCollection();
answers.Add("Date", DateTime.Now.ToString());
foreach(Control c in panelName.Controls)
{
switch(c.GetType().Name)
{
case "TextBox":
answers.Add(c.ID, ((TextBox)c).Text.Trim());
break;
case "DropDownList":
answers.Add(c.ID, ((DropDownList)c).SelectedValue);
break;
case "RadioButton":
answers.Add(c.ID, ((RadioButton)c).Checked.ToString());
break;
case "RadioButtonList":
answers.Add(c.ID, ((RadioButtonList)c).SelectedValue);
break;
}
}
FormMailer.SendResponses(answers);
I've been an advocate of web standards for some time now and I'm frequently surprised to find new ways to simplify seemingly unrelated tasks. This article discusses how web standards can be used in ASP.NET control development to simplify some development tasks, build lighter weight and accessible controls and increase layout flexibility.
This article assumes that you have a basic understanding and knowledge of Web Standards. If you need a primer there are a ton of references out there – one of my favorite being A List Apart.
You've probably built a glorified sign in page before using nested tables and lots of spacer images to control layout (see Traditonal.aspx in source) and a handful of properties to control how it is rendered. It may have even looked something like this:
Your HTML code probably resembled this:
<table>
<tr>
<td>
<table>
<tr>
<td></td>
</tr>
<tr>
<td></td>
</tr>
</table>
</td>
<td></td>
<td>
<table>
<tr>
<td></td>
</tr>
<tr>
<td></td>
</tr>
</table>
</td>
</tr>
</table>
Like all good developers, you built a web control to handle its functionality so you could reuse it across sites. I've added a property named LayoutDirection
to control swapping the two boxes left and right positions.
When it comes to custom control rendering of such controls, you probably often find yourself with if-else blocks scattered throughout your rendering code.
private void BuildControlTree()
{
Controls.Add(new LiteralControl(@"
<table style=""width: 100%"" cellspacing=""0"" cellpadding=""0"">
<tr> <td style=""width: 45%"">"));
if(LayoutDirection == Direction.Standard)
BuildNewCustomers();
else
BuildExistingCustomers();
Controls.Add(new LiteralControl(@"</td> <td style=""width: 10%""> </td>
<td style=""width: 45%;text-align:right;"">"));
if(LayoutDirection == Direction.Standard)
BuildExistingCustomers();
else
BuildNewCustomers();
Controls.Add(new LiteralControl(@"</td></tr></table>"));
}
This has the following drawbacks:
I always like to start with a barebones structure and then apply styling afterwards. The goal is to keep the presentation near identical. All we need here is a DIV for each box and something to designate a title (I choose the H3 tag in this case).
Our final HTML markup should look something like (See Standards.cs and WebStandards_Bare.aspx):
<div>
<div id="signupdiv">
<h3>Sign Up</h3>
<p>New customers <a href="#">click here</a></p>
</div>
<div id="signindiv">
<h3>Sign In</h3>
<p>Existing Customers Sign In Below:</p>
Email: <input type="text" /><br />
Pass: <input type="text" /><br />
<input type="submit" value="Sign In" />
</div>
</div>
The rendered output looks like:
Now all we need to do is apply some styling to get it looking right. For simplicity I assigned each DIV an ID in the web control and referenced it in CSS by ID. In the real world you would probably want to expose properties to control the CSS class assigned to the DIV. Our final CSS looks like:
#signupdiv, #signindiv {
width: 45%;
border: 1px solid #000;
padding: 1px 1px 0 1px;
text-align:center;
height: 175px;
}
#signupdiv
{
float:left;
}
#signindiv
{
float:right;
}
#signupdiv h3, #signindiv h3
{
color: #fff;
background: #444;
text-align:center;
width: 100%;
height: 25px;
font-weight:normal;
}
Remember how in the traditional model I added a property named LayoutDirection to the web control so I could swap the left and right boxes? That's no longer needed thanks to the floating techniques available. Not only did we remove an entire property block from our code, we replaced it with two lines of external CSS code!
We've accomplished identical layouts using both techniques, so how do they stack up next to each other? For simple tasks like this I often like to look at two factors: number of lines of code, size of rendered output. I know that line counting isn't a practical measurement for most projects, but in this case I think it's a good gauge of simplicity.
| Traditional | Standards Based | % Change |
Web Control Lines of Code | 128 | 55 | -57% |
Rendered HTML Page Size | 1,899 bytes | 1,224 bytes | -35% |
Easily viewable on Blackberry handheld | No | Yes | N/A |
Easily viewable without style sheet? | No | Yes | N/A |
If you have been paying attention you might point out that I've excluded the size of the external CSS document (which, by the way, is 361 bytes). The reason for this is that because it will be cached by the browser, it only needs to be downloaded once.
I just had a somewhat interesting security problem in Vista that I thought I’d share.
The problem was in Internet Explorer when creating a bookmark, I’d receive a message “Unable to create [URL]. Unspecified error”. The problem only occurred in protected mode websites which led me to believe it might have been a permissions issue. Indeed, trying to propagate my local account with full access generated an Access is Denied error, so I replaced ownership on the folder successfully, however the problem was still occurring. A bit of research later and here is what I discovered in a nutshell
- Windows Vista introduces a new concept called “Integrity Level”. Basically, along with every ACL, there is also a “class” – this defines what types of applications can modify a particular file.
- By default, all files allow “Medium” or higher applications to modified them, however Internet Explorer running Protected Mode runs at a “Low” integrity level (basically, IE can only directly write to files with a Low integrity level associated with them, otherwise, the write has to go through the protected mode broker process which runs as a medium level application)
- The icacls command line utility (introduced in W2K2 SP2 and Vista) includes a new switch that allows you to set the integrity level
To resolve the issue, navigate to c:\users\USERNAME and run the following command against the favorites folder:
icacls Favorites /setintegritylevel (OI)(CI)low
The (OI) and (CI) stand for “object inherit” and “container inherit” respectively (propagate permissions basically), and the low is pretty obviously assigning a low integrity level requirement to the file.
[Updated: June 19, 2008 - Step-by-step instructions due to popular demaind]
The sequence should look like this: