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