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