I've added a ScrollViewer behavior that allows me to scroll to the top when a property is set to true
based on Scroll the scrollviewer to top through viewmodel. I've found that this works perfectly the first time, but subsequent attempts do not fire because the TwoWay
binding isn't setting my property back to false
.
Here is my simple project showing my issue:
MainWindow.xaml
<Window x:Class="WpfApp1.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:local="clr-namespace:WpfApp1"
mc:Ignorable="d"
Title="MainWindow" Height="100" Width="200">
<ScrollViewer local:ScrollViewerBehavior.ScrollToTop="{Binding Reset, Mode=TwoWay, UpdateSourceTrigger=PropertyChanged}">
<Grid>
<Grid.RowDefinitions>
<RowDefinition Height="200"/>
</Grid.RowDefinitions>
<Button VerticalAlignment="Bottom" Content="Top" Command="{Binding Scroll}"/>
</Grid>
</ScrollViewer>
</Window>
MainWindow.xaml.cs
using System.Windows;
namespace WpfApp1
{
/// <summary>
/// Interaction logic for MainWindow.xaml
/// </summary>
public partial class MainWindow : Window
{
private ViewModel _ViewModel;
public MainWindow()
{
InitializeComponent();
DataContext = _ViewModel = new ViewModel();
}
}
}
ViewModel.cs
using System;
using System.ComponentModel;
using System.Windows.Input;
namespace WpfApp1
{
public class RelayCommand : ICommand
{
private readonly Action<object> action;
public RelayCommand(Action<object> action)
{
this.action = action;
}
public event EventHandler CanExecuteChanged;
public bool CanExecute(object parameter)
{
return true;
}
public void Execute(object parameter)
{
action(parameter);
}
}
public class ViewModel : INotifyPropertyChanged
{
private bool _reset = false;
public ViewModel()
{
Scroll = new RelayCommand(o =>
{
Reset = true;
});
}
public event PropertyChangedEventHandler PropertyChanged;
public bool Reset
{
get { return _reset; }
set
{
bool changed = value != _reset;
_reset = value;
if (changed)
{
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(nameof(Reset)));
}
}
}
public RelayCommand Scroll { get; set; }
}
}
ScrollViewerBehavior.cs
using System.Windows;
using System.Windows.Controls;
namespace WpfApp1
{
public static class ScrollViewerBehavior
{
public static readonly DependencyProperty ScrollToTopProperty = DependencyProperty.RegisterAttached("ScrollToTop", typeof(bool), typeof(ScrollViewerBehavior), new FrameworkPropertyMetadata(false, FrameworkPropertyMetadataOptions.BindsTwoWayByDefault, (o, e) =>
{
if (o is ScrollViewer sc)
{
if ((bool)e.NewValue)
{
sc.ScrollToTop();
SetScrollToTop((FrameworkElement)o, false); // this should set the property back to false
}
}
}));
public static bool GetScrollToTop(FrameworkElement o)
{
return (bool)o.GetValue(ScrollToTopProperty);
}
public static void SetScrollToTop(FrameworkElement o, bool value)
{
o.SetValue(ScrollToTopProperty, value);
}
}
}
I know that if I take out the changed check on the property, it works; however, that is not ideal for my situation. When I look at the element through WPF Inspector, I see that the property on the ScrollViewer is false
as it should be, but my ViewModel property remains true
.