diff --git a/Perspex.Controls/Templates/DataTemplateExtensions.cs b/Perspex.Controls/Templates/DataTemplateExtensions.cs index 9d4575e3db..f9b326c366 100644 --- a/Perspex.Controls/Templates/DataTemplateExtensions.cs +++ b/Perspex.Controls/Templates/DataTemplateExtensions.cs @@ -21,7 +21,7 @@ namespace Perspex.Controls.Templates { result = template.Build(data); - if (result != null) + if (result != null && result.DataContext == null) { result.DataContext = data; } diff --git a/Perspex.Diagnostics/DevTools.cs b/Perspex.Diagnostics/DevTools.cs index 4149b84ce2..5cbe24dc6f 100644 --- a/Perspex.Diagnostics/DevTools.cs +++ b/Perspex.Diagnostics/DevTools.cs @@ -6,107 +6,26 @@ namespace Perspex.Diagnostics { - using System; - using System.Reactive.Disposables; - using System.Reactive.Linq; using Perspex.Controls; - using Perspex.Controls.Primitives; - using Perspex.Controls.Shapes; using Perspex.Diagnostics.ViewModels; using Perspex.Input; - using Perspex.Interactivity; - using Perspex.Layout; - using Perspex.Media; using ReactiveUI; + using System; + using System.Reactive.Linq; public class DevTools : Decorator { public static readonly PerspexProperty RootProperty = PerspexProperty.Register("Root"); - private Control adorner; + private DevToolsViewModel viewModel; public DevTools() { - TabStrip tabStrip; - TreeView treeView; - - var treePane = new Grid - { - RowDefinitions = new RowDefinitions - { - new RowDefinition(GridLength.Auto), - new RowDefinition(new GridLength(1, GridUnitType.Star)), - }, - Children = new Controls - { - (tabStrip = new TabStrip - { - 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)), - } - }, - }), - (treeView = new TreeView - { - DataTemplates = new DataTemplates - { - new TreeDataTemplate(this.GetHeader, x => x.Children), - new TreeDataTemplate(this.GetHeader, x => x.Children), - }, - [!TreeView.ItemsProperty] = tabStrip.WhenAnyValue(x => x.SelectedTab.Tag), - [Grid.RowProperty] = 1, - }) - } - }; + this.viewModel = new DevToolsViewModel(); + this.GetObservable(RootProperty).Subscribe(x => this.viewModel.Root = x); - var detailsView = new ScrollViewer - { - Content = new ContentControl - { - DataTemplates = new DataTemplates - { - new DataTemplate(CreateDetailsView), - }, - [!ContentControl.ContentProperty] = treeView[!TreeView.SelectedItemProperty] - .Where(x => x != null) - .Cast() - .Select(x => new ControlDetails(x.Control)), - }, - [Grid.ColumnProperty] = 2, - }; - - var splitter = new GridSplitter - { - [Grid.ColumnProperty] = 1, - Width = 4, - }; - - this.Content = new Grid - { - ColumnDefinitions = new ColumnDefinitions - { - new ColumnDefinition(1, GridUnitType.Star), - new ColumnDefinition(4, GridUnitType.Pixel), - new ColumnDefinition(3, GridUnitType.Star), - }, - Children = new Controls - { - treePane, - splitter, - detailsView, - } - }; + this.InitializeComponent(); } public Control Root @@ -115,9 +34,12 @@ namespace Perspex.Diagnostics set { this.SetValue(RootProperty, value); } } - public static IDisposable Attach(Window w) + public static IDisposable Attach(Window window) { - return w.AddHandler(Window.KeyDownEvent, WindowPreviewKeyDown, Interactivity.RoutingStrategies.Tunnel); + return window.AddHandler( + Window.KeyDownEvent, + WindowPreviewKeyDown, + Interactivity.RoutingStrategies.Tunnel); } private static void WindowPreviewKeyDown(object sender, KeyEventArgs e) @@ -138,104 +60,52 @@ namespace Perspex.Diagnostics } } - private static Control CreateDetailsView(ControlDetails i) + private void InitializeComponent() { - return new ItemsControl - { - DataTemplates = new DataTemplates - { - new DataTemplate(x => - new StackPanel - { - Gap = 16, - Orientation = Orientation.Horizontal, - Children = new Controls - { - new TextBlock { Text = x.Name }, - new TextBlock { [!TextBlock.TextProperty] = x.WhenAnyValue(v => v.Value).Select(v => v?.ToString()) }, - new TextBlock { Text = x.Priority }, - }, - }), - }, - Items = i.Properties, - }; - } + this.DataTemplates.Add(new ViewLocator()); - private Control GetHeader(LogicalTreeNode node) - { - var result = new StackPanel + this.Content = new TabControl { - Orientation = Orientation.Horizontal, - Gap = 8, - Children = new Controls + Items = new[] { - new TextBlock + new TabItem { - Text = node.Type, + Header = "Logical Tree", + [!TabItem.ContentProperty] = this.viewModel.WhenAnyValue(x => x.LogicalTree), }, - new TextBlock + new TabItem { - [!TextBlock.TextProperty] = node.WhenAnyValue(x => x.Classes), + Header = "Visual Tree", + [!TabItem.ContentProperty] = this.viewModel.WhenAnyValue(x => x.VisualTree), } - } - }; - - result.PointerEnter += this.AddAdorner; - result.PointerLeave += this.RemoveAdorner; - - return result; - } - - private Control GetHeader(VisualTreeNode node) - { - var result = new StackPanel - { - Orientation = Orientation.Horizontal, - Gap = 8, - Children = new Controls - { - new TextBlock - { - Text = node.Type, - FontStyle = node.IsInTemplate ? Media.FontStyle.Italic : Media.FontStyle.Normal, - }, - new TextBlock - { - [!TextBlock.TextProperty] = node.WhenAnyValue(x => x.Classes), - } - } + }, }; - - result.PointerEnter += this.AddAdorner; - result.PointerLeave += this.RemoveAdorner; - - return result; } - private void AddAdorner(object sender, PointerEventArgs e) - { - var node = (TreeNode)((Control)sender).DataContext; - var layer = AdornerLayer.GetAdornerLayer(node.Control); - - if (layer != null) - { - this.adorner = new Rectangle - { - Fill = new SolidColorBrush(0x80a0c5e8), - [AdornerLayer.AdornedElementProperty] = node.Control, - }; - - layer.Children.Add(this.adorner); - } - } - - private void RemoveAdorner(object sender, PointerEventArgs e) - { - if (this.adorner != null) - { - ((Panel)this.adorner.Parent).Children.Remove(this.adorner); - this.adorner = null; - } - } + //private Control GetHeader(VisualTreeNode node) + //{ + // var result = new StackPanel + // { + // Orientation = Orientation.Horizontal, + // Gap = 8, + // Children = new Controls + // { + // new TextBlock + // { + // Text = node.Type, + // FontStyle = node.IsInTemplate ? Media.FontStyle.Italic : Media.FontStyle.Normal, + // }, + // new TextBlock + // { + // [!TextBlock.TextProperty] = node.WhenAnyValue(x => x.Classes), + // } + // } + // }; + + // result.PointerEnter += this.AddAdorner; + // result.PointerLeave += this.RemoveAdorner; + + // return result; + //} } } diff --git a/Perspex.Diagnostics/Perspex.Diagnostics.csproj b/Perspex.Diagnostics/Perspex.Diagnostics.csproj index 15a04c969e..97e263d315 100644 --- a/Perspex.Diagnostics/Perspex.Diagnostics.csproj +++ b/Perspex.Diagnostics/Perspex.Diagnostics.csproj @@ -74,11 +74,19 @@ + + + + - + + + + + diff --git a/Perspex.Diagnostics/ViewLocator.cs b/Perspex.Diagnostics/ViewLocator.cs new file mode 100644 index 0000000000..7da3ba0489 --- /dev/null +++ b/Perspex.Diagnostics/ViewLocator.cs @@ -0,0 +1,34 @@ +// ----------------------------------------------------------------------- +// +// Copyright 2015 MIT Licence. See licence.md for more information. +// +// ----------------------------------------------------------------------- + +namespace Perspex.Diagnostics +{ + using System; + using Perspex.Controls; + + internal class ViewLocator : IDataTemplate + { + public Control Build(object data) + { + var name = data.GetType().FullName.Replace("ViewModel", "View"); + var type = Type.GetType(name); + + if (type != null) + { + return (Control)Activator.CreateInstance(type); + } + else + { + return new TextBlock { Text = name }; + } + } + + public bool Match(object data) + { + return data is TViewModel; + } + } +} \ No newline at end of file diff --git a/Perspex.Diagnostics/ViewModels/ControlDetails.cs b/Perspex.Diagnostics/ViewModels/ControlDetailsViewModel.cs similarity index 82% rename from Perspex.Diagnostics/ViewModels/ControlDetails.cs rename to Perspex.Diagnostics/ViewModels/ControlDetailsViewModel.cs index 7d1d9ff301..cd31c1655a 100644 --- a/Perspex.Diagnostics/ViewModels/ControlDetails.cs +++ b/Perspex.Diagnostics/ViewModels/ControlDetailsViewModel.cs @@ -1,5 +1,5 @@ // ----------------------------------------------------------------------- -// +// // Copyright 2014 MIT Licence. See licence.md for more information. // // ----------------------------------------------------------------------- @@ -11,9 +11,9 @@ namespace Perspex.Diagnostics.ViewModels using Perspex.Controls; using ReactiveUI; - internal class ControlDetails : ReactiveObject + internal class ControlDetailsViewModel : ReactiveObject { - public ControlDetails(Control control) + public ControlDetailsViewModel(Control control) { if (control != null) { diff --git a/Perspex.Diagnostics/ViewModels/DevToolsViewModel.cs b/Perspex.Diagnostics/ViewModels/DevToolsViewModel.cs new file mode 100644 index 0000000000..e1342551fd --- /dev/null +++ b/Perspex.Diagnostics/ViewModels/DevToolsViewModel.cs @@ -0,0 +1,48 @@ +// ----------------------------------------------------------------------- +// +// Copyright 2015 MIT Licence. See licence.md for more information. +// +// ----------------------------------------------------------------------- + +namespace Perspex.Diagnostics.ViewModels +{ + using System; + using Perspex.Controls; + using ReactiveUI; + + internal class DevToolsViewModel : ReactiveObject + { + private Control root; + + private LogicalTreeViewModel logicalTree; + + private VisualTreeViewModel visualTree; + + public DevToolsViewModel() + { + this.WhenAnyValue(x => x.Root).Subscribe(x => + { + this.LogicalTree = new LogicalTreeViewModel(root); + this.VisualTree = new VisualTreeViewModel(root); + }); + } + + public Control Root + { + get { return this.root; } + set { this.RaiseAndSetIfChanged(ref this.root, value); } + } + + public LogicalTreeViewModel LogicalTree + { + get { return this.logicalTree; } + private set { this.RaiseAndSetIfChanged(ref this.logicalTree, value); } + } + + public VisualTreeViewModel VisualTree + { + get { return this.visualTree; } + private set { this.RaiseAndSetIfChanged(ref this.visualTree, value); } + } + } +} diff --git a/Perspex.Diagnostics/ViewModels/LogicalTreeViewModel.cs b/Perspex.Diagnostics/ViewModels/LogicalTreeViewModel.cs new file mode 100644 index 0000000000..b6766a61cb --- /dev/null +++ b/Perspex.Diagnostics/ViewModels/LogicalTreeViewModel.cs @@ -0,0 +1,40 @@ +// ----------------------------------------------------------------------- +// +// Copyright 2015 MIT Licence. See licence.md for more information. +// +// ----------------------------------------------------------------------- + +namespace Perspex.Diagnostics.ViewModels +{ + using System.Reactive.Linq; + using Perspex.Controls; + using ReactiveUI; + + internal class LogicalTreeViewModel : ReactiveObject + { + private LogicalTreeNode selected; + + private ObservableAsPropertyHelper details; + + public LogicalTreeViewModel(Control root) + { + this.Nodes = LogicalTreeNode.Create(root); + this.details = this.WhenAnyValue(x => x.SelectedNode) + .Select(x => x != null ? new ControlDetailsViewModel(x.Control) : null) + .ToProperty(this, x => x.Details); + } + + public LogicalTreeNode[] Nodes { get; } + + public LogicalTreeNode SelectedNode + { + get { return this.selected; } + set { this.RaiseAndSetIfChanged(ref this.selected, value); } + } + + public ControlDetailsViewModel Details + { + get { return this.details.Value; } + } + } +} diff --git a/Perspex.Diagnostics/ViewModels/VisualTreeViewModel.cs b/Perspex.Diagnostics/ViewModels/VisualTreeViewModel.cs new file mode 100644 index 0000000000..8a32b6e3f0 --- /dev/null +++ b/Perspex.Diagnostics/ViewModels/VisualTreeViewModel.cs @@ -0,0 +1,40 @@ +// ----------------------------------------------------------------------- +// +// Copyright 2015 MIT Licence. See licence.md for more information. +// +// ----------------------------------------------------------------------- + +namespace Perspex.Diagnostics.ViewModels +{ + using System.Reactive.Linq; + using Perspex.Controls; + using ReactiveUI; + + internal class VisualTreeViewModel : ReactiveObject + { + private VisualTreeNode selected; + + private ObservableAsPropertyHelper details; + + public VisualTreeViewModel(Control root) + { + this.Nodes = VisualTreeNode.Create(root); + this.details = this.WhenAnyValue(x => x.SelectedNode) + .Select(x => x != null ? new ControlDetailsViewModel(x.Control) : null) + .ToProperty(this, x => x.Details); + } + + public VisualTreeNode[] Nodes { get; } + + public VisualTreeNode SelectedNode + { + get { return this.selected; } + set { this.RaiseAndSetIfChanged(ref this.selected, value); } + } + + public ControlDetailsViewModel Details + { + get { return this.details.Value; } + } + } +} diff --git a/Perspex.Diagnostics/Views/ControlDetailsView.cs b/Perspex.Diagnostics/Views/ControlDetailsView.cs new file mode 100644 index 0000000000..3fb323026d --- /dev/null +++ b/Perspex.Diagnostics/Views/ControlDetailsView.cs @@ -0,0 +1,59 @@ +// ----------------------------------------------------------------------- +// +// Copyright 2014 MIT Licence. See licence.md for more information. +// +// ----------------------------------------------------------------------- + +namespace Perspex.Diagnostics.Views +{ + using Perspex.Controls; + using Perspex.Diagnostics.ViewModels; + using ReactiveUI; + using System; + using System.Reactive.Linq; + + internal class ControlDetailsView : UserControl + { + private static readonly PerspexProperty ViewModelProperty = + PerspexProperty.Register("ViewModel"); + + public ControlDetailsView() + { + this.InitializeComponent(); + this.GetObservable(DataContextProperty) + .Subscribe(x => this.ViewModel = (ControlDetailsViewModel)x); + } + + public ControlDetailsViewModel ViewModel + { + get { return this.GetValue(ViewModelProperty); } + private set { this.SetValue(ViewModelProperty, value); } + } + + private void InitializeComponent() + { + this.Content = new ScrollViewer + { + Content = new ItemsControl + { + DataTemplates = new DataTemplates + { + new DataTemplate(x => + new StackPanel + { + Gap = 16, + Orientation = Orientation.Horizontal, + Children = new Controls + { + new TextBlock { Text = x.Name }, + new TextBlock { [!TextBlock.TextProperty] = x.WhenAnyValue(v => v.Value).Select(v => v?.ToString()) }, + new TextBlock { Text = x.Priority }, + }, + }), + }, + [!ItemsControl.ItemsProperty] = this.WhenAnyValue(x => x.ViewModel.Properties), + } + }; + } + } +} diff --git a/Perspex.Diagnostics/Views/LogicalTreeView.cs b/Perspex.Diagnostics/Views/LogicalTreeView.cs new file mode 100644 index 0000000000..53f1435023 --- /dev/null +++ b/Perspex.Diagnostics/Views/LogicalTreeView.cs @@ -0,0 +1,98 @@ +// ----------------------------------------------------------------------- +// +// Copyright 2014 MIT Licence. See licence.md for more information. +// +// ----------------------------------------------------------------------- + +namespace Perspex.Diagnostics.Views +{ + using Perspex.Controls; + using Perspex.Diagnostics.ViewModels; + using ReactiveUI; + using System; + using System.Reactive.Linq; + + internal class LogicalTreeView : TreePage + { + private static readonly PerspexProperty ViewModelProperty = + PerspexProperty.Register("ViewModel"); + + public LogicalTreeView() + { + this.InitializeComponent(); + this.GetObservable(DataContextProperty) + .Subscribe(x => this.ViewModel = (LogicalTreeViewModel)x); + } + + public LogicalTreeViewModel ViewModel + { + get { return this.GetValue(ViewModelProperty); } + private set { this.SetValue(ViewModelProperty, value); } + } + + private void InitializeComponent() + { + TreeView tree; + + this.Content = new Grid + { + ColumnDefinitions = new ColumnDefinitions + { + new ColumnDefinition(1, GridUnitType.Star), + new ColumnDefinition(4, GridUnitType.Pixel), + new ColumnDefinition(3, GridUnitType.Star), + }, + Children = new Controls + { + (tree = new TreeView + { + DataTemplates = new DataTemplates + { + new TreeDataTemplate(this.GetHeader, x => x.Children), + }, + [!TreeView.ItemsProperty] = this.WhenAnyValue(x => x.ViewModel.Nodes), + }), + new GridSplitter + { + [Grid.ColumnProperty] = 1, + Width = 4, + }, + new ContentControl + { + [!ContentControl.ContentProperty] = this.WhenAnyValue(x => x.ViewModel.Details), + [Grid.ColumnProperty] = 2, + } + } + }; + + tree.GetObservable(TreeView.SelectedItemProperty) + .OfType() + .Subscribe(x => this.ViewModel.SelectedNode = x); + } + + private Control GetHeader(LogicalTreeNode node) + { + var result = new StackPanel + { + Orientation = Orientation.Horizontal, + Gap = 8, + Children = new Controls + { + new TextBlock + { + Text = node.Type, + }, + new TextBlock + { + [!TextBlock.TextProperty] = node.WhenAnyValue(x => x.Classes), + } + } + }; + + result.PointerEnter += this.AddAdorner; + result.PointerLeave += this.RemoveAdorner; + + return result; + } + } +} diff --git a/Perspex.Diagnostics/Views/TreePage.cs b/Perspex.Diagnostics/Views/TreePage.cs new file mode 100644 index 0000000000..4e498ba8f6 --- /dev/null +++ b/Perspex.Diagnostics/Views/TreePage.cs @@ -0,0 +1,46 @@ +// ----------------------------------------------------------------------- +// +// Copyright 2014 MIT Licence. See licence.md for more information. +// +// ----------------------------------------------------------------------- + +namespace Perspex.Diagnostics.Views +{ + using Perspex.Controls; + using Perspex.Controls.Primitives; + using Perspex.Controls.Shapes; + using Perspex.Diagnostics.ViewModels; + using Perspex.Input; + using Perspex.Media; + + internal class TreePage : UserControl + { + private Control adorner; + + protected void AddAdorner(object sender, PointerEventArgs e) + { + var node = (TreeNode)((Control)sender).DataContext; + var layer = AdornerLayer.GetAdornerLayer(node.Control); + + if (layer != null) + { + this.adorner = new Rectangle + { + Fill = new SolidColorBrush(0x80a0c5e8), + [AdornerLayer.AdornedElementProperty] = node.Control, + }; + + layer.Children.Add(this.adorner); + } + } + + protected void RemoveAdorner(object sender, PointerEventArgs e) + { + if (this.adorner != null) + { + ((Panel)this.adorner.Parent).Children.Remove(this.adorner); + this.adorner = null; + } + } + } +} diff --git a/Perspex.Diagnostics/Views/VisualTreeView.cs b/Perspex.Diagnostics/Views/VisualTreeView.cs new file mode 100644 index 0000000000..a1130fb957 --- /dev/null +++ b/Perspex.Diagnostics/Views/VisualTreeView.cs @@ -0,0 +1,100 @@ +// ----------------------------------------------------------------------- +// +// Copyright 2015 MIT Licence. See licence.md for more information. +// +// ----------------------------------------------------------------------- + +namespace Perspex.Diagnostics.Views +{ + using System; + using System.Reactive.Linq; + using Perspex.Controls; + using Perspex.Diagnostics.ViewModels; + using Perspex.Media; + using ReactiveUI; + + internal class VisualTreeView : TreePage + { + private static readonly PerspexProperty ViewModelProperty = + PerspexProperty.Register("ViewModel"); + + public VisualTreeView() + { + this.InitializeComponent(); + this.GetObservable(DataContextProperty) + .Subscribe(x => this.ViewModel = (VisualTreeViewModel)x); + } + + public VisualTreeViewModel ViewModel + { + get { return this.GetValue(ViewModelProperty); } + private set { this.SetValue(ViewModelProperty, value); } + } + + private void InitializeComponent() + { + TreeView tree; + + this.Content = new Grid + { + ColumnDefinitions = new ColumnDefinitions + { + new ColumnDefinition(1, GridUnitType.Star), + new ColumnDefinition(4, GridUnitType.Pixel), + new ColumnDefinition(3, GridUnitType.Star), + }, + Children = new Controls + { + (tree = new TreeView + { + DataTemplates = new DataTemplates + { + new TreeDataTemplate(this.GetHeader, x => x.Children), + }, + [!TreeView.ItemsProperty] = this.WhenAnyValue(x => x.ViewModel.Nodes), + }), + new GridSplitter + { + [Grid.ColumnProperty] = 1, + Width = 4, + }, + new ContentControl + { + [!ContentControl.ContentProperty] = this.WhenAnyValue(x => x.ViewModel.Details), + [Grid.ColumnProperty] = 2, + } + } + }; + + tree.GetObservable(TreeView.SelectedItemProperty) + .OfType() + .Subscribe(x => this.ViewModel.SelectedNode = x); + } + + private Control GetHeader(VisualTreeNode node) + { + var result = new StackPanel + { + Orientation = Orientation.Horizontal, + Gap = 8, + Children = new Controls + { + new TextBlock + { + FontStyle = node.IsInTemplate ? FontStyle.Italic : FontStyle.Normal, + Text = node.Type, + }, + new TextBlock + { + [!TextBlock.TextProperty] = node.WhenAnyValue(x => x.Classes), + } + } + }; + + result.PointerEnter += this.AddAdorner; + result.PointerLeave += this.RemoveAdorner; + + return result; + } + } +} diff --git a/Perspex.Themes.Default/DropDownStyle.cs b/Perspex.Themes.Default/DropDownStyle.cs index ada87df2a1..789696a23b 100644 --- a/Perspex.Themes.Default/DropDownStyle.cs +++ b/Perspex.Themes.Default/DropDownStyle.cs @@ -98,7 +98,7 @@ namespace Perspex.Themes.Default }, PlacementTarget = control, StaysOpen = false, - [~~Popup.IsOpenProperty] = control[~~DropDown.IsDropDownOpenProperty], + [!Popup.IsOpenProperty] = control[!DropDown.IsDropDownOpenProperty], [~Popup.MinWidthProperty] = control[~DropDown.BoundsProperty].Cast().Select(x => (object)x.Width), } },