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