Das WPF Command System ist ein sehr mächtiges System. Nur gibt es da eine Sache, die mich dann doch etwas stört: Zwar kann über CanExecute und PreviewCanExecute (also Bubbling bzw. Tunneling Ereignis) festgelegt werden, ob ein Command ausgeführt werden kann oder nicht, nur wird dabei lediglich darüber entschieden, ob das jeweilige Element aktiviert oder deaktiviert ist. In manchen Fällen würde ich mir jedoch wünschen, konfigurieren zu können, was genau geschehen soll. In meinem Fall möchte ich das Element gänzlich ausblenden.
Da ich dafür keine angebotene Funktionalität finden konnte, habe ich mir selbst etwas gebaut. Ob es die schönste Lösung ist kann ich nicht sagen. Hat jemand eine bessere zur Hand, freue ich mich natürlich über eine entsprechende Mitteilung.
Gehen wir von einer einfachen Anwendung aus, die ein Menü besitzt und dieses einige Einträge. An diesen Menüeinträgen hängen Commands. Kann nun ein Command nicht ausgeführt werden, soll der Menüeintrag verschwinden. Das Basis-Markup sieht folgendermaßen aus:
1: <DockPanel>
2: <Menu DockPanel.Dock="Top">
3: <Menu.CommandBindings>
4: <CommandBinding
5: Command="ApplicationCommands.Open"
6: Executed="OpenCommand_Executed"/>
7: <CommandBinding
8: Command="ApplicationCommands.Close"
9: Executed="CloseCommand_Executed"/>
10: <CommandBinding
11: Command="cmd:Commands.ExitCommand"
12: Executed="ExitCommand_Executed"/>
13: </Menu.CommandBindings>
14: <MenuItem x:Name="FileMenu" Header="File">
15: <MenuItem Command="ApplicationCommands.Open"/>
16: <MenuItem Command="ApplicationCommands.Close"/>
17: <Separator/>
18: <MenuItem Command="cmd:Commands.ExitCommand"/>
19: </MenuItem>
20: </Menu>
21: </DockPanel>
Wer mit dem Command-System nicht vertraut ist, der findet unter anderem hier entsprechende Informationen.
Im Markup ist zu sehen, dass bis jetzt kein Handler für CanExecute erstellt wurde. Normalerweise wird dies direkt beim CommandBinding gemacht, das hilft uns allerdings nicht bei der Lösung, da wir innerhalb dieses Handlers nicht mehr auf unseren eigentlichen Menüeintrag kommen und diesen daher auch nicht unsichtbar schalten können. Es muss also eine andere Lösung her.
Das MenuItem-Element besitzt ein Attached Property names CommandManager (diese Eigenschaft besitzen auch andere Elemente). Darüber stehen dieselben Ereignisse zur Verfügung. Anstatt also das Ereignis direkt beim CommandBinding zu abonnieren, können wir dies direkt beim Menüeintrag tun (über das Attached Property) und erhalten im resultierenden Eventhandler auch Zugriff auf unser Element.
So sieht das neue XAML Markup aus:
1: <Menu DockPanel.Dock="Top">
2: <Menu.CommandBindings>
3: <CommandBinding
4: Command="ApplicationCommands.Open"
5: Executed="OpenCommand_Executed"/>
6: <CommandBinding
7: Command="ApplicationCommands.Close"
8: Executed="CloseCommand_Executed"/>
9: <CommandBinding
10: Command="cmd:Commands.ExitCommand"
11: Executed="ExitCommand_Executed"/>
12: </Menu.CommandBindings>
13: <MenuItem x:Name="FileMenu" Header="File">
14: <MenuItem
15: Command="ApplicationCommands.Open"
16: CommandManager.CanExecute="OpenMenuCommand_CanExecute"/>
17: <MenuItem
18: Command="ApplicationCommands.Close"
19: CommandManager.CanExecute="CloseMenuCommand_CanExecute"/>
20: <Separator/>
21: <MenuItem Command="cmd:Commands.ExitCommand"/>
22: </MenuItem>
23: </Menu>
Nun können wir entweder über den ersten Parameter sender auf das MenuItem oder über die Eigenschaften OriginalSource bzw,. Source der Ereignis-Argumente zugreifen:
1: private void OpenMenuCommand_CanExecute(
2: object sender,
3: CanExecuteRoutedEventArgs e)
4: {
5: FrameworkElement elem = sender as FrameworkElement;
6: if (FileLoader.IsLoaded)
7: {
8: if (elem != null)
9: elem.Visibility = Visibility.Collapsed;
10: e.CanExecute = false;
11: }
12: else
13: {
14: if (elem != null)
15: elem.Visibility = Visibility.Visible;
16: e.CanExecute = true;
17: }
18:
19: e.Handled = true;
20: }
Was passiert hier? Es gibt einen statischen Typ FileLoader, der verschiedene Möglichkeiten (als Mock-Objekt) zur Verfügung stellt. So werden die Möglichkeiten angeboten, eine Datei zu öffnen bzw. wieder zu schließen. Über die Eigenschaft IsLoaded kann der aktuelle Status abgefragt werden. Je nachdem, wie diese Eigenschaft gesetzt ist, wird nun auch der jeweilige Menüeintrag angezeigt, oder nicht.
Aussehen tut es im Endeffekt so:
Wer sich dieses Beispiel ansehen möchte, der kann dieses natürlich herunter laden und testen: