2017-10-10 298 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事件中即可。而已。

如果控件相互接觸,一毫秒的時間段實際上足以徹底擺脫閃爍。

如果您想讓用戶有一點時間將鼠標從一個控件移動到另一個控件(通過其他控件的像素),只需提高時​​間範圍即可。