Fix TextBlock ellipsis not showing with TextWrapping and MaxLines - #22017
Conversation
When TextWrapping and MaxLines are set, the collapsing width was using the layout's maximum width instead of the current line's width. This caused the ellipsis to not appear when the last line was shorter than other lines. Also added HasCollapsed check to prevent double-collapsing when text has already been collapsed due to overflow.
MrJul
left a comment
There was a problem hiding this comment.
This needs a matching unit test.
|
You can test this PR using the following package version. |
Added! (Commit: afe15b2) |
|
You can test this PR using the following package version. |
|
If you think my test is a little redundant, I could modify it accordingly—what do you think? Either way, I can verify whether the fix worked (since the results will definitely change when I run the test with the pre-fix implementation versus the post-fix implementation). public void Should_Add_Ellipsis_When_MaxLines_Cuts_Short_Wrapped_Line(TextTrimming trimming)
{
using (Start())
{
const string text = "Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat. Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur. Excepteur sint occaecat cupidatat non proident, sunt in culpa qui officia deserunt mollit anim id est laborum.";
const int maxLines = 2;
var layout = new TextLayout(
text,
Typeface.Default,
12,
Brushes.Black,
textWrapping: TextWrapping.Wrap,
textTrimming: trimming,
maxWidth: 180,
maxLines: maxLines);
Assert.Equal(maxLines, layout.TextLines.Count);
var lastLine = layout.TextLines[layout.TextLines.Count - 1];
var formattedLineText = string.Concat(lastLine.TextRuns.Select(r => r.Text.ToString()));
Assert.True(lastLine.HasCollapsed, "The wrapped line cut off by MaxLines must be collapsed.");
Assert.Equal('\u2026', formattedLineText.Last());
layout.Dispose();
}
}One drawback of this change is that the code becomes slightly harder to understand because it is written on the assumption that the length of the last line is always shorter than the layout’s maximum |
|
I verified the change against upstream/main with real font metrics across a set of scenarios (wrap + MaxLines, hard breaks, RTL, CJK, overflowing single words, MaxLines=1). I could not find any regression. Please simplify the test you have added. The test builds a text layout, scans for a line index that satisfies the bug condition, and then asserts cutIndex >= 0 with "Test data does not satisfy the bug-reproduction condition". A test that can fail because its own input stopped qualifying is hard to maintain. The variant you posted in the comment is the one to take: fixed text, fixed maxWidth, MaxLines = 2, assert the line count, HasCollapsed, and that the last line text ends with the ellipsis. MaxHeight has the same defect. It collapses against MaxWidth, so a wrapped line that is narrower than MaxWidth gets no ellipsis either. That is the same user-visible bug expressed through MaxHeight/LineHeight, which is how the scenario behaves under WPF. We do not need to cover it as part of this PR; we can handle it separately. We should think about adding the ellipsis to the last line instead of collapsing if there is enough space available. Collapsing against the line's own width always buys the ellipsis by dropping content that would still have fit inside MaxWidth. On a line ending in a space, that costs the space, but a CJK line loses a character (...テキストで becomes ...テキスト... while 91 of 100 px were available) and WordEllipsis loses the whole last word. In my opinion, the real fix is to let the collapsing layer append the symbol without trimming when content is hidden for a reason other than width, which is what the issue was about. In the end, this is about defining the desired behavior. This change ensures we trim, so it is a first step for the final solution. |
Thank you for the detailed review and the clear guidance on the test structure. I have successfully refactored the test to fully adopt the specific variant you suggested (fixed text, fixed maxWidth, MaxLines = 2). This stabilizes the test setup as requested. Regarding the other advanced architectural points discussed (such as MaxHeight logic and core ellipsis handling), I understand these are being addressed by separate PRs or are planned for later. Therefore, I will strictly limit the scope of this current PR to the fix for Wrapped text cut by MaxLines. I appreciate your time and thorough testing! |
|
You can test this PR using the following package version. |
…aloniaUI#22017) * Fix TextBlock ellipsis not showing with TextWrapping and MaxLines When TextWrapping and MaxLines are set, the collapsing width was using the layout's maximum width instead of the current line's width. This caused the ellipsis to not appear when the last line was shorter than other lines. Also added HasCollapsed check to prevent double-collapsing when text has already been collapsed due to overflow. * Remove HasCollapsed check * Add unit test for MaxLines trailing ellipsis on short wrapped lines * Remove redundant comment * fix: simplify TextLayout test

What does the pull request do?
When MaxLines is greater than 0 (e.g., 2 in the issue example) and the current line
count reaches MaxLines, the last line is collapsed using textLine.Collapse(). However,
GetCollapsingProperties was being passed the maximum width of the entire TextLayout
instead of the width of that specific line, so ellipses were not being added correctly.
This issue has been fixed.
What is the current behavior?
When
TextWrappingandMaxLinesare both set, the last line that exceeds the maximumline count is truncated without an ellipsis. For example:
Result: The second line is truncated without "...".
What is the updated/expected behavior with this PR?
The ellipsis is consistently displayed on the last line when text exceeds the maximum
line count, matching the behavior of single-line TextBlock with
TextTrimmingset.How was the solution implemented (if it's not obvious)?
Checklist
Breaking changes
None
Obsoletions / Deprecations
None
Fixed issues
Fixes #17633