Is there a way to have the Parent node in a Treeview too have some differentiation so the user knows that is the parent they are under? It could be to highlight the cell or change the text but I want to be able to click on an item inside a folder but the parent node is different so the user knows what they have clicked inside of.
Thank you.
Model Level - Have "Parent" property in your model class.
E.g. if you have a class that is recursively bound to the TreeView say MyItemClass
public class MyItemClass
{
public string MyHeader { get; set; } //Header text of each tree view item.
public MyItemClass Parent { get; set; } //Parent MyItemClass object.
public List<MyItemClass> Children { get; set; } //Children MyItemClass
public bool IsSelected { get; set; } //When tree view item is clicked & selected.
public bool IsHighlighted { get; set; } //When parent is highlighted.
}
So when child is selected ...
private bool _isSelected;
public bool IsSelected
{
get { retrn _isSelected; }
set
{
_isSelected = value;
//The line below highlights the parent when either of the child is selected.
this.Parent.IsHighlighted
= this.Parent.Children.Where(item => item.IsSelected).Any();
//Notify Property Changed here...
}
}
So as the parent highlights...
private bool _isHighlighted;
public bool IsHighlighted
{
get { retrn _isHighlighted; }
set
{
_isHighlighted = value;
//When a node is highlighted all its Parent nodes should be too.
this.Parent.IsHighlighted
= this.Parent.Children.Where(item => item.IsHighlighted).Any();
//Notify Property Changed here...
}
}
You can then use this IsHighlighted flag from undelrying data context of the tree view item and then apply appropriate style to that TreeViewItem.
<TreeView ItemsSource="{Binding MyItems}">
<TreeView.ItemTemplate>
<HierarchicalDataTemplate ItemsSource="{Binding Children}">
<TextBlock Text="{Binding MyHeader}">
<TextBlock.Style>
<Style TargetType="{x:Type TextBlock}">
<Style.Triggers>
<DataTrigger Binding="{Binding IsHighlighted}"
Value="True">
<Setter Property="FontWeight" Value="Bold"/>
</DataTrigger>
</Style.Triggers>
</Style>
</TextBlock.Style>
</TextBlock>
</HierarchicalDataTemplate>
</TreeView.ItemTemplate>
</TreeView>
Make sure that you implement INotifyPropertyChanged on all the properties in your item class otherwise these effects wont work.
Related
Following the example from Omer van Kloeten I filled a menu with menu items bound from my observable collection. I got a single menu item for a single collection item. Fine, but I actually want more. I also want to be able to have a menu item with two or three sub items for one collection item. Here is some rough sketch:
What I Have What I Want
+ First Item + Create First Item
+ Second Item + Second Item
+ Delete
+ Update
Here "First Item" has property Exists = false, but "Second Item" has it true. My current code:
public class CollectionItem
{
public string Name { get; set; }
public bool Exists { get; set; }
}
public partial class MainWindow : Window
{
ObservableCollection<CollectionItem> items;
public MainWindow()
{
items = new ObservableCollection<CollectionItem>();
items.Add(new CollectionItem() { Name = "First Item", Exists = false });
items.Add(new CollectionItem() { Name = "Second Item", Exists = true });
AllItems.ItemsSource = items;
}
}
<MenuItem x:Name="AllItems" Header="What I Have">
<MenuItem.ItemContainerStyle>
<Style TargetType="{x:Type MenuItem}">
<Setter Property="Header" Value="{Binding Path=Name}" />
</Style>
</MenuItem.ItemContainerStyle>
</MenuItem>
How do I mix simple menu items with sub items?
How do I mix simple menu items with sub items?
First, you need to have your data structured appropriately, with collection properties to represent the child, or sub MenuItems. Then, you need to data bind the collection property to the ItemsSource property of the parent MenuItem using the MenuItem.ItemContainerStyle, similar to what you're doing now. You should end up with something like this:
public class CollectionItem
{
public string Name { get; set; }
public bool Exists { get; set; }
public ObservableCollection<CollectionItem> CollectionOfSubItems { get; set; }
}
...
<MenuItem x:Name="AllItems" Header="What I Have">
<MenuItem.ItemContainerStyle>
<Style TargetType="{x:Type MenuItem}">
<Setter Property="Header" Value="{Binding Path=Name}" />
<Setter Property="ItemsSource" Value="{Binding CollectionOfSubItems}" />
</Style>
</MenuItem.ItemContainerStyle>
</MenuItem>
I seem to have a problem with adding rows to a DataGridthrough the interface itself.
Here is a screenshot of the UI:
As you can see, 0 rows were found in the database so nothing shows up in the DataGrid on the right side.
But id like there to be one empty row there, for manually adding rows.
The DataGrid.CanUserAddRows is set to True but has no effect.
Here is the xaml for the DataGrid, I have taken the liberty of removing some of the code to make it smaller.
PrivilegeDetailsView.xaml
<UserControl ...
d:DataContext="{d:DesignInstance impl:PrivilegeDetailsViewModel}">
<DataGrid ...
ItemsSource="{Binding RolesHasPrivilegesOnObjects}"
AutoGenerateColumns="False"
CanUserAddRows="True">
<DataGrid.Columns>
<DataGridTemplateColumn Header="Type" CanUserSort="True">
<DataGridTemplateColumn.CellTemplate>
<DataTemplate DataType="{x:Type int:IRoleHasPrivilegeOnObjectListItemViewModel}">
<Image Source="{Binding Icon}" ToolTip="{Binding ToolTip}"/>
</DataTemplate>
</DataGridTemplateColumn.CellTemplate>
</DataGridTemplateColumn>
<DataGridTextColumn Width="*" Header="Name" Binding="{Binding Name}"/>
<DataGridCheckBoxColumn Header="Select" Binding="{Binding HasSelect, UpdateSourceTrigger=PropertyChanged}">
<DataGridCheckBoxColumn.ElementStyle>
<Style TargetType="CheckBox">
<Style.Triggers>
<DataTrigger Binding="{Binding CanHaveSelect}" Value="True">
<Setter Property="IsEnabled" Value="True"/>
<Setter Property="HorizontalAlignment" Value="Center"/>
</DataTrigger>
<DataTrigger Binding="{Binding CanHaveSelect}" Value="False">
<Setter Property="IsEnabled" Value="False"/>
<Setter Property="HorizontalAlignment" Value="Center"/>
</DataTrigger>
</Style.Triggers>
</Style>
</DataGridCheckBoxColumn.ElementStyle>
</DataGridCheckBoxColumn>
...
</DataGrid.Columns>
</DataGrid>
</UserControl>
PrivilegeDetailsView.xaml.cs
public partial class PrivilegeDetailsView : IPrivilegeDetailsView
{
public PrivilegeDetailsView() { InitializeComponent(); }
public DataGrid PrivilegesOnObjectsDataGrid { get { return PrivilegeDataGrid; } }
public IViewModel ViewModel
{
get { return (IViewModel)DataContext; }
set { DataContext = value; }
}
}
Here is the ViewModel (VM) for the xaml View above:
PrivilegeDetailsViewModel.cs
public class PrivilegeDetailsViewModel : ViewModelBase, IPrivilegeDetailsViewModel
{
private readonly IEventAggregator _eventAggregator;
private readonly IPrivilegeViewModel _privilegeViewModel;
private readonly IRoleHasPrivilegeOnObjectViewModelAdapterRepository _roleHasPrivilegeOnObjectViewModelAdapterRepository;
private ObservableCollection<IRoleHasPrivilegeOnObjectListItemViewModel> _rolesHasPrivilegesOnObjects;
public PrivilegeDetailsViewModel(IPrivilegeDetailsView view,
IRoleHasPrivilegeOnObjectViewModelAdapterRepository roleHasPrivilegeOnObjectViewModelAdapterRepository,
IPrivilegeViewModel privilegeViewModel,
IEventAggregator eventAggregator) : base(view)
{
_roleHasPrivilegeOnObjectViewModelAdapterRepository = roleHasPrivilegeOnObjectViewModelAdapterRepository;
_privilegeViewModel = privilegeViewModel;
_eventAggregator = eventAggregator;
Initialize();
}
protected override sealed void Initialize()
{
_privilegeViewModel.PropertyChanged += PrivilegeViewModelOnPropertyChanged;
_eventAggregator.GetEvent<ToggleSelectPrivilegeEvent>().Subscribe(ToggleSelectPrivilege);
...
}
public new IPrivilegeDetailsView View
{
get { return (IPrivilegeDetailsView)base.View; }
}
public ObservableCollection<IRoleHasPrivilegeOnObjectListItemViewModel> RolesHasPrivilegesOnObjects
{
get { return _rolesHasPrivilegesOnObjects; }
set
{
_rolesHasPrivilegesOnObjects = value;
OnPropertyChanged();
}
}
public void Save()
{
if(RolesHasPrivilegesOnObjects == null) return;
_roleHasPrivilegeOnObjectViewModelAdapterRepository.SaveChanges(RolesHasPrivilegesOnObjects);
}
private void ToggleExecutePrivilege(object obj)
{
var toggle = !View.PrivilegesOnObjectsDataGrid.SelectedItems.Cast<IRoleHasPrivilegeOnObjectListItemViewModel>()
.All(x => x.HasExecute);
foreach(var selectedItem in View.PrivilegesOnObjectsDataGrid
.SelectedItems
.Cast<IRoleHasPrivilegeOnObjectListItemViewModel>()
.Where(selectedItem => selectedItem.Object
.CanHavePrivilege("EXECUTE"))) {
selectedItem.HasExecute = toggle;
}
}
...
private void PrivilegeViewModelOnPropertyChanged(object s, PropertyChangedEventArgs e)
{
switch(e.PropertyName)
{
//When the SelectedSchema changes in the parent VM, I get the new rows to be shown in the DataGrid.
case "SelectedSchema":
RolesHasPrivilegesOnObjects = _roleHasPrivilegeOnObjectViewModelAdapterRepository
.GetPrivilegesOnObjectsAssociatedWith((IRoleEntityViewModel)_privilegeViewModel.SelectedRole,
(IContainerEntityViewModel)_privilegeViewModel.SelectedSchema);
break;
}
}
}
This is the VM for each row in the DataGrid
RoleHasPrivilegeOnObjectEntityViewModel.cs
public class RoleHasPrivilegeOnObjectEntityViewModel : EntityViewModelBase<RoleHasPrivilegeOnObjectEntityViewModel,
RoleHasPrivilegesOnObject>,
IRoleHasPrivilegeOnObjectListItemViewModel
{
private readonly RoleHasPrivilegesOnObject _roleHasPrivilegesOnObject;
public RoleHasPrivilegeOnObjectEntityViewModel(RoleHasPrivilegesOnObject roleHasPrivilegesOnObject)
{
_roleHasPrivilegesOnObject = roleHasPrivilegesOnObject;
Role = new RoleEntityViewModel(_roleHasPrivilegesOnObject.Role);
Object = new ObjectEntityViewModel(_roleHasPrivilegesOnObject.Object);
}
public override EntityType EntityType { get { return EntityType.NONE; } }
public override RoleHasPrivilegesOnObject OriginalEntity { get { return _roleHasPrivilegesOnObject; } }
public IRoleEntityViewModel Role { get; set; }
public IObjectEntityViewModel Object { get; set; }
public string ToolTip { get { return _roleHasPrivilegesOnObject.ToolTip; } }
public bool HasExecute
{
get { return _roleHasPrivilegesOnObject.HasExecute; }
set
{
_roleHasPrivilegesOnObject.HasExecute = value;
OnPropertyChanged();
}
}
public bool CanHaveExecute { get { return _roleHasPrivilegesOnObject.CanHaveExecute; } }
public override string Icon { get { return Object != null ? Object.Icon : string.Empty; } }
public override string NAME
{
get { return _roleHasPrivilegesOnObject.NAME; }
set
{
_roleHasPrivilegesOnObject.NAME = value;
OnPropertyChanged();
}
}
...
}
I know this is a lot of code, I have stripped away a lot and put in place a few dots (...) to show that more code exist. NOTE: Im using EF5 and PRISM
How can I make the DataGrid accept new rows through the GUI?
I believe your problem is using ObservableCollection<IRoleHasPrivilegeOnObjectListItemViewModel> as ItemsSource. In order for DataGrid to be able to create a new row, there has to be a type that can be constructed with an empty constructor.
If you changed it to say ObservableCollection<RoleHasPrivilegeOnObjectEntityViewModel> instead, i'm pretty sure your rows will start getting added.
What I ended up doing was partially/Mostly what Maverik suggested.
I changed ObservableCollection<IRoleHasPrivilegeOnObjectListItemViewModel> to be ObservableCollection<RoleHasPrivilegeOnObjectEntityViewModel> and created a default constructor, which it didn't previously have.
The issue then was that the RoleHasPrivilegeOnObjectEntityViewModel needs some fields and properties set in order to function, so I created a public Initialize function to provided the necessary parameters.
I added an event handler to the DataGrid's InitializingNewItem event, where i called the Initialize function.
private void PrivilegesOnObjectsDataGridOnInitializingNewItem(object s, InitializingNewItemEventArgs e)
{
var newItem = e.NewItem as RoleHasPrivilegeOnObjectEntityViewModel;
if (newItem == null) return;
var role = _privilegeViewModel.SelectedRole;
var schema = _privilegeViewModel.SelectedSchema;
newItem.Initialize(role.OriginalEntity, schema.OriginalEntity);
}
When trying to adda new row, clicking the ComboBox didn't fire off the InitializeNewItem event. But clicking any other column fired off the InitializeNewItem event, and since at first each Row's VM had it's own AvailableObjectTypes property, the ComboBox ItemSource was not set if the ComboBox was selected before any other column, thus making it empty.
That was not an acceptable behaviour so moving AvailableObjectTypes to the PrivilegeDetailsViewModel and changing the ComboBox's ItemSource binding to this helped
ItemsSource="{Binding DataContext.AvailableObjectTypes, RelativeSource={RelativeSource AncestorType={x:Type UserControl}}}"
I am new to WPF and am trying to bind a context menu to an ObservableCollection of models. I am able to get the context menu to display but I can't get the commands to fire. Here is my code
<ContextMenu ItemsSource="{Binding ContextMenuList}">
<ContextMenu.ItemContainerStyle>
<Style TargetType="{x:Type MenuItem}">
<Setter Property="MenuItem.Header" Value="{Binding Name}" />
<Setter Property="MenuItem.ItemsSource" Value ="{Binding Children}" />
<Setter Property="Command" Value="{Binding MenuCommand}"/>
</Style>
</ContextMenu.ItemContainerStyle>
</ContextMenu>
In the View Model I have
public class MenuItemViewModel
{
public string Name { get; set; }
public ObservableCollection Children { get; set; }
public ICommand MenuCommand;
public MenuItemViewModel(string name, ObservableCollection pChildren, ICommand pCommand)
{
this.Name = name;
this.Children = pChildren;
this.MenuCommand = pCommand;
}
}
and I populate the collection
public void AddContextMenuItems()
{
this.ContextMenuList = new ObservableCollection<MenuItemViewModel>();
ICommand pCommand = new ActionCommand(() => this.ChangeChartType());
MenuItemViewModel ohlc = new MenuItemViewModel("OHLC",null,pCommand);
MenuItemViewModel candlestick = new MenuItemViewModel("CandleStick");
ObservableCollection<MenuItemViewModel> chartTypeColl = new ObservableCollection<MenuItemViewModel>();
chartTypeColl.Add(ohlc);
chartTypeColl.Add(candlestick);
this.ContextMenuList.Add(new MenuItemViewModel("ChartType",chartTypeColl));
}
I don't see the ChangeChartType method ever get triggered, what I am I doing wrong here ?
Try to change MenuCommand field to property
public ICommand MenuCommand { get; set; }
Hope this work for you. I haven't tried this though.
I have a datagrid that potentially can have many rows. As the user right clicks one of the rows, I need to show a context menu for each of the rows and perform an action (same action but different data item according to the current selected row) when the user clicks the option.
What is the best strategy for this?
I'm fearing that a ContextMenu for each row is overkill even though I'm creating the menu using the ContextMenuOpening event, sort of a "lazy load" for the context menu. Should I only use one ContextMenu for the datagrid? But with this I would have some more work regarding the click event, to determine the correct row, etc.
As far as I know, some of the actions will be disabled or enabled depending on the row, so there is no point in a single ContextMenu for a DataGrid.
I have an example of the row-level context menu.
<UserControl.Resources>
<ContextMenu x:Key="RowMenu" DataContext="{Binding PlacementTarget.DataContext, RelativeSource={RelativeSource Self}}">
<MenuItem Header="Edit" Command="{Binding EditCommand}"/>
</ContextMenu>
<Style x:Key="DefaultRowStyle" TargetType="{x:Type DataGridRow}">
<Setter Property="ContextMenu" Value="{StaticResource RowMenu}" />
</Style>
</UserControl.Resources>
<DataGrid RowStyle="{StaticResource DefaultRowStyle}"/>
The DataGrid must have a binding to a list of view models with commands:
public class ItemModel
{
public ItemModel()
{
this.EditCommand = new SimpleCommand
{
ExecuteDelegate = _ => MessageBox.Show("Execute"),
CanExecuteDelegate = _ => this.Id == 1
};
}
public int Id { get; set; }
public string Title { get; set; }
public ICommand EditCommand { get; set; }
}
The context menu is created in the resources collection of the UserControl and I think there is only one object which is connected with datagrid rows by reference, not by value.
Here is another example of ContextMenu for a Command inside a MainViewModel. I suppose that DataGrid has a correct view model as the DataContext, also the CommandParameter attribute must be placed before the Command attribute:
<ContextMenu x:Key="RowMenu" DataContext="{Binding PlacementTarget.DataContext, RelativeSource={RelativeSource Self}}">
<MenuItem Header="Edit" CommandParameter="{Binding}"
Command="{Binding DataContext.DataGridActionCommand, RelativeSource={RelativeSource Mode=FindAncestor, AncestorType=DataGrid}}" />
</ContextMenu>
Models:
public class MainViewModel
{
public MainViewModel()
{
this.DataGridActionCommand = new DelegateCommand<ItemModel>(m => MessageBox.Show(m.Title), m => m != null && m.Id != 2);
}
public DelegateCommand<ItemModel> DataGridActionCommand { get; set; }
public List<ItemModel> Items { get; set; }
}
public class ItemModel
{
public int Id { get; set; }
public string Title { get; set; }
}
But there is a problem that MenuItem isn't displayed as a disabled item if CanExecute returns false. The possible workaround is using a ParentModel property inside the ItemModel, but it doesn't differ much from the first solution.
Here is example of above-described solution:
public class ItemModel
{
public int Id { get; set; }
public string Title { get; set; }
public MainViewModel ParentViewModel { get; set; }
}
//Somewhere in the code-behind, create the main view model
//and force child items to use this model as a parent model
var mainModel = new MainViewModel { Items = items.Select(item => new ItemViewModel(item, mainModel)).ToList()};
And MenuItem in XAML will be simplier:
<MenuItem Header="Edit" CommandParameter="{Binding}"
Command="{Binding ParentViewModel.DataGridActionCommand}" />
Given the following XAML snippet:
<UserControl.Resources>
<ResourceDictionary>
<Style x:Key="ContextMenuItemStyle">
<Setter Property="MenuItem.Header" Value="{Binding Text}"/>
<Setter Property="MenuItem.ItemsSource" Value="{Binding Children}"/>
<Setter Property="MenuItem.Command" Value="{Binding Command}" />
</Style>
<ContextMenu x:Key="contextMenu" ItemsSource="{Binding MenuOptions}" ItemContainerStyle="{StaticResource ContextMenuItemStyle}" />
</ResourceDictionary>
</UserControl.Resources>
<DockPanel>
<TextBox Height="30" DockPanel.Dock="Top" ContextMenu="{StaticResource contextMenu}" />
<Button Content="Add Menu Item" DockPanel.Dock="Top" Command="{Binding AddMenuItem}" />
</DockPanel>
And View Model:
public class MyViewModel {
public ObservableCollection<MenuItem> DocumentExplorerMenuOptions { get; set; }
MenuItem firstMenuItem;
MenuItem secondMenuItem;
public MyViewModel() {
firstMenuItem = new MenuItem("First") { Command = new DelegatingCommand(x => MessageBox.Show("First Selected") };
secondMenuItem = new MenuItem("Second") { Command = new DelegatingCommand(x => MessageBox.Show("Second Selected") };
MenuOptions = new ObservableCollection<MenuItem> { firstMenuItem, secondMenuItem };
AddMenuItem = new DelegateCommand<object>(x => firstMenuItem.Children.Add(
new MenuItem("Child of First")));
}
public DelegateCommand<object> AddMenuItem { get; set; }
}
And class:
public class MenuItem {
public MenuItem(string text) {
Text = text;
Children = new List<MenuItem>();
}
public string Text { get; set; }
public List<MenuItem> Children { get; private set; }
public ICommand Command { get; set; }
}
Clicking the button does add the child to firstMenuItem but it does not appear in the context menu of the TextBox.
I can't figure out how to make the context menu show the dynamic content of the context menu. Any thoughts?
I would not bind to a collection of MenuItems but rather to a more data-driven collection which may contain the MenuItem header, a command which is executed upon click and another collection of such items for the sub-items. Then you could use a (Hierarchical)DataTemplate to generate the menu on the fly. Doing so would probably take care of update issues if your datatype implements the necessary interfaces.
Edit: You seem to have such a datatype already, could you post its code?
Edit2: I think the problem is that you use a style that explicitly needs to be applied (it is probably only being applied to the main context menu, not the sub-items), as noted before i'd suggest a HierarchicalDataTemplate.
Edit3: lol...
public List<MenuItem> Children { get; private set; }
Of course it's not going to update if it's a List and not an ObservableCollection.
(The class is quite badly designed overall by the way, Lists should normally not even have a private setter, they should be properties with just a getter to a readonly field)