在WPF ListBox中丢失焦点问题并进行分组

时间:2012-08-20 19:00:54

标签: wpf listbox focus grouping

我正在尝试在WPF上创建一个包含分组的列表框。这可以很容易地完成,如WPF4 Unleashed和Web上的任何其他教程中所述。

XAML(这里有两个列表,有或没有分组+按钮来更新它们的公共项目来源):

<Window x:Class="GroupTest.MainWindow"
        xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
        xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
        Title="MainWindow" Height="350" Width="525"
        Loaded="Window_Loaded">
    <Grid>
        <Grid.RowDefinitions>
            <RowDefinition Height="*" />
            <RowDefinition Height="*" />
            <RowDefinition Height="Auto" />
        </Grid.RowDefinitions>

        <ListBox Grid.Row="0" ItemsSource="{Binding Path=Items}" x:Name="_listBox1">
            <ListBox.GroupStyle>
                <x:Static Member="GroupStyle.Default" />
            </ListBox.GroupStyle>
            <ListBox.ItemTemplate>
                <DataTemplate>
                    <TextBlock Text="{Binding Path=Name}" />
                </DataTemplate>
            </ListBox.ItemTemplate>
        </ListBox>

        <ListBox Grid.Row="1" ItemsSource="{Binding Path=Items}" x:Name="_listBox2">
            <ListBox.ItemTemplate>
                <DataTemplate>
                    <TextBlock Text="{Binding Path=Name}" />
                </DataTemplate>
            </ListBox.ItemTemplate>
        </ListBox>

        <Button Grid.Row="2" Content="Update Items" Click="Button_Click" Focusable="False"/>
    </Grid>
</Window>

代码(这里我在加载页面时设置分组,单击按钮时更新/替换项目源):

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Data;
using System.Windows.Documents;
using System.Windows.Input;
using System.Windows.Media;
using System.Windows.Media.Imaging;
using System.Windows.Navigation;
using System.Windows.Shapes;
using System.ComponentModel;

namespace GroupTest
{
    public partial class MainWindow : Window, INotifyPropertyChanged
    {
        public class Item
        {
            public string Name { get; set; }
            public bool Flag { get; set; }
        }

        private List<Item> _items;
        public List<Item> Items
        {
            get { return _items; }
            set
            {
                if (_items != value)
                {
                    _items = value;
                    NotifyPropertyChanged("Items");
                }
            }
        }

        public event PropertyChangedEventHandler PropertyChanged;

        public MainWindow()
        {
            InitializeComponent();
            MakeItems();
            DataContext = this;
        }

        private void Window_Loaded(object sender, RoutedEventArgs e)
        {
            ICollectionView view = CollectionViewSource.GetDefaultView(_listBox1.Items);
            view.GroupDescriptions.Add(new PropertyGroupDescription("Flag"));
        }

        private void MakeItems()
        {
            _items = new List<Item>();
            _items.Add(new Item() { Name = "1", Flag = true });
            _items.Add(new Item() { Name = "2", Flag = true });
            _items.Add(new Item() { Name = "3", Flag = false });
            _items.Add(new Item() { Name = "4", Flag = true });
            _items.Add(new Item() { Name = "5", Flag = false });
        }

        private void UpdateItems()
        {
            Items = new List<Item>(_items);
        }

        private void Button_Click(object sender, RoutedEventArgs e)
        {
            UpdateItems();
        }

        private void NotifyPropertyChanged(string propertyName)
        {
            if (PropertyChanged != null)
            {
                PropertyChanged(this, new PropertyChangedEventArgs(propertyName));
            }
        }

    }
}

它有效,但有一个我无法解决的奇怪错误。每次更新项目源时,带有分组的列表#1都会失去焦点。没有分组的列表#2保持焦点。

以下是完整项目来源的链接:https://dl.dropbox.com/u/60611528/GroupTest.zip

有什么建议吗?提前谢谢!

更新 我尝试将Items设为ObservableCollection&lt;&gt;,但这没有帮助。焦点仍然从分组列表中消失。

更新2 我的真实应用程序我在模型类中有项目,它不知道列表框。我希望有一个解决方案,可以在不紧密耦合窗口和模型类的情况下解决问题。

1 个答案:

答案 0 :(得分:0)

这是问题的解决方案。

我修改了它以明确数据正在发生变化。可能有一个更简单的解决方案。

想法是检测Grouped ListBox是否关注其任何项目......如果是,那么焦点将恢复到列表框....因为这就是丢失的内容

我认为它与货币和ListCollectionView在分组模式下有关,或者它可能与UI虚拟化有关...当你在{{1}上使用分组时被禁用}(因为样式设置ListBox

(您可以查看参考源代码以深入了解行为)。

ScrollViewer.CanContentScroll=False

如果您想要的版本更接近原作:

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Data;
using System.Windows.Documents;
using System.Windows.Input;
using System.Windows.Media;
using System.Windows.Media.Imaging;
using System.Windows.Navigation;
using System.Windows.Shapes;
using System.ComponentModel;
using System.Collections.ObjectModel;

namespace GroupTest
{
    public partial class MainWindow : Window, INotifyPropertyChanged
    {
        public class Item
        {
            public string Name { get; set; }
            public bool Flag { get; set; }
        }

        public ObservableCollection<Item> Items { get; set; }

        public event PropertyChangedEventHandler PropertyChanged;

        public MainWindow()
        {
            Items = new ObservableCollection<Item>();
            InitializeComponent();
            MakeItems();
            DataContext = this;
        }

        private void Window_Loaded(object sender, RoutedEventArgs e)
        {
            ICollectionView view = CollectionViewSource.GetDefaultView(_listBox1.Items);
            view.GroupDescriptions.Add(new PropertyGroupDescription("Flag"));
        }

        bool bFlip = false;

        private void MakeItems()
        {
            Items.Clear();
            if (bFlip)
            {
                Items.Add(new Item() { Name = "1", Flag = true });
                Items.Add(new Item() { Name = "2", Flag = true });
                Items.Add(new Item() { Name = "3", Flag = false });
                Items.Add(new Item() { Name = "4", Flag = true });
                Items.Add(new Item() { Name = "5", Flag = false });

                bFlip = false;
            }
            else
            {
                Items.Add(new Item() { Name = "1", Flag = true });
                Items.Add(new Item() { Name = "2", Flag = true });
                Items.Add(new Item() { Name = "3", Flag = false });
                Items.Add(new Item() { Name = "4", Flag = true });
                Items.Add(new Item() { Name = "5", Flag = false });
                Items.Add(new Item() { Name = "A", Flag = false });
                Items.Add(new Item() { Name = "B", Flag = false });

                bFlip = true;
            }
        }

        private void UpdateItems()
        {
            bool bListBox1HadFocus = false;
            IInputElement focussedelement = Keyboard.FocusedElement;
            ListBoxItem lbifocussed = focussedelement as ListBoxItem;
            Item itemwithfocus = (lbifocussed != null ? lbifocussed.Content as Item : null);

            for (int i = 0; i < Items.Count; i++)
            {
                ListBoxItem lbi = _listBox1.ItemContainerGenerator.ContainerFromIndex(i) as ListBoxItem;

                if (lbi == lbifocussed)
                {
                    bListBox1HadFocus = true;
                    break;
                }
            }

            Item oldselecteditem1 = _listBox1.SelectedItem as Item;
            Item oldselecteditem2 = _listBox2.SelectedItem as Item;

            MakeItems();

            // Set back the selections to what they were

            foreach (Item item in Items)
            {
                if (oldselecteditem1 != null && item.Name == oldselecteditem1.Name)
                {
                    _listBox1.SelectedItem = item;
                }
                if (oldselecteditem2 != null && item.Name == oldselecteditem2.Name)
                {
                    _listBox2.SelectedItem = item;
                }
            }

            if (bListBox1HadFocus)
            {
                _listBox1.Focus();
            }
        }

        private void Button_Click(object sender, RoutedEventArgs e)
        {
            UpdateItems();
        }

        private void NotifyPropertyChanged(string propertyName)
        {
            if (PropertyChanged != null)
            {
                PropertyChanged(this, new PropertyChangedEventArgs(propertyName));
            }
        }
    }
}