diff --git a/Perspex.Controls.UnitTests/ContentControlTests.cs b/Perspex.Controls.UnitTests/ContentControlTests.cs index 5ef6f09a58..9eee0c3fd6 100644 --- a/Perspex.Controls.UnitTests/ContentControlTests.cs +++ b/Perspex.Controls.UnitTests/ContentControlTests.cs @@ -174,7 +174,7 @@ namespace Perspex.Controls.UnitTests contentControl.ApplyTemplate(); // Need to call ApplyTemplate on presenter for CollectionChanged to be called. - var presenter = contentControl.GetTemplateControls().Single(x => x.Id == "presenter"); + var presenter = contentControl.GetTemplateControls().Single(x => x.Id == "contentPresenter"); presenter.ApplyTemplate(); Assert.IsTrue(called); @@ -197,7 +197,7 @@ namespace Perspex.Controls.UnitTests contentControl.Content = null; // Need to call ApplyTemplate on presenter for CollectionChanged to be called. - var presenter = contentControl.GetTemplateControls().Single(x => x.Id == "presenter"); + var presenter = contentControl.GetTemplateControls().Single(x => x.Id == "contentPresenter"); presenter.ApplyTemplate(); Assert.IsTrue(called); @@ -221,7 +221,7 @@ namespace Perspex.Controls.UnitTests contentControl.Content = child2; // Need to call ApplyTemplate on presenter for CollectionChanged to be called. - var presenter = contentControl.GetTemplateControls().Single(x => x.Id == "presenter"); + var presenter = contentControl.GetTemplateControls().Single(x => x.Id == "contentPresenter"); presenter.ApplyTemplate(); Assert.IsTrue(called); @@ -236,7 +236,7 @@ namespace Perspex.Controls.UnitTests Background = new Perspex.Media.SolidColorBrush(0xffffffff), Content = new ContentPresenter { - Id = "presenter", + Id = "contentPresenter", [~ContentPresenter.ContentProperty] = parent[~ContentControl.ContentProperty], } }; diff --git a/Perspex.Controls.UnitTests/ItemsControlTests.cs b/Perspex.Controls.UnitTests/ItemsControlTests.cs index 72a0f98a33..494d226ee8 100644 --- a/Perspex.Controls.UnitTests/ItemsControlTests.cs +++ b/Perspex.Controls.UnitTests/ItemsControlTests.cs @@ -275,7 +275,7 @@ namespace Perspex.Controls.UnitTests Background = new Perspex.Media.SolidColorBrush(0xffffffff), Content = new ItemsPresenter { - Id = "presenter", + Id = "itemsPresenter", [~ItemsPresenter.ItemsProperty] = parent[~ItemsControl.ItemsProperty], } }; diff --git a/Perspex.Controls/ContentControl.cs b/Perspex.Controls/ContentControl.cs index d51c85176b..2beec51b93 100644 --- a/Perspex.Controls/ContentControl.cs +++ b/Perspex.Controls/ContentControl.cs @@ -79,7 +79,7 @@ namespace Perspex.Controls this.presenterSubscription = null; } - this.presenter = this.FindTemplateChild("presenter"); + this.presenter = this.FindTemplateChild("contentPresenter"); if (this.presenter != null) { diff --git a/Perspex.Controls/Control.cs b/Perspex.Controls/Control.cs index 1e4bff5d94..e364b7519d 100644 --- a/Perspex.Controls/Control.cs +++ b/Perspex.Controls/Control.cs @@ -32,6 +32,9 @@ namespace Perspex.Controls public static readonly PerspexProperty ParentProperty = PerspexProperty.Register("Parent"); + public static readonly PerspexProperty TagProperty = + PerspexProperty.Register("Tag"); + public static readonly PerspexProperty TemplatedParentProperty = PerspexProperty.Register("TemplatedParent", inherits: true); @@ -156,6 +159,12 @@ namespace Perspex.Controls internal set { this.SetValue(ParentProperty, value); } } + public object Tag + { + get { return this.GetValue(TagProperty); } + set { this.SetValue(TagProperty, value); } + } + public ITemplatedControl TemplatedParent { get { return this.GetValue(TemplatedParentProperty); } diff --git a/Perspex.Controls/ItemsControl.cs b/Perspex.Controls/ItemsControl.cs index 133fa41559..7767904df4 100644 --- a/Perspex.Controls/ItemsControl.cs +++ b/Perspex.Controls/ItemsControl.cs @@ -93,9 +93,9 @@ namespace Perspex.Controls this.logicalChildren = null; } - this.presenter = this.FindTemplateChild("presenter"); + this.presenter = this.FindTemplateChild("itemsPresenter"); - if (presenter != null) + if (this.presenter != null) { var panel = (IVisual)this.presenter.GetVisualChildren().Single(); this.logicalChildren = new PerspexReadOnlyListView( diff --git a/Perspex.Controls/Primitives/TabStrip.cs b/Perspex.Controls/Primitives/TabStrip.cs index 03d3709a75..91b13da1c6 100644 --- a/Perspex.Controls/Primitives/TabStrip.cs +++ b/Perspex.Controls/Primitives/TabStrip.cs @@ -7,14 +7,15 @@ namespace Perspex.Controls.Primitives { using System; - using System.Collections; using System.Linq; + using System.Reactive.Linq; using Perspex.Controls.Generators; - using Perspex.Controls.Presenters; - using Perspex.Input; public class TabStrip : SelectingItemsControl { + public static readonly PerspexProperty SelectedTabProperty = + TabControl.SelectedTabProperty.AddOwner(); + private static readonly ItemsPanelTemplate PanelTemplate = new ItemsPanelTemplate( () => new StackPanel()); @@ -23,6 +24,19 @@ namespace Perspex.Controls.Primitives ItemsPanelProperty.OverrideDefaultValue(typeof(TabStrip), PanelTemplate); } + public TabStrip() + { + this.Bind( + SelectedTabProperty, + this.GetObservable(SelectedItemProperty).Select(x => x as TabItem)); + } + + public TabItem SelectedTab + { + get { return this.GetValue(SelectedTabProperty); } + private set { this.SetValue(SelectedTabProperty, value); } + } + protected override ItemContainerGenerator CreateItemContainerGenerator() { TabControl tabControl = this.TemplatedParent as TabControl; diff --git a/Perspex.Controls/Primitives/TemplatedControl.cs b/Perspex.Controls/Primitives/TemplatedControl.cs index ffe27a7471..547d7cd605 100644 --- a/Perspex.Controls/Primitives/TemplatedControl.cs +++ b/Perspex.Controls/Primitives/TemplatedControl.cs @@ -138,9 +138,7 @@ namespace Perspex.Controls.Primitives protected T FindTemplateChild(string id) where T : Control { - return this.GetTemplateControls() - .Where(x => x.TemplatedParent == this) - .OfType().FirstOrDefault(x => x.Id == id); + return (T)this.GetTemplateControls().SingleOrDefault(x => x.Id == id); } protected T GetTemplateChild(string id) where T : Control diff --git a/Perspex.Controls/TabControl.cs b/Perspex.Controls/TabControl.cs index 5e71dd7b7f..2790cf034e 100644 --- a/Perspex.Controls/TabControl.cs +++ b/Perspex.Controls/TabControl.cs @@ -9,6 +9,7 @@ namespace Perspex.Controls using System; using System.Linq; using System.Reactive.Linq; + using Perspex.Collections; using Perspex.Controls.Generators; using Perspex.Controls.Primitives; @@ -17,6 +18,9 @@ namespace Perspex.Controls public static readonly PerspexProperty SelectedContentProperty = PerspexProperty.Register("SelectedContent"); + public static readonly PerspexProperty SelectedTabProperty = + PerspexProperty.Register("SelectedTab"); + private TabStrip tabStrip; public TabControl() @@ -27,6 +31,22 @@ namespace Perspex.Controls object content = (c != null) ? c.Content : null; this.SetValue(SelectedContentProperty, content); }); + + this.Bind( + SelectedTabProperty, + this.GetObservable(SelectedItemProperty).Select(x => x as TabItem)); + } + + public object SelectedContent + { + get { return this.GetValue(SelectedContentProperty); } + set { this.SetValue(SelectedContentProperty, value); } + } + + public TabItem SelectedTab + { + get { return this.GetValue(SelectedTabProperty); } + private set { this.SetValue(SelectedTabProperty, value); } } protected override ItemContainerGenerator CreateItemContainerGenerator() @@ -36,6 +56,7 @@ namespace Perspex.Controls protected override void OnTemplateApplied() { + base.OnTemplateApplied(); this.tabStrip = this.GetTemplateControls().OfType().FirstOrDefault(); this.BindTwoWay(TabControl.SelectedItemProperty, this.tabStrip, TabControl.SelectedItemProperty); } diff --git a/Perspex.Diagnostics/DevTools.cs b/Perspex.Diagnostics/DevTools.cs index 38abb63506..bf8576a2a8 100644 --- a/Perspex.Diagnostics/DevTools.cs +++ b/Perspex.Diagnostics/DevTools.cs @@ -10,6 +10,7 @@ namespace Perspex.Diagnostics using System.Reactive.Disposables; using System.Reactive.Linq; using Perspex.Controls; + using Perspex.Controls.Primitives; using Perspex.Diagnostics.ViewModels; using Perspex.Input; using ReactiveUI; @@ -21,23 +22,43 @@ namespace Perspex.Diagnostics public DevTools() { - var treeView = new TreeView + var treePane = new Grid { - DataTemplates = new DataTemplates + RowDefinitions = new RowDefinitions { - new TreeDataTemplate(GetHeader, x => x.Children), + new RowDefinition(GridLength.Auto), + new RowDefinition(new GridLength(1, GridUnitType.Star)), }, - [!TreeView.ItemsProperty] = this[!DevTools.RootProperty].Select(x => + Children = new Controls { - if (x != null) + (var tabStrip = new TabStrip { - return new[] { new VisualTreeNode((IVisual)x) }; - } - else + Items = new[] + { + new TabItem + { + Header = "Logical Tree", + IsSelected = true, + [!TabItem.TagProperty] = this[!RootProperty].Select(x => LogicalTreeNode.Create(x)), + }, + new TabItem + { + Header = "Visual Tree", + [!TabItem.TagProperty] = this[!RootProperty].Select(x => VisualTreeNode.Create(x)), + } + }, + }), + (var treeView = new TreeView { - return null; - } - }), + DataTemplates = new DataTemplates + { + new TreeDataTemplate(GetHeader, x => x.Children), + new TreeDataTemplate(GetHeader, x => x.Children), + }, + [!TreeView.ItemsProperty] = tabStrip.WhenAnyValue(x => x.SelectedTab.Tag), + [Grid.RowProperty] = 1, + }) + } }; var detailsView = new ContentControl @@ -48,8 +69,8 @@ namespace Perspex.Diagnostics }, [!ContentControl.ContentProperty] = treeView[!TreeView.SelectedItemProperty] .Where(x => x != null) - .Cast() - .Select(x => new ControlDetails(x.Visual)), + .Cast() + .Select(x => new ControlDetails(x.Control)), [Grid.ColumnProperty] = 2, }; @@ -69,7 +90,7 @@ namespace Perspex.Diagnostics }, Children = new Controls { - treeView, + treePane, splitter, detailsView, } @@ -127,6 +148,26 @@ namespace Perspex.Diagnostics }; } + private static Control GetHeader(LogicalTreeNode node) + { + return new StackPanel + { + Orientation = Orientation.Horizontal, + Gap = 8, + Children = new Controls + { + new TextBlock + { + Text = node.Type, + }, + new TextBlock + { + [!TextBlock.TextProperty] = node.WhenAnyValue(x => x.Classes), + } + } + }; + } + private static Control GetHeader(VisualTreeNode node) { return new StackPanel diff --git a/Perspex.Diagnostics/Perspex.Diagnostics.csproj b/Perspex.Diagnostics/Perspex.Diagnostics.csproj index c33de242fd..53948a659a 100644 --- a/Perspex.Diagnostics/Perspex.Diagnostics.csproj +++ b/Perspex.Diagnostics/Perspex.Diagnostics.csproj @@ -76,6 +76,8 @@ + + diff --git a/Perspex.Diagnostics/ViewModels/ControlDetails.cs b/Perspex.Diagnostics/ViewModels/ControlDetails.cs index f6fa82fdb4..7d1d9ff301 100644 --- a/Perspex.Diagnostics/ViewModels/ControlDetails.cs +++ b/Perspex.Diagnostics/ViewModels/ControlDetails.cs @@ -8,18 +8,16 @@ namespace Perspex.Diagnostics.ViewModels { using System.Collections.Generic; using System.Linq; - using Perspex.Styling; + using Perspex.Controls; using ReactiveUI; internal class ControlDetails : ReactiveObject { - public ControlDetails(IVisual visual) + public ControlDetails(Control control) { - PerspexObject po = visual as PerspexObject; - - if (po != null) + if (control != null) { - this.Properties = po.GetAllValues() + this.Properties = control.GetAllValues() .Select(x => new PropertyDetails(x)) .OrderBy(x => x.Name); } diff --git a/Perspex.Diagnostics/ViewModels/LogicalTreeNode.cs b/Perspex.Diagnostics/ViewModels/LogicalTreeNode.cs new file mode 100644 index 0000000000..cf77931eb2 --- /dev/null +++ b/Perspex.Diagnostics/ViewModels/LogicalTreeNode.cs @@ -0,0 +1,29 @@ +// ----------------------------------------------------------------------- +// +// Copyright 2014 MIT Licence. See licence.md for more information. +// +// ----------------------------------------------------------------------- + +namespace Perspex.Diagnostics.ViewModels +{ + using System; + using Perspex.Controls; + using ReactiveUI; + + internal class LogicalTreeNode : TreeNode + { + private string classes; + + public LogicalTreeNode(ILogical logical) + : base((Control)logical) + { + this.Children = logical.LogicalChildren.CreateDerivedCollection(x => new LogicalTreeNode(x)); + } + + public static LogicalTreeNode[] Create(object control) + { + var logical = control as ILogical; + return logical != null ? new[] { new LogicalTreeNode(logical) } : null; + } + } +} diff --git a/Perspex.Diagnostics/ViewModels/TreeNode.cs b/Perspex.Diagnostics/ViewModels/TreeNode.cs new file mode 100644 index 0000000000..c0ecfbb395 --- /dev/null +++ b/Perspex.Diagnostics/ViewModels/TreeNode.cs @@ -0,0 +1,63 @@ +// ----------------------------------------------------------------------- +// +// Copyright 2014 MIT Licence. See licence.md for more information. +// +// ----------------------------------------------------------------------- + +namespace Perspex.Diagnostics.ViewModels +{ + using System; + using System.Reactive; + using System.Reactive.Linq; + using Perspex.Controls; + using ReactiveUI; + + internal class TreeNode : ReactiveObject + { + private string classes; + + public TreeNode(Control control) + { + this.Control = control; + this.Type = control.GetType().Name; + + control.Classes.Changed.Select(_ => Unit.Default) + .StartWith(Unit.Default) + .Subscribe(_ => + { + if (control.Classes.Count > 0) + { + this.Classes = "(" + string.Join(" ", control.Classes) + ")"; + } + else + { + this.Classes = string.Empty; + } + }); + } + + public IReactiveDerivedList Children + { + get; + protected set; + } + + public string Classes + { + get { return this.classes; } + private set { this.RaiseAndSetIfChanged(ref this.classes, value); } + } + + public string Type + { + get; + private set; + } + + public Control Control + { + get; + private set; + } + } +} diff --git a/Perspex.Diagnostics/ViewModels/VisualTreeNode.cs b/Perspex.Diagnostics/ViewModels/VisualTreeNode.cs index cd96c02f8a..e76983e2c0 100644 --- a/Perspex.Diagnostics/ViewModels/VisualTreeNode.cs +++ b/Perspex.Diagnostics/ViewModels/VisualTreeNode.cs @@ -6,55 +6,30 @@ namespace Perspex.Diagnostics.ViewModels { - using System; - using System.Reactive; - using System.Reactive.Linq; using Perspex.Controls; - using Perspex.Styling; using ReactiveUI; - internal class VisualTreeNode : ReactiveObject + internal class VisualTreeNode : TreeNode { private string classes; public VisualTreeNode(IVisual visual) + : base((Control)visual) { this.Children = visual.VisualChildren.CreateDerivedCollection(x => new VisualTreeNode(x)); - this.Type = visual.GetType().Name; - this.Visual = visual; - Control control = visual as Control; - - if (control != null) + if (this.Control != null) { - this.IsInTemplate = control.TemplatedParent != null; - - control.Classes.Changed.Select(_ => Unit.Default).StartWith(Unit.Default).Subscribe(_ => - { - if (control.Classes.Count > 0) - { - this.Classes = "(" + string.Join(" ", control.Classes) + ")"; - } - else - { - this.Classes = string.Empty; - } - }); + this.IsInTemplate = this.Control.TemplatedParent != null; } } - public IReactiveDerivedList Children { get; private set; } + public bool IsInTemplate { get; private set; } - public string Classes + public static VisualTreeNode[] Create(object control) { - get { return this.classes; } - private set { this.RaiseAndSetIfChanged(ref this.classes, value); } + var visual = control as IVisual; + return visual != null ? new[] { new VisualTreeNode(visual) } : null; } - - public bool IsInTemplate { get; private set; } - - public string Type { get; private set; } - - public IVisual Visual { get; private set; } } } diff --git a/Perspex.Themes.Default/ButtonStyle.cs b/Perspex.Themes.Default/ButtonStyle.cs index e32d4517ff..bc64c8f5f1 100644 --- a/Perspex.Themes.Default/ButtonStyle.cs +++ b/Perspex.Themes.Default/ButtonStyle.cs @@ -78,7 +78,7 @@ namespace Perspex.Themes.Default Padding = new Thickness(3), Content = new ContentPresenter { - Id = "presenter", + Id = "contentPresenter", [~ContentPresenter.ContentProperty] = control[~Button.ContentProperty], [~ContentPresenter.HorizontalAlignmentProperty] = control[~Button.HorizontalContentAlignmentProperty], [~ContentPresenter.VerticalAlignmentProperty] = control[~Button.VerticalContentAlignmentProperty], diff --git a/Perspex.Themes.Default/CheckBoxStyle.cs b/Perspex.Themes.Default/CheckBoxStyle.cs index 2ee0b0105c..2c8c9ed48b 100644 --- a/Perspex.Themes.Default/CheckBoxStyle.cs +++ b/Perspex.Themes.Default/CheckBoxStyle.cs @@ -83,7 +83,7 @@ namespace Perspex.Themes.Default }, new ContentPresenter { - Id = "presenter", + Id = "contentPresenter", Margin = new Thickness(4, 0, 0, 0), VerticalAlignment = VerticalAlignment.Center, [~ContentPresenter.ContentProperty] = control[~CheckBox.ContentProperty], diff --git a/Perspex.Themes.Default/ContentControlStyle.cs b/Perspex.Themes.Default/ContentControlStyle.cs index 1dac4417f9..4060c3bd44 100644 --- a/Perspex.Themes.Default/ContentControlStyle.cs +++ b/Perspex.Themes.Default/ContentControlStyle.cs @@ -32,7 +32,7 @@ namespace Perspex.Themes.Default { return new ContentPresenter { - Id = "presenter", + Id = "contentPresenter", [~ContentPresenter.ContentProperty] = control[~ContentControl.ContentProperty], }; } diff --git a/Perspex.Themes.Default/RadioButtonStyle.cs b/Perspex.Themes.Default/RadioButtonStyle.cs index ac56bdcd61..0393f4ea86 100644 --- a/Perspex.Themes.Default/RadioButtonStyle.cs +++ b/Perspex.Themes.Default/RadioButtonStyle.cs @@ -82,7 +82,7 @@ namespace Perspex.Themes.Default }, new ContentPresenter { - Id = "presenter", + Id = "contentPresenter", Margin = new Thickness(4, 0, 0, 0), VerticalAlignment = VerticalAlignment.Center, [~ContentPresenter.ContentProperty] = control[~RadioButton.ContentProperty], diff --git a/Perspex.Themes.Default/ScrollViewerStyle.cs b/Perspex.Themes.Default/ScrollViewerStyle.cs index a1d51c9024..e87a802503 100644 --- a/Perspex.Themes.Default/ScrollViewerStyle.cs +++ b/Perspex.Themes.Default/ScrollViewerStyle.cs @@ -47,7 +47,7 @@ namespace Perspex.Themes.Default { new ScrollContentPresenter { - Id = "presenter", + Id = "scrollContentPresenter", [~ScrollContentPresenter.ContentProperty] = control[~ScrollViewer.ContentProperty], [~~ScrollContentPresenter.ExtentProperty] = control[~~ScrollViewer.ExtentProperty], [~~ScrollContentPresenter.OffsetProperty] = control[~~ScrollViewer.OffsetProperty], diff --git a/Perspex.Themes.Default/TabControlStyle.cs b/Perspex.Themes.Default/TabControlStyle.cs index b902e46a75..b3b919374a 100644 --- a/Perspex.Themes.Default/TabControlStyle.cs +++ b/Perspex.Themes.Default/TabControlStyle.cs @@ -46,6 +46,7 @@ namespace Perspex.Themes.Default }, new ContentPresenter { + Id = "contentPresenter", [~ContentPresenter.ContentProperty] = control[~TabControl.SelectedContentProperty], [Grid.RowProperty] = 1, } diff --git a/Perspex.Themes.Default/TabItemStyle.cs b/Perspex.Themes.Default/TabItemStyle.cs index 93c38f03a4..459e0df884 100644 --- a/Perspex.Themes.Default/TabItemStyle.cs +++ b/Perspex.Themes.Default/TabItemStyle.cs @@ -41,7 +41,7 @@ namespace Perspex.Themes.Default { return new ContentPresenter { - Id = "presenter", + Id = "contentPresenter", [~ContentPresenter.ContentProperty] = control[~TabItem.HeaderProperty], }; } diff --git a/Perspex.Themes.Default/TabStripStyle.cs b/Perspex.Themes.Default/TabStripStyle.cs index 6a46bee554..eed4683886 100644 --- a/Perspex.Themes.Default/TabStripStyle.cs +++ b/Perspex.Themes.Default/TabStripStyle.cs @@ -40,6 +40,7 @@ namespace Perspex.Themes.Default { return new ItemsPresenter { + Id = "itemsPresenter", [~ItemsPresenter.ItemsProperty] = control[~ItemsControl.ItemsProperty], [~ItemsPresenter.ItemsPanelProperty] = control[~ItemsControl.ItemsPanelProperty], }; diff --git a/Perspex.Themes.Default/ToggleButtonStyle.cs b/Perspex.Themes.Default/ToggleButtonStyle.cs index 038c934143..1106030ea2 100644 --- a/Perspex.Themes.Default/ToggleButtonStyle.cs +++ b/Perspex.Themes.Default/ToggleButtonStyle.cs @@ -93,7 +93,7 @@ namespace Perspex.Themes.Default Padding = new Thickness(3), Content = new ContentPresenter { - Id = "presenter", + Id = "contentPresenter", [~ContentPresenter.ContentProperty] = control[~ToggleButton.ContentProperty], [~ContentPresenter.HorizontalAlignmentProperty] = control[~ToggleButton.HorizontalContentAlignmentProperty], [~ContentPresenter.VerticalAlignmentProperty] = control[~ToggleButton.VerticalContentAlignmentProperty], diff --git a/Perspex.Themes.Default/WindowStyle.cs b/Perspex.Themes.Default/WindowStyle.cs index 0e163d55aa..a6380b5017 100644 --- a/Perspex.Themes.Default/WindowStyle.cs +++ b/Perspex.Themes.Default/WindowStyle.cs @@ -37,7 +37,7 @@ namespace Perspex.Themes.Default [~Border.BackgroundProperty] = control[~Window.BackgroundProperty], Content = new ContentPresenter { - Id = "presenter", + Id = "contentPresenter", [~ContentPresenter.ContentProperty] = control[~Window.ContentProperty], } };