TheseusClassの詳細
Theseusの動作をさらに理解してもらうために、TheseusClassのTreeViewコントロール(サイドメニュー)関連のコードを詳しく説明します。前述のとおり、SideMenu.ascxユーザーコントロールは、TreeViewコントロールを使ってサイドメニューを表示します。このコントロールのPage_Loadイベントハンドラは、要求されたページに必要なノードをTreeViewにロードするために、TheseusClassのLoadSideMenuメソッドを呼び出します。このメソッドには、ユーザーコントロールのマークアップ部分で定義されているSiteMapDataSource(dsMenu3)とTreeViewコントロール(tvMenu3)の両方を渡します。
private TheseusClass Theseus;
protected void Page_Load(object sender, EventArgs e)
{
Theseus = ((TheseusPage)Page).Theseus;
Theseus.LoadSideMenu(dsMenu3, tvMenu3);
// do dah
}
LoadSideMenuメソッドは、まず、セクションメニューの選択ノードの参照を取得し、変数node2に代入します(サイドメニューは、セクションメニューから選択されたSiteMapNodeの下位ノードを表示することを思い出してください)。この選択されたセクションメニューのSiteMapNodeは、SiteMapExtensionクラスのCurrentBranchNodeメソッドにSectionMenuLevel変数を渡して呼び出すことによって取得します。SectionMenuLevel変数は、セクションメニューに表示された項目がサイトマップ階層内のどのレベルであるかを示す整数値です。このサンプルでは、この値は2に設定されていますが、TheseusClassのコンストラクタでカスタマイズできます。
public void LoadSideMenu(SiteMapDataSource dsMenu3, TreeView tv)
{
// I get a reference to the Section Menu level node on the Current Branch
// as this provides all the parameters I need to setup the Side Menu
SiteMapNode node2 = SiteMapExtender.CurrentBranchNode(SectionMenuLevel);
...
}
選択されたセクションメニューノードを取得したら、次はそのノードがnullかどうかを調べます。これは、選択されたサイトタブが葉ノードのとき、nullになります。例えば、要求されたページがホームページの場合、node2はnullになります。node2がnullでない場合は、サイドメニューのTreeViewコントロール(tvパラメータ)を表示し、適切な深さまで展開してnode2の下位ノードを表示します。
public void LoadSideMenu(SiteMapDataSource dsMenu3, TreeView tv)
{
...
bool bVisible = false;
// Always check the value returned by CurrentBranchNode for null!
if (node2 != null)
{
if (node2.HasChildNodes)
{
dsMenu3.StartingNodeUrl = node2.Url;
tv.ExpandDepth = SiteMapExtender.CurrentNodeLevel;
bVisible = true;
}
}
// unless the Side Menu has nodes, hide it!
tv.Visible = bVisible;
}
SiteMapDataSourceコントロールdsMenu3のStartingNodeUrlプロパティは、選択されたセクションメニューノードのURL(node2.Url)に設定します。SiteMapDataSourceのShowStartingNodeプロパティはユーザーコントロールのマークアップでfalseに設定されているので、node2の子ノードのみが返されます。
TreeViewのExpandDepthプロパティは、既定で展開されるTreeViewノードのレベル数を示します。この値には、サイトマップ階層における現在のノードのレベル(SiteMapExtensionsクラスのCurrentNodeLevelプロパティで取得)を指定します。サイドメニューのロードに必要な作業はこれで全部です。ただし、サイドメニューの表示に必要な重要な作業が残っています。現在位置情報に基づいて、TreeViewの適切なノードを強調表示することです。
TreeViewのノードの強調表示は、このコントロールにデータをバインドした後でなければできません。TreeViewにデータがバインドされると、TreeViewのDataBoundイベントが発生します。従って、DataBoundイベントにイベントハンドラを作成し、そこにTreeViewのノードを選択するコードを記述するのが適しています。このイベントハンドラは、SideMenu.ascxユーザーコントロールで定義しており、TheseusClassのSetNodeSideMenuメソッドにTreeViewコントロールを渡して呼び出すだけです。
protected void SideMenu_DataBound(object sender, EventArgs e)
{
Theseus.SetNodeSideMenu((TreeView)sender);
}
SetNodeSideMenuメソッドはまず、TreeViewの全ノードを閉じます。これは、現在位置情報に基づくノードのみが展開表示されるようにするためです。次に、TreeViewのノードが選択されているかを確認し、選択されている場合はそのノードを展開します。選択されていない場合は、SiteMap.CurrentNodeを表すTreeNodeが選択されます。最後に、選択ノードの親ノードを展開し、表示ページに選択ノードが表示されるようにします。
SetNodeSideMenuメソッドは次のようになっています。
public void SetNodeSideMenu(TreeView tv)
{
tv.CollapseAll();
// If a node is already selected, expand it!
if (tv.SelectedNode != null)
tv.SelectedNode.Expand();
else
SetSelectedTreeNode(tv);
// Now that I have my selected mode,
// I expand the nodes along the Current Branch.
// TreeView's Expanded property is set to true!
ExpandSideMenu(tv);
}
最も一般的なシナリオは、ノードが選択されていない場合です。この場合は、SetSelectedTreeNodeメソッドが呼び出されます。このSetSelectedTreeNodeメソッドは、SiteMapNodeオブジェクトSiteMap.CurrentNodeを表すTreeNodeを選択します。このコードの複雑なところは、SiteMap.CurrentNodeをTreeNodeの1つに割り当てる点です。TreeViewのFindNodeメソッドを使用することによって、現在のノードのValuePathを作成するだけでこれを実現できます。
private void SetSelectedTreeNode(TreeView tv)
{
if (SiteMap.CurrentNode != null)
{
// As there is more than one potential level to search for the node I want,
// I use the property CurrentNodeValuePath to calculate the ValuePath
TreeNode curr = tv.FindNode(SiteMapExtender.CurrentNodeValuePath(SideMenuLevel));
if (curr != null)
curr.Select();
}
}
よく見ると、このコードは現在のノードを選択しているだけだと気付くでしょう。では、現在位置情報に連なる他のノードはどうやって太字表示にしているのでしょうか。実は、何もしなくてよいのです。CSS Friendly Adaptersを使用することによって、現在位置情報を表すノードすべてに、AspNet-TreeView-ChildSelectedというCSSクラスが指定されます。従って、C#コードを一切書かなくても、CSS定義でこのような表示指定を設定できます。詳細については、~/App_Themes/ws/SideMenu.cssファイル内のCSSを参照ください。
CurrentNodeのValuePathは、SiteMapExtenderオブジェクトのCurrentBranchプロパティから作成できるのでとても簡単です。これを行うコードは、SiteMapExtensionsクラスのCurrentNodeValuePathメソッドに含まれています。
public string CurrentNodeValuePath(int startFromLevel)
{
string s = string.Empty;
List<SiteMapNode> ln = CurrentBranch;
if (ln.Count > 0)
{
for (int i = startFromLevel; i == CurrentNodeLevel; i++)
s = s + ln[i].Title + "/";
}
return s;
}
CurrentNodeValuePathのコードはとても単純で、気をつけねばならないのは、startFromLevelパラメータを正確に処理してValuePathを確実に正しい位置から計算することだけです。
後はExpandSideMenuメソッドのコードだけです。このメソッドは、CurrentNodeから始めて、TreeNodeオブジェクトのParentプロパティを使用してそこからツリーをさかのぼり、それぞれの親ノードを展開していきます。
private void ExpandSideMenu(TreeView tv)
{
TreeNode tn = tv.SelectedNode;
if (tn != null)
{
while(tn.Parent != null)
{
tn.Parent.Expanded = true;
tn = tn.Parent;
}
}
}
