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