【问题标题】:ListBox in which each item contains a ListListBox,其中每个项目都包含一个 List
【发布时间】:2012-08-07 06:35:11
【问题描述】:

我正在尝试实现一个包含特定类型项目的列表,即会话。每个 Session 都包含一个包含 Note 类型的列表。我想在它们各自的 Session 标题下的列表中显示这些注释。

目前我尝试了两种不同的方法。第一种方法是使用ItemsControls 作为ControlTemplate 用于ListBoxItems。这是我在下图中使用的,也是我希望列表的样子。每个红色矩形显示一个会话,标题下方的项目是注释。那么问题是来自ListBox 的选择选择ItemsControls 而不是每个单独的Note。

我尝试实现该列表的另一种方法是为每个 Note 赋予它所属的 Session 的属性,以便在 ListBox 上使用 GroupStyle。如果我随后将 ListBox 的 ItemsSource 设置为 Notes 列表而不是 Sessions,我将得到一个看起来像图片的列表,并且其中包含注释选择。现在的问题是我希望列表也显示不包含任何注释的会话。

有谁知道我应该使用什么来实现带有选择的列表,并且按照我描述的方式工作?

【问题讨论】:

  • 您想跨多个会话一次只选择一个节点吗?所以不能同时选择 session 1 中的 note 1 和 session 2 中的 note 1?
  • 没错。而且我希望能够像在普通 ListBox 中一样使用箭头键向下滚动列表。

标签: c# wpf listbox grouping


【解决方案1】:

MainWindow.xaml:

    <TreeView ItemsSource="{Binding}">
        <TreeView.Resources>
            <HierarchicalDataTemplate DataType="{x:Type local:Session}" ItemsSource="{Binding Path=Notes}">
                <TextBlock Text="{Binding Path=Name}" />
            </HierarchicalDataTemplate>
            <DataTemplate DataType="{x:Type local:Note}">
                <Expander Header="{Binding Path=Notek}">
                    <TextBlock Foreground="Red" Text="{Binding Path=Details}" />
                </Expander>
            </DataTemplate>
        </TreeView.Resources>
    </TreeView>

MainWindow.xaml.cs

public partial class MainWindow : Window
{
    public MainWindow()
    {
        InitializeComponent();
        List<Session> sessions = new List<Session>();
        for (int i = 0; i < 5; i++)
        {
            List<Note> notes = new List<Note>();
            for (int j = i * 5; j < (i + 1) * 5; j++)
            {
                Note note = new Note()
                {
                    Notek = string.Format("Note {0}", j),
                    Details = string.Format("Note j = {0}{1}j*j = {2}", j, System.Environment.NewLine, j*j)
                };

                notes.Add(note);
            }
            Session session = new Session()
            {
                Name = string.Format("Session # {0}", i),
                Notes = notes
            };
            sessions.Add(session);
        }
        DataContext = sessions;
    }
}

public class Session
{
    public string Name { get; set; }
    public List<Note> Notes { get; set; }
}
public class Note
{
    public string Notek { get; set; }
    public string Details { get; set; }
}

我认为您可以根据需要设置HierarchicalDataTemplate 的样式。我只是给你看例子。我认为它比带有事件处理程序的ItemsControl 更容易。

【讨论】:

  • 这是我现在正在尝试的。在做太多不必要的工作之前,我会尝试让 TreeView 工作。谢谢!
  • 看来这是正确的答案:stackoverflow.com/questions/2750394/…,使会话无法选择。
  • 我现在使用 TreeView 让一切都按我想要的方式工作。谢谢大家!
  • 哦,我在包含带有触发器的会话的 TreeViewItems 上将 Focusable 设置为 false,而不是在视图模型中这样做。
【解决方案2】:

为了创建答案,我将假设以下数据模型:

class Session 
{
    public IEnumerable<Note> Notes { get; }
}

class Note { }

这需要一些编码来同步列表框。我创建了一个名为“ListBoxGroup”的附加属性。具有相同组名的所有列表框只能有一个共享选定项。这是相当多的代码,所以它在底部。

重要提示: 列表框的列表框组在初始设置后无法更改,并且不支持删除项目,不检查空值等。所以如果您需要更改在运行时会话中,您应该从它们的组中删除项目,检查是否从可视树中删除了列表框,等等。

首先是页面的 XAML:

     xmlns:local="clr-namespace:YourApplication.YourNamespace"
    <!-- ItemsControl does not have selection -->
    <ItemsControl ItemsSource="{Binding SessionList}">
        <ItemsControl.ItemTemplate>
            <DataTemplate>
                <StackPanel>
                    <!-- Header for the session -->
                    <Border Background="Gray">
                        <TextBlock Text="{Binding Name}" />
                    </Border>
                    <!-- listbox for notes -->
                    <ListBox ItemsSource="{Binding Notes}" local:ListBoxGroup.GroupName="Group1">
                        <ListBox.ItemTemplate>
                            <DataTemplate>
                                <!-- Template for a single note -->
                                <TextBlock Text="{Binding Description}" />
                            </DataTemplate>
                        </ListBox.ItemTemplate>
                    </ListBox>
                </StackPanel>
            </DataTemplate>
        </ItemsControl.ItemTemplate>
    </ItemsControl>

下面是 ListBoxGroup 属性的 C# 代码:

public static class ListBoxGroup
{
    public static string GetGroupName(DependencyObject obj)
    {
        return (string)obj.GetValue(GroupNameProperty);
    }

    public static void SetGroupName(DependencyObject obj, string value)
    {
        obj.SetValue(GroupNameProperty, value);
    }

    // Using a DependencyProperty as the backing store for GroupName.  This enables animation, styling, binding, etc...
    public static readonly DependencyProperty GroupNameProperty =
        DependencyProperty.RegisterAttached("GroupName", typeof(string), typeof(ListBoxGroup), new UIPropertyMetadata(null, ListBoxGroupChanged));

    private static Dictionary<string, List<ListBox>> _listBoxes = new Dictionary<string, List<ListBox>>();

    private static void ListBoxGroupChanged(DependencyObject obj, DependencyPropertyChangedEventArgs e)
    {
        string newValue = e.NewValue as string;
        ListBox listBox = obj as ListBox;
        if (newValue == null || listBox == null) return;


        if (_listBoxes.ContainsKey(newValue))
        {
            _listBoxes[newValue].Add(listBox);
        }
        else
        {
            _listBoxes.Add(newValue, new List<ListBox>() { listBox });
        }

        listBox.SelectionChanged += new SelectionChangedEventHandler(listBox_SelectionChanged);
        listBox.PreviewKeyUp += new System.Windows.Input.KeyEventHandler(listBox_KeyUp);
    }

    static void listBox_KeyUp(object sender, System.Windows.Input.KeyEventArgs e)
    {
        ListBox listBox = sender as ListBox;

        if (e.Key == System.Windows.Input.Key.Up && listBox.SelectedIndex == 0)
        {
            //move to previous
            string groupName = GetGroupName(listBox);
            List<ListBox> group = _listBoxes[groupName];

            int senderIndex = group.IndexOf(listBox);
            if (senderIndex != 0)
            {
                listBox.SelectedItem = null;

                ListBox beforeSender = group[senderIndex - 1];

                int index = beforeSender.Items.Count - 1;
                beforeSender.SelectedIndex = index;

                var container = beforeSender.ItemContainerGenerator.ContainerFromIndex(index);

                (container as FrameworkElement).Focus();


            }
        }
        else if (e.Key == System.Windows.Input.Key.Down 
                    && listBox.SelectedIndex == listBox.Items.Count - 1)
        {
            //move to next
            string groupName = GetGroupName(listBox);
            List<ListBox> group = _listBoxes[groupName];

            int senderIndex = group.IndexOf(listBox);
            if (senderIndex != group.Count - 1)
            {
                listBox.SelectedItem = null;

                ListBox afterSender = group[senderIndex + 1];

                afterSender.SelectedIndex = 0;
                var container = afterSender.ItemContainerGenerator.ContainerFromIndex(0);

                (container as FrameworkElement).Focus();
            }
        }



    }

    static void listBox_SelectionChanged(object sender, SelectionChangedEventArgs e)
    {
        if (e.AddedItems.Count > 0)
        {
            ListBox listBox = sender as ListBox;
            string groupName = GetGroupName(listBox);

            foreach (var item in _listBoxes[groupName])
            {
                if (item != listBox)
                {
                    item.SelectedItem = null;
                }
            }

        }


    }
}

【讨论】:

  • 在浏览完代码之后,这似乎可以满足我的要求,我今天晚些时候会尝试一下。谢谢!
  • 我不认为 OP 想要什么。在这种情况下,选定的项目将是 Session 对象而不是 Note 对象,不是吗?
  • @DanielHilgarth 他想选择单条笔记,检查问题上的 cmets。
  • 我读它就像他为每个会话创建一个包含会话笔记的列表框。这会导致笔记被选中,不是吗?
  • 顺便说一句,我需要能够在运行时将新会话添加到列表中。我可以在运行时添加新的列表框组吗? (虽然不需要删除它们)
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多