Skip to content

Conversation

@nicolaihenriksen
Copy link
Contributor

@nicolaihenriksen nicolaihenriksen commented Sep 26, 2024

This is intended as a partial-fix for #3675. Partial, because it only concerns the TextBox based styles, while a similar issue is present with the ComboBox template; I intend to do another PR to address that issue eventually.

Problem

When a TextBox has multiple lines of text, either via AcceptsReturn=True or TextWrapping=Wrap/WrapWithOverflow, the hint position (and prefix/suffix text position) is not always correctly calculated.

Example:

image

Proposed solution

In order to calculate the position correctly, knowledge about the number of rows/lines of text is needed. The information is available in the BCL on the TextBox type (see here), but unfortunately this is not a dependency property. To make the information available via a bindable property (i.e. attached property), I have created a TextBoxLineCountBehavior which hooks up to TextBox.TextChanged and effectively updates TextFieldAssist.LineCount and TextFieldAssist.IsMultiLine accordingly.

These attached properties are then used in bindings and converters to improve the calculation of the hint position.

Example (GIF):

MultiLineFix

Changes

  • Extends the "Smart Hint" demo page with means of reproducing the issue(s).
  • Adds TextBoxLineCountBehavior, TextFieldAssist.LineCount and TextFieldAssist.IsMultiLine to support correct calculations.
  • Modifies FloatingHintInitialVerticalOffsetConverter which is responsible for doing the calculation.
  • Modifies relevant styles to leverage new APs.
  • Changes default value for RichTextBox.VerticalContentAlignment to Center because this seems to be the best fit if/when you want a hint in a RichTextBox; row/line count is not really available in this control, so hint placement can be wrong depending on how you "configure" the control.

Considerations

This effectively registers a TextBox.TextChanged listener on every single instance of a TextBox/AutoSuggestBox in an app using the MDIX styles.

Measurements

I wrote a simple WPF app simply laying out a configurable number of TextBox instances in a Canvas (to avoid the overhead of a Grid having to calculate row/columns sizes). I then measured the startup time and memory footprint (3 times and took an average value; not exactly empirical data, but meh!). The results are listed in the table below. I was looking for a linear increase which would indicate an issue, but the differences I see are so insignificant that I believe the changes in the PR do not introduce a perf cost. The fact that startup time was faster for the 2000-case also indicates that there is no issue.

I ran it on my laptop with the following specs:
Intel Core Ultra 7 165H, 1400 Mhz, 16 cores, 22 logical processors, 32GB RAM

Before After
# TextBoxes 100 1000 2000 100 1000 2000
Startup time ~2 sec ~8.5 sec ~13.2 sec ~2.2 sec ~8.5 sec ~12.8 sec
Memory footprint ~711 MB ~1028 MB ~1280 MB ~711 MB ~1026 MB ~1285 MB

Signed-off-by: Nicolai Henriksen <nicolai.h80@gmail.com>
TextBox.LineCount is not a DP on the TextBox control. So we add a behavior that can be attached which will listen for text changes and update the AP accordingly. This allows us to bind to TextFieldAssist.LineCount where needed.

Signed-off-by: Nicolai Henriksen <nicolai.h80@gmail.com>
Signed-off-by: Nicolai Henriksen <nicolai.h80@gmail.com>
Signed-off-by: Nicolai Henriksen <nicolai.h80@gmail.com>
Signed-off-by: Nicolai Henriksen <nicolai.h80@gmail.com>
… floating hint

Signed-off-by: Nicolai Henriksen <nicolai.h80@gmail.com>
@nicolaihenriksen nicolaihenriksen marked this pull request as ready for review October 5, 2024 18:55
TargetType="{x:Type TextBox}"
BasedOn="{StaticResource MaterialDesignTextBoxBase}">
<Setter Property="Padding" Value="{x:Static wpf:Constants.TextBoxDefaultPadding}" />
<Setter Property="wpf:BehaviorsAssist.Behaviors">
Copy link
Member

@Keboo Keboo Oct 6, 2024

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Probably doesn't need to be addressed now, but I wonder if we should be concerned about someone using BehaviorsAssist.Behaviors to add in their own behaviors and inadvertently clearing this one. Might make for some difficult to diagnose issues later. Probably a bigger thing to address more globally though.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

That is a fair point! When thinking about it, it almost feels like the new behavior type I added should be public? That way you can at least add it back in. Would also make it easier when copy-pasting one of our styles to make certain tweaks...

@Keboo Keboo merged commit 4279bb7 into master Oct 6, 2024
2 checks passed
@Keboo Keboo deleted the fix3675-part1 branch October 6, 2024 04:06
@AhmadKelany
Copy link

Would it be a good idea to make the suf text aligned to the last line of the text?

@nicolaihenriksen
Copy link
Contributor Author

Would it be a good idea to make the suf text aligned to the last line of the text?

I believe we discussed this very topic when refactoring the SmartHint. If I recall correctly, we went with the top line of text to avoid the "jumpy UI" where the prefix/suffix moves in the vertical direction when the number of lines change. @Keboo correct me if I am wrong...

@omllmo
Copy link

omllmo commented Oct 7, 2024

Is the TextBox fix already in version 5.1.1-ci776?

@nicolaihenriksen
Copy link
Contributor Author

Is the TextBox fix already in version 5.1.1-ci776?

Yes, it should be.

@omllmo
Copy link

omllmo commented Oct 7, 2024

Installed version 5.1.1-ci776.
When entering text manually, the hint began to behave correctly.
But if the text is specified via Binding="{Binding Content}", then the location of the hint remained incorrect with multi-line TextBox.

@nicolaihenriksen
Copy link
Contributor Author

Installed version 5.1.1-ci776. When entering text manually, the hint began to behave correctly. But if the text is specified via Binding="{Binding Content}", then the location of the hint remained incorrect with multi-line TextBox.

Is it a text with actual "newlines" in it, or is it simply wrapping using TexWrapping=Wrap[WithOverFlow]? Also, what is the value of TextBox.AcceptsReturn in your case? I would like to repro this locally. There is also a type that I need to change from internal to public, so I will be updating it anyways. Might as well look at this at the same time.

@omllmo
Copy link

omllmo commented Oct 7, 2024

I set VerticalContentAlignment="Top" and the hint behaved correctly.

Do you plan to make the hint behave correctly with the specified HintAssist.FloatingOffset?

@nicolaihenriksen
Copy link
Contributor Author

Do you plan to make the hint behave correctly with the specified HintAssist.FloatingOffset?

If this is set explicitly, that is an opt-out of automatic positioning, and thus you need to change it yourself based on changes in height/lines/etc. So I guess the answer is no.

@omllmo Can you elaborate on my questions in my previous comment, that will make it easier for me to repro locally. Thanks.

@omllmo
Copy link

omllmo commented Oct 7, 2024

Wrapped with TexWrapping=Wrap[WithOverFlow].
TextBox.AcceptsReturn is the default value.

@nicolaihenriksen As I wrote above, setting VerticalContentAlignment="Top" solves the problem with incorrect placement of the hint.

@nicolaihenriksen
Copy link
Contributor Author

As I wrote above, setting VerticalContentAlignment="Top" solves the problem with incorrect placement of the hint.

@omllmo That is good, but I would like it not to be necessary; I'll see if I can make it work.

@nicolaihenriksen nicolaihenriksen added this to the 5.2.0 milestone Oct 7, 2024
@omllmo
Copy link

omllmo commented Oct 8, 2024

In another situation, incorrect behavior of a multi-line TextBox with a hint was noticed (in particular, the MaterialDesignOutlinedTextBox style):

  • Value VerticalAligment="Stretch"
  • Value VerticalContentAligment="Top."

Безымянный

If you do not set the value VerticalContentAligment="Top.", then the text displayed in the TextBox is located in the center, not at the top

Безымянный

@nicolaihenriksen
Copy link
Contributor Author

If you do not set the value VerticalContentAligment="Top.", then the text displayed in the TextBox is located in the center, not at the top

This is expected behavior. The default value (VerticalContentAlignment=Stretch) has been chosen to make the most-common use case work "out of the box", whereas other scenarios (like this one), may require you to change a couple of properties. Single line without a custom height is deemed as the most-common use case.

By default, the text vertically aligns to the center such that it (vertically) aligns with all the other elements that could possible be there: leading icon, prefix text, suffix text, trailing icon, clear button.

JLdgu pushed a commit to JLdgu/MaterialDesignInXamlToolkit that referenced this pull request Nov 8, 2024
…rialDesignInXAML#3681)

* Extend SmartHint demo page to reproduce multi-line issue

Signed-off-by: Nicolai Henriksen <nicolai.h80@gmail.com>

* Expose TextBox.LineCount as an attached property
TextBox.LineCount is not a DP on the TextBox control. So we add a behavior that can be attached which will listen for text changes and update the AP accordingly. This allows us to bind to TextFieldAssist.LineCount where needed.

Signed-off-by: Nicolai Henriksen <nicolai.h80@gmail.com>

* Add AutoSuggestBox to SmartHint demo page

Signed-off-by: Nicolai Henriksen <nicolai.h80@gmail.com>

* Modify initial vert offset converter to use lineCount in calculations

Signed-off-by: Nicolai Henriksen <nicolai.h80@gmail.com>

* Apply LineCount AP in relevant styles

Signed-off-by: Nicolai Henriksen <nicolai.h80@gmail.com>

* Change default RichTextBox.VerticalContentAlignment for best fit with floating hint

Signed-off-by: Nicolai Henriksen <nicolai.h80@gmail.com>

* Align AcceptsReturn=True and TextWrapping=Wrap behaviors

* Remove left-over debug code

---------

Signed-off-by: Nicolai Henriksen <nicolai.h80@gmail.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

6 participants