C# 将关联菜单绑定到视图模型属性

C# 将关联菜单绑定到视图模型属性,c#,wpf,xaml,mvvm,C#,Wpf,Xaml,Mvvm,我有一个关联菜单,它绑定到datagrid中的一个按钮。我希望上下文菜单项根据视图模型中的字符串列表进行更改。当我点击按钮时,什么也没有显示 下面是我正在使用的xaml,它位于datagrid中: <Button Grid.Column="1" Content="..." Click="Button_Click"> <Button.ContextMenu> <ContextMenu ItemsSource="{Binding Rela

我有一个关联菜单,它绑定到datagrid中的一个按钮。我希望上下文菜单项根据视图模型中的字符串列表进行更改。当我点击按钮时,什么也没有显示

下面是我正在使用的xaml,它位于datagrid中:

<Button Grid.Column="1" Content="..."  Click="Button_Click">
        <Button.ContextMenu>
       <ContextMenu ItemsSource="{Binding RelativeSource={RelativeSource Mode=FindAncestor,AncestorType={x:Type UserControl}},Path=DataContext.SelectableDescriptions}">    
         <TextBlock Text="{Binding}"/>
       </ContextMenu>
       </Button.ContextMenu>        
</Button>

以下是整个DataGrid xaml:

<DataGrid Grid.Row="1" Grid.ColumnSpan="4" CanUserAddRows="True" AutoGenerateColumns="False" CanUserDeleteRows="True"  ItemsSource="{Binding JobPricings, Mode=TwoWay}" SelectedItem="{Binding SelectedJobPricing, Mode=TwoWay, UpdateSourceTrigger=PropertyChanged}" >
    <DataGrid.Columns>
        <DataGridTemplateColumn  Header="Description" Width="25*"   >
            <DataGridTemplateColumn.CellTemplate>
                <DataTemplate>
                    <Grid>
                        <Grid.ColumnDefinitions>
                            <ColumnDefinition Width="75*"/>
                            <ColumnDefinition Width="25*"/>
                        </Grid.ColumnDefinitions>
                        <TextBox Grid.Column="0" Text="{Binding Description,Mode=TwoWay, UpdateSourceTrigger=PropertyChanged}"/>
                        <Button Grid.Column="1" Content="..."  Click="Button_Click">
                            <Button.ContextMenu>
                                <ContextMenu ItemsSource="{Binding RelativeSource={RelativeSource Mode=FindAncestor,AncestorType={x:Type UserControl}},Path=DataContext.SelectableDescriptions}">
                                    <TextBlock Text="{Binding}"/>
                                </ContextMenu>
                            </Button.ContextMenu>
                        </Button>
                    </Grid>
                </DataTemplate>
            </DataGridTemplateColumn.CellTemplate>
         </DataGridTemplateColumn>
        <DataGridTextColumn Header="Unit Price" Binding="{Binding UnitPrice, Mode=TwoWay}" Width="25*"/>
        <DataGridTextColumn Header="Unit" Binding="{Binding Unit, Mode=TwoWay}" Width="25*"/>
        <DataGridTemplateColumn  Header="Currency   " Width="25*" >
            <DataGridTemplateColumn.CellTemplate>
                <DataTemplate>
                    <Grid>
                        <ComboBox  SelectedValue="{Binding CurrencyID, Mode=TwoWay}" SelectedValuePath="ID" DisplayMemberPath="Description" ItemsSource="{Binding RelativeSource={RelativeSource Mode=FindAncestor,AncestorType={x:Type UserControl}},Path=DataContext.Currencies}"  ></ComboBox>
                    </Grid>
                </DataTemplate>
            </DataGridTemplateColumn.CellTemplate>
        </DataGridTemplateColumn>
    </DataGrid.Columns>
</DataGrid>

以下是通过视图模型将关联菜单绑定到的属性:

public ObservableCollection<string> SelectableDescriptions
{
    get
    {
        _selectableDescriptions.Add("One");
        _selectableDescriptions.Add("Two");
        return _selectableDescriptions;
    }
    set
    {
        _selectableDescriptions = value;
    }
}
public observeablecollection SelectableDescriptions
{
得到
{
_可选择的描述。添加(“一”);
_可选择的描述。添加(“两个”);
返回_selectabledescription;
}
设置
{
_selectableDescriptions=值;
}
}

你知道为什么我的列表不会出现在关联菜单中吗

我认为您必须对您的上下文菜单执行类似的操作,请注意,我的假设是您设置货币的方式正在运行

<ContextMenu>
    <ContextMenu.ItemTemplate>
        <DataTemplate>
            <TextBlock Text="{Binding}"/>
        </DataTemplate>
    </ContextMenu.ItemTemplate>
</ContextMenu>


上下文菜单和弹出窗口等内容存在于主视觉树之外,因此无法使用RelativeSource向上移动到父对象。在大多数情况下,ElementName也会中断。根据具体情况,有各种解决办法。我喜欢使用继承的附加属性来传递额外的数据,因为它不涉及更改您的VM,并且在您设置好它之后就保留在XAML中。这里有一篇博客文章用例子解释了这项技术:

正如John所说,
上下文菜单是它自己的独立窗口,与按钮的视觉树分离,因此它不会自动继承按钮的
数据上下文

但是,ContextMenu确实有一个指向它所放置对象的链接(本例中的按钮):
PlacementTarget
。通过转到菜单的PlacementTarget可以找到按钮,找到按钮后可以找到其DataContext

因此,在xaml中,您可以通过将自己的DataContext绑定到自己的
PlacementTarget.DataContext
,手动使ContextMenu继承按钮的DataContext,并且所有其他绑定(例如,对于
ItemsSource
)都可以像平常一样编写:

<Button Grid.Column="1" Content="..."  Click="Button_Click">
    <Button.ContextMenu>
        <ContextMenu DataContext="{Binding RelativeSource={RelativeSource Self}, Path=PlacementTarget.DataContext}" 
                     ItemsSource="{Binding Path=SelectableDescriptions}" >    
            <ContextMenu.ItemTemplate>
                <DataTemplate>
                    <TextBlock Text="{Binding Path=SomePropertyOnItem}" />
                </DataTemplate>
            </ContextMenu.ItemTemplate>                            
        </ContextMenu>
    </Button.ContextMenu>        
</Button>

我最终在代码隐藏中为ContextMenu设置了ItemsSource(尽管我真的不想使用这种方法:),这很有效

这是xaml

<Button Grid.Column="1" Content="..."  Name="ellipseButton" Click="ellipseButton_Click">
    <Button.ContextMenu>
       <ContextMenu >
          <ContextMenu.ItemTemplate>
              <DataTemplate>
                   <TextBlock Text="{Binding}" />
              </DataTemplate>
          </ContextMenu.ItemTemplate>
        </ContextMenu>
     </Button.ContextMenu>
</Button>

您是否实现了INotifyPropertyChanged
?以前实现过,但它不起作用。无论如何,如果我没有弄错的话,您不应该使用ObservableCollectionDataContext=JobPricingViewModel。。。这里没有显示。我在后面的代码中设置了它。当你说“无显示”时,你的意思是没有项目,还是有许多空项目?菜单中没有显示项目。我可以看到SelectableDescriptions中有一些项目,但我需要检查一下。我读了一遍,但还是弄糊涂了。英雄联盟我会再看一遍,虽然这可能是有希望的,但当我尝试它时,我会在上下文菜单中显示数据类型。在这种情况下,这可能没有帮助,因为菜单位于项上,所以按钮DataContext是该项数据,而不是所需的主UC DataContext。@DJBurb-抱歉,代码错误。
TextBlock
需要进入
DataTemplate
中来描述每个菜单项的外观。我已经更新了我的答案。它之前所做的是将TextBlock用作唯一的菜单项,覆盖ItemsSource绑定。注意:您在文本块上看到的数据类型必须是保存
SelectableDescriptions
列表的数据类型。如果不是,则@John的评论是正确的。SelectableDescriptions是一个字符串列表,因此我不确定@DJBurb的绑定中应该包含什么-在这种情况下,您只需使用一个没有路径的绑定(就像最初一样):
private void ellipseButton_Click(object sender, RoutedEventArgs e)
{
    var button = sender as Button;
    button.ContextMenu.ItemsSource = JobPricingViewModel.SelectableDescriptions;
    if (button != null) button.ContextMenu.IsEnabled = true;
    var placementTarget = sender as Button;
    if (placementTarget != null) placementTarget.ContextMenu.PlacementTarget = placementTarget;
    var button1 = sender as Button;
    if (button1 != null)
        button1.ContextMenu.Placement = System.Windows.Controls.Primitives.PlacementMode.Bottom;
    var button2 = sender as Button;
    if (button2 != null) button2.ContextMenu.IsOpen = true;
}