AvalonDock document view content disapear after float window - wpf

I'm using AvalonDock control in my project. When i move my document anywhere and detach from control, document content disapear. And if i redock document to a control, document content come out. I'm sure i'm missing something so simple but i don't understand the problem. Here is the code snippet from the MainView;
<xcad:DockingManager AllowMixedOrientation="True" DocumentsSource="{Binding DocumentViewModels}">
<xcad:DockingManager.Resources>
<DataTemplate DataType="{x:Type viewModels:WatchListViewModel}">
<local:WatchListView DataContext="{Binding}"/>
</DataTemplate>
<DataTemplate DataType="{x:Type viewModels:ScanListViewModel}">
<local:ScanListView DataContext="{Binding}"/>
</DataTemplate>
<DataTemplate DataType="{x:Type viewModels:SignalListViewModel}">
<local:SignalListView DataContext="{Binding}"/>
</DataTemplate>
</xcad:DockingManager.Resources>
<xcad:DockingManager.LayoutItemContainerStyle>
<Style TargetType="{x:Type xcad:LayoutItem}">
<Setter Property="Title" Value="{Binding Model.Document.Title}"/>
</Style>
</xcad:DockingManager.LayoutItemContainerStyle>
</xcad:DockingManager>
For clarify the problem want to share three screenshots. First screenshot is showing document before move anywhere. Second screenshot is showing document after move anywhere (floating). And third screenshot is showing re-dock to same place. Actually first and third image same but i want to show clearly that actually content still there.

You need to add a DataTemplateSelector to your code, in order to teach AvalonDock which DataTemplate used for your own View/ViewModel.
In order to do so, you need to define a new class like the following:
class PanesTemplateSelector : System.Windows.Controls.DataTemplateSelector
{
public DataTemplate WatchListViewTemplate { get; set; }
public DataTemplate ScanListViewTemplate { get; set; }
public DataTemplate SignalListViewTemplate { get; set; }
public override DataTemplate SelectTemplate(object item, DependencyObject container)
{
if (item is WatchListViewModel)
return WatchListViewTemplate;
if (item is ScanListViewModel)
return ScanListViewTemplate;
if (item is SignalListViewModel)
return SignalListViewTemplate;
return base.SelectTemplate(item, container);
}
}
Then you need to add this class yo your XAML as follow:
<xcad:DockingManager.LayoutItemTemplateSelector>
<s:PanesTemplateSelector>
<s:PanesTemplateSelector.WatchListViewTemplate>
<DataTemplate>
<p:WatchListView />
</DataTemplate>
</s:PanesTemplateSelector.WatchListViewTemplate>
<s:PanesTemplateSelector.ScanListViewTemplate>
<DataTemplate>
<p:ScanListView />
</DataTemplate>
</s:PanesTemplateSelector.ScanListViewTemplate>
<s:PanesTemplateSelector.SignalListViewTemplate>
<DataTemplate>
<p:SignalListView />
</DataTemplate>
</s:PanesTemplateSelector.SignalListViewTemplate>
</s:PanesTemplateSelector>
</xcad:DockingManager.LayoutItemTemplateSelector>
Where s links to the namespace where you define the PanesTemplateSelector and p links to the namespace where you define your own Views

Related

WPF - How to implement DataTemplate with complex logic?

I am currently transferring my app from WinForms to WPF.
Since I'm new in WPF, I stucked at creating DataTemplates for my treeView items. The screenshot shows how my treeview looked in WinForms version, and I need to get close result in WPF.
(My WinForms treeview)
As you can see, my DataTemplate's logic should take into account these factors:
Node type / defines which icon and fields combination will be displayed for particular item (node). App has about 7-8 node types. Type stored in separate node's field.
Variable values / I need to replace with text if null, etc
Numeric variable values / e.g.: set gray color if zero, etc.
Other properties / e.g.: adding textblocks depending on boolean fields.
And so on...
All these factors result into huge amount of possible item params combinations.
Also I'm using DevComponents WPF DotNetBar AdvTree to divide item properties into columns. I presume I should create 'sub templates' for different field sets and compose from them the entire DataTemplate for each column.
I've learned about triggers, and have to say that implementing my logic with triggers will make my subtemplates huge anyway.
(Current state of my WPF treeview)
So here are my questions:
Are there any ways to dynamically compose complex templates with C# code (without creating raw XAML and loading it at runtime)?
Maybe I should use completely different way (instead of using DataTemplate)? In Winforms I just used OwnerDraw mode, so the task was MUCH easier than in WPF :(
And how to display nested properties inside template? e.g.: Item.Prop.Subprop1.Subprop2.Targetprop.
PS: English is not my first language, sorry for your eyes.
1) The answer is yes.
For exemple if you want to define a template in your window for a simple string
public MainWindow()
{
InitializeComponent();
DataTemplate template = new DataTemplate(typeof(string));
FrameworkElementFactory borderFactory = new FrameworkElementFactory(typeof(Border));
borderFactory.SetValue(Border.PaddingProperty, new Thickness(1));
borderFactory.SetValue(Border.BorderThicknessProperty, new Thickness(1));
borderFactory.SetValue(Border.BorderBrushProperty, Brushes.Red);
FrameworkElementFactory textFactory = new FrameworkElementFactory(typeof(TextBlock));
textFactory.SetBinding(TextBlock.TextProperty, new Binding
{
Mode = BindingMode.OneWay
});
borderFactory.AppendChild(textFactory);
template.VisualTree = borderFactory;
myControl.ContentTemplate = template;
}
And in the Window you just put something like
<ContentControl x:Name="myControl" Content="Test text" Margin="10"/>
Your content control will render the string surrounded by a red border.
But as you anc see it is really complex to define your templates in this way.
The only scenario where i could imagine this approache is for some kind of procedurally generated templates.
Another way is to generate a string for the template and then load it with XamlReader:
string xaml = "<Ellipse Name=\"EllipseAdded\" Width=\"300.5\" Height=\"200\"
Fill=\"Red\" xmlns=\"http://schemas.microsoft.com/winfx/2006/xaml/presentation\"/>";
object ellipse = XamlReader.Load(xaml);
2) I don't really see the need to generate templates in code behind. For exemple for this kind of data structure:
public class User
{
public string Name { get; set; }
public User Friend { get; set; }
}
public class RootNode
{
public string Title { get; set; }
public User User { get; set; }
public List<Node> Nodes { get; set; }
}
public class Node
{
public string Title { get; set; }
public List<SubNode> SubNodes { get; set; }
}
public class SubNode
{
public string Title { get; set; }
}
You can define this type of template:
<Window
...
Title="MainWindow" Height="350" Width="525" >
<Window.Resources>
<HierarchicalDataTemplate DataType="{x:Type local:RootNode}" ItemsSource="{Binding Nodes}">
<StackPanel x:Name="spContainer" Orientation="Horizontal">
<TextBlock Text="{Binding Title}"/>
<TextBlock Text="{Binding User.Friend.Friend.Name}"/>
</StackPanel>
<HierarchicalDataTemplate.Triggers>
<DataTrigger Binding="{Binding User}" Value="{x:Null}">
<Setter TargetName="spContainer" Property="Background" Value="Yellow"/>
</DataTrigger>
</HierarchicalDataTemplate.Triggers>
</HierarchicalDataTemplate>
<HierarchicalDataTemplate DataType="{x:Type local:Node}" ItemsSource="{Binding SubNodes}">
<TextBlock Text="{Binding Title}"/>
</HierarchicalDataTemplate>
<HierarchicalDataTemplate DataType="{x:Type local:SubNode}" ItemsSource="{Binding Nodes}">
<TextBlock Text="{Binding Title}"/>
</HierarchicalDataTemplate>
</Window.Resources>
<Grid>
<TreeView ItemsSource="{Binding RootNodes}"/>
</Grid>
</Window>
As you can see you can define a template by data type, you can also use triggers to modify the behavior in specific cases, you could also use som binding converters...
3) You can bind to nested properties just like to normal ones :
<TextBlock Text="{Binding User.Friend.Friend.Name}"/>
However in some cases more than two level bindings could fail (fail to resolve or fail to update when property changes, ...)

Binding on dynamically-added elements

TPTB have decided that our app must run in a single window, popping up new windows in modal mode is not allowed.
And naturally, we have a UI design that involves popping up modal dialogs all over the place.
So I added a top-level Grid to the Window. In that Grid I defined no rows or columns, so everything draws in Row 0/Column 0.
The first element in the Grid was another Grid that contained everything that was normally displayed in the Window. The second was a full-sized Border with a gray, semi-transparent Background. The rest were Borders with wide Margins and white Backgrounds, containing the various UserControls that needed to be displayed as popups. All but the first had Visibility="Collapsed".
And then, when I needed to show a popup, I'd set Visibility="Visible" on the gray background and on the appropriate UserControl. The result was a nice shadowbox effect that worked fine.
Until somebody decided that the popups needed to be able to display popups. In a non-predictable order.
The limitation of the method I had implemented, using Visibility="Collapsed" elements in a Grid was that their order was fixed. UserControlB would always be displayed on top of UserControlA, even if it was UserControlB that asked to have UserControlA displayed. And that's not acceptable.
So my next attempt was to define the various UserControls in Window.Resources, and to add them to the Grid in code:
this.masterGrid.Children.Add(this.Resources["userControlA"] as UserControlA);
And that almost works. But the bindings are all messed up.
As an example, one of the controls is supposed to bind a Property to the CurrentItem of a collection in a member object of the Window's viewmodel. When I had the control defined as an invisible item in the Grid, it worked fine. But when I defined it as a Resource, the Property was null - it was never bound.
So I tried binding it in code, after I added it to the grid:
userControlA.SetBinding(UserControlA.myProperty, new Binding()
{ Source = this.viewModel.myCollection.CurrentItem });
And that compiles and runs just fine, but I'm not binding to the right object.
The first time I display the UserControl, I see the right object bound to it. But when I close it, and move the CurrentItem in the collection to a different object, and display the UserControl again, I still see the first object bound. If I close it again, and open it a third time, then I will see the right object bound to the control.
I've checked in code, and the CurrentItem that I'm binding to is right, every time, but it only seems to take every other time.
So I tried explicitly clearing the binding, first:
BindingOperations.ClearBinding(userControlA, UserControlA.myProperty);
userControlA.SetBinding(UserControlA.myProperty, new Binding()
{ Source = this.viewModel.myCollection.CurrentItem });
But that doesn't seem to have made any difference.
In all, it feels like I'm running down a rabbit hole, chasing deeper and deeper into complexity, to solve what should be a fairly simple problem.
Does anyone have any suggestions as to:
How to get binding to work on dynamically-added elements, or
How to get arbitrarily-ordered popups to display, as shadowboxes, without using dynamically-ordered elements?
Thanks in advance.
While it seems really odd for me that you can't create new Windows, I would definitely recommend not to complicate it too much by doing unnecesary things such as storing your views in the MainWindow's resources.
It would be better if you just added new instances of these elements into an ObservableCollection:
XAML:
<Window x:Class="WpfApplication4.Window8"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:local="clr-namespace:WpfApplication4"
Title="Window8" Height="300" Width="300">
<Window.Resources>
<DataTemplate DataType="{x:Type local:ViewModel1}">
<StackPanel Background="Green">
<TextBlock Text="This is ViewModel1!!"/>
<TextBlock Text="{Binding Text}"/>
</StackPanel>
</DataTemplate>
<DataTemplate DataType="{x:Type local:ViewModel2}">
<StackPanel Background="Blue" HorizontalAlignment="Center">
<TextBlock Text="This is ViewModel2!!"/>
<TextBlock Text="{Binding Text2}"/>
</StackPanel>
</DataTemplate>
<DataTemplate DataType="{x:Type local:ViewModel3}">
<StackPanel Background="Red" VerticalAlignment="Center">
<TextBlock Text="This is ViewModel3!!"/>
<TextBlock Text="{Binding Text3}"/>
<TextBox Text="{Binding Text3}"/>
</StackPanel>
</DataTemplate>
</Window.Resources>
<DockPanel>
<Button Width="100" Content="Add" Click="Add_Click" DockPanel.Dock="Top"/>
<Button Width="100" Content="Remove" Click="Remove_Click" DockPanel.Dock="Top"/>
<ListBox ItemsSource="{Binding ActiveWidgets}" SelectedItem="{Binding SelectedWidget}">
<ListBox.Template>
<ControlTemplate>
<ItemsPresenter/>
</ControlTemplate>
</ListBox.Template>
<ItemsControl.ItemsPanel>
<ItemsPanelTemplate>
<Grid IsItemsHost="True"/>
</ItemsPanelTemplate>
</ItemsControl.ItemsPanel>
<ListBox.ItemContainerStyle>
<Style TargetType="ListBoxItem">
<Setter Property="VerticalAlignment" Value="Stretch"/>
<Setter Property="HorizontalAlignment" Value="Stretch"/>
<Setter Property="Template">
<Setter.Value>
<ControlTemplate TargetType="ListBoxItem">
<ContentPresenter ContentSource="Content"/>
</ControlTemplate>
</Setter.Value>
</Setter>
</Style>
</ListBox.ItemContainerStyle>
</ListBox>
</DockPanel>
</Window>
Code Behind:
using System.Linq;
using System.Windows;
using System.Collections.ObjectModel;
using System;
namespace WpfApplication4
{
public partial class Window8 : Window
{
private WidgetsViewModel Widgets { get; set; }
public Window8()
{
InitializeComponent();
DataContext = Widgets = new WidgetsViewModel();
}
private Random rnd = new Random();
private int lastrandom;
private void Add_Click(object sender, RoutedEventArgs e)
{
var random = rnd.Next(1, 4);
while (random == lastrandom)
{
random = rnd.Next(1, 4);
}
lastrandom = random;
switch (random)
{
case 1:
Widgets.ActiveWidgets.Add(new ViewModel1() {Text = "This is a Text"});
break;
case 2:
Widgets.ActiveWidgets.Add(new ViewModel2() { Text2 = "This is another Text" });
break;
case 3:
Widgets.ActiveWidgets.Add(new ViewModel3() { Text3 = "This is yet another Text" });
break;
}
Widgets.SelectedWidget = Widgets.ActiveWidgets.LastOrDefault();
}
private void Remove_Click(object sender, RoutedEventArgs e)
{
Widgets.ActiveWidgets.Remove(Widgets.SelectedWidget);
Widgets.SelectedWidget = Widgets.ActiveWidgets.LastOrDefault();
}
}
public class WidgetsViewModel: ViewModelBase
{
public ObservableCollection<ViewModelBase> ActiveWidgets { get; set; }
private ViewModelBase _selectedWidget;
public ViewModelBase SelectedWidget
{
get { return _selectedWidget; }
set
{
_selectedWidget = value;
NotifyPropertyChange(() => SelectedWidget);
}
}
public WidgetsViewModel()
{
ActiveWidgets = new ObservableCollection<ViewModelBase>();
}
}
public class ViewModel1: ViewModelBase
{
public string Text { get; set; }
}
public class ViewModel2: ViewModelBase
{
public string Text2 { get; set; }
}
public class ViewModel3: ViewModelBase
{
public string Text3 { get; set; }
}
}
Just copy and paste my code in a File - New - WPF Application and see the results for yourself.
Since the Grid always places the last UI Element added to it topmost, you will see that Adding items to the observablecollection makes these "different widgets" always appear on top of each other, with the topmost being the last one added.
The bottom line is, when WidgetA requests to open WidgetB, just create a new WidgetBViewModel and add it to the ActiveWidgets collection. Then, when WidgetB is no longer needed, just remove it.
Then, it's just a matter of putting your UserControls inside a proper DataTemplate for each ViewModel. I strongly suggest you keep a separate ViewModel for each of your Widgets, and if you need to share data between them, just share data between the ViewModels.
Don't attempt to do things like ListBox ItemsSource="{Binding Whatever, RelativeSource={RelativeSource FindAncestor, AncestorType=Window}" unless you have a good reason to.
This way you no longer have to deal with Panel.ZIndex stuff. Maybe you can create a couple of attached properties to deal with things like focus and whatnot, but this approach is dead simple, and by far more performant than the Visibility and the Resources approaches.

Problems binding to a the content of a WPF DataGridCell in XAML

I used the following post to implement a datagrid bound to a list of dynamic objects
Binding DynamicObject to a DataGrid with automatic column generation?
The ITypedList method GetItemProperties works fine, a grid is displayed with all the columns I described.
I use a custom PropertyDescriptor and override the GetValue and SetValue methods as described in the above post, I also implement the TryGetMember and TrySetMember methods in the dynamic objects.
so basically I have a ComplexObject:DynamicCobject with a field Dictionary and a ComplexObjectCollection implementing ITypedList and IList.
This all works fine except when I bind the itemsSource of the DataGrid to the collection, the cells will show the SimpleObject type name and I actually want to implement a template to show the property Value of the SimpleObject in a text block.
I've used all sorts of methods to try and get the underlying SimpleObject but nothing works and I always get the ComplexObject for the row. I am using autogenerated columns and this always seems to produce a text column, this may be the problem but why cant I still get the underlying SimpleObject from somewhere in the cell properties?
Below would be my ideal solution but this does not work.
<Grid>
<Grid.Resources>
<DataTemplate x:Key="DefaultNodeTempate">
<ContentControl Content="{Binding RelativeSource={RelativeSource TemplatedParent},
Path=Content}">
<ContentControl.Resources>
<DataTemplate DataType="local:SimpleObjectType">
<TextBlock Text="{Binding Value}" />
</DataTemplate>
</ContentControl.Resources>
</ContentControl>
</DataTemplate>
</Grid.Resources>
<DataGrid ItemsSource="{Binding ElementName=mainWin, Path=DynamicObjects}">
<DataGrid.Resources>
<Style TargetType="DataGridCell">
<Setter Property="ContentTemplate" Value="{StaticResource DefaultNodeTempate}" />
</Style>
</DataGrid.Resources>
</DataGrid>
</Grid>
Any suggestions would be much appreciated.
Thanks
Kieran
So I found the solution was to do some work in the code behind.
In the AutoGeneratingColumn event create a DataTemplate with a content control and a custom template selector (I create the selector in Xaml and found it as a resource).
Create a binding for the ContentProperty of the ContentControl with e.PropertyName as the path
Create a new DataGridTemplateColumn and set the new columns CellTemplate to your new DataTemplate
replace e.Column with your new column and hey presto the cells datacontext bind with the dynamic property for that column.
If anyone has any refinement to this please feel free to share your thoughts.
Thanks
EDIT: As requested some sample code for my solution
Custom template selector:
public class CustomDataTemplateSelector : DataTemplateSelector
{
public List<DataTemplate> Templates { get; set; }
public CustomDataTemplateSelector()
: base()
{
this.Templates = new List<DataTemplate>();
}
public override DataTemplate SelectTemplate(object item, DependencyObject container)
{
DataTemplate template = null;
if (item != null)
{
template = this.Templates.FirstOrDefault(t => t.DataType is Type ? (t.DataType as Type) == item.GetType() : t.DataType.ToString() == item.GetType().ToString());
}
if (template == null)
{
template = base.SelectTemplate(item, container);
}
return template;
}
}
XAML:
<Window x:Class="WpfApplication1.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:local="clr-namespace:WpfApplication1"
Title="MainWindow" Height="350" Width="525">
<Grid x:Name="ParentControl">
<Grid.Resources>
<local:CustomDataTemplateSelector x:Key="MyTemplateSelector" >
<local:CustomDataTemplateSelector.Templates>
<DataTemplate DataType="{x:Type local:MyCellObject}" >
<TextBox Text="{Binding MyStringValue}" IsReadOnly="{Binding IsReadOnly}" />
</DataTemplate>
</local:CustomDataTemplateSelector.Templates>
</local:CustomDataTemplateSelector>
</Grid.Resources>
<DataGrid ItemsSource="{Binding Rows}" AutoGenerateColumns="True" AutoGeneratingColumn="DataGrid_AutoGeneratingColumn" >
</DataGrid>
</Grid>
</Window>
Code behind:
private void DataGrid_AutoGeneratingColumn(object sender, DataGridAutoGeneratingColumnEventArgs e)
{
// Get template selector
CustomDataTemplateSelector selector = ParentControl.FindResource("MyTemplateSelector") as CustomDataTemplateSelector;
// Create wrapping content control
FrameworkElementFactory view = new FrameworkElementFactory(typeof(ContentControl));
// set template selector
view.SetValue(ContentControl.ContentTemplateSelectorProperty, selector);
// bind to the property name
view.SetBinding(ContentControl.ContentProperty, new Binding(e.PropertyName));
// create the datatemplate
DataTemplate template = new DataTemplate { VisualTree = view };
// create the new column
DataGridTemplateColumn newColumn = new DataGridTemplateColumn { CellTemplate = template };
// set the columns and hey presto we have bound data
e.Column = newColumn;
}
There may be a better way to create the data template, I have read recently that Microsoft suggest using a XamlReader but this is how I did it back then. Also I haven't tested this on a dynamic class but I'm sure it should work either way.

Styling GridViewColumns

I was browsing stackoverflow to try to figure out a way to style the GridViewColumns in my ListView. I came across this:
WPF Text Formatting in GridViewColumn
The top answer shows how to style one (1) GridViewColumn. My question is, is there a way to style these GridViewColumns in the Window.Resources so I don't have to do it for each individual GridViewColumn?
There are a few possibilities:
<!-- if style doesn't change -->
<GridViewColumn CellTemplate="{StaticResource yourCellTemplate}"/>
<!-- if you need to change it up based on criteria - template selector-->
<GridViewColumn CellTemplateSelector="{StaticResource YourTemplateSelector}"/>
<!-- same goes for headers -->
<GridViewColumn HeaderTemplate="{StaticResource yourheaderTempalte}"/>
..or HeaderContainerStyle, HeaderTemplateSelector
if you want to use template selectors: create a class, instanciate it in resource dict, and plug it in you gridview column, here's a little sample
public class MyTemplateSelector : DataTemplateSelector
{
public DataTemplate SimpleTemplate { get; set; }
public DataTemplate ComplexTemplate { get; set; }
public override DataTemplate SelectTemplate(object item, DependencyObject container)
{
//if I have just text
return SimpleTemplate;
//if I have comments and other fancy stuff
return ComplexTemplate;
then in your ResourceDictionary
<DataTemplate x:Key="ComplexTemplate">
<Views:MyCustomControl DataContext="{Binding}"/>
</DataTemplate>
<Views:MyTemplateSelector
x:Key="TxtVsExpensiveCell_TemplateSelector"
SimpleTemplate ="{StaticResource SimpleTemplate}"
ComplexTemplate="{StaticResource ComplexTemplate}"/>
<!-- then you use it in your view like this -->
<GridViewColumn CellTemplateSelector="{StaticResource TxtVsExpensiveCell_TemplateSelector}"/>
If you don't want to go through all that trouble, and just what to tweak styles of predefined controls, why not use DataGrid? It has predefined columns, where you can tweak styles per each..
<DataGrid>
<DataGrid.Columns>
<DataGridTextColumn/>
<DataGridCheckBoxColumn/>
<DataGridHyperlinkColumn/>
<DataGridCheckBoxColumn/>
....there are several more column types!
</DataGrid.Columns>
</DataGrid>

WPF, MVVM, and Menu Foreground Color

I'm new to both WPF and MVVM. I have searched for a good way to dynamically create menus in the MVVM parttern and I am not finding anything to my liking, so I rolled my own solution. It works, but for some reason the Foreground (text) color of the menus are sometimes (just sometimes) not correct.
I added a link for the image below.
http://img220.imageshack.us/img220/1912/badmenu.jpg (Dead Link)
My lowest submenu displays correctly with a white foreground, but its parent menus forground turned to black and is almost impossible to read. If I had hard coded the menus then the parent's forground color would be white. If I move my mouse over the parent its text will switch back to white and the submenu will become black.
Further, once I move my mouse away from the parent, all of its boolean properties IsHighlighted, IsSubmenuOpen, etc... become false, which surprising to me because I would think they should stay true. The end result is I haven't been able to solve this with a style trigger.
Here is my XAML .
<Window.Resources>
<DataTemplate DataType="{x:Type src:ParentMenu}" >
<Menu >
<MenuItem Header="{Binding MenuName}" ItemsSource="{Binding ChildMenuItems}" />
</Menu>
</DataTemplate>
<HierarchicalDataTemplate DataType="{x:Type src:ChildMenu}"
ItemsSource="{Binding ChildMenuItems}" >
<MenuItem Header="{Binding MenuName}" Command="{Binding Path=Command}" />
</HierarchicalDataTemplate>
' StackOverflow is masking my end tag for Window.Resources
<DockPanel>
<Menu DockPanel.Dock="Top" ItemsSource="{Binding Menus}" />
<Grid>
<!-- Add additional content here -->
</Grid>
</DockPanel>
Both ParentMenu and ChildMenu inherit from a common class that actually holds all the menus and exposes the sub-menus through the ChildMenuItems collection. ChildMenuItems is a list of ChildMenu objects. My ViewModels expose a list of ParentMenu objects.
There are probably better ways to accomplish what I want here. Here is an example:
img132.imageshack.us/img132/4160/bettermenu.jpg (Dead Link)
Any suggestions on what I'm doing wrong and/or how to fix the display problem?
The problem is that your VMs automatically get wrapped in MenuItems, so you essentially have MenuItems nested as the Header of MenuItems.
You can get around this by defining a Style (and pointing to it via ItemContainerStyle) that DataBinds to your VMs (Name to Header, DelegateCommands to Command, etc.) using MenuItem as the DataType.
An example of a way you can do this is below. Note that I've dropped the HierarchicalDataTemplate in favor of an ItemContainerStyle. I also took the liberty of defining a DataTemplate for your MainViewModel as it wasn't very clear how that was data bound.
<Window.Resources>
<DataTemplate DataType="{x:Type src:MainViewModel}">
<ItemsControl ItemsSource="{Binding Menus}"></ItemsControl>
</DataTemplate>
<DataTemplate DataType="{x:Type src:ParentMenu}" >
<Menu>
<MenuItem Header="{Binding Name}"
ItemsSource="{Binding ChildMenuItems}" ItemContainerStyle="{DynamicResource ChildMenuItemStyle}" />
</Menu>
</DataTemplate>
<Style x:Key="ChildMenuItemStyle" TargetType="MenuItem">
<Setter Property="Header" Value="{Binding Name}"></Setter>
<Setter Property="ItemsSource" Value="{Binding ChildMenuItems}"></Setter>
</Style>
</Window.Resources>
I've also cut some of the Command binding out for simplicity, but you can add it back in as necessary.
As requested, here are my ViewModels.
ViewModelBase is the standard one created by studio. MainVieModel has got just enough to in it to create the test menus I was using to experiment with.
Basically I am working towards creating a Parent/Child menu classes I can use with a series of apps we sell to a broad collection of clients. I hope to make it where each customer can have a customizable collection of menus based upon their needs and which moudles they've purchased licenses for.
public abstract class ViewModelBase : INotifyPropertyChanged
{
public event PropertyChangedEventHandler PropertyChanged;
protected void OnPropertyChanged(string propertyName)
{
PropertyChangedEventHandler handler = PropertyChanged;
if (handler != null)
{
handler(this, new PropertyChangedEventArgs(propertyName));
}
}
}
public class MainViewModel : ViewModelBase
{
public MainViewModel() { MakeMenus(); }
private void MakeMenus()
{
// Makes some dummy menus to test with.
Menus = new ObservableCollection<MyMenuItem>();
ParentMenu parent;
ChildMenu child;
parent = new ParentMenu("First Level");
Menus.Add(parent);
child = new ChildMenu(parent, "second level");
parent.ChildMenuItems.Add(child);
ChildMenu child2 = new ChildMenu(child, "third level");
child2.MenuCommand = new DelegateCommand(CommandTest,
CommandCanExecute_First);
child.ChildMenuItems.Add(child2);
child = new ChildMenu(parent, "second level 2");
parent.ChildMenuItems.Add(child);
child2 = new ChildMenu(child, "third level 2");
child2.MenuCommand = new DelegateCommand(CommandTest,
CommandCanExecute_Second);
child.ChildMenuItems.Add(child2);
parent = new ParentMenu("Another First");
parent.ChildMenuItems.Add(new ChildMenu(parent, "Another Second"));
Menus.Add(parent);
OnPropertyChanged("Menus");
}
private bool ExecuteToggle { get; set; }
private void CommandTest() { ExecuteToggle = !ExecuteToggle; }
public ObservableCollection<MyMenuItem> Menus { get; private set;}
public bool CommandCanExecute_First() { return ExecuteToggle; }
public bool CommandCanExecute_Second() { return !ExecuteToggle; }
}

Resources