WPF MenuItem Binding Issue - c#

I faced an issue regarding MenuItem Binding, I need to bind a nested object to a MenuItem.
public class QuestionType
{
public string Name { get; set; }
public ICollection<QuestionType> Types { get; set; }
}
public class ViewModel
{
public ICollection<QuestionType> QuestionTypes { get; set; }
public ViewModel()
{
QuestionTypes = new List<QuestionType>()
{
new QuestionType() { Name="Completion" },
new QuestionType() { Name="Easy" },
new QuestionType() { Name="MoreType", Types = new List<QuestionType>()
{
new QuestionType() { Name="SingleChoice" },
new QuestionType() { Name="MultiChoice" },
new QuestionType() { Name="Blend" },
} },
};
}
}
public partial class CustomMenu : UserControl
{
public CustomMenu()
{
InitializeComponent();
this.DataContext = new ViewModel();
}
}
In XAML Code:
I binding them to MenuItem like this way:
<MenuItem ItemsSource="{Binding QuestionTypes}">
<MenuItem.Resources>
<HierarchicalDataTemplate DataType="{x:Type local:QuestionType}"
ItemsSource="{Binding Types}">
<TextBlock Text="{Binding Name}"/>
</HierarchicalDataTemplate>
<DataTemplate DataType="{x:Type local:QuestionType}">
<TextBlock Text="{Binding Name}"/>
</DataTemplate>
</MenuItem.Resources>
</MenuItem>
When I run my project, an exception occurred.
Additional information: Set property 'System.Windows.ResourceDictionary.DeferrableContent' threw an exception.
In addition:
I need to add click event for Menu Item like
<MenuItem.Resources>
...
</MenuItem.Resources>
<MenuItem.ItemContainerStyle>
<Style TargetType="{x:Type MenuItem}">
<EventSetter Event="Menu.Click" Handler="MenuItem_Click"/>
</Style>
</MenuItem.ItemContainerStyle>
It didn't work, I don't know how to solve this issue.

Try this:
<MenuItem Header="Question Type" ItemsSource="{Binding QuestionTypes}" Click="MenuItem_Click">
<MenuItem.Resources>
<HierarchicalDataTemplate DataType="{x:Type local:QuestionType}" ItemsSource="{Binding Types}">
<TextBlock Text="{Binding Name}"/>
</HierarchicalDataTemplate>
</MenuItem.Resources>
</MenuItem>

Related

TreeView binding to a class with a list of another class (and so on)

I tried everything but no matter how I configure HierarchicalDataTemplate it always shows only the top level of the collection (StarSystem items and StarSystemName properties)
This is how my TreeView is configured right now:
<TreeView x:Name="StarSystemTreeView" ItemsSource="{Binding StarSystems, ElementName=window}">
<TreeView.ItemTemplate>
<HierarchicalDataTemplate ItemsSource="{Binding}">
<TextBlock Text="{Binding Path=StarSystemName}"/>
<HierarchicalDataTemplate.ItemTemplate>
<HierarchicalDataTemplate ItemsSource="{Binding Path=Planets}">
<TextBlock Text="{Binding Path=PlanetNumber}"/>
<HierarchicalDataTemplate.ItemTemplate>
<HierarchicalDataTemplate ItemsSource="{Binding Path=Moons}">
<TextBlock Text="{Binding Path=MoonNumber}"/>
<HierarchicalDataTemplate.ItemTemplate>
<DataTemplate>
<TextBlock Text="{Binding Path=MoonMinerals}"/>
</DataTemplate>
</HierarchicalDataTemplate.ItemTemplate>
</HierarchicalDataTemplate>
</HierarchicalDataTemplate.ItemTemplate>
</HierarchicalDataTemplate>
</HierarchicalDataTemplate.ItemTemplate>
</HierarchicalDataTemplate>
</TreeView.ItemTemplate>
</TreeView>
window is my MainWindow class, in which StarSystems is initialized:
public partial class MainWindow
{
private ObservableCollection<StarSystem> _starSystems = new ObservableCollection<StarSystem>
{
new StarSystem("FooSystem",
new ObservableCollection<Planet>
{
new Planet(1, new ObservableCollection<Moon>
{
new Moon(1, new ObservableCollection<string>
{
"FooMineral"
})
})
})
};
public ObservableCollection<StarSystem> StarSystems
{
get { return _starSystems; }
set { _starSystems = value; }
}
}
The 3 classes I want to bind to the TreeView are defined outside of MainWindow class, in the same namespace:
[NotifyPropertyChanged]
public class StarSystem
{
public StarSystem()
: this("", new ObservableCollection<Planet>())
{
}
public StarSystem(string starSystemName, ObservableCollection<Planet> planets)
{
StarSystemName = starSystemName;
Planets = planets;
}
public string StarSystemName { get; set; }
public ObservableCollection<Planet> Planets { get; set; }
}
[NotifyPropertyChanged]
public class Planet
{
public Planet()
: this(0, new ObservableCollection<Moon>())
{
}
public Planet(int planetNumber, ObservableCollection<Moon> moons)
{
PlanetNumber = planetNumber;
Moons = moons;
}
public int PlanetNumber { get; set; }
public ObservableCollection<Moon> Moons { get; set; }
}
[NotifyPropertyChanged]
public class Moon
{
public Moon()
: this(0, new ObservableCollection<string>())
{
}
public Moon(int moonNumber, ObservableCollection<string> moonMinerals)
{
MoonNumber = moonNumber;
MoonMinerals = moonMinerals;
}
public int MoonNumber { get; set; }
public ObservableCollection<string> MoonMinerals { get; set; }
}
This is how it look after starting the application:
I've been trying to figure it out for the past 3 days and I think I've tried every way possible to configure the HierarchicalDataTemplate so I'm probably just doing something wrong...
ItemTemplate is bit messed up. You have set ItemSource one hierarchy level down. It should be:
<TreeView x:Name="StarSystemTreeView"
ItemsSource="{Binding StarSystems, ElementName=window}">
<TreeView.ItemTemplate>
<HierarchicalDataTemplate ItemsSource="{Binding Path=Planets}">
<TextBlock Text="{Binding Path=StarSystemName}"/>
<HierarchicalDataTemplate.ItemTemplate>
<HierarchicalDataTemplate ItemsSource="{Binding Path=Moons}">
<TextBlock Text="{Binding Path=PlanetNumber}"/>
<HierarchicalDataTemplate.ItemTemplate>
<HierarchicalDataTemplate
ItemsSource="{Binding Path=MoonMinerals}">
<TextBlock Text="{Binding Path=MoonNumber}"/>
<HierarchicalDataTemplate.ItemTemplate>
<DataTemplate>
<TextBlock Text="{Binding}"/>
</DataTemplate>
</HierarchicalDataTemplate.ItemTemplate>
</HierarchicalDataTemplate>
</HierarchicalDataTemplate.ItemTemplate>
</HierarchicalDataTemplate>
</HierarchicalDataTemplate.ItemTemplate>
</HierarchicalDataTemplate>
</TreeView.ItemTemplate>
</TreeView>

How can i use expander as a child item of treeview in WPF

I want to show a custom set of repeating datas
Class M
prop1 // treeview heading
List<String> // treeview items
List<ClassSub> [ prop 1, porp2 ,List<String> sub items] //as an expander with List<String> as expander items
Class M
prop1 // treeview heading
prop2 // treeview items
List<ClassSub>[prop1 ,prop2,List<String>...]//as an expander
Can i use a Treeview + Expander combination to arrange this complete set of data?
Or do i need to use a Codeplex librarys like http://complexdatatemplates.codeplex.com [But i dont see a clear documentation on it]
here is how I offer to solve your issue
xaml
<TreeView ItemsSource="{Binding TreeItems}">
<TreeView.ItemTemplate>
<HierarchicalDataTemplate ItemsSource="{Binding SubItems}">
<Expander Header="{Binding Header}">
<ItemsControl Margin="25,0,0,0" ItemsSource="{Binding TreeItems}" />
</Expander>
</HierarchicalDataTemplate>
</TreeView.ItemTemplate>
</TreeView>
Item class
class Item
{
public string Header { get; set; }
public List<string> TreeItems { get; set; }
public List<Item> SubItems { get; set; }
}
View Model
class ViewModel
{
public ViewModel()
{
TreeItems = new ObservableCollection<Item>();
string[] data = new string[]{
"Subchapter 1","Subchapter 2",
};
Item item = new Item()
{
Header = "Sub Getting Started",
TreeItems = new List<string>(data)
};
TreeItems.Add(new Item()
{
Header = "Getting Started 1",
SubItems = new List<Item>(new Item[] { item }),
TreeItems = new List<string>(data)
});
TreeItems.Add(new Item()
{
Header = "Getting Started 2",
SubItems = new List<Item>(new Item[] { item }),
TreeItems = new List<string>(data)
});
TreeItems.Add(new Item()
{
Header = "Getting Started 3",
SubItems = new List<Item>(new Item[] { item }),
TreeItems = new List<string>(data)
});
}
public ObservableCollection<Item> TreeItems { get; private set; }
}
result
In example above I haven't styled the Expander to match the one in screenshot posted in question. let me know if that is also required.
EDIT
as discussed, to display SubChapter1.1. and 1.2 to be visible by default or expand any other tree view item by default
start by defining a style for TreeViewItem in the resources
<TreeView.Resources>
<Style TargetType="TreeViewItem">
<Setter Property="IsExpanded"
Value="{Binding IsExpanded}" />
</Style>
</TreeView.Resources>
add a property IsExpanded to BookChapter class
public class BookChapter
{
public string name { get; set; }
public string id { get; set; }
public List<BookPage> pages { get; set; }
//public List<Enrichment> enrichment { get; set; }
public List<SubChapter> chapters { get; set; }
public bool IsExpanded { get; set; }
}
then set IsExpanded to true if you want it to be initially expanded
BookChapter cha = new BookChapter { name = "Chapter Intro", pages = pags, IsExpanded=true };
result
you can control other tree items in a similar manner too.
EDIT 2
sample for the same using Listbox and data templates
<Grid>
<Grid.Resources>
<DataTemplate x:Key="chapterTemplate">
<Expander Header="{Binding name}" IsExpanded="{Binding IsExpanded}">
<StackPanel>
<ItemsControl Margin="25,0,0,0"
ItemsSource="{Binding pages}">
<ItemsControl.ItemTemplate>
<DataTemplate>
<Button Content="{Binding Path=label}">
</Button>
</DataTemplate>
</ItemsControl.ItemTemplate>
</ItemsControl>
<ListBox ItemsSource="{Binding chapters}"
BorderBrush="{x:Null}"
Margin="20,0,0,0" />
</StackPanel>
</Expander>
</DataTemplate>
<DataTemplate DataType="{x:Type l:BookChapter}">
<ContentControl Content="{Binding}"
ContentTemplate="{StaticResource chapterTemplate}" />
</DataTemplate>
<DataTemplate DataType="{x:Type l:SubChapter}">
<ContentControl Content="{Binding}"
ContentTemplate="{StaticResource chapterTemplate}" />
</DataTemplate>
<DataTemplate DataType="{x:Type l:BookPage}">
<Button Content="{Binding Path=label}" />
</DataTemplate>
</Grid.Resources>
<ListBox ItemsSource="{Binding}"
Name="TOCView" />
</Grid>
result
with this approach you may not need to template the tree view item for the arrow. just template the expander as you wish
as a suggestion you can reuse BookChapter class as subchapters instead of creating a new class for SubChapter unless necessary.

WPF - How can I create menu and submenus using binding

I am trying to create a dynamic menu using binding. I my viewmodel I have a list of objects which contains an header and a command. However, it is not working. I think the problem is in the data template. See my code below:
<Menu Background="{x:Null}" Grid.Row="0" Grid.Column="1" Panel.ZIndex="2" Width="865" Height="85" HorizontalAlignment="Left" ItemsSource="{Binding Path=MenuItems}">
<Menu.ItemTemplate>
<HierarchicalDataTemplate DataType="MenuItemViewModel" ItemsSource="{Binding Path=MenuItems}">
<MenuItem Header="{Binding Header}" Style="{DynamicResource MenuItemStyle1}" ItemsSource="{Binding Path=MenuItems}" Padding="10,12,10,0" Height="44.1" Margin="30,0,0,0" FontWeight="Bold">
<MenuItem.ItemsPanel>
<ItemsPanelTemplate>
<VirtualizingStackPanel Orientation="Horizontal"/>
</ItemsPanelTemplate>
</MenuItem.ItemsPanel>
</MenuItem>
<HierarchicalDataTemplate.ItemTemplate>
<DataTemplate>
<MenuItem Header="{Binding Header}" Style="{DynamicResource MenuItemStyle1}" Padding="0,8,0,0" Height="38">
</MenuItem>
</DataTemplate>
</HierarchicalDataTemplate.ItemTemplate>
</HierarchicalDataTemplate>
</Menu.ItemTemplate>
</Menu>
The result only shows the first menu. The submenus are not shown but they are there since the menus which have children, the arrow is print after menu header.
Could anyone find something wrong on the binding? Or any suggestion?
Just for information, MenuItems is a list of MenuItemViewModel objects which has an header and a list of MenuItemViewModel objects (submenus) called MenuItems too.
For me, it worked with this simple template:
<Menu.ItemContainerStyle>
<Style TargetType="{x:Type MenuItem}">
<Setter Property="Command" Value="{Binding Command}" />
</Style>
</Menu.ItemContainerStyle>
<Menu.ItemTemplate>
<HierarchicalDataTemplate DataType="{x:Type local:MenuItemViewModel}" ItemsSource="{Binding Path=MenuItems}">
<TextBlock Text="{Binding Header}"/>
</HierarchicalDataTemplate>
</Menu.ItemTemplate>
Here is the complete example:
MainWindow.xaml:
<Window x:Class="WpfApplication14.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:local="clr-namespace:WpfApplication14"
Title="MainWindow" Height="350" Width="525">
<DockPanel>
<Menu DockPanel.Dock="Top" ItemsSource="{Binding MenuItems}">
<Menu.ItemContainerStyle>
<Style TargetType="{x:Type MenuItem}">
<Setter Property="Command" Value="{Binding Command}" />
</Style>
</Menu.ItemContainerStyle>
<Menu.ItemTemplate>
<HierarchicalDataTemplate DataType="{x:Type local:MenuItemViewModel}" ItemsSource="{Binding Path=MenuItems}">
<TextBlock Text="{Binding Header}"/>
</HierarchicalDataTemplate>
</Menu.ItemTemplate>
</Menu>
<Grid>
</Grid>
</DockPanel>
</Window>
MainWindow.xaml.cs:
using System;
using System.Collections.ObjectModel;
using System.Windows;
using System.Windows.Input;
namespace WpfApplication14
{
public partial class MainWindow : Window
{
public ObservableCollection<MenuItemViewModel> MenuItems { get; set; }
public MainWindow()
{
InitializeComponent();
MenuItems = new ObservableCollection<MenuItemViewModel>
{
new MenuItemViewModel { Header = "Alpha" },
new MenuItemViewModel { Header = "Beta",
MenuItems = new ObservableCollection<MenuItemViewModel>
{
new MenuItemViewModel { Header = "Beta1" },
new MenuItemViewModel { Header = "Beta2",
MenuItems = new ObservableCollection<MenuItemViewModel>
{
new MenuItemViewModel { Header = "Beta1a" },
new MenuItemViewModel { Header = "Beta1b" },
new MenuItemViewModel { Header = "Beta1c" }
}
},
new MenuItemViewModel { Header = "Beta3" }
}
},
new MenuItemViewModel { Header = "Gamma" }
};
DataContext = this;
}
}
public class MenuItemViewModel
{
private readonly ICommand _command;
public MenuItemViewModel()
{
_command = new CommandViewModel(Execute);
}
public string Header { get; set; }
public ObservableCollection<MenuItemViewModel> MenuItems { get; set; }
public ICommand Command
{
get
{
return _command;
}
}
private void Execute()
{
// (NOTE: In a view model, you normally should not use MessageBox.Show()).
MessageBox.Show("Clicked at " + Header);
}
}
public class CommandViewModel : ICommand
{
private readonly Action _action;
public CommandViewModel(Action action)
{
_action = action;
}
public void Execute(object o)
{
_action();
}
public bool CanExecute(object o)
{
return true;
}
public event EventHandler CanExecuteChanged
{
add { }
remove { }
}
}
}
The resulting window looks like this:
that is very easy,you can use this code for your nested menu
ViewModel: TopMenuViewModel.cs
public partial class TopMenuViewModel
{
public TopMenuViewModel()
{
TopMenuItems = new ObservableCollection<MenuItem>
{
new MenuItem
{
Title = "File",
PageName =typeof(OfficeListView).FullName,
ChildMenuItems= {
new MenuItem
{
Title = "New"
},
new MenuItem
{
Title = "Open"
},
new MenuItem
{
Title = "Save"
}
}
},
new MenuItem
{
Title = "Edit"
},
new MenuItem
{
Title = "Search"
}
};
}
View: TopMenuView.xaml
<Menu IsMainMenu="True" ItemsSource="{Binding TopMenuItems}">
<Menu.ItemContainerStyle>
<Style TargetType="{x:Type MenuItem}">
<Setter Property="Header" Value="{Binding Title}"/>
<Setter Property="ItemsSource" Value="{Binding Path=ChildMenuItems}"/>
</Style>
</Menu.ItemContainerStyle>
</Menu>
If like me you are keen to keep UI constructs in the XAML code, after some difficulty, I have worked a nice way of binding custom-typed collections to create menu items.
In XAML:
<Menu DockPanel.Dock="Top">
<MenuItem Header="SomeHeaderName" ItemsSource="{Binding Path=MyCollection}">
<MenuItem.ItemsContainerStyle>
<Setter Property="Header" Value="{Binding Path=SomeRelevantTextProperty}"/>
<EventSetter Event="Click" Handler="SomeMenuItemClickEventHandler"/>
</MenuItem.ItemsContainerStyle>
</MenuItem>
</Menu>
In code-behind:
ObservableCollection<MyClass> MyCollection;
private void SomeMenuItemClickEventHandler(object sender, RoutedEventArgs e)
{
MenuItem menuItem = sender as MenuItem;
MyClass myClass = menuItem.DataContext as MyClass;
// do something useful!
}
public class MyClass
{
public string SomeRelevantTextProperty { get; }
}

How do I set a DataTemplate for a WPF TreeView to display all Elements of an List?

I'd like to visualize the following data structure using TreeViews in WPF:
class MyDataContext
{
ICollectionView Outers {get;set;}
//...
}
class Outer
{
string Name {get;set;}
IEnumberable<Inner> Actions {get;set;}
}
class Inner
{
string Description {get;set;}
Command OnClick {get;set;}
}
This is my attempt so far:
<!-- DataContext is MyDataContext at this point -->
<TreeView ItemsSource="{Binding Path=Outers}">
<TreeView.Resources>
<DataTemplate DataType="{x:Type myns:Outer}">
<StackPanel Orientation="Horizontal">
<TextBlock Text="{Binding Path=Name}"/>
<TreeView ItemsSource="{Binding Path=Actions}" >
<DataTemplate DataType="{x:Type myns:Inner}">
<Button Command={Binding Path=OnClick}>
<TextBlock Text="{Binding Path=Description}"/>
</Button>
</DataTemplate>
</TreeView>
</StackPanel>
</DataTemplate>
</TreeView.Resources>
</TreeView>
It seams like there's something wrong with this access since I get the following InvalidOperationException:
Operation is not valid while ItemsSource is in use. Access and modify elements with ItemsControl.ItemsSource instead.
If I drop the inner TreeView there's no exception (but also no buttons of course).
I used the page Mateusz mentioned (HierarchicalDataTemplate) and after reading the answer to this question: Bind Collection to StackPanel I found a solution that did what I wanted:
Here the players (level 3) are on the same row as the team (level 2):
<TreeView ItemsSource="{Binding League}">
<!-- Conference template -->
<TreeView.ItemTemplate>
<HierarchicalDataTemplate ItemsSource="{Binding Teams}">
<TextBlock Foreground="Red" Text="{Binding Name}" />
<!-- Team template -->
<HierarchicalDataTemplate.ItemTemplate>
<DataTemplate>
<StackPanel Orientation="Horizontal">
<TextBlock Text="{Binding Name}"/>
<ItemsControl ItemsSource="{Binding Players}">
<ItemsControl.ItemsPanel>
<ItemsPanelTemplate>
<StackPanel Orientation="Horizontal">
</StackPanel>
</ItemsPanelTemplate>
</ItemsControl.ItemsPanel>
<ItemsControl.ItemTemplate>
<DataTemplate>
<Button Content="{Binding }"/>
</DataTemplate>
</ItemsControl.ItemTemplate>
</ItemsControl>
</StackPanel>
</DataTemplate>
</HierarchicalDataTemplate.ItemTemplate>
</HierarchicalDataTemplate>
</TreeView.ItemTemplate>
</TreeView>
Please try to use HierarchicalDataTemplate with TreeView.
HierarchicalDataTemplate
Maybe this helps you a little bit ;)
The xaml:
<TreeView ItemsSource="{Binding Outers}">
<TreeView.ItemTemplate>
<DataTemplate>
<TreeViewItem ItemsSource="{Binding Actions}" Header="{Binding Name}">
<TreeViewItem.ItemTemplate>
<DataTemplate>
<Button Command="{Binding Click}" Content="{Binding Name}" />
</DataTemplate>
</TreeViewItem.ItemTemplate>
</TreeViewItem>
</DataTemplate>
</TreeView.ItemTemplate>
</TreeView>
The data:
public partial class MainWindow : Window
{
public MainWindow()
{
InitializeComponent();
DataContext = new MyDataContext();
}
}
class MyDataContext
{
public ObservableCollection<Outer> Outers { get; set; }
public MyDataContext()
{
Outers = new ObservableCollection<Outer>();
Outers.Add(new Outer() { Name = "Herp" });
Outers.Add(new Outer() { Name = "Derp" });
}
}
class Outer
{
public string Name { get; set; }
public ObservableCollection<Inner> Actions { get; set; }
public Outer()
{
Actions = new ObservableCollection<Inner>();
Actions.Add(new Inner { Name = "Test1" });
Actions.Add(new Inner { Name = "Test2" });
Actions.Add(new Inner { Name = "Test3" });
Actions.Add(new Inner { Name = "Test4" });
Actions.Add(new Inner { Name = "Test5" });
Actions.Add(new Inner { Name = "Test6" });
Actions.Add(new Inner { Name = "Test7" });
}
}
class Inner
{
public string Name { get; set; }
public ICommand OnClick { get; set; }
}
And if you are using Commands...
Try it with this example:
ICommand

TreeViewItem not selectible with datacontext

When I set the treeviewitem's header via a DataContext, it adds a few pixels of padding that are clickable, and then puts the text with isn't clickable. I shall post an image; blue: clickable, red: unclickable.
The classes that store the data:
public class TagClass
{
public string TagClassMagic { get; set; }
public ITagClass RawClass { get; set; }
public List<TagEntry> TagEntries = new List<TagEntry>();
public IList Children
{
get
{
return new CompositeCollection()
{
new CollectionContainer() { Collection = TagEntries }
};
}
}
}
public class TagEntry
{
public string TagFileName { get; set; }
public ITagEntry RawTag { get; set; }
}
The XAML for displaying the Data:
<TreeView x:Name="tvTagList" Margin="15, 40, 15, 50" ItemsSource="{Binding}" VerticalAlignment="Stretch" HorizontalAlignment="Stretch" Background="{x:Null}" BorderBrush="{DynamicResource ExtryzeAccentBrushSecondary}" BorderThickness="2" ScrollViewer.CanContentScroll="True" Foreground="White">
<TreeView.Resources>
<HierarchicalDataTemplate DataType="{x:Type DataBind:TagClass}" ItemsSource="{Binding Children}" >
<TreeViewItem Header="{Binding TagClassMagic}" />
</HierarchicalDataTemplate>
<HierarchicalDataTemplate DataType="{x:Type DataBind:TagEntry}" >
<TreeViewItem Header="{Binding TagFileName}" />
</HierarchicalDataTemplate>
</TreeView.Resources>
</TreeView>
Try to remove this ones:
<TreeViewItem Header="{Binding TagClassMagic}" />
<TreeViewItem Header="{Binding TagFileName}" />
and instead add a data templates for TagEntry accordingly - put just simple textblocks in those data templates

Categories