web-dev-qa-db-ja.com

リストボックスアイテム内のコマンドをビューモデルの親のプロパティにバインドする

私はこれに約1時間取り組んでおり、関連するすべてのSO質問を調べました。

私の問題は非常に単純です:

私は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>

魔法を実行するMVVMLightツールキットのViewModelLocatorクラスを使用します。

20
gideon

ここであなたに反対する2つの問題があります。

DataContextDataTemplateは、テンプレートが表示しているアイテムに設定されます。つまり、ソースを設定せずにバインディングを使用することはできません。

もう1つの問題は、テンプレートがアイテムが技術的に論理ツリーの一部ではないことを意味するため、DataTemplateノードを超えて祖先を検索できないことです。

これを解決するには、バインディングが論理ツリーの外側に到達する必要があります。定義された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>
13

少し異なる例ですが、バインディングで(ElementNameを使用して)親コンテナーを参照することにより、Path構文を使用してそのDataContextとそれに続くプロパティにアクセスできることがわかりました。以下に示すように:

<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>
31
Darren

したがって、ICommandをトリガーするために、ハイパーリンクに適切なDataContextを与えようとしているように見えます。単純な要素名バインディングでこれを解決できると思います。

<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は、ViewModelタイプではなく、Visual-Typeのみをチェックします。

8
Jobi Joy

このようなものを試してください

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

リストボックスのビューモデルとは異なるデータコンテキストを設定したため、リストボックス内でコマンドバインディングを見つけることができません。

6
xtds

まあ、それは少し遅いです、私は知っています。しかし、私は最近同じ問題に直面したばかりです。アーキテクチャ上の理由から、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>

静的ビューモデルロケーターは、ビューモデルをインスタンス化します。

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

この回避策を使用することは、データテンプレートからビューモデルにあるコマンドにバインドする別の方法です。

2
throbi

@Darrenからの回答はほとんどの場合うまく機能し、可能であれば推奨される方法です。ただし、次の(ニッチな)条件がすべて発生する実用的なソリューションではありません。

  • DataGrid with DataGridTemplateColumn
  • .NET 4
  • Windows XP

...そしておそらく他の状況でも。理論的には、すべてのバージョンのWindowsで失敗するはずです。しかし、私の経験では、ElementNameアプローチはWindows7以降のDataGridで機能しますが、XPでは機能しません。

次の架空の例では、serControl.DataContext(ViewModel)でICommandと呼ばれるShowThingCommandにバインドしようとしています。

<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でコントロールにバックアップを参照することはできません。

これを解決するには、あまり知られていない.NET4以降{x:Reference}を使用できます。上記の例を変更します。

<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が機能しない理由の説明については、 このブログ投稿 を参照してください。これには、.NET4より前の回避策が含まれています。

1
Andrew B