ButtonTextBox.cs
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
641
642
643
644
645
646
647
648
649
650
651
652
653
654
655
656
657
658
659
660
661
662
663
664
665
666
667
668
669
670
671
672
673
674
675
676
677
678
679
680
681
682
683
684
685
686
687
688
689
690
691
692
693
694
695
696
697
698
699
700
701
702
703
704
705
706
707
708
709
710
711
712
713
714
715
716
717
718
719
720
721
722
723
724
725
726
727
728
729
730
731
732
733
734
735
736
737
738
739
740
741
742
743
744
745
746
747
748
749
750
751
752
753
754
755
756
757
758
759
760
761
762
763
764
765
766
767
768
769
770
771
772
773
774
775
776
777
778
779
780
781
782
783
784
785
786
787
788
789
790
791
792
793
794
795
796
797
798
799
800
801
802
803
804
805
806
807
808
809
810
811
812
813
814
815
816
817
818
819
820
821
822
823
824
825
826
827
828
829
830
831
832
833
834
835
836
837
838
839
840
841
842
843
844
845
846
847
848
849
850
851
852
853
854
855
856
857
858
859
860
#define MF3

using System;
using Microsoft.SPOT;
using Microsoft.SPOT.Input;
using Microsoft.SPOT.Presentation;
using Microsoft.SPOT.Presentation.Controls;
using Microsoft.SPOT.Presentation.Media;
using UAM.InformatiX.SPOT.Input;
#if MF3
using Microsoft.SPOT.Hardware;
#endif

namespace UAM.InformatiX.SPOT.Presentation.Controls
{
/// <summary>
/// Represents a TextBox control which can be controlled using buttons.
/// </summary>
public class ButtonTextBox : UIElement
{
/// <summary>
/// Direction of text selecting.
/// </summary>
private enum SelectDirection
{
/// <summary>
/// Selection mode is not active.
/// </summary>
None,

/// <summary>
/// The direction was not determinted yet (eg. selection length = 0).
/// </summary>
Unknown,

/// <summary>
/// The selection was started in the left direction.
/// </summary>
Left,

/// <summary>
/// The selection was started in the right direction.
/// </summary>
Right
}

private Font _font; // text box font
private TextAlignment _alignment = TextAlignment.Left; // alignment of the user's text
private Pen _caretPen = new Pen(Color.Black, 1); // pen for the caret
private Pen _borderPen = new Pen((Color)0x000000, 1); // pen for the text box border
private Color _foreColor = Color.Black; // color of the text
private Brush _backBrush = new SolidColorBrush(Color.White); // background of the text box
private Brush _highlightBrush = new SolidColorBrush((Color)0xF1CDA8); // background of the selected text
private Color _highlightColor = (Color)0x4E2A05; // color of the selected text

private readonly IButtonTranslator _translator; // translator for looking up chars assigned to button
private char _passwordChar = '\0'; // character to be displayed instead of normal ones in password mode
private bool _overtypeMode = false; // true if new character replaces the one after caret, otherwise it will be inserted between
private bool _numbersOnly = false; // true to allow only numbers
private int _selectionStart = 0; // index where the selection was started
private int _selectionLength = 0; // length of the selection
private int _maxLength = int.MaxValue; // the maximum length of text allowed
private string _textContent = string.Empty; // the text value entered
private SelectDirection _selectDirection = SelectDirection.None; // direction to which the selection started
private bool _allowSelection = true; // true to allow switching to selection mode using Button.Select

private int _paddingTop = 2; //
private int _paddingLeft = 2; // padding between border and the text area
private int _paddingRight = 2; //
private int _paddingBottom = 2; //

/// <summary>
/// Initializes a new instance of the <see cref="ButtonTextBox" /> class.
/// </summary>
/// <param name="translator">The button translator to associate with this text box.</param>
public ButtonTextBox(IButtonTranslator translator)
{
_translator = translator;
_lastTimer = new DispatcherTimer(); // initialize auto commit timer
_lastTimer.Interval = TimeSpan.FromTicks(TimeSpan.TicksPerSecond); // to the default interval of one second
_lastTimer.Tick += new EventHandler(OnAutoCommitInput);
}

public override void OnRender(DrawingContext dc) // draws the text box on drawing context
{
base.OnRender(dc); // in case our base would like something to draw, like background or something like that...
// (actually UIElement does nothing)
int width, height;
GetRenderSize(out width, out height); // check out how big surface we have for drawing

dc.DrawRectangle(_backBrush, _borderPen, 0, 0, width, height); // draw the background and border, both brush and pen can be null

int bW, sW, aW;
string b, s, a;
GetTextRectangles(out b, out bW, out s, out sW, out a, out aW); // split text to before selection, selected and after selection, and measure widths

int border = (_borderPen == null ? 0 : (int)_borderPen.Thickness); // compute the starting coordinates
int x = _paddingLeft + border; // offset by border thickness and padding
int y = _paddingTop + border;
int w = bW + sW + aW; // total width of the text
int h = height - y - _paddingBottom - border; // total height available for the text
switch (_alignment)
{ // adjust the starting x coordinate
case TextAlignment.Center: // according to the text alignment
x += (width - w - _paddingLeft - _paddingRight) / 2;
break; // x will get negative if the text is wider than the text box
case TextAlignment.Right: // and the alignment is set to center or right,
x += width - w - _paddingRight; // this is okay, it will be clipped
break;
default:
x += System.Math.Min(width - bW, 0);
break;
}
// clip anything what will be outside the client area (the one for text)
dc.PushClippingRectangle(x, y, width - x - border - _paddingRight, height - y - _paddingBottom); // both x and y already has border and padding included

///if (bW != 0) // clear the background of the text before selection
// dc.DrawRectangle(_backBrush, null, x, y, bW, h); // not a good idea for case the textbox background brush is an ImageBrush for example
if (b != null)
dc.DrawText(b, Font, _foreColor, x, y); // draw the text before selection

if (sW != 0)
dc.DrawRectangle(_highlightBrush, null, x + bW, y, sW, h); // draw the selection background if available, shame it can't be semi-transparent ;-)
if (s != null)
dc.DrawText(s, Font, _highlightColor, x + bW, y); // draw the selected text

//if (aW != 0)
// dc.DrawRectangle(_backBrush, null, x + bW + sW, y, aW, h); // clear the background of the text after selection
if (a != null)
dc.DrawText(a, Font, _foreColor, x + bW + sW, y); // draw the text after selection

dc.PopClippingRectangle(); // restore clipping so that we can draw outside the client area again

if (IsFocused && !_overtypeMode) // only if the control is focused
dc.DrawLine(_caretPen, x + bW, y, x + bW, y + h - 1); // draw the caret (in overtype mode there is selection rectangle visible)
}

// finds out how big the text box would want to be
protected override void MeasureOverride(int availableWidth, int availableHeight, out int desiredWidth, out int desiredHeight)
{
// Font.ComputeTextInRect(_textContent, out desiredWidth, out desiredHeight, 0, 0, availableWidth, availableHeight, 0);
Font.ComputeExtent(_textContent, out desiredWidth, out desiredHeight); // measure the size of whole text
desiredWidth += Font.AverageWidth + _paddingLeft + _paddingTop; // add padding and place for one character
desiredHeight += _paddingTop + _paddingBottom; // (so that our textbox want collapse into single line when there is no text inside)

int border = _borderPen == null ? 0 : (int)_borderPen.Thickness; // if we should draw a border
desiredWidth += border + border; // add it too - for left one and for right one
desiredHeight += border + border; // top and bottom

desiredHeight = System.Math.Min(availableHeight, desiredHeight); // the WPF patterns say we should not return bigger values
desiredWidth = System.Math.Min(availableWidth, desiredWidth); // than we were told that are available
}

protected override void OnGotFocus(FocusChangedEventArgs e) // called when the buttons will be redirected to the text box
{
Invalidate(); // redraw, we might want to draw selection or caret if focused
base.OnGotFocus(e); // and let the base to accommodate as well
}
protected override void OnLostFocus(FocusChangedEventArgs e) // called when the buttons will no longer be redirected to the text box
{
Invalidate(); // redraw, we might want to hide selection or caret
base.OnLostFocus(e);
}

/// <summary>
/// Splits the text box content into strings before selection, the selected and the one after selection.
/// If any of this parts are not present, there will be set to null and their width to 0.
/// </summary>
/// <param name="before">The text before caret/selection.</param>
/// <param name="beforeWidth">The width the text before selection needs.</param>
/// <param name="selection">The selected text.</param>
/// <param name="selWidth">The width needed by seleted text.</param>
/// <param name="after">The text after selection.</param>
/// <param name="afterWidth">The width the text after selection needs.</param>
protected void GetTextRectangles(out string before, out int beforeWidth, out string selection, out int selWidth, out string after, out int afterWidth)
{
string renderText = GetRenderText(); // gets the string to render, this can be either TextContent directly
// or string consisting of the same length consisting of the password character
int height;
if (!IsFocused) // if there we don't have focus, we won't show the selection
{
before = renderText; // so it is easy - all the text will be considered as before selection
Font.ComputeExtent(before, out beforeWidth, out height); // and can be measured as one string

selection = after = null; // clear the other ones
selWidth = afterWidth = 0; // and their widths
return;
}

if (_selectionStart < 1) // if the cursor is at the beginning of the text
{
before = null; // there is no text before selection
beforeWidth = 0;
}
else
{
before = renderText.Substring(0, _selectionStart); // otherwise extract the part before selection
Font.ComputeExtent(before, out beforeWidth, out height); // and measure it
}

if (_selectionStart == renderText.Length && _overtypeMode) // if the cursor is at the end of the text and overtype mode is active
{
selection = after = null; // we have no selected text neither any after it
selWidth = Font.AverageWidth; // but we want to display an empty selected area to indicate overtype mode
afterWidth = 0;
return;
}
else // the cursor is before and or insert mode is active
{
if (_selectionLength < 1) // if there is no selection
{
if (_overtypeMode) // if the overtype mode is active
{
selection = renderText[_selectionStart].ToString(); // treat the first character afer the cursor position as selected (for rendering only purposes)
Font.ComputeExtent(selection, out selWidth, out height); // and measure it

after = renderText.Substring(_selectionStart + 1); // and the rest is after selection
Font.ComputeExtent(after, out afterWidth, out height); // measure it
return;
}
else // the insert mode is active
{
selection = null; // so there is just no selection
selWidth = 0;
}
}
else // if anything is selected
{
selection = renderText.Substring(_selectionStart, _selectionLength); // extract it
Font.ComputeExtent(selection, out selWidth, out height); // and measure it
}

after = renderText.Substring(_selectionStart + _selectionLength); // in every case this the text after selection
Font.ComputeExtent(after, out afterWidth, out height);
}
}

/// <summary>
/// Gets the string to render. This is either the TextContent directly or a string of the same length consisting of PasswordChar characters
/// </summary>
private string GetRenderText()
{
return _passwordChar == '\0' ? _textContent : new string(_passwordChar, _textContent.Length);
}

/// <summary>
/// Sets the padding of the text box.
/// </summary>
/// <param name="length">The desired padding value.</param>
public void SetPadding(int length)
{
_paddingTop = _paddingLeft = _paddingRight = _paddingBottom = length;
}
/// <summary>
/// Sets the padding of the text box for each side individually.
/// </summary>
/// <param name="left">The desired left pading.</param>
/// <param name="top">The desired top pading.</param>
/// <param name="right">The desired right pading.</param>
/// <param name="bottom">The desired bottom pading.</param>
public void SetPadding(int left, int top, int right, int bottom)
{
_paddingTop = top;
_paddingLeft = left;
_paddingRight = right;
_paddingBottom = bottom;
}

/// <summary>
/// Selects all the text in the text box.
/// </summary>
public void SelectAll()
{
_selectionStart = 0;
_selectionLength = _textContent.Length;
Invalidate();
}

private int _lastCount = 0; // a number of how many times the same button was pressed until the auto commit time out occured
private bool _commitWaiting = false; // true if user pressed button and we are waiting for the next button press or the auto commit time out
private Button _lastButton = Button.None; // the last button pressed
private DispatcherTimer _lastTimer; // ticks when the user has not pressed any button for specified interval
private bool CommitInput(Button replacingButton) // forces the currently uncommited input to become commited, returns if the commitment was successful
{
_lastTimer.Stop(); // stop the auto commit timer
_lastButton = replacingButton; // remember the button wich commited current input pressed
_lastCount = 0; // clear the number of how many times was the button pressed
if (!_commitWaiting) return false; // we were not waiting to commit input (eg. was just cursor movement)
else _commitWaiting = false; // we were waiting and here it is, so do not wait anymore

// advance cursor by the inserted text length
_selectionStart = System.Math.Min(_textContent.Length, _selectionStart + _selectionLength);
_selectionLength = 0; // clear any selection
_selectDirection = SelectDirection.None; // and cancel selection mode if active

return true; // we commited the input
}
protected virtual void OnAutoCommitInput(object sender, EventArgs e) // this handler is called when the user does not press any button during the AutoCommitInterval
{
CommitInput(Button.None); // commit the input without remembering any button so that if user presses the button again
Invalidate(); // it can type the first letter assigned to the button again, and redraw (selection cleared)
}
protected override void OnButtonDown(ButtonEventArgs e) // this handler is called when the user presses any button
{
#if MF3
Button button = e.Button;
#else
Button button = (e is UncheckedButtonEventArgs ? (e as UncheckedButtonEventArgs).Button : e.Button); // check if we have extended button range
#endif
bool dirty = false, commited = false; // dirty if we just need to redraw (like caret moved or selection updated)
// commited if the last commit succeeded and has already moved the cursor
if (button != _lastButton) // first, if this button is different than the last one,
dirty = commited = CommitInput(button); // commit the previous character (updates last button, cancels the timer and moves the cursor
else // if there was anything to commit)
{
_lastCount++; // otherwise increase the letter index which will be used
_lastTimer.Stop(); // and cancel the auto commit timer (it will be restarted again)
}

e.Handled = true; // assume we will use this button
switch (button) // okay, what button was it?
{
// behaves like BackSpace if there is any text before the cursor, or Delete if there is not or if in overtype mode
#if MF3
case Button.VK_BACK:
#else
case
Button.Back:
#endif
if (_textContent.Length < 1) break; // if there is no text at all, this button has nothing to do
if (_selectionLength > 0) // if there is something selected
{
_textContent = _textContent.Substring(0, _selectionStart) + _textContent.Substring(_selectionStart + _selectionLength);
_selectionLength = 0; // the selection will be deleted
}
else if (_overtypeMode) // if there is nothing selected but we are in overtype mode
if (_selectionStart < _textContent.Length) // and not at the end of the text, remove the character after cursor
_textContent = _textContent.Substring(0, _selectionStart) + _textContent.Substring(_selectionStart + 1);
else
{ // if we are at the end of the text
_textContent = _textContent.Substring(0, _textContent.Length - 1);
_selectionStart--; // remove the last one and move the cursor appropriately
}
else // if there is nothing selected and we are in insert mode
if (_selectionStart > 0)
{ // if we are not at the beginning at the text, remove the character before cursor
_textContent = _textContent.Substring(0, _selectionStart - 1) + _textContent.Substring(_selectionStart);
_selectionStart--; // and advance the cursor appropriately
}
else // if we are at the beginning of the text, remove the first character
_textContent = _textContent.Substring(1, _textContent.Length - 1);
OnTextChanged(); // notify others that the text was changed
break;

// behave like LeftArrow: move the cursor one character left, or adjust selection range
#if MF3
case Button.VK_LEFT:
case Button.VK_UP:
#else
case
Button.Left:
case Button.Up:
#endif
switch (_selectDirection) // are we selecting?
{
case SelectDirection.None: // if not
if (_selectionLength > 0) // but there is something selected,
{ // move the cursor one character before selection if possible,
_selectionStart = System.Math.Max(0, _selectionStart - 1);
_selectionLength = 0; // cancel the selection
dirty = true; // and request redraw
}
else // if there is no selection, just move the cursor left if possible
if (_selectionStart > 0) dirty |= SetValue(ref _selectionStart, _selectionStart - 1);
break; // and request redraw if it changed its position (was not at the beginning)
case SelectDirection.Unknown: // we are selecting, but hadn't known in which direction (the selection length is 0)
if (_selectionStart > 0)
{ // if we are not at the beginning,
_selectDirection = SelectDirection.Left; // it is to the left
goto case SelectDirection.Left;
}
break;
case SelectDirection.Left: // we are selecting and the selection started to the left
if (_selectionStart > 0)
{
_selectionStart--; // we do not support positioning the cursor in the selection, so it just must stay at the beginning
_selectionLength++; // expand the selection by one character, since we are moving in the direction the selecting started
dirty = true;
}
break;
case SelectDirection.Right: // we are selecting and the selection started to the right
if (_selectionLength > 0) // the other case should never happen
{ // shorten the selection by one character,
_selectionLength--; // since we are moving against the direction the selecting started
if (_selectionLength == 0) // if this resulted in selection to collapse
_selectDirection = SelectDirection.Unknown; // clear the direction so that user can start selecting in any direction over again
dirty = true;
}
break;
}
break;

// behave like RightArrow: move the cursor one character right, or adjust selection range
#if MF3
case Button.VK_RIGHT:
case Button.VK_DOWN:
#else
case
Button.Right:
case Button.Down:
#endif
switch (_selectDirection) // are we selecting?
{
case SelectDirection.None: // if not
if (_selectionLength > 0) // but there is something selected,
{ // move the cursor one character after the selection if possible,
_selectionStart = System.Math.Min(_textContent.Length, _selectionStart + _selectionLength);
_selectionLength = 0; // cancel the selection
dirty = true; // and request redraw
}
else // if there is no selection, just move the cursor right if possible, but not if the input was just commited
if (_selectionStart < _textContent.Length && !commited) dirty |= SetValue(ref _selectionStart, _selectionStart + 1);
break; // because in that case it is already moved
case SelectDirection.Unknown: // we are selecting, but hadn't known in which direction (the selection length is 0)
if (_selectionStart < _textContent.Length)
{ // if we are not at the end,
_selectDirection = SelectDirection.Right; // it is to the right
goto case SelectDirection.Right;
}
break;
case SelectDirection.Left: // we are selecting and the selection started to the left
if (_selectionLength > 0) // the other case should never happen
{ // shorten the selection by one character,
_selectionLength--; // since we are moving against the direction the selecting started
_selectionStart++; // (keep the cursor at the beginning of the seleciton as we do not support anything else)
if (_selectionLength == 0) // if this resulted in selection to collapse
_selectDirection = SelectDirection.Unknown; // clear the direction so that user can start selecting in any direction over again
dirty = true;
}
break;
case SelectDirection.Right: // we are selecting and the selection started to the right
dirty |= SetValue(ref _selectionLength, System.Math.Min(_textContent.Length - _selectionStart, _selectionLength + 1));
break; // just expand the selection by one character if possile
} // and request redraw if it changed its position (was not at the end)
break;

// behave like Home: move the cursor at the beginnig
#if MF3
case Button.VK_HOME:
#else
case
Button.Rewind:
case Button.Home:
#endif
dirty |= SetValue(ref _selectionLength, 0); // cancel the selection
dirty |= SetValue(ref _selectionStart, 0); // and move the cursor to the beginning
break; // SetValue returns true if it has changed the value, and thus redraw is required

// behaves like End: move the cursor to the end
#if MF3
case Button.VK_END:
#else
case
Button.FastForward:
#endif
dirty |= SetValue(ref _selectionLength, 0); // cancel the selection
dirty |= SetValue(ref _selectionStart, _textContent.Length); // end move the cursor to the end
break; // and we use |= instead of = to not cancel the redraw request if it has been already placed

// toggles between selecting and typing mode
#if MF3
case Button.VK_RETURN:
case Button.VK_SELECT:
#else
case
Button.Select:
#endif
if (_allowSelection) // of course only if the selecting mode is supported at all
_selectDirection = _selectDirection == SelectDirection.None ? SelectDirection.Unknown : SelectDirection.None;
break; // by turning the selection on, we do not know in which direction will it start

// the pressed button was not known control one and may represent a character
default:
string insertion = string.Empty; // by default do not insert anything if this button isn't mapped to any letter
if (_numbersOnly)
{ // if we allow numbers only
int number;
if (_translator.GetNumber(button, out number)) // and if there was any number assigned to the button
insertion = number.ToString(); // use it
}
else
{
string text;
if (_translator.GetText(button, _lastCount, out text)) // if there was any character/text assigned to the button under the number of buttons pressed
insertion = text; // use it
}

if (insertion != null && insertion.Length > 0) // if we have anything to insert
{ // simulate selection if overtype mode is active and nothing is selected
if (_overtypeMode && _selectionLength < 1 && _selectionStart < _textContent.Length) _selectionLength = 1;
_textContent = _textContent.Substring(0, _selectionStart) + insertion + _textContent.Substring(_selectionStart + _selectionLength);
// replace the selection with insertion (will be inserted if nothing is selected)
if (_textContent.Length > _maxLength) // if this resulted in longer string than allowed
_textContent = _textContent.Substring(0, _maxLength); // truncate it (this is different behavior than in Windows,
// which would have not allowed to type anything what does not fit)
if (_numbersOnly)
{ // there is only one number per button so this is final input, move the cursor after the inserted text
_selectionStart = System.Math.Min(_textContent.Length, _selectionStart + insertion.Length);
_selectionLength = 0; // and cancel selection if any
}
else
{ // we have to wait for the AutoCommitInterval or another button event to confirm the input
_commitWaiting = true; // select the input length so that the next proposal replaces the current one
_selectionLength = System.Math.Min(_textContent.Length - _selectionStart, insertion.Length); // but be careful that the insertion could be truncated by MaxLength
_lastTimer.Start(); // start the timer... ťik ťak...
}
OnTextChanged(); // notify others that the text was changed
}
else
e.Handled = false; // our assumption was wrong, we don't know what to do with this button
break;
}

if (dirty) Invalidate(); // redraw if requested
base.OnButtonDown(e); // in case our base would like to do something with the event, like switch focus...(UIElement does nothing)
}

/// <summary>
/// Gets or sets the font of the text.
/// </summary>
public Font Font
{
get { return _font; }
set
{
this._font = value;
base.InvalidateMeasure(); // changing font could affect our size if we can choose it
}
}
/// <summary>
/// Gets or sets how the text should be aligned in the text box.
/// </summary>
public TextAlignment TextAlignment
{
get { return _alignment; }
set
{
if (_alignment != value)
{
_alignment = value;
Invalidate();
}
}
}
/// <summary>
/// Gets or sets the color of the text.
/// </summary>
public Color ForeColor
{
get { return _foreColor; }
set
{
if (_foreColor != value)
{
_foreColor = value;
Invalidate();
}
}
}
/// <summary>
/// Gets or sets the border pen.
/// </summary>
public Pen Border
{
get { return _borderPen; }
set
{
if (_borderPen != value)
if (_borderPen == null || value == null || _borderPen.Thickness != value.Thickness)
{
_borderPen = value;
InvalidateMeasure(); // changing the border thickness or turning it off or on affects our size if wee can choose it
}
else
{
_borderPen = value; // changing color only
Invalidate();
}
}
}
/// <summary>
/// Gets or sets the text box background.
/// </summary>
public Brush Background
{
get { return _backBrush; }
set
{
if (_backBrush != value)
{
_backBrush = value;
Invalidate();
}
}
}

/// <summary>
/// Gets or sets the color of the selected text.
/// </summary>
public Color HighlightColor
{
get { return _highlightColor; }
set
{
if (_highlightColor != value)
{
_highlightColor = value;
if (_selectionLength > 0 || _overtypeMode) Invalidate(); // redraw only if there is any selected text visible
}
}
}
/// <summary>
/// Gets or sets the selection background.
/// </summary>
public Brush HighlightBackground
{
get { return _highlightBrush; }
set
{
if (_highlightBrush != value)
{
_highlightBrush = value;
if (_selectionLength > 0 || _overtypeMode) Invalidate(); // redraw only if there is any selection visible
}
}
}

/// <summary>
/// Gets or sets the character which will be rendered instead all other characters.
/// The null character '\0' means that the text will not be masked.
/// </summary>
public char PasswordChar
{
get { return _passwordChar; }
set
{
if (_passwordChar != value)
{
_passwordChar = value;
InvalidateMeasure(); // the width of password char can change rendering width which affects size if we can choose it
}
}
}
/// <summary>
/// Gets or sets whether new input will replace the character after the cursor instead of being inserted.
/// </summary>
public bool OvertypeMode
{
get { return _overtypeMode; }
set
{
if (_overtypeMode != value)
{
_overtypeMode = value;
if (IsFocused) Invalidate(); // neither caret nor selection is visible if we don't have a focus
}
}
}
/// <summary>
/// Gets or sets whether only numbers are allowed in the text box.
/// If there is already text in the textbox, setting this property to true will remove any non-numeric characters.
/// </summary>
public bool AllowNumbersOnly
{
get { return _numbersOnly; }
set
{
if (_numbersOnly != value)
{
_numbersOnly = value;
if (value)
{
_textContent = FilterNumbers(_textContent);
InvalidateMeasure();
}
}
}
}
/// <summary>
/// Gets or sets the caret position.
/// </summary>
public int CaretIndex
{
get { return SelectionStart; }
set { SelectionStart = value; }
}
/// <summary>
/// Gets or sets the selection start (cursor position).
/// </summary>
public int SelectionStart
{
get { return _selectionStart; }
set
{
value = System.Math.Min(_textContent.Length, value); // keep it inside the text
if (_selectionStart != value)
{
_selectionStart = value; // adjust selection length if it exceeded the text length
_selectionLength = System.Math.Max(0, System.Math.Min(_textContent.Length - _selectionStart, _selectionLength));
if (IsFocused) Invalidate(); // neither caret nor selection is visible if we don't have a focus
}
}
}
/// <summary>
/// Gets or sets the selection length.
/// </summary>
public int SelectionLength
{
get { return _selectionLength; }
set
{
value = System.Math.Min(_textContent.Length, value); // keep it short enough
if (_selectionLength != value)
{
if (value < 0) throw new ArgumentOutOfRangeException("value");
_selectionLength = System.Math.Max(0, System.Math.Min(_textContent.Length - _selectionStart, value)); // keep it inside the text
if (IsFocused) Invalidate(); // selection is not visible if we don't have a focus
}
}
}
/// <summary>
/// Gets or sets the value that determines the maximum number of characters allowed for user input.
/// Set zero to not limit the input length.
/// </summary>
public int MaxLength
{
get { return _maxLength; }
set
{
if (_maxLength != value)
{
if (_maxLength < 0) throw new ArgumentOutOfRangeException("value");
_maxLength = value == 0 ? int.MaxValue : value;
if (_maxLength > 0)
{
if (_selectionStart > _maxLength) _selectionStart = _maxLength; // keep the selection in new max length
if (_textContent.Length > _maxLength) _textContent = _textContent.Substring(0, _maxLength); // and truncate the text if it is longer
InvalidateMeasure();
}
}
}
}
/// <summary>
/// Gets or sets the text contents of the text box.
/// </summary>
public string TextContent
{
get { return _textContent; }
set
{
if (value == null) value = string.Empty;
if (_textContent != value)
{
_textContent = _numbersOnly ? FilterNumbers(value) : value; // remove numbers if they are not allowed
if (_textContent.Length > _maxLength)
_textContent = _textContent.Substring(0, _maxLength); // truncate the value if longer than MaxLength

if (_selectionStart > _textContent.Length)
_selectionStart = _textContent.Length; // keep the cursor inside the text

_selectionLength = 0; // cancel the selection
OnTextChanged(); // notify others that the text was changed
}
}
}
/// <summary>
/// Gets the content of the current selection in the text box.
/// </summary>
public string SelectedText
{
get
{
if (_selectionLength == 0) return string.Empty;
return _textContent.Substring(_selectionStart, _selectionLength);
}
}
/// <summary>
/// Gets whether the text box is in selection mode.
/// </summary>
public bool IsSelecting
{
get { return _selectDirection == SelectDirection.None; }
}
/// <summary>
/// Gets or sets whether the text box supports switching to the selection mode.
/// </summary>
public bool AllowSelection
{
get { return _allowSelection; }
set
{
_allowSelection = value;
if (_allowSelection != value && !value) // if the selection is not allowed anymore
{
_selectDirection = SelectDirection.None; // turn of selection mode
_selectionLength = 0; // and cancel any selection
if (IsFocused) Invalidate(); // selection is not visible if we don't have a focus
}
}
}

/// <summary>
/// Gets or sets the interval after which the suggested input will be automatically commited.
/// </summary>
public TimeSpan AutoCommitInterval
{
get { return _lastTimer.Interval; }
set { _lastTimer.Interval = value; }
}

/// <summary>
/// Fires the TextChanged event.
/// </summary>
protected virtual void OnTextChanged()
{
InvalidateMeasure(); // text change affects size if we can choose it
if (TextChanged != null)
TextChanged(this, EventArgs.Empty);
}
public event EventHandler TextChanged;

/// <summary>
/// Removes any non-numeric characters from the string
/// </summary>
/// <param name="value">The string to filter.</param>
private static string FilterNumbers(string value)
{
if (value == null) return null;
if (value == string.Empty) return string.Empty;

char[] chars = new char[value.Length]; // create a char buffer, which cannot be bigger than the number of characters in the value
int charsIndex = 0;
for (int i = 0; i < chars.Length; i++) // test every char of the value and if it is a valid number, copy it to the chars array
if (value[i] >= '0' && value[i] <= '9') // TODO: use IButtonTranslator's number values instead of fixed 0-9, which by the way misses lots of Unicode valid number characters
chars[charsIndex++] = value[i];

return new string(chars, 0, charsIndex); // build string from the chars array, using the filled part only
}
/// <summary>
/// Sets the value of a variable and returns wheter the variable was changed.
/// </summary>
/// <param name="value">The variable which value is to be set.</param>
/// <param name="newValue">The new value for the variable.</param>
/// <returns>true if newValue was different than value; otherwise false.</returns>
private static bool SetValue(ref int value, int newValue)
{
if (value != newValue)
{
value = newValue;
return true;
}
else
return false
;
}
}
}