在列表框的 ItemTemplate 中包含来自 itemsource 本身的项目

Including an item from the itemsource itself in the ItemTemplate of listbox

我的 WPF 应用程序中有一个列表框。

 <ListBox ItemsSource="{Binding ButtonsCollection}">
    <ListBox.ItemTemplate>
        <DataTemplate>
            <Border BorderBrush="Black" BorderThickness="2" >
                **Here I want to insert the current button**
            </StackPanel>
        </DataTemplate>
    </ListBox.ItemTemplate>
</ListBox>

在我的视图模型中,我有一个按钮集合,一个名为 ButtonsCollection 的 属性。 让我们说: 内容为 "a" 的按钮, 内容为 "b" 的按钮, 内容为 "c".

的按钮

现在我希望列表框显示每个带有边框的按钮,正如我在 ItemTemplate 中声明的那样。

DataTemplate 是为 ItemsControl(在您的例子中是 ListBox)中的每个项目实例化的。它的唯一作用是描述您的项目在呈现时的外观。

DataContext 应该 包含描述您的 UI 状态的对象。

这就是关注点分离。这样 UI 和后端可以由几个人独立开发,DataContext 就是契约。

当然,正如 Thomas Christof 指出的那样,框架不会以任何方式强制您那样做。

如果你这样做:

 <ListBox ItemsSource="{Binding ButtonsCollection}">
    <ListBox.ItemTemplate>
        <DataTemplate>
            <Border BorderBrush="Black" BorderThickness="2" >
                <ContentControl Content={Binding}"/>
            </StackPanel>
        </DataTemplate>
    </ListBox.ItemTemplate>
</ListBox>

它可能会起作用,但是只要您将同一个集合绑定到另一个 ItemsControl, 它会崩溃。为什么?因为 WPF 中的每个 FrameworkElement 都必须只有一个父级。 WPF 将抛出异常 "Element is already the child of another element."

如果您遵循 MVVM 模式并将按钮的逻辑表示封装在 class:

public class NamedCommand : ICommand
{
    private Action _action;

    public string Name { get; private set; }

    public NamedCommand(string name, Action action)
    {
        Name = name;
        _action = action;
    }

    public virtual bool CanExecute(object parameter)
    {
        return true;
    }

    public void Execute(object parameter)
    {
        if (_action != null)
            _action();
    }

    // Call this whenever you need to update the IsEnabled of the binding target
    public void Update()
    {
        if (CanExecuteChanged != null)
            CanExecuteChanged(this, EventArgs.Empty);
    }

    public event EventHandler CanExecuteChanged;
}

您可以将同一个集合绑定到多个控件,例如菜单栏、上下文菜单、侧面板等

在你的例子中,这是一个列表框:

 <ListBox ItemsSource="{Binding Commands}">
    <ListBox.ItemTemplate>
        <DataTemplate>
            <Border BorderBrush="Black" BorderThickness="2" >
                <Button Content="{Binding Name}" Command="{Binding}"/>
            </StackPanel>
        </DataTemplate>
    </ListBox.ItemTemplate>
</ListBox>

您将 运行 使用当前方法的另一个问题是当后台线程试图操纵您的按钮时。 UI 元素与创建它们的线程(STA 线程)相关联。 您最终会将所有调用包装在 Dispatcher.Invokes 中,有时可能会陷入僵局。 然而,实施 INotifyPropertyChanged 并在需要时引发 PropertyChanged 会将此负担放在 WPF 框架上(更新通知在幕后的主线程上调度)。

最后一点,在后面的代码中创建 UI 并不总是一个坏主意。假设您想在您的应用程序中实现一个插件系统,并在您的布局中保留一个可折叠区域,它将承载未知插件的 UI。您不能强迫插件的开发人员恰好有 2 个按钮和一个文本框,这样它才能很好地适应您的 DataTemplate。一个好的解决方案是在保留的 space 中放置一个 ContentControl 并为开发人员提供一个接口来实现,其中包含一个 object GetUI(); 方法并进行如下调用: ContentControl.Content = ActivePlugin.GetUI();