Server/MarqueeLabel.cs
author StephaneLenclud
Sat, 07 Feb 2015 14:39:03 +0100
changeset 110 31e63bd07dfa
parent 100 7e02ac8b13ba
child 123 0df386e37e29
permissions -rw-r--r--
Cleaning up design tab by removing layout debug buttons.
Separator changes now reflected instantly.
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@100
    23
        private Font iFontInUse;
StephaneLenclud@110
    24
		private string iSeparator;
sl@8
    25
sl@8
    26
        [Category("Appearance")]
sl@8
    27
        [Description("Separator in our scrolling loop.")]
sl@8
    28
        [DefaultValue(" | ")]
sl@8
    29
        [Browsable(true), EditorBrowsable(EditorBrowsableState.Always)]
sl@11
    30
        [DesignerSerializationVisibility(DesignerSerializationVisibility.Visible)]
StephaneLenclud@110
    31
        public string Separator
StephaneLenclud@110
    32
		{
StephaneLenclud@110
    33
			get
StephaneLenclud@110
    34
			{
StephaneLenclud@110
    35
				return iSeparator;
StephaneLenclud@110
    36
			}
StephaneLenclud@110
    37
			set
StephaneLenclud@110
    38
			{
StephaneLenclud@110
    39
				if (value != Separator)
StephaneLenclud@110
    40
				{
StephaneLenclud@110
    41
					iSeparator = value;
StephaneLenclud@110
    42
					OnTextChanged(EventArgs.Empty);
StephaneLenclud@110
    43
				}
StephaneLenclud@110
    44
			}
StephaneLenclud@110
    45
		}
sl@0
    46
sl@0
    47
        [Category("Behavior")]
sl@0
    48
        [Description("How fast is our text scrolling, in pixels per second.")]
sl@0
    49
        [DefaultValue(32)]
sl@0
    50
        [Browsable(true), EditorBrowsable(EditorBrowsableState.Always)]
sl@11
    51
        [DesignerSerializationVisibility(DesignerSerializationVisibility.Visible)]
sl@0
    52
        public int PixelsPerSecond { get; set; }
sl@0
    53
sl@2
    54
        [Category("Behavior")]
sl@100
    55
        [Description("Should we scale down our font to try fit our text without scrolling.")]
sl@100
    56
        [DefaultValue(false)]
sl@100
    57
        [Browsable(true), EditorBrowsable(EditorBrowsableState.Always)]
sl@100
    58
        [DesignerSerializationVisibility(DesignerSerializationVisibility.Visible)]
sl@100
    59
        public bool ScaleToFit { get; set; }
sl@100
    60
sl@100
    61
        [Category("Behavior")]
sl@100
    62
        [Description("Minimum size of our font allowed when scaling is enabled.")]
sl@100
    63
        [DefaultValue(15)]
sl@100
    64
        [Browsable(true), EditorBrowsable(EditorBrowsableState.Always)]
sl@100
    65
        [DesignerSerializationVisibility(DesignerSerializationVisibility.Visible)]
sl@100
    66
        public float MinFontSize { get; set; }
sl@100
    67
sl@100
    68
        [Category("Behavior")]
sl@2
    69
        [Description("Use an internal or an external timer.")]
sl@2
    70
        [DefaultValue(true)]
sl@2
    71
        [Browsable(true), EditorBrowsable(EditorBrowsableState.Always)]
sl@11
    72
        [DesignerSerializationVisibility(DesignerSerializationVisibility.Visible)]
sl@2
    73
        public bool OwnTimer
sl@2
    74
        {
sl@2
    75
            get
sl@2
    76
            {
sl@2
    77
                return iOwnTimer;
sl@2
    78
            }
sl@2
    79
            set
sl@2
    80
            {
sl@2
    81
                iOwnTimer = value;
sl@2
    82
sl@2
    83
                if (iOwnTimer)
sl@2
    84
                {
sl@2
    85
                    Timer = new Timer();
sl@2
    86
                    Timer.Interval = 10;
sl@2
    87
                    Timer.Tick += new EventHandler(Timer_Tick);
sl@2
    88
                    Timer.Start();
sl@2
    89
                }
sl@2
    90
                else
sl@2
    91
                {
sl@2
    92
                    if (Timer != null)
sl@2
    93
                        Timer.Dispose();
sl@2
    94
                    Timer = null;
sl@2
    95
                }
sl@2
    96
sl@2
    97
            }
sl@2
    98
        }
sl@5
    99
sl@2
   100
        private int CurrentPosition { get; set; }
sl@2
   101
        private Timer Timer { get; set; }
sl@0
   102
        private DateTime LastTickTime { get; set; }
sl@0
   103
        private double PixelsLeft { get; set; }
sl@0
   104
        //DateTime a = new DateTime(2010, 05, 12, 13, 15, 00);
sl@2
   105
        //DateTime b = new DateTime(2010, 05, 12, 13, 45, 00);
sl@2
   106
        //Console.WriteLine(b.Subtract(a).TotalMinutes);
sl@0
   107
sl@0
   108
        public MarqueeLabel()
sl@0
   109
        {
sl@0
   110
            UseCompatibleTextRendering = true;
sl@0
   111
            //PixelsPerSecond = 32;
sl@0
   112
            LastTickTime = DateTime.Now;
sl@0
   113
            PixelsLeft = 0;
sl@33
   114
            CurrentPosition = 0;
sl@5
   115
            iBrush = new SolidBrush(ForeColor);
sl@100
   116
            MinFontSize = 15;
sl@100
   117
            ScaleToFit = true;
sl@100
   118
            //Just clone our font
sl@100
   119
            iFontInUse = new Font(Font, Font.Style);
StephaneLenclud@35
   120
sl@41
   121
            //Following is needed if we ever switch from Label to Control base class.
StephaneLenclud@35
   122
            //Without it you get some pretty nasty flicker
StephaneLenclud@35
   123
            //SetStyle(ControlStyles.OptimizedDoubleBuffer, true);
StephaneLenclud@35
   124
            //SetStyle(ControlStyles.UserPaint, true);
sl@41
   125
            //SetStyle(ControlStyles.AllPaintingInWmPaint, true);
StephaneLenclud@35
   126
            //SetStyle(ControlStyles.DoubleBuffer, true);
sl@0
   127
        }
sl@0
   128
sl@2
   129
        public void UpdateAnimation(DateTime aLastTickTime, DateTime aNewTickTime)
sl@0
   130
        {
sl@6
   131
            if (!NeedToScroll())
sl@0
   132
            {
sl@6
   133
                CurrentPosition = 0;
sl@6
   134
                return;
sl@6
   135
            }
sl@6
   136
sl@34
   137
            /*
sl@8
   138
            while (CurrentPosition > (iTextSize.Width + iSeparatorSize.Width))
sl@6
   139
            {
sl@8
   140
                CurrentPosition -= ((int)(iTextSize.Width + iSeparatorSize.Width));
sl@0
   141
            }
sl@34
   142
             */
sl@34
   143
sl@34
   144
            while (CurrentPosition > iScrollSize.Width)
sl@34
   145
            {
sl@34
   146
                CurrentPosition -= (int)iScrollSize.Width;
sl@34
   147
            }
sl@34
   148
sl@0
   149
sl@2
   150
            PixelsLeft += aNewTickTime.Subtract(aLastTickTime).TotalSeconds * PixelsPerSecond;
sl@0
   151
sl@0
   152
            //Keep track of our pixels left over
sl@0
   153
            //PixelsLeft = offset - Math.Truncate(offset);
sl@0
   154
            double offset = Math.Truncate(PixelsLeft);
sl@0
   155
            PixelsLeft -= offset;
sl@0
   156
sl@0
   157
            CurrentPosition += Convert.ToInt32(offset);
sl@0
   158
sl@0
   159
            /*
sl@0
   160
            if (offset > 1.0)
sl@0
   161
            {
sl@0
   162
                BackColor = Color.Red;
sl@0
   163
            }
sl@0
   164
            else if (offset==1.0)
sl@0
   165
            {
sl@0
   166
                if (BackColor != Color.White)
sl@0
   167
                {
sl@0
   168
                    BackColor = Color.White;
sl@0
   169
                }
sl@0
   170
sl@0
   171
            }
sl@0
   172
            else
sl@0
   173
            {
sl@0
   174
                //Too slow
sl@0
   175
                //BackColor = Color.Green;
sl@0
   176
            }*/
sl@0
   177
sl@0
   178
            //Only redraw if something has changed
sl@0
   179
            if (offset != 0)
sl@0
   180
            {
sl@0
   181
                Invalidate();
sl@0
   182
            }
sl@2
   183
        }
sl@0
   184
sl@2
   185
        void Timer_Tick(object sender, EventArgs e)
sl@2
   186
        {
sl@5
   187
            DateTime NewTickTime = DateTime.Now;
sl@2
   188
            //
sl@2
   189
            UpdateAnimation(LastTickTime, NewTickTime);
sl@2
   190
            //
sl@2
   191
            LastTickTime = NewTickTime;
sl@0
   192
        }
sl@0
   193
sl@5
   194
        private StringFormat GetStringFormatFromContentAllignment(ContentAlignment ca)
sl@5
   195
        {
sl@41
   196
            StringFormat format = new StringFormat(StringFormat.GenericTypographic);
sl@5
   197
            switch (ca)
sl@5
   198
            {
sl@5
   199
                case ContentAlignment.TopCenter:
sl@34
   200
                    format.Alignment = StringAlignment.Center;
sl@34
   201
                    format.LineAlignment = StringAlignment.Near;
sl@5
   202
                    break;
sl@5
   203
                case ContentAlignment.TopLeft:
sl@5
   204
                    format.Alignment = StringAlignment.Near;
sl@5
   205
                    format.LineAlignment = StringAlignment.Near;
sl@5
   206
                    break;
sl@5
   207
                case ContentAlignment.TopRight:
sl@34
   208
                    format.Alignment = StringAlignment.Far;
sl@34
   209
                    format.LineAlignment = StringAlignment.Near;
sl@5
   210
                    break;
sl@5
   211
                case ContentAlignment.MiddleCenter:
sl@5
   212
                    format.Alignment = StringAlignment.Center;
sl@5
   213
                    format.LineAlignment = StringAlignment.Center;
sl@5
   214
                    break;
sl@5
   215
                case ContentAlignment.MiddleLeft:
sl@34
   216
                    format.Alignment = StringAlignment.Near;
sl@34
   217
                    format.LineAlignment = StringAlignment.Center;
sl@5
   218
                    break;
sl@5
   219
                case ContentAlignment.MiddleRight:
sl@34
   220
                    format.Alignment = StringAlignment.Far;
sl@34
   221
                    format.LineAlignment = StringAlignment.Center;
sl@34
   222
                    break;
sl@34
   223
                case ContentAlignment.BottomCenter:
sl@5
   224
                    format.Alignment = StringAlignment.Center;
sl@5
   225
                    format.LineAlignment = StringAlignment.Far;
sl@5
   226
                    break;
sl@5
   227
                case ContentAlignment.BottomLeft:
sl@34
   228
                    format.Alignment = StringAlignment.Near;
sl@34
   229
                    format.LineAlignment = StringAlignment.Far;
sl@5
   230
                    break;
sl@5
   231
                case ContentAlignment.BottomRight:
sl@5
   232
                    format.Alignment = StringAlignment.Far;
sl@5
   233
                    format.LineAlignment = StringAlignment.Far;
sl@5
   234
                    break;
sl@5
   235
            }
sl@5
   236
sl@5
   237
            format.FormatFlags |= StringFormatFlags.NoWrap;
sl@5
   238
            format.FormatFlags |= StringFormatFlags.NoClip;
sl@11
   239
            format.FormatFlags |= StringFormatFlags.MeasureTrailingSpaces;
sl@5
   240
            format.Trimming = StringTrimming.None;
sl@5
   241
sl@5
   242
            return format;
sl@5
   243
        }
sl@5
   244
sl@5
   245
        protected override void OnForeColorChanged(EventArgs e)
sl@5
   246
        {
sl@6
   247
            //Color has changed recreate our brush
sl@5
   248
            iBrush = new SolidBrush(ForeColor);
sl@5
   249
sl@5
   250
            base.OnForeColorChanged(e);
sl@5
   251
        }
sl@5
   252
sl@6
   253
sl@100
   254
        private void ComputeSizes()
sl@6
   255
        {
sl@11
   256
            //For all string measurements and drawing issues refer to the following article:
sl@11
   257
            // http://stackoverflow.com/questions/1203087/why-is-graphics-measurestring-returning-a-higher-than-expected-number
sl@6
   258
            //Update text size according to text and font
sl@6
   259
            Graphics g = this.CreateGraphics();
sl@6
   260
            g.TextRenderingHint = System.Drawing.Text.TextRenderingHint.SingleBitPerPixelGridFit;
sl@6
   261
            iStringFormat = GetStringFormatFromContentAllignment(TextAlign);
sl@100
   262
            iTextSize = g.MeasureString(Text, iFontInUse, Int32.MaxValue, iStringFormat);
sl@100
   263
            iSeparatorSize = g.MeasureString(Separator, iFontInUse, Int32.MaxValue, iStringFormat);
sl@34
   264
            //Scroll width is the width of our text and our separator without taking kerning into account since
sl@34
   265
            //both text and separator are drawn independently from each other.
sl@34
   266
            iScrollSize.Width = iSeparatorSize.Width + iTextSize.Width;
sl@34
   267
            iScrollSize.Height = Math.Max(iSeparatorSize.Height, iTextSize.Height); //Not relevant for now
sl@100
   268
            //We don't want scroll width to take kerning into account so we don't use the following
sl@34
   269
            //iScrollSize = g.MeasureString(Text + Separator, Font, Int32.MaxValue, iStringFormat);
sl@100
   270
        }
sl@100
   271
sl@100
   272
        private void HandleTextSizeChange()
sl@100
   273
        {
sl@100
   274
            ComputeSizes();
sl@6
   275
sl@6
   276
            if (NeedToScroll())
sl@6
   277
            {
sl@100
   278
                if (ScaleToFit && iFontInUse.SizeInPoints > MinFontSize)
sl@100
   279
                {
sl@100
   280
                    //Try scaling down
sl@100
   281
                    iFontInUse = new Font(Font.FontFamily, iFontInUse.SizeInPoints - 1, Font.Style);
sl@100
   282
                    //Recurse until we are done
sl@100
   283
                    HandleTextSizeChange();
sl@100
   284
                }
sl@100
   285
                else
sl@100
   286
                {
sl@100
   287
                    if (ScaleToFit)
sl@100
   288
                    {
sl@100
   289
                        //Our minimum font size still needs scrolling
sl@100
   290
                        //Reset our font then
sl@100
   291
                        iFontInUse = new Font(Font,Font.Style);
sl@100
   292
                        ComputeSizes();
sl@100
   293
                    }
sl@100
   294
sl@100
   295
                    //Scrolling is ok or needed
sl@100
   296
                    //Always align left when scrolling
sl@100
   297
                    iStringFormat.Alignment = StringAlignment.Near;
sl@100
   298
                }
sl@41
   299
            }
sl@42
   300
sl@100
   301
            //Reset our timer whenever our text changes
sl@100
   302
            CurrentPosition = 0;
sl@100
   303
            PixelsLeft = 0;
sl@100
   304
            LastTickTime = DateTime.Now;
sl@6
   305
        }
sl@6
   306
sl@6
   307
        protected override void OnTextChanged(EventArgs e)
sl@6
   308
        {
sl@100
   309
            //Just clone our font
sl@100
   310
            iFontInUse = new Font(Font, Font.Style);
sl@100
   311
sl@6
   312
            HandleTextSizeChange();
sl@6
   313
sl@6
   314
            base.OnTextChanged(e);
sl@6
   315
        }
sl@6
   316
sl@6
   317
        protected override void OnFontChanged(EventArgs e)
sl@6
   318
        {
sl@100
   319
            //Just clone our font
sl@100
   320
            iFontInUse = new Font(Font,Font.Style);
sl@100
   321
sl@6
   322
            HandleTextSizeChange();
sl@6
   323
sl@6
   324
            base.OnFontChanged(e);
sl@6
   325
        }
sl@6
   326
sl@100
   327
        protected override void OnSizeChanged(EventArgs e)
sl@100
   328
        {
sl@100
   329
            //Just clone our font
sl@100
   330
            iFontInUse = new Font(Font, Font.Style);
sl@100
   331
sl@100
   332
            HandleTextSizeChange();
sl@100
   333
sl@100
   334
            base.OnSizeChanged(e);
sl@100
   335
        }
sl@100
   336
sl@5
   337
        protected override void OnTextAlignChanged(EventArgs e)
sl@5
   338
        {
sl@42
   339
            iStringFormat = GetStringFormatFromContentAllignment(TextAlign);
sl@41
   340
            if (NeedToScroll())
sl@41
   341
            {
sl@41
   342
                //Always align left when scrolling to avoid bugs
sl@42
   343
                iStringFormat.Alignment = StringAlignment.Near;
sl@41
   344
            }
sl@42
   345
sl@41
   346
            Invalidate();
sl@41
   347
            //
sl@5
   348
            base.OnTextAlignChanged(e);
sl@5
   349
        }
sl@5
   350
sl@0
   351
        protected override void OnPaint(PaintEventArgs e)
sl@0
   352
        {
sl@0
   353
            //Disable anti-aliasing
sl@0
   354
            e.Graphics.TextRenderingHint = System.Drawing.Text.TextRenderingHint.SingleBitPerPixelGridFit;
sl@6
   355
            if (NeedToScroll())
sl@6
   356
            {
sl@34
   357
                //Draw it all in a single call would take kerning into account
sl@34
   358
                //e.Graphics.TranslateTransform(-(float)CurrentPosition, 0);
sl@41
   359
                //e.Graphics.DrawString(Text + Separator + Text, Font, iBrush, ClientRectangle, StringFormat);
sl@34
   360
sl@34
   361
                //Doing separate draw operation allows us not to take kerning into account between separator and string
sl@34
   362
                //Draw the first one
sl@33
   363
                e.Graphics.TranslateTransform(-(float)CurrentPosition, 0);
sl@100
   364
                e.Graphics.DrawString(Text, iFontInUse, iBrush, ClientRectangle, iStringFormat);
sl@8
   365
                //Draw separator
sl@34
   366
                e.Graphics.TranslateTransform(iTextSize.Width, 0);
sl@100
   367
                e.Graphics.DrawString(Separator, iFontInUse, iBrush, ClientRectangle, iStringFormat);
sl@6
   368
                //Draw the last one
sl@34
   369
                e.Graphics.TranslateTransform(iSeparatorSize.Width, 0);
sl@100
   370
                e.Graphics.DrawString(Text, iFontInUse, iBrush, ClientRectangle, iStringFormat);
sl@6
   371
            }
sl@6
   372
            else
sl@6
   373
            {
sl@100
   374
                e.Graphics.DrawString(Text, iFontInUse, iBrush, ClientRectangle, iStringFormat);
sl@6
   375
            }
sl@6
   376
sl@6
   377
sl@5
   378
sl@5
   379
            //DrawText is not working without anti-aliasing. See: stackoverflow.com/questions/8283631/graphics-drawstring-vs-textrenderer-drawtextwhich-can-deliver-better-quality
sl@5
   380
            //TextRenderer.DrawText(e.Graphics, Text, Font, ClientRectangle, ForeColor, BackColor, iTextFormatFlags);
sl@5
   381
sl@5
   382
            //base.OnPaint(e);
sl@0
   383
        }
sl@0
   384
sl@6
   385
        public bool NeedToScroll()
sl@6
   386
        {
sl@6
   387
            //if (Width < e.Graphics.MeasureString(Text, Font).Width)
sl@6
   388
            if (Width < iTextSize.Width)
sl@6
   389
            {
sl@6
   390
                return true;
sl@6
   391
            }
sl@6
   392
            return false;
sl@6
   393
        }
sl@6
   394
sl@0
   395
        protected override void Dispose(bool disposing)
sl@0
   396
        {
sl@0
   397
            if (disposing)
sl@0
   398
            {
sl@0
   399
                if (Timer != null)
sl@0
   400
                    Timer.Dispose();
sl@0
   401
            }
sl@0
   402
            Timer = null;
sl@0
   403
        }
sl@0
   404
    }
sl@0
   405
}