2013-04-08 9 views
6

Miałem do czynienia z kolejnym nieporozumieniem.MouseButtonEventArgs.MouseDevice.DirectlyOver nieporozumienie

Preambuła:

mam WPF aplikacji z najbliższych zasadniczych części Ui: RadioButtons i pewną kontrolę które używają rozwijaną na podstawie Popup (w combobox sposób). Zgodnie z pewną logiką każde zdarzenie z haczykiem radiowym wykonuje pewne obliczenia. W kolejnym scenariuszu

  1. Użytkownik otwiera okienko (nie wybrać coś, popup tylko pobyt otwarty) kliknij
  2. użytkownika na radiobutton

PreviewMouseDown nie zostanie zwolniony za radiobutton zgodnie z oczekiwaniami (bo z Popupfeature).

I moim celem jest odpalenie PreviewMouseDown dla RadioButton pomimo jednego.

Próby rozwiązania:

szybki i brudny roztwór: hak PreviewMouseDown dla Popup i ponownego ognia PreviewMouseDown zdarzenia z nowego źródła jeśli to konieczne, za pomocą radiobutton jako źródło. Nowe źródło można uzyskać przez MouseButtonEventArgs.MouseDevice.DirectlyOver. Kolejny kawałek kodu to zrobić (zdarzenie zostanie ponownie zwolniony tylko jeśli Popup „jeść” PreviewMouseDown dla zewnętrznej kliknięcia):

private static void GrantedPopupPreviewMouseDown(object sender, MouseButtonEventArgs e) 
    { 
     var popup = sender as Popup; 
     if(popup == null) 
      return; 

     var realSource = e.MouseDevice.DirectlyOver as FrameworkElement; 
     if(realSource == null || !realSource.IsLoaded) 
      return; 

     var parent = LayoutTreeHelper.GetParent<Popup>(realSource); 

     if(parent == null || !Equals(parent, popup)) 
     { 
      e.Handled = true; 
      var args = new MouseButtonEventArgs(e.MouseDevice, 
               e.Timestamp, 
               e.ChangedButton) 
      { 
       RoutedEvent = UIElement.PreviewMouseDownEvent, 
       Source = e.MouseDevice.DirectlyOver, 
      }; 
      realSource.RaiseEvent(args); 
     } 
    } 

This prace dobrze kiedy jestem dołączenie tej obsługi do Popup.PreviewMouseDown bezpośrednio poprzez Behavior i nie działają (PreviewMouseDown nie jest zwolniony za radiobutton) jakbym poprzez dołączenie jednego EventManager.RegisterClassHandler (celem jest uniknięcie dołączenie do każdego zachowania, które mogą wystąpić maksymalnie Popup na stronie z tych radiobuttons):

EventManager.RegisterClassHandler(
      typeof (Popup), 
      PreviewMouseDownEvent, 
      new MouseButtonEventHandler(GrantedPopupPreviewMouseDown)); 

Debugger pokazał, że e.MouseDevice.DirectlyOver (patrz powyższy kod) to Popup, a nie Radiobutton (jak to było, kiedy dołączam handler'a przez Behavior)!

Pytanie:

Jak i dlaczego MouseButtonEventArgs mogą być różne dla tego samego działania, jeśli Podprogram przymocowane na dwa różne sposoby?

Czy ktoś może wyjaśnić to zachowanie?

Wielkie dzięki.

+0

Czy istnieje minimalna powtarzalny przypadek testowy w XAML za to dla nas grać? Może wystąpić zamieszanie związane z tym, co Podgląd oznacza w kontekście WPF. Jako Podgląd * Wydarzenia są wydarzeniami tunelującymi i możesz chcieć użyć zdarzenia MouseDown, ponieważ jest ono domyślnie propagujące twoje wizualne drzewo – Samuel

+1

Czy możesz opublikować XAML dla swojego Popupu i RadioButton? Tak, jak powiedział Samuel, jest to problem z tunelowaniem vs bulgotami. Wydarzenie jest przetwarzane na wyskakującym okienku, zanim będzie mogło dojść do Radiobutton, to moje przypuszczenie. –

+0

Po prostu pchnięcie w ciemność, ale czy coś takiego działa? - Obsługa zdarzeń MouseDown dla popup => popup.Close(); Obsługa zdarzeń MouseUp dla radiobutton => rób swoje. Pomysł polega na tym, że MouseClick składa się z dwóch akcji, pierwsza akcja MouseDown zamknie okienko wyskakujące, a druga akcja MouseUp zostanie przechwycona przez przycisk radiowy ... – Marko

Odpowiedz

0

Pole kombi służy do wybierania przez użytkownika opcji z grupy opcji i prawdopodobnie będzie to potrzebne. Ale ma też inne umowy. Mówi, że użytkownik powinien skupić się na tym i tylko na tym zadaniu. Ale to nie jest twoja sytuacja. Chcesz pokazać opcje, mieć je w stanie ukryć i pozwolić użytkownikowi na robienie innych rzeczy, gdy są one wyświetlane.

Myślę, że zamiast pól złożonych potrzebujesz innej kontroli. Moja sugestia jest użycie ekspandera, który zawiera listbox. Biorąc pod uwagę:

class NotificationObject : INotifyPropertyChanged 
{ 
    public void RaisePropertyChanged(string name) 
    { 
     if(PropertyChanged != null) 
     { 
      PropertyChanged(this, new PropertyChangedEventArgs(name)); 
     } 
    } 
    public event PropertyChangedEventHandler PropertyChanged; 
} 

class ComboEntry : NotificationObject 
{ 
    public string Name { get; private set; } 

    private string _option = "Off"; 
    public string Option 
    { 
     get { return _option; } 
     set { _option = value; RaisePropertyChanged("Option"); } 
    } 

    public ComboEntry() 
    { 
     Name = Guid.NewGuid().ToString(); 
    } 
} 

class MyDataContext : NotificationObject 
{ 
    public ObservableCollection<ComboEntry> Entries { get; private set; } 

    private ComboEntry _selectedEntry; 
    public ComboEntry SelectedEntry 
    { 
     get { return _selectedEntry; } 
     set { _selectedEntry = value; RaisePropertyChanged("SelectedEntry"); } 
    } 
    public MyDataContext() 
    { 
     Entries = new ObservableCollection<ComboEntry> 
     { 
      new ComboEntry(), 
      new ComboEntry(), 
      new ComboEntry() 
     }; 
     SelectedEntry = Entries.FirstOrDefault(); 
    } 
    public void SetOption(string value) 
    { 
     Entries 
      .ToList() 
      .ForEach(entry => entry.Option = value); 
    } 
} 

myślę chcesz następujące XAML:

<Window x:Class="RadioInCombo.MainWindow" 
    xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation" 
    xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" 
    xmlns:local="clr-namespace:RadioInCombo" 
    Title="MainWindow" Height="350" Width="525"> 
    <Window.Resources> 
     <local:MyDataContext x:Key="myDataContext" /> 
     <DataTemplate x:Key="ComboEntryTemplate"> 
      <StackPanel Orientation="Horizontal"> 
       <TextBlock Text="{Binding Name}" /> 
       <Border Width="5" /> 
       <TextBlock Text="{Binding Option}" /> 
      </StackPanel> 
     </DataTemplate> 
    </Window.Resources> 
    <StackPanel DataContext="{StaticResource myDataContext}"> 
     <RadioButton x:Name="OnButton" 
        Content="On" 
        PreviewMouseDown="OnButton_PreviewMouseDown" /> 
     <RadioButton x:Name="OffButton" 
        Content="Off" 
        PreviewMouseDown="OffButton_PreviewMouseDown" /> 
     <Expander Header="{Binding SelectedEntry}" 
        HeaderTemplate="{StaticResource ComboEntryTemplate}"> 
      <ListBox ItemsSource="{Binding Entries}" 
        ItemTemplate="{StaticResource ComboEntryTemplate}" /> 
     </Expander> 
    </StackPanel> 
</Window> 

I następujący kod z opóźnieniem:

private MyDataContext GetMyDataContext() 
    { 
     var candidate = FindResource("myDataContext") as MyDataContext; 
     if (candidate == null) throw new ApplicationException("Could not locate the myDataContext object"); 
     return candidate; 
    } 

    private void OnButton_PreviewMouseDown(object sender, MouseButtonEventArgs e) 
    { 
     GetMyDataContext().SetOption("On");    
    } 

    private void OffButton_PreviewMouseDown(object sender, MouseButtonEventArgs e) 
    { 
     GetMyDataContext().SetOption("Off"); 
    }