Wie dynamisch, hinzufügen von MenuItems (mit header) zu einem WPF-Menü
[Edit #3] - an jeden Leser die Frage: tun nicht unter keinen Umständen verwenden Sie die beschriebene Vorgehensweise in dieser Frage. Es ist ein Coding Horror. Ich gestehe dies, wohl wissend, dass alle Programmierer, die an sich gearbeitet haben, in eine Ecke, in der Vergangenheit und (vor allem, wenn das erlernen einer neuen Technologie) sind wir alle wurden in die Irre geführt von anderen, wohlmeinenden Entwickler auf der interweb. Lesen Sie die Antwort von Robert zuerst, dann Lesen Sie diese Frage. Bitte.
[Bearbeiten #2b]
Ich entschuldige mich für die Länge dieser Frage - es ist eine Frage, die hier (am Ende!), aber ich wollte sicher gehen, dass der source-code wurde explizit. Sowieso.
[Edit #2] - Frage Titel geändert, um genau das... Frage.
[Bearbeiten] - ich habe aktualisiert, ein bisschen mehr von der Geschichte, wie ich landete in den design - /code, die ich hier gemacht habe: Obligatorische Blog-Post. Wenn es hilft bei der Klärung der Frage unten, fühlen Sie sich frei, es zu Lesen...
Ursprüngliche Frage
Die Anwendung an der ich arbeite nutzt Prism und WPF, mit einer Anzahl von Modulen (derzeit 3), von denen die die Anwendung hostet Menü. Ursprünglich war das menu statisch mit Haken in CompositeCommand /DelegateCommands, die Super geklappt für routing-Taste drückt, um den zuständigen Moderator. Jedes MenuItem verwendet ein StackPanel-Element im header, um den Inhalt anzuzeigen-wie eine Kombination aus einem Bild und einem text-label - das war der Blick, den ich wollte für:
<Menu Height="48" Margin="5,0,5,0" Name="MainMenu" VerticalAlignment="Top" Background="Transparent">
<MenuItem Name="MenuFile" AutomationProperties.AutomationId="File">
<MenuItem.Header>
<StackPanel>
<Image Height="24" VerticalAlignment="Center" Source="../Resources/066.png"/>
<ContentPresenter Content="Main"/>
</StackPanel>
</MenuItem.Header>
<MenuItem AutomationProperties.AutomationId="FileExit" Command="{x:Static local:ToolBarCommands.FileExit}">
<MenuItem.Header>
<StackPanel>
<Image Height="24" VerticalAlignment="Center" Source="../Resources/002.png"/>
<ContentPresenter Content="Exit"/>
</StackPanel>
</MenuItem.Header>
</MenuItem>
</MenuItem>
<MenuItem Name="MenuHelp" AutomationProperties.AutomationId="Help" Command="{x:Static local:ToolBarCommands.Help}">
<MenuItem.Header>
<StackPanel>
<Image Height="24" VerticalAlignment="Center" Source="../Resources/152.png"/>
<ContentPresenter Content="Help"/>
</StackPanel>
</MenuItem.Header>
</MenuItem>
</Menu>
Leider, die Anwendung ist mittlerweile ein wenig komplexer und es ist wünschenswert, dass noch weitere Module registrieren Sie sich mit dem Menü - also, ich habe auf der Suche zu machen, das Menü dynamisch. Das Ziel ist, andere Module (durch eine Dienstleistung) in der Lage sein, um das hinzufügen von Befehlen zum Menü wird - zum Beispiel, Ein Modul fügt einen Menüpunkt in der Symbolleiste Modul, ruft eine Prozedur im Modul A. Es gibt ein paar gute Artikel auf diesem Thema - die zwei habe ich mir angeschaut sind Aufbau einer Datenbindung in WPF-Menü Mit einem HierarchicalDataTemplate und WPF-Sample-Serie - Datenbindung HierarchicalDataTemplate-Menü-Beispiel. Die folgenden Ratschläge in dem Artikel habe ich es geschafft, ein Menü dynamisch aufgebaut, die keine offensichtliche Datenbindung Probleme - Sie können erstellen Sie ein Menü mit Elementen verknüpft, gesichert, um meine Präsentation Modell, was die Struktur der ObservableCollection in der Präsentation Modell
Derzeit ist meine XAML-Code sieht wie folgt aus:
<UserControl x:Class="Modules.ToolBar.Views.ToolBarView"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:model="clr-namespace:Modules.ToolBar.PresentationModels"
xmlns:local="clr-namespace:Modules.ToolBar">
<UserControl.Resources>
<model:ToolBarPresentationModel x:Key="modelData" />
<HierarchicalDataTemplate DataType="{x:Type model:ToolbarObject}"
ItemsSource="{Binding Path=Children}">
<ContentPresenter Content="{Binding Path=Name}"
Loaded="ContentPresenter_Loaded"
RecognizesAccessKey="True"/>
</HierarchicalDataTemplate>
</UserControl.Resources>
<UserControl.DataContext>
<Binding Source="{StaticResource modelData}"/>
</UserControl.DataContext>
<Menu Height="48" Margin="5,0,5,0" Name="MainMenu" VerticalAlignment="Top" Background="Transparent"
ItemsSource="{Binding}">
</Menu>
</Grid>
</UserControl>
Den code hinter den anzeigen tut die schwerarbeit im ContentPresenter_Loaded Methode:
private void ContentPresenter_Loaded(object sender, System.Windows.RoutedEventArgs e)
{
ContentPresenter presenter = sender as ContentPresenter;
if (sender != null)
{
DependencyObject parentObject = VisualTreeHelper.GetParent(presenter);
bool bContinue = true;
while (bContinue
|| parentObject == null)
{
if (parentObject is MenuItem)
bContinue = false;
else
parentObject = VisualTreeHelper.GetParent(parentObject);
}
var menuItem = parentObject as MenuItem;
if (menuItem != null)
{
ToolbarObject toolbarObject = menuItem.DataContext as ToolbarObject;
StackPanel panel = new StackPanel();
if (!String.IsNullOrEmpty(toolbarObject.ImageLocation))
{
Image image = new Image();
image.Height = 24;
image.VerticalAlignment = System.Windows.VerticalAlignment.Center;
Binding sourceBinding = new Binding("ImageLocation");
sourceBinding.Mode = BindingMode.TwoWay;
sourceBinding.Source = toolbarObject;
image.SetBinding(Image.SourceProperty, sourceBinding);
panel.Children.Add(image);
}
ContentPresenter contentPresenter = new ContentPresenter();
Binding contentBinding = new Binding("Name");
contentBinding.Mode = BindingMode.TwoWay;
contentBinding.Source = toolbarObject;
contentPresenter.SetBinding(ContentPresenter.ContentProperty, contentBinding);
panel.Children.Add(contentPresenter);
menuItem.Header = panel;
Binding commandBinding = new Binding("Command");
commandBinding.Mode = BindingMode.TwoWay;
commandBinding.Source = toolbarObject;
menuItem.SetBinding(MenuItem.CommandProperty, commandBinding);
}
}
}
Wie Sie sehen können, bin ich versucht zu reproduzieren, ist das StackPanel /Bild /Name-Kombination von original-Menü, tun nur so in der code-behind. Der Versuch zu tun, das hat nicht funktioniert so gut - während der Menü-Objekte sind sicherlich geschaffen, die Sie nicht "erscheint" als etwas anderes als leere, anklickbare Objekte - StackPanel-Element, Bild, Name, etc. nicht gerendert wird. Interessanterweise, es verursacht auch den originalen text in der ContentPresent in der HierarchicalDataTemplate gelöscht werden.
Dann die Frage, gibt es eine Möglichkeit zum festlegen eines MenuItem-Header-Eigenschaft im Load-Ereignis so, dass es angezeigt wird, auf das Benutzersteuerelement richtig? Ist die Tatsache, dass die Elemente in der Kopfzeile nicht angezeigt wird, bezeichnend für ein Binding-problem? Wenn ja, was wäre die richtige Art und Weise binden Sie die Header, um ein transientes Objekt (das StackPanel-Steuerelement, das erstellt wurde, werden in die load-Ereignisprozedur)?
Ich bin offen für Veränderung nichts im code oben - dies ist alle Art von prototyping zusammen, um herauszufinden, der beste Weg, damit umzugehen dynamische Menü-Erstellung.
Danke!
- +1 für edit #3, hat mich gerettet eine Menge zu Lesen 🙂
Du musst angemeldet sein, um einen Kommentar abzugeben.
Ich werde gestehen, dass ich noch nicht gegraben, nicht ganz so tief in deinem Beispiel, als ich vielleicht sollte, aber wenn ich sehe, wie code-behind-das ist die Suche die visuelle Struktur, denke ich, könnte dies gehandhabt werden explizit in einem view-Modell?
Scheint es mir in diesem Fall, dass Sie eine ziemlich einfache Sicht-Modell - ein Objekt Belichten
Text
,Image
,Command
, undChildren
Eigenschaften, zum Beispiel - und dann erstellen Sie eine einfache Daten-Vorlage, die für die Präsentation alsMenuItem
. Dann ist alles, was benötigt, um zu ändern, den Inhalt deines Menüs manipuliert dieses Modell.Edit:
Angeschaut, was man gerade im detail, und die beiden Beispiele, die du verlinkt hast in deinem blog-post, ich bin schlug meinen Kopf gegen den Schreibtisch. Diese beiden Entwickler scheinen unter dem Missverständnis, dass die Methode zum festlegen der Eigenschaften auf die Menü-Elemente von der Vorlage generiert, ist die Suche über die visuelle Struktur, in die
ContentPresenter.Load
Ereignis, nachdem Sie geschaffen sind. Nicht so. Das ist das, was dieItemContainerStyle
ist für.Wenn Sie diese verwenden, es ist ziemlich einfach zu erstellen, dynamische Menüs von der Art die du beschreibst. Sie brauchen eine
MenuItemViewModel
Klasse, hatINotifyPropertyChanged
umgesetzt und macht, die diese öffentliche Eigenschaften:Diesem:
wo die
ItemsSource
ist einObservableCollection<MenuItemViewModel>
, und mit dieser Vorlage:den Menüs in dem Fenster genau darstellen, was in der Sammlung, und werden dynamisch aktualisiert, wenn Elemente Hinzugefügt und entfernt, die beide zu den top-level-Elemente und auf die Nachkommen.
Gibt es keinen kletterten über die in der visual-tree, keine manuelle Erstellung von Objekten, kein code-behind (andere als in der Ansicht Modell, und in was füllt die Sammlung in den ersten Platz).
Habe ich baute ein ziemlich gründlich gearbeitet Beispiel dafür; Sie können laden Sie das Projekt hier.
MenuItem
Objekte mit derHierarchicalDataTemplate
. Das wird zu nichts aber Verwirrung, wenn man zu Fuß die visuelle Struktur, weil eineMenuItem
's ElternMenuItem
ist eigentlich nicht das übergeordnete Menü zu kommen, es ist das Element container. Ich wirklich empfehlen, Blick auf die demo-app, die ich zusammen gestellt, und sehen, wenn Sie entweder a) es kann angepasst werden für Ihren Zweck, oder b) Sie können das problem reproduzieren, das Sie beschreiben in es.Ein weiterer möglicher Ansatz könnte sein, dass das Menü eine region und einigen sich auf eine Konvention, so dass alle Ansichten Hinzugefügt, dass die region ein ViewModel mit einer Eigenschaft namens " MenuHeader. So, die region-adapter können Sie einfach den Menü-header aus der Sicht der Daten-Kontext, und legen Sie es auf den Punkt, wenn Sie es.
Etwas ähnliches geschieht in der PRISMA-mit Ansichten Hinzugefügt, um eine Registerkarte Region. Lesen Sie mehr hier.
Ich hoffe, dieser bietet einige nützliche Anleitungen.
Dank,
Damian