// Copyright (c) The Perspex Project. All rights reserved. // Licensed under the MIT license. See licence.md file in the project root for full license information. using System; using System.Linq; using System.Reactive.Linq; using Perspex.Collections; using Perspex.Controls.Primitives; using Perspex.Controls.Templates; using Perspex.Input; using Perspex.Interactivity; using Perspex.LogicalTree; using Perspex.Rendering; using Perspex.Styling; namespace Perspex.Controls { /// /// Base class for Perspex controls. /// /// /// The control class extends and adds the following features: /// /// - A . /// - An inherited . /// - A property to allow user-defined data to be attached to the control. /// - A collection of class strings for custom styling. /// - Implements to allow styling to work on the control. /// - Implements to form part of a logical tree. /// public class Control : InputElement, IControl, ISetLogicalParent { /// /// Defines the property. /// public static readonly PerspexProperty DataContextProperty = PerspexProperty.Register(nameof(DataContext), inherits: true); /// /// Defines the property. /// public static readonly PerspexProperty> FocusAdornerProperty = PerspexProperty.Register>(nameof(FocusAdorner)); /// /// Defines the property. /// public static readonly PerspexProperty ParentProperty = PerspexProperty.Register(nameof(Parent)); /// /// Defines the property. /// public static readonly PerspexProperty TagProperty = PerspexProperty.Register(nameof(Tag)); /// /// Defines the property. /// public static readonly PerspexProperty TemplatedParentProperty = PerspexProperty.Register(nameof(TemplatedParent)); /// /// Event raised when an element wishes to be scrolled into view. /// public static readonly RoutedEvent RequestBringIntoViewEvent = RoutedEvent.Register("RequestBringIntoView", RoutingStrategies.Bubble); private readonly Classes _classes = new Classes(); private DataTemplates _dataTemplates; private IControl _focusAdorner; private string _id; private IPerspexList _logicalChildren; private Styles _styles; /// /// Initializes static members of the class. /// static Control() { AffectsMeasure(IsVisibleProperty); PseudoClass(IsEnabledCoreProperty, x => !x, ":disabled"); PseudoClass(IsFocusedProperty, ":focus"); PseudoClass(IsPointerOverProperty, ":pointerover"); } /// /// Gets or sets the control's classes. /// /// /// /// Classes can be used to apply user-defined styling to controls, or to allow controls /// that share a common purpose to be easily selected. /// /// /// Even though this property can be set, the setter is only intended for use in object /// initializers. Assigning to this property does not change the underlying collection, /// it simply clears the existing collection and addds the contents of the assigned /// collection. /// /// public Classes Classes { get { return _classes; } set { if (_classes != value) { _classes.Clear(); _classes.Add(value); } } } /// /// Gets or sets the control's data context. /// /// /// The data context is an inherited property that specifies the default object that will /// be used for data binding. /// public object DataContext { get { return GetValue(DataContextProperty); } set { SetValue(DataContextProperty, value); } } /// /// Gets or sets the control's focus adorner. /// public ITemplate FocusAdorner { get { return GetValue(FocusAdornerProperty); } set { SetValue(FocusAdornerProperty, value); } } /// /// Gets or sets the data templates for the control. /// /// /// Each control may define data templates which are applied to the control itself and its /// children. /// public DataTemplates DataTemplates { get { if (_dataTemplates == null) { _dataTemplates = new DataTemplates(); } return _dataTemplates; } set { _dataTemplates = value; } } /// /// Gets or sets the name of the control. /// /// /// A control's name is used to uniquely identify a control within the control's name /// scope. Once a control is added to a visual tree, its name cannot be changed. /// public string Name { get { return _id; } set { if (_id != null) { throw new InvalidOperationException("ID already set."); } if (((IVisual)this).VisualParent != null) { throw new InvalidOperationException("Cannot set ID : control already added to tree."); } _id = value; } } /// /// Gets or sets the styles for the control. /// /// /// Styles for the entire application are added to the Application.Styles collection, but /// each control may in addition define its own styles which are applied to the control /// itself and its children. /// public Styles Styles { get { if (_styles == null) { _styles = new Styles(); } return _styles; } set { _styles = value; } } /// /// Gets the control's logical parent. /// public IControl Parent => GetValue(ParentProperty); /// /// Gets or sets a user-defined object attached to the control. /// public object Tag { get { return GetValue(TagProperty); } set { SetValue(TagProperty, value); } } /// /// Gets the control whose lookless template this control is part of. /// public ITemplatedControl TemplatedParent { get { return GetValue(TemplatedParentProperty); } internal set { SetValue(TemplatedParentProperty, value); } } /// /// Gets the control's logical parent. /// ILogical ILogical.LogicalParent => Parent; /// /// Gets the control's logical children. /// IPerspexReadOnlyList ILogical.LogicalChildren => LogicalChildren; /// /// Gets the type by which the control is styled. /// /// /// Usually controls are styled by their own type, but there are instances where you want /// a control to be styled by its base type, e.g. creating SpecialButton that /// derives from Button and adds extra functionality but is still styled as a regular /// Button. /// Type IStyleable.StyleKey => GetType(); /// /// Gets the control's logical children. /// protected IPerspexList LogicalChildren { get { if (_logicalChildren == null) { _logicalChildren = new PerspexList(); } return _logicalChildren; } } /// /// Sets the control's logical parent. /// /// The parent. void ISetLogicalParent.SetParent(ILogical parent) { var old = Parent; if (old != null && parent != null) { throw new InvalidOperationException("The Control already has a parent."); } SetValue(ParentProperty, parent); } /// /// Adds a pseudo-class to be set when a property is true. /// /// The property. /// The pseudo-class. protected static void PseudoClass(PerspexProperty property, string className) { PseudoClass(property, x => x, className); } /// /// Adds a pseudo-class to be set when a property equals a certain value. /// /// The type of the property. /// The property. /// Returns a boolean value based on the property value. /// The pseudo-class. protected static void PseudoClass( PerspexProperty property, Func selector, string className) { Contract.Requires(property != null); Contract.Requires(selector != null); Contract.Requires(className != null); Contract.Requires(property != null); if (string.IsNullOrWhiteSpace(className)) { throw new ArgumentException("Cannot supply an empty className."); } Observable.Merge(property.Changed, property.Initialized) .Subscribe(e => { if (selector((T)e.NewValue)) { ((Control)e.Sender).Classes.Add(className); } else { ((Control)e.Sender).Classes.Remove(className); } }); } /// protected override void OnGotFocus(GotFocusEventArgs e) { base.OnGotFocus(e); if (IsFocused && (e.NavigationMethod == NavigationMethod.Tab || e.NavigationMethod == NavigationMethod.Directional)) { var adornerLayer = AdornerLayer.GetAdornerLayer(this); if (adornerLayer != null) { if (_focusAdorner == null) { var template = GetValue(FocusAdornerProperty); if (template != null) { _focusAdorner = template.Build(); } } if (_focusAdorner != null) { AdornerLayer.SetAdornedElement((Visual)_focusAdorner, this); adornerLayer.Children.Add(_focusAdorner); } } } } /// protected override void OnLostFocus(RoutedEventArgs e) { base.OnLostFocus(e); if (_focusAdorner != null) { var adornerLayer = _focusAdorner.Parent as Panel; adornerLayer.Children.Remove(_focusAdorner); _focusAdorner = null; } } /// protected override void OnAttachedToVisualTree(IRenderRoot root) { base.OnAttachedToVisualTree(root); IStyler styler = PerspexLocator.Current.GetService(); styler.ApplyStyles(this); } /// /// Makes the control use a different control's logical children as its own. /// /// The logical children to use. protected void RedirectLogicalChildren(IPerspexList collection) { _logicalChildren = collection; } } }