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