【问题标题】:WPF .NET Popup - open on hover and keep open if mouse is overWPF .NET Popup - 在悬停时打开并在鼠标悬停时保持打开
【发布时间】:2017-10-10 16:20:52
【问题描述】:

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

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

鼠标从textblock 移动到popup-->textblockIsMouseOver 设置为false --> 调用转换器,两个参数都为 false -->只有在popupIsMouseOver 设置为true --> 转换器执行,两个参数都为false,弹出窗口消失--> 转换器调用 和再次执行,因为之前为弹出窗口的IsMouseOver 引发了另一个事件,这次Popup TrueIsMouseOver --> 弹出窗口再次出现。我尝试添加 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();
    }
}

【问题讨论】:

    标签: c# .net wpf


    【解决方案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)));
        }
    }
    

    【讨论】:

      【解决方案2】:

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

      我以另一种方式解决了它(我通常会像瘟疫一样避免这种情况):隐藏代码。 在这种情况下,只是在几个控件上根据 MouseOver 打开/关闭一个弹出窗口,而不改变模型,尽管恕我直言,这没关系。

      这是我的解决方案:

      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 事件中。就是这样。

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

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

      【讨论】:

        猜你喜欢
        • 2011-06-22
        • 2011-06-23
        • 2023-03-26
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        相关资源
        最近更新 更多