// ----------------------------------------------------------------------- // // Copyright 2014 MIT Licence. See licence.md for more information. // // ----------------------------------------------------------------------- namespace Perspex.Controls { 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; using Splat; /// /// 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 Classes classes = new Classes(); private DataTemplates dataTemplates; private Control focusAdorner; private string id; private IPerspexList logicalChildren; private Styles styles; /// /// Initializes static members of the class. /// static Control() { Control.AffectsMeasure(Control.IsVisibleProperty); PseudoClass(InputElement.IsEnabledCoreProperty, x => !x, ":disabled"); PseudoClass(InputElement.IsFocusedProperty, ":focus"); PseudoClass(InputElement.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 this.classes; } set { if (this.classes != value) { this.classes.Clear(); this.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 this.GetValue(DataContextProperty); } set { this.SetValue(DataContextProperty, value); } } /// /// Gets or sets the control's focus adorner. /// public AdornerTemplate FocusAdorner { get { return this.GetValue(FocusAdornerProperty); } set { this.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 (this.dataTemplates == null) { this.dataTemplates = new DataTemplates(); } return this.dataTemplates; } set { this.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 this.id; } set { if (this.id != null) { throw new InvalidOperationException("ID already set."); } if (((IVisual)this).VisualParent != null) { throw new InvalidOperationException("Cannot set ID : control already added to tree."); } this.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 (this.styles == null) { this.styles = new Styles(); } return this.styles; } set { this.styles = value; } } /// /// Gets the control's logical parent. /// public IControl Parent { get { return this.GetValue(ParentProperty); } } /// /// Gets or sets a user-defined object attached to the control. /// public object Tag { get { return this.GetValue(TagProperty); } set { this.SetValue(TagProperty, value); } } /// /// Gets the control whose lookless template this control is part of. /// public ITemplatedControl TemplatedParent { get { return this.GetValue(TemplatedParentProperty); } internal set { this.SetValue(TemplatedParentProperty, value); } } /// /// Gets the control's logical parent. /// ILogical ILogical.LogicalParent { get { return this.Parent; } } /// /// Gets the control's logical children. /// IPerspexReadOnlyList ILogical.LogicalChildren { get { return this.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 { get { return this.GetType(); } } /// /// Gets the control's logical children. /// protected IPerspexList LogicalChildren { get { if (this.logicalChildren == null) { this.logicalChildren = new PerspexList(); } return this.logicalChildren; } } /// /// Sets the control's logical parent. /// /// The parent. void ISetLogicalParent.SetParent(ILogical parent) { var old = this.Parent; if (old != null && parent != null) { throw new InvalidOperationException("The Control already has a parent."); } this.SetValue(ParentProperty, parent); } protected static void PseudoClass(PerspexProperty property, string className) { PseudoClass(property, x => x, className); } 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 (this.IsFocused && e.KeyboardNavigated) { var adornerLayer = AdornerLayer.GetAdornerLayer(this); if (adornerLayer != null) { if (this.focusAdorner == null) { var template = this.GetValue(FocusAdornerProperty); if (template != null) { this.focusAdorner = template.Build(); } } if (this.focusAdorner != null) { AdornerLayer.SetAdornedElement(this.focusAdorner, this); adornerLayer.Children.Add(this.focusAdorner); } } } } /// protected override void OnLostFocus(RoutedEventArgs e) { base.OnLostFocus(e); if (this.focusAdorner != null) { var adornerLayer = this.focusAdorner.Parent as Panel; adornerLayer.Children.Remove(this.focusAdorner); this.focusAdorner = null; } } /// protected override void OnAttachedToVisualTree(IRenderRoot root) { base.OnAttachedToVisualTree(root); IStyler styler = Locator.Current.GetService(); styler.ApplyStyles(this); } protected void RedirectLogicalChildren(IPerspexList collection) { this.logicalChildren = collection; } } }