WPF .NET 弹出窗口 - 悬停时打开,如果鼠标悬停,则保持打开状态



我有一个Textblock,当鼠标悬停在它上方时,我想打开一个Popup。我已经使用MultiBinding绑定了IsOpen属性,以PopupIsMouseOverTextBlockIsMouseOver,并且它可以工作,除了当鼠标从文本移动到弹出窗口时,弹出窗口闪烁。

闪烁的原因是引擎盖下事件的执行顺序:

鼠标从textblock移动到popup--textblock的>IsMouseOver设置为false->-调用转换器时两个参数均为假 --> 只有这样popupIsMouseOver才设置为true--> 转换器在两个参数都为 false 的情况下执行,弹出窗口消失 --> 转换器再次调用执行,因为之前为弹出窗口的IsMouseOver引发了另一个事件,这次PopupTrue的鼠标悬停-->弹出窗口再次出现。我尝试添加StaysOpen=False,但它永远不会关闭/行为与预期不同。

问题:如何避免闪烁?

法典:

<Grid>
<ListBox ItemsSource="{Binding RandomNames}">
<ListBox.ItemTemplate>
<DataTemplate>
<Grid>
<Grid.ColumnDefinitions>
<ColumnDefinition></ColumnDefinition>
<ColumnDefinition></ColumnDefinition>
</Grid.ColumnDefinitions>
<TextBlock Text="Name: "
Grid.Column="0"/>
<TextBlock Grid.Column="1"
x:Name="NameBlock"
Text="{Binding}">
<TextBlock.Style>
<Style TargetType="TextBlock">
<Style.Triggers>
<Trigger Property="IsMouseOver" Value="True">
<Setter Property="Foreground" Value="Red" />
</Trigger>
</Style.Triggers>
</Style>
</TextBlock.Style>
</TextBlock>
<Popup x:Name="PopupX"
Grid.Column="1"
PlacementTarget="{Binding ElementName=NameBlock}"
Placement="Bottom">
<!--<Popup.IsOpen>
<MultiBinding Converter="{StaticResource PopupIsOpenConverter}">
<Binding ElementName="PopupX" Path="IsMouseOver" Mode="OneWay" />
<Binding ElementName="NameBlock" Path="IsMouseOver" Mode="OneWay" />
</MultiBinding>
</Popup.IsOpen>-->
<Popup.Style>
<Style TargetType="Popup">
<Setter Property="IsOpen" Value="True" />
<Style.Triggers>
<MultiDataTrigger>
<MultiDataTrigger.Conditions>
<Condition Binding="{Binding IsMouseOver, ElementName=NameBlock}" Value="False" />
<Condition Binding="{Binding IsMouseOver, RelativeSource={RelativeSource Self}}" Value="False" />
</MultiDataTrigger.Conditions>
<Setter Property="IsOpen" Value="False" />
</MultiDataTrigger>
</Style.Triggers>
</Style>
</Popup.Style>
<TextBlock Text="{Binding}"
Foreground="Coral" />
</Popup>
</Grid>
</DataTemplate>
</ListBox.ItemTemplate>
</ListBox>
</Grid>

转换器代码

[ValueConversion(typeof(bool), typeof(bool))]
public class PopupIsOpenConverter : IMultiValueConverter
{
public object Convert(object[] values, Type targetType, object parameter, CultureInfo culture)
{
return values.Any(value => value is bool && (bool) value);
}
public object[] ConvertBack(object value, Type[] targetTypes, object parameter, CultureInfo culture)
{
throw new ActionNotSupportedException();
}
}

多亏了这篇文章,我能够使用延迟多绑定来解决问题。请注意,多重绑定转换器是通用的,可以接受任何常规的多重绑定转换器加上延迟。

我的 XAML:

<Popup.IsOpen>
<local:DelayedMultiBindingExtension Converter="{StaticResource PopupIsOpenConverter}" Delay="0:0:0.01">
<Binding ElementName="PopupX" Path="IsMouseOver" Mode="OneWay" />
<Binding ElementName="RecipientsTextBlock" Path="IsMouseOver" Mode="OneWay" />
</local:DelayedMultiBindingExtension>
</Popup.IsOpen>

我的多重绑定转换器:

[ContentProperty("Bindings")]
internal sealed class DelayedMultiBindingExtension : MarkupExtension, IMultiValueConverter, INotifyPropertyChanged
{
public event PropertyChangedEventHandler PropertyChanged;
public Collection<BindingBase> Bindings { get; }
public IMultiValueConverter Converter { get; set; }
public object ConverterParameter { get; set; }
public CultureInfo ConverterCulture { get; set; }
public BindingMode Mode { get; set; }
public UpdateSourceTrigger UpdateSourceTrigger { get; set; }
private object _undelayedValue;
private object _delayedValue;
private DispatcherTimer _timer;
public object CurrentValue
{
get { return _delayedValue; }
set
{
_delayedValue = _undelayedValue = value;
_timer.Stop();
}
}
public int ChangeCount { get; private set; } // Public so Binding can bind to it
public TimeSpan Delay
{
get { return _timer.Interval; }
set { _timer.Interval = value; }
}
public DelayedMultiBindingExtension()
{
this.Bindings = new Collection<BindingBase>();
_timer = new DispatcherTimer();
_timer.Tick += Timer_Tick;
_timer.Interval = TimeSpan.FromMilliseconds(10);
}
public override object ProvideValue(IServiceProvider serviceProvider)
{
var valueProvider = serviceProvider.GetService(typeof(IProvideValueTarget)) as IProvideValueTarget;
if (valueProvider == null) return null;
var bindingTarget = valueProvider.TargetObject as DependencyObject;
var bindingProperty = valueProvider.TargetProperty as DependencyProperty;
var multi = new MultiBinding { Converter = this, Mode = Mode, UpdateSourceTrigger = UpdateSourceTrigger };
foreach (var binding in Bindings) multi.Bindings.Add(binding);
multi.Bindings.Add(new Binding("ChangeCount") { Source = this, Mode = BindingMode.OneWay });
var bindingExpression = BindingOperations.SetBinding(bindingTarget, bindingProperty, multi);
return bindingTarget.GetValue(bindingProperty);
}
public object Convert(object[] values, Type targetType, object parameter, CultureInfo culture)
{
var newValue = Converter.Convert(values.Take(values.Length - 1).ToArray(),
targetType,
ConverterParameter,
ConverterCulture ?? culture);
if (Equals(newValue, _undelayedValue)) return _delayedValue;
_undelayedValue = newValue;
_timer.Stop();
_timer.Start();
return _delayedValue;
}
public object[] ConvertBack(object value, Type[] targetTypes, object parameter, CultureInfo culture)
{
return Converter.ConvertBack(value, targetTypes, ConverterParameter, ConverterCulture ?? culture)
.Concat(new object[] { ChangeCount }).ToArray();
}
private void Timer_Tick(object sender, EventArgs e)
{
_timer.Stop();
_delayedValue = _undelayedValue;
ChangeCount++;
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(nameof(ChangeCount)));
}
}

我偶然发现了同样的闪烁问题,很想使用你的解决方案,但首先搜索了更轻量级的东西。

我用另一种方式解决了它(我通常避免像瘟疫一样):代码后面。 在这种情况下,只是根据鼠标悬停在多个控件上打开/关闭弹出窗口,而不更改模型,尽管恕我直言,这是可以的。

这是我的解决方案:

public class CodebehindOfSomeView
{
private readonly DispatcherTimer m_ClosePopupTimer = new DispatcherTimer { Interval = TimeSpan.FromMilliseconds(1) };
public CodebehindOfSomeView()
{
InitializeComponent();
m_ClosePopupTimer.Tick += ClosePopupTimer_Tick;
}
private void ClosePopupTimer_Tick(object _sender, EventArgs _e)
{
SomePopup.IsOpen = false;
}
private void PopupMouseOverControls_MouseEnter(object _sender, System.Windows.Input.MouseEventArgs _e)
{
m_ClosePopupTimer.Stop();
SomePopup.IsOpen = true;
}
private void PopupMouseOverControls_MouseLeave(object _sender, System.Windows.Input.MouseEventArgs _e)
{
m_ClosePopupTimer.Start();
}
}

不使用转换器。 在视图中,只需将PopupMouseOverControls_MouseEnter和PopupMouseOverControls_MouseLeave添加到每个所需控件的 MouseEnter 和 MouseLeave 事件。就是这样。

如果控件相互接触,一毫秒的时间跨度实际上足以彻底消除闪烁。

如果您想给用户一点时间将鼠标从一个控件移动到另一个控件(在其他控件的像素上),只需提高时间跨度即可。

最新更新