Po długiej przerwie związanej z pracą zawodową wracam do studiowania, zwłaszcza że na horyzoncie widać sesję :) Na przedmiot “Programowanie i architektury systemów komponentowych” miałem przygotować projekt. Wybrałem wykorzystanie JAXB do wizualizacji drzewa dokumentu XML. JAXB jest jednym z elementów J2EE, ale nie mogłem się oprzeć okazji zrobienia czegoś podobnego w .NETcie, tak “dla sportu” :).
Zabawę zacząłem od stworzenia XML Schema. Założyłem, że w pliku XML chcę przechowywać korespondencję. Oczywiście zrobiłem bardzo uproszczoną wersję, ale myślę że i tak ciekawą:
<xsd:schema xmlns:xsd="http://www.w3.org/2001/XMLSchema"
xmlns="urn:mails-schema"
elementFormDefault="qualified"
targetNamespace="urn:mails-schema">
<!-- korzeń - zbiór maili -->
<xsd:element name="mails" type="mailsType" />
<xsd:complexType name="mailsType">
<xsd:sequence minOccurs="0" maxOccurs="unbounded">
<xsd:element name="mail" type="mailType" />
</xsd:sequence>
</xsd:complexType>
<!-- pojedyńczy mail -->
<xsd:complexType name="mailType">
<xsd:sequence>
<xsd:element name="envelope" type="envelopeType" minOccurs="1" maxOccurs="1" />
<xsd:element name="body" type="bodyType" minOccurs="1" maxOccurs="1" />
<xsd:element name="attachment" type="attachmentType" minOccurs="0" maxOccurs="unbounded" />
</xsd:sequence>
<xsd:attribute name="ID" use="required" type="xsd:integer" />
</xsd:complexType>
<!-- inf. podstawowe (dla uproszenia pomijam możliwość wysyłki maili do większej ilości użytkowników oraz zaawansowane nagłówki) -->
<xsd:complexType name="envelopeType">
<xsd:sequence>
<xsd:element name="from" type="emailAddress" />
<xsd:element name="to" type="emailAddress" />
<xsd:element ref="date" />
<xsd:element name="subject" type="xsd:string" />
</xsd:sequence>
</xsd:complexType>
<!-- prosta walidacja adresu mailowego -->
<xsd:simpleType name="emailAddress">
<xsd:restriction base="xsd:string">
<xsd:pattern value="^[A-Za-z0-9_\.]+@[A-Za-z0-9]+\.[a-zA-Z]{2,3}$"/>
</xsd:restriction>
</xsd:simpleType>
<xsd:element name="date" type="xsd:dateTime" /> <!-- żeby nie było nudno :) -->
<!-- ciało (tutaj także pomijam m.in. możliwość wysyłki jako czysty tekst i html) -->
<xsd:simpleType name="bodyType">
<xsd:restriction base="xsd:string" />
</xsd:simpleType>
<!-- załącznik -->
<xsd:complexType name="attachmentType">
<xsd:group ref="attachmentContent" />
<xsd:attribute name="name" type="xsd:string" use="required" />
</xsd:complexType>
<!-- załącznik: typ i zawartość -->
<xsd:group name="attachmentContent">
<xsd:sequence>
<xsd:element name="mimetype">
<xsd:complexType>
<xsd:attributeGroup ref="mimeTypeAttributes" />
</xsd:complexType>
</xsd:element>
<xsd:element name="content" type="xsd:string" />
</xsd:sequence>
</xsd:group>
<!-- załącznik - MIME -->
<xsd:attributeGroup name="mimeTypeAttributes">
<xsd:attribute name="type" type="mimeTopLevelType" use="required" />
<xsd:attribute name="subtype" type="xsd:string" use="required" />
</xsd:attributeGroup>
<!-- załącznik - wyliczenie dla głównego typu MIME -->
<xsd:simpleType name="mimeTopLevelType">
<xsd:restriction base="xsd:string">
<xsd:enumeration value="text" />
<xsd:enumeration value="multipart" />
<xsd:enumeration value="application" />
<xsd:enumeration value="message" />
<xsd:enumeration value="image" />
<xsd:enumeration value="audio" />
<xsd:enumeration value="video" />
</xsd:restriction>
</xsd:simpleType>
</xsd:schema>
Przygotowałem 3 pliki - poprawny:
<?xml version="1.0" encoding="utf-8" ?>
<mails xmlns="urn:mails-schema"> <!-- określenie namespace'a krytycznie ważne -->
<mail ID="0">
<envelope>
<from>piotr.kowalski@gmail.com</from>
<to>j.nowak@o2.pl</to>
<date>2010-05-02T10:26:00Z</date>
<subject>Prezentacja - przypomnienie</subject>
</envelope>
<body>
Pamiętaj o przesłaniu mi naszej prezentacji. Pozdrawiam Piotr
</body>
</mail>
<mail ID="1">
<envelope>
<from>j.nowak@o2.pl</from>
<to>piotr.kowalski@gmail.com</to>
<date>2010-05-04T15:44:01Z</date>
<subject>Prezentacja na jutrzejsze zajęcia</subject>
</envelope>
<body>
W załączniku przesyłam prezentację na jutrzejsze spotkanie wraz z obliczeniami. Pozdrawiam Jan
</body>
<attachment name="prezentacja.ppt">
<mimetype type="application" subtype="vnd.ms-powerpoint"/>
<content>
/9j/4AAQSkZJRgABAgAAZABkAAD/7AARRHVja3kAAQAEAAAAMgAA/+4ADkFkb2JlAGTAAAAAAf/b
AIQACAYGBgYGCAYGCAwIBwgMDgoICAoOEA0NDg0N...
</content>
</attachment>
<attachment name="obliczenia.xls">
<mimetype type="application" subtype="vnd.ms-excel"/>
<content>
dk9NlNCCCMsyyPCGEYjiPmyCBwoodsEq3Poa9sj9lqLSfAw+L8MO7Vyylj1PQ5OGXiqLVPGI8Uuq
qwANeuBpNv8AP6MWdqb74Fkk8RhksUfo+galr8k6...
</content>
</attachment>
</mail>
</mails>
I dwa niepoprawne:
<?xml version="1.0" encoding="utf-8" ?>
<mails xmlns="urn:mails-schema">
<mail id="0">
<envelope>
<from>piotr.kowalski@gmail.com</from>
<to>j.nowak@poczta.onet.pl</to>
<date>2010-05-02T10:26:00Z</date>
<subject>Prezentacja - przypomnienie</subject>
</envelope>
</mail>
</mails>
<?xml version="1.0" encoding="utf-8" ?>
<mails xmlns="urn:mails-schema">
<mail ID="1" priority="high">
<envelope>
<from>j.nowak@o2.pl</from>
<to>piotr.kowalski@gmail.com</to>
<date>2010-02-31T15:44:01Z</date>
<subject>Prezentacja na jutrzejsze zajęcia</subject>
</envelope>
<body>
W załączniku przesyłam prezentację na jutrzejsze spotkanie wraz z obliczeniami. Pozdrawiam Jan
</body>
<attachment name="prezentacja.ppt">
<mimetype type="mp3" subtype="vnd.ms-powerpoint"/>
</attachment>
</mail>
</mails>
Dostęp do pliku będzie realizowany z wykorzystaniem klasy XmlSerializer, w konstruktorze której zostanie podany (de)serializowany typ. Aby wygenerować odpowiednie klasy posłużę się narzędziem XML Schema Definition (odpowiednik JAXB w Javie) - polecenie (ścieżka C:\Program Files\Microsoft Visual Studio 9.0\VC):
xsd "<dir>\App_Data\mails.xsd" /classes /outputdir:"<dir>\App_Code" Wygenerowany plik wygląda następująco:
using System.Xml.Serialization;
[System.CodeDom.Compiler.GeneratedCodeAttribute("xsd", "2.0.50727.3038")]
[System.SerializableAttribute()]
[System.Diagnostics.DebuggerStepThroughAttribute()]
[System.ComponentModel.DesignerCategoryAttribute("code")]
[System.Xml.Serialization.XmlTypeAttribute(Namespace = "urn:mails-schema")]
[System.Xml.Serialization.XmlRootAttribute("mails", Namespace = "urn:mails-schema", IsNullable = false)]
public partial class mailsType
{
private mailType[] mailField;
[System.Xml.Serialization.XmlElementAttribute("mail")]
public mailType[] mail
{
get
{
return this.mailField;
}
set
{
this.mailField = value;
}
}
}
[System.CodeDom.Compiler.GeneratedCodeAttribute("xsd", "2.0.50727.3038")]
[System.SerializableAttribute()]
[System.Diagnostics.DebuggerStepThroughAttribute()]
[System.ComponentModel.DesignerCategoryAttribute("code")]
[System.Xml.Serialization.XmlTypeAttribute(Namespace = "urn:mails-schema")]
public partial class mailType
{
private envelopeType envelopeField;
private string bodyField;
private attachmentType[] attachmentField;
private string idField;
public envelopeType envelope
{
get
{
return this.envelopeField;
}
set
{
this.envelopeField = value;
}
}
public string body
{
get
{
return this.bodyField;
}
set
{
this.bodyField = value;
}
}
[System.Xml.Serialization.XmlElementAttribute("attachment")]
public attachmentType[] attachment
{
get
{
return this.attachmentField;
}
set
{
this.attachmentField = value;
}
}
[System.Xml.Serialization.XmlAttributeAttribute(DataType = "integer")]
public string ID
{
get
{
return this.idField;
}
set
{
this.idField = value;
}
}
}
[System.CodeDom.Compiler.GeneratedCodeAttribute("xsd", "2.0.50727.3038")]
[System.SerializableAttribute()]
[System.Diagnostics.DebuggerStepThroughAttribute()]
[System.ComponentModel.DesignerCategoryAttribute("code")]
[System.Xml.Serialization.XmlTypeAttribute(Namespace = "urn:mails-schema")]
public partial class envelopeType
{
private string fromField;
private string toField;
private System.DateTime dateField;
private string subjectField;
public string from
{
get
{
return this.fromField;
}
set
{
this.fromField = value;
}
}
public string to
{
get
{
return this.toField;
}
set
{
this.toField = value;
}
}
public System.DateTime date
{
get
{
return this.dateField;
}
set
{
this.dateField = value;
}
}
public string subject
{
get
{
return this.subjectField;
}
set
{
this.subjectField = value;
}
}
}
[System.CodeDom.Compiler.GeneratedCodeAttribute("xsd", "2.0.50727.3038")]
[System.SerializableAttribute()]
[System.Diagnostics.DebuggerStepThroughAttribute()]
[System.ComponentModel.DesignerCategoryAttribute("code")]
[System.Xml.Serialization.XmlTypeAttribute(Namespace = "urn:mails-schema")]
public partial class attachmentType
{
private attachmentTypeMimetype mimetypeField;
private string contentField;
private string nameField;
public attachmentTypeMimetype mimetype
{
get
{
return this.mimetypeField;
}
set
{
this.mimetypeField = value;
}
}
public string content
{
get
{
return this.contentField;
}
set
{
this.contentField = value;
}
}
[System.Xml.Serialization.XmlAttributeAttribute()]
public string name
{
get
{
return this.nameField;
}
set
{
this.nameField = value;
}
}
}
[System.CodeDom.Compiler.GeneratedCodeAttribute("xsd", "2.0.50727.3038")]
[System.SerializableAttribute()]
[System.Diagnostics.DebuggerStepThroughAttribute()]
[System.ComponentModel.DesignerCategoryAttribute("code")]
[System.Xml.Serialization.XmlTypeAttribute(AnonymousType = true, Namespace = "urn:mails-schema")]
public partial class attachmentTypeMimetype
{
private mimeTopLevelType typeField;
private string subtypeField;
[System.Xml.Serialization.XmlAttributeAttribute()]
public mimeTopLevelType type
{
get
{
return this.typeField;
}
set
{
this.typeField = value;
}
}
[System.Xml.Serialization.XmlAttributeAttribute()]
public string subtype
{
get
{
return this.subtypeField;
}
set
{
this.subtypeField = value;
}
}
}
[System.CodeDom.Compiler.GeneratedCodeAttribute("xsd", "2.0.50727.3038")]
[System.SerializableAttribute()]
[System.Xml.Serialization.XmlTypeAttribute(Namespace = "urn:mails-schema")]
public enum mimeTopLevelType
{
text,
multipart,
application,
message,
image,
audio,
video,
}
Mamy już wszystkie części, czas więc na kodowanie. Zdecydowałem się na wykorzystanie niemal wyłącznie kontrolek DevExpressa (oprócz Ajaxa i Multiview). Kontrolkę ASPxTreeList można podpiąć bezpośrednio pod plik XML - można obejrzeć demo pod: http://demos.devexpress.com/ASPxTreeListDemos/Data/Hierarchical.aspx. Markup strony przedstawia się następująco:
<%@ Page Language="C#" AutoEventWireup="true" CodeFile="Default.aspx.cs" Inherits="_Default" %>
<%@ Register Assembly="DevExpress.Web.ASPxTreeList.v9.1, Version=9.1.2.0, Culture=neutral, PublicKeyToken=b88d1754d700e49a"
Namespace="DevExpress.Web.ASPxTreeList" TagPrefix="dxwtl" %>
<%@ Register Assembly="DevExpress.Web.ASPxEditors.v9.1, Version=9.1.2.0, Culture=neutral, PublicKeyToken=b88d1754d700e49a"
Namespace="DevExpress.Web.ASPxEditors" TagPrefix="dxe" %>
<%@ Register Assembly="DevExpress.Web.v9.1, Version=9.1.2.0, Culture=neutral, PublicKeyToken=b88d1754d700e49a"
Namespace="DevExpress.Web.ASPxRoundPanel" TagPrefix="dxrp" %>
<%@ Register Assembly="DevExpress.Web.v9.1, Version=9.1.2.0, Culture=neutral, PublicKeyToken=b88d1754d700e49a"
Namespace="DevExpress.Web.ASPxPanel" TagPrefix="dxp" %>
<%@ Register Assembly="DevExpress.Web.v9.1, Version=9.1.2.0, Culture=neutral, PublicKeyToken=b88d1754d700e49a"
Namespace="DevExpress.Web.ASPxHiddenField" TagPrefix="dxhf" %>
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
<html xmlns="http://www.w3.org/1999/xhtml">
<head runat="server">
<title>Przeglądarka plików XML</title>
<style type="text/css">
.bulletedList
{
font: 9pt Tahoma;
color: Black;
}
#lblError
{
font: 9pt Tahoma;
color: Red;
font-weight: bold;
}
</style>
</head>
<body>
<form id="form1" runat="server">
<asp:ScriptManager ID="ScriptManager1" runat="server">
</asp:ScriptManager>
<asp:UpdatePanel ID="UpdatePanel1" runat="server">
<ContentTemplate>
<br />
<br />
<table>
<tr>
<td>
<dxe:ASPxLabel ID="lblAvailableFiles" runat="server" Text="Dostępne pliki XML:">
</dxe:ASPxLabel>
</td>
<td>
<dxe:ASPxComboBox ID="cmbAvailableFilesList" runat="server" EnableIncrementalFiltering="True"
AutoPostBack="True" OnSelectedIndexChanged="cmbAvailableFilesList_SelectedIndexChanged"
ValueType="System.String">
</dxe:ASPxComboBox>
</td>
<td>
<dxe:ASPxCheckBox ID="chkShowContent" runat="server" Text="Razem z zawartością" AutoPostBack="True"
OnCheckedChanged="chkShowContent_CheckedChanged">
<ClientSideEvents CheckedChanged="function(s, e) {
var isValid = hiddenFieldIsValidClient.Get("isValid");
if(isValid == false)
{
alert('Plik XML nie jest poprawny');
s.SetChecked(!s.GetChecked()); /* najprościej i najlepiej byłoby zablokować kontrolkę, ale chcę pokazać możliwości kontrolek DevExpressa, stąd takie dziwaczne konstrukcje :) */
e.processOnServer = false;
} }" />
</dxe:ASPxCheckBox>
</td>
<td>
<dxe:ASPxLabel ID="lblError" runat="server" EnableViewState="false">
</dxe:ASPxLabel>
</td>
</tr>
</table>
<asp:MultiView ID="multiViewTreeErrors" runat="server">
<asp:View ID="viewTree" runat="server">
<!-- KONIECZNE WYŁĄCZENIE DataCacheMode - inaczej 'Object reference not set to an instance of an object.' -->
<dxwtl:ASPxTreeList ID="treeLstXmlTree" runat="server" AutoGenerateColumns="False"
ClientInstanceName="treeLstXmlTreeClient" Width="100%"
OnCustomDataCallback="treeLstXmlTree_CustomDataCallback"
DataCacheMode="Disabled">
<Styles>
<FocusedNode BackColor="#99CCFF">
</FocusedNode>
</Styles>
<Settings ShowColumnHeaders="False" />
<SettingsBehavior AutoExpandAllNodes="True" AllowFocusedNode="True" ProcessFocusedNodeChangedOnServer="False" />
<Columns>
<dxwtl:TreeListTextColumn FieldName="value">
</dxwtl:TreeListTextColumn>
</Columns>
<ClientSideEvents CustomDataCallback="function(s, e) { document.getElementById('contentText').innerHTML = e.result; }"
FocusedNodeChanged="function(s, e) {
var key = treeLstXmlTreeClient.GetFocusedNodeKey();
treeLstXmlTreeClient.PerformCustomDataCallback(key); }" />
</dxwtl:ASPxTreeList>
<dxrp:ASPxRoundPanel ID="rndPnlContent" runat="server" HeaderText="Zawartość" Width="100%">
<PanelCollection>
<dxp:PanelContent runat="server">
<span id="contentText">
<asp:Literal ID="ltrContent" runat="server" Text="(nie wybrano)"></asp:Literal></span>
</dxp:PanelContent>
</PanelCollection>
</dxrp:ASPxRoundPanel>
</asp:View>
<asp:View ID="viewErrors" runat="server">
<asp:BulletedList ID="bulletedListValidationErrors" runat="server" BulletStyle="Numbered"
CssClass="bulletedList">
</asp:BulletedList>
</asp:View>
</asp:MultiView>
<dxhf:ASPxHiddenField ID="hiddenFieldIsValid" ClientInstanceName="hiddenFieldIsValidClient"
runat="server">
</dxhf:ASPxHiddenField>
</ContentTemplate>
</asp:UpdatePanel>
</form>
</body>
</html>
Na co warto zwrócić uwagę:
- ASPxComboBox pozwala na inkrementacyjne wyszukiwanie i tak jak standardowa kontrolka wykonuje postback do serwera przy zmianie wybranej opcji,
- dziwna konstrukcja w ASPxCheckBox pokazuje jak wykorzystać ASPxHiddenField jako most pomiędzy serwerem a klientem i na odwrót,
- wykorzystałem standardowe kontroli: MultiView (uwalnia od żonglowania oddzielnymi panelami) i BulletedList (nie ma potrzeby konstruowania listy i przypisywania jej np. do kontrolki Literal),
- w ASPxTreeList zastosowałem mechanizm callback do pobierania wartości tagu (dzięki temu nie trzeba wykonywać “pełnego” postbacku strony). Podczas wdrożenia “produkcyjnego” napotkałem na błąd wyskakujący przy wybraniu węzła: 'Object reference not set to an instance of an object.’, który co ciekawe nie występował lokalnie. Po wykorzystaniu ELMAH znalazłem przyczynę - błąd serializacji z TypeConverterem w tle. Sprawdziłem jeszcze raz lokalnie i wszystko działało. Po spytaniu Google okazało się, że podobne błędy występują m.in. z ASPxGridView - wystarczyło wyłączyć cache’owanie danych (EnableRowsCache, u mnie DataCacheMode) i pomogło :)
Code-behind:
using System;
using System.IO;
using System.Xml;
using System.Xml.Schema;
using System.Xml.Serialization;
using DevExpress.Web.ASPxTreeList;
using System.Collections.Specialized;
public partial class _Default : System.Web.UI.Page
{
protected void Page_Load(object sender, EventArgs e)
{
if (!this.IsPostBack)
{
DirectoryInfo dirInfo = new DirectoryInfo(this.Server.MapPath("~/App_Data/"));
foreach (FileInfo fi in dirInfo.GetFiles("*.xml"))
{
cmbAvailableFilesList.Items.Add(fi.Name, fi.Name); // ew.: cmbMailsList.Items.Add(fi.Name, fi.FullName); całego obiektu fi nie można przechowywać
}
if (cmbAvailableFilesList.Items.Count > 0)
{
cmbAvailableFilesList.SelectedIndex = 0;
validateAndShowSelectedFile();
}
}
else
{
if (ViewState["mails"] != null)
{
generateTreeView((mailsType)ViewState["mails"]);
}
}
}
private void validateAndShowSelectedFile()
{
XmlReader xmlRdrSchema = null, xmlRdrFile = null;
FileStream fs = null;
try
{
string physicalPath = this.Server.MapPath("~/App_Data/");
xmlRdrSchema = XmlReader.Create(physicalPath + "mails.xsd");
XmlSchemaSet xmlSchemaSet = new XmlSchemaSet();
xmlSchemaSet.Add("urn:mails-schema", xmlRdrSchema);
XmlReaderSettings xmlRdrSettings = new XmlReaderSettings();
xmlRdrSettings.ValidationType = ValidationType.Schema;
xmlRdrSettings.Schemas = xmlSchemaSet;
xmlRdrSettings.ValidationEventHandler += new ValidationEventHandler(ValidationCallBack);
bulletedListValidationErrors.Items.Clear();
xmlRdrFile = XmlReader.Create(physicalPath + cmbAvailableFilesList.SelectedItem.Value, xmlRdrSettings);
while (xmlRdrFile.Read()) // wymuszenie walidacji
;
if (bulletedListValidationErrors.Items.Count > 0)
{
multiViewTreeErrors.ActiveViewIndex = 1;
ViewState["mails"] = null;
hiddenFieldIsValid["isValid"] = false;
}
else
{
multiViewTreeErrors.ActiveViewIndex = 0;
XmlSerializer xmlSerializer = new XmlSerializer(typeof(mailsType));
// nie mogę ponownie wykorzystać XmlReader - provides fast, non-cached, forward-only access to XML data
fs = new FileStream(physicalPath + cmbAvailableFilesList.SelectedItem.Value, FileMode.Open, FileAccess.Read);
mailsType mails = (mailsType)xmlSerializer.Deserialize(fs);
generateTreeView(mails);
ViewState["mails"] = mails; // w profesjonalnych zastosowaniach korzystaj z prefixów, żeby nie nadpisać przez nieuwagę innych wartości; tutaj następuje proces serializacji
hiddenFieldIsValid["isValid"] = true;
}
}
catch (XmlException ex)
{
lblError.Text = "Wystąpił błąd: " + ex.Message;
}
catch (Exception ex)
{
lblError.Text = "Wystąpił błąd: " + ex.GetType().ToString();
}
finally
{
if (xmlRdrSchema != null)
{
xmlRdrSchema.Close();
}
if (xmlRdrFile != null)
{
xmlRdrFile.Close();
}
if (fs != null)
{
fs.Close();
}
}
}
private void ValidationCallBack(object sender, ValidationEventArgs e)
{
bulletedListValidationErrors.Items.Add(e.Message);
}
private void generateTreeView(mailsType mails)
{
treeLstXmlTree.ClearNodes();
for (int i = 0; i < mails.mail.Length; i++)
{
NameValueCollection attrs = new NameValueCollection();
attrs.Add("ID", mails.mail[i].ID);
TreeListNode mail = createNode(new nodeType("mail", attrs, null), null);
TreeListNode envelope = createNode(new nodeType("envelope", null, null), mail);
createNode(new nodeType("from", null, mails.mail[i].envelope.from), envelope);
createNode(new nodeType("to", null, mails.mail[i].envelope.to), envelope);
createNode(new nodeType("date", null, mails.mail[i].envelope.date.ToString()), envelope);
createNode(new nodeType("subject", null, mails.mail[i].envelope.subject), envelope);
createNode(new nodeType("body", null, mails.mail[i].body), mail);
if (mails.mail[i].attachment != null)
{
for (int j = 0; j < mails.mail[i].attachment.Length; j++)
{
attrs = new NameValueCollection();
attrs.Add("name", mails.mail[i].attachment[j].name);
TreeListNode attachment = createNode(new nodeType("attachment", attrs, null), mail);
attrs = new NameValueCollection();
attrs.Add("type", mails.mail[i].attachment[j].mimetype.type.ToString());
attrs.Add("subtype", mails.mail[i].attachment[j].mimetype.subtype);
createNode(new nodeType("mimetype", attrs, null), attachment);
createNode(new nodeType("content", null, mails.mail[i].attachment[j].content), attachment);
}
}
}
}
#region helper members
private int keySeq = 0;
private TreeListNode createNode(nodeType node, TreeListNode parentNode)
{
TreeListNode treeLstNode = treeLstXmlTree.AppendNode(keySeq++, parentNode);
treeLstNode["value"] = node;
return treeLstNode;
}
[Serializable()] // ponieważ między postbackami przechowuję drzewo w ViewState
private class nodeType
{
private string nodeTag, nodeContent;
private NameValueCollection nodeAttributes;
internal static bool isChecked = false; // nie może być protected
public nodeType(string nodeTag, NameValueCollection nodeAttributes, string nodeContent)
{
this.nodeTag = nodeTag;
this.nodeAttributes = nodeAttributes;
this.nodeContent = nodeContent;
}
public string NodeContent
{
get
{
return nodeContent;
}
}
public override string ToString()
{
string attrsConcat = String.Empty;
if (nodeAttributes != null)
{
for (int i = 0; i < nodeAttributes.Count; i++)
{
attrsConcat += nodeAttributes.Keys[i] + "=" + nodeAttributes[i];
if (i < nodeAttributes.Count - 1)
{
attrsConcat += ", ";
}
}
}
if (attrsConcat == String.Empty)
{
return nodeTag + ((isChecked == true && nodeContent != null) ? (" = " + nodeContent) : "");
}
else
{
return nodeTag + " (" + attrsConcat + ")" + ((isChecked == true && nodeContent != null) ? (" = " + nodeContent) : "");
}
}
}
#endregion
protected void cmbAvailableFilesList_SelectedIndexChanged(object sender, EventArgs e)
{
validateAndShowSelectedFile();
}
protected void chkShowContent_CheckedChanged(object sender, EventArgs e)
{
nodeType.isChecked = chkShowContent.Checked;
}
protected void treeLstXmlTree_CustomDataCallback(object sender, TreeListCustomDataCallbackEventArgs e)
{
string key = e.Argument.ToString();
TreeListNode node = treeLstXmlTree.FindNodeByKeyValue(key);
string result = ((nodeType)node.GetValue("value")).NodeContent;
e.Result = (result == null ? "(brak)" : result);
}
}
Proces deserializacji ogranicza się do zaznaczonych 3 linijek. Oczywiście dokument można modyfikować i w równie łatwy sposób serializować z powrotem do XML-a. Działającą stronę można zobaczyć tutaj.