开发者

ElementName Binding does not work in custom control in Silverlight 4

We have a screen and its view model:

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

On this page we have custom control (CustomControl) and following xaml to bind the command:

<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>
                </CustomContro开发者_JAVA技巧l.ItemTemplate>
        </CustomControl>

Our custom SL control uses the above template (DataTemplate) to display it's children:

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

We are sure that:

  • ViewModel is correctly bound to View
  • All nodes are displayed correctly
  • Regular binding works correctly
  • There are no binding warnings in VS
  • Command binding works if we bind with Command="{Binding NodeClickedCommand}", but of course this binds to command that should exist on single node and we want to bind to command that exists on the screen view model.
  • Simmilar scenario works with ListBox and ListBox.ItemTemplate

The problem is that NodeClickedCommand is never bound, why?


I think the problem might be with the order of item generation and command binding. Your custom node items might be added to the layout tree later than the binding tries to resolve the command, so the binding in the datatemplate can't traverse the layout tree to resolve your element.

You wrote that ListBox works with this setup, so try to dig into it a bit to see at which point does it generate the items and make sure you follow a similar pattern.


It's the naming container. The ItemTemplates will be rendered by a control "later" in the visual cycle, so the naming container is a different scope than the location in the UserControl. Therefore, Screen is not a valid element in the scope.

Since we don't see the inner workings of your custom control, my best solution for you right now is wrap your nodes in separate view models and have those view models reference the 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");
    }
}

Then your XAML would look like this:

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

You would still reference the same ICommand from the ScreenViewModel, so you are not creating multiple instances of that specific command.


It seems that using ContentPresenter instead of ItemTemplate.LoadContent solves this issue:

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

Thanks to dain as he pointed me to the right direction.

0

上一篇:

下一篇:

精彩评论

暂无评论...
验证码 换一张
取 消

最新问答

问答排行榜