|
|||||||||||||||||||||
|
|||||||||||||||||||||
|
Announcements
Chapters
Services
Feature Zones
|
IntroductionMany recent programs have a web look. Take for example the login screen of Windows XP, the Control Panel in category view on XP, the User Accounts manager again on XP. Or the the start page of the Visual Studio .NET or its wizards. They all look like HTML pages (some of them, the last 2 for example, actually are). The reason for that is because HTML pages can look and feel really cool. Much better than the traditional dialogs. How are such applications made?There are 2 ways:
The first one is no good at all. It turns out that you spend most of the time writing code that aligns the controls, pictures, etc. A small change in the user interface design results in vast changes in the source code done by hand (no designer available when you align the controls by yourself). Microsoft has tried to solve this problem trough anchoring and docking. In my opinion they have failed. So this leads us to the second choice - use the IE as an ActiveX control embedded in your application. Using the IE controlThere are 2 tasks to solve so that we can use the IE control: How to fill it with HTML and how to handle events from the Web page. Microsoft provides a solution for both called HTML dialogs. Unfortunately, it has many drawbacks. First, it is only for C++ and MFC. Second, the web page and its resources are located in the Win32 resources of the exe/DLL. This is not easy achievable in .Net. Another issue is that the event handling does not always work. Sometimes you press a button and it does not generate any events. In addition, the dialogs have a fixed look. You create them at design time and they do not change anymore. In conclusion: it is better to find some other method. Filling the control with HTMLI will address the problem of generating HTML later. For now I will assume that the HTML for the dialog has somehow been generated. To fill the HTML in the control it is sufficient to get it's using System;
using System.Windows.Forms;
using AxSHDocVw;
using mshtml;
namespace ShowHowToWriteToDocument
{
public class ShowHowToWriteToDocument {
AxWebBrowser m_webBrowser = null;
// Here goes the code that initializes the form and so on
// ............................
//
public void WriteToDocument(string aString)
{
IHTMLDocument2 doc = m_webBrowser.Document as IHTMLDocument2;
doc.write(aString);
}
void ClearContent()
{
IHTMLDocument2 doc = m_webBrowser.Document as IHTMLDocument2;
doc.write("");
doc.close();
doc.write("");
}
bool OnClickHandler(IHTMLEventObj o)
{
MessageBox.Show("click");
return true;
}
public void AttachEvents()
{
IHTMLDocument2 doc = m_webBrowser.Document as IHTMLDocument2;
doc.writeln("<body><button name='button1'>button1</button>"
+ "<button name='button1'>button2</button></body>");
object button = doc.all.item("button1", 0);
HTMLButtonElementEvents2_Event buttonEvents =
(HTMLButtonElementEvents2_Event)button;
buttonEvents.onclick += new
HTMLButtonElementEvents2_onclickEventHandler(OnClickHandler);
}
}
}
Note: you have to navigate to about:blank prior to using the Handling eventsis more complicated. Pieces of .NET code have to be attached to events of controls on the web page. This can be achieved using the using System;
using System.Windows.Forms;
using AxSHDocVw;
using mshtml;
using MsHtmHstInterop;
using System.Runtime.InteropServices;
namespace ShowHowToUseWindowExternal
{
[InterfaceType(ComInterfaceType.InterfaceIsIDispatch)]
public interface TheWindowExternalInterface
{
[DispId(0)]
void HandleSomeEvent(string someInformation);
[DispId(1)]
bool HandleAnotherEvent(int someInformation);
}
public class TheWindowExternalImplementation : TheWindowExternalInterface
{
public TheWindowExternalImplementation() {}
public void HandleSomeEvent(string someInformation)
{
//Do something here
}
public bool HandleAnotherEvent(int someInformation)
{
//Do something here
return true;
}
}
public class DemonstrateWindowExternal
{
public class DocHostUIHandlerImpl : IDocHostUIHandler
{
object m_external;
public DocHostUIHandlerImpl(object aExternal)
{
m_external = aExternal;
}
public void EnableModeless(int fEnable) { }
public void GetOptionKeyPath(out string pchKey, uint dw)
{ pchKey = null; }
public void TranslateAccelerator(ref MsHtmHstInterop.tagMSG lpmsg,
ref System.Guid pguidCmdGroup, uint nCmdID) { }
public void FilterDataObject(MsHtmHstInterop.IDataObject pDO,
out MsHtmHstInterop.IDataObject ppDORet) { ppDORet = null; }
public void OnFrameWindowActivate(int fActivate) { }
public void UpdateUI() { }
public void ShowContextMenu(uint dwID,
ref MsHtmHstInterop.tagPOINT ppt,
bject pcmdtReserved, object pdispReserved)
{
throw new COMException("", 1);
}
public void TranslateUrl(uint dwTranslate, ref ushort pchURLIn,
System.IntPtr ppchURLOut)
{
throw new COMException("", 1);
}
public void ShowUI(uint dwID,
MsHtmHstInterop.IOleInPlaceActiveObject pActiveObject,
MsHtmHstInterop.IOleCommandTarget pCommandTarget,
MsHtmHstInterop.IOleInPlaceFrame pFrame,
MsHtmHstInterop.IOleInPlaceUIWindow pDoc) { }
public void GetExternal(out object ppDispatch)
{
ppDispatch = m_external;
}
public void ResizeBorder(ref MsHtmHstInterop.tagRECT prcBorder,
MsHtmHstInterop.IOleInPlaceUIWindow pUIWindow, int fRameWindow)
{ }
public void GetDropTarget(MsHtmHstInterop.IDropTarget pDropTarget,
out MsHtmHstInterop.IDropTarget ppDropTarget)
{ ppDropTarget = null; }
public void GetHostInfo(ref
MsHtmHstInterop._DOCHOSTUIINFO pInfo) { }
public void HideUI() { }
public void OnDocWindowActivate(int fActivate) { }
}
AxWebBrowser m_webBrowser = null;
// Here goes the code that initializes the form and so on
// ............................
//
public void InstallWindowExternal()
{
TheWindowExternalImplementation exObj;
exObj = new TheWindowExternalImplementation();
ICustomDoc custDoc = (ICustomDoc)m_webBrowser.Document;
custDoc.SetUIHandler(new DocHostUIHandlerImpl(exObj));
}
}
}
Again - to use the <html>
<body>
<a href="javascript:window.external.HandleSomeEvent('someInfo')">
Invoke window.external.HandleSomeEvent('someInfo')
</a>
<a href="javascript:if(window.external.HandleAnotherEvent(5))
alert('true');">
Invoke window.external.HandleAnotherEvent(5)
</a>
</body>
</html>
Some unresolved issuesUntil now we haven't talked about the HTML generation. One way is to store it somewhere, read it and fill it in when needed. This works for some applications but the HTML pages displayed in this way will be static. It would be nice to use some kind of framework for generating dynamic WEB pages. Some other issues:
The solutionWhat this class library offers as a solution is a small web server. It solves all the problems described above. Architecture of the serverThe web server has a plug-in architecture. The server acts as a container and it is up to you to provide it with the plug-ins. All the work is done by them. The plug-ins should provide 3 functions: Predefined resolversThere are some resolvers at your disposition that you can use. They fall into 2 categories : Dynamic content resolvers and static content resolvers. The static resolvers are used to serve the HTML server for static content. Their work is to map virtual addresses to physical ones. There are 2 static resolvers in the library: One for resources located on physical file systems and one for resources embedded in the application. There is only one dynamic content resolver. It serves as a container for user defined servlets. I will come back later on it. Below is an example of how to start the server and add 2 resolvers to it: using System;
using System.Reflection;
using Vitamin.Research.WebFramework;
namespace ShowHowToUseServer
{
class ShowHowToUseServer
{
WebServer m_server;
public void Start()
{
m_server = new WebServer(8080);
ContentLocationResolver clr = new
ContentLocationResolver("c:\temp", "phys");
m_server.AddResolver(clr, 10, -1);
EmbeddedLocationResolver elr;
elr = new EmbeddedLocationResolver(
Assembly.GetExecutingAssembly(),
"stf.res", "emb");
m_server.AddResolver(elr, 10, 20);
m_server.Start();
}
}
}
After you execute the Dynamic resolvers and servletsThere is currently only one dynamic resolver in the framework. It serves as a container for servlets. It is up to the programmer to write them. A servlet is a class implementing the The plain servlet:using System;
using System.Reflection;
using Vitamin.Research.WebFramework;
namespace PlainServletExample
{
class PlainServletExample : ServletPageBase
{
public PlainServletExample() {}
public override string Address
{
get
{
return "/test/test.sfrm";
}
}
public override void Answer
(Vitamin.Research.WebFramework.WebRequest aRequest)
{
aRequest.Response.WriteLine("<html><body>"
+ "A plain servlet example</body></html>")
}
}
}
The XSLT servlet:using System;
using System.Xml;
using System.Reflection;
using Vitamin.Research.WebFramework;
namespace XsltServletExample
{
class XsltServletExample : XsltServletPage
{
public XsltServletExample() {}
public override string XslTransformName
{
get
{
return "/view/XsltTest.xslt";
}
}
public override XmlDocument getXML(WebRequest aRequest)
{
XmlDocument xdoc = new XmlDocument();
XmlElement elPage = xdoc.CreateElement("page");
xdoc.AppendChild(elPage);
for(int i = 0; i < 100; i++)
{
XmlElement el = xdoc.CreateElement("number");
XmlAttribute attr = xdoc.CreateAttribute("value");
attr.Value = i.ToString();
el.Attributes.Append(attr);
elPage.AppendChild(el);
}
return xdoc;
}
public override string Address
{
get
{
return "/view/XsltTest.xfrm";
}
}
}
}
The XsltTest.xslt file:<?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" version="1.0" encoding="UTF-8" indent="yes"/>
<xsl:template match="/">
<html>
<body>
<xsl:for-each select="page/number">
<xsl:value-of select="@value"/>
<br/>
</xsl:for-each>
</body>
</html>
</xsl:template>
</xsl:stylesheet>
Note that in order for the above example to work you should have some static resolver that resolves /view/XsltTest.xslt to the file above. To add servlets to the dynamic resolver use the Tips on debugging servletsPlain servlets are debugged just as you debug a program. This is not the case with XSLT servlets. I still haven't found a good XSL transformation debugger so the technique I use is to write messages in the output. You could eventually ease your self if you write an extension object for the XSLT with a function that prints messages to the debug console via Drawbacks and possible improvementsOne of the things that has not been thought of is security. In this release anyone from any computer can connect to the running server. A good improvement would be to add ASP support. The hardest part of it is to create the servlet (source code) from the asp. Compiling it afterwards is easy using the compilers provided with .NET. | ||||||||||||||||||||