Подтвердить что ты не робот

Привязка команды внутри элемента списка к свойству родителя viewmodel

Я работаю над этим около часа и смотрю на все связанные с ним вопросы.

Моя проблема очень проста:

У меня есть HomePageVieModel:

HomePageVieModel
+IList<NewsItem> AllNewsItems
+ICommand OpenNews

Моя разметка:

<Window DataContext="{Binding HomePageViewModel../>
<ListBox ItemsSource="{Binding Path=AllNewsItems}">
 <ListBox.ItemTemplate>
   <DataTemplate>
       <StackPanel>
        <TextBlock>
           <Hyperlink Command="{Binding Path=OpenNews}">
               <TextBlock Text="{Binding Path=NewsContent}" />
           </Hyperlink>
        </TextBlock>
      </StackPanel>
    </DataTemplate>
</ListBox.ItemTemplate>

В списке отображаются все элементы, но для жизни меня все, что я пытаюсь выполнить для команды, не будет работать:

<Hyperlink Command="{Binding Path=OpenNewsItem, RelativeSource={RelativeSource AncestorType=vm:HomePageViewModel, AncestorLevel=1}}">
<Hyperlink Command="{Binding Path=OpenNewsItem, RelativeSource={RelativeSource AncestorType=vm:HomePageViewModel,**Mode=FindAncestor}**}">
<Hyperlink Command="{Binding Path=OpenNewsItem, RelativeSource={RelativeSource AncestorType=vm:HomePageViewModel,**Mode=TemplatedParent}**}">

Я всегда получаю:

Ошибка System.Windows.Data: 4: Не удается найти источник для привязки со ссылкой.....

Обновление Я устанавливаю свой ViewModel следующим образом? Не думал, что это имеет значение:

 <Window.DataContext>
        <Binding Path="HomePage" Source="{StaticResource Locator}"/>
    </Window.DataContext>

Я использую класс ViewModelLocator из инструментария MVVMLight, который делает магию.

4b9b3361

Ответ 1

Здесь два вопроса против вас.

Для параметра DataContext для DataTemplate установлен элемент, отображаемый шаблоном. Это означает, что вы не можете просто использовать привязку без установки источника.

Другая проблема заключается в том, что шаблон означает, что элемент не является технически частью логического дерева, поэтому вы не можете искать предков за пределами DataTemplate node.

Чтобы решить эту проблему, вам нужно, чтобы привязка была недоступна за пределами логического дерева. Вы можете использовать DataContextSpy, указанный здесь.

<ListBox ItemsSource="{Binding Path=AllNewsItems}">
    <ListBox.Resources>
        <l:DataContextSpy x:Key="dataContextSpy" />
    </ListBox.Resources>

    <ListBox.ItemTemplate>
        <DataTemplate>
            <StackPanel>
                <TextBlock>
                   <Hyperlink Command="{Binding Source={StaticResource dataContextSpy}, Path=DataContext.OpenNews}" CommandParameter="{Binding}">
                       <TextBlock Text="{Binding Path=NewsContent}" />
                   </Hyperlink>
               </TextBlock>
           </StackPanel>
       </DataTemplate>
   </ListBox.ItemTemplate>
</ListBox>

Ответ 2

Немного другой пример, но, Я обнаружил, что, ссылаясь на родительский контейнер (используя ElementName) в привязке, вы можете получить к нему DataContext и его последующие свойства с помощью синтаксиса Path. Как показано ниже:

<ItemsControl x:Name="lstDevices" ItemsSource="{Binding DeviceMappings}">
 <ItemsControl.ItemTemplate>
  <DataTemplate>
   <Grid>
    <ComboBox Text="{Binding Device}" ItemsSource="{Binding ElementName=lstDevices, Path=DataContext.AvailableDevices}" />
    ...
   </Grid>
  </DataTemplate>
 </ItemsControl.ItemTemplate>
</ItemsControl>

Ответ 3

Похоже, вы пытаетесь предоставить соответствующий DataContext для HyperLink, чтобы вызвать ICommand. Я думаю, что простое связывание имени элемента может решить эту проблему.

<Window x:Name="window" DataContext="{Binding HomePageViewModel../>
 <ListBox ItemsSource="{Binding Path=AllNewsItems}">
 <ListBox.ItemTemplate>
  <DataTemplate>
   <StackPanel>
    <TextBlock>
       <Hyperlink DataContext="{Binding DataContext ,ElementName=window}" Command="{Binding Path=OpenNews}">
           <TextBlock Text="{Binding Path=NewsContent}" />
       </Hyperlink>
    </TextBlock>
  </StackPanel>
</DataTemplate>

AncestorType проверяет только типы Visual-Type, а не типы ViewModel.

Ответ 4

попробуйте что-то вроде этого

<Button Command="{Binding DataContext.YourCommand,RelativeSource={RelativeSource AncestorType={x:Type ListBox}}}"

он не может найти вашу привязку команды внутри списка, потому что вы устанавливаете diffatatontext, чем viewmodel для этого списка

Ответ 5

Хорошо, это немного поздно, я знаю. Но я только недавно столкнулся с той же проблемой. Из-за архитектурных причин я решил использовать статический локатор viewmodel вместо dataContextSpy.

<UserControl x:Class="MyView"
             xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
             xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
             xmlns:locator="clr-namespace: MyNamespace"
             DataContext="{Binding Source={x:Static locator:ViewModelLocator.MyViewModel}}" >
    <ListBox ItemsSource="{Binding Path=AllNewsItems}">        

        <ListBox.ItemTemplate>
            <DataTemplate>
                <StackPanel>
                    <TextBlock>
                        <Hyperlink Command="{Binding Source={x:Static locator:ViewModelLocator.MyViewModel}, 
                                                     Path=OpenNews}" 
                                   CommandParameter="{Binding}">
                            <TextBlock Text="{Binding Path=NewsContent}" />
                        </Hyperlink>
                    </TextBlock>
                </StackPanel>
            </DataTemplate>
        </ListBox.ItemTemplate>
    </ListBox>
</UserControl>

Статический локатор viewmodel создает экземпляр модели представления:

namespace MyNamespace
{
    public static class ViewModelLocator
    {
        private static MyViewModelType myViewModel = new MyViewModelType();
        public static MyViewModelType MyViewModel 
        {
            get
            {
                return myViewModel ;
            }
        }
    }
}

Использование этого обходного пути - это еще один способ привязки из шаблона данных к команде, находящейся в режиме просмотра.

Ответ 6

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

  • DataGrid с DataGridTemplateColumn
  • .NET 4
  • Windows XP

... и, возможно, в других обстоятельствах. Теоретически он должен терпеть неудачу во всех версиях Windows; но по моему опыту подход ElementName работает в DataGrid в Windows 7 вверх, но не в XP.

В следующем вымышленном примере мы пытаемся связать ICommand с именем ShowThingCommand в UserControl.DataContext(который является ViewModel):

<UserControl x:Name="ThisUserControl" DataContext="whatever...">
    <DataGrid ItemsSource="{Binding Path=ListOfThings}">
        <DataGrid.Columns>
            <DataGridTemplateColumn Header="Thing">
                <DataGridTemplateColumn.CellTemplate>
                    <DataTemplate>
                        <Button
                            Command="{Binding ElementName=ThisUserControl, Path=ShowThingCommand}"
                            CommandParameter="{Binding Path=ThingId}"
                            Content="{Binding Path=ThingId}" />
                    </DataTemplate>
                </DataGridTemplateColumn.CellTemplate>
            </DataGridTemplateColumn>
        </DataGrid.Columns>
    </DataGrid>
</UserControl>

Из-за того, что DataTemplate не находится в том же VisualTree, что и основной элемент управления, невозможно ссылаться на элемент управления с помощью ElementName.

Чтобы решить эту проблему, можно использовать малоизвестный .NET 4 и выше {x: Ссылка}. Изменение приведенного выше примера:

<UserControl x:Name="ThisUserControl" DataContext="whatever...">
    <DataGrid ItemsSource="{Binding Path=ListOfThings}">
        <DataGrid.Columns>
            <DataGridTemplateColumn Header="Thing">
                <DataGridTemplateColumn.CellTemplate>
                    <DataTemplate>
                        <Button
                            Command="{Binding Source={x:Reference ThisUserControl}, Path=ShowThingCommand}"
                            CommandParameter="{Binding Path=ThingId}"
                            Content="{Binding Path=ThingId}" />
                    </DataTemplate>
                </DataGridTemplateColumn.CellTemplate>
            </DataGridTemplateColumn>
        </DataGrid.Columns>
    </DataGrid>
</UserControl>

Для справки см. следующие сообщения stackoverflow:

Вопрос 19244111

Вопрос 5834336

... и для объяснения причин, почему ElementName не работает в этом случае, см. этот пост в блоге,.NET 4.