WPF How to load Items inside ContextMenu without open it? - c#

I try to get MenuItem from ContextMenu. If I open context menu at least once it works fine, otherwise I get error ItemCollection has no inner collection. As I understand this behavior is caused by ItemSource binding. How can I load Items without manual open context menu?
XAML:
<StackPanel>
<Rectangle Fill="Red" Height="100" Width="100">
<Rectangle.ContextMenu>
<ContextMenu x:Name="MainContextMenu" ItemsSource="{Binding Items}">
<ContextMenu.ItemContainerStyle>
<Style TargetType="MenuItem">
<Setter Property="Header" Value="{Binding}"/>
</Style>
</ContextMenu.ItemContainerStyle>
</ContextMenu>
</Rectangle.ContextMenu>
</Rectangle>
<Button Content="Click" Command="{Binding GetMenuItemCommand}"
CommandParameter="{Binding ElementName=MainContextMenu}"
Height="30" Width="100"/>
</StackPanel>
ViewModel:
public class ViewModel
{
public List<string> Items { get; }
public MyCommand GetMenuItemCommand { get; }
public ViewModel()
{
Items = new List<string>()
{
"a0", "a1", "a2", "a3", "a4", "a5"
};
GetMenuItemCommand = new MyCommand(GetMenuItem);
}
public void GetMenuItem(object obj)
{
var contextMenu = (ContextMenu)obj;
// Here I get exception
var item = contextMenu.Items[0] as MenuItem;
var header = item.Header
}
}
MyCommand
public class MyCommand : ICommand
{
private Action<object> execute;
public MyCommand(Action<object> execute)
{
this.execute = execute;
}
public event EventHandler? CanExecuteChanged;
public bool CanExecute(object? parameter) => true;
public void Execute(object? parameter)
{
execute?.Invoke(parameter);
}
}
NOTE: It is demo code, I'm not following MVVM approach here
EDIT: Full exception message: System.InvalidOperationException: 'Operation is not valid while ItemCollection has no inner collection. ItemCollection is uninitialized or binding on ItemsControl.ItemSource supplied null for collection.'
EDIT 2: As people mentioned in the comments, probably purpose of getting MenuItem is not clear, so I introduce more details.
I'm trying to write helper class, that will merge child context menu with parent menu if needed. I do not know anything about context menus I will merge. I am currently trying to copy elements from parent menu and add them to child menu, when child menu is opening. For that reason I need to create new MenuItem, copy properties from parent's MenuItem and add this new MenuItem to child's context menu. My solution works fine, but only when parent menu was opened at least once.
I already asked straight question about merging context menus, but got no appropriate answers, so there I tried to ask something different.

There is no MenuItem until the ContextMenu has been opened. And passing a ContextMenu to a view model breaks the MVVM pattern.
You should get the underlying data value from the source collection instead of trying to access the eventually created visual element. The MVVM version of your code looks something like this:
public void GetMenuItem(object _)
{
var item = Items[0];
...
}
Obviously a view model cannot create additional MenuItem elements. It may add more strings to the source collection though.

Related

How to tell which Button has been clicked, when it's generated dynamically? (MVVM)

I have a SearchResultsViewModel with observable collection of recipe class and a command to show a recipe:
private ObservableCollection<Recipe> _searchedRecipes;
public ObservableCollection<Recipe> SearchedRecipes
{
get
{
return _searchedRecipes;
}
set
{
_searchedRecipes = value;
OnPropertyChanged();
}
}
#endregion
#region Show Recipe Command
public ICommand ShowRecipeCommand { get { return new RelayCommand(() =>
ExecuteShowRecipeCommand()); } }
public void ExecuteShowRecipeCommand()
{
_locator.Main.CurrentViewModel = new DisplayRecipeViewModel();
}
#endregion
Another ViewModel performs a query and passes results in the constructor of this ViewModel.
In XAML part of the SearchResultsViewModel, results are presented as Buttons dynamically. Each Recipe is a Button with it's name as content:
<StackPanel>
<ItemsControl ItemsSource="{Binding Path = SearchedRecipes}">
<ItemsControl.ItemTemplate>
<DataTemplate>
<Button Content="{Binding Path=Name}" Command="{Binding ShowRecipeCommand}"/>
</DataTemplate>
</ItemsControl.ItemTemplate>
</ItemsControl>
</StackPanel>
I want ShowRecipeCommand to create new DisplayRecipeViewModel with a View bound to it, displaying the properties of Recipe that was clicked but I don't know how
to tell which Button was clicked.
Is it possible to do this without code behind ??
You could just move the command property to the Recipe class. Then each Button (or rather each data object that is represented by a Button) has its own command and you always know which one that was clicked.
If the Recipe class is auto-generated by some ORM such as for example Entity Framework, you could create another partial class where you define the command property.

How does databinding between ContextMenuItems of a DataGridRow and the DataGridRow itself to properties within encapsulated Viewmodel works?

I know I really should start reading a book about XAML and WPF because I think all my Problems here belong to a lack of understanding about Data Binding (I used WinForms for years):
My Application consists of a TreeView and a DataGrid.
In the TreeView I have added ViewModels for each ParentNode, ChildNode an GrandChildNode.
I've used the sample from Josh Smith found here.
To be short, he/I used
<HierarchicalDataTemplate
DataType="{x:Type local:ViewModel.TreeViewChildNodeViewModel}"
ItemsSource="{Binding Children}">
</HierarchicalDataTemplate.Resources>
to bind the ChildNode to a ChildNodeViewModel and to the corresponding Model.
I than added - in the TreeViewChildNodeViewModel constructor:
ContextMenuItems = new List<MenuItem>();
ContextMenuItems.Add(new MenuItem() {
Header = "Click",
Command = _cmdDoSmth
ToolTip = new ToolTip() { Content = "blabla" }
}
);
which is exposed to the View with this property:
private readonly List<MenuItem> ContextMenuItems;
public List<MenuItem> ContextMenu {
get { return ContextMenuItems; }
}
Note that, I have multiple constructors. I add different ContextMenuItems to the ContextMenu List depending on what Model i want the ViewModel to work with. The "root" ChildNode consist of a:
<TextBlock
Text="{Binding ChildNodeDisplayItem}">
<TextBlock.ContextMenu>
<ContextMenu
ItemsSource="{Binding ContextMenu}"></ContextMenu>
</TextBlock.ContextMenu>
</TextBlock>
That works like it should. Now my problems start with trying to do some similar with the datagrid.
What I need to achieve is:
I'd like to show rows in the datagrid. Each Row has its own Viewmodel with an exposed List of ContextMenuItem's (as well as the model of course). I'd like to be able to define the count, header and command of each contextmenuitem in dependence of the viewmodel that is selected.
What I did so far:
In my MainWindow.xaml:
<Controls:MetroWindow.Resources>
<ContextMenu x:Key="DataRowContextMenu" ItemsSource="{Binding Path=ActionReactionDataGridViewModel/ContextMenu, RelativeSource={RelativeSource AncestorType=DataGrid, Mode=FindAncestor}}"/>
</Controls:MetroWindow.Resources>
<DataGrid
AutoGenerateColumns="True"
AutoGeneratingColumn="OnAutoGeneratingColumn"
HorizontalAlignment="Stretch"
VerticalAlignment="Stretch"
BorderThickness="1,1,1,1"
Margin="0,0,0,0"
ItemsSource="{Binding Path=ActionReactionDataGridViewModel/DataGridSource}"
SelectedItem="{Binding Path=ActionReactionDataGridViewModel/SelectedDataGridItem}"
BorderBrush="#FF020202">
<DataGrid.RowStyle>
<Style TargetType="{x:Type DataGridRow}">
<Setter Property="ContextMenu" Value="{StaticResource RowMenu}" /> </Style>
<DataGrid.RowStyle>
</DataGrid>
In my MainWindowViewModel:
public MainWindowViewModel() // Constructor
{
actionReactionDataGrid = new ObservableCollection<ActionReactionDataGridViewModel>();
actionReactionDataGrid.Add(new ActionReactionDataGridViewModel());
}
private ObservableCollection<ActionReactionDataGridViewModel> actionReactionDataGrid;
public ObservableCollection<ActionReactionDataGridViewModel> ActionReactionDataGridViewModel
{
get { return actionReactionDataGrid; }
}
My ActionReactionDataGridViewModel is here:
public class ActionReactionDataGridViewModel : ViewModelBase
{
private readonly List<MenuItem> ContextMenuItems;
public ActionReactionDataGridViewModel()
{
ContextMenuItems = new List<MenuItem>();
ContextMenuItems.Add(new MenuItem()
{
Header = "blubb"
});
dataGridSource = new ObservableCollection<ActionReactionDataGridModel>();
dataGridSource.Add(new ActionReactionDataGridModel("Status","Eventname","Eventtyp","ReaktionName","ReaktionTyp"));
}
public List<MenuItem> ContextMenu {
get { return ContextMenuItems; }
}
private ActionReactionDataGridModel selectedDataGridItem;
public ActionReactionDataGridModel SelectedDataGridItem {
get { return selectedDataGridItem; }
set {selectedDataGridItem = value; RaisePropertyChanged("SelectedDataGridItem"); }
}
private ObservableCollection<ActionReactionDataGridModel> dataGridSource;
public ObservableCollection<ActionReactionDataGridModel> DataGridSource {
get { return dataGridSource; }
set { dataGridSource = value; RaisePropertyChanged("DataGridSource"); }
}
}
I think posting the content of the model is not neccessary because it just contains the column headers and some sample strings. I think what iam missing is the knowledge of telling the DataGrid Control in the View in MainWindow.xaml to bind the itemssource to "DataGridSource" instead of "ActionReactionDataGridViewModel".
I found other posts on SO about adding Context Menus to a datagridrow. what i was missing is the ability to bind the count, text and command to each viewmodel.
Any Help would be greatly appreciated.
Thank you.
// EDIT 1
Ok. finding out how to pass the property of a viewmodel from inside a collection of viewmodels was easy.
i added
ItemsSource="{Binding Path=ActionReactionDataGridViewModel/DataGridSource}
explanation is here
Now I "just" need to figure out how to add the contextmenu items to each viewmodel...
<DataGrid.ContextMenu>
<ContextMenu>
<MenuItem Header="HeaderName">
</MenuItem>
</ContextMenu>
</DataGrid.ContextMenu>
inside menu item you can write your control.

WPF TreeView context menu is disabled if no items exist

I am trying to show a ContextMenu in a TreeView. Some entries must be available whether an item was selected or not, but all commands are disabled until I populate the TreeView with at least one item:
<TreeView Name="myTreeView" Width="200px">
<TreeView.ContextMenu>
<ContextMenu>
<MenuItem Command="New" IsEnabled="True" />
</ContextMenu>
</TreeView.ContextMenu>
<TreeView.ItemTemplate>
<HierarchicalDataTemplate>
<TextBlock Text="{Binding Path=Title}" />
</HierarchicalDataTemplate>
</TreeView.ItemTemplate>
</TreeView>
However, the menu item is still disabled:
The very same command is enabled in the File menu in the menu bar and there is no CanExecute attribute.
How can I enable the context menu entry even if no item exists?
The issue is that the DataContext of the ContextMenu (i.e. where it's looking to bind the New command) is the tree-view node, not the tree view itself. Great if you've got commands related to the node - editing, moving, changing settings.
Not so good for the few that are pan-node like adding and deleting.
As it's looking in the node's DataContext (and no nodes exit) it can't find the command (and it doesn't make sense for it to be there anyway, as the object that manages the TreeView should be creating new items, not the items themselves).
The solution is to bind to a New command that's not in the DataContext of the item, but the TreeView. There's the frustration of dealing with data-binding with ContextMenu... as it's not in the same visual tree as the rest of the window it's often frustrating to deal with.
A solution is to reference the PlacementTarget of the context menu like this:
<TreeView Name="myTreeView" Width="200px">
<TreeView.ContextMenu>
<ContextMenu>
<MenuItem Header="Edit (This command exists in the Node's ViewModel)" Command="{Binding Edit}"/>
<MenuItem Header="New (This command exists in the Window's ViewModel)" Command="{Binding PlacementTarget.DataContext.New, RelativeSource={RelativeSource FindAncestor, AncestorType={x:Type ContextMenu}}}"/>
</ContextMenu>
</TreeView.ContextMenu>
<TreeView.ItemTemplate>
<HierarchicalDataTemplate>
<TextBlock Text="{Binding Path=Title}" />
</HierarchicalDataTemplate>
</TreeView.ItemTemplate>
</TreeView>
Further questions
An example of adding a command as a static resource (Change Window to UserControl if you're in a view that's a UserControl):
<Window.Resources>
<local:MyCommand x:Key="MyCommand"/>
</Window.Resources>
Then referenced with:
<MenuItem Header="MyCommand" Command="{StaticResource MyCommand}"/>
Binding to your commands in the ViewModel (i.e. DataContext) is done like in the first example. In the same way you bind the Title, you can bind to any property, such as an ICommand.
So for a view:
<MenuItem Header="New" Command="{Binding New}"/>
The View Model has a property NewCommand named New:
public NewCommand New { get; private set; }
People often use this because they have a generic ICommand that takes a delegate so they can configure all the actions that relate to that ViewModel. For example:
public class MyCommand : ICommand
{
public event EventHandler CanExecuteChanged;
public Action<object> Action { get; set; }
public MyCommand(Action<object> action)
{
Action = Action;
}
public bool CanExecute(object parameter)
{
return true;
}
public void Execute(object parameter)
{
Action(parameter);
}
}
Then in the ViewModel, instead of having loads of ICommand classes all implemented, we can just re-use this and get it to do different things:
public MyCommand New { get; private set; }
public MyCommand Delete { get; private set; }
public MyCommand ClearAll { get; private set; }
public MyViewModelConstructor()
{
New = new MyCommand((parameter) =>
{
//Add new object
});
Delete = new MyCommand((parameter) =>
{
//Delete object
});
ClearAll = new MyCommand((parameter) =>
{
//Clear all objects
});
}

ComboBox with "Refresh" Entry

I've the following situation in my project and i'm wondering what's the best way to achieve my goal.
Goal: Having a combobox with itemsource binding and one entry which is functioning like a refresh button (fetching items from database and update combobox items).
Currently I set up my combobox with itemsource binding (see below), but currently i'm struggling with the binding of the command for refreshing.
ItemsSource Binding:
<UserControl.Resources>
<CollectionViewSource x:Key="ProjectSource" Source="{Binding Projects, ElementName=Ancestor}"/>
<CompositeCollection x:Key="ProjectCollection">
<CollectionContainer Collection="{Binding Source={StaticResource ProjectSource}}"/>
<Button Content="Refresh!"/>
</CompositeCollection>
</UserControl.Resources>
Where Projects is a dependency property with an enumeration of items, another dependency property with the refresh command (an ICommand) is also available.
My ComboBox ist defined as follows:
<ComboBox SelectedValue="{Binding Project}"
ItemsSource="{StaticResource ProjectCollection}"
VerticalContentAlignment="Center"
HorizontalAlignment="Left"
Name="Box"
IsHitTestVisible="{Binding IsEditable}"
IsEnabled="{Binding IsEnabled, Mode=OneWay, IsAsync=True}">
<ComboBox.Resources>
<DataTemplate DataType="{x:Type viewModels:ProjectViewModel}">
<TextBlock Text="{Binding Name}"/>
</DataTemplate>>
</ComboBox.Resources>
</ComboBox>
The problem is that the Command can't find the source of the binding, so the question is am i on the right way doing it and there is a solution, or am I on the wrong way (which would be better?).
Sure i could just add a button next to my combobox, but i'd like to have it in my combobox. :)
Btw.: I'm trying to follow the MVVM pattern.
I have solved this issue in the past by using code behind. When the combobox loads, create a new List<objects> of the Projects and add a Refresh string (maybe "<Refresh...>") to the list, and finally setting the ItemsSource to this list. Use a template selector to show the appropriate DataTemplate. When the selection changes, check if the Refresh string was selected, and if so, do your refresh, and reload the combobox. When you refresh, you can try to set the selection back to the previously selected item, or index 0, so the user can never have "refresh" selected in the combobox.
Some snippets to demonstrate.
in ctor
SelectedProjectComboBoxTemplateSelector.StringTemplate = FindResource("StringTemplate") as DataTemplate;
SelectedProjectComboBoxTemplateSelector.ProjectTemplate = FindResource("ProjectTemplate") as DataTemplate;
SelectedProjectComboBox.SelectionChanged += SelectedProjectComboBox_SelectionChanged;
SelectedProjectComboBox.ItemTemplateSelector = new SelectedProjectComboBoxTemplateSelector();
and
void SelectedProjectComboBox_SelectionChanged(object sender, SelectionChangedEventArgs e) {
if (SelectedProjectComboBox.SelectedItem is string && ((string)SelectedProjectComboBox.SelectedItem) == RefreshProjectSelectionItem) {
object current = e.RemovedItems.Count > 0 ? e.RemovedItems[0] : null;
bool ret = RefreshData(); // from db
if (ret) {
LoadData(); // repopulate combobox
} else {
SelectedProjectComboBox.SelectedItem = current;
}
}
}
and
public class SelectedProjectComboBoxTemplateSelector : DataTemplateSelector {
public static DataTemplate StringTemplate { get; set; }
public static DataTemplate ProjectTemplate { get; set; }
public override DataTemplate SelectTemplate(object item, DependencyObject container) {
if (item == null || Designer.IsInDesignMode) return null;
if (item is string) return StringTemplate;
if (item is Project) return ProjectTemplate;
return null;
}
}
You get the idea... This should be enough to get you going if this solution meets your needs.

WPF/MVVM - Checking A MenuItem Based On String Match

I'm attempting to implement a theme-selection menu in a WPF/MVVM application. I've got the selection itself working, but can't seem to figure out how to set IsChecked on the appropriate MenuItem with pure databinding (aka without breaking the MVVM pattern).
XAML:
<MenuItem Header="_Theme">
<MenuItem Header="Classic" Command="{Binding ChangeThemeCommand}" CommandParameter="Classic" />
<MenuItem Header="Metro White" Command="{Binding ChangeThemeCommand}" CommandParameter="MetroWhite" />
</MenuItem>
ViewModel:
RelayCommand _changeThemeCommand;
public ICommand ChangeThemeCommand
{
get
{
return _changeThemeCommand ?? (_changeThemeCommand = new RelayCommand(param =>
{
ThemeManager.CurrentTheme = param.ToString();
}));
}
}
The theming is being handled by Actipro's WPF control suite (http://www.actiprosoftware.com); as you can see, the current theme is represented as a string only.
My problem lies in figuring out how to bind IsChecked in a way that will mark the MenuItem for the active theme. The way the XAML is currently structured, that would mean matching the current theme name to the MenuItem's CommandParameter.
Any tips/pointers would be greatly appreciated.
Your first problem is that you are hard-coding all your themes. Better would be to create a class called Theme:
public class Theme : INotifyPropertyChanged
{
public string Name { get; set; } // Implement PropertyChanged event on this.
public bool Checked { get; set; } // Implement PropertyChanged event on this.
}
In your main view model, have an observable collection of these, then fill it up with your themes, i.e.:
ObservableCollection<Theme> Themes { get; private set; }
In constructor, something like:
Themes.Add(new Theme() { Name = "Classic" });
Themes.Add(new Theme() { Name = "MetroWhite" });
Now your context menu should look something like:
<MenuItem Header="_Theme" ItemsSource="{Binding Themes}">
<MenuItem.ItemTemplate>
<DataTemplate>
<MenuItem Header="{Binding Name}" IsChecked="{Binding Checked}" IsCheckable="True"/>
</DataTemplate>
</MenuItem.ItemTemplate>
</MenuItem>
Now, this gives you a set of themes, and when you click on one it's Checked property is set. Now you can assign your Command to the MenuItems, preferably as part of the Theme class (i.e. Theme.Set() seems like a reasonable OO design to me). Should all be pretty straightforward from here on.
Update
How do I enforce that only one theme is selected at once?
Assuming you have a MainViewModel, extend the Theme constructor to take a reference back to the MainViewModel. Then in your SetTheme() command, iterate over all other themes making sure they are not Checked.
void SetTheme()
{
foreach (Theme theme in MainViewModel.Themes)
{
if (theme != this)
{
theme.Checked = false;
}
}
// Do actual theme setting .
}
Why should I implement INotifyPropertyChanged?
Because the above doesn't work if you dont. Sure, you could just implement it for Checked, but as a matter of good practice I recommend implementing it for all public accessible properties that form part of the interface. That way if you use this ViewModel with some different View later on that wants to edit these properties, everything will just work.

Categories

Resources