Предупреждение: это длинный комментарий, в основном он объясняет мои изменения в ответе * Рика Сладки . Это была отличная отправная точка, но я заметил несколько изменений, которые я сделал с некоторыми вещами, которые я видел происходящими.
Выполняя свои собственные элементы управления, я хотел что-то похожее на это (я хотел закрыть всплывающее окно на свитке), и обнаружил, что ответ очень похож на ответ в ответе Рика Слэдки с несколькими незначительными изменениями, чтобы помочь улучшить некоторые предметы.
Внесенные мною изменения касались в основном трех пунктов. Во-первых, я видел, что ScrollViewer_ScrollChanged
даже стрелял, когда я активно не прокручивал (очевидно, другие вещи это вызывали). Затем было то, что когда я выгружал свои элементы управления, ScrollViewer_ScrollChanged
не был отсоединен от ScrollViewer
s, поэтому, если бы я добавил 3, а затем удалил 1 и прокрутил, он все равно сработал бы 3 раза вместо 2. Наконец, я хотел чтобы иметь возможность добавить функциональность, позволяющую потребителю моего элемента управления также динамически устанавливать свойство IsOpen.
При этом моя модифицированная версия класса ScrollTrigger
выглядит примерно так:
public class ScrollTrigger : TriggerBase<FrameworkElement>
{
public bool TriggerOnNoChange
{
get
{
var val = GetValue(TriggerOnNoChangeProperty);
if (val is bool b)
{
return b;
}
return false;
}
set => SetValue(TriggerOnNoChangeProperty, value);
}
public static readonly DependencyProperty TriggerOnNoChangeProperty =
DependencyProperty.Register(
"TriggerOnNoChange",
typeof(bool),
typeof(ScrollTrigger),
new FrameworkPropertyMetadata(
false,
FrameworkPropertyMetadataOptions.BindsTwoWayByDefault));
protected override void OnAttached()
{
AssociatedObject.Loaded += AssociatedObject_Loaded;
AssociatedObject.Unloaded += AssociatedObject_Unloaded;
}
private void AssociatedObject_Loaded(
object sender,
RoutedEventArgs e)
{
foreach (var scrollViewer in GetScrollViewers())
scrollViewer.ScrollChanged += ScrollViewer_ScrollChanged;
}
private void AssociatedObject_Unloaded(
object sender,
RoutedEventArgs e)
{
foreach (var scrollViewer in GetScrollViewers())
scrollViewer.ScrollChanged -= ScrollViewer_ScrollChanged;
}
private void ScrollViewer_ScrollChanged(
object sender,
ScrollChangedEventArgs e)
{
if(TriggerOnNoChange ||
Math.Abs(e.VerticalChange) > 0 ||
Math.Abs(e.HorizontalChange) > 0)
InvokeActions(e.OriginalSource);
}
private IEnumerable<ScrollViewer> GetScrollViewers()
{
for (DependencyObject element = AssociatedObject;
element != null;
element = VisualTreeHelper.GetParent(element))
if (element is ScrollViewer viewer) yield return viewer;
}
}
Первое изменение здесь заключается в том, что я добавил логику в ScrollViewer_ScrollChanged
, чтобы увидеть, действительно ли значения смещения изменились или нет. Я добавил свойство зависимости в триггер, чтобы вы могли обойти эту логику, если хотите. Второе изменение - добавление события unloaded в связанный объект, так что если элемент управления был удален, он удалит связанные действия с ScrollViewers
, сократив количество обращений ScrollViewer_ScrollChanged
при добавлении и удалении. мой контроль динамически.
Учитывая эти изменения и тот факт, что я хочу, чтобы потребители моего элемента управления могли диктовать, как отображается всплывающее окно, мой .xaml выглядел примерно так:
<UserControl ...
xmlns:i="http://schemas.microsoft.com/expression/2010/interactivity"
xmlns:tgrs="clr-namespace:NameSpace.To.ScrollTrigger.class.Namespace"
x:Name="MyControlNameRoot"
.../>
<i:Interaction.Triggers>
<tgrs:ScrollTrigger TriggerOnNoChange="False">
<i:InvokeCommandAction Command="{Binding ElementName=MyCommandNameRoot, Path=ClosePopupCommand}"/>
</tgrs:ScrollTrigger>
</i:Interaction.Triggers>
...
<Popup ...
IsOpen="{Binding ElementName=MyControlNameRoot, Path=IsPopupOpen, Mode=OneWay}"
.../>
...
</Popup>
...
</UserControl>
Теперь мне нужно было что-то связать, и поскольку я создаю пользовательский элемент управления, я создал некоторые свойства зависимостей и некоторые другие элементы в коде. Если вы используете этот подход с MVVM, вам нужно написать 'INotifyProperty
' и убедиться, что ваши привязки соответствуют им (может не потребоваться часть привязки ElementName в зависимости от того, как вы это делаете). Есть много способов сделать это, и если вы не знаете, просто Google "привязка данных mvvm INotifyPropertyChanged", и вы легко найдете это.
В качестве примечания я также использую Prism, поэтому я использую DelegateCommand
s, но вы можете использовать любую реализацию ICommand
, какую пожелаете. При этом мой программный код выглядел примерно так:
public partial class MyUserControl : UserControl
{
public MyUserControl()
{
ClosePopupCommand = new DelegateCommand(OnPopupCommand);
InitializeComponent();
}
...
public ICommand ClosePopupCommand { get; }
private OnClosePopupCommand ()
{
IsPopupOpen = false;
}
public static readonly DependencyProperty IsPopupOpenProperty =
DependencyProperty.Register(
"IsPopupOpen",
typeof(bool),
typeof(MyUserControl),
new FrameworkPropertyMetadata(
false,
FrameworkPropertyMetadataOptions.BindsTwoWayByDefault));
public bool IsPopupOpen
{
get
{
var val = GetValue(IsPopupOpenProperty);
if (val is bool b)
{
return b;
}
return false;
}
set => SetValue(IsPopupOpenProperty, value);
}
...
}
И с этим я могу иметь всплывающее окно, которое будет закрываться при любом триггере прокрутки, который действительно имеет изменение, не имеет никаких ненужных вызовов, а также позволит пользователю изменять, открыт или нет .
Если вы сделали это далеко, спасибо. Я ценю вашу преданность, и, надеюсь, это немного поможет.