質問
WPF、ブラウザのようなアプリ。
ListViewを含む1ページを取得しました。 PageFunctionを呼び出した後、ListViewに行を追加し、新しい行をスクロールして表示します。
ListViewItem item = ItemContainerGenerator.ContainerFromIndex(index) as ListViewItem;
if (item != null)
ScrollIntoView(item);
これは動作します。新しい行が表示されている限り、その行は本来のようにフォーカスを取得します。
問題は、線が見えない場合は機能しないことです。
行が表示されていない場合、生成された行にはListViewItemがないため、ItemContainerGenerator.ContainerFromIndexはnullを返します。
しかし、アイテムなしで、行をスクロールして表示するにはどうすればよいですか? ListViewItemを必要とせずに最後の行(または任意の場所)にスクロールする方法はありますか?
解決
ここでの問題は、行が表示されていない場合、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);
他のヒント
誰かが私に、特定の行にスクロールするさらに良い方法を教えてくれました。これは簡単で、魅力のように機能します。
要するに:
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();
}
サムの答えにいくつか変更を加えました。最後の行までスクロールしたかったことに注意してください。残念ながら、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>
最後のヒントをありがとうサム。ダイアログが開いたため、ダイアログが閉じるたびにグリッドがフォーカスを失いました。私はこれを使用します:
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();
}
これを試してください
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();
}
新しいデータアイテムを作成した後、最後のアイテムを表示してフォーカスしたいだけの場合は、この方法の方が良いでしょう。 ScrollIntoViewと比較して、ScrollViewerのScrollToEndは私のテストではより信頼性が高くなっています。 上記のようなListViewのScrollIntoViewメソッドを使用した一部のテストでは失敗し、理由がわかりません。ただし、ScrollViewerを使用して最後までスクロールすると機能します。
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();
}
ItemContainerGenerator.ContainerFromItem()とItemContainerGenerator.ContainerFromIndex()がリストボックスに明確に存在するアイテムに対してnullを返すのと同じ問題がありました。デカステルジャウは正しかったが、私は彼が何を意味していたのかを正確に理解するために掘り下げなければならなかった。次の男/ギャルの脚を救うための内訳は次のとおりです。
長い話を簡単に言えば、ListBoxItemはビュー内にない場合は破棄されます。その結果、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>
仮想化の問題を克服するが、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 DispatcherPriority
プロパティは常にそこにあります。すべてのバインディングとロードが完了した後(正しい<=>を使用)、このメソッドを呼び出してください。
ViewCommand パターンの使用、ViewModelコードは次のようになります。
PersonVMs.ForEach(vm => vm.IsSelected = false);
PersonVMs.Add(newPersonVM);
newPersonVM.IsSelected = true;
ViewCommandManager.InvokeLoaded("ScrollToSelectedPerson");
プロジェクトでは、選択したインデックス行をリストビューからユーザーに表示する必要があるため、選択したアイテムをリストビューコントロールに割り当てました。このコードは、スクロールバーをスクロールし、選択したアイテムを表示します。
BooleanListView.ScrollIntoView(BooleanListView.SelectedItem);
または
var listView = BooleanListView; listView.SelectedItem = listView.Items.GetItemAt(BooleanListView.SelectedIndex); listView.ScrollIntoView(listView.Items [0]); listView.ScrollIntoView(listView.SelectedItem);
これが進むべきかどうかはわかりませんが、これは現在、WPF、MVVM Light、および.NET 3.5を使用して動作します
<!> quot; lbPossibleError_SelectionChanged <!> quotというListBoxのSelectionChangedイベントを追加しました。
この<!> quot; lbPossibleError_SelectionChanged <!> quot;イベント、ここにコードがあります
私にとってはうまくいく。