0

Есть окно, у которого в DataContext записана ViewModel. Внутри окна есть UserControl в ресурсах которого описаны несколько шаблонов отображения данных - DataTemplate. В каждом шаблоне есть TexBox-ы текст которых я привязываю к некоторому свойству во ViewModel.

   <TextBox Text="{Binding DataContext.PropertyName, RelativeSource={RelativeSource Mode=FindAncestor, AncestorType=Window}}"/>

Все вроде бы хорошо, но мне нужно добавить валидацию вводимых данных. Делаю так:

 <TextBox Margin="15,0,15,0" Style="{DynamicResource TextStyle}">
                        <TextBox.Text>
                            <Binding
                                Path="PropertyName"
                                RelativeSource="{RelativeSource Mode=FindAncestor,
                                                                AncestorType=Window}">
                                <Binding.ValidationRules>
                                    <DataErrorValidationRule />
                                </Binding.ValidationRules>
                            </Binding>
                        </TextBox.Text>
                    </TextBox> 

И дизайнер студии выдает ошибку "Не удалось найти свойство "PropertyName" в типе "String"". В общем проблема такова: я не могу "привязаться" к свойству ViewModel при использовании валидации данных. Прошу подсказать как это можно реализовать. Спасибо!

alex6327
  • 118
  • А почему у вас UserControl вообще знает что-либо про какие-то там данные? Вот взять ванильный TextBox, он зависит от ViewModel или любого другого источника данных? Нет. Он имеет dependency property, через которые уже снаружи вы пишете <TextBox Text = "{Binding ...}">. Так почему вы тогда гвоздями приколачиваете данные к контрлам? – EvgeniyZ Mar 06 '22 at 09:25
  • Не очень понял вашего вопроса. Я не использую dependency property. Я реализую интерфейс INotifyPropertyChanged. Текст внутри TextBox через механизм привязки заполняет нужные поля в объекте, который создан во ViewModel. – alex6327 Mar 06 '22 at 10:22
  • А что не понятного в А почему у вас UserControl вообще знает что-либо про какие-то там данные? Вроде вполне конкретный вопрос, даже пример с TextBox привел. Ну ок, давайте по другому пойдем... Вот вы пишете Внутри окна есть UserControl в ресурсах которого описаны несколько шаблонов отображения данных, то есть, у вас есть собственный контол, со своим видом, дальше вы пишете где-то так <uc:MyUserControl/>, а должны писать так <uc:MyUserControl Text = "{Binding Text}"/>. Разницу заметили? В первом варианте у вас все привязки заданы внутри стиля, то есть контрол заточен под одну задачу. – EvgeniyZ Mar 06 '22 at 10:29
  • Суть контролов в универсальности. Вот захотели вы вывести допустим имя сотрудника, или захотели вывести в другом месте, с тем же видом его фамилию, а в другом месте, его должность. По вашей текущей реализации, это все будет в виде отдельных стилей, где будет "гвоздями" прибита конкретная привязка, когда эта привязка должна идти в том месте, где вы используете контрол (как и показывал выше <uc:MyUserControl Text = "{Binding Name}"/>). Также и валидация, не везде она нужна, что, будете пилить к каждому свойству, валидации свой отдельный стиль? – EvgeniyZ Mar 06 '22 at 10:34
  • Хорошо, я учту на будущее ваш совет о сути контролов в универсальности. Но в своей программе я их реализовал как альтернативу Page внутри Frame. Т.е. по нажатию кнопки выполняется команда смены UC внутри Window. Поэтому один UC и содержит шаблоны отображения данных, чтобы их отобразить внутри ContentControl. Я понял, что вашей логике сделано изначально не правильно. – alex6327 Mar 06 '22 at 10:45
  • Если у вас это страницы, то 1. "разные шаблоны" для одного контрола весьма странная затея. 2. RelativeSource="{RelativeSource Mode=FindAncestor, AncestorType=Window}" (поиск родителя) - у вас этого не должно быть, ибо каждая страница должна иметь свою VM. – EvgeniyZ Mar 06 '22 at 10:49
  • Я вот читал мнение наоборот, что у UC не должно быть своей VM. У меня 1 экземпляр класса VM на одно окно. А уже UC внутри этого окна - обращаются к этому экземпляру через RelativeSource. – alex6327 Mar 06 '22 at 10:59
  • Я правильно понимаю, что UC у вас идут в виде страниц, внутри окна? Которые вы неким образом переключаете? Если да, то как же вы их переключаете, не имея для них свою VM? Да и вообще, подумайте логически, должно ли главное окно содержать в себе данные страниц? Или это не его ответственность и всеж должна быть своя VM под каждую страницу, каждое окно? Вот вам базовый пример страниц, вот нечто такое у вас должно быть. Даже если установите популярные фреймворки по типу Prism, то также увидите, что под каждый UC идет своя VM. – EvgeniyZ Mar 06 '22 at 11:05
  • А в чем проблема использовать команды по нажатию кнопки для переключения UC ? Для этого не нужно создавать отдельную VM. Вот у меня есть документ, например договор. Вот один договор это одно окно и одна VM. В договоре разделы: Общие положения(UC_1), Ответственность сторон(UC_2), Заказчик(UC_3). Заказчик мб физ. лицом или юр. лицом - тут и применяю шаблоны для их отображения. – alex6327 Mar 06 '22 at 11:24
  • А в чем проблема использовать команды по нажатию кнопки для переключения UC - как? Пример можете предоставить? – EvgeniyZ Mar 06 '22 at 11:26
  • @EvgeniyZ Вот код xaml
                Grid.Row="1"
                Grid.Column="1"         
                Content="{Binding CurrentUC}" />``` Вот код команды: ```CurrentUC = new UC_3(); ``` CurrentUC - это свойство в VM.
    
    – alex6327 Mar 06 '22 at 11:32
  • Ясно. Таким кодом вы нарушаете MVVM, ибо по этому подходу, все должно быть разделено на слои, которые не связаны друг с другом, и которые отвечают за свою конкретную задачу. Model - слой с данными (работа с файлом, сайтом, базой, устройством и др.). Этот слой не должен знать про другие слои, должен быть максимально независим. Слой View - UI (все цвета, контролы, анимации, шрифты и др.), также, должен быть максимально независим. Слой ViewModel - связующий слой, который берет Model и делает свойства, к которым идет привязка V слоя, также обрабатывает команды. – EvgeniyZ Mar 06 '22 at 11:40
  • UC_3 - это контрол, то есть View слой, а значит, ViewModel вообще не должен про него что-либо знать. Это равноценно new MainWindow().Show(); - тоже нарушение MVVM, ибо напрямую инициализируем и вызываем окно в VM слое. Ваш проект, при использовании MVVM подхода, должен базироваться на абстракциях, интерфейсах, все с минимальной связью. Другими словами, должно быть так, чтоб если вы удалите UC_3 (View слой), ваш проект запускался бы и работал, но без интерфейса. То есть логика других слоев не должна страдать из-за этого. – EvgeniyZ Mar 06 '22 at 11:44
  • Тут соглашусь. Да, я пока что только учусь. Благодарю за разъяснения! – alex6327 Mar 06 '22 at 11:47
  • Ссылку выше я вам дал, советую сделать все по ней, где под каждую страницу будет свой VM, которые вы и будете задавать (не = new UC_3, а = new SettingPageVM();), тем самым вы отделите View от ViewModel слоя. А так, я вам советую стремиться к подобному, там можете заметить, что все вовсе разбито по отдельным проектам, с минимальной связью. – EvgeniyZ Mar 06 '22 at 11:50

1 Answers1

0

В общем сам разобрался. Вместо вот этого:

<TextBox.Text>
<Binding Path="PropertyName" RelativeSource="{RelativeSource Mode=FindAncestor, AncestorType=Window}">
<Binding.ValidationRules>
   <DataErrorValidationRule />
</Binding.ValidationRules>
</Binding>
</TextBox.Text>

Я просто в TextBox добавил атрибут ValidatesOnDataErrors=True и все заработало.

Text="{Binding DataContext.PropertyName, RelativeSource={RelativeSource Mode=FindAncestor, AncestorType=Window}, ValidatesOnDataErrors=True, UpdateSourceTrigger=PropertyChanged}">
0xdb
  • 51,614
alex6327
  • 118