Я пытаюсь создать визуальную обратную связь о результате проверки элемента управления вводом TextBox
со следующими требованиями:
Соответствующий код проверки, похоже, работает: функция, выполняющая проверку, запущена, сообщения об ошибках накапливаются, вызывается OnErrorsChanged
, а WPF делает... что-то... вокруг всего моего UserControl
появляется красная рамка.
Прочитав множество связанных с SO вопросов/ответов и приняв во внимание тот факт, что рисуется красный прямоугольник, я изначально почувствовал, что это связано с размещением декоративного элемента. Однако я пробовал ставить AdornerDecorator
на каждом слое, но безрезультатно. Я также проверил во время выполнения, есть ли у элементов управления текстовым полем доступный для них слой оформления, и они есть. Он создается на основе ContentControl
хостинга моего представления (View1.xaml
). Кажется, это объясняет, почему я вижу красную рамку вокруг всего ContentControl
, но меня смущает, почему TextBox
Validation.ErrorTemplate
не контролирует размещение украшения.
View1.xaml
UserControl
Grid
GroupBox
StackPanel
(вертикальная планировка)
StackPanel
(горизонтальное расположение — повторяется для всех элементов управления текстовым полем)
Label
Textbox
(подтвержденный элемент управления — ErrorTemplate
здесь)<UserControl x:Class = "View1"
....namespaces....
xmlns:Fluent = "urn:fluent-ribbon"
xmlns:vm = "clr-namespace:MyNamespace.ViewModel"
xmlns:view = "clr-namespace:MyNamespace.View"
Name = "MyView1"
DataContext = "{StaticResource g_MainWindowViewModel}"
Validation.ErrorTemplate = "{x:Null}">
<DockPanel LastChildFill = "True">
<ContentControl Name = "NewSessionFormContentControl"
Content = "{Binding m_SessionFormViewModel}"
DockPanel.Dock = "Left"/>
</DockPanel>
С ControlTemplate
, взятым из ответа BionicCode здесь:
<ControlTemplate x:Key = "ValidationErrorTemplate">
<DockPanel LastChildFill = "True">
<Border BorderBrush = "Red" BorderThickness = "1">
<AdornedElementPlaceholder x:Name = "AdornedElement"/>
</Border>
<Border Background = "White"
BorderBrush = "Red"
Padding = "4"
BorderThickness = "1,0,1,1"
HorizontalAlignment = "Left">
<ItemsControl ItemsSource = "{Binding}"
HorizontalAlignment = "Left"
DockPanel.Dock = "Right">
<ItemsControl.ItemTemplate>
<DataTemplate>
<TextBlock Text = "{Binding ErrorContent}"
Foreground = "Red"
DockPanel.Dock = "Right"/>
</DataTemplate>
</ItemsControl.ItemTemplate>
</ItemsControl>
</Border>
</DockPanel>
</ControlTemplate>
Вот пример элемента управления вводом, который необходимо проверить в этом представлении:
<TextBox Name = "Name1"
Width = "350"
Validation.ErrorTemplate = "{DynamicResource ValidationErrorTemplate}"
Text = "{Binding Name, Mode=OneWayToSource, ValidatesOnNotifyDataErrors=True,UpdateSourceTrigger=Explicit}"/>
Вам придется установить свойство Validation.ValidationAdornerSite
, чтобы заставить механизм привязки украшать другой элемент. Например, UserControl
состоит из нескольких элементов управления, каждый из которых привязан к источнику данных. И если одна из этих привязок не проходит проверку, вы хотите, чтобы UserControl
отображался как недействительный и предоставлял визуальную обратную связь об ошибке. Затем автор элемента управления установил Validation.ValidationAdornerSiteFor
для дочернего элемента, чтобы он ссылался на родителя UserControl
. Механизм привязки теперь выберет значение Validation.ValidationAdornerSite
в качестве украшаемого элемента.
Короче говоря. По умолчанию TextBox
будет украшен Validation.ErrorTemplate
. Весь код, связанный с декоративными элементами, не требуется и должен быть удален, поскольку декораторы полностью управляются механизмом привязки.
Проблема, которую вы описываете, обычно является результатом привязки дочернего элемента управления к свойству родительского элемента UserControl
, в то время как это свойство UserControl
привязывается к фактическому источнику данных (это было бы правильной конструкцией элемента управления). Если это не так, например, потому что ваш дочерний элемент контролирует все привязки непосредственно к DataContext, было бы неплохо, если бы вы могли опубликовать контекст XAML TextBox
и то, как UserControl
используется/настраивается. Немного больше контекста для проверки кода. Таким образом, мы можем узнать, что переопределяет поведение по умолчанию.
Код модели представления также не имеет значения в этом случае. Сама проверка работает правильно. Только отрисовка шаблона ошибки не такая как хотелось. Модель представления (или реализация INotifyDataErrorInfo в целом) не может иметь никакого влияния на сайт рендеринга. Проблема должна быть связана с целью привязки (UserControl и TextBox). Итак, если TextBox не является обязательным следующим образом: TextBox ==> UserControl ==> data source
, опубликуйте контекст, связанный с представлением. В противном случае сообщите мне, что ваш путь привязки соответствует шаблону, и я немедленно опубликую исправление.
Спасибо за подробные комментарии! Подлежащий проверке TextBox
привязан непосредственно к источнику данных (в моем исходном вопросе есть образец TextBox
, с моей точки зрения) через OneWayToSource
. Я обновлю вопрос, добавив больше кода из представления, а также некоторые подробности о потоке данных.
Спасибо за обновление вопроса. Исходная привязка отображала только {Binding Name}
, поэтому я не был уверен, является ли Name
свойством, определенным UserControl, которое называется так же, как свойство модели представления. Это не было бы редкостью. Итак, я спросил. Как правило, вы не используете внедрение зависимостей для назначения экземпляров модели представления представлениям. Обычно у вас будет, например. MainViewModel
, который предоставляет несколько классов дочерних моделей представлений. Вы, вероятно, вводите MainViewModel
в MainWindow
. Отсюда все ссылки на XAML.
Затем вы можете ссылаться на MainViewModel
, чтобы назначить более специализированный DataContext отдельным элементам управления или областям элементов управления, но всегда внешне. Например, в вашем случае назначение DataContext должно выглядеть следующим образом: Установить извне! <Window DataContext = "{StaticResource g_MainWindowViewModel}"> <View1 DataContext = "{Binding m_ViewModel}" /> </Window>
. Кроме того, вы можете использовать наследование DataContext.
И ваш элемент управления не будет явно ссылаться на текущий DataContext. Это не критично, если только вы не хотите, чтобы элементы управления можно было использовать повторно. Чтобы их можно было использовать повторно, они должны быть независимыми от DataContext или модели представления (внутренне). Элемент управления будет запрашивать все необходимые данные, предоставляя свойства зависимостей, которые клиент затем внешне привязывает к DataContext. Точный способ установки свойства ItemsSource ListBox. ListBox не привязывается напрямую к DataContext внутри. Вместо этого он привязывается к свойству ItemsSource.
Большое спасибо за предоставленную вами дополнительную информацию. К сожалению, этого недостаточно, чтобы воспроизвести поведение. Судя по тому, что вы опубликовали, поведение должно быть таким, как вы ожидаете: красная рамка вокруг TextBox
, а не View1
элемента управления. Не могли бы вы создать минимальный воспроизводимый пример? Возможно, вы могли бы начать с пустого проекта, а затем добавлять код, пока не сможете воспроизвести поведение. Никаких дополнительных услуг, только минимум. Спасибо!
Я также понял, что ваша привязка TextBox — OneWayToSource. Это не должно работать именно так, как вы хотите: OneWayToSource запускает проверку, как ожидалось, но не запускает представление для отображения визуальной обратной связи об ошибке. Это связано с тем, что информация, связанная с ошибкой, назначается цели привязки через прикрепленные свойства. Но целевой элемент будет обновляться только тогда, когда его обновит механизм привязки, что возможно только в привязках TwoWay, OneWay или OneTime.
OneWay и OneTime не обновляют источник (чтобы запустить проверку данных), поэтому они дисквалифицируются. Остался только TwoWay. Чтобы получить проверку привязки, привязка должна быть TwoWay или OneWayToSource. Чтобы получить обратную связь об ошибке, привязка должна быть двусторонней. Несмотря на это, вам каким-то образом удалось показать обратную связь об ошибках, хотя и не на том управлении. Вот почему я думаю, что проблема может быть связана с тем, как вы явно обновляете привязку. Однако наличие минимально воспроизводимого примера внесет ясность.
Я добавил MRE к исходному вопросу, хотя боюсь, что это раздуло эту страницу до невозможности чтения.
UpdateSourceTrigger=Explicit
на всех проверенных элементах управления просто вызывать проверку явным образом является ошибкой. В вашей модели представления должен быть метод, который будет проверять все свойства и уведомлять об ошибках.
@Liero Изначально я хотел обновить информацию об изменении поля (в средстве установки свойств для каждого элемента данных, привязанного к текстовому полю), но это вызвало проверку при создании экземпляра данных резервной модели представления, поэтому я не был уверен, как этого избежать.
Спасибо за предоставление более подробной информации. Я бы не стал считать это минимально воспроизводимым примером. Он содержит много шума. Например, все сторонние зависимости. Я почти уверен, что поведение сохранится, если вы удалите этот зависимый/избыточный код. Но это помогает. Я рассмотрю его и вернусь, чтобы опубликовать решение.
В вашем коде есть несколько недостатков, и он не учитывает некоторые детали поведения механизма проверки привязки. У меня также есть несколько предложений, которые должны улучшить ваш общий код.
TextBox
Как я уже упоминал в комментариях, вы должны установить Binding.Mode
на BindingMode.TwoWay
, если хотите, чтобы цель привязки показывала ошибки проверки.
Если вам нужна только простая проверка свойства, тогда
BindingMode.OneWayToSource
достаточно. Наборы механизмов привязки
прикрепленные свойства, такие как свойство Validation.Errors
на
целевой элемент. Это происходит после обновления цели
свойство. Цель обновляется только в том случае, если Binding.Mode
есть.
OneWay
, OneTime
или TwoWay
. Поскольку вы хотите вызвать
проверка данных (которая требует обновления источника привязки),
единственный возможный режим — TwoWay
.
SessionFromView.xaml
Установите Binding.Mode
привязки TextBox
на BindingMode.TwoWay
<TextBox Name = "SessionName"
Validation.ErrorTemplate = "{DynamicResource ValidationErrorTemplate}"
Text = "{Binding Name, Mode=TwoWay, ValidatesOnNotifyDataErrors=True}" />
Вы пропустили событие INotifyDataErrorInfo.ErrorsChanged
в случае, если параметр propertyName
равен null
или пуст.
Механизм привязки подписывается на событие INotifyDataErrorInfo.ErrorsChanged
для обновления привязки (на основе настроенного Binding.Mode
). Если вы вызываете ViewModelbase.ClearErrors
без указания имени свойства, как вы это делаете, то очистка ошибок не повлияет на пользовательский интерфейс, поскольку механизм привязки никогда не запускается для повторной оценки привязки, связанной с событием. В результате шаблон ошибки не удаляется после устранения ошибок.
ВьюМоделБасе.cs
protected bool ClearErrors(string propertyName = "")
{
if (string.IsNullOrEmpty(propertyName))
{
Errors.Clear();
// FIX::Raise ErrrorsChanged event
OnErrorsChanged(propertyName);
return true;
}
if (Errors.Remove(propertyName))
{
OnErrorsChanged(propertyName);
return true;
}
return false;
}
TextBox
У вас есть три возможных варианта исправить это:
Binding.ValidatesOnNotifyDataErrors
до false
(рекомендуется).Validation.ErrorTemplate
на null
(не рекомендуется). Хотя я бы не рекомендовал это решение (поскольку привязка по-прежнему будет занята обработкой ошибок проверки, что приводит к накладным расходам и снижению производительности), это решение более удобно, поскольку вы можете использовать глобальные неявные стили для установки Validation.ErrorTemplate
в {x:Null}
.INotifyDataErrorInfo.GetErrors
(не рекомендуется). Это исключит возможность получения всех ошибок источника привязки. WPF активно использует эту функцию (что в вашем случае приводит к красной рамке вокруг UserControl
(или родительского хостинга ContentControl
, если быть точным).Значение по умолчанию для Binding.ValidatesOnNotifyDataErrors
— true
. Кроме того, прикрепленное свойство Validation.ErrorTemplate
возвращает шаблон по умолчанию, который просто рисует красную рамку вокруг целевого элемента.
Если элемент управления привязывается к объекту источника данных, а не к свойству этого объекта, механизм привязки передает пустую строку вместо определенного имени свойства в метод INotifyDataErrorInfo.GetErrors
. Если этот метод реализован для возврата всех текущих ошибок экземпляра, как это делает ваш ViewModelBase
(и это рекомендуемая реализация),
тогда цель имеет ошибки для отображения:
<!--
The Content property binding targets the whole data source object
and not a property of the data source. As a result, the binding engine can't query the errors
for a particular property and instead tries to get all errors of the source object.
And because ValidatesOnNotifyDataErrors is TRUE by default, the binding on the Content property
will be validated.
The following two bindings are equivalaent
-->
<ContentControl Name = "NewSessionFormContentControl"
Content = "{Binding m_SessionFormViewModel}" />
<ContentControl Name = "NewSessionFormContentControl"
Content = "{Binding m_SessionFormViewModel, ValidatesOnNotifyDataErrors=True}" />
// ViewModelBase:
// This is were all errors of the source object are returned
// in case the binding engine passes in an empty string
public System.Collections.IEnumerable GetErrors(string propertyName)
=> string.IsNullOrWhiteSpace(propertyName)
? Errors.SelectMany(entry => entry.Value) // Return all errors
: Errors.TryGetValue(propertyName, out IList<object> errors)
? (IEnumerable<object>)errors
: new List<object>();
Сеансвиев.xaml
<!-- Disable validation for the particular binding and avoid related overhead -->
<ContentControl Name = "NewSessionFormContentControl"
Content = "{Binding m_SessionFormViewModel, ValidatesOnNotifyDataErrors=False}" />
Сеансвиев.xaml
<!--
Set Validation.ErrorTemplate to NULL.
The binding will still perform all the validation related operations
but won't show the visual error feedback .
-->
<ContentControl Name = "NewSessionFormContentControl"
Validation.ErrorTemplate = "{x:Null}"
Content = "{Binding m_SessionFormViewModel}" />
ВьюМоделБасе.xaml
// Don't support NULL and empty string queries
public System.Collections.IEnumerable GetErrors(string propertyName)
=> string.IsNullOrWhiteSpace(propertyName)
? new List<object>() // Return no errors
: Errors.TryGetValue(propertyName, out IList<object> errors)
? (IEnumerable<object>)errors
: new List<object>();
Это не связано с
AdornerDecorator
. Неважно, какой элемент содержитAdornerLayer
. Механизм привязки просто добавитAdorner
к первому родителюAdornerLayer
, который он найдет в текущем визуальном дереве. ЭтоAdorner
отображает значениеValidation.ErrorTemplate
. СимволAdorner
украшает фактический элемент, имеющий целевое свойство, для которого привязка сообщает об ошибке проверки (TextBox.Text). Отображение ошибок проверки основано на привязке. Каждая отдельная привязка отслеживает и управляет собственными ошибками проверки. Механизм привязки по умолчанию всегда украшает цель привязки.