WPF .NET弹出窗口 - 悬停时打开并在鼠标悬停时保持打开

时间:2017-10-10 16:20:52

标签: c# .net wpf

我有一个Textblock,当鼠标悬停在它上面时,我想打开Popup。我已使用IsOpenMultiBinding属性绑定到Popup的{​​{1}}和IsMouseOver的{​​{1}},但除此之外当鼠标从文本移动到弹出窗口时,弹出窗口会闪烁。

闪烁的原因是幕后事件的执行顺序:

鼠标从TextBlock移至IsMouseOver - > textblock popup设置为IsMouseOver - >转换器称为,两个参数均为false - >只有textblock的{​​{1}}设置为false - >转换器执行,两个参数都为假,弹出消失 - >转换器再次调用执行,因为之前为IsMouseOver弹出窗口引发了另一个事件,这次{em> IsMouseOver popup true - >弹出窗口再次出现。我尝试添加IsMouseOver,但它从未关闭/表现与预期不同。

问题:如何避免闪烁?

代码:

Popup

转换器代码

True

2 个答案:

答案 0 :(得分:1)

感谢this post,我能够使用延迟多重绑定解决问题。请注意,多重绑定转换器是通用的,可以接受任何常规的多重绑定转换器加上延迟。

我的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)));
    }
}

答案 1 :(得分:0)

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

我用另一种方式解决了它(我通常会像瘟疫一样避免):背后的代码。 在这种情况下,只是根据MouseOver在几个控件上打开/关闭一个弹出窗口,没有更改模型,这是好的,虽然imho。

这是我的解决方案:

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事件中。多数民众赞成。

如果控制器相互接触,则毫秒的时间跨度实际上足以彻底摆脱闪烁。

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