TextBox.cs 27 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849
  1. // Copyright (c) The Avalonia Project. All rights reserved.
  2. // Licensed under the MIT license. See licence.md file in the project root for full license information.
  3. using Avalonia.Input.Platform;
  4. using System;
  5. using System.Collections.Generic;
  6. using System.Linq;
  7. using System.Reactive.Linq;
  8. using Avalonia.Controls.Presenters;
  9. using Avalonia.Controls.Primitives;
  10. using Avalonia.Controls.Templates;
  11. using Avalonia.Controls.Utils;
  12. using Avalonia.Input;
  13. using Avalonia.Interactivity;
  14. using Avalonia.Media;
  15. using Avalonia.Metadata;
  16. using Avalonia.Data;
  17. namespace Avalonia.Controls
  18. {
  19. public class TextBox : TemplatedControl, UndoRedoHelper<TextBox.UndoRedoState>.IUndoRedoHost
  20. {
  21. public static readonly StyledProperty<bool> AcceptsReturnProperty =
  22. AvaloniaProperty.Register<TextBox, bool>("AcceptsReturn");
  23. public static readonly StyledProperty<bool> AcceptsTabProperty =
  24. AvaloniaProperty.Register<TextBox, bool>("AcceptsTab");
  25. public static readonly DirectProperty<TextBox, bool> CanScrollHorizontallyProperty =
  26. AvaloniaProperty.RegisterDirect<TextBox, bool>("CanScrollHorizontally", o => o.CanScrollHorizontally);
  27. public static readonly DirectProperty<TextBox, int> CaretIndexProperty =
  28. AvaloniaProperty.RegisterDirect<TextBox, int>(
  29. nameof(CaretIndex),
  30. o => o.CaretIndex,
  31. (o, v) => o.CaretIndex = v);
  32. public static readonly DirectProperty<TextBox, IEnumerable<Exception>> DataValidationErrorsProperty =
  33. AvaloniaProperty.RegisterDirect<TextBox, IEnumerable<Exception>>(
  34. nameof(DataValidationErrors),
  35. o => o.DataValidationErrors);
  36. public static readonly StyledProperty<bool> IsReadOnlyProperty =
  37. AvaloniaProperty.Register<TextBox, bool>(nameof(IsReadOnly));
  38. public static readonly DirectProperty<TextBox, int> SelectionStartProperty =
  39. AvaloniaProperty.RegisterDirect<TextBox, int>(
  40. nameof(SelectionStart),
  41. o => o.SelectionStart,
  42. (o, v) => o.SelectionStart = v);
  43. public static readonly DirectProperty<TextBox, int> SelectionEndProperty =
  44. AvaloniaProperty.RegisterDirect<TextBox, int>(
  45. nameof(SelectionEnd),
  46. o => o.SelectionEnd,
  47. (o, v) => o.SelectionEnd = v);
  48. public static readonly DirectProperty<TextBox, string> TextProperty =
  49. TextBlock.TextProperty.AddOwner<TextBox>(
  50. o => o.Text,
  51. (o, v) => o.Text = v,
  52. defaultBindingMode: BindingMode.TwoWay,
  53. enableDataValidation: true);
  54. public static readonly StyledProperty<TextAlignment> TextAlignmentProperty =
  55. TextBlock.TextAlignmentProperty.AddOwner<TextBox>();
  56. public static readonly StyledProperty<TextWrapping> TextWrappingProperty =
  57. TextBlock.TextWrappingProperty.AddOwner<TextBox>();
  58. public static readonly StyledProperty<string> WatermarkProperty =
  59. AvaloniaProperty.Register<TextBox, string>("Watermark");
  60. public static readonly StyledProperty<bool> UseFloatingWatermarkProperty =
  61. AvaloniaProperty.Register<TextBox, bool>("UseFloatingWatermark");
  62. struct UndoRedoState : IEquatable<UndoRedoState>
  63. {
  64. public string Text { get; }
  65. public int CaretPosition { get; }
  66. public UndoRedoState(string text, int caretPosition)
  67. {
  68. Text = text;
  69. CaretPosition = caretPosition;
  70. }
  71. public bool Equals(UndoRedoState other) => ReferenceEquals(Text, other.Text) || Equals(Text, other.Text);
  72. }
  73. private string _text;
  74. private int _caretIndex;
  75. private int _selectionStart;
  76. private int _selectionEnd;
  77. private bool _canScrollHorizontally;
  78. private TextPresenter _presenter;
  79. private UndoRedoHelper<UndoRedoState> _undoRedoHelper;
  80. private bool _ignoreTextChanges;
  81. private IEnumerable<Exception> _dataValidationErrors;
  82. static TextBox()
  83. {
  84. FocusableProperty.OverrideDefaultValue(typeof(TextBox), true);
  85. }
  86. public TextBox()
  87. {
  88. this.GetObservable(TextWrappingProperty)
  89. .Select(x => x == TextWrapping.NoWrap)
  90. .Subscribe(x => CanScrollHorizontally = x);
  91. var horizontalScrollBarVisibility = this.GetObservable(AcceptsReturnProperty)
  92. .Select(x => x ? ScrollBarVisibility.Auto : ScrollBarVisibility.Hidden);
  93. Bind(
  94. ScrollViewer.HorizontalScrollBarVisibilityProperty,
  95. horizontalScrollBarVisibility,
  96. BindingPriority.Style);
  97. _undoRedoHelper = new UndoRedoHelper<UndoRedoState>(this);
  98. }
  99. public bool AcceptsReturn
  100. {
  101. get { return GetValue(AcceptsReturnProperty); }
  102. set { SetValue(AcceptsReturnProperty, value); }
  103. }
  104. public bool AcceptsTab
  105. {
  106. get { return GetValue(AcceptsTabProperty); }
  107. set { SetValue(AcceptsTabProperty, value); }
  108. }
  109. public bool CanScrollHorizontally
  110. {
  111. get { return _canScrollHorizontally; }
  112. private set { SetAndRaise(CanScrollHorizontallyProperty, ref _canScrollHorizontally, value); }
  113. }
  114. public int CaretIndex
  115. {
  116. get
  117. {
  118. return _caretIndex;
  119. }
  120. set
  121. {
  122. value = CoerceCaretIndex(value);
  123. SetAndRaise(CaretIndexProperty, ref _caretIndex, value);
  124. UndoRedoState state;
  125. if (_undoRedoHelper.TryGetLastState(out state) && state.Text == Text)
  126. _undoRedoHelper.UpdateLastState();
  127. }
  128. }
  129. public IEnumerable<Exception> DataValidationErrors
  130. {
  131. get { return _dataValidationErrors; }
  132. private set { SetAndRaise(DataValidationErrorsProperty, ref _dataValidationErrors, value); }
  133. }
  134. public bool IsReadOnly
  135. {
  136. get { return GetValue(IsReadOnlyProperty); }
  137. set { SetValue(IsReadOnlyProperty, value); }
  138. }
  139. public int SelectionStart
  140. {
  141. get
  142. {
  143. return _selectionStart;
  144. }
  145. set
  146. {
  147. value = CoerceCaretIndex(value);
  148. SetAndRaise(SelectionStartProperty, ref _selectionStart, value);
  149. }
  150. }
  151. public int SelectionEnd
  152. {
  153. get
  154. {
  155. return _selectionEnd;
  156. }
  157. set
  158. {
  159. value = CoerceCaretIndex(value);
  160. SetAndRaise(SelectionEndProperty, ref _selectionEnd, value);
  161. }
  162. }
  163. [Content]
  164. public string Text
  165. {
  166. get { return _text; }
  167. set
  168. {
  169. if (!_ignoreTextChanges)
  170. {
  171. CaretIndex = CoerceCaretIndex(CaretIndex, value?.Length ?? 0);
  172. SetAndRaise(TextProperty, ref _text, value);
  173. }
  174. }
  175. }
  176. public TextAlignment TextAlignment
  177. {
  178. get { return GetValue(TextAlignmentProperty); }
  179. set { SetValue(TextAlignmentProperty, value); }
  180. }
  181. public string Watermark
  182. {
  183. get { return GetValue(WatermarkProperty); }
  184. set { SetValue(WatermarkProperty, value); }
  185. }
  186. public bool UseFloatingWatermark
  187. {
  188. get { return GetValue(UseFloatingWatermarkProperty); }
  189. set { SetValue(UseFloatingWatermarkProperty, value); }
  190. }
  191. public TextWrapping TextWrapping
  192. {
  193. get { return GetValue(TextWrappingProperty); }
  194. set { SetValue(TextWrappingProperty, value); }
  195. }
  196. protected override void OnTemplateApplied(TemplateAppliedEventArgs e)
  197. {
  198. _presenter = e.NameScope.Get<TextPresenter>("PART_TextPresenter");
  199. _presenter.Cursor = new Cursor(StandardCursorType.Ibeam);
  200. if(IsFocused)
  201. {
  202. _presenter.ShowCaret();
  203. }
  204. }
  205. protected override void OnGotFocus(GotFocusEventArgs e)
  206. {
  207. base.OnGotFocus(e);
  208. // when navigating to a textbox via the tab key, select all text if
  209. // 1) this textbox is *not* a multiline textbox
  210. // 2) this textbox has any text to select
  211. if (e.NavigationMethod == NavigationMethod.Tab &&
  212. !AcceptsReturn &&
  213. Text?.Length > 0)
  214. {
  215. SelectionStart = 0;
  216. SelectionEnd = Text.Length;
  217. }
  218. else
  219. {
  220. _presenter?.ShowCaret();
  221. }
  222. }
  223. protected override void OnLostFocus(RoutedEventArgs e)
  224. {
  225. base.OnLostFocus(e);
  226. SelectionStart = 0;
  227. SelectionEnd = 0;
  228. _presenter.HideCaret();
  229. }
  230. protected override void OnTextInput(TextInputEventArgs e)
  231. {
  232. HandleTextInput(e.Text);
  233. }
  234. private void HandleTextInput(string input)
  235. {
  236. if (!IsReadOnly)
  237. {
  238. string text = Text ?? string.Empty;
  239. int caretIndex = CaretIndex;
  240. if (!string.IsNullOrEmpty(input))
  241. {
  242. DeleteSelection();
  243. caretIndex = CaretIndex;
  244. text = Text ?? string.Empty;
  245. SetTextInternal(text.Substring(0, caretIndex) + input + text.Substring(caretIndex));
  246. CaretIndex += input.Length;
  247. SelectionStart = SelectionEnd = CaretIndex;
  248. _undoRedoHelper.DiscardRedo();
  249. }
  250. }
  251. }
  252. private async void Copy()
  253. {
  254. await ((IClipboard)AvaloniaLocator.Current.GetService(typeof(IClipboard)))
  255. .SetTextAsync(GetSelection());
  256. }
  257. private async void Paste()
  258. {
  259. var text = await ((IClipboard)AvaloniaLocator.Current.GetService(typeof(IClipboard))).GetTextAsync();
  260. if (text == null)
  261. {
  262. return;
  263. }
  264. _undoRedoHelper.Snapshot();
  265. HandleTextInput(text);
  266. }
  267. protected override void OnKeyDown(KeyEventArgs e)
  268. {
  269. string text = Text ?? string.Empty;
  270. int caretIndex = CaretIndex;
  271. bool movement = false;
  272. bool handled = true;
  273. var modifiers = e.Modifiers;
  274. switch (e.Key)
  275. {
  276. case Key.A:
  277. if (modifiers == InputModifiers.Control)
  278. {
  279. SelectAll();
  280. }
  281. break;
  282. case Key.C:
  283. if (modifiers == InputModifiers.Control)
  284. {
  285. Copy();
  286. }
  287. break;
  288. case Key.X:
  289. if (modifiers == InputModifiers.Control)
  290. {
  291. Copy();
  292. DeleteSelection();
  293. }
  294. break;
  295. case Key.V:
  296. if (modifiers == InputModifiers.Control)
  297. {
  298. Paste();
  299. }
  300. break;
  301. case Key.Z:
  302. if (modifiers == InputModifiers.Control)
  303. _undoRedoHelper.Undo();
  304. break;
  305. case Key.Y:
  306. if (modifiers == InputModifiers.Control)
  307. _undoRedoHelper.Redo();
  308. break;
  309. case Key.Left:
  310. MoveHorizontal(-1, modifiers);
  311. movement = true;
  312. break;
  313. case Key.Right:
  314. MoveHorizontal(1, modifiers);
  315. movement = true;
  316. break;
  317. case Key.Up:
  318. MoveVertical(-1, modifiers);
  319. movement = true;
  320. break;
  321. case Key.Down:
  322. MoveVertical(1, modifiers);
  323. movement = true;
  324. break;
  325. case Key.Home:
  326. MoveHome(modifiers);
  327. movement = true;
  328. break;
  329. case Key.End:
  330. MoveEnd(modifiers);
  331. movement = true;
  332. break;
  333. case Key.Back:
  334. if (modifiers == InputModifiers.Control && SelectionStart == SelectionEnd)
  335. {
  336. SetSelectionForControlBackspace(modifiers);
  337. }
  338. if (!DeleteSelection() && CaretIndex > 0)
  339. {
  340. var removedCharacters = 1;
  341. // handle deleting /r/n
  342. // you don't ever want to leave a dangling /r around. So, if deleting /n, check to see if
  343. // a /r should also be deleted.
  344. if (CaretIndex > 1 &&
  345. text[CaretIndex - 1] == '\n' &&
  346. text[CaretIndex - 2] == '\r')
  347. {
  348. removedCharacters = 2;
  349. }
  350. SetTextInternal(text.Substring(0, caretIndex - removedCharacters) + text.Substring(caretIndex));
  351. CaretIndex -= removedCharacters;
  352. SelectionStart = SelectionEnd = CaretIndex;
  353. }
  354. break;
  355. case Key.Delete:
  356. if (modifiers == InputModifiers.Control && SelectionStart == SelectionEnd)
  357. {
  358. SetSelectionForControlDelete(modifiers);
  359. }
  360. if (!DeleteSelection() && caretIndex < text.Length)
  361. {
  362. var removedCharacters = 1;
  363. // handle deleting /r/n
  364. // you don't ever want to leave a dangling /r around. So, if deleting /n, check to see if
  365. // a /r should also be deleted.
  366. if (CaretIndex < text.Length - 1 &&
  367. text[caretIndex + 1] == '\n' &&
  368. text[caretIndex] == '\r')
  369. {
  370. removedCharacters = 2;
  371. }
  372. SetTextInternal(text.Substring(0, caretIndex) + text.Substring(caretIndex + removedCharacters));
  373. }
  374. break;
  375. case Key.Enter:
  376. if (AcceptsReturn)
  377. {
  378. HandleTextInput("\r\n");
  379. }
  380. break;
  381. case Key.Tab:
  382. if (AcceptsTab)
  383. {
  384. HandleTextInput("\t");
  385. }
  386. else
  387. {
  388. base.OnKeyDown(e);
  389. handled = false;
  390. }
  391. break;
  392. default:
  393. handled = false;
  394. break;
  395. }
  396. if (movement && ((modifiers & InputModifiers.Shift) != 0))
  397. {
  398. SelectionEnd = CaretIndex;
  399. }
  400. else if (movement)
  401. {
  402. SelectionStart = SelectionEnd = CaretIndex;
  403. }
  404. if (handled)
  405. {
  406. e.Handled = true;
  407. }
  408. }
  409. protected override void OnPointerPressed(PointerPressedEventArgs e)
  410. {
  411. if (e.Source == _presenter)
  412. {
  413. var point = e.GetPosition(_presenter);
  414. var index = CaretIndex = _presenter.GetCaretIndex(point);
  415. var text = Text;
  416. if (text != null)
  417. {
  418. switch (e.ClickCount)
  419. {
  420. case 1:
  421. SelectionStart = SelectionEnd = index;
  422. break;
  423. case 2:
  424. if (!StringUtils.IsStartOfWord(text, index))
  425. {
  426. SelectionStart = StringUtils.PreviousWord(text, index);
  427. }
  428. SelectionEnd = StringUtils.NextWord(text, index);
  429. break;
  430. case 3:
  431. SelectionStart = 0;
  432. SelectionEnd = text.Length;
  433. break;
  434. }
  435. }
  436. e.Device.Capture(_presenter);
  437. e.Handled = true;
  438. }
  439. }
  440. protected override void OnPointerMoved(PointerEventArgs e)
  441. {
  442. if (_presenter != null && e.Device.Captured == _presenter)
  443. {
  444. var point = e.GetPosition(_presenter);
  445. CaretIndex = SelectionEnd = _presenter.GetCaretIndex(point);
  446. }
  447. }
  448. protected override void OnPointerReleased(PointerReleasedEventArgs e)
  449. {
  450. if (_presenter != null && e.Device.Captured == _presenter)
  451. {
  452. e.Device.Capture(null);
  453. }
  454. }
  455. protected override void UpdateDataValidation(AvaloniaProperty property, BindingNotification status)
  456. {
  457. if (property == TextProperty)
  458. {
  459. var classes = (IPseudoClasses)Classes;
  460. DataValidationErrors = UnpackException(status.Error);
  461. classes.Set(":error", DataValidationErrors != null);
  462. }
  463. }
  464. private static IEnumerable<Exception> UnpackException(Exception exception)
  465. {
  466. if (exception != null)
  467. {
  468. var aggregate = exception as AggregateException;
  469. var exceptions = aggregate == null ?
  470. (IEnumerable<Exception>)new[] { exception } :
  471. aggregate.InnerExceptions;
  472. var filtered = exceptions.Where(x => !(x is BindingChainException)).ToList();
  473. if (filtered.Count > 0)
  474. {
  475. return filtered;
  476. }
  477. }
  478. return null;
  479. }
  480. private int CoerceCaretIndex(int value) => CoerceCaretIndex(value, Text?.Length ?? 0);
  481. private int CoerceCaretIndex(int value, int length)
  482. {
  483. var text = Text;
  484. if (value < 0)
  485. {
  486. return 0;
  487. }
  488. else if (value > length)
  489. {
  490. return length;
  491. }
  492. else if (value > 0 && text[value - 1] == '\r' && text[value] == '\n')
  493. {
  494. return value + 1;
  495. }
  496. else
  497. {
  498. return value;
  499. }
  500. }
  501. private int DeleteCharacter(int index)
  502. {
  503. var start = index + 1;
  504. var text = Text;
  505. var c = text[index];
  506. var result = 1;
  507. if (c == '\n' && index > 0 && text[index - 1] == '\r')
  508. {
  509. --index;
  510. ++result;
  511. }
  512. else if (c == '\r' && index < text.Length - 1 && text[index + 1] == '\n')
  513. {
  514. ++start;
  515. ++result;
  516. }
  517. Text = text.Substring(0, index) + text.Substring(start);
  518. return result;
  519. }
  520. private void MoveHorizontal(int direction, InputModifiers modifiers)
  521. {
  522. var text = Text ?? string.Empty;
  523. var caretIndex = CaretIndex;
  524. if ((modifiers & InputModifiers.Control) == 0)
  525. {
  526. var index = caretIndex + direction;
  527. if (index < 0 || index > text.Length)
  528. {
  529. return;
  530. }
  531. else if (index == text.Length)
  532. {
  533. CaretIndex = index;
  534. return;
  535. }
  536. var c = text[index];
  537. if (direction > 0)
  538. {
  539. CaretIndex += (c == '\r' && index < text.Length - 1 && text[index + 1] == '\n') ? 2 : 1;
  540. }
  541. else
  542. {
  543. CaretIndex -= (c == '\n' && index > 0 && text[index - 1] == '\r') ? 2 : 1;
  544. }
  545. }
  546. else
  547. {
  548. if (direction > 0)
  549. {
  550. CaretIndex += StringUtils.NextWord(text, caretIndex) - caretIndex;
  551. }
  552. else
  553. {
  554. CaretIndex += StringUtils.PreviousWord(text, caretIndex) - caretIndex;
  555. }
  556. }
  557. }
  558. private void MoveVertical(int count, InputModifiers modifiers)
  559. {
  560. var formattedText = _presenter.FormattedText;
  561. var lines = formattedText.GetLines().ToList();
  562. var caretIndex = CaretIndex;
  563. var lineIndex = GetLine(caretIndex, lines) + count;
  564. if (lineIndex >= 0 && lineIndex < lines.Count)
  565. {
  566. var line = lines[lineIndex];
  567. var rect = formattedText.HitTestTextPosition(caretIndex);
  568. var y = count < 0 ? rect.Y : rect.Bottom;
  569. var point = new Point(rect.X, y + (count * (line.Height / 2)));
  570. var hit = formattedText.HitTestPoint(point);
  571. CaretIndex = hit.TextPosition + (hit.IsTrailing ? 1 : 0);
  572. }
  573. }
  574. private void MoveHome(InputModifiers modifiers)
  575. {
  576. var text = Text ?? string.Empty;
  577. var caretIndex = CaretIndex;
  578. if ((modifiers & InputModifiers.Control) != 0)
  579. {
  580. caretIndex = 0;
  581. }
  582. else
  583. {
  584. var lines = _presenter.FormattedText.GetLines();
  585. var pos = 0;
  586. foreach (var line in lines)
  587. {
  588. if (pos + line.Length > caretIndex || pos + line.Length == text.Length)
  589. {
  590. break;
  591. }
  592. pos += line.Length;
  593. }
  594. caretIndex = pos;
  595. }
  596. CaretIndex = caretIndex;
  597. }
  598. private void MoveEnd(InputModifiers modifiers)
  599. {
  600. var text = Text ?? string.Empty;
  601. var caretIndex = CaretIndex;
  602. if ((modifiers & InputModifiers.Control) != 0)
  603. {
  604. caretIndex = text.Length;
  605. }
  606. else
  607. {
  608. var lines = _presenter.FormattedText.GetLines();
  609. var pos = 0;
  610. foreach (var line in lines)
  611. {
  612. pos += line.Length;
  613. if (pos > caretIndex)
  614. {
  615. if (pos < text.Length)
  616. {
  617. --pos;
  618. if (pos > 0 && text[pos - 1] == '\r' && text[pos] == '\n')
  619. {
  620. --pos;
  621. }
  622. }
  623. break;
  624. }
  625. }
  626. caretIndex = pos;
  627. }
  628. CaretIndex = caretIndex;
  629. }
  630. private void SelectAll()
  631. {
  632. SelectionStart = 0;
  633. SelectionEnd = Text?.Length ?? 0;
  634. }
  635. private bool DeleteSelection()
  636. {
  637. if (!IsReadOnly)
  638. {
  639. var selectionStart = SelectionStart;
  640. var selectionEnd = SelectionEnd;
  641. if (selectionStart != selectionEnd)
  642. {
  643. var start = Math.Min(selectionStart, selectionEnd);
  644. var end = Math.Max(selectionStart, selectionEnd);
  645. var text = Text;
  646. SetTextInternal(text.Substring(0, start) + text.Substring(end));
  647. SelectionStart = SelectionEnd = CaretIndex = start;
  648. return true;
  649. }
  650. else
  651. {
  652. return false;
  653. }
  654. }
  655. else
  656. {
  657. return true;
  658. }
  659. }
  660. private string GetSelection()
  661. {
  662. var text = Text;
  663. if (string.IsNullOrEmpty(text))
  664. return "";
  665. var selectionStart = SelectionStart;
  666. var selectionEnd = SelectionEnd;
  667. var start = Math.Min(selectionStart, selectionEnd);
  668. var end = Math.Max(selectionStart, selectionEnd);
  669. if (start == end || (Text?.Length ?? 0) < end)
  670. {
  671. return "";
  672. }
  673. return text.Substring(start, end - start);
  674. }
  675. private int GetLine(int caretIndex, IList<FormattedTextLine> lines)
  676. {
  677. int pos = 0;
  678. int i;
  679. for (i = 0; i < lines.Count; ++i)
  680. {
  681. var line = lines[i];
  682. pos += line.Length;
  683. if (pos > caretIndex)
  684. {
  685. break;
  686. }
  687. }
  688. return i;
  689. }
  690. private void SetTextInternal(string value)
  691. {
  692. try
  693. {
  694. _ignoreTextChanges = true;
  695. SetAndRaise(TextProperty, ref _text, value);
  696. }
  697. finally
  698. {
  699. _ignoreTextChanges = false;
  700. }
  701. }
  702. private void SetSelectionForControlBackspace(InputModifiers modifiers)
  703. {
  704. SelectionStart = CaretIndex;
  705. MoveHorizontal(-1, modifiers);
  706. SelectionEnd = CaretIndex;
  707. }
  708. private void SetSelectionForControlDelete(InputModifiers modifiers)
  709. {
  710. SelectionStart = CaretIndex;
  711. MoveHorizontal(1, modifiers);
  712. SelectionEnd = CaretIndex;
  713. }
  714. UndoRedoState UndoRedoHelper<UndoRedoState>.IUndoRedoHost.UndoRedoState
  715. {
  716. get { return new UndoRedoState(Text, CaretIndex); }
  717. set
  718. {
  719. Text = value.Text;
  720. SelectionStart = SelectionEnd = CaretIndex = value.CaretPosition;
  721. }
  722. }
  723. }
  724. }