65.9K
CodeProject 正在变化。 阅读更多。
Home

声明式编程——统一窗体和 Web 开发

starIconstarIconstarIconstarIcon
emptyStarIcon
starIcon

4.69/5 (10投票s)

2004年11月23日

4分钟阅读

viewsIcon

73945

downloadIcon

914

使用声明式编程创建 Web 和窗体小程序通用的 UI。

引言

这是一篇概念性文章,旨在激发您对声明式编程的兴趣,并探讨是否有可能统一 System.Windows.FormsSystem.Web.UI.WebControls 命名空间(至少对于简单应用程序而言)的开发。

当然,“真正的”网页包含大量 JavaScript 来减少回发,服务器端会进行大量优化,需要处理流量等实际问题。而且计算器示例,其中每个按钮点击都是一次回发,绝对不是最好的例子!

单击 此处 观看在线演示。

它是如何工作的?

非常简单。给定一个 MyXaml 标记文件,该文件在 System.Windows.Forms 命名空间中定义 UI,一个非常简单的 XSLT(由 Justin 编写)会将该标记转换为可以存在于 System.Web.UI.WebControls 命名空间中的标记。因此,一个标记文件可以驱动窗体和 Web UI,事件处理代码也相同,只有一个例外。

标记

源标记非常简单。没有样式,没有子窗体等。为了实现这一点,简单是最好的。

<?xml version="1.0" encoding="utf-8"?> 
<!-- (c) 2004 Marc Clifton All Rights Reserved -->
<wf:MyXaml
  xmlns:wf="System.Windows.Forms" 
  xmlns:def="Definitions">
  <wf:Panel Name="Calc" Location="10, 10" Size="600, 400">
    <Controls>
      <wf:TextBox def:Name="display" Location="0, 0" Size="155, 20" 
          BackColor="Black" ForeColor="Yellow" Text="0.00"/>

      <wf:Button Text="C" Location="0, 20" Size="60, 25"
                 Click="OnClear" FlatStyle="System"/>
      <wf:Button Text="CE" Location="60, 20" Size="30, 25" 
                 Click="OnClearEntry" FlatStyle="System"/>
      <wf:Button Text="=" Location="95, 20" Size="60, 25" 
                 Click="OnEqual" FlatStyle="System"/>

      <wf:Button Text="7" Location="0, 50" Size="30, 28" 
                 Click="OnDigit" FlatStyle="System"/>
      <wf:Button Text="8" Location="30, 50" Size="30, 28" 
                 Click="OnDigit" FlatStyle="System"/>
      <wf:Button Text="9" Location="60, 50" Size="30, 28"
                 Click="OnDigit" FlatStyle="System"/>
      <wf:Button Text="4" Location="0, 80" Size="30, 28" 
                 Click="OnDigit" FlatStyle="System"/>
      <wf:Button Text="5" Location="30, 80" Size="30, 28" 
                 Click="OnDigit" FlatStyle="System"/>
      <wf:Button Text="6" Location="60, 80" Size="30, 28" 
                 Click="OnDigit" FlatStyle="System"/>
      <wf:Button Text="1" Location="0, 110" Size="30, 28" 
                 Click="OnDigit" FlatStyle="System"/>
      <wf:Button Text="2" Location="30, 110" Size="30, 28" 
                 Click="OnDigit" FlatStyle="System"/>
      <wf:Button Text="3" Location="60, 110" Size="30, 28" 
                 Click="OnDigit" FlatStyle="System"/>
      <wf:Button Text="0" Location="0, 140" Size="30, 28" 
                 Click="OnDigit" FlatStyle="System"/>
      <wf:Button Text="." Location="60, 140" Size="30, 28" 
                 Click="OnDigit" FlatStyle="System"/>

      <wf:Button Text="+" Location="95, 50" Size="60, 28" 
                 Click="OnAdd" FlatStyle="System"/>
      <wf:Button Text="-" Location="95, 80" Size="60, 28" 
                 Click="OnSubtract" FlatStyle="System"/>
      <wf:Button Text="*" Location="95, 110" Size="60, 28" 
                 Click="OnMultiply" FlatStyle="System"/>
      <wf:Button Text="/" Location="95, 140" Size="60, 28" 
                 Click="OnDivide" FlatStyle="System"/>
    </Controls>
  </wf:Panel>
</wf:MyXaml>

生成的内容是什么?

XSLT 将其转换为

<wf:MyXaml
  xmlns:wf="System.Web.UI.WebControls, System.Web, Version=1.0.5000.0, 
            Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a"
  xmlns:def="Definitions">
  <wf:Panel Name="Calc" CssStyle="position:absolute; left:10px; 
            top:10px; width:600px; height:400px;">
    <Controls>
      <wf:TextBox def:Name="display" BackColor="Black" ForeColor="Yellow" 
                  Text="0.00" CssStyle="position:absolute; left:0px; top:0px;
                  width:155px; height:20px;"></wf:TextBox>

      <wf:Button Text="C" Click="OnClear" FlatStyle="System" 
          CssStyle="position:absolute; left:0px; top:20px; width:60px; 
          height:25px;"></wf:Button>
      <wf:Button Text="CE" Click="OnClearEntry" FlatStyle="System" 
          CssStyle="position:absolute; left:60px; top:20px; width:30px; 
          height:25px;"></wf:Button>
      <wf:Button Text="=" Click="OnEqual" FlatStyle="System" 
          CssStyle="position:absolute; left:95px; top:20px; width:60px; 
          height:25px;"></wf:Button>

      <wf:Button Text="7" Click="OnDigit" FlatStyle="System" 
          CssStyle="position:absolute; left:0px; top:50px; width:30px; 
          height:28px;"></wf:Button>
      <wf:Button Text="8" Click="OnDigit" FlatStyle="System" 
          CssStyle="position:absolute; left:30px; top:50px; width:30px; 
          height:28px;"></wf:Button>
      <wf:Button Text="9" Click="OnDigit" FlatStyle="System" 
          CssStyle="position:absolute; left:60px; top:50px; width:30px; 
          height:28px;"></wf:Button>
      <wf:Button Text="4" Click="OnDigit" FlatStyle="System" 
          CssStyle="position:absolute; left:0px; top:80px; width:30px; 
          height:28px;"></wf:Button>
      <wf:Button Text="5" Click="OnDigit" FlatStyle="System" 
          CssStyle="position:absolute; left:30px; top:80px; width:30px; 
          height:28px;"></wf:Button>
      <wf:Button Text="6" Click="OnDigit" FlatStyle="System" 
          CssStyle="position:absolute; left:60px; top:80px; width:30px; 
          height:28px;"></wf:Button>
      <wf:Button Text="1" Click="OnDigit" FlatStyle="System" 
          CssStyle="position:absolute; left:0px; top:110px; width:30px; 
          height:28px;"></wf:Button>
      <wf:Button Text="2" Click="OnDigit" FlatStyle="System" 
          CssStyle="position:absolute; left:30px; top:110px; width:30px; 
          height:28px;"></wf:Button>
      <wf:Button Text="3" Click="OnDigit" FlatStyle="System" 
          CssStyle="position:absolute; left:60px; top:110px; width:30px; 
          height:28px;"></wf:Button>
      <wf:Button Text="0" Click="OnDigit" FlatStyle="System" 
          CssStyle="position:absolute; left:0px; top:140px; width:30px; 
          height:28px;"></wf:Button>
      <wf:Button Text="." Click="OnDigit" FlatStyle="System" 
          CssStyle="position:absolute; left:60px; top:140px; width:30px; 
          height:28px;"></wf:Button>

      <wf:Button Text="+" Click="OnAdd" FlatStyle="System" 
          CssStyle="position:absolute; left:95px; top:50px; width:60px; 
          height:28px;"></wf:Button>
      <wf:Button Text="-" Click="OnSubtract" FlatStyle="System" 
          CssStyle="position:absolute; left:95px; top:80px; width:60px; 
          height:28px;"></wf:Button>
      <wf:Button Text="*" Click="OnMultiply" FlatStyle="System" 
          CssStyle="position:absolute; left:95px; top:110px; width:60px; 
          height:28px;"></wf:Button>
      <wf:Button Text="/" Click="OnDivide" FlatStyle="System" 
          CssStyle="position:absolute; left:95px; top:140px; width:60px; 
          height:28px;"></wf:Button>
    </Controls>
  </wf:Panel>
</wf:MyXaml>

XSLT 看起来是什么样的?

XSLT 非常直接

<?xml version="1.0" encoding="UTF-8" ?>
<xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
  <xsl:output method="xml"/>

  <xsl:template match="*">
    <xsl:choose>
      <xsl:when test="namespace-uri(.) = 'System.Windows.Forms'">
        <xsl:element name="{name(.)}" namespace="System.Web.UI.WebControls, 
             System.Web, Version=1.0.5000.0, Culture=neutral, 
             PublicKeyToken=b03f5f7f11d50a3a">
          <xsl:copy-of select="namespace::*[not(.='System.Windows.Forms')]"/>
          <xsl:copy-of select="@*[not(name()='Location') and not(name()='Size')]"/>
          <xsl:if test="@Location or @Size">
            <xsl:attribute name="CssStyle">
              <xsl:if test="@Location">position:absolute; left:<xsl:value-of 
              select="normalize-space(substring-before(@Location,','))"/>px; 
              top:<xsl:value-of 
              select="normalize-space(substring-after(@Location,','))"/>px;
              </xsl:if>
              <xsl:if test="@Size">width:<xsl:value-of 
              select="normalize-space(substring-before(@Size,','))"/>px; 
              height:<xsl:value-of 
              select="normalize-space(substring-after(@Size,','))"/>px;
              </xsl:if>
            </xsl:attribute>
          </xsl:if>
          <xsl:apply-templates></xsl:apply-templates>
        </xsl:element>
      </xsl:when>
      <xsl:otherwise>
        <xsl:element name="{name(.)}" namespace="{namespace-uri(.)}">
          <xsl:copy-of select="namespace::*[not(.='System.Windows.Forms')]"/>
          <xsl:copy-of select="@*[not(name()='Location') and not(name()='Size')]"/>
          <xsl:if test="@Location or @Size">
            <xsl:attribute name="CssStyle">
              <xsl:if test="@Location">position:absolute; left:<xsl:value-of 
              select="normalize-space(substring-before(@Location,','))"/>px;
              top:<xsl:value-of 
              select="normalize-space(substring-after(@Location,','))"/>px;
              </xsl:if>
              <xsl:if test="@Size">width:<xsl:value-of 
              select="normalize-space(substring-before(@Size,','))"/>px; 
              height:<xsl:value-of 
              select="normalize-space(substring-after(@Size,','))"/>px;
              </xsl:if>
            </xsl:attribute>
          </xsl:if>
        <xsl:apply-templates></xsl:apply-templates>
        </xsl:element>
      </xsl:otherwise>
    </xsl:choose>
  </xsl:template>
</xsl:stylesheet>

加载器

为了让所有这些魔法生效,标记必须被解析,这由 MyXaml 完成。解析在应用程序启动时完成。对于窗体版本,这在调用 Main() 时完成。在 Web 版本中,它在 Page_Load 事件处理程序中完成。

窗体加载器

窗体版本有一个非常简单的加载器

using System;
using System.Drawing;
using System.Collections;
using System.ComponentModel;
using System.Windows.Forms;
using System.Data;

using MyXaml;

namespace declarativeForm
{
  public class App
  {
    [STAThread]
    static void Main() 
    {
      new App();
    }

    public App()
    {
      Parser p=new Parser();
      SimpleCalc sc=new SimpleCalc();
      Panel panel=(Panel)p.LoadObject("calcForm.xml", "Calc", sc, null);
      Form form=new Form();
      form.Controls.Add(panel);
      Application.Run(form);
    }
  }
}

Web 加载器

Web 加载器更复杂,因为没有会话状态的概念。因此,管理计算器状态(连接数字、记住最后一个运算符等)的类必须保存在 Session 容器中。此外,每次回发时,都必须重新生成 UI。

using System;
using System.Collections;
using System.ComponentModel;
using System.Data;
using System.Diagnostics;
using System.Drawing;
using System.IO;
using System.Text;
using System.Web;
using System.Web.SessionState;
using System.Web.UI;
using System.Web.UI.WebControls;
using System.Web.UI.HtmlControls;
using System.Xml;
using System.Xml.Xsl;

using MyXaml;

namespace declarativeWeb
{
  public class WebForm1 : System.Web.UI.Page
  {
    protected System.Web.UI.WebControls.Panel formPanel;

    private void Page_Load(object sender, System.EventArgs e)
    {
      if (!IsPostBack)
      {
        SimpleCalc sc=new SimpleCalc();
        XmlDocument doc=LoadDocument();

        if (doc != null)
        {
          Parser parser=new Parser();
          Panel panel=(Panel)parser.LoadObject(doc, "Calc", sc, null);
          if (panel != null)
          {
            formPanel.Controls.Add(panel);
          }
        }

        Session["SimpleCalc"]=sc;
      }
      else
      {
        SimpleCalc sc=(SimpleCalc)Session["SimpleCalc"];
        XmlDocument doc=LoadDocument();

        if (doc != null)
        {
          Parser parser=new Parser();
          Panel panel=(Panel)parser.LoadObject(doc, "Calc", sc, null);
          if (panel != null)
          {
            formPanel.Controls.Add(panel);
          }
        }
      }
    }

    override protected void OnInit(EventArgs e)
    {
      InitializeComponent();
      base.OnInit(e);
    }

    private void InitializeComponent()
    { 
      this.Load += new System.EventHandler(this.Page_Load);
    }

    private XmlDocument LoadDocument()
    {
      XmlDocument doc=null;
      string path=MapPath("");

      try
      {
        doc=new XmlDataDocument();
        doc.PreserveWhitespace=true;
        doc.Load(path+"\\calcForm.xml");
        XslTransform xt=new XslTransform();
        xt.Load(path+"\\form2web.xslt");
        StringBuilder sb=new StringBuilder();
        StringWriter sw=new StringWriter(sb);
        xt.Transform(doc, null, new XmlTextWriter(sw), new XmlUrlResolver());

        doc=new XmlDocument();
        doc.LoadXml(sb.ToString());
        System.Diagnostics.Trace.WriteLine(sb.ToString());

      }
      catch(Exception ex)
      {
        Trace.Warn(ex.Message);
      }
      return doc;
    }
  }
}

计算器事件处理程序代码

计算器事件处理程序代码对于窗体和 Web 版本是相同的,只是在窗体版本中您必须指定

using System.Windows.Forms;

而在 Web 版本中,您指定

using System.Web.UI.WebControls;

这是 Web 版本

using System;
using System.Globalization;
using System.Web.UI.WebControls;    // Form version is System.Windows.Forms

using MyXaml;

namespace declarativeWeb
{
  public class SimpleCalc
  {
    private bool cleared;
    private string lastOp;
    private string lastValue;
    private NumberFormatInfo formatProvider;

    [MyXamlAutoInitialize] private TextBox display=null;

    public SimpleCalc()
    {
      cleared=true;
      lastOp=String.Empty;
      lastValue=String.Empty;

      formatProvider=new NumberFormatInfo();
      formatProvider.NumberDecimalDigits=2;
    }

    public void OnClear(object sender, EventArgs e)
    {
      display.Text="0.00";
      cleared=true;
      lastOp=String.Empty;
      lastValue=String.Empty;
    }

    public void OnClearEntry(object sender, EventArgs e)
    {
      display.Text="0.00";
      cleared=true;
    }

    public void OnDigit(object sender, EventArgs e)
    {
      Button btn=(Button)sender;
      if (cleared)
      {
        display.Text=btn.Text;
        cleared=false;
      }
      else
      {
        display.Text=display.Text+btn.Text;
      }
    }

    public string ProcessLastOp(string val)
    {
      double d=0;
      switch(lastOp)
      {
        case "+":
        {
          d=Convert.ToDouble(lastValue) + Convert.ToDouble(val);
          break;
        }
        case "-":
        {
          d=Convert.ToDouble(lastValue) - Convert.ToDouble(val);
          break;
        }
        case "*":
        {
          d=Convert.ToDouble(lastValue) * Convert.ToDouble(val);
          break;
        }
        case "/":
        {
          if (Convert.ToDouble(val) != 0.0)
          {
            d=Convert.ToDouble(lastValue) / Convert.ToDouble(val);
          }
          break;
        }
      }
      lastValue=d.ToString("N", formatProvider);
      return lastValue;
    }

    public void OnAdd(object sender, EventArgs e)
    {
      if (lastValue==String.Empty)
      {
        lastValue=display.Text;
      }
      else
      {
        lastValue=ProcessLastOp(display.Text);
        display.Text=lastValue;
      }
      lastOp="+";
      cleared=true;
    }

    public void OnSubtract(object sender, EventArgs e)
    {
      if (lastValue==String.Empty)
      {
        lastValue=display.Text;
      }
      else
      {
        lastValue=ProcessLastOp(display.Text);
        display.Text=lastValue;
      }
      lastOp="-";
      cleared=true;
    }

    public void OnMultiply(object sender, EventArgs e)
    {
      if (lastValue==String.Empty)
      {
        lastValue=display.Text;
      }
      else
      {
        lastValue=ProcessLastOp(display.Text);
        display.Text=lastValue;
      }
      lastOp="*";
      cleared=true;
    }

    public void OnDivide(object sender, EventArgs e)
    {
      if (lastValue==String.Empty)
      {
        lastValue=display.Text;
      }
      else
      {
        lastValue=ProcessLastOp(display.Text);
        display.Text=lastValue;
      }
      lastOp="/";
      cleared=true;
    }

    public void OnEqual(object sender, EventArgs e)
    {
      if (lastValue != String.Empty)
      {
        display.Text=ProcessLastOp(display.Text);
        lastValue=String.Empty;
        lastOp=String.Empty;
        cleared=true;
      }
    }
  }
}

烟雾和镜子在哪里?

这里只使用了一个“技巧”。绝对定位,而 Web 控件不支持这一点,除非使用 HTML 中的 style 属性。 .NET 对此的支持是通过 Style 属性。但是,此属性不接受用于构造 HTML style 属性的字符串。相反,WebControl.Style 属性是 CssStyleCollection 类型。更糟糕的是,虽然此类型表现得有点像字典(它有一个键值对 Add 方法),但它没有从 IDictionary 派生。

因此,烟雾和镜子在于 MyXaml 为 CssStyle 属性提供了自定义属性设置器代码。当它遇到 CssStyle 属性时,它会调用该属性的句柄(CssStyleCustomProperty),该句柄会解码字符串并将 CssStyleCollection 加载到正在解析的实例的 Style 属性中。至少使用的机制是一个通用的自定义属性句柄,而不是将 Web 样式处理直接嵌入 MyXaml 解析器中。

下载内容

对于 Web 演示

  1. 创建一个 ASP.NET Web 应用程序项目。
  2. 将 Web 下载中的文件复制到项目的文件夹中,覆盖 WebForm.* 文件。
  3. 添加对已放入 bin 目录的 MyXaml.dll 程序集的引用。

对于窗体演示,只需解压缩下载文件并编译提供的 csproj。

致 MyXaml 用户的一封信

如果您想针对 MyXaml 源代码进行编译,MyXaml 有一个小的更改,用于处理 MyXaml 节点上的前缀。在 InitializeNamespaces 中,更改

XmlNode uiNode=doc.GetElementsByTagName("MyXaml")[0];

to

XmlNode uiNode=doc.DocumentElement;

由于 MyXaml 是一个 GPL 开源项目,我希望人们直接从 MyXaml 网站获取源代码,并同意许可条款。下载仅包含 MyXaml 程序集。

GAC 问题

MyXaml 将自身注册到 GAC,但出于某种非常恼人的原因,我在 Web 版本获取正确的 MyXaml 文件时遇到了问题。如果您怀疑存在此类问题,请从 GAC 中删除 MyXaml(请注意,MyXaml 项目有一个生成后步骤会将自身注册到 GAC,因此您可能也想将其删除)。

结论

在简单的 UI 控件、简单的需求以及回发性能不是问题的范围内,声明式编程和 XSLT 可以从同一个代码库创建窗体和 Web 小程序。

© . All rights reserved.