Bug 55243 - Text Alignment on TextView begin reset to Gravity if parent view hidden then shown
Summary: Text Alignment on TextView begin reset to Gravity if parent view hidden then ...
Status: RESOLVED ANSWERED
Alias: None
Product: Forms
Classification: Xamarin
Component: Android ()
Version: 2.5.0
Hardware: PC Windows
: Normal normal
Target Milestone: ---
Assignee: Bugzilla
URL:
Depends on:
Blocks:
 
Reported: 2017-04-16 15:28 UTC by Scott Davis
Modified: 2018-01-11 14:05 UTC (History)
8 users (show)

Tags: android textalignment grid layout ac
Is this bug a regression?: ---
Last known good build:


Attachments
repro project (296.35 KB, application/x-zip-compressed)
2017-05-05 23:57 UTC, Jimmy [MSFT]
Details


Notice (2018-05-24): bugzilla.xamarin.com is now in read-only mode.

Please join us on Visual Studio Developer Community and in the Xamarin and Mono organizations on GitHub to continue tracking issues. Bugzilla will remain available for reference in read-only mode. We will continue to work on open Bugzilla bugs, copy them to the new locations as needed for follow-up, and add the new items under Related Links.

Our sincere thanks to everyone who has contributed on this bug tracker over the years. Thanks also for your understanding as we make these adjustments and improvements for the future.


Please create a new report on Developer Community or GitHub with your current version information, steps to reproduce, and relevant error messages or log files if you are hitting an issue that looks similar to this resolved bug and you do not yet see a matching new report.

Related Links:
Status:
RESOLVED ANSWERED

Description Scott Davis 2017-04-16 15:28:01 UTC
Xamarin Forms 2.3.4.231

If a view contains a TextView (lives inside a Grid, if that matters) and the parent view is hidden and then reshown, the TextAlignment of the TextView is being reset back to "Gravity", when it was previously set to End.

I've confirmed this happens by adding a check in a custom render.

Odd behavior: if you change the text in the view, the alignment is restored to "End".  If you do not change the text, it is reset back to Gravity, instead of retaining the right alignment (End) originally set.
Comment 1 Jimmy [MSFT] 2017-04-17 18:23:03 UTC
I created a sample project but I was not able to reproduce the issue described. I tested on Android, iOS, and UWP and the Label kept it's End HorizontalTextAlignment even after hiding its parent Grid.

Can you attach a project that reproduces the issue so we can look into this further? Thanks!

* Please note that we may close this issue after 30 days of no response. After including the requested info, set the status to NEW again.
Comment 2 Scott Davis 2017-04-19 22:30:14 UTC
It took me 6 hours to narrow down the layout combination that causes this problem.  Create a new project, delete MainPage.xaml and the code behind. Create a new MainPage.cs class.  Use this code.  Reproduce on Android.


    public class MainPage : ContentPage
    {
        private CustomProgressBar _progbar;
        private Grid _zonePreviewGrid;


        public MainPage()
        {
            CreatePage();
        }

        private void CreatePage()
        { 
            var abs = new AbsoluteLayout { BackgroundColor = Color.PowderBlue };
            this.Content = abs;

            _zonePreviewGrid = new Grid()
            {
                RowSpacing = 0,
                ColumnDefinitions = new ColumnDefinitionCollection()
                {
                    new ColumnDefinition() { Width = new GridLength(1, GridUnitType.Star) }
                },
                RowDefinitions = new RowDefinitionCollection()
                {
                    new RowDefinition() { Height = GridLength.Auto },
                    new RowDefinition() { Height = GridLength.Auto },
                    new RowDefinition() { Height = GridLength.Auto },
                    new RowDefinition() { Height = GridLength.Auto },
                    new RowDefinition() { Height = GridLength.Auto }
                },
                Margin = new Thickness(0, 30, 0, 0)
            };

            abs.Children.Add(_zonePreviewGrid, new Rectangle(0.5, 0, 0.8, AbsoluteLayout.AutoSize), AbsoluteLayoutFlags.XProportional | AbsoluteLayoutFlags.WidthProportional);

            Grid factionStrengthGrid = new Grid()
            {
                BackgroundColor = Color.RosyBrown,
                VerticalOptions = LayoutOptions.Center,
                Padding = new Thickness(0, 5),
                RowSpacing = 2,
                ColumnDefinitions = new ColumnDefinitionCollection()
                {
                    new ColumnDefinition() { Width = new GridLength(15, GridUnitType.Absolute) },
                    new ColumnDefinition() { Width = new GridLength(1, GridUnitType.Star) }
                },
                RowDefinitions = new RowDefinitionCollection()
                {
                    new RowDefinition() {Height = new GridLength(30, GridUnitType.Absolute) },
                    new RowDefinition() {Height = new GridLength(30, GridUnitType.Absolute) },
                    new RowDefinition() {Height = new GridLength(30, GridUnitType.Absolute) }
                }
            };
            _zonePreviewGrid.Children.Add(factionStrengthGrid, 0, 1);

            _progbar = new CustomProgressBar
            {
                HeightRequest = 30,
                BarColor = Color.Yellow,
                BorderColor = Color.Yellow,
                CenteredText = "Initial Text",
                CurrentCount = 0,
                MaxCount = 100,
                Percent = .8
            };
            factionStrengthGrid.Children.Add(_progbar, 1, 2);

            Label lbl = new Label { Text = "The text in the bar will shift", HorizontalTextAlignment = TextAlignment.Start };
            factionStrengthGrid.Children.Add(lbl, 1, 0);

            Button btn = new Button { WidthRequest = 200, HeightRequest = 50, Text = "Change Bar" };
            btn.Clicked += Btn_Clicked;
            abs.Children.Add(btn, new Rectangle(.5, 1, AbsoluteLayout.AutoSize, AbsoluteLayout.AutoSize), AbsoluteLayoutFlags.PositionProportional);

            Label instructions = new Label { Text = "To see this bug, tap the button at the bottom, see the text change the current date (centered), switch the phone to a different app, then return to this app, notice the date and the \"100\" have shifted to be left aligned", LineBreakMode = LineBreakMode.WordWrap };
            abs.Children.Add(instructions, new Rectangle(.5, .7, 1, AbsoluteLayout.AutoSize), AbsoluteLayoutFlags.PositionProportional | AbsoluteLayoutFlags.WidthProportional);

        }

        private void Btn_Clicked(object sender, EventArgs e)
        {
            _progbar.CenteredText = DateTime.Now.ToString();
        }
    }

    // Progress Bar with borders and text in the middle
    internal class CustomProgressBar : AbsoluteLayout
    {
        private AbsoluteLayout _progBar;
        private AbsoluteLayout _progBarContainer;
        private List<BoxView> _borders;
        private Label _lblCurrent;
        private Label _lblMax;
        private Label _lblCenter;

        internal CustomProgressBar()
        {
            this.VerticalOptions = LayoutOptions.Center;
            this.HeightRequest = 20;

            Grid borderGrid = new Grid()
            {
                HorizontalOptions = LayoutOptions.Fill,
                VerticalOptions = LayoutOptions.Fill,
                Padding = 0,
                ColumnSpacing = 1,
                RowSpacing = 1,
                RowDefinitions =
                {
                   new RowDefinition { Height = new GridLength(1, GridUnitType.Absolute) },
                   new RowDefinition { Height = new GridLength(1, GridUnitType.Star) },
                   new RowDefinition { Height = new GridLength(1, GridUnitType.Absolute) }
                },
                ColumnDefinitions =
                {
                   new ColumnDefinition { Width = new GridLength(1, GridUnitType.Absolute) },
                   new ColumnDefinition { Width = new GridLength(1, GridUnitType.Star) },
                   new ColumnDefinition { Width = new GridLength(1, GridUnitType.Absolute) }
                }
            };

            // Create the Border
            BoxView border1 = new BoxView { Color = Color.Red };
            BoxView border2 = new BoxView { Color = Color.Red };
            BoxView border3 = new BoxView { Color = Color.Red };
            BoxView border4 = new BoxView { Color = Color.Red };
            _borders = new List<BoxView>();
            _borders.Add(border1);
            _borders.Add(border2);
            _borders.Add(border3);
            _borders.Add(border4);

            borderGrid.Children.Add(border1, 0, 3, 0, 1);
            borderGrid.Children.Add(border2, 0, 3, 2, 3);
            borderGrid.Children.Add(border3, 0, 1, 0, 3);
            borderGrid.Children.Add(border4, 2, 3, 0, 3);

            // Create the Progressbar
            _progBarContainer = new AbsoluteLayout();
            _progBar = new AbsoluteLayout();
            _progBar.BackgroundColor = Color.Black;
            _progBarContainer.Children.Add(_progBar, new Rectangle(0, 0, 0, 1), AbsoluteLayoutFlags.All);

            // Add Progressbar to Grid
            borderGrid.Children.Add(_progBarContainer, 1, 1);

            this.Children.Add(borderGrid, new Rectangle(0, 0.5, 1, 1), AbsoluteLayoutFlags.All);

            // Text
            _lblCenter = new Label();
            _lblCenter.HorizontalTextAlignment = TextAlignment.Center;
            _lblCenter.VerticalTextAlignment = TextAlignment.Center;
            borderGrid.Children.Add(_lblCenter, 1, 1);

            _lblCurrent = new Label();
            _lblCurrent.HorizontalTextAlignment = TextAlignment.Start;
            _lblCurrent.VerticalTextAlignment = TextAlignment.Center;
            borderGrid.Children.Add(_lblCurrent, 1, 1);

            _lblMax = new Label();
            _lblMax.HorizontalTextAlignment = TextAlignment.End;
            _lblMax.VerticalTextAlignment = TextAlignment.Center;
            borderGrid.Children.Add(_lblMax, 1, 1);

            Animate = false;
            this.SizeChanged += CustomProgressBar_SizeChanged;
        }

        // This fixes prog bars not filling when they were not drawn when percent was set
        private void CustomProgressBar_SizeChanged(object sender, EventArgs e)
        {
            if (Animate)
            {
                AnimateProgressBar();
            }
            else
            {
                _progBar.LayoutTo(new Rectangle(0, 0, (_progBarContainer.Width * Percent), _progBarContainer.Height), 0);
            }
        }

        private async void AnimateProgressBar()
        {
            await _progBar.LayoutTo(new Rectangle(0, 0, (_progBarContainer.Width * Percent), _progBarContainer.Height), 2000, Easing.CubicInOut);
        }

        public void Reset()
        {
            Percent = 0;
            _progBar.LayoutTo(new Rectangle(0, 0, 0, _progBarContainer.Height), 0);
        }

        private bool _animate = true;
        internal bool Animate
        {
            get { return _animate; }
            set
            {
                _animate = value;
            }
        }

        public string CenteredText
        {
            get { return _lblCenter.Text; }
            set
            {
                _lblCenter.Text = value;
            }
        }

        private double _currentCount;
        public double CurrentCount
        {
            get { return _currentCount; }
            set
            {
                _currentCount = value;
                _lblCurrent.Text = value.ToString();
            }
        }

        private double _maxCount;
        public double MaxCount
        {
            get { return _maxCount; }
            set
            {
                _maxCount = value;
                _lblMax.Text = value.ToString();
            }
        }

        public double _pct;
        public double Percent
        {
            get { return _pct; }
            set
            {
                if (value > 1)
                {
                    _pct = 1;
                }
                else
                {
                    _pct = value;
                }
                if (Animate)
                {
                    AnimateProgressBar(); // Uses Percent value so must call after value set
                }
                else
                {
                    _progBar.LayoutTo(new Rectangle(0, 0, (_progBarContainer.Width * _pct), _progBarContainer.Height), 0);
                }
            }
        }

        private Color _borderColor = Color.Black;
        public Color BorderColor
        {
            get { return _borderColor; }
            set
            {
                foreach (var border in _borders)
                {
                    border.Color = value;
                }
                _borderColor = value;
            }
        }

        private Color _barColor = Color.Black;
        public Color BarColor
        {
            get { return _barColor; }
            set
            {
                _progBar.BackgroundColor = value;
                _barColor = value;
            }
        }
    }
Comment 3 Scott Davis 2017-04-26 08:26:13 UTC
Jimmy, any update on this?
Comment 4 Jimmy [MSFT] 2017-05-05 23:57:21 UTC
Created attachment 21990 [details]
repro project

I was able to reproduce this issue with the code provided. I am attaching a project and confirming the report so the engineering team can investigate.


### Version Tests
2.3.6.94    BAD
2.3.5-pre2  BAD
2.3.4.231   BAD
Comment 5 Scott Davis 2017-05-11 04:50:14 UTC
Additional information: 

We have a couple labels in our app that col span two columns with one of the columns being a star. I have no idea if the star matters or the col span, but it might be the only thing that makes these labels unique in our app.  The label does not have a horizontal alignment, so it fills both columns.

When you suspend and resume the app, the label only shows the first letter of the text when you return to the app.  If you ask the label for its width, it will give you its expected full width. If you give the label a background color it will show that it spans both columns. Yet the label only shows the first character of the text that was there before you suspended the app.

The work around is to add a SizeChanged event and record the width of the label after it autosizes to fit the two columns it spans.  Then when you resume the app, to call label.WidthRequest = savedWidth;

This will do something and "fix" the label and it will show the full text again, rather than just the first character.  Setting MinimumWidthRequest does not work. You must call Width Request.

I'm assuming a Xamarin Forms Label control is actually some type of container with a native label inside. AND Like with the horizontal and vertical alignment issue, the width is being reset on a suspend/resume if the parent container has an "unknown" width because of the colspan or star width column, or combination of both. Thus reseting the native label width inside so small, that only the first character shows.
Comment 6 John Hardman 2017-11-16 10:14:18 UTC
Also seeing this in my app using XF 2.4.0.38779
Comment 7 John Hardman 2017-11-16 13:34:03 UTC
In my scenario, I found that resetting WidthRequest did not work. In fact, changing WidthRequest, VerticalTextAlignment and VerticalOptions (plus BackgroundColor just to prove that changes had been made), then reverting them to the correct values also did not work - the Label was still rendered with the VerticalTextAlignment being ignored. In the end, I had to recreate the Label completely in OnAppearing() and replace the original one in order to get the VerticalTextAlignment correct.
Comment 8 Scott Davis 2018-01-10 14:03:51 UTC
This still happens in Xamarin Forms 2.5.  It has been 9 months!
Comment 9 David Ortinau [MSFT] 2018-01-11 14:05:39 UTC
This issue has been migrated to GitHub Issues. Please follow https://github.com/xamarin/Xamarin.Forms/issues/1557 for future updates.