Friday, April 22, 2016

A textbox that auto-wraps to fit contents

The Windows Forms Textbox control is a thin wrapper for the underlying Win32 control and there is no Paint event you can hang extra code on in the normal way. If you write something like mycontrol.Paint += myFunction; and mycontrol is a Textbox them myFunction() will never get called.

You can go the whole hog and

SetStyle(ControlStyles.UserPaint, true);

and provide a Paint method but you have to do everything as it is no good calling the base.OnPaint(). Give it a try, and you will end up with an astonishingly unresponsive textbox.

I wanted to create a version of a Textbox that would resize itself vertically to accommodate extended word wrapped text. That did not result in any issues – all it needed was a FitToContents() method that retained the set width and allowed the height to vary to fit the control content.

protected virtual void FitToContents() {     Size size = this.GetPreferredSize(new Size(this.Width, 0));     if (multiLine) { this.Height = size.Height; } }

However I also wanted to be able to switch between a label displaying a given string and a textbox capable of editing that string. Just to make that interesting I wanted a “fade in” and “fade out” animation to switch between the two. While I was at it, I also wanted to support a textbox “Placeholder” facility to keep the UI nice and clean. It was that final element that reminded me that it was possible to slide a limited paint facility into position to be used in place of the controls own paint when the control was relatively inactive. My own paint facility only needed to manage the fades and the placeholder text display – all the rest could be left to the underlying control.

Fading text in and out was just a matter of tweaking the Alpha component of the control ForeColor in steps over a defined time period. It was a good opportunity to use the recently introduced async/await functionality although a timer would have worked just fine. The control border is unreachable to all intents and purposes although if the control was set borderless one could be drawn around it and arrangements made to fade that rectangle in and out.

OK – I know - WPF and all that, but I also have this funny feeling that as soon as I invest any real time in WPF code Microsoft are going to announce a newer superer duperer common base for Windows apps and it will all be to no avail. Don’t take advice from me though – anything could happen.

The full code for this class can be found at the bottom of this post – usual caveats.

In case anyone fancies just using the placeholder functionality I have generously added an attribute (AutoMultiLine) that can be set false to stop the control re-sizing in response to text longer than the control width. Just set the PlaceHolderText attribute at design or run time. You can always ignore or remove the code associated with the fade.

The control based upon Label with a matching fade facility is very similar but simpler and again the code can be found below. This label also supports automatic height resizing to match the Text.

Please note that these controls are using a language feature from .NET v4.5 so you would need to switch to using a timer to make use of the fade functionality with earlier versions.

Combining these two custom controls into a UserControl made sense as they could then be addressed together as a single entity. This did have its challenges – partly because the TextBox BackColor property can’t be set Transparent and that makes the fade through a bit clunky in one direction – perhaps I should have gone for a “wipe” effect instead…

namespace Adit.Classes.Controls {     [ToolboxBitmap(typeof(TextBox))]     class MultiLineTextBox : TextBox     {         #region Private Declarations         private Color foreColour;         private Color placeholderColour = Color.Gray;         private int opacity = 256;         private int fadeSteps = 10;         private int fadeTime = 750;         private string placeholderText = "Type here";         private bool showPlaceholderText = false, multiLine = true;         #endregion         public MultiLineTextBox()         {             this.Multiline = multiLine;             this.WordWrap = multiLine;         }         #region Design attributes         [Description("Fade time in milliseconds"), Category("Behavior")]         public int FadeTime         {             get { return fadeTime; }             set { fadeTime = value; }         }         [Description("Number of steps from transparent 0 to opaque 256"), Category("Behavior")]         public int FadeSteps         {             get { return fadeSteps; }             set { fadeSteps = value; }         }         [Description("Placeholder Text"), Category("Appearance")]         public string PlaceHolderText         {             get { return placeholderText; }             set { placeholderText = value; Invalidate(); }         }         [Description("Placeholder Colour"), Category("Appearance")]         public Color PlaceHolderColour         {             get { return placeholderColour; }             set { placeholderColour = value; }         }         [Description("Automatic switch to multiline"), Category("Appearance")]         public bool AutoMultiLine         {             get { return multiLine; }             set {                 multiLine = value;                 Multiline = value;                 WordWrap = value;             }         }         #endregion         #region Public methods         public async void FadeInOut()         {             bool saveUserPaint = GetStyle(ControlStyles.UserPaint);             foreColour = ForeColor;             int opStep = 256 / fadeSteps;             if (Visible)             {                 opacity = 256;                 opStep *= -1;             } else             {                 Visible = true;                 opacity = 0;             }             SetStyle(ControlStyles.UserPaint, true); // set after any Visibility change             opacity = opacity + opStep;             while(opacity > 0 && opacity < 256)             {                 foreColour = fadeColour(opacity, foreColour);                 placeholderColour = fadeColour(opacity, placeholderColour);                 Invalidate();                 await Task.Delay(fadeTime / fadeSteps);                 opacity = opacity + opStep;             }             Visible = (opacity >= 255);             SetStyle(ControlStyles.UserPaint, saveUserPaint);             if (Visible)             {                 Invalidate();                 Focus();             }         }         #endregion         #region Override control methods         protected override void OnResize(EventArgs e)         {             base.OnResize(e);             this.FitToContents();         }         protected override void OnKeyUp(KeyEventArgs e)         {             base.OnKeyUp(e);             FitToContents();         }         protected override void OnTextChanged(EventArgs e)         {             base.OnTextChanged(e);             placeholderToggle();             FitToContents();         }         protected override void OnCreateControl()         {             base.OnCreateControl();             placeholderToggle();         }         protected override void OnPaint(PaintEventArgs e)         {             using (var drawBrush = new SolidBrush((showPlaceholderText)? placeholderColour: foreColour))             {                 e.Graphics.DrawString((showPlaceholderText) ? placeholderText : Text, Font, drawBrush, this.ClientRectangle);                 // The underlying control is probably using TextRenderer.DrawText (gdi not gdi+)             }         }         protected virtual void FitToContents()         {             Size size = this.GetPreferredSize(new Size(this.Width, 0));             if (multiLine) { this.Height = size.Height; }         }         #endregion         #region Private methods         private Color fadeColour(int opacity, Color argbColour)         {             return Color.FromArgb(opacity, argbColour);         }         private void placeholderToggle()         {             showPlaceholderText = (Text.Length > 0) ? false : true;             SetStyle(ControlStyles.UserPaint, showPlaceholderText);         }         #endregion     } }

and

namespace CheckBuilder.Classes.Controls {     public partial class WrapLabel : Label     {         #region Private Declarations         private Color foreColour;         private int opacity = 256;         private int fadeSteps = 10;         private int fadeTime = 750;         private bool fading = false;         #endregion         public WrapLabel()         {             base.AutoSize = false;         }         #region Public methods         public async void FadeInOut()         {             fading = true;             foreColour = ForeColor;             int opStep = 256 / fadeSteps;             if (Visible)             {                 opacity = 256;                 opStep *= -1;             }             else             {                 opacity = 0;                 Visible = true;             }             opacity = opacity + opStep;             while (opacity > 0 && opacity < 256)             {                 Invalidate();                 await Task.Delay(fadeTime / fadeSteps);                 opacity = opacity + opStep;             }             Visible = (opacity >= 255);             fading = false;         }         #endregion         #region Design attributes         [Description("Fade time in milliseconds"), Category("Behavior")]         public int FadeTime         {             get { return fadeTime; }             set { fadeTime = value; }         }         [Description("Number of steps from transparent 0 to opaque 256"), Category("Behavior")]         public int FadeSteps         {             get { return fadeSteps; }             set { fadeSteps = value; }         }         #endregion         #region Override control events         protected override void OnPaint(PaintEventArgs pe)         {             if(fading)             {                 using (var drawBrush = new SolidBrush(fadeColour(opacity, foreColour)))                 {                     pe.Graphics.DrawString(Text, Font, drawBrush, ClientRectangle);                 }             } else             {                 base.OnPaint(pe);             }         }         protected override void OnResize(EventArgs e)         {             base.OnResize(e);             this.FitToContents();         }         protected override void OnTextChanged(EventArgs e)         {             base.OnTextChanged(e);             this.FitToContents();         }         protected virtual void FitToContents()         {             Size size = this.GetPreferredSize(new Size(this.Width, 0));             this.Height = size.Height;         }         protected override void OnCreateControl()         {             base.OnCreateControl();             this.AutoSize = false;         }         #endregion         #region Stomp on AutoSize         [DefaultValue(false), Browsable(false), EditorBrowsable(EditorBrowsableState.Never), DesignerSerializationVisibility(DesignerSerializationVisibility.Hidden)]         public override bool AutoSize         {             get { return base.AutoSize; }             set { base.AutoSize = value; }         }         #endregion         #region Private methods         private Color fadeColour(int opacity, Color argbColour)         {             return Color.FromArgb(opacity, argbColour);         }         #endregion     } }

Tuesday, April 19, 2016

Structure C# like JavaScript

Been building a “proof of concept” Windows program that ended up including 9 different user controls, 4 Custom controls and some (gasp) printed output.

My custom controls included:
  • A Textbox with the equivalent of the HTML Textbox “placeholder”.
  • A custom Checkbox to emulate a material Design checkbox.
  • A Label control to automatically support word wrapped text – like HTML.
  • A Panel with rounded corners (which also required a couple of extensions to System.Drawing.Graphics to support drawing and filling the bounding round cornered “box”**).
There was the makings of a “rant” here; taking Microsoft to task on the lack of updates to the basic controls to meet modern requirements. I stifled it when I realised that rather than providing solutions matching what would, after all, be ever shifting changes in design “fashion” they had simply provided the tools necessary to remedy the situation. If you count yourself as a programmer then you have to accept the challenge to one’s design skills – mine do lack I admit.

What is the true difference between a Custom Control and a User Control?

This is a question I have seen asked in many places and so far all the answers I have seen have been “lies for children*” (simplifications that attempt to help someone make the right choice).

The key difference is that a User Control inherits from UserControl and a Custom control inherits from Control. That’s about it. You can add other controls to either and both will turn up in the visual designer “toolbox” in Visual Studio after a “build”. They can both place custom attributes and events into the control properties window as well as take advantage of properties inherited from their base types (these can also be suppressed if required).

Generally, you would choose to create a Custom Control if it is intended as a single control rather than a defined collection. (However there is no reason why a custom control might not act as a custom container for other Windows controls added, perhaps, at design time – like my panel with rounded corners). You would also select this type if you intend to manage drawing the control and also if you intend to subclass an existing control type.

User controls are a good choice if you are going to build a control using two or more pre-existing controls. This approach effectively provides a local name space and would normally provide code to handle individual sub-component events. User controls are better used when adding additional visual attributes during the paint event rather than managing the whole drawing requirement.

I think that my custom built control base type selection would normally be founded upon usage. “User Controls” being particular to a given application while “Custom Controls” might well be ported to other projects. Which probably does not help at all.

Why all those User Controls?

Well the program required repetitive collections of controls arranged as cards, subsections of cards and subsections of those subsections – all at the whim of the user. In addition, the program provides a preview pane to show how the output would be presented with support for interactive testing on the fly. At the design stage I could see that this had quite a lot of potential to get messy built using a conventional Windows Forms approach. I knew that if I built something similar in JavaScript then I would end up with a set of objects that managed their portion of the UI and dealt with the relevant events. Any required communication would be through callbacks to functions. The C# equivalent then became clear – a set of UserControls communication between themselves using Delegates. In fact some of the presentation side of the program had already been built using JavaScript and it was interesting to note the strong similarities between some of the C# and JavaScript code blocks.

That was how it worked out – a varying number of custom UserControls communicating with each other in the main but with the owning form being called upon to manage “global” functions like database saves. I can only estimate the savings in code lines as “substantial”.

Printing controls

Providing a print output started out feeling a bit “retro” as the most obvious way to accomplish the task was to print what was effectively an image of the program output preview pane (or at least the contents). I could just about recall that the Visual Basic 3 manual(s)*** had included some functionality to print a window content but had never actually done any such thing in all my years of code.

Establishing the basic mechanism proved simple enough as any control has the capacity to draw itself to a bitmap and the resulting bitmaps can easily be drawn to the printed page (scaling as required).

Worth noting that when doing this it is easy to add a drop shadow by extending the size of the bitmap by a few pixels. You can then draw some lines in a sequence of grey shades to create the shadow effect.

private Bitmap drawControlsImage(Control control, bool addShadow = false) {     Bitmap bm = new Bitmap(control.Width, control.Height + ((addShadow) ? 3 : 0));     control.DrawToBitmap(bm, new Rectangle(0, 0, control.Width, control.Height));     if(dropShadow)     {         Point pt = new Point(0, bm.Height - 3);         using (Graphics g = Graphics.FromImage(bm))         {             using (var pen = new Pen(shadow[0]))             {                 for (var sp = 0; sp < 3; sp++)                 {                     pen.Color = shadow[sp];                     g.DrawLine(pen, pt.X, pt.Y, pt.X + bm.Width - 2, pt.Y);                     pt.Y++;                 }             }         }     }     return bm; }

having previously coded

private Color[] shadow = new Color[3];         and shadow[0] = Color.FromArgb(181, 181, 181); shadow[1] = Color.FromArgb(195, 195, 195); shadow[2] = Color.FromArgb(211, 211, 211);

My printer output took the form of one or more major components (User Controls) emulating a Material design “card”. Finding the best way to order the cards in columns to optimise the printed layout looked set to be an interesting problem. It was analogous to the 2D rectangle bin packing challenge but modified in that there was at least an implicit order in the cards plus the target rectangle could be proportionately increased in size (at least conceptually) by applying ScaleTransform() to the output graphics surface and thus (negative) zoom. While thinking of the best approach I also thought about the issue as a “flow layout” problem – with the option to resize the logical bounding rectangle until a fit was achieved.

After a happy hour reading around the potential algorithms I realised that my requirement was very much a special case – a simple stacking exercise.

Which at first sight produced an acceptable result – but a little thought turned up the most obvious fault. Early positioning of objects in a column could undermine later reductions in the overall width.

What was needed was a two pass process with the first pass constrained to not building a column “higher” than the measured page “vertical” space. Second pass could then work as before towards the smallest packed size. So far, testing shows that the two pass method produces a better result and (on most occasions) a nicely balanced layout.

It amused me to note that I was quite happy to write the first draft of my stacking algorithm based upon a two dimensional array of SizeF (SizeF[,]) but when I came to re-write it to include two passes I changed the structure to List<List<SizeF>> (effectively a sparse array with inbuilt equivalents of Push() and Pop()) and achieved some code reduction as a consequence.


** Graphics extensions: You might like to take a look at this Code Project post by Arun Zaheeruddin which provides a fully overloaded set of rounded rectangle drawing functions that look enticing.

*** The VB3 manual set (included in the box) was probably the pinnacle of written language documentation. It was clearly intended to empower a generation of new Windows programmers – as indeed it probably did. We have come to accept that Google searches and Stack Overflow now provide detailed technical documentation for most things but this well written, detailed and accurate set in three (as I recall) volumes filled in the gap for a vast army until the World Wide Web was invented.

Monday, April 11, 2016

Dynamic C#

Just used the dynamic key word for the first time in C# - to get the compiler to relax a bit and act more like VB which allows late binding.

I was using a control Tag to store a couple of data items and as Tag accepts an object decided to use an Anonymous Object rather than define another class or whatnot. Something like:

aControl.Tag = new { Sequence = ItemSequence, id = ItemID};

But how to consume those object attributes in another function?

The IDE and compiler are not going to take kindly to anything like aControl.Tag.Sequence as that can only make sense at runtime.

The dynamic keyword comes to the rescue.

Here I am looping through a control collection filtered by my specific control type and in the order of the Sequence attribute in the anonymous object stored in the control Tag:

foreach(MyCheckbox mb in this.Controls.OfType().OrderBy(p => ((dynamic)p.Tag).Sequence))
            { … }

Also (after prompting by Visual Studio) used the following syntax to check if a reference to a delegate is null and execute it if it is valid:

notifyChange?.Invoke(ids);

You can add a conditional call to a delegate to a standard control event. For example:

myControl.SizeChanged += (object s, EventArgs e) => { checkSize?.Invoke(); };

The “is” keyword also came in handy with the ability to say something like:

if (myobj is CardSubsection){} 

and do something if myobj can be safely cast to the relevant class. “is” makes it way simpler (and produces clearer code) when passing one of a range of classes into a function via an object reference.

On the subject of clarity, I came across a little bit of code like this in a blog:

if (pa.TicksToGo-- > 0) {}

which is shorter than

pa.TicksToGo--; // or maybe pa.TicksToGo -=1;
if (pa.TicksToGo > 0) {}

but less easy to maintain at some future date. I know that JSLint would like to persuade you that -– and ++ are evil in and of themselves but I am not convinced – safe to use with due caution I think.

Looks like C# is going to “keep on giving” with the news that the dev teams for C# and VB are going to let the two languages drift further apart. Seems that developers using VB favour stability (presumably finding multiple language versions bothersome within their organisations) and the C# types are gluttons for novelty. Should be interesting.

Read more here: http://www.infoworld.com/article/3051066/application-development/microsoft-c-visual-basic-are-now-set-to-diverge.html