2017-10-10 296 views
1

我有一个Textblock,当鼠标悬停在上面时,我想打开一个Popup。我已使用MultiBindingPopupIsMouseOverTextBlockIsMouseOverIsOpen属性绑定,并且它工作正常,除非将鼠标从文本移至弹出窗口时,弹出式窗口会闪烁。WPF .NET Popup - 悬停时打开,如果鼠标悬停,请保持打开

闪烁的原因是事件的执行的发动机罩下的顺序:

鼠标移动从textblockpopup - >IsMouseOvertextblock设置为false - >转换器被称为两个参数是假的 - >才把popupIsMouseOver设置为true - >转换器执行有两个参数是假的,弹出窗口消失了 - >转换称为执行因为另一个事件是因为弹出的IsMouseOver提前弹出,此时IsMouseOverPopupTrue - >弹出窗口再次出现。我曾尝试添加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(); 
    } 
} 

回答

1

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

我的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> 

我multibinding转换器:

[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))); 
    } 
} 
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事件中即可。而已。

如果控件相互接触,一毫秒的时间段实际上足以彻底摆脱闪烁。

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