在项目模板列表框中包含项目源本身的项目

时间:2017-09-29 16:20:42

标签: c# wpf listbox itemtemplate

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

在我的viewmodel中,我有一组按钮,一个名为ButtonsCollection的属性。 让我们说: 内容为“a”的按钮, 内容为“b”的按钮, 内容为“c”的按钮。

现在我想让列表框显示每个带有边框的按钮,就像我在ItemTemplate中声明的一样。

1 个答案:

答案 0 :(得分:1)

DataConmplate是为ItemsControl中的每个项目实例化的(在您的情况下为ListBox)。它的唯一作用是描述你的物品在渲染时的样子。

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

这是一个令人担忧的分离。通过这种方式,UI和后端可以由几个人独立开发,DataContext是合同。

当然,正如托马斯·克里斯托夫所指出的那样,框架并没有强迫你以任何方式这样做。

如果你这样做:

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

它可能有效,但只要将同一个集合绑定到另一个ItemsControl, 它会崩溃。为什么?因为WPF中的每个FrameworkElement都必须只有一个父级。 WPF将抛出异常“元素已经是另一个元素的子元素。”

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

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:

 <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。一个很好的解决方案是将ContentControl放在保留空间中,并为开发人员提供一个实现的接口,包含object GetUI();方法并进行如下调用:ContentControl.Content = ActivePlugin.GetUI();