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

时间:2011-05-04 09:57:38

标签: silverlight xaml silverlight-4.0 binding

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

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");
    }
}

在此页面上,我们有自定义控件(CustomControl),并在xaml之后绑定命令:

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

我们的自定义SL控件使用上面的模板(DataTemplate)来显示它的孩子:

foreach(Node node in Nodes)
{
    FrameworkElement frameworkElement = (FrameworkElement)ItemTemplate.LoadContent();
    frameworkElement.DataContext = node ;
    this._canvas.Children.Add(frameworkElement);
}

我们确信:

  • ViewModel已正确绑定到View
  • 所有节点都正确显示
  • 常规绑定正常工作
  • VS
  • 中没有绑定警告
  • 如果我们使用Command =&#34; {Binding NodeClickedCommand}&#34;绑定命令绑定,但当然这会绑定到应该存在于单个节点上的命令,我们希望绑定到屏幕上存在的命令查看模型。
  • Simmilar场景适用于ListBox和ListBox.ItemTemplate

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

3 个答案:

答案 0 :(得分:0)

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

您写道ListBox适用于此设置,因此请尝试深入了解它,以确定它生成项目的位置,并确保您遵循类似的模式。

答案 1 :(得分:0)

这是命名容器。 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");
    }
}

然后您的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,因此您不会创建该特定命令的多个实例。

答案 2 :(得分:0)

似乎使用 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,因为他指出了我正确的方向。