Udostępnij za pośrednictwem


Jak obsługiwać zdarzenie ContextMenuOpening

Zdarzenie ContextMenuOpening można obsłużyć w aplikacji, aby dostosować istniejące menu kontekstowe przed wyświetleniem lub pominąć menu, które w przeciwnym razie będzie wyświetlane, ustawiając właściwość Handled na true w danych zdarzenia. Typową przyczyną zmiany Handled na true w danych zdarzenia jest całkowite zastąpienie menu nowym obiektem ContextMenu, co czasami wymaga anulowania operacji i rozpoczęcia nowego otwarcia. Jeśli piszesz programy obsługi dla zdarzenia ContextMenuOpening, należy pamiętać o problemach z chronometrażem między kontrolką ContextMenu a usługą, która jest odpowiedzialna za otwieranie i pozycjonowanie menu kontekstowych dla kontrolek w ogóle. W tym temacie przedstawiono niektóre techniki kodu dla różnych scenariuszy otwierania menu kontekstowego i przedstawiono przypadek, w którym występuje problem z chronometrażem.

Istnieje kilka scenariuszy obsługi zdarzenia ContextMenuOpening:

  • Dostosowywanie elementów menu przed wyświetleniem.

  • Zastąpienie całego menu przed wyświetleniem.

  • Całkowicie pomijając wszystkie istniejące menu kontekstowe i nie wyświetlając menu kontekstowego.

Przykład

Dostosowywanie elementów menu przed wyświetleniem

Dostosowanie istniejących elementów menu jest dość proste i prawdopodobnie jest najbardziej typowym scenariuszem. Można to zrobić, aby dodać lub odjąć opcje menu kontekstowego w odpowiedzi na bieżące informacje o stanie w aplikacji lub określone informacje o stanie, które są dostępne jako właściwość w obiekcie, w którym żądane jest menu kontekstowe.

Ogólną techniką jest pobranie źródła zdarzenia, czyli określonej kontrolki, która została kliknięta prawym przyciskiem myszy, i pobranie właściwości ContextMenu z niego. Zazwyczaj chcesz sprawdzić kolekcję Items, aby zobaczyć, jakie elementy menu kontekstowego już istnieją w menu, a następnie dodać lub usunąć odpowiednie nowe elementy MenuItem do lub z kolekcji.

void AddItemToCM(object sender, ContextMenuEventArgs e)
{
    //check if Item4 is already there, this will probably run more than once
    FrameworkElement fe = e.Source as FrameworkElement;
    ContextMenu cm = fe.ContextMenu;
    foreach (MenuItem mi in cm.Items)
    {
        if ((String)mi.Header == "Item4") return;
    }
    MenuItem mi4 = new MenuItem();
    mi4.Header = "Item4";
    fe.ContextMenu.Items.Add(mi4);
}

Zamienianie całego menu przed wyświetleniem

Alternatywnym scenariuszem jest zastąpienie całego menu kontekstowego. Oczywiście można również użyć odmiany poprzedniego kodu, aby usunąć każdy element istniejącego menu kontekstowego i dodać nowe, zaczynając od zera elementu. Jednak bardziej intuicyjne podejście do zastępowania wszystkich elementów w menu kontekstowym polega na utworzeniu nowego ContextMenu, wypełnieniu go elementami, a następnie ustawieniu właściwości FrameworkElement.ContextMenu kontrolki na nową ContextMenu.

Poniżej znajduje się prosty kod obsługi zastępujący ContextMenu. Kod odwołuje się do niestandardowej metody BuildMenu, która jest oddzielona, ponieważ jest wywoływana przez więcej niż jedną z przykładowych procedur obsługi.

void HandlerForCMO(object sender, ContextMenuEventArgs e)
{
    FrameworkElement fe = e.Source as FrameworkElement;
    fe.ContextMenu = BuildMenu();
}
ContextMenu BuildMenu()
{
    ContextMenu theMenu = new ContextMenu();
    MenuItem mia = new MenuItem();
    mia.Header = "Item1";
    MenuItem mib = new MenuItem();
    mib.Header = "Item2";
    MenuItem mic = new MenuItem();
    mic.Header = "Item3";
    theMenu.Items.Add(mia);
    theMenu.Items.Add(mib);
    theMenu.Items.Add(mic);
    return theMenu;
}

Jeśli jednak używasz tego stylu obsługi dla ContextMenuOpening, możesz potencjalnie uwidocznić problem z chronometrażem, jeśli obiekt, w którym ustawiasz ContextMenu nie ma istniejącego menu kontekstowego. Gdy użytkownik kliknie prawym przyciskiem myszy kontrolkę, ContextMenuOpening zostanie podniesiona, nawet jeśli istniejąca ContextMenu jest pusta lub ma wartość null. Ale w tym przypadku, cokolwiek nowego ContextMenu ustawisz na elemencie źródłowym, pojawia się za późno, aby można było to wyświetlić. Ponadto, jeśli użytkownik kliknie prawym przyciskiem myszy po raz drugi, tym razem pojawi się nowy ContextMenu, wartość jest niepusta, a procedura obsługi poprawnie zastąpi i wyświetli menu, gdy program obsługi zostanie uruchomiony po raz drugi. Sugeruje to dwa możliwe obejścia:

  1. Upewnij się, że procedury obsługi ContextMenuOpening zawsze działają na kontrolkach, które mają co najmniej dostępny symbol zastępczy ContextMenu, który zostanie zastąpiony przez kod tej procedury. W takim przypadku nadal można użyć obsługi pokazanej w poprzednim przykładzie, ale zazwyczaj chcesz przypisać symbol zastępczy ContextMenu w początkowym markupu.

    <StackPanel>
      <Rectangle Fill="Yellow" Width="200" Height="100" ContextMenuOpening="HandlerForCMO">
        <Rectangle.ContextMenu>
          <ContextMenu>
            <MenuItem>Initial menu; this will be replaced ...</MenuItem>
          </ContextMenu>
        </Rectangle.ContextMenu>
      </Rectangle>
      <TextBlock>Right-click the rectangle above, context menu gets replaced</TextBlock>
    </StackPanel>
    
  2. Załóżmy, że początkowa wartość ContextMenu może mieć wartość null na podstawie pewnej wstępnej logiki. Możesz sprawdzić ContextMenu pod kątem wartości null lub użyć flagi w kodzie, aby sprawdzić, czy procedura obsługi została wykonana co najmniej raz. Ponieważ zakłada się, że ContextMenu ma być wyświetlane, twój program obsługi ustawia Handled na true w danych zdarzenia. Do ContextMenuService, który jest odpowiedzialny za wyświetlanie menu kontekstowego, wartość true dla Handled w danych zdarzenia reprezentuje żądanie anulowania wyświetlania tego menu w połączeniu z kontrolką, która wzbudziła zdarzenie.

Teraz, gdy pominięto potencjalnie podejrzane menu kontekstowe, następnym krokiem jest podanie nowego, a następnie wyświetlenie go. Ustawienie nowego jest w zasadzie takie samo jak poprzednia obsługa: tworzysz nową ContextMenu i ustawiasz właściwość FrameworkElement.ContextMenu źródła kontroli za jej pomocą. Dodatkowym krokiem jest to, że teraz musisz wymusić wyświetlanie menu kontekstowego, ponieważ zablokowałeś pierwszą próbę. Aby wymusić wyświetlanie, należy ustawić właściwość Popup.IsOpen na true w programie obsługi. Podczas wykonywania tej czynności należy zachować ostrożność, ponieważ otwarcie menu kontekstowego w programie obsługi powoduje ponowne wywołanie zdarzenia ContextMenuOpening. Jeśli ponownie wejdziesz do swojego modułu obsługi, staje się ona nieskończenie rekursywna. Dlatego zawsze musisz sprawdzić null lub użyć flagi, jeśli otworzysz menu kontekstowe z poziomu programu obsługi zdarzeń ContextMenuOpening.

Pomijanie istniejącego menu kontekstowego i nie wyświetlanie żadnego menu kontekstowego.

Ostatni scenariusz, pisząc procedurę obsługi, która całkowicie pomija menu, jest rzadkością. Jeśli dana kontrolka nie ma wyświetlać menu kontekstowego, prawdopodobnie istnieją bardziej odpowiednie sposoby zapewnienia tego niż pomijając menu tylko wtedy, gdy użytkownik zażąda go. Jeśli jednak chcesz użyć programu obsługi, aby pominąć menu kontekstowe i pokazać nic, program obsługi powinien po prostu ustawić Handled na true w danych zdarzenia. ContextMenuService, który jest odpowiedzialny za wyświetlanie menu kontekstowego, sprawdzi dane zdarzenia, które wywołał na komponencie. Jeśli zdarzenie zostało oznaczone Handled w dowolnym miejscu wzdłuż trasy, zostanie pominięta akcja otwierania menu kontekstowego, która zainicjowała zdarzenie.

    void HandlerForCMO2(object sender, ContextMenuEventArgs e)
    {
        if (!FlagForCustomContextMenu)
        {
            e.Handled = true; //need to suppress empty menu
            FrameworkElement fe = e.Source as FrameworkElement;
            fe.ContextMenu = BuildMenu();
            FlagForCustomContextMenu = true;
            fe.ContextMenu.IsOpen = true;
        }
    }
}

Zobacz też