1
0

TextBox.cs 27 KB

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