ElementName绑定在Silverlight 4的自定义控件中不起作用

ElementName绑定在Silverlight 4的自定义控件中不起作用,silverlight,xaml,silverlight-4.0,binding,Silverlight,Xaml,Silverlight 4.0,Binding,我们有一个屏幕及其视图模型: public class ScreenViewModel : BaseViewModel { [NotifyPropertyChanged] public List<Node> Nodes { get; set; } public ICommand NodeClickedCommand { get; set; } public ScreenViewModel() { NodeClickedCom

我们有一个屏幕及其视图模型:

public class ScreenViewModel : BaseViewModel
{
    [NotifyPropertyChanged]
    public List<Node> Nodes { get; set; }

    public ICommand NodeClickedCommand { get; set; }

    public ScreenViewModel()
    {
        NodeClickedCommand = new RelayCommand(NodeClicked);
        // ....
        // Some code that binds Nodes.
        // ....
    }

    private void NodeClicked()
    {
        MessageBox.Show("This is never shown");
    }
}
我们确信:

  • ViewModel已正确绑定到视图
  • 所有节点都正确显示
  • 常规绑定工作正常
  • VS中没有绑定警告
  • 如果我们使用Command=“{binding NodeClickedCommand}”进行绑定,则命令绑定可以工作,但这当然会绑定到应该存在于单个节点上的命令,并且我们希望绑定到存在于屏幕视图模型上的命令
  • Similar场景使用ListBox和ListBox.ItemTemplate

问题是NodeClickedCommand从未绑定,为什么?

我认为问题可能在于项生成和命令绑定的顺序。自定义节点项可能会在绑定尝试解析命令之后添加到布局树中,因此datatemplate中的绑定无法遍历布局树解析元素


您编写的ListBox与此设置配合使用,因此请尝试深入了解它在哪一点生成项目,并确保遵循类似的模式。

它是命名容器。ItemTemplates将在视觉循环中“稍后”由控件呈现,因此命名容器的作用域与UserControl中的位置不同。因此,Screen不是范围中的有效元素

因为我们看不到自定义控件的内部工作方式,所以我现在为您提供的最佳解决方案是将节点包装在单独的视图模型中,并让这些视图模型引用NodeClickedCommand

public class NodeViewModel : BaseViewModel
{
   public Node Node { get; set; }
   public ICommand NodeClickedCommand { get; set; }
}

public class ScreenViewModel : BaseViewModel
{
    [NotifyPropertyChanged]
    public List<NodeViewModel> Nodes { get; set; }

    public ICommand NodeClickedCommand { get; set; }

    public ScreenViewModel()
    {
        NodeClickedCommand = new RelayCommand(NodeClicked);
        // ....
        // Some code that binds Nodes.
        // ....
        // This code here whatever it does, when it gets the list of 
        // nodes, wrap them inside a NodeViewModel instead like this

        var nvm = new NodeViewModel()
        {
            NodeClickedCommand = this.NodeClickedCommand,
            Node = Node
        };

        nodes.Add(nvm);
    }

    private void NodeClicked()
    {
        MessageBox.Show("This is never shown");
    }
}
公共类节点设备模型:BaseViewModel
{
公共节点节点{get;set;}
公共ICommand NodeClickedCommand{get;set;}
}
公共类ScreenViewModel:BaseViewModel
{
[NotifyPropertyChanged]
公共列表节点{get;set;}
公共ICommand NodeClickedCommand{get;set;}
公共屏幕视图模型()
{
NODECLICKEDCOMAND=新的中继命令(NodeClicked);
// ....
//一些绑定节点的代码。
// ....
//这里的代码不管它做什么,当它得到
//节点,将它们包装在NodeView模型中,而不是像这样
var nvm=new nodevewmodel()
{
NodeClickedCommand=this.NodeClickedCommand,
节点=节点
};
添加(nvm);
}
私有void NodeClicked()
{
MessageBox.Show(“这是从未显示过的”);
}
}
那么您的XAML将如下所示:

<UserControl x:Class="ScreenView"
    x:Name="Screen"
    >
     <CustomControl Nodes="{Binding Nodes}">
                <CustomControl.ItemTemplate>
                    <DataTemplate>
                                <Button Command="{Binding NodeClickedCommand}">
                                    <TextBlock>hello</TextBlock>
                                </Button>
                    </DataTemplate>
                </CustomControl.ItemTemplate>
        </CustomControl>

你好

您仍然可以从ScreenViewModel中引用相同的ICommand,因此您不会创建该特定命令的多个实例。

似乎使用ContentPresenter而不是ItemTemplate。LoadContent解决了此问题:

foreach(Node node in Nodes)
{
    ContentPresenter contentPresenter = new ContentPresenter();
    contentPresenter.Content = node;
    contentPresenter.ContentTemplate = ItemTemplate;
    this._canvas.Children.Add(contentPresenter);

//    FrameworkElement frameworkElement = (FrameworkElement)ItemTemplate.LoadContent();
//    frameworkElement.DataContext = node ;
//    this._canvas.Children.Add(frameworkElement);
}

多亏dain为我指出了正确的方向。

我用reflector查看ListBox是如何做到这一点的,我必须说,30分钟后我完全迷路了。看起来他们根本不使用DataTemplate.LoadContent方法。这是PrepareContainerForItemOverride方法(请参阅:),所有神奇的事情都发生在这里:)好的,您也可以使用element.ContentTemplate=ItemTemplate;这正是我试图避免的,我不想在节点级别使用ICommand。
<UserControl x:Class="ScreenView"
    x:Name="Screen"
    >
     <CustomControl Nodes="{Binding Nodes}">
                <CustomControl.ItemTemplate>
                    <DataTemplate>
                                <Button Command="{Binding NodeClickedCommand}">
                                    <TextBlock>hello</TextBlock>
                                </Button>
                    </DataTemplate>
                </CustomControl.ItemTemplate>
        </CustomControl>
foreach(Node node in Nodes)
{
    ContentPresenter contentPresenter = new ContentPresenter();
    contentPresenter.Content = node;
    contentPresenter.ContentTemplate = ItemTemplate;
    this._canvas.Children.Add(contentPresenter);

//    FrameworkElement frameworkElement = (FrameworkElement)ItemTemplate.LoadContent();
//    frameworkElement.DataContext = node ;
//    this._canvas.Children.Add(frameworkElement);
}