web-dev-qa-db-ja.com

WPFリストビューを特定の行までスクロールします

WPF、ブラウザのようなアプリ。
ListViewを含む1ページを取得しました。 PageFunctionを呼び出した後、ListViewに行を追加し、新しい行をスクロールしてビューにします。

  ListViewItem item = ItemContainerGenerator.ContainerFromIndex(index) as ListViewItem;
  if (item != null)
    ScrollIntoView(item);

これは機能します。新しい行が表示されている限り、その行は本来のフォーカスを取得します。

問題は、線が見えないと物事が機能しないことです。
行が表示されていない場合、生成された行のListViewItemがないため、ItemContainerGenerator.ContainerFromIndexはnullを返します。

しかし、アイテムがない場合、行をスクロールして表示するにはどうすればよいですか? ListViewItemを必要とせずに最後の行(または任意の場所)にスクロールする方法はありますか?

23
Sam

ここでの問題は、行が表示されていない場合、ListViewItemがまだ作成されていないことだと思います。 WPFは、オンデマンドでVisibleを作成します。

したがって、この場合、おそらくアイテムに対してnullを取得しますか? (あなたのコメントによると、あなたはそうします)

スクロールするために Scrollviewerに直接アクセスすることを提案するMSDNフォーラムのリンク を見つけました。私には、そこで提示された解決策はハックのように見えますが、自分で決めることができます。

上記のリンク からのコードスニペットは次のとおりです。

VirtualizingStackPanel vsp =  
  (VirtualizingStackPanel)typeof(ItemsControl).InvokeMember("_itemsHost",
   BindingFlags.Instance | BindingFlags.GetField | BindingFlags.NonPublic, null, 
   _listView, null);

double scrollHeight = vsp.ScrollOwner.ScrollableHeight;

// itemIndex_ is index of the item which we want to show in the middle of the view
double offset = scrollHeight * itemIndex_ / _listView.Items.Count;

vsp.SetVerticalOffset(offset);
10
EFrank

誰かが私に特定の行にスクロールするさらに良い方法を教えてくれました。それは簡単で魅力のように機能します。
要するに:

public void ScrollToLastItem()
{
  lv.SelectedItem = lv.Items.GetItemAt(rows.Count - 1);
  lv.ScrollIntoView(lv.SelectedItem);
  ListViewItem item = lv.ItemContainerGenerator.ContainerFromItem(lv.SelectedItem) as ListViewItem;
  item.Focus();
}

MSDNフォーラム の長いバージョン:

39
Sam

サムの答えにいくつか変更を加えました。最後の行までスクロールしたかったことに注意してください。残念ながら、ListViewの一部には最後の行が表示されただけなので(たとえば、その上に100行あった場合でも)、次のように修正しました。

    public void ScrollToLastItem()
    {
        if (_mainViewModel.DisplayedList.Count > 0)
        {
            var listView = myListView;
            listView.SelectedItem = listView.Items.GetItemAt(_mainViewModel.DisplayedList.Count - 1);
            listView.ScrollIntoView(listView.Items[0]);
            listView.ScrollIntoView(listView.SelectedItem);
            //item.Focus();
        }
    }

乾杯

これに対する1つの回避策は、ListViewのItemsPanelを変更することです。デフォルトのパネルはVirtualizingStackPanelで、ListBoxItemが最初に表示されたときにのみ作成されます。リストにあまり多くのアイテムがない場合、それは問題ではないはずです。

<ListView>
   ...
   <ListView.ItemsPanel>
      <ItemsPanelTemplate>
         <StackPanel/>
      </ItemsPanelTemplate>
   </ListView.ItemsPanel>
</ListView>
3
decasteljau

これを試して

private void OnSelectionChanged(object sender, SelectionChangedEventArgs e)
        {
            ScrollViewer scrollViewer = GetScrollViewer(lstVw) as ScrollViewer;
            scrollViewer.ScrollToHorizontalOffset(dataRowToFocus.RowIndex);
            if (dataRowToFocus.RowIndex < 2)
                lstVw.ScrollIntoView((Entity)lstVw.Items[0]);
            else
                lstVw.ScrollIntoView(e.AddedItems[0]);
        } 

 public static DependencyObject GetScrollViewer(DependencyObject o)
        {
            if (o is ScrollViewer)
            { return o; }

            for (int i = 0; i < VisualTreeHelper.GetChildrenCount(o); i++)
            {
                var child = VisualTreeHelper.GetChild(o, i);

                var result = GetScrollViewer(child);
                if (result == null)
                {
                    continue;
                }
                else
                {
                    return result;
                }
            }
            return null;
        } 

private void Focus()
{
 lstVw.SelectedIndex = dataRowToFocus.RowIndex;
 lstVw.SelectedItem = (Entity)dataRowToFocus.Row;

 ListViewItem lvi = (ListViewItem)lstVw.ItemContainerGenerator.ContainerFromItem(lstVw.SelectedItem);
ContentPresenter contentPresenter = FindVisualChild<ContentPresenter>(lvi);
contentPresenter.Focus();
contentPresenter.BringIntoView();

}
2
Murali.S

その最後のヒントサムをありがとう。ダイアログが開きました。つまり、ダイアログを閉じるたびにグリッドのフォーカスが失われました。私はこれを使用します:

if(currentRow >= 0 && currentRow < lstGrid.Items.Count) {
    lstGrid.SelectedIndex = currentRow;
    lstGrid.ScrollIntoView(lstGrid.SelectedItem);
    if(shouldFocusGrid) {
        ListViewItem item = lstGrid.ItemContainerGenerator.ContainerFromItem(lstGrid.SelectedItem) as ListViewItem;
        item.Focus();
    }
} else if(shouldFocusGrid) {
    lstGrid.Focus();
}
2
Echilon

新しいデータアイテムを作成した後で最後のアイテムを表示してフォーカスしたいだけの場合は、この方法の方が適している可能性があります。 ScrollIntoViewと比較して、ScrollViewerのScrollToEndは私のテストではより信頼性があります。上記のようなListViewのScrollIntoViewメソッドを使用したいくつかのテストで失敗し、理由がわかりません。ただし、ScrollViewerを使用して最後の1つまでスクロールすることはできます。

void FocusLastOne(ListView lsv)
{
   ObservableCollection<object> items= sender as ObservableCollection<object>;

   Decorator d = VisualTreeHelper.GetChild(lsv, 0) as Decorator;
   ScrollViewer v = d.Child as ScrollViewer;
   v.ScrollToEnd();

   lsv.SelectedItem = lsv.Items.GetItemAt(items.Count - 1);
   ListViewItem lvi = lsv.ItemContainerGenerator.ContainerFromIndex(items.Count - 1) as ListViewItem;
   lvi.Focus();
}
2
Welson Zhong

ItemContainerGenerator.ContainerFromItem()とItemContainerGenerator.ContainerFromIndex()で同じ問題が発生し、リストボックスに明確に存在するアイテムに対してnullが返されました。 Decasteljauは正しかったが、彼が何を意味するのかを正確に理解するために、私はいくつかの掘り下げをしなければならなかった。これが次の男/ギャルを救うための内訳です。

簡単に言うと、ListBoxItemsは、表示されていない場合は破棄されます。その結果、ListBoxItemsが存在しないため、ContainerFromItem()およびContainerFromIndex()はnullを返します。これは明らかに、ここで詳しく説明されているメモリ/パフォーマンスの節約機能です: http://blogs.msdn.com/b/oren/archive/2010/11/08/wp7-silverlight-perf-demo-1-virtualizingstackpanel- vs-stackpanel-as-a-listbox-itemspanel.aspx

空の<ListBox.ItemsPanel>コードは、仮想化をオフにするものです。私の問題を修正したサンプルコード:

データテンプレート:

<phone:PhoneApplicationPage.Resources>
    <DataTemplate x:Key="StoryViewModelTemplate">
        <StackPanel>
          <your datatemplated stuff here/>
        </StackPanel>
    </DataTemplate>
</phone:PhoneApplicationPage.Resources>

本体:

<Grid x:Name="ContentPanel">
    <ListBox Name="lbResults" ItemsSource="{Binding SearchResults}" ItemTemplate="{StaticResource StoryViewModelTemplate}">
        <ListBox.ItemsPanel>
             <ItemsPanelTemplate>
                 <StackPanel>
                 </StackPanel>
             </ItemsPanelTemplate>
        </ListBox.ItemsPanel>
    </ListBox>
</Grid>
1
Ray Ackley

仮想化の問題を克服するために、ScrollIntoViewを使用し、ListViewの内臓をハッキングしないようにするには、ViewModelオブジェクトを使用して何が選択されているかを判断することもできます。 IsSelectedプロパティを特徴とするViewModelオブジェクトがリストにあると仮定します。次のように、アイテムをXAMLのListViewにリンクします。

<ListView Name="PersonsListView" ItemsSource="{Binding PersonVMs}">
  <ListView.ItemContainerStyle>
    <Style TargetType="{x:Type ListViewItem}">
      <Setter Property="IsSelected" Value="{Binding IsSelected, Mode=TwoWay}" />
    </Style>
  </ListView.ItemContainerStyle>
</ListView>

次に、コードビハインドメソッドは次のようにして最初に選択されたアイテムにスクロールできます。

var firstSelected = PersonsListView.Items
    .OfType<TreeViewItemViewModel>().FirstOrDefault(x => x.IsSelected);
if (firstSelected != null)
    CoObjectsListView.ScrollIntoView(firstSelected);

これは、選択したアイテムが十分に見えない場合にも機能します。私の実験では、PersonsListView.SelectedItemプロパティはnullでしたが、もちろん、ViewModel IsSelectedプロパティは常に存在します。すべてのバインドとロードが完了したら、必ずこのメソッドを呼び出してください(正しいDispatcherPriorityを使用)。

ViewCommand パターンを使用すると、ViewModelコードは次のようになります。

PersonVMs.ForEach(vm => vm.IsSelected = false);
PersonVMs.Add(newPersonVM);
newPersonVM.IsSelected = true;
ViewCommandManager.InvokeLoaded("ScrollToSelectedPerson");
0
ygoe

私のプロジェクトでは、選択したインデックス行をリストビューからユーザーに表示する必要があるため、選択したアイテムをListViewコントロールに割り当てました。このコードはスクロールバーをスクロールし、選択したアイテムを表示します。

BooleanListView.ScrollIntoView(BooleanListView.SelectedItem);

OR

var listView = BooleanListView; listView.SelectedItem = listView.Items.GetItemAt(BooleanListView.SelectedIndex); listView.ScrollIntoView(listView.Items [0]); listView.ScrollIntoView(listView.SelectedItem);

0
Jeyavel

これが進むべき道かどうかはわかりませんが、現在、WPF、MVVM Light、および.NET3.5を使用してこれは機能します

lbPossibleError_SelectionChanged "」というリストボックスのSelectionChangedイベントを追加しました I added the SelectionChanged event for ListBox

次に、この "lbPossibleError_SelectionChanged"イベントの背後に、コードがあります enter image description here

私にとっては正常に機能します。

0
Jayson Ragasa