xacc.propertygrid

分享于 

20分钟阅读

Web开发

  繁體

Screenshot - xaccpg.png

内容

  • 介绍
  • 功能
  • 限制
  • 设计
  • 实现
  • 安装
  • 用法示例
  • Points of interest
  • 结束语
  • 引用

介绍

xacc.propertygrid 是具有部分设计时间支持的ASP.NET 自定义控件。 它利用了一系列的front-和后端技术来为用户提供动态而响应的经验。 转到 http://xacc.qsh.eu/ ( 在新窗口中打开) 以进行演示 ! Fiddle,,改变值 !

功能

  • asp.net 服务器控件
  • 易于用作WinForms对应
  • 包含了 WinForms PropertyGrid的所有功能,排除了编辑器和设计器
  • 自动数据绑定
  • 自动错误检查
  • 可自定义的
  • 可折叠
  • 'Live模式'
  • 非常精简( 15kb Javascript和 CSS )
  • 非常响应
  • 在 ASP.NET 1.1和 2.0中工作
  • 适用于所有现代浏览器( IE 6,Firefox 1.5 +,Opera 8 + )
  • W3C XHTML 1.0兼容
  • W3C CSS投诉

限制

  • 没有刷新就不能添加子属性( 控件将自动重新加载本例中的页)
  • 没有编辑支持的时间
  • 有限设计时间支持
  • IE 7似乎已损坏,当他们发布'稳定'版本时,我将支持这里浏览器

设计

1st 实现

这是一个非常粗糙的概念证明,基于圣歌和使用它的控件。 这证明工作正常,但是AJAX交互对于我来说太长了。 另外,不能有效地应用 CSS,使得这种方法不能很灵活,但它工作起来非常灵活。

真正的事情

  • 规划- 这个阶段涉及到许多事情的思考,确切地展示了最终结果的路径。
  • Layout - 这个阶段只是分解控件所需的所有元素。 作为网格,使用简单的DIV表/网格非常简单。
  • 模拟- 这个阶段涉及用CSS和Javascript来模拟 static的HTML。
  • ASP.NET 服务器控件- 这个阶段涉及到将 static HTML重构为可以重用控件,也将歌曲转换为'瘦子'。
  • 把一切都放在一起- finally 需要一些调整使一切都能一起工作。

实现

asp.net 服务器控件

控件本身并不令人兴奋,除了实际绑定之外。 让我们看一下代码:

object selobj;
[Browsable(false)]publicobject SelectedObject
{
 get {return selobj;}
 set {
 if (selobj!= value)
 {
 selobj = value;
 CreateGrid();
 }
 }
}
ArrayList proplist = new ArrayList();
Hashtable properties = new Hashtable();
ArrayList catlist = new ArrayList();int catcounter = 0;int subcounter = 0;int itemcounter = 0;void CreateGrid()
{
 if (selobj == null)
 {
 return;
 }
 Controls.Clear();
 properties.Clear();
 proplist.Clear();
 itemcounter =
 catcounter =
 subcounter = 0;
 Controls.Add( new PropertyGridHeader());
 Hashtable cats = new Hashtable();
 foreach (PropertyDescriptor pd in TypeDescriptor.GetProperties(selobj))
 {
 if (!pd.IsBrowsable)
 {
 continue;
 }
 string cat = pd.Category;
 Hashtable mems = cats[cat] as Hashtable;
 if (mems == null)
 {
 cats[cat] = mems = new Hashtable();
 }
 try {
 PropertyGridItem pgi = new PropertyGridItem(pd);
 pgi.controlid = ClientID + "_" + itemcounter++;
 properties[pgi.controlid] = pgi;
 object o = selobj;
 object subo = null;
 try {
 subo = pd.GetValue(o);
 }
 catch {}
 if ( pd.Converter.GetPropertiesSupported())
 {
 foreach (PropertyDescriptor spd in pd.Converter.GetProperties(subo))
 {
 if (spd.IsBrowsable)
 {
 PropertyGridItem pgsi = new PropertyGridSubItem(spd, pgi);
 pgsi.controlid = ClientID + "_" + itemcounter++;
 pgi.subitems.Add(pgsi);
 properties[pgsi.controlid] = pgsi;
 }
 }
 }
 mems.Add(pd.Name, pgi);
 }
 catch (Exception ex)
 {
 Page.Response.Write(ex);
 }
 }
 this.catlist.Clear();
 ArrayList catlist = new ArrayList(cats.Keys);
 catlist.Sort();
 HtmlContainerControl cc = new HtmlGenericControl("div");
 cc.ID = "cats";
 Controls.Add(cc);
 foreach (string cat in catlist)
 {
 PropertyGridCategory pgc = new PropertyGridCategory();
 pgc.CategoryName = cat;
 this.catlist.Add(pgc);
 cc.Controls.Add(pgc);
 Hashtable i = cats[cat] as Hashtable;
 ArrayList il = new ArrayList(i.Keys);
 il.Sort();
 foreach (string pginame in il)
 {
 PropertyGridItem pgi = i[pginame] as PropertyGridItem;
 proplist.Add(pgi);
 pgc.Controls.Add(pgi);
 if (pgi.subitems.Count >0)
 {
 SubItems si = new SubItems();
 pgi.Controls.Add(si);
 foreach (PropertyGridItem spgi in pgi.subitems)
 {
 si.Controls.Add(spgi);
 proplist.Add(spgi);
 }
 }
 }
 }
 Controls.Add( new PropertyGridFooter());
}

在这里,我只是迭代从 TypeDescriptor 提供的PropertyDescriptorCollection。 在这个场景中使用 PropertyDescriptor 非常有用,因为它需要大量的无法使用的反射代码。 它还具有缓存反射调用的好处,在重新创建相同控件时加快重用速度。

首先,添加所有属性( 和子属性),并跟踪它们的类别。 然后类别按字母顺序排序,类别容器被创建,属性被添加到它们。

我决定使用自定义控件( 从控件派生),而不是使用命令行。 这有好处,我几乎可以发出任何我需要的HTML。 当设计被重新分析时,我只需要控制一些控制来控制发射。 它们如下所示:

  • PropertyGridHeader - 在控件顶部携带信息。
  • PropertyGridCategory - 为每个类别创建。
  • PropertyGridItem - 选定对象的'顶层'属性。
  • SubItems - 子属性容器。
  • PropertyGridSubItem -'子级别'属性。
  • PropertyGridFooter - 包含帮助和底部栏。

让我们看看 PropertyGridItem 是如何工作的,特别是它的RenderEditor 方法。

void RenderEditor(HtmlTextWriter writer)
{
 if (propdesc.IsReadOnly || ParentGrid.ReadOnly)
 {
 writer.Write(@"<span title=""{1}""><span" + 
 @" id=""{0}"" style=""color:gray"">{1}" + 
 @"</span></span>",
 controlid,
 PropertyValue);
 }
 else {
 TypeConverter tc = propdesc.Converter;
 if ( tc.GetStandardValuesSupported())
 {
 string pv = PropertyValue;
 writer.Write(@"<a onclick=""{2}.BeginEdit" + 
 @"(this); return false;"" href=""#""" +
 @" title=""Click to edit""><span" + 
 @" id=""{0}"">{1}</span></a>",
 controlid,
 pv,
 ParentGrid.ClientID);
 writer.Write(@"<select style=""display" + 
 @":none"" onblur=""{0}.CancelEdit(this)"""+
 @" onchange=""{0}.EndEdit(this)"">",
 ParentGrid.ClientID);
 foreach (object si in tc.GetStandardValues())
 {
 string val = tc.ConvertToString(si);
 if (val == pv)
 {
 writer.Write(@"<option" + 
 @"selected=""selected"">{0}</option>", val);
 }
 else {
 writer.Write(@"<option>{0}</option>", val);
 }
 }
 writer.Write("</select>");
 }
 else {
 if (tc.CanConvertFrom(typeof(string)))
 {
 writer.Write(@"<a onclick=""{2}.BeginEdit" + 
 @"(this);return false"" href=""#""" +
 @" title=""Click to edit""><span" + 
 @"id=""{0}"">{1}</span></a>",
 controlid,
 PropertyValue,
 ParentGrid.ClientID);
 writer.Write(@"<input onkeydown=""return {0}." + 
 @"HandleKey(this,event)"" onblur=""{0}.CancelEdit"""+
 @"(this) style=""display:none"" type" + 
 @"=""text"" onchange=""{0}.EndEdit(this)""/>",
 ParentGrid.ClientID);
 }
 else {
 writer.Write(@"<span title=""{1}""><span" + 
 @"id=""{0}"" style=""color:gray"">{1}</span></span>",
 controlid,
 PropertyValue);
 }
 }
 }
}

这是一个简单的消除树,决定应该做。

  • 如果属性或者 PropertyGrid 是只读的,则呈现为'标签'。
  • 如果属性支持'supportedvalues',则使用隐藏的DropDownList ( 选择) 呈现为'标签'。 ( 注意:这并不是真正的,我将尝试为支持值和从字符串转换的属性找到解决方案。
  • 如果属性支持从类型字符串转换,则使用隐藏文本框( 输入) 呈现为'标签'。
  • 呈现为'标签'。

如果选择了选项 2或者 3,当单击'标签'时,'标签'将隐藏,并显示'编辑'控件,或者更改它的值。

AJAX

[Skinny.Method]public string[] GetValues()
{
 string[] output = new string[proplist.Count];
 for (int i = 0; i < output.Length; i++)
 {
 output[i] = (proplist[i] as PropertyGridItem).PropertyValue;
 }
 return output;
}
[Skinny.Method]public string[] SetValue(string id, string val)
{
 if (!ReadOnly)
 {
 PropertyGridItem pgi = properties[ClientID + 
 "_" + id] as PropertyGridItem;
 pgi.PropertyValue = val;
 }
 return GetValues();
}
[Skinny.Method]public string[] GetDescription(string id)
{
 PropertyGridItem pgi = properties[ClientID + 
 "_" + id] as PropertyGridItem;
 PropertyDescriptor pd = pgi.Descriptor;
 string[] output = new string[] { pd.DisplayName + " :" + 
 pd.PropertyType.Name, pd.Description };
 return output;
}

最初我使用Anthem支持 AJAX,但是他们的模型太臃肿了。 我真的想要一个缩减版本。 它是一个bare版本,它只支持调用方法,但它只支持调用方法,而添加了方法的功能。 PropertyGrid 方法设计为尽可以能多地执行每个'回拨',最小化所需的流量。

Javascript

Element =
{
extended: true,
visible: function(vis)
{
 if (vis!= null) {
 if (typeof vis == 'boolean')
 vis = vis? '' : 'none';
 this.style.display = vis;
 }
 returnthis.style.display!= 'none';
},
kids: function(index)
{
 if (index == null) {
 var c = [];
 for (var i = 0; i <this.childNodes.length; i++)
 if (this.childNodes[i].nodeType!= 3)
 c.push($(this.childNodes[i]));
 return c;
 }
 else {
 for (var i = 0, j = 0; i <this.childNodes.length; i++) {
 if (this.childNodes[i].nodeType!= 3) {
 if (j == index)
 return $(this.childNodes[i]);
 j++;
 }
 }
 returnnull;
 }
},
parent: function()
{
 return $((this.parentNode == 'undefined')? 
 this.parentElement : this.parentNode);
},
prev: function()
{
 var p = this.previousSibling;
 while (p.nodeType == 3)
 p = p.previousSibling;
 return $(p);
},
next: function()
{
 var p = this.nextSibling;
 while (p.nodeType == 3)
 p = p.nextSibling;
 return $(p);
}
};function $(e)
{
 function extend(dst,src)
 {
 if (!dst.extended)
 for (var i in src)
 dst[i] = src[i];
 return dst;
 }
 return extend( (typeof e == 'string')? 
 document.getElementById(e) : e, Element);
}

这个 $ 函数受到Prototype库的启发,允许我使用 跨浏览器 安全 JavaScript。 JavaScript具有漂亮的功能位。 这对大家来说可能不适用,但对我来说已经很方便了。

JavaScript的其余部分包含三个AJAX函数,而 finally 是主要的PropertyGrid Prototype。 这允许我以每个实例方式处理所有内容,并允许我动态地将样式注入到控件中。 有两类函数,事件处理程序响应用户输入,以及由 BeginEditEndEditCancelEdit 组成的'编辑'函数。

CSS

CSS也不是很令人兴奋,只是在控件加载时注入CSS以应用它的样式。 不幸的是,Opera 中不支持这一点,但是我有一个修复方法,它将样式发送到 HTML body ( 但是破坏了XHTML一致性)。 让我们看看这是如何完成的:

ApplyStyles: function(stylesheet)
{
 var self = this;
 function rule(sel,val)
 {
 var sels = sel.split(',');
 for (var i = 0; i <sels.length; i++)
 {
 var s = sels[i];
 var re =/s/;
 var res = re.exec(s);
 if (res)
 s = s.replace(re, '_' + self.id + ' ');
 else s = s + '_' + self.id;
 if (stylesheet.addRule) //IE stylesheet.addRule(s, val);
 elseif (stylesheet.insertRule) // Moz stylesheet.insertRule(s + '{' + val + '}', 
 stylesheet.cssRules.length);
 elsereturn; //opera }
 }
 rule('.PG','width:' + this.width + 'px');
 rule('.PG *','color:' + this.fgcolor + ';font-family:' + 
 this.family + ';font-size:' + this.fontsize);
 rule('.PGH,.PGF,.PGC,.PGF2','border-color:' + 
 this.headerfgcolor + ';background-color:' + this.bgcolor);
 rule('.PGC *','line-height:' + this.lineheight + 
 'px;height:' + this.lineheight +'px');
 rule('.PGC a,.PGC_OPEN,.PGC_CLOSED',
 'width:' + this.padwidth + 'px');
 rule('.PGC_HEAD span','color:' + this.headerfgcolor);
 rule('.PGI_NONE,.PGI_OPEN,.PGI_CLOSED','width:'+ 
 this.padwidth+'px;height:'+this.LineHeightMargin()+'px');
 rule('.PGI_NAME,.PGI_VALUE,.PGI_NAME_SUB','width:'+
 this.HalfWidth()+'px;background-color:'+this.itembgcolor);
 rule('.PGI_VALUE a,.PGI_VALUE select','width:100%');
 rule('.PGI_NAME_SUB span','margin-left:' + this.padwidth + 'px');
 rule('.PGI_VALUE a:hover','background-color:' + this.selcolor);
 rule('.PGI_VALUE input','width:' + this.HalfWidthLess3() +
 'px;line-height:' + this.InputLineHeight() + 
 'px;height:' + this.InputLineHeight() + 'px');
}

这些规则将添加到浏览器窗口中的最后一个样式表中。 to'范例',类名被附加到 PropertyGridClientID 中。

安装

  • 将'pg'目录(。图像,css和脚本) 复制到webroot目录中。
  • 添加对项目的引用,或者添加到工具箱。

用法示例

目标代码

演示用于控制输出的属性用法的示例属性:

[Category("Appearance")][Description("Change this value, and see the ones below change too." +
 "Change a value from below and see how this one changes.")]public Rectangle Bounds
{
 get {return bounds;}
 set {bounds = value;}
}
[TypeConverter(typeof(ExpandableObjectConverter))]public Nested2 NestedStruct
{
 get {return n2;}
 set {n2 = value;}
}

允许通过逗号分隔字符串( 因为当前没有访问编辑器对话框的权限) 设置 string[]的更高级示例:

string[] buddies = {"Tom","Dick","Harry"};
[TypeConverter(typeof(StringArrayConverter))]public string[] Buddies
{
 get {return buddies ; }
 set {buddies = value; }
}publicclass StringArrayConverter : 
 System.ComponentModel.ArrayConverter
{
 publicoverridebool CanConvertTo(ITypeDescriptorContext 
 context, Type destinationType)
 {
 if (destinationType == typeof(string))
 {
 returntrue;
 }
 returnbase.CanConvertTo (context, destinationType);
 }
 publicoverridebool CanConvertFrom(ITypeDescriptorContext 
 context, Type sourceType)
 {
 if (sourceType == typeof(string))
 {
 returntrue;
 }
 returnbase.CanConvertFrom (context, sourceType);
 }
 publicoverrideobject ConvertFrom(ITypeDescriptorContext 
 context, CultureInfo culture, objectvalue)
 {
 if (valueisstring)
 {
 return (valueasstring).Split(',');
 }
 returnbase.ConvertFrom (context, culture, value);
 }
 publicoverrideobject ConvertTo(ITypeDescriptorContext 
 context, CultureInfo culture,
 objectvalue, Type destinationType)
 {
 if (destinationType == typeof(string))
 {
 returnstring.Join(",", valueas string[]);
 }
 returnbase.ConvertTo (context, culture, value, destinationType);
 }
}

注意:如果类必须是 public,否则你将在有限的ASPNET帐户( IE ) 下运行SecurityAccess异常( )。 大多数服务器)。

ASP.NET

在前面的代码中:

<%@Pagelanguage="c#"Codebehind="default.aspx.cs"AutoEventWireup="false"Inherits="PropertyGridWeb.WebForm1"enableViewState="true"%><%@RegisterTagPrefix="xacc"Namespace="Xacc"Assembly="xacc.propertygrid"%><!DOCTYPEHTMLPUBLIC"-//W3C//DTDHTML4.0Transitional//EN"><HTMLlang="en"><HEAD><title>ASP.NET PropertyGrid Demo</title></HEAD><body><formid="Form1"method="post"runat="server"><xacc:propertygridid="pg1"runat="server"ShowHelp="True"></xacc:propertygrid><xacc:propertygridid="pg2"runat="server"ReadOnly="True"Width="350"SelectionColor="CadetBlue"BackgroundColor="NavajoWhite"FontFamily="Tahoma"FontSize="9pt"ForeColor="DimGray"HeaderForeColor="Brown"ItemBackgroundColor="WhiteSmoke"></xacc:propertygrid></form></body></HTML>

设计时间支持有限。 当将控件拖放到窗体中时,上面 将是'生成'。 要使网格看起来更好,请向样式表添加一个设计阶段链接。

后面的代码:

void Page_Load(object sender, System.EventArgs e)
{
 pg1.SelectedObject = Global.STATIC;
 pg2.SelectedObject = Global.STATIC;
}

就像使用普通 PropertyGrid 一样。 记住你使用'无状态'环境,因此为什么我只是在示例中使用 static 成员。

Points of interest

  • Javascript摇滚 !
  • DevBoi ( 离线) - 浏览器中的所有标准
  • Firefox WebDeveloper - 非常方便,但在添加的样式表上失败
  • FireBug - 非常方便的DOM浏览器
  • IE Web工具栏- 非常方便,在 Firefox 失败的地方工作
  • VS.NET JavaScript调试- 在 VS.NET, 中调试JavaScript只是在 IE 中启用它,然后在. js 文件(. aspx 无法工作) 中设置断点,然后在中进行调试。

结束语

可能的增强功能:

  • 编辑器支持。
  • 折叠/折叠动画。
  • 从选定对象中公开'动词'方法( IE。 foo.Save() )。
  • 更好的设计时间支持而不增加控制。
  • 更多Javascript客户端利用率。

感谢 Paul Watson关于JavaScript和CSS的帮助。

感谢Anthem的作者。

引用

  • EMCA 262
  • W3C CSS
  • W3C XHTML
  • Prototype库

相关文章