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