警告:这是一个很长的评论,它基本上只是解释我对@Rick Sladkey回复的更改。这是一个很好的起点,但我确实注意到我对我看到的一些事情进行了一些更改。
在做我的自定义控件时,我想要类似的东西(我想关闭滚动上的弹出窗口),并发现答案与 Rick Sladkey 的答案非常相似,只是做了一些小改动以帮助改进某些项目.
我所做的更改主要涉及 3 个项目。第一个是当我没有主动滚动时,我看到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 部分,具体取决于您的操作方式)。有很多方法可以做到这一点,如果你不知道,只需谷歌“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);
}
...
}
有了这个,我可以有一个弹出窗口,它会在任何实际发生变化的滚动触发器上关闭,没有任何不需要的调用,并且还允许用户修改是否打开。
如果你已经做到了这一步,谢谢你。我感谢您的奉献精神,希望这对您有所帮助。