1 //------------------------------------------------------------------------------
2 // <copyright file="MenuAdapter.cs" company="Microsoft">
3 // Copyright (c) Microsoft Corporation. All rights reserved.
5 //------------------------------------------------------------------------------
7 namespace System.Web.UI.WebControls.Adapters {
10 using System.Diagnostics;
11 using System.Globalization;
15 using System.Web.UI.HtmlControls;
16 using System.Web.UI.WebControls;
18 public class MenuAdapter : WebControlAdapter, IPostBackEventHandler {
21 private Panel _menuPanel;
22 private int _currentAccessKey = 0;
23 private MenuItem _titleItem;
25 protected new Menu Control {
27 return (Menu) base.Control;
31 protected internal override void LoadAdapterControlState(Object state) {
33 Pair pairState = state as Pair;
34 if (pairState != null) {
35 base.LoadAdapterViewState(pairState.First);
36 _path = (string)pairState.Second;
39 base.LoadAdapterViewState(null);
40 _path = state as string;
45 private string Escape(string path) {
46 // This function escapes \\ so that they don't get replaced because of
49 StringBuilder b = null;
51 if (String.IsNullOrEmpty(path)) {
57 for (int i = 0; i < path.Length; i++) {
61 if (i + 1 < path.Length && path[i + 1] == '\\') {
63 b = new StringBuilder(path.Length + 5);
66 b.Append(path, startIndex, count);
79 b = new StringBuilder(path.Length + 5);
83 b.Append(path, startIndex, count);
102 b.Append(path, startIndex, count);
108 private string UnEscape(string path) {
109 return path.Replace(@"\\", @"\").Replace(@"\_\", @"\\").Replace("__", "_");
112 protected internal override void OnInit(EventArgs e) {
114 Control.Page.RegisterRequiresControlState(Control);
117 protected internal override void OnPreRender(EventArgs e) {
118 Control.OnPreRender(e, false);
121 protected internal override object SaveAdapterControlState() {
122 object baseState = base.SaveAdapterViewState();
124 if (baseState == null) {
133 return new Pair(baseState, _path);
137 private void RenderBreak(HtmlTextWriter writer) {
138 if (Control.Orientation == Orientation.Vertical) {
146 protected override void RenderBeginTag(HtmlTextWriter writer) {
147 Menu owner = Control;
149 if (owner.SkipLinkText.Length != 0) {
150 HyperLink skipLink = new HyperLink();
151 skipLink.NavigateUrl = '#' + owner.ClientID + "_SkipLink";
152 skipLink.ImageUrl = owner.SpacerImageUrl;
153 skipLink.Text = owner.SkipLinkText;
154 skipLink.Height = Unit.Pixel(1);
155 skipLink.Width = Unit.Pixel(1);
156 skipLink.Page = Page;
157 skipLink.RenderControl(writer);
159 _menuPanel = new Panel();
160 _menuPanel.ID = owner.UniqueID;
161 _menuPanel.Page = Page;
162 // Determine root menu style
165 titleItem = owner.Items.FindItem(_path.Split(TreeView.InternalPathSeparator), 0);
166 _titleItem = titleItem;
169 titleItem = owner.RootItem;
171 SubMenuStyle rootMenuStyle = owner.GetSubMenuStyle(titleItem);
172 if (!rootMenuStyle.IsEmpty) {
173 if (Page != null && Page.SupportsStyleSheets) {
174 string styleClass = owner.GetSubMenuCssClassName(titleItem);
175 if (styleClass.Trim().Length > 0) {
176 _menuPanel.CssClass = styleClass;
180 _menuPanel.ApplyStyle(rootMenuStyle);
183 _menuPanel.Width = owner.Width;
184 _menuPanel.Height = owner.Height;
185 _menuPanel.Enabled = owner.IsEnabled;
186 _menuPanel.RenderBeginTag(writer);
189 protected override void RenderContents(HtmlTextWriter writer) {
190 Menu owner = Control;
192 if (_titleItem != null) {
193 if (_titleItem.Depth + 1 >= owner.MaximumDepth) {
194 throw new InvalidOperationException(SR.GetString(SR.Menu_InvalidDepth));
196 if (!_titleItem.IsEnabled) {
197 throw new InvalidOperationException(SR.GetString(SR.Menu_InvalidNavigation));
199 RenderItem(writer, _titleItem, position++);
200 foreach (MenuItem child in _titleItem.ChildItems) {
201 RenderItem(writer, child, position++);
203 if (PageAdapter != null) {
204 PageAdapter.RenderPostBackEvent(writer,
207 SR.GetString(SR.MenuAdapter_Up),
208 SR.GetString(SR.MenuAdapter_UpOneLevel));
211 HyperLink link = new HyperLink();
212 link.NavigateUrl = Page.ClientScript.GetPostBackClientHyperlink(owner, "u");
213 link.Text = SR.GetString(SR.MenuAdapter_UpOneLevel);
215 link.RenderControl(writer);
223 foreach(MenuItem child in owner.Items) {
224 RenderItem(writer, child, position++);
225 if (owner.StaticDisplayLevels > 1 && child.ChildItems.Count > 0) {
226 RenderContentsRecursive(writer, child, 1, owner.StaticDisplayLevels);
231 private void RenderContentsRecursive(HtmlTextWriter writer, MenuItem parentItem, int depth, int maxDepth) {
233 foreach(MenuItem child in parentItem.ChildItems) {
234 RenderItem(writer, child, position++);
235 if (depth + 1 < maxDepth && child.ChildItems.Count > 0) {
236 RenderContentsRecursive(writer, child, depth + 1, maxDepth);
241 protected override void RenderEndTag(HtmlTextWriter writer) {
242 _menuPanel.RenderEndTag(writer);
244 if (Control.SkipLinkText.Length != 0) {
245 HtmlAnchor skipAnchor = new HtmlAnchor();
246 skipAnchor.Name = Control.ClientID + "_SkipLink";
247 skipAnchor.Page = Page;
248 skipAnchor.RenderControl(writer);
252 private void RenderExpand(HtmlTextWriter writer, MenuItem item, Menu owner) {
253 string expandImageUrl = item.GetExpandImageUrl();
254 if (expandImageUrl.Length > 0) {
255 Image expandImage = new Image();
256 expandImage.ImageUrl = expandImageUrl;
257 expandImage.GenerateEmptyAlternateText = true;
258 if (item.Depth < owner.StaticDisplayLevels) {
260 expandImage.AlternateText = String.Format(
261 CultureInfo.CurrentCulture,
262 owner.StaticPopOutImageTextFormatString,
267 expandImage.AlternateText = String.Format(
268 CultureInfo.CurrentCulture,
269 owner.DynamicPopOutImageTextFormatString,
272 // expandImage.ImageAlign = ImageAlign.Right;
273 expandImage.ImageAlign = ImageAlign.AbsMiddle;
274 expandImage.Page = Page;
275 expandImage.RenderControl(writer);
279 if (item.Depth < owner.StaticDisplayLevels &&
280 owner.StaticPopOutImageTextFormatString.Length != 0) {
282 writer.Write(HttpUtility.HtmlEncode(String.Format(
283 CultureInfo.CurrentCulture,
284 owner.StaticPopOutImageTextFormatString,
287 else if (item.Depth >= owner.StaticDisplayLevels &&
288 owner.DynamicPopOutImageTextFormatString.Length != 0) {
290 writer.Write(HttpUtility.HtmlEncode(String.Format(
291 CultureInfo.CurrentCulture,
292 owner.DynamicPopOutImageTextFormatString,
296 writer.Write(HttpUtility.HtmlEncode(SR.GetString(SR.MenuAdapter_Expand, item.Text)));
301 protected virtual internal void RenderItem(HtmlTextWriter writer, MenuItem item, int position) {
302 Menu owner = Control;
303 MenuItemStyle mergedStyle = owner.GetMenuItemStyle(item);
305 string imageUrl = item.ImageUrl;
306 int depth = item.Depth;
307 int depthPlusOne = depth + 1;
308 string toolTip = item.ToolTip;
309 string navigateUrl = item.NavigateUrl;
310 string text = item.Text;
311 bool enabled = item.IsEnabled;
312 bool selectable = item.Selectable;
313 MenuItemCollection childItems = item.ChildItems;
316 string topSeparatorUrl = null;
317 if (depth < owner.StaticDisplayLevels && owner.StaticTopSeparatorImageUrl.Length != 0) {
318 topSeparatorUrl = owner.StaticTopSeparatorImageUrl;
320 else if (depth >= owner.StaticDisplayLevels && owner.DynamicTopSeparatorImageUrl.Length != 0) {
321 topSeparatorUrl = owner.DynamicTopSeparatorImageUrl;
323 if (topSeparatorUrl != null) {
324 Image separatorImage = new Image();
325 separatorImage.ImageUrl = topSeparatorUrl;
326 separatorImage.GenerateEmptyAlternateText = true; // XHtml compliance
327 separatorImage.Page = Page;
328 separatorImage.RenderControl(writer);
332 // Don't render the top spacing if this is the first root item
333 if ((mergedStyle != null) && !mergedStyle.ItemSpacing.IsEmpty &&
334 ((_titleItem != null) || (position != 0))) {
335 RenderSpace(writer, mergedStyle.ItemSpacing, owner.Orientation);
339 Panel itemPanel = new SpanPanel();
340 itemPanel.Enabled = enabled;
341 itemPanel.Page = Page;
344 if (Page != null && Page.SupportsStyleSheets) {
345 string styleClass = owner.GetCssClassName(item, false);
346 if (styleClass.Trim().Length > 0) {
347 itemPanel.CssClass = styleClass;
350 else if (mergedStyle != null) {
351 itemPanel.ApplyStyle(mergedStyle);
355 if (item.ToolTip.Length != 0) {
356 itemPanel.ToolTip = item.ToolTip;
359 // Render item begin tag
360 itemPanel.RenderBeginTag(writer);
362 // If there is a navigation url on this item, set up the navigation stuff if:
363 // - the item is the current title item for this level
364 // - the item has no children
365 // - the item is a static item of depth + 1 < StaticDisplayLevels
366 bool clickOpensThisNode = !((position == 0) ||
367 (childItems.Count == 0) ||
368 (depthPlusOne < owner.StaticDisplayLevels) ||
369 (depthPlusOne >= owner.MaximumDepth));
374 owner.StaticSubMenuIndent != Unit.Pixel(0) &&
375 depth < owner.StaticDisplayLevels) {
376 Image spacerImage = new Image();
377 spacerImage.ImageUrl = owner.SpacerImageUrl;
378 spacerImage.GenerateEmptyAlternateText = true; // XHtml compliance
379 double indent = owner.StaticSubMenuIndent.Value * depth;
380 if (indent < Unit.MaxValue) {
381 spacerImage.Width = new Unit(indent, owner.StaticSubMenuIndent.Type);
384 spacerImage.Width = new Unit(Unit.MaxValue, owner.StaticSubMenuIndent.Type);;
386 spacerImage.Height = Unit.Pixel(1);
387 spacerImage.Page = Page;
388 spacerImage.RenderControl(writer);
391 // Render out the item icon, if it is set and if the item is not templated (VSWhidbey 501618)
392 if (imageUrl.Length > 0 && item.NotTemplated()) {
393 Image newImage = new Image();
394 newImage.ImageUrl = imageUrl;
395 if (toolTip.Length != 0) {
396 newImage.AlternateText = toolTip;
399 newImage.GenerateEmptyAlternateText = true; // XHtml compliance
401 newImage.Page = Page;
402 newImage.RenderControl(writer);
406 bool applyInlineBorder;
408 if (Page != null && Page.SupportsStyleSheets) {
409 linkClass = owner.GetCssClassName(item, true, out applyInlineBorder);
412 linkClass = String.Empty;
413 applyInlineBorder = false;
415 if (enabled && (clickOpensThisNode || selectable)) {
416 string accessKey = owner.AccessKey;
417 string itemAccessKey = ((position == 0 || (position == 1 && depth == 0)) && accessKey.Length != 0) ?
420 if (navigateUrl.Length > 0 && !clickOpensThisNode) {
421 if (PageAdapter != null) {
422 PageAdapter.RenderBeginHyperlink(writer,
423 owner.ResolveClientUrl(navigateUrl),
425 SR.GetString(SR.Adapter_GoLabel),
426 itemAccessKey != null ?
428 (_currentAccessKey < 10 ?
429 (_currentAccessKey++).ToString(CultureInfo.InvariantCulture) :
431 writer.Write(HttpUtility.HtmlEncode(item.FormattedText));
432 PageAdapter.RenderEndHyperlink(writer);
435 HyperLink link = new HyperLink();
436 link.NavigateUrl = owner.ResolveClientUrl(navigateUrl);
437 string target = item.Target;
438 if (String.IsNullOrEmpty(target)) {
439 target = owner.Target;
441 if (!String.IsNullOrEmpty(target)) {
442 link.Target = target;
444 link.AccessKey = itemAccessKey;
446 if (writer is Html32TextWriter) {
447 link.RenderBeginTag(writer);
448 SpanPanel lbl = new SpanPanel();
450 RenderStyle(writer, lbl, linkClass, mergedStyle, applyInlineBorder);
451 lbl.RenderBeginTag(writer);
452 item.RenderText(writer);
453 lbl.RenderEndTag(writer);
454 link.RenderEndTag(writer);
457 RenderStyle(writer, link, linkClass, mergedStyle, applyInlineBorder);
458 link.RenderBeginTag(writer);
459 item.RenderText(writer);
460 link.RenderEndTag(writer);
464 // Otherwise, write out a postback that will open or select the item
466 if (PageAdapter != null) {
467 PageAdapter.RenderPostBackEvent(writer,
469 (clickOpensThisNode ? 'o' : 'b') +
470 Escape(item.InternalValuePath),
471 SR.GetString(SR.Adapter_OKLabel),
474 itemAccessKey != null ?
476 (_currentAccessKey < 10 ?
477 (_currentAccessKey++).ToString(CultureInfo.InvariantCulture) :
481 if (clickOpensThisNode) {
482 RenderExpand(writer, item, owner);
486 HyperLink link = new HyperLink();
487 link.NavigateUrl = Page.ClientScript.GetPostBackClientHyperlink(owner,
488 (clickOpensThisNode ? 'o' : 'b') + Escape(item.InternalValuePath), true);
489 link.AccessKey = itemAccessKey;
491 if (writer is Html32TextWriter) {
492 link.RenderBeginTag(writer);
493 SpanPanel lbl = new SpanPanel();
495 RenderStyle(writer, lbl, linkClass, mergedStyle, applyInlineBorder);
496 lbl.RenderBeginTag(writer);
497 item.RenderText(writer);
498 if (clickOpensThisNode) {
499 RenderExpand(writer, item, owner);
501 lbl.RenderEndTag(writer);
502 link.RenderEndTag(writer);
505 RenderStyle(writer, link, linkClass, mergedStyle, applyInlineBorder);
506 link.RenderBeginTag(writer);
507 item.RenderText(writer);
508 if (clickOpensThisNode) {
509 RenderExpand(writer, item, owner);
511 link.RenderEndTag(writer);
517 item.RenderText(writer);
519 itemPanel.RenderEndTag(writer);
521 // Bottom (or right) item spacing
523 if ((mergedStyle != null) && !mergedStyle.ItemSpacing.IsEmpty) {
524 RenderSpace(writer, mergedStyle.ItemSpacing, owner.Orientation);
528 string bottomSeparatorUrl = null;
529 if (item.SeparatorImageUrl.Length != 0) {
530 bottomSeparatorUrl = item.SeparatorImageUrl;
532 else if ((depth < owner.StaticDisplayLevels) && (owner.StaticBottomSeparatorImageUrl.Length != 0)) {
533 bottomSeparatorUrl = owner.StaticBottomSeparatorImageUrl;
535 else if ((depth >= owner.StaticDisplayLevels) && (owner.DynamicBottomSeparatorImageUrl.Length != 0)) {
536 bottomSeparatorUrl = owner.DynamicBottomSeparatorImageUrl;
538 if (bottomSeparatorUrl != null) {
539 Image separatorImage = new Image();
540 separatorImage.ImageUrl = bottomSeparatorUrl;
541 separatorImage.GenerateEmptyAlternateText = true; // XHtml compliance
542 separatorImage.Page = Page;
543 separatorImage.RenderControl(writer);
548 private void RenderSpace(HtmlTextWriter writer, Unit space, Orientation orientation) {
549 Image spacerImage = new Image();
550 spacerImage.ImageUrl = Control.SpacerImageUrl;
551 spacerImage.GenerateEmptyAlternateText = true; // XHtml compliance
552 spacerImage.Page = Page;
553 if (orientation == Orientation.Vertical) {
554 spacerImage.Height = space;
555 spacerImage.Width = Unit.Pixel(1);
556 spacerImage.RenderControl(writer);
560 spacerImage.Width = space;
561 spacerImage.Height = Unit.Pixel(1);
562 spacerImage.RenderControl(writer);
566 private void RenderStyle(HtmlTextWriter writer, WebControl control, string className, MenuItemStyle style, bool applyInlineBorder) {
567 if (!String.IsNullOrEmpty(className)) {
568 control.CssClass = className;
569 if (applyInlineBorder) {
570 // Add inline style to force the border to none to override any CssClass (VSWhidbey 336610)
571 writer.AddStyleAttribute(HtmlTextWriterStyle.BorderStyle, "none");
572 // And an inline font-size of 1em to avoid squaring relative font sizes by applying them twice
573 writer.AddStyleAttribute(HtmlTextWriterStyle.FontSize, "1em");
576 else if (style != null) {
577 control.ApplyStyle(style);
581 void IPostBackEventHandler.RaisePostBackEvent(string eventArgument) {
582 RaisePostBackEvent(eventArgument);
585 protected virtual void RaisePostBackEvent(string eventArgument) {
586 if (eventArgument.Length == 0) {
590 // On postback, see what kind of event we received by checking the first character
591 char eventType = eventArgument[0];
595 // 'o' means that we're opening the secondary navigation for this node
596 // Get the path of the item specified in the eventArgument
597 // The replace is to correct a Netscape 4 bug
598 string newPath = UnEscape(HttpUtility.UrlDecode(eventArgument.Substring(1)));
599 // Check the number of separator characters in the argument (should not be more than the max depth)
601 for (int i = 0; i < newPath.Length; i++) {
602 if (newPath[i] == TreeView.InternalPathSeparator) {
603 if (++matches >= Control.MaximumDepth) {
604 throw new InvalidOperationException(SR.GetString(SR.Menu_InvalidDepth));
608 // Check this item does have subitem
609 // (otherwise, it should just be selected, not opened)
610 MenuItem item = Control.Items.FindItem(newPath.Split(TreeView.InternalPathSeparator), 0);
612 if (item.ChildItems.Count > 0) {
616 Control.InternalRaisePostBackEvent(newPath);
622 // 'u' means go up a level
624 // Find that item in the tree
625 MenuItem item = Control.Items.FindItem(_path.Split(TreeView.InternalPathSeparator), 0);
627 MenuItem parentItem = item.Parent;
628 if (parentItem != null && item.Depth + 1 > Control.StaticDisplayLevels) {
629 _path = parentItem.InternalValuePath;
639 // 'b' means bubble for to the control to handle
640 // The replace is to correct a Netscape 4 bug
641 Control.InternalRaisePostBackEvent(
642 UnEscape(HttpUtility.UrlDecode(eventArgument.Substring(1))));
647 internal void SetPath(string path) {
651 private class SpanPanel : Panel {
652 protected override HtmlTextWriterTag TagKey {
654 return HtmlTextWriterTag.Span;