10

WPF ListViewコントロールがあり、ItemsSourceは次のように作成されたICollectionViewに設定されています。

var collectionView = 
  System.Windows.Data.CollectionViewSource.GetDefaultView(observableCollection);
this.listView1.ItemsSource = collectionView;

...ここで、observableCollectionは複合型のObservableCollectionです。ListViewは、アイテムごとに、複合型の1つの文字列プロパティのみを表示するように構成されています。

ユーザーはListViewを更新できます。その時点で、ロジックは現在選択されているアイテムの「キー文字列」を格納し、基になるobservableCollectionを再入力します。次に、前の並べ替えとフィルターがcollectionViewに適用されます。この時点で、更新要求の前に選択されていたアイテムを「再選択」したいと思います。observableCollectionのアイテムは新しいインスタンスなので、それぞれの文字列プロパティを比較して、一致するものを選択します。このような:

private void SelectThisItem(string value)
{
    foreach (var item in collectionView) // for the ListView in question
    {
        var thing = item as MyComplexType;
        if (thing.StringProperty == value)
        {
            this.listView1.SelectedItem = thing;
            return;
        }
    }
}

これはすべて機能します。4番目の項目が選択され、ユーザーがF5キーを押すと、リストが再構成され、前の4番目の項目と同じ文字列プロパティを持つ項目が選択されます。これは新しい4番目の項目である場合もあれば、そうでない場合もありますが、「驚き最小の動作」を提供します。

この問題は、ユーザーがその後矢印キーを使用してListView内を移動するときに発生します。更新後の最初の上矢印または下矢印により、前のロジックで選択されたアイテムに関係なく、(新しい)リストビューの最初のアイテムが選択されます。それ以降の矢印キーは期待どおりに機能します。

なぜこうなった?

これは明らかに「驚き最小の原則」のルールに違反しています。どうすればそれを回避できますか?


編集さらに検索すると、これは、詳細を提供することを除いて、未回答のWPFListViewの矢印ナビゲーションとキーストロークの問題
で 説明されているのと同じ異常のようです。

4

9 に答える 9

16

これは、ListView (およびおそらく他のいくつかのWPFコントロール)でのある種の既知であるが十分に説明されていない問題のある動作が原因であるように見えます。Focus()プログラムでSelectedItemを設定した後、特定のListViewItemでアプリを呼び出す必要があります。

ただし、SelectedItem自体はUIElementではありません。これは、ListViewに表示しているものすべてのアイテムであり、多くの場合、カスタムタイプです。したがって、を呼び出すことはできませんthis.listView1.SelectedItem.Focus()。それはうまくいきません。その特定のアイテムを表示するUIElement(またはコントロール)を取得する必要があります。ItemContainerGeneratorと呼ばれるWPFインターフェイスの暗いコーナーがあります。これにより、ListViewに特定のアイテムを表示するコントロールを取得できると思われます。

このようなもの:

this.listView1.SelectedItem = thing;
// *** WILL NOT WORK!
((UIElement)this.listView1.ItemContainerGenerator.ContainerFromItem(thing)).Focus();

ただし、これには2つ目の問題もあります。SelectedItemを設定した直後は機能しません。ItemContainerGenerator.ContainerFromItem()は常にnullを返すようです。googlespaceの他の場所では、GroupStyleが設定されているとnullを返すと報告されています。しかし、それはグループ化せずに、私と一緒にこの振る舞いを示しました。

ItemContainerGenerator.ContainerFromItem()リストに表示されているすべてのオブジェクトに対してnullを返します。またItemContainerGenerator.ContainerFromIndex()、すべてのインデックスに対してnullを返します。必要なのは、ListView(または何か)がレンダリングされた後でのみそれらを呼び出すことです。

私はこれを直接経由して試しましDispatcher.BeginInvoke()たが、それも機能しません。

他のいくつかのスレッドの提案で、私はのイベントDispatcher.BeginInvoke()内から使用しました。ええ、簡単ですね?(いいえ)StatusChangedItemContainerGenerator

コードは次のようになります。

MyComplexType current;

private void SelectThisItem(string value)
{
    foreach (var item in collectionView) // for the ListView in question
    {
        var thing = item as MyComplexType;
        if (thing.StringProperty == value)
        {
            this.listView1.ItemContainerGenerator.StatusChanged += icg_StatusChanged;
            this.listView1.SelectedItem = thing;
            current = thing;
            return;
        }
    }
}


void icg_StatusChanged(object sender, EventArgs e)
{
    if (this.listView1.ItemContainerGenerator.Status
        == System.Windows.Controls.Primitives.GeneratorStatus.ContainersGenerated)
    {
        this.listView1.ItemContainerGenerator.StatusChanged
            -= icg_StatusChanged;
        Dispatcher.BeginInvoke(System.Windows.Threading.DispatcherPriority.Input,
                               new Action(()=> {
                                       var uielt = (UIElement)this.listView1.ItemContainerGenerator.ContainerFromItem(current);
                                       uielt.Focus();}));

    }
}

それはいくつかの醜いコードです。ただし、プログラムでSelectedItemをこのように設定すると、後続の矢印ナビゲーションがListViewで機能するようになります。

于 2011-09-09T16:52:45.537 に答える
4

ListBox コントロールでこの問題が発生していました (これが、この SO の質問を見つけた方法です)。私の場合、SelectedItem はバインディングを介して設定されていました。その後のキーボード ナビゲーションの試行により、ListBox がリセットされ、最初の項目が選択されました。また、アイテムを追加/削除することで (毎回新しいコレクションにバインドするのではなく)、基礎となる ObservableCollection を同期していました。

受け入れられた回答に記載されている情報に基づいて、ListBoxの次のサブクラスを使用して回避できました。

internal class KeyboardNavigableListBox : ListBox
{
    protected override void OnSelectionChanged(SelectionChangedEventArgs e)
    {
        base.OnSelectionChanged(e);

        var container = (UIElement) ItemContainerGenerator.ContainerFromItem(SelectedItem);

        if(container != null)
        {
            container.Focus();
        }
    }
}

これが誰かの時間を節約するのに役立つことを願っています。

于 2014-09-01T19:06:08.233 に答える
2

少し違うアプローチを見つけました。コード内で正しい項目が強調表示されるようにデータ バインディングを使用しています。次に、すべての再バインドにフォーカスを設定する代わりに、キーボード ナビゲーション用のコード ビハインドにプレイベント ハンドラーを追加するだけです。このような。

    public MainWindow()
    {
         ...
         this.ListView.PreviewKeyDown += this.ListView_PreviewKeyDown;
    }

    private void ListView_PreviewKeyDown(object sender, KeyEventArgs e)
    {
        UIElement selectedElement = (UIElement)this.ListView.ItemContainerGenerator.ContainerFromItem(this.ListView.SelectedItem);
        if (selectedElement != null)
        {
            selectedElement.Focus();
        }

        e.Handled = false;
    }

これは、WPF にキープレスを処理させる前に、正しいフォーカスが設定されていることを確認するだけです。

于 2014-10-14T08:59:26.030 に答える
1

BeginInvoke優先順位を指定することで、アイテムを見つけた後にフォーカスすることができます:

Dispatcher.CurrentDispatcher.BeginInvoke(DispatcherPriority.Loaded, new Action(() =>
{
    var lbi = AssociatedObject.ItemContainerGenerator.ContainerFromIndex(existing) as ListBoxItem;
    lbi.Focus();
}));
于 2014-11-20T18:55:26.067 に答える
0

いろいろいじった後、MVVMで動作させることができませんでした。私は自分で試してみて、DependencyProperty を使用しました。これは私にとってうまくいきました。

public class ListBoxExtenders : DependencyObject
{
    public static readonly DependencyProperty AutoScrollToCurrentItemProperty = DependencyProperty.RegisterAttached("AutoScrollToCurrentItem", typeof(bool), typeof(ListBoxExtenders), new UIPropertyMetadata(default(bool), OnAutoScrollToCurrentItemChanged));

    public static bool GetAutoScrollToCurrentItem(DependencyObject obj)
    {
        return (bool)obj.GetValue(AutoScrollToSelectedItemProperty);
    }

    public static void SetAutoScrollToCurrentItem(DependencyObject obj, bool value)
    {
        obj.SetValue(AutoScrollToSelectedItemProperty, value);
    }

    public static void OnAutoScrollToCurrentItemChanged(DependencyObject s, DependencyPropertyChangedEventArgs e)
    {
        var listBox = s as ListBox;
        if (listBox != null)
        {
            var listBoxItems = listBox.Items;
            if (listBoxItems != null)
            {
                var newValue = (bool)e.NewValue;

                var autoScrollToCurrentItemWorker = new EventHandler((s1, e2) => OnAutoScrollToCurrentItem(listBox, listBox.Items.CurrentPosition));

                if (newValue)
                    listBoxItems.CurrentChanged += autoScrollToCurrentItemWorker;
                else
                    listBoxItems.CurrentChanged -= autoScrollToCurrentItemWorker;
            }
        }
    }

    public static void OnAutoScrollToCurrentItem(ListBox listBox, int index)
    {
        if (listBox != null && listBox.Items != null && listBox.Items.Count > index && index >= 0)
            listBox.ScrollIntoView(listBox.Items[index]);
    }

}

XAML での使用

<ListBox IsSynchronizedWithCurrentItem="True" extenders:ListBoxExtenders.AutoScrollToCurrentItem="True" ..../>
于 2014-05-12T12:12:29.823 に答える
0

Cheeso、以前の回答で次のように述べました。

しかし、これには 2 つ目の問題もあります。SelectedItem を設定した直後には機能しません。ItemContainerGenerator.ContainerFromItem() は常に null を返すようです。

これに対する簡単な解決策は、SelectedItem をまったく設定しないことです。これは、要素にフォーカスすると自動的に発生します。したがって、次の行を呼び出すだけで済みます。

((UIElement)this.listView1.ItemContainerGenerator.ContainerFromItem(thing)).Focus();
于 2013-01-17T18:13:19.923 に答える
0

プログラムで項目を選択しても、キーボード フォーカスは与えられません。あなたはそれを明示的にしなければなりません...((Control)listView1.SelectedItem).Focus()

于 2011-09-09T15:23:26.677 に答える
0

Cheesoのソリューションは私にとってはうまくいきます。nullこれを行うようにa を設定するだけで例外を防ぐtimer.tickことができるため、元のルーチンから離れます。

var uiel = (UIElement)this.lv1.ItemContainerGenerator                        
           .ContainerFromItem(lv1.Items[ix]); 
if (uiel != null) uiel.Focus();

RemoveAt/Insertの後にタイマーを呼び出し、Window.Loadedフォーカスを設定して最初のアイテムを選択するときに問題が解決しました。

SE で得た多くのインスピレーションと解決策に対して、この最初の投稿を返したいと思いました。ハッピーコーディング!

于 2016-08-01T17:32:00.147 に答える