Отладка: почему раздел с привязкой к данным обрывается при повторном применении DataContext? - PullRequest
3 голосов
/ 02 декабря 2010

Обновление 2010 06 12
Извлечение сущности в меньший образец - решение доступно здесь http://db.tt/v6r45a4

Теперь, похоже, проблема, связанная с элементом управления вкладкой:Когда контекст данных повторно применяется (нажмите кнопку), кажется, что все работает, как ожидалось.Привязки обновляются, но затем все привязки на активной вкладке нарушаются.

Вы можете убедиться в этом, выбрав любую вкладку и нажав кнопку, и вы увидите, что элементы управления на этой вкладке идут капутом.Добавил кучу операторов регистрации, как мой друг пришел к этому краткому описанию, и его исправление [Set DataContext = "{Binding}" также в элементе управления вкладками]

Но мы все еще не уверены, почему это происходиткак это происходит ...

TabControl Data Context now set to ReproTabItemBug.MainViewModel
TabPage [LeftTabPage] Data Context now set to ReproTabItemBug.LeftViewModel
System.Windows.Data Error: 40 : BindingExpression path error: 'MiddleProp' property not found on 'object' ''MainViewModel' (HashCode=50608417)'. BindingExpression:Path=MiddleProp; DataItem='MainViewModel' (HashCode=50608417); target element is 'TextBox' (Name='MiddleTabTextbox'); target property is 'Text' (type 'String')
TabPage [MiddleTabPage] Data Context now set to ReproTabItemBug.MiddleViewModel
Middle tab textbox text changed to 634272638824920423
TabPage [RightTabPage] Data Context now set to ReproTabItemBug.RightViewModel
Middle tab textbox text changed to 

Предыдущий пост (Отказ от ответственности: Длинный пост впереди ... получите свой попкорн. Я потратил большую часть дняна этом ..)

Моя ViewModel состоит из трех POCO subViewModel.Каждый subVieModel имеет свойства, которые связаны в 3 разделах, упомянутых выше.Каждый subViewModel предоставляется как стандартное свойство .net get-only (без INotifyPropertyChanged)

В My View есть 3 раздела.В каждом разделе есть установщик DataContext примерно так ..

...
<TabItem  x:Name="_tabPageForVM2"  DataContext="{Binding PropReturningSubVM2}">
  <!-- followed by UI Items that are data-bound to props inside this sub-viewmodel -->
</TabItem>
...

Итак, подведем итог

<MainView> <!--DataContext set programmmatically to an Instance Of MainViewModel) -->
   <Control DataContext="{Binding PropReturningSubVM1}" >.. Section1 .. </Control>
   <Control DataContext="{Binding PropReturningSubVM2}" >.. Section2 .. </Control>
   <Control DataContext="{Binding PropReturningSubVM3}" >.. Section3 .. </Control>
</MainView>

Теперь вот загадочный бит.При обычном запуске я создаю экземпляр MainViewModel (в котором модели дочерних представлений передаются в его ctor).Свойства в окне просмотра подтверждают это.

    Trace.WriteLine("Before setting datacontext");
    mainView.DataContext = null;
    mainView.DataContext = mainViewModel;
    //mainView.Refresh();
    Trace.WriteLine("After setting datacontext");

Все работает отлично.Теперь по независящим от меня причинам существует сценарий, когда пользовательский интерфейс отклоняется, но представление все еще находится в памяти.Таким образом, чтобы очистить его, когда он будет показан в следующий раз, я создаю новые экземпляры моей модели представления и повторно применяю текст данных (вызывая ту же процедуру инициализации, что и раньше). Однако, когда выполняется набор DataContext=mainViewModel, я вижу связку связыванияошибки в окне вывода.Что интересно, так это то, что только привязки внутри одной вкладки (модель подвида) нарушены.Другие 2 модели подвидов работают правильно - без ошибок привязки.

System.Windows.Data Error: 40 : BindingExpression path error: 'RphGaugeMaxScale' property not found on 'object' ''MainViewModel' (HashCode=38546056)'. BindingExpression:Path=RphGaugeMaxScale; DataItem='MainViewModel' (HashCode=38546056); target element is 'Gauge' (Name='GaugeControl'); target property is 'MaxValue' (type 'Double')
System.Windows.Data Error: 40 : BindingExpression path error: 'RphGaugeMaxScale' property not found on 'object' ''MainViewModel' (HashCode=38546056)'. BindingExpression:Path=RphGaugeMaxScale; DataItem='MainViewModel' (HashCode=38546056); target element is 'Gauge' (Name='GaugeControl'); target property is 'MajorTickCount' (type 'Int32')
...

Свойства существуют в SubViewModel2, но вместо этого поиск использует MainViewModel.

Далее я добавил метод обновления вMainView (вызывается после повторного применения DataContext), который делает это

    _tabPageForVM2.DataContext = null;
    _tabPageForVM2.DataContext = mainVM.PropReturningSubVM2;

, и это решает проблему.

Что меня удивляет, так это

  • , что особенного в этомsubVM2?Если он работает при первом назначении DataContext, почему он прерывается во второй раз?
  • Я установил заголовок tabPage равным {Binding}, чтобы проверить, с чем он связан, и показывает «FullTypeNameOfSubVM2»,который указывает, что свойство DataContext вкладки устанавливается.Так почему же сломаны привязки?

Ответы [ 3 ]

2 голосов
/ 08 декабря 2010

Вы можете загрузить Snoop или другой инструмент, который показывает вам визуальное дерево и видит там, что содержимое TabItem на самом деле не является визуальным дочерним элементом этого TabItem, но является визуальным дочерним элементом TabControl.

alt text

Таким образом, TabItem является логическим дочерним элементом содержимого TabItem, а TabControl является визуальным дочерним элементом содержимого TabItem.DataContext должен быть унаследован от логического родителя, но мне кажется, что он унаследован случайным образом от TabItem или TabControl.

Лучшее решение, которое я могу предложить, - переместить привязки к содержимому следующим образом:

<TabItem x:Name="LeftTabPage" Header="LeftModel">
    <StackPanel Orientation="Horizontal" DataContext="{Binding Left}">
        <my:Gauge x:Name="gauge" Height="200" Width="200" Value="{Binding LeftProp}"/>
        <Viewbox Height="200" Width="200" >
            <my:Gauge x:Name="scaledGauge" Value="{Binding LeftProp}"/>
        </Viewbox>
    </StackPanel>
</TabItem>

<TabItem x:Name="MiddleTabPage" Header="{Binding}">
    <TextBox x:Name="MiddleTabTextbox" DataContext="{Binding Middle}" Text="{Binding MiddleProp}" />
</TabItem>

<TabItem x:Name="RightTabPage" Header="RightModel">
    <TextBox DataContext="{Binding Right}" Text="{Binding RightProp}"/>
</TabItem>

Я думаю, я понял.Я попытаюсь объяснить.

Если вторая вкладка активна (например), MiddleTabTextbox является логическим дочерним элементом MiddleTabPage и является визуальным дочерним элементом MyTabControl (у него 2 разных родителя с 2 разными DataContexts).Первая вкладка не активна и имеет только логический дочерний элемент - StackPanel.При нажатии на кнопку DataContext изменяется.И это нормально с StackPanel - у него есть один родитель.Но что должен делать MiddleTabTextbox?Должен ли он взять DataContext от визуального или логического родителя?Кажется логичным получить контекст от логического родителя :) Я исключил это поведение, но MiddleTabTextbox получает его от визуального потомка, т.е. MyTabControl.Таким образом, вместо получения MiddleViewModel вы получаете MainViewModel с кучей ошибок привязки.Я не знаю, почему WPF наследует DataContext от визуального родителя, а не логического.

0 голосов
/ 08 декабря 2010

Насколько мне известно, все элементы управления автоматически устанавливают DataContext для своих элементов. Обязательство в вашем локальном текстовом тексте должно заботиться об этом поведении. Как я вижу, происходит, когда привязки переоценивают локальный DataContext, а привязки разрешают DataContext на уровень выше.
Я хотел бы попробовать установить updatebehaviours на привязку, установив BindingMode. Если это не сработает, я постараюсь обновить информацию в нужное время. Один из способов сделать это - использовать мультисвязывание: 1. привязка в мультисвязи - это ваша привязка, как сейчас 2. привязка - это привязка к родительскому тексту данных

многозначный преобразователь просто передает первое значение в обоих направлениях - таким образом, вы получаете привязку для обновления с любым изменением, которое вы хотите - 2. привязка предназначена только для дополнительного обновления, когда родительский текст данных (или другое значение изменяется).

0 голосов
/ 02 декабря 2010

Это:

DataContext="{Binding PropReturningSubVM2}"

выглядит подозрительно.Вы используете выражение Binding, а не указываете Source или RelativeSource, что означает, что вы хотите, чтобы он искал свойство с именем PropReturningSubVM2 для любого объекта, на который ваш DataContext ссылается , и связывал результат этого выраженияназад в ваш DataContext.

Итак, у вас здесь происходит нечто вроде циклической ссылки;Вы декларативно говорите, что ваш DataContext - это еще одна вещь в вашем DataContext.В процедурном коде это было бы хорошо, так как вы полностью контролируете, когда это происходит, вы делаете это и продолжаете.Но XAML не процедурный, он декларативный;поэтому вы заявляете: «Это отношения, которые я хочу установить и обеспечить», даже если отношения, которые вы объявляете, противоречат сами себе.Если это вообще работает, то только потому, что фреймворк делает вещи в определенном порядке, что делает его работающим (сначала свойство наследуется от родительского элемента управления, затем применяются привязки, и изменение свойства, вызванное привязкой, не 't запуск привязки для повторного запуска).По-видимому, иногда вещи не происходят в этом конкретном порядке, поэтому ваша проблема.

Я бы предложил удалить циклическую ссылку.Измените привязку на что-то вроде этого:

DataContext="{Binding PropReturningSubVM2,
 RelativeSource={RelativeSource FindAncestor, AncestorType={x:Type TabControl}}}"

Это удаляет странную вещь с круговыми ссылками, потому что вы больше не привязываете свой DataContext на основе вашего собственного DataContext, выТеперь явно связать его на основе DataContext вашего родительского TabControl - так что больше нет цикличности, больше не упорядочение зависимости, и, надеюсь, больше нет ошибки.

(При необходимости замените другой тип вместо TabControl - в идеале вы бывозможно, вы захотите использовать тип родительского элемента управления, для которого вы программно устанавливаете DataContext для начала.)

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...