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

发布于 2024-11-05 14:01:10 字数 1796 浏览 0 评论 0原文

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

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 是正确绑定到视图
  • 所有节点都正确显示
  • 常规绑定工作正常
  • 如果我们使用 Command="{Binding NodeClickedCommand}" 进行绑定,则VS 命令绑定不会出现任何绑定警告
  • ,但是当然,这会绑定到应该存在于单个节点上的命令,并且我们想要绑定到屏幕视图模型上存在的命令。
  • 类似的场景适用于 ListBox 和 ListBox.ItemTemplate

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

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>
                </CustomControl.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?

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(3

鱼窥荷 2024-11-12 14:01:10

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

您写到 ListBox 可以使用此设置,因此请尝试深入研究一下它在哪个点生成项目,并确保您遵循类似的模式。

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.

遮了一弯 2024-11-12 14:01:10

这是命名容器。 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,因此您不会创建该特定命令的多个实例。

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.

半边脸i 2024-11-12 14:01:10

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

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.

~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文