diff --git a/Perspex.Base/Diagnostics/PerspexObjectExtensions.cs b/Perspex.Base/Diagnostics/PerspexObjectExtensions.cs new file mode 100644 index 0000000000..76bf1ca0ba --- /dev/null +++ b/Perspex.Base/Diagnostics/PerspexObjectExtensions.cs @@ -0,0 +1,35 @@ +// -------------------------------------------------------------------- +// +// Copyright 2014 MIT Licence. See licence.md for more information. +// +// ----------------------------------------------------------------------- + +namespace Perspex.Diagnostics +{ + public static class PerspexObjectExtensions + { + public static PerspexPropertyValue GetDiagnostic(this PerspexObject o, PerspexProperty property) + { + var set = o.GetSetValues(); + + PriorityValue value; + + if (set.TryGetValue(property, out value)) + { + return new PerspexPropertyValue( + property, + value.Value, + (BindingPriority)value.ValuePriority, + value.GetDiagnostic()); + } + else + { + return new PerspexPropertyValue( + property, + o.GetValue(property), + BindingPriority.Unset, + "Unset"); + } + } + } +} diff --git a/Perspex.Base/Diagnostics/PerspexPropertyValue.cs b/Perspex.Base/Diagnostics/PerspexPropertyValue.cs index 7a4392f3b4..74f5cbf418 100644 --- a/Perspex.Base/Diagnostics/PerspexPropertyValue.cs +++ b/Perspex.Base/Diagnostics/PerspexPropertyValue.cs @@ -11,17 +11,21 @@ namespace Perspex.Diagnostics public PerspexPropertyValue( PerspexProperty property, object value, - BindingPriority priority) + BindingPriority priority, + string diagnostic) { this.Property = property; this.Value = value; this.Priority = priority; + this.Diagnostic = diagnostic; } - public PerspexProperty Property { get; private set; } + public PerspexProperty Property { get; } - public object Value { get; private set; } + public object Value { get; } - public BindingPriority Priority { get; private set; } + public BindingPriority Priority { get; } + + public string Diagnostic { get; } } } diff --git a/Perspex.Base/Perspex.Base.csproj b/Perspex.Base/Perspex.Base.csproj index cd618b7f8f..d23f3650cb 100644 --- a/Perspex.Base/Perspex.Base.csproj +++ b/Perspex.Base/Perspex.Base.csproj @@ -37,6 +37,7 @@ + diff --git a/Perspex.Base/PerspexObject.cs b/Perspex.Base/PerspexObject.cs index 69a504b1f5..04a369c84b 100644 --- a/Perspex.Base/PerspexObject.cs +++ b/Perspex.Base/PerspexObject.cs @@ -409,35 +409,6 @@ namespace Perspex return (T)this.GetValue((PerspexProperty)property); } - /// - /// Gets the value of of all properties that are registered on this object. - /// - /// - /// A collection of objects. - /// - public IEnumerable GetAllValues() - { - foreach (PerspexProperty property in this.GetRegisteredProperties()) - { - PriorityValue value; - - if (this.values.TryGetValue(property, out value)) - { - yield return new PerspexPropertyValue( - property, - value.Value, - (BindingPriority)value.ValuePriority); - } - else - { - yield return new PerspexPropertyValue( - property, - this.GetValue(property), - BindingPriority.Unset); - } - } - } - /// /// Gets all properties that are registered on this object. /// @@ -464,20 +435,6 @@ namespace Perspex } } - /// - /// Gets all of the values explicitly set on this object. - /// - public IEnumerable GetSetValues() - { - foreach (var value in this.values) - { - yield return new PerspexPropertyValue( - value.Key, - value.Value.Value, - (BindingPriority)value.Value.ValuePriority); - } - } - /// /// Checks whether a is set on this object. /// @@ -681,6 +638,15 @@ namespace Perspex } } + /// + /// Gets all priority values set on the object. + /// + /// A collection of property/value tuples. + internal IDictionary GetSetValues() + { + return this.values; + } + /// /// Forces re-coercion of properties when a property value changes. /// diff --git a/Perspex.Base/PerspexProperty.cs b/Perspex.Base/PerspexProperty.cs index 9d41a8d1e9..c2eb1fb00d 100644 --- a/Perspex.Base/PerspexProperty.cs +++ b/Perspex.Base/PerspexProperty.cs @@ -361,7 +361,7 @@ namespace Perspex { public override string ToString() { - return "{Unset}"; + return "(unset)"; } } } diff --git a/Perspex.Base/PriorityValue.cs b/Perspex.Base/PriorityValue.cs index 7ec0bdda01..b22ef91890 100644 --- a/Perspex.Base/PriorityValue.cs +++ b/Perspex.Base/PriorityValue.cs @@ -11,7 +11,7 @@ namespace Perspex using System.Linq; using System.Reactive.Subjects; using System.Reflection; - using Perspex.Diagnostics; + using System.Text; /// /// Maintains a list of prioritised bindings together with a current value. @@ -171,6 +171,46 @@ namespace Perspex } } + /// + /// Returns diagnostic string that can help the user debug the bindings in effect on + /// this object. + /// + /// A diagnostic string. + public string GetDiagnostic() + { + var b = new StringBuilder(); + var first = true; + + foreach (var level in this.levels) + { + if (!first) + { + b.AppendLine(); + } + + b.Append(this.ValuePriority == level.Key ? "*" : ""); + b.Append("Priority "); + b.Append(level.Key); + b.Append(": "); + b.AppendLine(level.Value.Value?.ToString() ?? "(null)"); + b.AppendLine("--------"); + b.Append("Direct: "); + b.AppendLine(level.Value.DirectValue.ToString()); + + foreach (var binding in level.Value.Bindings) + { + b.Append(level.Value.ActiveBindingIndex == binding.Index ? "*" : ""); + b.Append(binding.Description ?? binding.Observable.GetType().Name); + b.Append(": "); + b.AppendLine(binding.Value.ToString()); + } + + first = false; + } + + return b.ToString(); + } + /// /// Causes a re-coercion of the value. /// diff --git a/Perspex.Diagnostics/Debug.cs b/Perspex.Diagnostics/Debug.cs index 27eea5fda0..ddf0716f15 100644 --- a/Perspex.Diagnostics/Debug.cs +++ b/Perspex.Diagnostics/Debug.cs @@ -40,16 +40,21 @@ namespace Perspex.Diagnostics builder.Append(" "); builder.AppendLine(control.Classes.ToString()); - foreach (var value in control.GetSetValues()) + foreach (var property in control.GetRegisteredProperties()) { - builder.Append(Indent(indent)); - builder.Append(" | "); - builder.Append(value.Property.Name); - builder.Append(" = "); - builder.Append(value.Value ?? "(null)"); - builder.Append(" ["); - builder.Append(value.Priority); - builder.AppendLine("]"); + var value = control.GetDiagnostic(property); + + if (value.Priority != BindingPriority.Unset) + { + builder.Append(Indent(indent)); + builder.Append(" | "); + builder.Append(value.Property.Name); + builder.Append(" = "); + builder.Append(value.Value ?? "(null)"); + builder.Append(" ["); + builder.Append(value.Priority); + builder.AppendLine("]"); + } } } else diff --git a/Perspex.Diagnostics/ViewModels/ControlDetailsViewModel.cs b/Perspex.Diagnostics/ViewModels/ControlDetailsViewModel.cs index 25d22bd364..9d2e61157a 100644 --- a/Perspex.Diagnostics/ViewModels/ControlDetailsViewModel.cs +++ b/Perspex.Diagnostics/ViewModels/ControlDetailsViewModel.cs @@ -17,8 +17,8 @@ namespace Perspex.Diagnostics.ViewModels { if (control != null) { - this.Properties = control.GetAllValues() - .Select(x => new PropertyDetails(x)) + this.Properties = control.GetRegisteredProperties() + .Select(x => new PropertyDetails(control, x)) .OrderBy(x => x.Name) .OrderBy(x => x.IsAttached); } diff --git a/Perspex.Diagnostics/ViewModels/PropertyDetails.cs b/Perspex.Diagnostics/ViewModels/PropertyDetails.cs index c3570aa5c7..30b25faa57 100644 --- a/Perspex.Diagnostics/ViewModels/PropertyDetails.cs +++ b/Perspex.Diagnostics/ViewModels/PropertyDetails.cs @@ -13,32 +13,43 @@ namespace Perspex.Diagnostics.ViewModels { private object value; - public PropertyDetails(PerspexPropertyValue value) + private string priority; + + private string diagnostic; + + public PropertyDetails(PerspexObject o, PerspexProperty property) { - this.Name = value.Property.IsAttached ? - string.Format("[{0}.{1}]", value.Property.OwnerType.Name, value.Property.Name) : - value.Property.Name; - this.IsAttached = value.Property.IsAttached; - - this.value = value.Value ?? "(null)"; - this.Priority = (value.Priority != BindingPriority.Unset) ? - value.Priority.ToString() : - value.Property.Inherits ? "Inherited" : "Unset"; - - //if (value.PriorityValue != null) - //{ - // value.PriorityValue.Changed.Subscribe(x => this.Value = x.Item2); - //} + this.Name = property.IsAttached ? + string.Format("[{0}.{1}]", property.OwnerType.Name, property.Name) : + property.Name; + this.IsAttached = property.IsAttached; + + // TODO: Unsubscribe when view model is deactivated. + o.GetObservable(property).Subscribe(x => + { + var diagnostic = o.GetDiagnostic(property); + this.Value = diagnostic.Value ?? "(null)"; + this.Priority = (diagnostic.Priority != BindingPriority.Unset) ? + diagnostic.Priority.ToString() : + diagnostic.Property.Inherits ? "Inherited" : "Unset"; + this.Diagnostic = diagnostic.Diagnostic; + }); } - public string Name + public string Name { get; } + + public bool IsAttached { get; } + + public string Priority { - get; + get { return this.priority; } + private set { this.RaiseAndSetIfChanged(ref this.priority, value); } } - public bool IsAttached + public string Diagnostic { - get; + get { return this.diagnostic; } + private set { this.RaiseAndSetIfChanged(ref this.diagnostic, value); } } public object Value @@ -46,11 +57,5 @@ namespace Perspex.Diagnostics.ViewModels get { return this.value; } private set { this.RaiseAndSetIfChanged(ref this.value, value); } } - - public string Priority - { - get; - private set; - } } } diff --git a/Perspex.Diagnostics/Views/ControlDetailsView.cs b/Perspex.Diagnostics/Views/ControlDetailsView.cs index b3a05d4143..d73bd75ed7 100644 --- a/Perspex.Diagnostics/Views/ControlDetailsView.cs +++ b/Perspex.Diagnostics/Views/ControlDetailsView.cs @@ -55,7 +55,7 @@ namespace Perspex.Diagnostics.Views { new Setter(Control.MarginProperty, new Thickness(2)), } - } + }, }, [GridRepeater.TemplateProperty] = pt, [!GridRepeater.ItemsProperty] = this.WhenAnyValue(x => x.ViewModel.Properties), @@ -69,7 +69,8 @@ namespace Perspex.Diagnostics.Views yield return new TextBlock { - Text = property.Name + Text = property.Name, + [!ToolTip.TipProperty] = property.WhenAnyValue(x => x.Diagnostic), }; yield return new TextBlock @@ -77,7 +78,10 @@ namespace Perspex.Diagnostics.Views [!TextBlock.TextProperty] = property.WhenAnyValue(v => v.Value).Select(v => v?.ToString()), }; - yield return new TextBlock { Text = property.Priority }; + yield return new TextBlock + { + [!TextBlock.TextProperty] = property.WhenAnyValue(x => x.Priority), + }; } } }