From 8d8480c0863132ea995c7f24f078c40fdcd755ab Mon Sep 17 00:00:00 2001 From: Jeremy Koritzinsky Date: Thu, 17 May 2018 14:24:32 -0500 Subject: [PATCH 1/7] Allow indexer bindings to be at the start of a binding path. --- .../Data/Parsers/ArgumentListParser.cs | 4 +- .../Data/Parsers/ExpressionParser.cs | 42 +++++++++++++------ .../Data/ExpressionObserverTests_Indexer.cs | 12 ++++++ 3 files changed, 43 insertions(+), 15 deletions(-) diff --git a/src/Markup/Avalonia.Markup/Data/Parsers/ArgumentListParser.cs b/src/Markup/Avalonia.Markup/Data/Parsers/ArgumentListParser.cs index 563b372c78..05b3b29be0 100644 --- a/src/Markup/Avalonia.Markup/Data/Parsers/ArgumentListParser.cs +++ b/src/Markup/Avalonia.Markup/Data/Parsers/ArgumentListParser.cs @@ -51,10 +51,10 @@ namespace Avalonia.Markup.Data.Parsers } } - throw new ExpressionParseException(r.Position, "Expected ']'."); + throw new ExpressionParseException(r.Position, $"Expected '{close}'."); } - return null; + throw new ExpressionParseException(r.Position, $"Expected '{open}'."); } } } diff --git a/src/Markup/Avalonia.Markup/Data/Parsers/ExpressionParser.cs b/src/Markup/Avalonia.Markup/Data/Parsers/ExpressionParser.cs index 93ddf77376..f31f6eccb7 100644 --- a/src/Markup/Avalonia.Markup/Data/Parsers/ExpressionParser.cs +++ b/src/Markup/Avalonia.Markup/Data/Parsers/ExpressionParser.cs @@ -40,6 +40,10 @@ namespace Avalonia.Markup.Data.Parsers case State.AttachedProperty: state = ParseAttachedProperty(r, nodes); break; + + case State.Indexer: + state = ParseIndexer(r, nodes); + break; } } @@ -67,6 +71,10 @@ namespace Avalonia.Markup.Data.Parsers { return State.AttachedProperty; } + else if (PeekOpenBracket(r)) + { + return State.Indexer; + } else { var identifier = IdentifierParser.Parse(r); @@ -92,20 +100,9 @@ namespace Avalonia.Markup.Data.Parsers nodes.Add(new StreamNode()); return State.AfterMember; } - else + else if (PeekOpenBracket(r)) { - var args = ArgumentListParser.Parse(r, '[', ']'); - - if (args != null) - { - if (args.Count == 0) - { - throw new ExpressionParseException(r.Position, "Indexer may not be empty."); - } - - nodes.Add(new IndexerNode(args)); - return State.AfterMember; - } + return State.Indexer; } return State.End; @@ -151,6 +148,19 @@ namespace Avalonia.Markup.Data.Parsers return State.AfterMember; } + private State ParseIndexer(Reader r, List nodes) + { + var args = ArgumentListParser.Parse(r, '[', ']'); + + if (args.Count == 0) + { + throw new ExpressionParseException(r.Position, "Indexer may not be empty."); + } + + nodes.Add(new IndexerNode(args)); + return State.AfterMember; + } + private static bool ParseNot(Reader r) { return !r.End && r.TakeIf('!'); @@ -166,6 +176,11 @@ namespace Avalonia.Markup.Data.Parsers return !r.End && r.TakeIf('('); } + private static bool PeekOpenBracket(Reader r) + { + return !r.End && r.Peek == '['; + } + private static bool ParseStreamOperator(Reader r) { return !r.End && r.TakeIf('^'); @@ -177,6 +192,7 @@ namespace Avalonia.Markup.Data.Parsers AfterMember, BeforeMember, AttachedProperty, + Indexer, End, } } diff --git a/tests/Avalonia.Markup.UnitTests/Data/ExpressionObserverTests_Indexer.cs b/tests/Avalonia.Markup.UnitTests/Data/ExpressionObserverTests_Indexer.cs index a68213baee..1bdbf0d895 100644 --- a/tests/Avalonia.Markup.UnitTests/Data/ExpressionObserverTests_Indexer.cs +++ b/tests/Avalonia.Markup.UnitTests/Data/ExpressionObserverTests_Indexer.cs @@ -338,6 +338,18 @@ namespace Avalonia.Markup.UnitTests.Data GC.KeepAlive(data); } + [Fact] + public async Task Indexer_Only_Binding_Works() + { + var data = new[] { 1, 2, 3 }; + + var target = new ExpressionObserver(data, "[1]"); + + var value = await target.Take(1); + + Assert.Equal(data[1], value); + } + private class NonIntegerIndexer : NotifyingBase { private readonly Dictionary _storage = new Dictionary(); From 4b5ce0cf406b1c91ef7a7bfa0e04567a47173a8c Mon Sep 17 00:00:00 2001 From: Unknown Date: Fri, 18 May 2018 11:10:24 +0100 Subject: [PATCH 2/7] add failing unit test. --- .../Presenters/TextPresenter_Tests.cs | 27 +++++++++++++++++++ 1 file changed, 27 insertions(+) create mode 100644 tests/Avalonia.Controls.UnitTests/Presenters/TextPresenter_Tests.cs diff --git a/tests/Avalonia.Controls.UnitTests/Presenters/TextPresenter_Tests.cs b/tests/Avalonia.Controls.UnitTests/Presenters/TextPresenter_Tests.cs new file mode 100644 index 0000000000..f40ba3cee0 --- /dev/null +++ b/tests/Avalonia.Controls.UnitTests/Presenters/TextPresenter_Tests.cs @@ -0,0 +1,27 @@ +using Avalonia.Controls.Presenters; +using Xunit; + +namespace Avalonia.Controls.UnitTests.Presenters +{ + public class TextPresenter_Tests + { + [Fact] + public void TextPresenter_can_contain_null_with_password_char_set() + { + var target = new TextPresenter + { + PasswordChar = '*' + }; + + Assert.NotNull(target.FormattedText); + } + + [Fact] + public void TextPresenter_can_contain_null_without_password_char_set() + { + var target = new TextPresenter(); + + Assert.NotNull(target.FormattedText); + } + } +} From 18c963aac9e5505ce2fec6a748902c88b1fa7cd6 Mon Sep 17 00:00:00 2001 From: Unknown Date: Fri, 18 May 2018 11:21:57 +0100 Subject: [PATCH 3/7] Add test for password char on TextPresenter. --- .../Presenters/TextPresenter_Tests.cs | 13 +++++++++++++ 1 file changed, 13 insertions(+) diff --git a/tests/Avalonia.Controls.UnitTests/Presenters/TextPresenter_Tests.cs b/tests/Avalonia.Controls.UnitTests/Presenters/TextPresenter_Tests.cs index f40ba3cee0..d7276fed5b 100644 --- a/tests/Avalonia.Controls.UnitTests/Presenters/TextPresenter_Tests.cs +++ b/tests/Avalonia.Controls.UnitTests/Presenters/TextPresenter_Tests.cs @@ -23,5 +23,18 @@ namespace Avalonia.Controls.UnitTests.Presenters Assert.NotNull(target.FormattedText); } + + [Fact] + public void Text_presenter_replaces_formatted_text_with_password_char() + { + var target = new TextPresenter + { + PasswordChar = '*', + Text = "Test" + }; + + Assert.NotNull(target.FormattedText); + Assert.Equal("****", target.FormattedText.Text); + } } } From 92ad156d83af9051d2214878c468116bdf7f616e Mon Sep 17 00:00:00 2001 From: Unknown Date: Fri, 18 May 2018 11:22:20 +0100 Subject: [PATCH 4/7] Add test that default value of TextBlock.Text property is empty string. --- tests/Avalonia.Controls.UnitTests/TextBlockTests.cs | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/tests/Avalonia.Controls.UnitTests/TextBlockTests.cs b/tests/Avalonia.Controls.UnitTests/TextBlockTests.cs index b8886456dd..9a1140fc05 100644 --- a/tests/Avalonia.Controls.UnitTests/TextBlockTests.cs +++ b/tests/Avalonia.Controls.UnitTests/TextBlockTests.cs @@ -15,5 +15,15 @@ namespace Avalonia.Controls.UnitTests BindingMode.OneWay, TextBlock.TextProperty.GetMetadata(typeof(TextBlock)).DefaultBindingMode); } + + [Fact] + public void Default_Text_Value_Should_Be_EmptyString() + { + var textBlock = new TextBlock(); + + Assert.Equal( + "", + textBlock.Text); + } } } From f97d2932401c12880bad105805651b96a1735881 Mon Sep 17 00:00:00 2001 From: Unknown Date: Fri, 18 May 2018 11:22:53 +0100 Subject: [PATCH 5/7] fix textblock and texstpresenter issues related to password char. --- src/Avalonia.Controls/Presenters/TextPresenter.cs | 4 ++-- src/Avalonia.Controls/TextBlock.cs | 2 ++ 2 files changed, 4 insertions(+), 2 deletions(-) diff --git a/src/Avalonia.Controls/Presenters/TextPresenter.cs b/src/Avalonia.Controls/Presenters/TextPresenter.cs index c241466aea..bda660be51 100644 --- a/src/Avalonia.Controls/Presenters/TextPresenter.cs +++ b/src/Avalonia.Controls/Presenters/TextPresenter.cs @@ -223,11 +223,11 @@ namespace Avalonia.Controls.Presenters if (PasswordChar != default(char)) { - result = base.CreateFormattedText(constraint, new string(PasswordChar, Text.Length)); + result = base.CreateFormattedText(constraint, new string(PasswordChar, text?.Length ?? 0)); } else { - result = base.CreateFormattedText(constraint, Text); + result = base.CreateFormattedText(constraint, text); } var selectionStart = SelectionStart; diff --git a/src/Avalonia.Controls/TextBlock.cs b/src/Avalonia.Controls/TextBlock.cs index 1fee309360..e7cf666589 100644 --- a/src/Avalonia.Controls/TextBlock.cs +++ b/src/Avalonia.Controls/TextBlock.cs @@ -111,6 +111,8 @@ namespace Avalonia.Controls /// public TextBlock() { + _text = string.Empty; + Observable.Merge( this.GetObservable(TextProperty).Select(_ => Unit.Default), this.GetObservable(TextAlignmentProperty).Select(_ => Unit.Default), From 8266621d6520cf490ad377ac05e5482f50882b98 Mon Sep 17 00:00:00 2001 From: Dan Walmsley Date: Fri, 18 May 2018 15:13:46 +0100 Subject: [PATCH 6/7] fix nits. --- .../Presenters/TextPresenter_Tests.cs | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/tests/Avalonia.Controls.UnitTests/Presenters/TextPresenter_Tests.cs b/tests/Avalonia.Controls.UnitTests/Presenters/TextPresenter_Tests.cs index d7276fed5b..23dae8f341 100644 --- a/tests/Avalonia.Controls.UnitTests/Presenters/TextPresenter_Tests.cs +++ b/tests/Avalonia.Controls.UnitTests/Presenters/TextPresenter_Tests.cs @@ -6,7 +6,7 @@ namespace Avalonia.Controls.UnitTests.Presenters public class TextPresenter_Tests { [Fact] - public void TextPresenter_can_contain_null_with_password_char_set() + public void TextPresenter_Can_Contain_Null_With_Password_Char_Set() { var target = new TextPresenter { @@ -17,7 +17,7 @@ namespace Avalonia.Controls.UnitTests.Presenters } [Fact] - public void TextPresenter_can_contain_null_without_password_char_set() + public void TextPresenter_Can_Contain_Null_WithOut_Password_Char_Set() { var target = new TextPresenter(); @@ -25,7 +25,7 @@ namespace Avalonia.Controls.UnitTests.Presenters } [Fact] - public void Text_presenter_replaces_formatted_text_with_password_char() + public void Text_Presenter_Replaces_Formatted_Text_With_Password_Char() { var target = new TextPresenter { From 51a376f3e25dff4cddf860ada5243b4d7da8a93c Mon Sep 17 00:00:00 2001 From: Steven Kirk Date: Fri, 18 May 2018 16:32:53 +0200 Subject: [PATCH 7/7] Register ToolTip attached properties correctly. Was previously registering them with `Popup` as the owner, which was causing two properties with the same name to get registered, confusing XAML. We should probably put in a check to make sure this doesn't happen, but this fixes the problem for now. Fixes #1573 --- src/Avalonia.Controls/ToolTip.cs | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/src/Avalonia.Controls/ToolTip.cs b/src/Avalonia.Controls/ToolTip.cs index e45f30f818..464e4188d2 100644 --- a/src/Avalonia.Controls/ToolTip.cs +++ b/src/Avalonia.Controls/ToolTip.cs @@ -34,25 +34,25 @@ namespace Avalonia.Controls /// Defines the ToolTip.Placement property. /// public static readonly AttachedProperty PlacementProperty = - AvaloniaProperty.RegisterAttached("Placement", defaultValue: PlacementMode.Pointer); + AvaloniaProperty.RegisterAttached("Placement", defaultValue: PlacementMode.Pointer); /// /// Defines the ToolTip.HorizontalOffset property. /// public static readonly AttachedProperty HorizontalOffsetProperty = - AvaloniaProperty.RegisterAttached("HorizontalOffset"); + AvaloniaProperty.RegisterAttached("HorizontalOffset"); /// /// Defines the ToolTip.VerticalOffset property. /// public static readonly AttachedProperty VerticalOffsetProperty = - AvaloniaProperty.RegisterAttached("VerticalOffset", 20); + AvaloniaProperty.RegisterAttached("VerticalOffset", 20); /// /// Defines the ToolTip.ShowDelay property. /// public static readonly AttachedProperty ShowDelayProperty = - AvaloniaProperty.RegisterAttached("ShowDelay", 400); + AvaloniaProperty.RegisterAttached("ShowDelay", 400); /// /// Stores the curernt instance in the control.