e79aa3c0ed
Former-commit-id: a2155e9bd80020e49e72e86c44da02a8ac0e57a4
665 lines
24 KiB
C#
665 lines
24 KiB
C#
//------------------------------------------------------------------------------
|
|
// <copyright file="MobileContainerDesigner.cs" company="Microsoft">
|
|
// Copyright (c) Microsoft Corporation. All rights reserved.
|
|
// </copyright>
|
|
//------------------------------------------------------------------------------
|
|
|
|
namespace System.Web.UI.Design.MobileControls
|
|
{
|
|
using System;
|
|
using System.Collections;
|
|
using System.Collections.Specialized;
|
|
using System.ComponentModel;
|
|
using System.ComponentModel.Design;
|
|
using System.Diagnostics;
|
|
using System.Drawing;
|
|
using System.Globalization;
|
|
using System.Reflection;
|
|
using System.Web.UI.Design.MobileControls.Adapters;
|
|
using System.Web.UI.MobileControls;
|
|
using System.Web.UI.MobileControls.Adapters;
|
|
|
|
using IHTMLElement = NativeMethods.IHTMLElement;
|
|
using IHTMLElementCollection = NativeMethods.IHTMLElementCollection;
|
|
|
|
/// <summary>
|
|
/// <para>Provides a base designer class for all mobile container controls.</para>
|
|
/// </summary>
|
|
[
|
|
System.Security.Permissions.SecurityPermission(System.Security.Permissions.SecurityAction.Demand,
|
|
Flags=System.Security.Permissions.SecurityPermissionFlag.UnmanagedCode)
|
|
]
|
|
[Obsolete("The System.Web.Mobile.dll assembly has been deprecated and should no longer be used. For information about how to develop ASP.NET mobile applications, see http://go.microsoft.com/fwlink/?LinkId=157231.")]
|
|
internal abstract class MobileContainerDesigner : ControlDesigner, IMobileDesigner
|
|
{
|
|
private MobileControl _mobileControl;
|
|
private readonly Size _defaultSize;
|
|
private bool _containmentStatusDirty = true;
|
|
private bool _hasAttributesCached = false;
|
|
private bool _shouldDirtyPage = false;
|
|
private ContainmentStatus _containmentStatus = ContainmentStatus.Unknown;
|
|
private IDictionary _behaviorAttributes;
|
|
private String _currentErrorMessage = null;
|
|
private IWebFormsDocumentService _iWebFormsDocumentService;
|
|
private IMobileWebFormServices _iMobileWebFormServices;
|
|
private EventHandler _loadComplete = null;
|
|
|
|
// cached Behavior object
|
|
private IHtmlControlDesignerBehavior _cachedBehavior = null;
|
|
|
|
/// <summary>
|
|
/// <para>
|
|
/// Initializes an instance of the <see cref='System.Web.UI.Design.MobileControls.MobileContainerDesigner'/> class.
|
|
/// </para>
|
|
/// </summary>
|
|
internal MobileContainerDesigner()
|
|
{
|
|
ReadOnly = false;
|
|
|
|
_defaultSize = new Size(300, 100);
|
|
_behaviorAttributes = new HybridDictionary();
|
|
}
|
|
|
|
/// <summary>
|
|
/// return the containment status
|
|
/// </summary>
|
|
protected ContainmentStatus ContainmentStatus
|
|
{
|
|
get
|
|
{
|
|
if (!_containmentStatusDirty)
|
|
{
|
|
return _containmentStatus;
|
|
}
|
|
|
|
_containmentStatus =
|
|
DesignerAdapterUtil.GetContainmentStatus(_mobileControl);
|
|
|
|
_containmentStatusDirty = false;
|
|
return _containmentStatus;
|
|
}
|
|
}
|
|
|
|
internal Object DesignTimeElementInternal
|
|
{
|
|
get
|
|
{
|
|
return typeof(HtmlControlDesigner).InvokeMember("DesignTimeElement",
|
|
BindingFlags.Instance | BindingFlags.GetProperty | BindingFlags.NonPublic,
|
|
null, this, null, CultureInfo.InvariantCulture);
|
|
}
|
|
}
|
|
|
|
private IMobileWebFormServices IMobileWebFormServices
|
|
{
|
|
get
|
|
{
|
|
if (_iMobileWebFormServices == null)
|
|
{
|
|
_iMobileWebFormServices =
|
|
(IMobileWebFormServices)GetService(typeof(IMobileWebFormServices));
|
|
}
|
|
|
|
return _iMobileWebFormServices;
|
|
}
|
|
}
|
|
|
|
private IWebFormsDocumentService IWebFormsDocumentService
|
|
{
|
|
get
|
|
{
|
|
if (_iWebFormsDocumentService == null)
|
|
{
|
|
_iWebFormsDocumentService =
|
|
(IWebFormsDocumentService)GetService(typeof(IWebFormsDocumentService));
|
|
|
|
Debug.Assert(_iWebFormsDocumentService != null);
|
|
}
|
|
|
|
return _iWebFormsDocumentService;
|
|
}
|
|
}
|
|
|
|
/// <summary>
|
|
/// Indicates whether the initial page load is completed
|
|
/// </summary>
|
|
protected bool LoadComplete
|
|
{
|
|
get
|
|
{
|
|
return !IWebFormsDocumentService.IsLoading;
|
|
}
|
|
}
|
|
|
|
/// <summary>
|
|
/// Control's style, available only when page is MobilePage
|
|
/// </summary>
|
|
protected Style Style
|
|
{
|
|
get
|
|
{
|
|
if (!DesignerAdapterUtil.InMobilePage(_mobileControl))
|
|
{
|
|
return null;
|
|
}
|
|
|
|
Style style = ((ControlAdapter)_mobileControl.Adapter).Style;
|
|
|
|
// Each MobileControl should have its own style
|
|
Debug.Assert(style != null);
|
|
|
|
return style;
|
|
}
|
|
}
|
|
|
|
/// <summary>
|
|
/// Apply style related properties to behavior
|
|
/// </summary>
|
|
/// <param name="propName">
|
|
/// property that needs to be applied, null to apply all
|
|
/// </param>
|
|
private void ApplyPropertyToBehavior(String propName)
|
|
{
|
|
if (Style == null)
|
|
{
|
|
return;
|
|
}
|
|
|
|
if (propName == null || propName.Equals("BackColor"))
|
|
{
|
|
Color backColor = (Color)Style[Style.BackColorKey, true];
|
|
SetBehaviorStyle("backgroundColor", ColorTranslator.ToHtml(backColor));
|
|
}
|
|
if (propName == null || propName.Equals("ForeColor"))
|
|
{
|
|
Color foreColor = (Color)Style[Style.ForeColorKey, true];
|
|
SetBehaviorStyle("color", ColorTranslator.ToHtml(foreColor));
|
|
}
|
|
if (propName == null || propName.Equals("Font"))
|
|
{
|
|
bool bold =
|
|
(BooleanOption)Style[Style.BoldKey, true] == BooleanOption.True;
|
|
bool italic =
|
|
(BooleanOption)Style[Style.ItalicKey, true] == BooleanOption.True;
|
|
FontSize fontSize = (FontSize) Style[Style.FontSizeKey , true];
|
|
String fontName = (String) Style[Style.FontNameKey , true];
|
|
|
|
SetBehaviorStyle("fontWeight", bold? "bold" : "normal");
|
|
SetBehaviorStyle("fontStyle", italic? "italic" : "normal");
|
|
|
|
if (fontSize == FontSize.Large)
|
|
{
|
|
SetBehaviorStyle("fontSize", "medium");
|
|
}
|
|
else if (fontSize == FontSize.Small)
|
|
{
|
|
SetBehaviorStyle("fontSize", "x-small");
|
|
}
|
|
else
|
|
{
|
|
RemoveBehaviorStyle("fontSize");
|
|
}
|
|
|
|
SetBehaviorStyle("fontFamily", fontName);
|
|
}
|
|
if (propName == null || propName.Equals("Alignment"))
|
|
{
|
|
Alignment alignment = (Alignment)Style[Style.AlignmentKey, true];
|
|
bool alignmentNotSet = alignment == Alignment.NotSet;
|
|
|
|
SetBehaviorStyle("textAlign",
|
|
alignmentNotSet ? "" : Enum.Format(typeof(Alignment), alignment, "G"));
|
|
}
|
|
}
|
|
|
|
/// <summary>
|
|
/// Performs the cleanup of the designer class.
|
|
/// </summary>
|
|
/// <seealso cref='IDesigner'/>
|
|
protected override void Dispose(bool disposing)
|
|
{
|
|
if (disposing)
|
|
{
|
|
if (_loadComplete != null)
|
|
{
|
|
IWebFormsDocumentService.LoadComplete -= _loadComplete;
|
|
_loadComplete = null;
|
|
}
|
|
}
|
|
|
|
base.Dispose(disposing);
|
|
}
|
|
|
|
/// <summary>
|
|
/// The default size of Container Control.
|
|
/// </summary>
|
|
protected virtual Size GetDefaultSize()
|
|
{
|
|
return _defaultSize;
|
|
}
|
|
|
|
/// <summary>
|
|
/// non-null string will render the text as an image
|
|
/// on the top of container control.
|
|
/// </summary>
|
|
protected virtual String GetErrorMessage(out bool infoMode)
|
|
{
|
|
infoMode = false;
|
|
return null;
|
|
}
|
|
|
|
/// <summary>
|
|
/// <para>
|
|
/// Initializes the designer using
|
|
/// the specified component.
|
|
/// </para>
|
|
/// </summary>
|
|
/// <param name='component'>
|
|
/// The control element being designed.
|
|
/// </param>
|
|
/// <remarks>
|
|
/// <para>
|
|
/// This is called by the designer host to establish the component being
|
|
/// designed.
|
|
/// </para>
|
|
/// </remarks>
|
|
/// <seealso cref='System.ComponentModel.Design.IDesigner'/>
|
|
public override void Initialize(IComponent component)
|
|
{
|
|
Debug.Assert(component is MobileControl,
|
|
"MobileContainerDesigner.Initialize - Invalid Mobile Control");
|
|
|
|
_mobileControl = (MobileControl) component;
|
|
base.Initialize(component);
|
|
|
|
_loadComplete = new EventHandler(this.OnLoadComplete);
|
|
IWebFormsDocumentService.LoadComplete += _loadComplete;
|
|
}
|
|
|
|
/// <summary>
|
|
/// return true if the property is an appearance attribute that needs
|
|
/// to apply to all child controls.
|
|
/// </summary>
|
|
/// <param name="propertyName">
|
|
/// </param>
|
|
private bool IsAppearanceAttribute(String propertyName)
|
|
{
|
|
return (
|
|
propertyName.Equals("Font") ||
|
|
propertyName.Equals("ForeColor") ||
|
|
propertyName.Equals("BackColor") ||
|
|
propertyName.Equals("Wrapping") ||
|
|
propertyName.Equals("Alignment") ||
|
|
propertyName.Equals("StyleReference"));
|
|
}
|
|
|
|
internal virtual void OnBackgroundImageChange(String message, bool infoMode)
|
|
{
|
|
}
|
|
|
|
/// <summary>
|
|
/// <para>
|
|
/// Notification that is called when the designer is attached to the behavior.
|
|
/// </para>
|
|
/// </summary>
|
|
protected override void OnBehaviorAttached()
|
|
{
|
|
Debug.Assert(_cachedBehavior == null);
|
|
_cachedBehavior = Behavior;
|
|
|
|
PrefixDeviceSpecificTags();
|
|
base.OnBehaviorAttached();
|
|
|
|
// Reload the original state if an old Behavior is cached.
|
|
if (_hasAttributesCached)
|
|
{
|
|
ReloadBehaviorState();
|
|
}
|
|
}
|
|
|
|
/// <summary>
|
|
/// Notification that is called when the behavior is detached from designer
|
|
/// </summary>
|
|
protected override void OnBehaviorDetaching()
|
|
{
|
|
// dispose the cached behavior.
|
|
_cachedBehavior = null;
|
|
}
|
|
|
|
/// <summary>
|
|
/// <para>
|
|
/// Delegate to handle component changed event.
|
|
/// </para>
|
|
/// </summary>
|
|
/// <param name='sender'>
|
|
/// The object sending the event.
|
|
/// </param>
|
|
/// <param name='ce'>
|
|
/// The event object used when firing a component changed notification.
|
|
/// </param>
|
|
/// <remarks>
|
|
/// <para>
|
|
/// This is called after a property has been changed. It allows the implementor
|
|
/// to do any post-processing that may be needed after a property change.
|
|
/// </para>
|
|
/// </remarks>
|
|
public override void OnComponentChanged(Object sender, ComponentChangedEventArgs ce)
|
|
{
|
|
// Delegate to the base class implementation first!
|
|
base.OnComponentChanged(sender, ce);
|
|
|
|
MemberDescriptor member = ce.Member;
|
|
if (member != null &&
|
|
member.GetType().FullName.Equals(Constants.ReflectPropertyDescriptorTypeFullName))
|
|
{
|
|
PropertyDescriptor propDesc = (PropertyDescriptor)member;
|
|
String propName = propDesc.Name;
|
|
|
|
if (IsAppearanceAttribute(propName))
|
|
{
|
|
// Update control rendering
|
|
UpdateRenderingRecursive();
|
|
}
|
|
}
|
|
}
|
|
|
|
/// <summary>
|
|
/// Subclasses can override to modify their container appearance,
|
|
/// this method is invoked by OnLoadComplete()
|
|
/// </summary>
|
|
protected virtual void OnContainmentChanged()
|
|
{
|
|
// do nothing
|
|
}
|
|
|
|
/// <summary>
|
|
/// helper method for external UIs
|
|
/// </summary>
|
|
protected virtual void OnInternalChange()
|
|
{
|
|
ISite site = _mobileControl.Site;
|
|
if (site != null)
|
|
{
|
|
IComponentChangeService changeService =
|
|
(IComponentChangeService)site.GetService(typeof(IComponentChangeService));
|
|
if (changeService != null)
|
|
{
|
|
try
|
|
{
|
|
changeService.OnComponentChanging(_mobileControl, null);
|
|
}
|
|
catch (CheckoutException ex)
|
|
{
|
|
if (ex == CheckoutException.Canceled)
|
|
return;
|
|
throw;
|
|
}
|
|
changeService.OnComponentChanged(_mobileControl, null, null, null);
|
|
}
|
|
}
|
|
}
|
|
|
|
/// <summary>
|
|
/// <para>
|
|
/// Notification that is called when the page completes loading.
|
|
/// </para>
|
|
/// </summary>
|
|
private void OnLoadComplete(Object source, EventArgs e)
|
|
{
|
|
// Need to apply behavior attributes since none are cached
|
|
if (!_hasAttributesCached)
|
|
{
|
|
SetControlDefaultAppearance();
|
|
|
|
// Apply the style properties to Behavior
|
|
ApplyPropertyToBehavior(null);
|
|
}
|
|
|
|
bool infoMode = false;
|
|
String msg = GetErrorMessage(out infoMode);
|
|
if (msg != _currentErrorMessage || !_hasAttributesCached)
|
|
{
|
|
OnBackgroundImageChange(msg, infoMode);
|
|
_currentErrorMessage = msg;
|
|
}
|
|
|
|
// we could reload the attributes
|
|
_hasAttributesCached = true;
|
|
|
|
// Change containment related appearance
|
|
OnContainmentChanged();
|
|
|
|
// Don't forget the change children appearance,
|
|
// this call is necessary to solve multi-nested control problem.
|
|
UpdateRenderingRecursive();
|
|
|
|
// Make the page dirty by calling OnInternalChange if an subsitution occurs.
|
|
if (_shouldDirtyPage)
|
|
{
|
|
OnInternalChange();
|
|
_shouldDirtyPage = false;
|
|
}
|
|
}
|
|
|
|
/// <summary>
|
|
/// <para>
|
|
/// Notification that is called when the associated control is parented.
|
|
/// </para>
|
|
/// </summary>
|
|
public override void OnSetParent()
|
|
{
|
|
base.OnSetParent();
|
|
|
|
// The containment status is invalidated
|
|
_containmentStatusDirty = true;
|
|
|
|
// Make sure the control refreshes when it is moved around
|
|
if (LoadComplete)
|
|
{
|
|
OnLoadComplete(this, EventArgs.Empty);
|
|
}
|
|
}
|
|
|
|
protected override void PreFilterProperties(IDictionary properties)
|
|
{
|
|
base.PreFilterProperties(properties);
|
|
|
|
PropertyDescriptor property = (PropertyDescriptor) properties["Expressions"];
|
|
if (property != null) {
|
|
properties["Expressions"] = TypeDescriptor.CreateProperty(this.GetType(), property, BrowsableAttribute.No);
|
|
}
|
|
}
|
|
|
|
/// <summary>
|
|
/// dynamically transform DeviceSpecific element to a server control,
|
|
/// called from OnBehaviorAttached
|
|
/// </summary>
|
|
private void PrefixDeviceSpecificTags()
|
|
{
|
|
IHTMLElement htmlElement = (IHTMLElement) DesignTimeElementInternal;
|
|
Debug.Assert(htmlElement != null,
|
|
"Invalid HTML element in FormDesigner.OnBehaviorAttached");
|
|
|
|
IWebFormReferenceManager refMgr =
|
|
(IWebFormReferenceManager) GetService(typeof(IWebFormReferenceManager));
|
|
Debug.Assert(refMgr != null, "Did not get back IWebFormReferenceManager service.");
|
|
|
|
String tagPrefix = refMgr.GetTagPrefix(typeof(DeviceSpecific));
|
|
Debug.Assert(tagPrefix != null && tagPrefix.Length > 0, "TagPrefix is invalid");
|
|
|
|
IHTMLElementCollection allChildren = (IHTMLElementCollection) htmlElement.GetChildren();
|
|
if (null != allChildren)
|
|
{
|
|
bool substitutions = false;
|
|
int nestingLevel = 0;
|
|
String modifiedInnerHTML = String.Empty;
|
|
for (Int32 i = 0; i < allChildren.GetLength(); i++)
|
|
{
|
|
IHTMLElement htmlChild = (IHTMLElement) allChildren.Item(i, 0);
|
|
Debug.Assert(null != htmlChild, "htmlChild is null");
|
|
String childContent = htmlChild.GetOuterHTML();
|
|
String childUpperContent = childContent.ToUpper(CultureInfo.InvariantCulture);
|
|
if (childContent.StartsWith("<", StringComparison.Ordinal) &&
|
|
!(childContent.StartsWith("</", StringComparison.Ordinal) || (childContent.EndsWith("/>", StringComparison.Ordinal))))
|
|
{
|
|
if (!childUpperContent.StartsWith("<" + tagPrefix.ToUpper(CultureInfo.InvariantCulture) + ":", StringComparison.Ordinal))
|
|
{
|
|
nestingLevel++;
|
|
}
|
|
}
|
|
else if (childContent.StartsWith("</", StringComparison.Ordinal))
|
|
{
|
|
nestingLevel--;
|
|
}
|
|
if (1 == nestingLevel &&
|
|
childUpperContent.StartsWith("<DEVICESPECIFIC", StringComparison.Ordinal) &&
|
|
childUpperContent.EndsWith(">", StringComparison.Ordinal))
|
|
{
|
|
Debug.Assert(substitutions == false, "substitutions is true");
|
|
modifiedInnerHTML += "<" + tagPrefix + ":DeviceSpecific runat=\"server\">\r\n";
|
|
substitutions = true;
|
|
}
|
|
else if (1 == nestingLevel &&
|
|
childUpperContent.StartsWith("<DEVICESPECIFIC", StringComparison.Ordinal) &&
|
|
childUpperContent.EndsWith("/>", StringComparison.Ordinal))
|
|
{
|
|
modifiedInnerHTML += "<" + tagPrefix + ":DeviceSpecific runat=\"server\"></" + tagPrefix + ":DeviceSpecific>\r\n";
|
|
substitutions = true;
|
|
}
|
|
else if (0 == nestingLevel && 0 == String.Compare(childUpperContent, "</DEVICESPECIFIC>", StringComparison.Ordinal))
|
|
{
|
|
Debug.Assert(substitutions == true, "substitutions is false");
|
|
modifiedInnerHTML += "</" + tagPrefix + ":DeviceSpecific>\r\n";
|
|
}
|
|
else
|
|
{
|
|
modifiedInnerHTML += childContent + "\r\n";
|
|
}
|
|
}
|
|
if (substitutions)
|
|
{
|
|
_shouldDirtyPage = true;
|
|
htmlElement.SetInnerHTML(modifiedInnerHTML);
|
|
}
|
|
}
|
|
}
|
|
|
|
/// <summary>
|
|
/// Reload the cached Behavior states
|
|
/// </summary>
|
|
private void ReloadBehaviorState()
|
|
{
|
|
Debug.Assert(Behavior != null && _behaviorAttributes != null);
|
|
|
|
IDictionaryEnumerator enumerator = _behaviorAttributes.GetEnumerator();
|
|
while (enumerator.MoveNext())
|
|
{
|
|
String key = (String)enumerator.Key;
|
|
Object obj = _behaviorAttributes[key];
|
|
|
|
Behavior.SetStyleAttribute(key, true, obj, true);
|
|
}
|
|
}
|
|
|
|
/// <summary>
|
|
/// Remove the attribute from Behavior
|
|
/// </summary>
|
|
/// <param name="attribute">
|
|
/// attribute that need to be removed.
|
|
/// </param>
|
|
protected void RemoveBehaviorStyle(String attribute)
|
|
{
|
|
Debug.Assert (_behaviorAttributes != null);
|
|
|
|
if (Behavior != null)
|
|
{
|
|
Behavior.RemoveStyleAttribute(attribute, true, true);
|
|
}
|
|
|
|
// also remove the cached attribute
|
|
_behaviorAttributes.Remove(attribute);
|
|
}
|
|
|
|
/// <summary>
|
|
/// Apply the style attribute to Behavior
|
|
/// </summary>
|
|
/// <param name="attribute">
|
|
/// attribute that needs to be applied to Behavior
|
|
/// </param>
|
|
/// <param name="obj">
|
|
/// value to apply
|
|
/// </param>
|
|
protected void SetBehaviorStyle(String attribute, Object obj)
|
|
{
|
|
Debug.Assert (obj != null, "null object passed in!");
|
|
Debug.Assert (_behaviorAttributes != null);
|
|
|
|
// here we cache the value;
|
|
// Note that the value is cached even if Behavior is not available,
|
|
// this is because this method could be called between Behavior
|
|
// detached and attached events, we want to re-apply these lost
|
|
// attributes when Behavior is attached again.
|
|
_behaviorAttributes[attribute] = obj;
|
|
|
|
if (Behavior == null)
|
|
{
|
|
return;
|
|
}
|
|
Behavior.SetStyleAttribute(attribute, true, obj, true);
|
|
}
|
|
|
|
/// <summary>
|
|
/// This method will be called only once when the control is first created.
|
|
/// </summary>
|
|
protected virtual void SetControlDefaultAppearance()
|
|
{
|
|
// Default border appearance
|
|
SetBehaviorStyle("borderWidth", "1px");
|
|
SetBehaviorStyle("borderColor", ColorTranslator.ToHtml(SystemColors.ControlDark));
|
|
|
|
// Default margin, paddings for container controls.
|
|
SetBehaviorStyle("paddingTop", "8px");
|
|
SetBehaviorStyle("paddingBottom", "8px");
|
|
SetBehaviorStyle("paddingRight", "4px");
|
|
SetBehaviorStyle("paddingLeft", "5px");
|
|
SetBehaviorStyle("marginTop", "3px");
|
|
SetBehaviorStyle("marginBottom", "3px");
|
|
SetBehaviorStyle("marginRight", "5px");
|
|
SetBehaviorStyle("marginLeft", "5px");
|
|
|
|
// Setup background parameters
|
|
SetBehaviorStyle("backgroundRepeat", "no-repeat");
|
|
SetBehaviorStyle("backgroundAttachment", "fixed");
|
|
SetBehaviorStyle("backgroundPositionX", "left");
|
|
SetBehaviorStyle("backgroundPositionY", "top");
|
|
|
|
// Container sze info.
|
|
SetBehaviorStyle("height", GetDefaultSize().Height);
|
|
SetBehaviorStyle("width", GetDefaultSize().Width);
|
|
}
|
|
|
|
/// <summary>
|
|
/// Update the designtime rendering for the container control
|
|
/// </summary>
|
|
public void UpdateRendering()
|
|
{
|
|
_mobileControl.RefreshStyle();
|
|
ApplyPropertyToBehavior(null);
|
|
}
|
|
|
|
/// <summary>
|
|
/// Update the designtime rendering for the container control and all controls
|
|
/// inside this container control.
|
|
/// </summary>
|
|
private void UpdateRenderingRecursive()
|
|
{
|
|
UpdateRendering();
|
|
|
|
if (IMobileWebFormServices != null)
|
|
{
|
|
IMobileWebFormServices.UpdateRenderingRecursive(_mobileControl);
|
|
}
|
|
}
|
|
}
|
|
}
|