/// <summary>Создаёт новый экземпляр TextBoxBindingState, записывает его в /// присоединённое к <see cref="TextBox"/> свойство BindingState /// и возвращает его.</summary> /// <param name="textBox"><see cref="TextBox"/> чьё свойство инициализируется.</param> /// <param name="isNumericOnly">Режим ввода только чисел.</param> /// <returns>Созданный в методе <see cref="TextBoxBindingState"/>.</returns> private static TextBoxBindingState InitBindingState(TextBox textBox, bool isNumericOnly) { TextBoxBindingState state = new TextBoxBindingState(null, textBox.Text, isNumericOnly); textBox.SetValue(BindingStatePropertyKey, state); return(state); }
/// <summary>Обновляет значение свойств присоединённого свойства BindingState.</summary> /// <param name="textBox"><see cref="TextBox"/> свойство которого обновляется.</param> /// <param name="changes">Коллекция объектов, содержащий сведения о произошедших изменениях.</param> /// <remarks>Свойство <see cref="TextBoxBindingState.NewText"/> обновляется значением <paramref name="textBox"/>.Text,<br/> /// в свойство <see cref="TextBoxBindingState.Changes"/> записывается значение <paramref name="changes"/>.</remarks> private static void UpdateTextBindingState(TextBox textBox, ICollection <TextChange> changes) { TextBoxBindingState state = GetBindingState(textBox); //if (state.NewText != textBox.Text) state.UpdateText(textBox.Text); state.Changes = changes; }
public object Convert(object[] values, Type targetType, object parameter, CultureInfo culture) { object ret = null; object source = values[0]; if (source == DependencyProperty.UnsetValue) { return(source); } TextBox textBox = (TextBox)values[1]; string newText = textBox.Text; // Получение состояния привязки TextBox TextBoxBindingState bindingState = GetBindingState(textBox); string oldText = bindingState.OldText; // Получение парсера для типа в первой привязке (привязка к Источнику). TryParseNumberHandler tryParse = NumericTryParse.GetTryParse(source.GetType()); // Если парсер получить не удалось, значит первая привязка получила значение недопустимого типа. if (tryParse == null) { throw InvalidNumberType; } Debug.Write(GetType().Name + $".Convert.values: {source}, \"{oldText}\", \"{newText}\""); // Получение цифрового стиля из параметра. Если он не задан, то используется стиль по умолчанию для этого типа. NumberStyles style = NumericTryParse.GetNumberStyle(parameter, values[0].GetType()); // Проверка причины вызова конвертера. switch (bindingState.UpdateSourceState) { // Метод UpdateSource() не вызывался, значит значение обновляется по привязке от Источника. case UpdateSourceStateEnum.NotCalled: // Получение из TextBox числа в том же типе, что получено от Источника (в первой привязке). // И сравнение этого числа с числом Источника. if (tryParse(newText, style, culture, out object target) && target.Equals(source)) { // Отменяется присвоение значения привязкой. ret = Binding.DoNothing; } // Иначе возвращается число Источника в текстовом виде в заданной культуре. else { ret = System.Convert.ToString(source, culture); } break; // Обновление источника произошло в ходе выполнения метода UpdateSource(). // Значит в TextBox.Text корректное значение и его надо проверить только на крайние пробелы // для режима ввода "Tолько Числа". case UpdateSourceStateEnum.Called: if (bindingState.IsNumericOnly) { if (newText.Length < 1 || char.IsWhiteSpace(newText[0]) || char.IsWhiteSpace(newText[newText.Length - 1])) { // Устанавливается флаг обновления по привязке bindingState.TextChangeSource = PropertyChangeSourceEnum.Binding; // Возращается TextBox старое значение. UndoText(textBox, oldText, bindingState.Changes); } } // Отменяется присваивание. ret = Binding.DoNothing; break; // После вызова UpdateSource() обновление источника не произошло // Значит в TextBox.Text некорректное значение и надо вернуть предыдущее его значение. case UpdateSourceStateEnum.CallCanceled: // Устанавливается флаг обновления из конвертера привязки bindingState.TextChangeSource = PropertyChangeSourceEnum.Binding; // Проверяется на пустую строку. // Если она пустая, то надо её заменить на "0". // Это автоматически сделает метод UndoText(). if (string.IsNullOrWhiteSpace(newText)) { ZeroText(textBox); } else if (!BeginScientific(newText)) { // Возращается TextBox старое значение. UndoText(textBox, oldText, bindingState.Changes); } // Сброс флага обновления из конвертера привязки bindingState.TextChangeSource = PropertyChangeSourceEnum.NotBinding; // Проверка строки в TextBox. // Если она корректна, то для сброса валидации надо её же и вернуть if (tryParse(textBox.Text, style, culture, out _)) { ret = textBox.Text; } else { // Иначе - отмена присвоения по привязке ret = Binding.DoNothing; } break; default: break; } Debug.WriteLine($"; return: {ret ?? "null"}"); if (ret is string str && str != textBox.Text) { // Устанавливается флаг обновления по привязке bindingState.TextChangeSource = PropertyChangeSourceEnum.Binding; } return(ret);; // Проверка строки на начало научной записи числа bool BeginScientific(string text) { if (string.IsNullOrWhiteSpace(text)) { return(false); } if (style.HasFlag(NumberStyles.AllowExponent)) { if (text[text.Length - 1] == 'e' || text[text.Length - 1] == 'E') { text = text.Remove(text.Length - 1, 1); } else if (text.Length > 1 && (text[text.Length - 1] == '-' || text[text.Length - 1] == '+') && (text[text.Length - 2] == 'e' || text[text.Length - 2] == 'E')) { text = text.Remove(text.Length - 2, 2); } } return(tryParse(text, style, culture, out _)); } }
/// <summary>Метод-прослушка изменения текста в <see cref="TextBox"/> в котором была создана привязка <see cref="BindToNumericExtension"/>.</summary> /// <param name="sender"><see cref="TextBox"/> в котором изменился текст.</param> /// <param name="e">Параметры события. Не используются.</param> private void TextBoxTextChanged(object sender, TextChangedEventArgs e) { // Извлечение TetxBox и привязки его свойства Text TextBox textBox = (TextBox)sender; MultiBindingExpression multiBindingExpression = BindingOperations.GetMultiBindingExpression(textBox, TextBox.TextProperty); // Если привязка - это не PrivateMulti, то отключается прослушка и выход из метода. if (!(multiBindingExpression?.ParentMultiBinding is PrivateMulti bindPriv)) { textBox.TextChanged -= TextBoxTextChanged; // Очистка приисоединённого свойства. ClearTextBindingState(textBox); return; } // Получение состояния привязки TextBoxBindingState bindingState = GetBindingState(textBox); // Сохранить новый текст и параметры изменения UpdateTextBindingState(textBox, e.Changes); // Если изменение произошло по привязке - выйти из метода if (bindingState.TextChangeSource == PropertyChangeSourceEnum.Binding) { // Сброс состояния обновления по привязке. bindingState.TextChangeSource = PropertyChangeSourceEnum.NotBinding; return; } // Создание триггера обновления источника и обновления источника в нём, // запоминание состояния триггера bool triggerHasUpdated; using (var trigger = new SourceUpdateTrigger(textBox, TextBox.TextProperty)) { // Обновление источника с флагом обновления из метода UpdateSource(). bindingState.UpdateSourceState = UpdateSourceStateEnum.Called; multiBindingExpression.UpdateSource(); // Запоминание состояния триггера. triggerHasUpdated = trigger.HasUpdated; } // Получение привязки к источнику. BindingExpressionBase bindingSource = multiBindingExpression.BindingExpressions[0]; // Проверка триггера обновления источника if (triggerHasUpdated) { // Вызов обновления привязки к источнику для передачи нового значения. //bindingSource.UpdateSource(); } // Если обновления не было - значит была ошибка конвертера // Для режима ввода "Только числа" надо вызвать ковертер. else if (!triggerHasUpdated && bindPriv.IsNumericOnly) { // Установка флага отменённого обновления. bindingState.UpdateSourceState = UpdateSourceStateEnum.CallCanceled; // Вызов обновления привязки от источника для обработки причины прерывания обновления. bindingSource.UpdateTarget(); } // Сброс флага обновления из метода UpdateSource(). bindingState.UpdateSourceState = UpdateSourceStateEnum.NotCalled; }