Server/MarqueeLabel.cs
author sl
Fri, 22 Aug 2014 10:44:45 +0200
changeset 41 1864e4fd1728
parent 35 f3893924a6eb
child 42 a9048f350975
permissions -rw-r--r--
Adding buttons to test our alignment setup.
Fixing a nasty bug in iStringFormat that would cause us to use the same instance in all marquee objects.
sl@0
     1
using System;
sl@0
     2
using System.Collections.Generic;
sl@0
     3
using System.ComponentModel;
sl@0
     4
using System.Diagnostics;
sl@0
     5
using System.Linq;
sl@0
     6
using System.Text;
sl@0
     7
using System.Threading.Tasks;
sl@0
     8
//using System.Timers;
sl@0
     9
using System.Windows.Forms;
sl@0
    10
using System.Drawing;
sl@0
    11
sl@0
    12
namespace SharpDisplayManager
sl@0
    13
{
sl@15
    14
    [System.ComponentModel.DesignerCategory("Code")]
sl@17
    15
    public class MarqueeLabel : Label
sl@0
    16
    {
sl@2
    17
        private bool iOwnTimer;
sl@5
    18
        private StringFormat iStringFormat;
sl@5
    19
        private SolidBrush iBrush;
sl@6
    20
        private SizeF iTextSize;
sl@8
    21
        private SizeF iSeparatorSize;
sl@34
    22
        private SizeF iScrollSize;
sl@41
    23
        private ContentAlignment iRequestedContentAlignment;
sl@8
    24
sl@8
    25
        [Category("Appearance")]
sl@8
    26
        [Description("Separator in our scrolling loop.")]
sl@8
    27
        [DefaultValue(" | ")]
sl@8
    28
        [Browsable(true), EditorBrowsable(EditorBrowsableState.Always)]
sl@11
    29
        [DesignerSerializationVisibility(DesignerSerializationVisibility.Visible)]
sl@8
    30
        public string Separator { get; set; }
sl@0
    31
sl@0
    32
        [Category("Behavior")]
sl@0
    33
        [Description("How fast is our text scrolling, in pixels per second.")]
sl@0
    34
        [DefaultValue(32)]
sl@0
    35
        [Browsable(true), EditorBrowsable(EditorBrowsableState.Always)]
sl@11
    36
        [DesignerSerializationVisibility(DesignerSerializationVisibility.Visible)]
sl@0
    37
        public int PixelsPerSecond { get; set; }
sl@0
    38
sl@2
    39
        [Category("Behavior")]
sl@2
    40
        [Description("Use an internal or an external timer.")]
sl@2
    41
        [DefaultValue(true)]
sl@2
    42
        [Browsable(true), EditorBrowsable(EditorBrowsableState.Always)]
sl@11
    43
        [DesignerSerializationVisibility(DesignerSerializationVisibility.Visible)]
sl@2
    44
        public bool OwnTimer
sl@2
    45
        {
sl@2
    46
            get
sl@2
    47
            {
sl@2
    48
                return iOwnTimer;
sl@2
    49
            }
sl@2
    50
            set
sl@2
    51
            {
sl@2
    52
                iOwnTimer = value;
sl@2
    53
sl@2
    54
                if (iOwnTimer)
sl@2
    55
                {
sl@2
    56
                    Timer = new Timer();
sl@2
    57
                    Timer.Interval = 10;
sl@2
    58
                    Timer.Tick += new EventHandler(Timer_Tick);
sl@2
    59
                    Timer.Start();
sl@2
    60
                }
sl@2
    61
                else
sl@2
    62
                {
sl@2
    63
                    if (Timer != null)
sl@2
    64
                        Timer.Dispose();
sl@2
    65
                    Timer = null;
sl@2
    66
                }
sl@2
    67
sl@2
    68
            }
sl@2
    69
        }
sl@5
    70
sl@2
    71
        private int CurrentPosition { get; set; }
sl@2
    72
        private Timer Timer { get; set; }
sl@0
    73
        private DateTime LastTickTime { get; set; }
sl@0
    74
        private double PixelsLeft { get; set; }
sl@0
    75
        //DateTime a = new DateTime(2010, 05, 12, 13, 15, 00);
sl@2
    76
        //DateTime b = new DateTime(2010, 05, 12, 13, 45, 00);
sl@2
    77
        //Console.WriteLine(b.Subtract(a).TotalMinutes);
sl@0
    78
sl@0
    79
        public MarqueeLabel()
sl@0
    80
        {
sl@0
    81
            UseCompatibleTextRendering = true;
sl@0
    82
            //PixelsPerSecond = 32;
sl@0
    83
            LastTickTime = DateTime.Now;
sl@0
    84
            PixelsLeft = 0;
sl@33
    85
            CurrentPosition = 0;
sl@5
    86
            iBrush = new SolidBrush(ForeColor);
sl@41
    87
            iRequestedContentAlignment = TextAlign;
StephaneLenclud@35
    88
sl@41
    89
            //Following is needed if we ever switch from Label to Control base class.
StephaneLenclud@35
    90
            //Without it you get some pretty nasty flicker
StephaneLenclud@35
    91
            //SetStyle(ControlStyles.OptimizedDoubleBuffer, true);
StephaneLenclud@35
    92
            //SetStyle(ControlStyles.UserPaint, true);
sl@41
    93
            //SetStyle(ControlStyles.AllPaintingInWmPaint, true);
StephaneLenclud@35
    94
            //SetStyle(ControlStyles.DoubleBuffer, true);
sl@0
    95
        }
sl@0
    96
sl@2
    97
        public void UpdateAnimation(DateTime aLastTickTime, DateTime aNewTickTime)
sl@0
    98
        {
sl@6
    99
            if (!NeedToScroll())
sl@0
   100
            {
sl@6
   101
                CurrentPosition = 0;
sl@6
   102
                return;
sl@6
   103
            }
sl@6
   104
sl@34
   105
            /*
sl@8
   106
            while (CurrentPosition > (iTextSize.Width + iSeparatorSize.Width))
sl@6
   107
            {
sl@8
   108
                CurrentPosition -= ((int)(iTextSize.Width + iSeparatorSize.Width));
sl@0
   109
            }
sl@34
   110
             */
sl@34
   111
sl@34
   112
            while (CurrentPosition > iScrollSize.Width)
sl@34
   113
            {
sl@34
   114
                CurrentPosition -= (int)iScrollSize.Width;
sl@34
   115
            }
sl@34
   116
sl@0
   117
sl@2
   118
            PixelsLeft += aNewTickTime.Subtract(aLastTickTime).TotalSeconds * PixelsPerSecond;
sl@0
   119
sl@0
   120
            //Keep track of our pixels left over
sl@0
   121
            //PixelsLeft = offset - Math.Truncate(offset);
sl@0
   122
            double offset = Math.Truncate(PixelsLeft);
sl@0
   123
            PixelsLeft -= offset;
sl@0
   124
sl@0
   125
            CurrentPosition += Convert.ToInt32(offset);
sl@0
   126
sl@0
   127
            /*
sl@0
   128
            if (offset > 1.0)
sl@0
   129
            {
sl@0
   130
                BackColor = Color.Red;
sl@0
   131
            }
sl@0
   132
            else if (offset==1.0)
sl@0
   133
            {
sl@0
   134
                if (BackColor != Color.White)
sl@0
   135
                {
sl@0
   136
                    BackColor = Color.White;
sl@0
   137
                }
sl@0
   138
sl@0
   139
            }
sl@0
   140
            else
sl@0
   141
            {
sl@0
   142
                //Too slow
sl@0
   143
                //BackColor = Color.Green;
sl@0
   144
            }*/
sl@0
   145
sl@0
   146
            //Only redraw if something has changed
sl@0
   147
            if (offset != 0)
sl@0
   148
            {
sl@0
   149
                Invalidate();
sl@0
   150
            }
sl@2
   151
        }
sl@0
   152
sl@2
   153
        void Timer_Tick(object sender, EventArgs e)
sl@2
   154
        {
sl@5
   155
            DateTime NewTickTime = DateTime.Now;
sl@2
   156
            //
sl@2
   157
            UpdateAnimation(LastTickTime, NewTickTime);
sl@2
   158
            //
sl@2
   159
            LastTickTime = NewTickTime;
sl@0
   160
        }
sl@0
   161
sl@5
   162
        private StringFormat GetStringFormatFromContentAllignment(ContentAlignment ca)
sl@5
   163
        {
sl@41
   164
            StringFormat format = new StringFormat(StringFormat.GenericTypographic);
sl@5
   165
            switch (ca)
sl@5
   166
            {
sl@5
   167
                case ContentAlignment.TopCenter:
sl@34
   168
                    format.Alignment = StringAlignment.Center;
sl@34
   169
                    format.LineAlignment = StringAlignment.Near;
sl@5
   170
                    break;
sl@5
   171
                case ContentAlignment.TopLeft:
sl@5
   172
                    format.Alignment = StringAlignment.Near;
sl@5
   173
                    format.LineAlignment = StringAlignment.Near;
sl@5
   174
                    break;
sl@5
   175
                case ContentAlignment.TopRight:
sl@34
   176
                    format.Alignment = StringAlignment.Far;
sl@34
   177
                    format.LineAlignment = StringAlignment.Near;
sl@5
   178
                    break;
sl@5
   179
                case ContentAlignment.MiddleCenter:
sl@5
   180
                    format.Alignment = StringAlignment.Center;
sl@5
   181
                    format.LineAlignment = StringAlignment.Center;
sl@5
   182
                    break;
sl@5
   183
                case ContentAlignment.MiddleLeft:
sl@34
   184
                    format.Alignment = StringAlignment.Near;
sl@34
   185
                    format.LineAlignment = StringAlignment.Center;
sl@5
   186
                    break;
sl@5
   187
                case ContentAlignment.MiddleRight:
sl@34
   188
                    format.Alignment = StringAlignment.Far;
sl@34
   189
                    format.LineAlignment = StringAlignment.Center;
sl@34
   190
                    break;
sl@34
   191
                case ContentAlignment.BottomCenter:
sl@5
   192
                    format.Alignment = StringAlignment.Center;
sl@5
   193
                    format.LineAlignment = StringAlignment.Far;
sl@5
   194
                    break;
sl@5
   195
                case ContentAlignment.BottomLeft:
sl@34
   196
                    format.Alignment = StringAlignment.Near;
sl@34
   197
                    format.LineAlignment = StringAlignment.Far;
sl@5
   198
                    break;
sl@5
   199
                case ContentAlignment.BottomRight:
sl@5
   200
                    format.Alignment = StringAlignment.Far;
sl@5
   201
                    format.LineAlignment = StringAlignment.Far;
sl@5
   202
                    break;
sl@5
   203
            }
sl@5
   204
sl@5
   205
            format.FormatFlags |= StringFormatFlags.NoWrap;
sl@5
   206
            format.FormatFlags |= StringFormatFlags.NoClip;
sl@11
   207
            format.FormatFlags |= StringFormatFlags.MeasureTrailingSpaces;
sl@5
   208
            format.Trimming = StringTrimming.None;
sl@5
   209
sl@5
   210
            return format;
sl@5
   211
        }
sl@5
   212
sl@5
   213
        protected override void OnForeColorChanged(EventArgs e)
sl@5
   214
        {
sl@6
   215
            //Color has changed recreate our brush
sl@5
   216
            iBrush = new SolidBrush(ForeColor);
sl@5
   217
sl@5
   218
            base.OnForeColorChanged(e);
sl@5
   219
        }
sl@5
   220
sl@6
   221
sl@6
   222
        private void HandleTextSizeChange()
sl@6
   223
        {
sl@33
   224
            //Reset our timer whenever our text changes
sl@30
   225
            CurrentPosition = 0;
sl@33
   226
            LastTickTime = DateTime.Now;
sl@33
   227
            PixelsLeft = 0;
sl@34
   228
            //Reset text align
sl@34
   229
            //TextAlign = iRequestedContentAlignment;
sl@33
   230
sl@11
   231
            //For all string measurements and drawing issues refer to the following article:
sl@11
   232
            // http://stackoverflow.com/questions/1203087/why-is-graphics-measurestring-returning-a-higher-than-expected-number
sl@6
   233
            //Update text size according to text and font
sl@6
   234
            Graphics g = this.CreateGraphics();
sl@6
   235
            g.TextRenderingHint = System.Drawing.Text.TextRenderingHint.SingleBitPerPixelGridFit;
sl@6
   236
            iStringFormat = GetStringFormatFromContentAllignment(TextAlign);
sl@11
   237
            iTextSize = g.MeasureString(Text, Font, Int32.MaxValue, iStringFormat);
sl@11
   238
            iSeparatorSize = g.MeasureString(Separator, Font, Int32.MaxValue, iStringFormat);
sl@34
   239
            //Scroll width is the width of our text and our separator without taking kerning into account since
sl@34
   240
            //both text and separator are drawn independently from each other.
sl@34
   241
            iScrollSize.Width = iSeparatorSize.Width + iTextSize.Width;
sl@34
   242
            iScrollSize.Height = Math.Max(iSeparatorSize.Height, iTextSize.Height); //Not relevant for now
sl@34
   243
            //We don't want scroll with to take kerning into account so we don't use the following
sl@34
   244
            //iScrollSize = g.MeasureString(Text + Separator, Font, Int32.MaxValue, iStringFormat);
sl@6
   245
sl@6
   246
            if (NeedToScroll())
sl@6
   247
            {
sl@6
   248
                //Always align left when scrolling
sl@34
   249
                //Somehow draw string still takes into our control alignment so we need to set it too
sl@34
   250
                //ContentAlignment original = TextAlign;
sl@34
   251
                TextAlign = ContentAlignment.MiddleLeft;
sl@34
   252
                //Make sure our original text alignment remain the same even though we override it when scrolling
sl@41
   253
                //iRequestedContentAlignment = original;
sl@34
   254
                //iStringFormat will get updated in OnTextAlignChanged
sl@41
   255
                //StringFormat.Alignment = StringAlignment.Near;
sl@41
   256
            }
sl@41
   257
            else
sl@41
   258
            {
sl@41
   259
                //We don't need to scroll so make sure the desired alignment is used
sl@41
   260
                TextAlign = iRequestedContentAlignment;
sl@6
   261
            }
sl@6
   262
        }
sl@6
   263
sl@6
   264
        protected override void OnTextChanged(EventArgs e)
sl@6
   265
        {
sl@6
   266
            HandleTextSizeChange();
sl@6
   267
sl@6
   268
            base.OnTextChanged(e);
sl@6
   269
        }
sl@6
   270
sl@6
   271
        protected override void OnFontChanged(EventArgs e)
sl@6
   272
        {
sl@6
   273
            HandleTextSizeChange();
sl@6
   274
sl@6
   275
            base.OnFontChanged(e);
sl@6
   276
        }
sl@6
   277
sl@5
   278
        protected override void OnTextAlignChanged(EventArgs e)
sl@5
   279
        {
sl@41
   280
            iRequestedContentAlignment = TextAlign;
sl@41
   281
            if (NeedToScroll())
sl@41
   282
            {
sl@41
   283
                //Always align left when scrolling to avoid bugs
sl@41
   284
                TextAlign = ContentAlignment.MiddleLeft;
sl@41
   285
            }
sl@5
   286
            iStringFormat = GetStringFormatFromContentAllignment(TextAlign);
sl@41
   287
            Invalidate();
sl@41
   288
            //
sl@5
   289
            base.OnTextAlignChanged(e);
sl@5
   290
        }
sl@5
   291
sl@0
   292
        protected override void OnPaint(PaintEventArgs e)
sl@0
   293
        {
sl@0
   294
            //Disable anti-aliasing
sl@0
   295
            e.Graphics.TextRenderingHint = System.Drawing.Text.TextRenderingHint.SingleBitPerPixelGridFit;
sl@6
   296
            if (NeedToScroll())
sl@6
   297
            {
sl@34
   298
                //Draw it all in a single call would take kerning into account
sl@34
   299
                //e.Graphics.TranslateTransform(-(float)CurrentPosition, 0);
sl@41
   300
                //e.Graphics.DrawString(Text + Separator + Text, Font, iBrush, ClientRectangle, StringFormat);
sl@34
   301
sl@34
   302
                //Doing separate draw operation allows us not to take kerning into account between separator and string
sl@34
   303
                //Draw the first one
sl@33
   304
                e.Graphics.TranslateTransform(-(float)CurrentPosition, 0);
sl@34
   305
                e.Graphics.DrawString(Text, Font, iBrush, ClientRectangle, iStringFormat);
sl@8
   306
                //Draw separator
sl@34
   307
                e.Graphics.TranslateTransform(iTextSize.Width, 0);
sl@34
   308
                e.Graphics.DrawString(Separator, Font, iBrush, ClientRectangle, iStringFormat);
sl@6
   309
                //Draw the last one
sl@34
   310
                e.Graphics.TranslateTransform(iSeparatorSize.Width, 0);
sl@34
   311
                e.Graphics.DrawString(Text, Font, iBrush, ClientRectangle, iStringFormat);
sl@6
   312
            }
sl@6
   313
            else
sl@6
   314
            {
sl@6
   315
                e.Graphics.DrawString(Text, Font, iBrush, ClientRectangle, iStringFormat);
sl@6
   316
            }
sl@6
   317
sl@6
   318
sl@5
   319
sl@5
   320
            //DrawText is not working without anti-aliasing. See: stackoverflow.com/questions/8283631/graphics-drawstring-vs-textrenderer-drawtextwhich-can-deliver-better-quality
sl@5
   321
            //TextRenderer.DrawText(e.Graphics, Text, Font, ClientRectangle, ForeColor, BackColor, iTextFormatFlags);
sl@5
   322
sl@5
   323
            //base.OnPaint(e);
sl@0
   324
        }
sl@0
   325
sl@6
   326
        public bool NeedToScroll()
sl@6
   327
        {
sl@6
   328
            //if (Width < e.Graphics.MeasureString(Text, Font).Width)
sl@6
   329
            if (Width < iTextSize.Width)
sl@6
   330
            {
sl@6
   331
                return true;
sl@6
   332
            }
sl@6
   333
            return false;
sl@6
   334
        }
sl@6
   335
sl@0
   336
        protected override void Dispose(bool disposing)
sl@0
   337
        {
sl@0
   338
            if (disposing)
sl@0
   339
            {
sl@0
   340
                if (Timer != null)
sl@0
   341
                    Timer.Dispose();
sl@0
   342
            }
sl@0
   343
            Timer = null;
sl@0
   344
        }
sl@0
   345
    }
sl@0
   346
}