私はスレッドを通過しました:
2つのVerticalScrollBarを互いにバインドする
それはほとんど目標を達成するのに役立ちましたが、それでも何かが欠けています。スクロールバーを左右または上下に動かすと、両方のスクロールビューアーで期待されるスクロール動作が得られますが、スクロールビューアーでこれらのスクロールバーの端にある矢印ボタンを使用してスクロールしようとすると、スクロールされないスクロールビューアーは1つだけです。期待される動作。
では、これを解決するために他に何を追加/編集する必要がありますか?
これを行う1つの方法は、ScrollChanged
イベントを使用して他のScrollViewer
を更新することです。
<ScrollViewer Name="sv1" Height="100"
HorizontalScrollBarVisibility="Auto"
ScrollChanged="ScrollChanged">
<Grid Height="1000" Width="1000" Background="Green" />
</ScrollViewer>
<ScrollViewer Name="sv2" Height="100"
HorizontalScrollBarVisibility="Auto"
ScrollChanged="ScrollChanged">
<Grid Height="1000" Width="1000" Background="Blue" />
</ScrollViewer>
private void ScrollChanged(object sender, ScrollChangedEventArgs e)
{
if (sender == sv1)
{
sv2.ScrollToVerticalOffset(e.VerticalOffset);
sv2.ScrollToHorizontalOffset(e.HorizontalOffset);
}
else
{
sv1.ScrollToVerticalOffset(e.VerticalOffset);
sv1.ScrollToHorizontalOffset(e.HorizontalOffset);
}
}
問題はWPFに関するものですが、UWPを開発している人がこれに遭遇した場合に備えて、少し異なるアプローチを取る必要がありました。
UWPでは、他のスクロールビューアのスクロールオフセットを設定すると( ScrollViewer.ChangeView を使用)、他のスクロールで ViewChanged イベントもトリガーされますビューア。基本的にループを作成しているため、非常に途切れ途切れになり、正しく機能しません。
スクロールされているオブジェクトがイベントを処理した最後のオブジェクトと等しくない場合は、イベントの処理で少しタイムアウトを使用することでこれを解決しました。
XAML:
<ScrollViewer x:Name="ScrollViewer1" ViewChanged="SynchronizedScrollerOnViewChanged"> ... </ScrollViewer>
<ScrollViewer x:Name="ScrollViewer2" ViewChanged="SynchronizedScrollerOnViewChanged"> ... </ScrollViewer>
背後にあるコード:
public sealed partial class MainPage
{
private const int ScrollLoopbackTimeout = 500;
private object _lastScrollingElement;
private int _lastScrollChange = Environment.TickCount;
public SongMixerUserControl()
{
InitializeComponent();
}
private void SynchronizedScrollerOnViewChanged(object sender, ScrollViewerViewChangedEventArgs e)
{
if (_lastScrollingElement != sender && Environment.TickCount - _lastScrollChange < ScrollLoopbackTimeout) return;
_lastScrollingElement = sender;
_lastScrollChange = Environment.TickCount;
ScrollViewer sourceScrollViewer;
ScrollViewer targetScrollViewer;
if (sender == ScrollViewer1)
{
sourceScrollViewer = ScrollViewer1;
targetScrollViewer = ScrollViewer2;
}
else
{
sourceScrollViewer = ScrollViewer2;
targetScrollViewer = ScrollViewer1;
}
targetScrollViewer.ChangeView(null, sourceScrollViewer.VerticalOffset, null);
}
}
タイムアウトは500msであることに注意してください。これは少し長いように思えるかもしれませんが、UWPアプリのスクロール(または実際にはイージング)にアニメーション(または実際にはイージング)があるため(マウスでスクロールホイールを使用する場合)、イベントが数百ミリ秒以内に数回トリガーされます。このタイムアウトは完全に機能しているようです。
役立つ場合は、次の動作があります(UWPの場合ですが、アイデアを得るには十分です)。ビヘイビアーを使用すると、MVVMデザインのビューとコードを分離するのに役立ちます。
using Microsoft.Xaml.Interactivity;
using Windows.UI.Xaml;
using Windows.UI.Xaml.Controls;
public class SynchronizeHorizontalOffsetBehavior : Behavior<ScrollViewer>
{
public static ScrollViewer GetSource(DependencyObject obj)
{
return (ScrollViewer)obj.GetValue(SourceProperty);
}
public static void SetSource(DependencyObject obj, ScrollViewer value)
{
obj.SetValue(SourceProperty, value);
}
// Using a DependencyProperty as the backing store for Source. This enables animation, styling, binding, etc...
public static readonly DependencyProperty SourceProperty =
DependencyProperty.RegisterAttached("Source", typeof(object), typeof(SynchronizeHorizontalOffsetBehavior), new PropertyMetadata(null, SourceChangedCallBack));
private static void SourceChangedCallBack(DependencyObject d, DependencyPropertyChangedEventArgs e)
{
SynchronizeHorizontalOffsetBehavior synchronizeHorizontalOffsetBehavior = d as SynchronizeHorizontalOffsetBehavior;
if (synchronizeHorizontalOffsetBehavior != null)
{
var oldSourceScrollViewer = e.OldValue as ScrollViewer;
var newSourceScrollViewer = e.NewValue as ScrollViewer;
if (oldSourceScrollViewer != null)
{
oldSourceScrollViewer.ViewChanged -= synchronizeHorizontalOffsetBehavior.SourceScrollViewer_ViewChanged;
}
if (newSourceScrollViewer != null)
{
newSourceScrollViewer.ViewChanged += synchronizeHorizontalOffsetBehavior.SourceScrollViewer_ViewChanged;
synchronizeHorizontalOffsetBehavior.UpdateTargetViewAccordingToSource(newSourceScrollViewer);
}
}
}
private void SourceScrollViewer_ViewChanged(object sender, ScrollViewerViewChangedEventArgs e)
{
ScrollViewer sourceScrollViewer = sender as ScrollViewer;
this.UpdateTargetViewAccordingToSource(sourceScrollViewer);
}
private void UpdateTargetViewAccordingToSource(ScrollViewer sourceScrollViewer)
{
if (sourceScrollViewer != null)
{
if (this.AssociatedObject != null)
{
this.AssociatedObject.ChangeView(sourceScrollViewer.HorizontalOffset, null, null);
}
}
}
protected override void OnAttached()
{
base.OnAttached();
var source = GetSource(this.AssociatedObject);
this.UpdateTargetViewAccordingToSource(source);
}
}
使用方法は次のとおりです。
<ScrollViewer
HorizontalScrollMode="Enabled"
HorizontalScrollBarVisibility="Hidden"
>
<interactivity:Interaction.Behaviors>
<behaviors:SynchronizeHorizontalOffsetBehavior Source="{Binding ElementName=ScrollViewer}" />
</interactivity:Interaction.Behaviors>
</ScrollViewer>
<ScrollViewer x:Name="ScrollViewer" />
さて、私は https://www.codeproject.com/Articles/39244/Scroll-Synchronization に基づいて実装を行いましたが、それは私がよりきれいだと思います。
スクロールするものへの参照を保持する同期スクロールトークンがあります。次に、別の添付プロパティがあります。参照が残っているため、登録を解除する方法がわかりません。そのため、未実装のままにしました。
ええ、ここに行きます:
public class SynchronisedScroll
{
public static SynchronisedScrollToken GetToken(ScrollViewer obj)
{
return (SynchronisedScrollToken)obj.GetValue(TokenProperty);
}
public static void SetToken(ScrollViewer obj, SynchronisedScrollToken value)
{
obj.SetValue(TokenProperty, value);
}
public static readonly DependencyProperty TokenProperty =
DependencyProperty.RegisterAttached("Token", typeof(SynchronisedScrollToken), typeof(SynchronisedScroll), new PropertyMetadata(TokenChanged));
private static void TokenChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
{
var scroll = d as ScrollViewer;
var oldToken = e.OldValue as SynchronisedScrollToken;
var newToken = e.NewValue as SynchronisedScrollToken;
if (scroll != null)
{
oldToken?.unregister(scroll);
newToken?.register(scroll);
}
}
}
と他のビット
public class SynchronisedScrollToken
{
List<ScrollViewer> registeredScrolls = new List<ScrollViewer>();
internal void unregister(ScrollViewer scroll)
{
throw new NotImplementedException();
}
internal void register(ScrollViewer scroll)
{
scroll.ScrollChanged += ScrollChanged;
registeredScrolls.Add(scroll);
}
private void ScrollChanged(object sender, ScrollChangedEventArgs e)
{
var sendingScroll = sender as ScrollViewer;
foreach (var potentialScroll in registeredScrolls)
{
if (potentialScroll == sendingScroll)
continue;
if (potentialScroll.VerticalOffset != sendingScroll.VerticalOffset)
potentialScroll.ScrollToVerticalOffset(sendingScroll.VerticalOffset);
if (potentialScroll.HorizontalOffset != sendingScroll.HorizontalOffset)
potentialScroll.ScrollToHorizontalOffset(sendingScroll.HorizontalOffset);
}
}
}
スクロール同期する必要があるすべてのものにアクセス可能なリソースでトークンを定義することによって使用します。
<blah:SynchronisedScrollToken x:Key="scrollToken" />
そして、必要な場所で次の方法で使用します。
<ListView.Resources>
<Style TargetType="ScrollViewer">
<Setter Property="blah:SynchronisedScroll.Token"
Value="{StaticResource scrollToken}" />
</Style>
</ListView.Resources>
垂直方向にスクロールしたときにのみテストしましたが、うまくいきました。
UWP用のC#でのRene Sackersコードリストのフォローアップで、ビューが変更されたために1つのScroll Viewerオブジェクトがイベントを起動することによる驚異的な影響を回避するために、タイムアウトを使用してVB.Net forUWPでこの同じ問題に対処した方法を次に示します。コードであり、ユーザーの操作によるものではありません。私のアプリケーションに適した500ミリ秒のタイムアウト期間を設定しました。
注:svLvMainはscrollviewerです(私にとってはメインウィンドウです)svLVMainHeaderはscrollviewerです(私にとってはメインウィンドウの上にあるヘッダーであり、メインウィンドウと一緒に追跡したいものです。その逆も同様です)。いずれかのscrollviewerをズームまたはスクロールすると、両方のscrollviewerの同期が維持されます。
Private Enum ScrollViewTrackingMasterSv
Header = 1
ListView = 2
None = 0
End Enum
Private ScrollViewTrackingMaster As ScrollViewTrackingMasterSv
Private DispatchTimerForSvTracking As DispatcherTimer
Private Sub DispatchTimerForSvTrackingSub(sender As Object, e As Object)
ScrollViewTrackingMaster = ScrollViewTrackingMasterSv.None
DispatchTimerForSvTracking.Stop()
End Sub
Private Sub svLvTracking(sender As Object, e As ScrollViewerViewChangedEventArgs, ByRef inMastScrollViewer As ScrollViewer)
Dim tempHorOffset As Double
Dim tempVerOffset As Double
Dim tempZoomFactor As Single
Dim tempSvMaster As New ScrollViewer
Dim tempSvSlave As New ScrollViewer
Select Case inMastScrollViewer.Name
Case svLvMainHeader.Name
Select Case ScrollViewTrackingMaster
Case ScrollViewTrackingMasterSv.Header
tempSvMaster = svLvMainHeader
tempSvSlave = svLvMain
tempHorOffset = tempSvMaster.HorizontalOffset
tempVerOffset = tempSvMaster.VerticalOffset
tempZoomFactor = tempSvMaster.ZoomFactor
tempSvSlave.ChangeView(tempHorOffset, tempVerOffset, tempZoomFactor)
If DispatchTimerForSvTracking.IsEnabled Then
DispatchTimerForSvTracking.Stop()
DispatchTimerForSvTracking.Start()
End If
Case ScrollViewTrackingMasterSv.ListView
Case ScrollViewTrackingMasterSv.None
tempSvMaster = svLvMainHeader
tempSvSlave = svLvMain
ScrollViewTrackingMaster = ScrollViewTrackingMasterSv.Header
DispatchTimerForSvTracking = New DispatcherTimer()
AddHandler DispatchTimerForSvTracking.Tick, AddressOf DispatchTimerForSvTrackingSub
DispatchTimerForSvTracking.Interval = New TimeSpan(0, 0, 0, 0, 500)
DispatchTimerForSvTracking.Start()
tempHorOffset = tempSvMaster.HorizontalOffset
tempVerOffset = tempSvMaster.VerticalOffset
tempZoomFactor = tempSvMaster.ZoomFactor
tempSvSlave.ChangeView(tempHorOffset, tempVerOffset, tempZoomFactor)
End Select
Case svLvMain.Name
Select Case ScrollViewTrackingMaster
Case ScrollViewTrackingMasterSv.Header
Case ScrollViewTrackingMasterSv.ListView
tempSvMaster = svLvMain
tempSvSlave = svLvMainHeader
tempHorOffset = tempSvMaster.HorizontalOffset
tempVerOffset = tempSvMaster.VerticalOffset
tempZoomFactor = tempSvMaster.ZoomFactor
tempSvSlave.ChangeView(tempHorOffset, tempVerOffset, tempZoomFactor)
If DispatchTimerForSvTracking.IsEnabled Then
DispatchTimerForSvTracking.Stop()
DispatchTimerForSvTracking.Start()
End If
Case ScrollViewTrackingMasterSv.None
tempSvMaster = svLvMain
tempSvSlave = svLvMainHeader
ScrollViewTrackingMaster = ScrollViewTrackingMasterSv.ListView
DispatchTimerForSvTracking = New DispatcherTimer()
AddHandler DispatchTimerForSvTracking.Tick, AddressOf DispatchTimerForSvTrackingSub
DispatchTimerForSvTracking.Interval = New TimeSpan(0, 0, 0, 0, 500)
DispatchTimerForSvTracking.Start()
tempHorOffset = tempSvMaster.HorizontalOffset
tempVerOffset = tempSvMaster.VerticalOffset
tempZoomFactor = tempSvMaster.ZoomFactor
tempSvSlave.ChangeView(tempHorOffset, tempVerOffset, tempZoomFactor)
End Select
Case Else
Exit Sub
End Select
End Sub
Private Sub svLvMainHeader_ViewChanged(sender As Object, e As ScrollViewerViewChangedEventArgs) Handles svLvMainHeader.ViewChanged
Call svLvTracking(sender, e, svLvMainHeader)
End Sub
Private Sub svLvMain_ViewChanged(sender As Object, e As ScrollViewerViewChangedEventArgs) Handles svLvMain.ViewChanged
Call svLvTracking(sender, e, svLvMain)
End Sub