Прикрепленное свойство не обновляет значение, к которому оно привязано (MVVM)

#c# #wpf #user-interface #mvvm

Вопрос:

У меня есть ScrollViewer, заполненный зелеными прямоугольниками, и мне нужно сообщать о его значении прокрутки (или процентах) в текстовом блоке при каждом его изменении. Мне также нужно иметь возможность изменять значение прокрутки, изменяя свойство, к которому она привязана.

Я создал вложенное свойство ScrollViewerBehavior , которое дает мне нужное значение, и я привязываю его к свойству моей viewmodel ( ScrollValue ), однако значение не обновляется, так как сеттер никогда не достигается. Модель представления правильно связана с представлением.

Вот мой код:
Вид

 <Grid>
    <Grid.RowDefinitions>
        <RowDefinition Height="6*" />
        <RowDefinition Height="*" />
    </Grid.RowDefinitions>

    <ScrollViewer Grid.Row="0" Background="Gray" local:ScrollViewerBehavior.ScrollState="{Binding ScrollValue, Mode=TwoWay}" >
        <StackPanel>
            <Rectangle Width="50" Height="50" Fill="Green" Margin="5" />
            <Rectangle Width="50" Height="50" Fill="Green" Margin="5" />
            <Rectangle Width="50" Height="50" Fill="Green" Margin="5" />
            <Rectangle Width="50" Height="50" Fill="Green" Margin="5" />
            <Rectangle Width="50" Height="50" Fill="Green" Margin="5" />
        </StackPanel>
    </ScrollViewer>

    <TextBlock Grid.Row="1" Text="{Binding ScrollValue}" />
</Grid>
 

Модель представления

 public class MainViewModel : BaseViewModel
{
    private double _scrollValue = 0;
    public double ScrollValue
    {
        get { return _scrollValue; }
        set
        {
            // This code is never reached 
            _scrollValue = value;
            OnPropertyChanged("ScrollValue");
        }
    }
}
 

ScrollViewerBehavior

     public static double GetScrollState(DependencyObject obj)
    {
        return (double)obj.GetValue(ScrollStateProperty);
    }

    public static void SetScrollState(DependencyObject obj, double value)
    {
        obj.SetValue(ScrollStateProperty, value);
    }

    public static readonly DependencyProperty ScrollStateProperty =
        DependencyProperty.RegisterAttached("ScrollState", typeof(double), typeof(ScrollViewerBehavior), new PropertyMetadata(0.5, (o, e) =>
        {
            var scrollViewer = o as ScrollViewer;
            
            if (scrollViewer == null)
            {
                return;
            }
            else
            {
                double newVO = (double)e.NewValue * scrollViewer.ScrollableHeight;
                scrollViewer.ScrollToVerticalOffset(newVO);
                SetScrollState(o, newVO);
            }
        }));
 

Я не вижу, что я пропустил

Комментарии:

1. не вдаваясь в подробности: у вас где-то установлен текст данных?

2. Да, он задается непосредственно в конструкторе представления

3. Как примечание, if (scrollViewer == null) { return; } else { ... } выглядит странно. Так и должно быть if (scrollViewer != null) { ... } . Кроме того, нет смысла вызывать SetScrollState в обработчике PropertyChanged свойства ScrollState. Вы должны устанавливать это свойство при прокрутке средства просмотра прокрутки.

Ответ №1:

Я нигде не вижу в коде, где вы меняете значение ScrollValue . Поскольку ваше прикрепленное свойство связано с этим, оно обновляется только тогда, когда ScrollValue в вашей модели представления обновляется. Если я не неправильно понимаю ваше объяснение, вы хотите обновлять ScrollValue его каждый раз, когда изменяется значение полосы прокрутки. Для этого вы можете подключиться к ScrollChanged событию:

(Я только обновил ваш код, просто покажите, что было нужно)

 public static readonly DependencyProperty ScrollStateProperty =
    DependencyProperty.RegisterAttached("ScrollState", typeof(double), typeof(ScrollViewerBehavior), new PropertyMetadata(0.5, (o, e) =>
        {
            if (o is ScrollViewer scrollViewer)
            {
                scrollViewer.ScrollChanged  = OnScrollChanged;
            }
        }));

    private static void OnScrollChanged(object sender, ScrollChangedEventArgs e)
    {
        if (sender is ScrollViewer scrollViewer)
        {
            SetScrollState(scrollViewer, scrollViewer.VerticalOffset);
        }
    }