|
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.
// See the LICENSE file in the project root for more information.
//
//
using MS.Internal;
using MS.Internal.Interop;
using MS.Internal.KnownBoxes;
using MS.Internal.PresentationCore;
using System.Windows.Automation;
using System.Windows.Automation.Peers;
using System.Windows.Input;
using System.Windows.Media;
using System.Windows.Media.Media3D;
namespace System.Windows
{
/// <summary>
/// UIElement3D is the base class for frameworks building on the Windows Presentation Core.
/// </summary>
/// <remarks>
/// UIElement3D adds to the base visual class "IFE" - Input, Focus, and Eventing.
/// </remarks>
public abstract partial class UIElement3D : Visual3D, IInputElement
{
static UIElement3D()
{
UIElement.RegisterEvents(typeof(UIElement3D));
// add owner and then override metadata for IsVisibile
IsVisibleProperty = UIElement.IsVisibleProperty.AddOwner(typeof(UIElement3D));
_isVisibleMetadata = new ReadOnlyPropertyMetadata(BooleanBoxes.FalseBox,
new GetReadOnlyValueCallback(GetIsVisible),
new PropertyChangedCallback(OnIsVisibleChanged));
IsVisibleProperty.OverrideMetadata(typeof(UIElement3D),
_isVisibleMetadata,
UIElement.IsVisiblePropertyKey);
// add owner and then override metadta for IsFocused
IsFocusedProperty = UIElement.IsFocusedProperty.AddOwner(typeof(UIElement3D));
IsFocusedProperty.OverrideMetadata(typeof(UIElement3D),
new PropertyMetadata(
BooleanBoxes.FalseBox, // default value
new PropertyChangedCallback(IsFocused_Changed)),
UIElement.IsFocusedPropertyKey);
}
/// <summary>
/// Constructor. This form of constructor will encounter a slight perf hit since it needs to initialize Dispatcher for the instance.
/// </summary>
protected UIElement3D()
{
_children = new Visual3DCollection(this);
Initialize();
}
private void Initialize()
{
BeginPropertyInitialization();
VisibilityCache = (Visibility)VisibilityProperty.GetDefaultValue(DependencyObjectType);
// Note: IsVisibleCache is false by default.
// to cause the first render to occur
InvalidateModel();
}
#region AllowDrop
/// <summary>
/// The DependencyProperty for the AllowDrop property.
/// </summary>
public static readonly DependencyProperty AllowDropProperty =
UIElement.AllowDropProperty.AddOwner(
typeof(UIElement3D),
new PropertyMetadata(BooleanBoxes.FalseBox));
/// <summary>
/// A dependency property that allows the drop object as DragDrop target.
/// </summary>
public bool AllowDrop
{
get { return (bool)GetValue(AllowDropProperty); }
set { SetValue(AllowDropProperty, BooleanBoxes.Box(value)); }
}
#endregion AllowDrop
private object CallRenderCallback(object o)
{
OnUpdateModel();
_renderRequestPosted = false;
return null;
}
/// <summary>
/// Invalidates the rendering of the element.
/// Causes <see cref="System.Windows.UIElement3D.OnUpdateModel"/> to be called at a later time.
/// </summary>
public void InvalidateModel()
{
if (!_renderRequestPosted)
{
MediaContext.From(Dispatcher).BeginInvokeOnRender(CallRenderCallback, this);
_renderRequestPosted = true;
}
}
/// <summary>
/// This class provides a point for subclasses to access the protected content of the
/// UIElement to set what the geometry they should render should be.
/// </summary>
protected virtual void OnUpdateModel()
{
}
/// <summary>
/// OnVisualParentChanged is called when the parent of the Visual3D is changed.
/// </summary>
/// <param name="oldParent">Old parent or null if the Visual3D did not have a parent before.</param>
protected internal override void OnVisualParentChanged(DependencyObject oldParent)
{
// Synchronize ForceInherit properties
if (InternalVisualParent != null)
{
DependencyObject parent = InternalVisualParent;
if (parent is not UIElement and not UIElement3D)
{
Visual parentAsVisual = parent as Visual;
if (parentAsVisual != null)
{
// We are being plugged into a non-UIElement visual. This
// means that our parent doesn't play by the same rules we
// do, so we need to track changes to our ancestors in
// order to bridge the gap.
parentAsVisual.VisualAncestorChanged += new Visual.AncestorChangedEventHandler(OnVisualAncestorChanged_ForceInherit);
}
else
{
Visual3D parentAsVisual3D = parent as Visual3D;
if (parentAsVisual3D != null)
{
// We are being plugged into a non-UIElement visual. This
// means that our parent doesn't play by the same rules we
// do, so we need to track changes to our ancestors in
// order to bridge the gap.
parentAsVisual3D.VisualAncestorChanged += new Visual.AncestorChangedEventHandler(OnVisualAncestorChanged_ForceInherit);
}
}
// find a UIElement(3D) ancestor to use for coersion
parent = InputElement.GetContainingUIElement(parentAsVisual);
}
if (parent != null)
{
UIElement.SynchronizeForceInheritProperties(null, null, this, parent);
}
else
{
// We don't have a UIElement parent or ancestor, so no
// coersions are necessary at this time.
}
}
else
{
DependencyObject parent = oldParent;
if (parent is not UIElement and not UIElement3D)
{
// We are being unplugged from a non-UIElement visual. This
// means that our parent didn't play by the same rules we
// do, so we started track changes to our ancestors in
// order to bridge the gap. Now we can stop.
if (oldParent is Visual)
{
((Visual)oldParent).VisualAncestorChanged -= new Visual.AncestorChangedEventHandler(OnVisualAncestorChanged_ForceInherit);
}
else if (oldParent is Visual3D)
{
((Visual3D)oldParent).VisualAncestorChanged -= new Visual.AncestorChangedEventHandler(OnVisualAncestorChanged_ForceInherit);
}
// Try to find a UIElement ancestor to use for coersion.
parent = InputElement.GetContainingUIElement(oldParent);
}
if (parent != null)
{
UIElement.SynchronizeForceInheritProperties(null, null, this, parent);
}
else
{
// We don't have a UIElement parent or ancestor, so no
// coersions are necessary at this time.
}
}
// Synchronize ReverseInheritProperty Flags
//
// NOTE: do this AFTER synchronizing force-inherited flags, since
// they often effect focusability and such.
this.SynchronizeReverseInheritPropertyFlags(oldParent, true);
}
private void OnVisualAncestorChanged_ForceInherit(object sender, AncestorChangedEventArgs e)
{
// NOTE:
//
// We are forced to listen to AncestorChanged events because
// a UIElement may have raw Visuals between it and its nearest
// UIElement parent. We only care about changes that happen
// to the visual tree BETWEEN this UIElement and its nearest
// UIElement parent. This is because we can rely on our
// nearest UIElement parent to notify us when its force-inherit
// properties change.
DependencyObject parent = null;
if (e.OldParent == null)
{
// We were plugged into something.
// Find our nearest UIElement parent.
parent = InputElement.GetContainingUIElement(InternalVisualParent);
// See if this parent is a child of the ancestor who's parent changed.
// If so, we don't care about changes that happen above us.
if (parent != null && VisualTreeHelper.IsAncestorOf(e.Ancestor, parent))
{
parent = null;
}
}
else
{
// we were unplugged from something.
// Find our nearest UIElement parent.
parent = InputElement.GetContainingUIElement(InternalVisualParent);
if (parent != null)
{
// If we found a UIElement parent in our subtree, the
// break in the visual tree must have been above it,
// so we don't need to respond.
parent = null;
}
else
{
// There was no UIElement parent in our subtree, so we
// may be detaching from some UIElement parent above
// the break point in the tree.
parent = InputElement.GetContainingUIElement(e.OldParent);
}
}
if (parent != null)
{
UIElement.SynchronizeForceInheritProperties(null, null, this, parent);
}
}
internal void OnVisualAncestorChanged(object sender, AncestorChangedEventArgs e)
{
UIElement3D uie3D = sender as UIElement3D;
if (null != uie3D)
PresentationSource.OnVisualAncestorChanged(uie3D, e);
}
internal DependencyObject GetUIParent(bool continuePastVisualTree)
{
return UIElementHelper.GetUIParent(this, continuePastVisualTree);
}
/// <summary>
/// Called to get the UI parent of this element when there is
/// no visual parent.
/// </summary>
/// <returns>
/// Returns a non-null value when some framework implementation
/// of this method has a non-visual parent connection,
/// </returns>
protected internal DependencyObject GetUIParentCore()
{
// When we get FrameworkElement3D make this function virtual. See
// UIElement for the similar usage.
return null;
}
#region LoadedAndUnloadedEvents
///<summary>
/// Initiate the processing for [Un]Loaded event broadcast starting at this node
/// </summary>
internal virtual void OnPresentationSourceChanged(bool attached)
{
// Reset the FocusedElementProperty in order to get LostFocus event
if (!attached && FocusManager.GetFocusedElement(this) != null)
FocusManager.SetFocusedElement(this, null);
}
#endregion LoadedAndUnloadedEvents
/// <summary>
/// A property indicating if the mouse is over this element or not.
/// </summary>
public bool IsMouseDirectlyOver
{
get
{
// We do not return the cached value of reverse-inherited seed properties.
//
// The cached value is only used internally to detect a "change".
//
// More Info:
// The act of invalidating the seed property of a reverse-inherited property
// on the first side of the path causes the invalidation of the
// reverse-inherited properties on both sides. The input system has not yet
// invalidated the seed property on the second side, so its cached value can
// be incorrect.
//
return IsMouseDirectlyOver_ComputeValue();
}
}
private bool IsMouseDirectlyOver_ComputeValue()
{
return (Mouse.DirectlyOver == this);
}
#region new
/// <summary>
/// Asynchronously re-evaluate the reverse-inherited properties.
/// </summary>
internal void SynchronizeReverseInheritPropertyFlags(DependencyObject oldParent, bool isCoreParent)
{
if (IsKeyboardFocusWithin)
{
Keyboard.PrimaryDevice.ReevaluateFocusAsync(this, oldParent, isCoreParent);
}
// Reevelauate the stylus properties first to guarentee that our property change
// notifications fire before mouse properties.
if (IsStylusOver)
{
StylusLogic.CurrentStylusLogicReevaluateStylusOver(this, oldParent, isCoreParent);
}
if (IsStylusCaptureWithin)
{
StylusLogic.CurrentStylusLogicReevaluateCapture(this, oldParent, isCoreParent);
}
if (IsMouseOver)
{
Mouse.PrimaryDevice.ReevaluateMouseOver(this, oldParent, isCoreParent);
}
if (IsMouseCaptureWithin)
{
Mouse.PrimaryDevice.ReevaluateCapture(this, oldParent, isCoreParent);
}
if (AreAnyTouchesOver)
{
TouchDevice.ReevaluateDirectlyOver(this, oldParent, isCoreParent);
}
if (AreAnyTouchesCapturedWithin)
{
TouchDevice.ReevaluateCapturedWithin(this, oldParent, isCoreParent);
}
}
/// <summary>
/// Controls like popup want to control updating parent properties. This method
/// provides an opportunity for those controls to participate and block it.
/// </summary>
internal virtual bool BlockReverseInheritance()
{
return false;
}
/// <summary>
/// A property indicating if the mouse is over this element or not.
/// </summary>
public bool IsMouseOver
{
get
{
return ReadFlag(CoreFlags.IsMouseOverCache);
}
}
/// <summary>
/// A property indicating if the stylus is over this element or not.
/// </summary>
public bool IsStylusOver
{
get
{
return ReadFlag(CoreFlags.IsStylusOverCache);
}
}
/// <summary>
/// Indicates if Keyboard Focus is anywhere
/// within in the subtree starting at the
/// current instance
/// </summary>
public bool IsKeyboardFocusWithin
{
get
{
return ReadFlag(CoreFlags.IsKeyboardFocusWithinCache);
}
}
#endregion new
/// <summary>
/// A property indicating if the mouse is captured to this element or not.
/// </summary>
public bool IsMouseCaptured
{
get { return (bool)GetValue(IsMouseCapturedProperty); }
}
/// <summary>
/// Captures the mouse to this element.
/// </summary>
public bool CaptureMouse()
{
return Mouse.Capture(this);
}
/// <summary>
/// Releases the mouse capture.
/// </summary>
public void ReleaseMouseCapture()
{
if (Mouse.Captured == this)
{
Mouse.Capture(null);
}
}
/// <summary>
/// Indicates if mouse capture is anywhere within the subtree
/// starting at the current instance
/// </summary>
public bool IsMouseCaptureWithin
{
get
{
return ReadFlag(CoreFlags.IsMouseCaptureWithinCache);
}
}
/// <summary>
/// A property indicating if the stylus is over this element or not.
/// </summary>
public bool IsStylusDirectlyOver
{
get
{
// We do not return the cached value of reverse-inherited seed properties.
//
// The cached value is only used internally to detect a "change".
//
// More Info:
// The act of invalidating the seed property of a reverse-inherited property
// on the first side of the path causes the invalidation of the
// reverse-inherited properties on both sides. The input system has not yet
// invalidated the seed property on the second side, so its cached value can
// be incorrect.
//
return IsStylusDirectlyOver_ComputeValue();
}
}
private bool IsStylusDirectlyOver_ComputeValue()
{
return (Stylus.DirectlyOver == this);
}
/// <summary>
/// A property indicating if the stylus is captured to this element or not.
/// </summary>
public bool IsStylusCaptured
{
get { return (bool)GetValue(IsStylusCapturedProperty); }
}
/// <summary>
/// Captures the stylus to this element.
/// </summary>
public bool CaptureStylus()
{
return Stylus.Capture(this);
}
/// <summary>
/// Releases the stylus capture.
/// </summary>
public void ReleaseStylusCapture()
{
Stylus.Capture(null);
}
/// <summary>
/// Indicates if stylus capture is anywhere within the subtree
/// starting at the current instance
/// </summary>
public bool IsStylusCaptureWithin
{
get
{
return ReadFlag(CoreFlags.IsStylusCaptureWithinCache);
}
}
/// <summary>
/// A property indicating if the keyboard is focused on this
/// element or not.
/// </summary>
public bool IsKeyboardFocused
{
get
{
// We do not return the cached value of reverse-inherited seed properties.
//
// The cached value is only used internally to detect a "change".
//
// More Info:
// The act of invalidating the seed property of a reverse-inherited property
// on the first side of the path causes the invalidation of the
// reverse-inherited properties on both sides. The input system has not yet
// invalidated the seed property on the second side, so its cached value can
// be incorrect.
//
return IsKeyboardFocused_ComputeValue();
}
}
private bool IsKeyboardFocused_ComputeValue()
{
return (Keyboard.FocusedElement == this);
}
/// <summary>
/// Set a logical focus on the element. If the current keyboard focus is within the same scope move the keyboard on this element.
/// </summary>
public bool Focus()
{
if (Keyboard.Focus(this) == this)
{
// In order to show the touch keyboard we need to prompt the WinRT InputPane API.
// We only do this when the keyboard focus has changed as the keyboard focus dictates
// our current input targets for the touch and physical keyboards.
TipTsfHelper.Show(this);
// Successfully setting the keyboard focus updated the logical focus as well
return true;
}
if (Focusable && IsEnabled)
{
// If we cannot set keyboard focus then set the logical focus only
// Find element's FocusScope and set its FocusedElement if not already set
// If FocusedElement is already set we don't want to steal focus for that scope
DependencyObject focusScope = FocusManager.GetFocusScope(this);
if (FocusManager.GetFocusedElement(focusScope) == null)
{
FocusManager.SetFocusedElement(focusScope, (IInputElement)this);
}
}
// Return false because current KeyboardFocus is not set on the element - only the logical focus is set
return false;
}
/// <summary>
/// Request to move the focus from this element to another element
/// </summary>
/// <param name="request">Determine how to move the focus</param>
/// <returns> Returns true if focus is moved successfully. Returns false if there is no next element</returns>
public virtual bool MoveFocus(TraversalRequest request)
{
return false;
}
/// <summary>
/// Request to predict the element that should receive focus relative to this element for a
/// given direction, without actually moving focus to it.
/// </summary>
/// <param name="direction">The direction for which focus should be predicted</param>
/// <returns>
/// Returns the next element that focus should move to for a given FocusNavigationDirection.
/// Returns null if focus cannot be moved relative to this element.
/// </returns>
public virtual DependencyObject PredictFocus(FocusNavigationDirection direction)
{
return null;
}
/// <summary>
/// The access key for this element was invoked. Base implementation sets focus to the element.
/// </summary>
/// <param name="e">The arguments to the access key event</param>
protected virtual void OnAccessKey(AccessKeyEventArgs e)
{
this.Focus();
}
/// <summary>
/// A property indicating if the inptu method is enabled.
/// </summary>
public bool IsInputMethodEnabled
{
get { return (bool)GetValue(InputMethod.IsInputMethodEnabledProperty); }
}
/// <summary>
/// The Visibility property.
/// </summary>
[CommonDependencyProperty]
public static readonly DependencyProperty VisibilityProperty =
UIElement.VisibilityProperty.AddOwner(
typeof(UIElement3D),
new PropertyMetadata(
VisibilityBoxes.VisibleBox,
new PropertyChangedCallback(OnVisibilityChanged)));
private static void OnVisibilityChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
{
UIElement3D uie = (UIElement3D)d;
Visibility newVisibility = (Visibility)e.NewValue;
uie.VisibilityCache = newVisibility;
uie.switchVisibilityIfNeeded(newVisibility);
// The IsVisible property depends on this property.
uie.UpdateIsVisibleCache();
}
private static bool ValidateVisibility(object o)
{
Visibility value = (Visibility)o;
return (value == Visibility.Visible) || (value == Visibility.Hidden) || (value == Visibility.Collapsed);
}
/// <summary>
/// Visibility accessor
/// </summary>
[Localizability(LocalizationCategory.None, Readability = Readability.Unreadable)]
public Visibility Visibility
{
get { return VisibilityCache; }
set { SetValue(VisibilityProperty, VisibilityBoxes.Box(value)); }
}
private void switchVisibilityIfNeeded(Visibility visibility)
{
switch (visibility)
{
case Visibility.Visible:
ensureVisible();
break;
case Visibility.Hidden:
ensureInvisible(false);
break;
case Visibility.Collapsed:
ensureInvisible(true);
break;
}
}
private void ensureVisible()
{
InternalIsVisible = true;
}
private void ensureInvisible(bool collapsed)
{
InternalIsVisible = false;
if (!ReadFlag(CoreFlags.IsCollapsed) && collapsed) //Hidden or Visible->Collapsed
{
WriteFlag(CoreFlags.IsCollapsed, true);
}
else if (ReadFlag(CoreFlags.IsCollapsed) && !collapsed) //Collapsed -> Hidden
{
WriteFlag(CoreFlags.IsCollapsed, false);
}
}
// Internal accessor for AccessKeyManager class
internal void InvokeAccessKey(AccessKeyEventArgs e)
{
OnAccessKey(e);
}
/// <summary>
/// GotFocus event
/// </summary>
public static readonly RoutedEvent GotFocusEvent = FocusManager.GotFocusEvent.AddOwner(typeof(UIElement3D));
/// <summary>
/// An event announcing that IsFocused changed to true.
/// </summary>
public event RoutedEventHandler GotFocus
{
add { AddHandler(GotFocusEvent, value); }
remove { RemoveHandler(GotFocusEvent, value); }
}
/// <summary>
/// LostFocus event
/// </summary>
public static readonly RoutedEvent LostFocusEvent = FocusManager.LostFocusEvent.AddOwner(typeof(UIElement3D));
/// <summary>
/// An event announcing that IsFocused changed to false.
/// </summary>
public event RoutedEventHandler LostFocus
{
add { AddHandler(LostFocusEvent, value); }
remove { RemoveHandler(LostFocusEvent, value); }
}
/// <summary>
/// The DependencyProperty for IsFocused.
/// Flags: None
/// Read-Only: true
/// </summary>
public static readonly DependencyProperty IsFocusedProperty;
private static void IsFocused_Changed(DependencyObject d, DependencyPropertyChangedEventArgs e)
{
UIElement3D uiElement3D = ((UIElement3D)d);
if ((bool)e.NewValue)
{
uiElement3D.OnGotFocus(new RoutedEventArgs(GotFocusEvent, uiElement3D));
}
else
{
uiElement3D.OnLostFocus(new RoutedEventArgs(LostFocusEvent, uiElement3D));
}
}
/// <summary>
/// This method is invoked when the IsFocused property changes to true
/// </summary>
/// <param name="e">RoutedEventArgs</param>
protected virtual void OnGotFocus(RoutedEventArgs e)
{
RaiseEvent(e);
}
/// <summary>
/// This method is invoked when the IsFocused property changes to false
/// </summary>
/// <param name="e">RoutedEventArgs</param>
protected virtual void OnLostFocus(RoutedEventArgs e)
{
RaiseEvent(e);
}
/// <summary>
/// Gettor for IsFocused Property
/// </summary>
public bool IsFocused
{
get { return (bool)GetValue(IsFocusedProperty); }
}
//*********************************************************************
#region IsEnabled Property
//*********************************************************************
/// <summary>
/// The DependencyProperty for the IsEnabled property.
/// </summary>
[CommonDependencyProperty]
public static readonly DependencyProperty IsEnabledProperty =
UIElement.IsEnabledProperty.AddOwner(
typeof(UIElement3D),
new UIPropertyMetadata(
BooleanBoxes.TrueBox, // default value
new PropertyChangedCallback(OnIsEnabledChanged),
new CoerceValueCallback(CoerceIsEnabled)));
/// <summary>
/// A property indicating if this element is enabled or not.
/// </summary>
public bool IsEnabled
{
get { return (bool)GetValue(IsEnabledProperty); }
set { SetValue(IsEnabledProperty, BooleanBoxes.Box(value)); }
}
/// <summary>
/// IsEnabledChanged event
/// </summary>
public event DependencyPropertyChangedEventHandler IsEnabledChanged
{
add { EventHandlersStoreAdd(UIElement.IsEnabledChangedKey, value); }
remove { EventHandlersStoreRemove(UIElement.IsEnabledChangedKey, value); }
}
/// <summary>
/// Fetches the value that IsEnabled should be coerced to.
/// </summary>
/// <remarks>
/// This method is virtual is so that controls derived from UIElement3D
/// can combine additional requirements into the coersion logic.
/// <P/>
/// It is important for anyone overriding this property to also
/// call CoerceValue when any of their dependencies change.
/// </remarks>
protected virtual bool IsEnabledCore
{
get
{
return true;
}
}
private static object CoerceIsEnabled(DependencyObject d, object value)
{
UIElement3D uie = (UIElement3D)d;
// We must be false if our parent is false, but we can be
// either true or false if our parent is true.
//
// Another way of saying this is that we can only be true
// if our parent is true, but we can always be false.
if ((bool)value)
{
// Our parent can constrain us. We can be plugged into either
// a "visual" or "content" tree. If we are plugged into a
// "content" tree, the visual tree is just considered a
// visual representation, and is normally composed of raw
// visuals, not UIElement3Ds, so we prefer the content tree.
//
// The content tree uses the "logical" links. But not all
// "logical" links lead to a content tree.
//
DependencyObject parent = uie.GetUIParentCore() as ContentElement;
if (parent == null)
{
parent = InputElement.GetContainingUIElement(uie.InternalVisualParent);
}
if (parent == null || (bool)parent.GetValue(UIElement.IsEnabledProperty))
{
return BooleanBoxes.Box(uie.IsEnabledCore);
}
else
{
return BooleanBoxes.FalseBox;
}
}
else
{
return BooleanBoxes.FalseBox;
}
}
private static void OnIsEnabledChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
{
UIElement3D uie = (UIElement3D)d;
// Raise the public changed event.
uie.RaiseDependencyPropertyChanged(UIElement.IsEnabledChangedKey, e);
// Invalidate the children so that they will inherit the new value.
uie.InvalidateForceInheritPropertyOnChildren(e.Property);
// The input manager needs to re-hittest because something changed
// that is involved in the hit-testing we do, so a different result
// could be returned.
InputManager.SafeCurrentNotifyHitTestInvalidated();
//Notify Automation in case it is interested.
AutomationPeer peer = uie.GetAutomationPeer();
if (peer != null)
peer.InvalidatePeer();
}
//*********************************************************************
#endregion IsEnabled Property
//*********************************************************************
//*********************************************************************
#region IsHitTestVisible Property
//*********************************************************************
/// <summary>
/// The DependencyProperty for the IsHitTestVisible property.
/// </summary>
public static readonly DependencyProperty IsHitTestVisibleProperty =
UIElement.IsHitTestVisibleProperty.AddOwner(
typeof(UIElement3D),
new UIPropertyMetadata(
BooleanBoxes.TrueBox, // default value
new PropertyChangedCallback(OnIsHitTestVisibleChanged),
new CoerceValueCallback(CoerceIsHitTestVisible)));
/// <summary>
/// A property indicating if this element is hit test visible or not.
/// </summary>
public bool IsHitTestVisible
{
get { return (bool)GetValue(IsHitTestVisibleProperty); }
set { SetValue(IsHitTestVisibleProperty, BooleanBoxes.Box(value)); }
}
/// <summary>
/// IsHitTestVisibleChanged event
/// </summary>
public event DependencyPropertyChangedEventHandler IsHitTestVisibleChanged
{
add { EventHandlersStoreAdd(IsHitTestVisibleChangedKey, value); }
remove { EventHandlersStoreRemove(IsHitTestVisibleChangedKey, value); }
}
internal static readonly EventPrivateKey IsHitTestVisibleChangedKey = new EventPrivateKey(); // Used by ContentElement
private static object CoerceIsHitTestVisible(DependencyObject d, object value)
{
UIElement3D uie = (UIElement3D)d;
// We must be false if our parent is false, but we can be
// either true or false if our parent is true.
//
// Another way of saying this is that we can only be true
// if our parent is true, but we can always be false.
if ((bool)value)
{
// Our parent can constrain us. We can be plugged into either
// a "visual" or "content" tree. If we are plugged into a
// "content" tree, the visual tree is just considered a
// visual representation, and is normally composed of raw
// visuals, not UIElements, so we prefer the content tree.
//
// The content tree uses the "logical" links. But not all
// "logical" links lead to a content tree.
//
// However, ContentElements don't understand IsHitTestVisible,
// so we ignore them.
//
DependencyObject parent = InputElement.GetContainingUIElement(uie.InternalVisualParent);
if (parent == null || UIElementHelper.IsHitTestVisible(parent))
{
return BooleanBoxes.TrueBox;
}
else
{
return BooleanBoxes.FalseBox;
}
}
else
{
return BooleanBoxes.FalseBox;
}
}
private static void OnIsHitTestVisibleChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
{
UIElement3D uie = (UIElement3D)d;
// Raise the public changed event.
uie.RaiseDependencyPropertyChanged(IsHitTestVisibleChangedKey, e);
// Invalidate the children so that they will inherit the new value.
uie.InvalidateForceInheritPropertyOnChildren(e.Property);
// The input manager needs to re-hittest because something changed
// that is involved in the hit-testing we do, so a different result
// could be returned.
InputManager.SafeCurrentNotifyHitTestInvalidated();
}
//*********************************************************************
#endregion IsHitTestVisible Property
//*********************************************************************
//*********************************************************************
#region IsVisible Property
//*********************************************************************
// both _isVisibleMetadata and IsVisibileProperty are constructed in UIElement3D's static constructor
private static PropertyMetadata _isVisibleMetadata;
/// <summary>
/// The DependencyProperty for the IsVisible property.
/// </summary>
public static readonly DependencyProperty IsVisibleProperty;
/// <summary>
/// A property indicating if this element is Visible or not.
/// </summary>
public bool IsVisible
{
get { return ReadFlag(CoreFlags.IsVisibleCache); }
}
private static object GetIsVisible(DependencyObject d, out BaseValueSourceInternal source)
{
source = BaseValueSourceInternal.Local;
return ((UIElement3D)d).IsVisible ? BooleanBoxes.TrueBox : BooleanBoxes.FalseBox;
}
/// <summary>
/// IsVisibleChanged event
/// </summary>
public event DependencyPropertyChangedEventHandler IsVisibleChanged
{
add { EventHandlersStoreAdd(UIElement.IsVisibleChangedKey, value); }
remove { EventHandlersStoreRemove(UIElement.IsVisibleChangedKey, value); }
}
internal static readonly EventPrivateKey IsVisibleChangedKey = new EventPrivateKey(); // Used by ContentElement
internal void UpdateIsVisibleCache() // Called from PresentationSource
{
// IsVisible is a read-only property. It derives its "base" value
// from the Visibility property.
bool isVisible = (Visibility == Visibility.Visible);
// We must be false if our parent is false, but we can be
// either true or false if our parent is true.
//
// Another way of saying this is that we can only be true
// if our parent is true, but we can always be false.
if (isVisible)
{
bool constraintAllowsVisible = false;
// Our parent can constrain us. We can be plugged into either
// a "visual" or "content" tree. If we are plugged into a
// "content" tree, the visual tree is just considered a
// visual representation, and is normally composed of raw
// visuals, not UIElements, so we prefer the content tree.
//
// The content tree uses the "logical" links. But not all
// "logical" links lead to a content tree.
//
// However, ContentElements don't understand IsVisible,
// so we ignore them.
//
DependencyObject parent = InputElement.GetContainingUIElement(InternalVisualParent);
if (parent != null)
{
constraintAllowsVisible = UIElementHelper.IsVisible(parent);
}
else
{
// We cannot be visible if we have no visual parent, unless:
// 1) We are the root, connected to a PresentationHost.
PresentationSource presentationSource = PresentationSource.CriticalFromVisual(this);
if (presentationSource != null)
{
constraintAllowsVisible = true;
}
else
{
// What if We are the root of a VisualBrush? How can we tell?
}
}
if (!constraintAllowsVisible)
{
isVisible = false;
}
}
if (isVisible != IsVisible)
{
// Our IsVisible force-inherited property has changed. Update our
// cache and raise a change notification.
WriteFlag(CoreFlags.IsVisibleCache, isVisible);
NotifyPropertyChange(new DependencyPropertyChangedEventArgs(IsVisibleProperty, _isVisibleMetadata, !isVisible, isVisible));
}
}
private static void OnIsVisibleChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
{
UIElement3D uie = (UIElement3D)d;
// Raise the public changed event.
uie.RaiseDependencyPropertyChanged(IsVisibleChangedKey, e);
// Invalidate the children so that they will inherit the new value.
uie.InvalidateForceInheritPropertyOnChildren(e.Property);
// The input manager needs to re-hittest because something changed
// that is involved in the hit-testing we do, so a different result
// could be returned.
InputManager.SafeCurrentNotifyHitTestInvalidated();
}
//*********************************************************************
#endregion IsVisible Property
//*********************************************************************
//*********************************************************************
#region Focusable Property
//*********************************************************************
/// <summary>
/// The DependencyProperty for the Focusable property.
/// </summary>
[CommonDependencyProperty]
public static readonly DependencyProperty FocusableProperty =
UIElement.FocusableProperty.AddOwner(
typeof(UIElement3D),
new UIPropertyMetadata(
BooleanBoxes.FalseBox, // default value
new PropertyChangedCallback(OnFocusableChanged)));
/// <summary>
/// Gettor and Settor for Focusable Property
/// </summary>
public bool Focusable
{
get { return (bool)GetValue(FocusableProperty); }
set { SetValue(FocusableProperty, BooleanBoxes.Box(value)); }
}
/// <summary>
/// FocusableChanged event
/// </summary>
public event DependencyPropertyChangedEventHandler FocusableChanged
{
add { EventHandlersStoreAdd(UIElement.FocusableChangedKey, value); }
remove { EventHandlersStoreRemove(UIElement.FocusableChangedKey, value); }
}
private static void OnFocusableChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
{
UIElement3D uie = (UIElement3D)d;
// Raise the public changed event.
uie.RaiseDependencyPropertyChanged(UIElement.FocusableChangedKey, e);
}
//*********************************************************************
#endregion Focusable Property
//*********************************************************************
/// <summary>
/// Called by the Automation infrastructure when AutomationPeer
/// is requested for this element. The element can return null or
/// the instance of AutomationPeer-derived clas, if it supports UI Automation
/// </summary>
protected virtual AutomationPeer OnCreateAutomationPeer() { return null; }
/// <summary>
/// Called by the Automation infrastructure or Control author
/// to make sure the AutomationPeer is created. The element may
/// create AP or return null, depending on OnCreateAutomationPeer override.
/// </summary>
internal AutomationPeer CreateAutomationPeer()
{
VerifyAccess(); //this will ensure the AP is created in the right context
AutomationPeer ap = null;
if (HasAutomationPeer)
{
ap = AutomationPeerField.GetValue(this);
}
else
{
ap = OnCreateAutomationPeer();
if (ap != null)
{
AutomationPeerField.SetValue(this, ap);
HasAutomationPeer = true;
}
}
return ap;
}
/// <summary>
/// Returns AutomationPeer if one exists.
/// The AutomationPeer may not exist if not yet created by Automation infrastructure
/// or if this element is not supposed to have one.
/// </summary>
internal AutomationPeer GetAutomationPeer()
{
VerifyAccess();
if (HasAutomationPeer)
return AutomationPeerField.GetValue(this);
return null;
}
// If this element is currently listening to synchronized input, add a pre-opportunity handler to keep track of event routed through this element.
internal void AddSynchronizedInputPreOpportunityHandler(EventRoute route, RoutedEventArgs args)
{
if (InputManager.IsSynchronizedInput)
{
if (SynchronizedInputHelper.IsListening(this, args))
{
RoutedEventHandler eventHandler = new RoutedEventHandler(this.SynchronizedInputPreOpportunityHandler);
SynchronizedInputHelper.AddHandlerToRoute(this, route, eventHandler, false);
}
}
}
// If this element is currently listening to synchronized input, add a handler to post process the synchronized input otherwise
// add a synchronized input pre-opportunity handler from parent if parent is listening.
internal void AddSynchronizedInputPostOpportunityHandler(EventRoute route, RoutedEventArgs args)
{
if (InputManager.IsSynchronizedInput)
{
if (SynchronizedInputHelper.IsListening(this, args))
{
RoutedEventHandler eventHandler = new RoutedEventHandler(this.SynchronizedInputPostOpportunityHandler);
SynchronizedInputHelper.AddHandlerToRoute(this, route, eventHandler, true);
}
else
{
// Add a preview handler from the parent.
SynchronizedInputHelper.AddParentPreOpportunityHandler(this, route, args);
}
}
}
// This event handler to be called before all the class & instance handlers for this element.
internal void SynchronizedInputPreOpportunityHandler(object sender, RoutedEventArgs args)
{
if (!args.Handled)
{
SynchronizedInputHelper.PreOpportunityHandler(sender, args);
}
}
// This event handler to be called after class & instance handlers for this element.
internal void SynchronizedInputPostOpportunityHandler(object sender, RoutedEventArgs args)
{
if (args.Handled && (InputManager.SynchronizedInputState == SynchronizedInputStates.HadOpportunity))
{
SynchronizedInputHelper.PostOpportunityHandler(sender, args);
}
}
// Called by automation peer, when called this element will be the listening element for synchronized input.
internal bool StartListeningSynchronizedInput(SynchronizedInputType inputType)
{
if (InputManager.IsSynchronizedInput)
{
return false;
}
else
{
InputManager.StartListeningSynchronizedInput(this, inputType);
return true;
}
}
// When called, input processing will return to normal mode.
internal void CancelSynchronizedInput()
{
InputManager.CancelSynchronizedInput();
}
// Helper method to retrieve and fire Clr Event handlers for DependencyPropertyChanged event
private void RaiseDependencyPropertyChanged(EventPrivateKey key, DependencyPropertyChangedEventArgs args)
{
EventHandlersStore store = EventHandlersStore;
if (store != null)
{
Delegate handler = store.Get(key);
if (handler != null)
{
((DependencyPropertyChangedEventHandler)handler)(this, args);
}
}
}
// Cache for the Visibility property. Storage is in Visual._nodeProperties.
private Visibility VisibilityCache
{
get
{
if (CheckFlagsAnd(VisualFlags.VisibilityCache_Visible))
{
return Visibility.Visible;
}
else if (CheckFlagsAnd(VisualFlags.VisibilityCache_TakesSpace))
{
return Visibility.Hidden;
}
else
{
return Visibility.Collapsed;
}
}
set
{
Debug.Assert(value == Visibility.Visible || value == Visibility.Hidden || value == Visibility.Collapsed);
switch (value)
{
case Visibility.Visible:
SetFlags(true, VisualFlags.VisibilityCache_Visible);
SetFlags(false, VisualFlags.VisibilityCache_TakesSpace);
break;
case Visibility.Hidden:
SetFlags(false, VisualFlags.VisibilityCache_Visible);
SetFlags(true, VisualFlags.VisibilityCache_TakesSpace);
break;
case Visibility.Collapsed:
SetFlags(false, VisualFlags.VisibilityCache_Visible);
SetFlags(false, VisualFlags.VisibilityCache_TakesSpace);
break;
}
}
}
#region ForceInherit property support
// This is called from the force-inherit property changed events.
internal static void InvalidateForceInheritPropertyOnChildren(Visual3D v, DependencyProperty property)
{
int cChildren = v.InternalVisual2DOr3DChildrenCount;
for (int iChild = 0; iChild < cChildren; iChild++)
{
DependencyObject vChild = v.InternalGet2DOr3DVisualChild(iChild);
if (vChild != null)
{
UIElement element = vChild as UIElement;
UIElement3D element3D = vChild as UIElement3D;
if (element3D != null)
{
if (property == IsVisibleProperty)
{
// For Read-Only force-inherited properties, use
// a private update method.
element3D.UpdateIsVisibleCache();
}
else
{
// For Read/Write force-inherited properties, use
// the standard coersion pattern.
element3D.CoerceValue(property);
}
}
else if (element != null)
{
if (property == IsVisibleProperty)
{
// For Read-Only force-inherited properties, use
// a private update method.
element.UpdateIsVisibleCache();
}
else
{
// For Read/Write force-inherited properties, use
// the standard coersion pattern.
element.CoerceValue(property);
}
}
else
{
// We have to "walk through" non-UIElement visuals.
if (vChild is Visual)
{
((Visual)vChild).InvalidateForceInheritPropertyOnChildren(property);
}
else
{
((Visual3D)vChild).InvalidateForceInheritPropertyOnChildren(property);
}
}
}
}
}
#endregion
#region Touch
/// <summary>
/// A property indicating if any touch devices are over this element or not.
/// </summary>
public bool AreAnyTouchesOver
{
get { return ReadFlag(CoreFlags.TouchesOverCache); }
}
/// <summary>
/// A property indicating if any touch devices are directly over this element or not.
/// </summary>
public bool AreAnyTouchesDirectlyOver
{
get { return (bool)GetValue(AreAnyTouchesDirectlyOverProperty); }
}
/// <summary>
/// A property indicating if any touch devices are captured to elements in this subtree.
/// </summary>
public bool AreAnyTouchesCapturedWithin
{
get { return ReadFlag(CoreFlags.TouchesCapturedWithinCache); }
}
/// <summary>
/// A property indicating if any touch devices are captured to this element.
/// </summary>
public bool AreAnyTouchesCaptured
{
get { return (bool)GetValue(AreAnyTouchesCapturedProperty); }
}
/// <summary>
/// Captures the specified device to this element.
/// </summary>
/// <param name="touchDevice">The touch device to capture.</param>
/// <returns>True if capture was taken.</returns>
public bool CaptureTouch(TouchDevice touchDevice)
{
ArgumentNullException.ThrowIfNull(touchDevice);
return touchDevice.Capture(this);
}
/// <summary>
/// Releases capture from the specified touch device.
/// </summary>
/// <param name="touchDevice">The device that is captured to this element.</param>
/// <returns>true if capture was released, false otherwise.</returns>
public bool ReleaseTouchCapture(TouchDevice touchDevice)
{
ArgumentNullException.ThrowIfNull(touchDevice);
if (touchDevice.Captured == this)
{
touchDevice.Capture(null);
return true;
}
else
{
return false;
}
}
/// <summary>
/// Releases capture on any touch devices captured to this element.
/// </summary>
public void ReleaseAllTouchCaptures()
{
TouchDevice.ReleaseAllCaptures(this);
}
/// <summary>
/// The touch devices captured to this element.
/// </summary>
public IEnumerable<TouchDevice> TouchesCaptured
{
get
{
return TouchDevice.GetCapturedTouches(this, /* includeWithin = */ false);
}
}
/// <summary>
/// The touch devices captured to this element and any elements in the subtree.
/// </summary>
public IEnumerable<TouchDevice> TouchesCapturedWithin
{
get
{
return TouchDevice.GetCapturedTouches(this, /* includeWithin = */ true);
}
}
/// <summary>
/// The touch devices which are over this element and any elements in the subtree.
/// This is particularly relevant to elements which dont take capture (like Label).
/// </summary>
public IEnumerable<TouchDevice> TouchesOver
{
get
{
return TouchDevice.GetTouchesOver(this, /* includeWithin = */ true);
}
}
/// <summary>
/// The touch devices which are directly over this element.
/// This is particularly relevant to elements which dont take capture (like Label).
/// </summary>
public IEnumerable<TouchDevice> TouchesDirectlyOver
{
get
{
return TouchDevice.GetTouchesOver(this, /* includeWithin = */ false);
}
}
#endregion
//------------------------------------------------------
//
// Private Fields
//
//------------------------------------------------------
#region Private Fields
private readonly Visual3DCollection _children;
private bool _renderRequestPosted;
#endregion Private Fields
// Perf analysis showed we were not using these fields enough to warrant
// bloating each instance with the field, so storage is created on-demand
// in the local store.
internal static readonly UncommonField<EventHandlersStore> EventHandlersStoreField = new UncommonField<EventHandlersStore>();
internal static readonly UncommonField<InputBindingCollection> InputBindingCollectionField = new UncommonField<InputBindingCollection>();
internal static readonly UncommonField<CommandBindingCollection> CommandBindingCollectionField = new UncommonField<CommandBindingCollection>();
private static readonly UncommonField<AutomationPeer> AutomationPeerField = new UncommonField<AutomationPeer>();
internal bool HasAutomationPeer
{
get { return ReadFlag(CoreFlags.HasAutomationPeer); }
set { WriteFlag(CoreFlags.HasAutomationPeer, value); }
}
}
}
|