binding a command inside a listbox item to a property on the viewmodel parent

后端 未结 7 2075
北海茫月
北海茫月 2020-12-15 18:42

I\'ve been working on this for about an hour and looked at all related SO questions.

My problem is very simple:

I have HomePageVieModel:

Home         


        
相关标签:
7条回答
  • 2020-12-15 19:19

    The answer from @Darren works well in most cases, and should be the preferred method if possible. However, it is not a working solution where the following (niche) conditions all occur:

    • DataGrid with DataGridTemplateColumn
    • .NET 4
    • Windows XP

    ...and possibly in other circumstances too. In theory it should fail on all versions of Windows; but in my experience the ElementName approach works in a DataGrid on Windows 7 upwards but not XP.

    In the following fictional example, we are trying to bind to an ICommand called ShowThingCommand on the UserControl.DataContext (which is the 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>
    

    Due to the DataTemplate not being in the same VisualTree as the main control, it's not possible to reference back up to the control by ElementName.

    To solve this, the little known .NET 4 and above {x:Reference} can be used. Modifying the above example:

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

    For reference, see the following stackoverflow posts:

    Question 19244111

    Question 5834336

    ...and for an explanation of why ElementName doesn't work in this circumstance, see this blog post which contains a pre-.NET 4 workaround.

    0 讨论(0)
  • 2020-12-15 19:20

    try something like this

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

    he can't find your command binding inside the listbox because you set a diffrent datacontext than the viewmodel for that listbox

    0 讨论(0)
  • 2020-12-15 19:24

    So looks like you are trying to give the proper DataContext to the HyperLink so as to trigger ICommand. I think a simple element name binding can solve this.

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

    The AncestorType checks only for Visual-Types not for ViewModel types.

    0 讨论(0)
  • 2020-12-15 19:26

    Slightly different example but, I found that by referencing the parent container (using ElementName) in the binding you can get to it's DataContext and its subsequent properties using the Path syntax. As shown below:

    <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>
    
    0 讨论(0)
  • 2020-12-15 19:26

    Well, it's a little bit late, I know. But I have only recently faced the same problem. Due to architectural reasons I decided to use a static viewmodel locator instead of the 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>
    

    The static viewmodel locator instantiates the view model:

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

    Using this workaround is another way to bind from a data template to a command that is in the viewmodel.

    0 讨论(0)
  • 2020-12-15 19:30
    <ListBox xmlns:model="clr-namespace:My.Space;assembly=My.Assembly"
             ItemsSource="{Binding Path=AllNewsItems, Mode=OneWay}">
        <ListBox.ItemTemplate>
            <DataTemplate>
                <StackPanel>
                    <TextBlock>
                        <Hyperlink Command="{Binding RelativeSource={RelativeSource Mode=FindAncestor, AncestorType={x:Type ListBox}}, Path=DataContext.(model:MyNewsModel.OpenNews), Mode=OneWay}"
                                   CommandParameter="{Binding Path=., Mode=OneWay}">
                            <TextBlock Text="{Binding Path=NewsContent, Mode=OneWay}" />
                        </Hyperlink>
                    </TextBlock>
                </StackPanel>
            </DataTemplate>
        </ListBox.ItemTemplate>
    </ListBox>
    
    0 讨论(0)
提交回复
热议问题