자동으로 종료되는 WPF 목록 상자 스크롤
내 어플리케이션에는ListBox
아이템이 포함되어 있습니다.애플리케이션은 WPF로 기술되어 있습니다.
마지막으로 추가한 항목으로 자동 스크롤하려면 어떻게 해야 합니까?나는 그 것을 원한다.ScrollViewer
새 항목이 추가되면 목록 끝으로 이동합니다.
이런 이벤트가 있나요?ItemsChanged
? (사용하고 싶지 않습니다.SelectionChanged
이벤트)
이것을 시험해 보세요.
lstBox.SelectedIndex = lstBox.Items.Count -1;
lstBox.ScrollIntoView(lstBox.SelectedItem) ;
Main Window에서 목록의 마지막 항목을 선택하고 포커스를 맞춥니다!
가장 쉬운 방법은 다음과 같습니다.
if (VisualTreeHelper.GetChildrenCount(listView) > 0)
{
Border border = (Border)VisualTreeHelper.GetChild(listView, 0);
ScrollViewer scrollViewer = (ScrollViewer)VisualTreeHelper.GetChild(border, 0);
scrollViewer.ScrollToBottom();
}
ListView 및 ListBox 컨트롤에서는 항상 작동합니다.이 코드를 에 부가합니다.listView.Items.SourceCollection.CollectionChanged
이벤트 및 완전 자동 자동 검출 동작이 있습니다.
주의해 주세요listBox.ScrollIntoView(listBox.Items[listBox.Items.Count - 1]);
는 중복 항목이 없는 경우에만 작동합니다.같은 내용의 아이템이 있으면 첫 번째 검색까지 스크롤됩니다.
다음은 제가 찾은 해결책입니다.
ListBoxAutomationPeer svAutomation =
ListBoxAutomationPeer)ScrollViewerAutomationPeer.
CreatePeerForElement(myListBox);
IScrollProvider scrollInterface =
(IScrollProvider)svAutomation.GetPattern(PatternInterface.Scroll);
System.Windows.Automation.ScrollAmount scrollVertical =
System.Windows.Automation.ScrollAmount.LargeIncrement;
System.Windows.Automation.ScrollAmount scrollHorizontal =
System.Windows.Automation.ScrollAmount.NoAmount;
// If the vertical scroller is not available,
// the operation cannot be performed, which will raise an exception.
if (scrollInterface.VerticallyScrollable)
scrollInterface.Scroll(scrollHorizontal, scrollVertical);
가장 좋은 해결책은 ListBox 컨트롤 내에서 ItemCollection 개체를 사용하는 것입니다. 이 컬렉션은 콘텐츠 뷰어를 위해 특별히 설계되었습니다.마지막 항목을 선택하고 커서 위치 참조를 유지하는 사전 정의된 방법이 있습니다.
myListBox.Items.MoveCurrentToLast();
myListBox.ScrollIntoView(myListBox.Items.CurrentItem);
지금까지 제시된 접근법과는 약간 다르다.
를 사용할 수 있습니다.ScrollViewer
ScrollChanged
이벤트와 감시 내용ScrollViewer
점점 커지고 있습니다.
private void ListBox_OnLoaded(object sender, RoutedEventArgs e)
{
var listBox = (ListBox) sender;
var scrollViewer = FindScrollViewer(listBox);
if (scrollViewer != null)
{
scrollViewer.ScrollChanged += (o, args) =>
{
if (args.ExtentHeightChange > 0)
scrollViewer.ScrollToBottom();
};
}
}
이것에 의해, 에의 바인딩에 관한 몇개의 문제가 회피됩니다.ListBox
ItemsSource
변화하는.
그ScrollViewer
또, 그 전제를 하지 않아도 알 수 있다.ListBox
는 기본 제어 템플릿을 사용하고 있습니다.
// Search for ScrollViewer, breadth-first
private static ScrollViewer FindScrollViewer(DependencyObject root)
{
var queue = new Queue<DependencyObject>(new[] {root});
do
{
var item = queue.Dequeue();
if (item is ScrollViewer)
return (ScrollViewer) item;
for (var i = 0; i < VisualTreeHelper.GetChildrenCount(item); i++)
queue.Enqueue(VisualTreeHelper.GetChild(item, i));
} while (queue.Count > 0);
return null;
}
그런 다음 이것을ListBox
Loaded
이벤트:
<ListBox Loaded="ListBox_OnLoaded" />
이것은 첨부 속성으로 쉽게 수정할 수 있으며, 이를 보다 일반적인 용도로 사용할 수 있습니다.
또는 야릭의 제안:
<ListBox ScrollViewer.ScrollChanged="ScrollViewer_OnScrollChanged" />
그리고 뒤에 있는 코드:
private void ScrollViewer_OnScrollChanged(object sender, ScrollChangedEventArgs e)
{
if (e.OriginalSource is ScrollViewer scrollViewer &&
Math.Abs(e.ExtentHeightChange) > 0.0)
{
scrollViewer.ScrollToBottom();
}
}
listBox.ScrollIntoView(listBox).아이템[리스트 박스]Items. Count - 1 ] ;
여기 있는 어떤 대답도 내가 원하는 것을 해 주지 않았다.그래서 아이템 컨트롤을 자동으로 스크롤하고 사용자가 위로 스크롤하면 자동 스크롤을 일시 중지하고 사용자가 아래로 스크롤하면 자동 스크롤을 재개하는 나만의 동작을 썼습니다.
/// <summary>
/// This will auto scroll a list view to the bottom as items are added.
/// Automatically suspends if the user scrolls up, and recommences when
/// the user scrolls to the end.
/// </summary>
/// <example>
/// <ListView sf:AutoScrollToBottomBehavior="{Binding viewModelAutoScrollFlag}" />
/// </example>
public class AutoScrollToBottomBehavior
{
/// <summary>
/// Enumerated type to keep track of the current auto scroll status
/// </summary>
public enum StatusType
{
NotAutoScrollingToBottom,
AutoScrollingToBottom,
AutoScrollingToBottomButSuppressed
}
public static StatusType GetAutoScrollToBottomStatus(DependencyObject obj)
{
return (StatusType)obj.GetValue(AutoScrollToBottomStatusProperty);
}
public static void SetAutoScrollToBottomStatus(DependencyObject obj, StatusType value)
{
obj.SetValue(AutoScrollToBottomStatusProperty, value);
}
// Using a DependencyProperty as the backing store for AutoScrollToBottomStatus. This enables animation, styling, binding, etc...
public static readonly DependencyProperty AutoScrollToBottomStatusProperty =
DependencyProperty.RegisterAttached(
"AutoScrollToBottomStatus",
typeof(StatusType),
typeof(AutoScrollToBottomBehavior),
new PropertyMetadata(StatusType.NotAutoScrollingToBottom, (s, e) =>
{
if (s is DependencyObject viewer && e.NewValue is StatusType autoScrollToBottomStatus)
{
// Set the AutoScrollToBottom property to mirror this one
bool? autoScrollToBottom = autoScrollToBottomStatus switch
{
StatusType.AutoScrollingToBottom => true,
StatusType.NotAutoScrollingToBottom => false,
StatusType.AutoScrollingToBottomButSuppressed => false,
_ => null
};
if (autoScrollToBottom.HasValue)
{
SetAutoScrollToBottom(viewer, autoScrollToBottom.Value);
}
// Only hook/unhook for cases below, not when suspended
switch(autoScrollToBottomStatus)
{
case StatusType.AutoScrollingToBottom:
HookViewer(viewer);
break;
case StatusType.NotAutoScrollingToBottom:
UnhookViewer(viewer);
break;
}
}
}));
public static bool GetAutoScrollToBottom(DependencyObject obj)
{
return (bool)obj.GetValue(AutoScrollToBottomProperty);
}
public static void SetAutoScrollToBottom(DependencyObject obj, bool value)
{
obj.SetValue(AutoScrollToBottomProperty, value);
}
// Using a DependencyProperty as the backing store for AutoScrollToBottom. This enables animation, styling, binding, etc...
public static readonly DependencyProperty AutoScrollToBottomProperty =
DependencyProperty.RegisterAttached(
"AutoScrollToBottom",
typeof(bool),
typeof(AutoScrollToBottomBehavior),
new FrameworkPropertyMetadata(false, FrameworkPropertyMetadataOptions.BindsTwoWayByDefault, (s, e) =>
{
if (s is DependencyObject viewer && e.NewValue is bool autoScrollToBottom)
{
// Set the AutoScrollToBottomStatus property to mirror this one
if (autoScrollToBottom)
{
SetAutoScrollToBottomStatus(viewer, StatusType.AutoScrollingToBottom);
}
else if (GetAutoScrollToBottomStatus(viewer) == StatusType.AutoScrollingToBottom)
{
SetAutoScrollToBottomStatus(viewer, StatusType.NotAutoScrollingToBottom);
}
// No change if autoScrollToBottom = false && viewer.AutoScrollToBottomStatus = AutoScrollToBottomStatusType.AutoScrollingToBottomButSuppressed;
}
}));
private static Action GetUnhookAction(DependencyObject obj)
{
return (Action)obj.GetValue(UnhookActionProperty);
}
private static void SetUnhookAction(DependencyObject obj, Action value)
{
obj.SetValue(UnhookActionProperty, value);
}
// Using a DependencyProperty as the backing store for MyProperty. This enables animation, styling, binding, etc...
private static readonly DependencyProperty UnhookActionProperty =
DependencyProperty.RegisterAttached("UnhookAction", typeof(Action), typeof(AutoScrollToBottomBehavior), new PropertyMetadata(null));
private static void ItemsControl_Loaded(object sender, RoutedEventArgs e)
{
if (sender is ItemsControl itemsControl)
{
itemsControl.Loaded -= ItemsControl_Loaded;
HookViewer(itemsControl);
}
}
private static void HookViewer(DependencyObject viewer)
{
if (viewer is ItemsControl itemsControl)
{
// If this is triggered the xaml setup then the control won't be loaded yet,
// and so won't have a visual tree which we need to get the scrollviewer,
// so defer this hooking until the items control is loaded.
if (!itemsControl.IsLoaded)
{
itemsControl.Loaded += ItemsControl_Loaded;
return;
}
if (FindScrollViewer(viewer) is ScrollViewer scrollViewer)
{
scrollViewer.ScrollToBottom();
// Scroll to bottom when the item count changes
NotifyCollectionChangedEventHandler itemsCollectionChangedHandler = (s, e) =>
{
if (GetAutoScrollToBottom(viewer))
{
scrollViewer.ScrollToBottom();
}
};
((INotifyCollectionChanged)itemsControl.Items).CollectionChanged += itemsCollectionChangedHandler;
ScrollChangedEventHandler scrollChangedEventHandler = (s, e) =>
{
bool userScrolledToBottom = (e.VerticalOffset + e.ViewportHeight) > (e.ExtentHeight - 1.0);
bool userScrolledUp = e.VerticalChange < 0;
// Check if auto scrolling should be suppressed
if (userScrolledUp && !userScrolledToBottom)
{
if (GetAutoScrollToBottomStatus(viewer) == StatusType.AutoScrollingToBottom)
{
SetAutoScrollToBottomStatus(viewer, StatusType.AutoScrollingToBottomButSuppressed);
}
}
// Check if auto scrolling should be unsuppressed
if (userScrolledToBottom)
{
if (GetAutoScrollToBottomStatus(viewer) == StatusType.AutoScrollingToBottomButSuppressed)
{
SetAutoScrollToBottomStatus(viewer, StatusType.AutoScrollingToBottom);
}
}
};
scrollViewer.ScrollChanged += scrollChangedEventHandler;
Action unhookAction = () =>
{
((INotifyCollectionChanged)itemsControl.Items).CollectionChanged -= itemsCollectionChangedHandler;
scrollViewer.ScrollChanged -= scrollChangedEventHandler;
};
SetUnhookAction(viewer, unhookAction);
}
}
}
/// <summary>
/// Unsubscribes the event listeners on the ItemsControl and ScrollViewer
/// </summary>
/// <param name="viewer"></param>
private static void UnhookViewer(DependencyObject viewer)
{
var unhookAction = GetUnhookAction(viewer);
SetUnhookAction(viewer, null);
unhookAction?.Invoke();
}
/// <summary>
/// A recursive function that drills down a visual tree until a ScrollViewer is found.
/// </summary>
/// <param name="viewer"></param>
/// <returns></returns>
private static ScrollViewer FindScrollViewer(DependencyObject viewer)
{
if (viewer is ScrollViewer scrollViewer)
return scrollViewer;
return Enumerable.Range(0, VisualTreeHelper.GetChildrenCount(viewer))
.Select(i => FindScrollViewer(VisualTreeHelper.GetChild(viewer, i)))
.Where(child => child != null)
.FirstOrDefault();
}
}
저에게 가장 간단한 작업 방법은 다음과 같습니다. (바인딩 없음)
private void WriteMessage(string message, Brush color, ListView lv)
{
Dispatcher.BeginInvoke(new Action(delegate
{
ListViewItem ls = new ListViewItem
{
Foreground = color,
Content = message
};
lv.Items.Add(ls);
lv.ScrollIntoView(lv.Items[lv.Items.Count - 1]);
}));
}
클래스를 만들거나 xaml을 변경할 필요가 없습니다.이 방법으로 메시지를 작성하기만 하면 자동으로 스크롤됩니다.
그냥 전화하는 거야
myLv.Items.Add(ls);
myLv.ScrollIntoView(lv.Items[lv.Items.Count - 1]);
예를 들면, 날 위해 일하지 마.
자동 롤링을 실현하는 가장 쉬운 방법은 Collection Changed 이벤트에 후크하는 것입니다.ListBox 컨트롤에서 파생된 커스텀클래스에 이 기능을 추가합니다.
using System.Collections.Specialized;
using System.Windows.Controls;
using System.Windows.Media;
namespace YourProgram.CustomControls
{
public class AutoScrollListBox : ListBox
{
public AutoScrollListBox()
{
if (Items != null)
{
// Hook to the CollectionChanged event of your ObservableCollection
((INotifyCollectionChanged)Items).CollectionChanged += CollectionChange;
}
}
// Is called whenever the item collection changes
private void CollectionChange(object sender, NotifyCollectionChangedEventArgs e)
{
if (Items.Count > 0)
{
// Get the ScrollViewer object from the ListBox control
Border border = (Border)VisualTreeHelper.GetChild(this, 0);
ScrollViewer SV = (ScrollViewer)VisualTreeHelper.GetChild(border, 0);
// Scroll to bottom
SV.ScrollToBottom();
}
}
}
}
커스텀 컨트롤의 네임스페이스를 WPF 창에 추가하고 커스텀 ListBox 컨트롤을 사용합니다.
<Window x:Class="MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:local="clr-namespace:YourProgram"
xmlns:cc="clr-namespace:YourProgram.CustomControls"
mc:Ignorable="d"
d:DesignHeight="450" d:DesignWidth="800">
<cc:AutoScrollListBox ItemsSource="{Binding YourObservableCollection}"/>
</Window>
ListBox를 사용해 보세요.ScrollIntoView() 메서드입니다만, 경우에 따라서는 문제가 있습니다.
다음은 Tamir Khason의 예입니다.WPF의 ListBox 자동 스크롤
이것은 나에게 100% 효과가 있었던 방법이다.
초기화 부분:
private ObservableCollection<ActionLogData> LogListBind = new ObservableCollection<ActionLogData>();
LogList.ItemsSource = LogListBind;
LogListBind.CollectionChanged += this.OnCollectionChanged;
ListView의 항목 소스로 사용되는 My ObservableCollection의 CollectionChanged에 바인딩된 위임자:
private void OnCollectionChanged(object sender, NotifyCollectionChangedEventArgs e)
{
if (VisualTreeHelper.GetChildrenCount(LogList) > 0)
{
Decorator border = VisualTreeHelper.GetChild(LogList, 0) as Decorator;
ScrollViewer scrollViewer = border.Child as ScrollViewer;
scrollViewer.ScrollToBottom();
}
}
이 솔루션은 @mateusz-my-lak 솔루션을 기반으로 합니다만, 몇개의 수정과 심플화를 실시했습니다.
.NET 5에서 이 답변과 모두의 답변을 조합하여 생각해낸 가장 깔끔한 방법은 다음과 같습니다.
View 생성자에서 이벤트를 구독합니다(뒤 코드).
var listViewItemsSource = (INotifyCollectionChanged)MyListView.Items.SourceCollection;
listViewItemsSource.CollectionChanged += MyListViewCollectionChanged;
★★★★★★★★★★★★★★★★★★★★★★★★에MyListViewCollectionChanged
「」, 「」를 합니다.ScrollViewer
.
private void MyListViewCollectionChanged(object? sender, NotifyCollectionChangedEventArgs e)
{
var border = (Decorator)VisualTreeHelper.GetChild(LoggerListView, 0);
var scrollViewer = (ScrollViewer)border.Child;
scrollViewer.ScrollToEnd();
}
메모: 구성 요소가 초기화되지 않았기 때문에 생성자에서 스크롤 뷰어를 가져올 수 없습니다.
마지막으로 추가된 항목이 목록의 마지막 항목인 경우에만 맨 아래로 스크롤하십시오.
if (lstBox.SelectedIndex == lstBox.Items.Count - 1)
{
// Scroll to bottom
lstBox.ScrollIntoView(lstBox.SelectedItem);
}
언급URL : https://stackoverflow.com/questions/2337822/wpf-listbox-scroll-to-end-automatically
'prosource' 카테고리의 다른 글
두 리비전 간에 변경된 파일 표시 (0) | 2023.04.13 |
---|---|
Git이 자꾸 내 ssh 키 패스프레이즈를 묻는다. (0) | 2023.04.13 |
WPF의 커스텀커서? (0) | 2023.04.13 |
VBA에서 문자열을 연결하려면 어떻게 해야 하나요? (0) | 2023.04.13 |
LINQ의 .Skip(1000)을 쓰는 방법순수 SQL로 취득(100)하시겠습니까? (0) | 2023.04.13 |