I am trying to implement in WPF a way for the user to select multiple items in one box and via button click add those selected items to the other box.
I am trying to adhere to MVVM w/ minimal code behind. The solutions I find show the DataContext being manipulated via the View code behind which I am trying to avoid.
I think my issue is I do not know how to toggle the IsSelected from xaml, but not sure.
XAML
<ListView
ItemsSource="{Binding AvailableStates, Mode=TwoWay}"
SelectedItem="{Binding SelectedStates, Mode=TwoWay}"
SelectionMode="Multiple"
DisplayMemberPath="state"
Grid.Row="1"
Margin="5"
Grid.Column="1"
Height="125"
Name="lvAvailableStates"
Grid.RowSpan="6"
ScrollViewer.VerticalScrollBarVisibility="Visible"
ScrollViewer.CanContentScroll="True">
<ListView.ItemContainerStyle>
<Style TargetType="{x:Type ListBoxItem}">
<Setter Property="IsSelected" Value="{Binding IsSelected}"/>
</Style>
</ListView.ItemContainerStyle>
</ListView>
<Button
Grid.Row="2"
Grid.Column="2"
Margin="10"
Command="{Binding AddSelectedStatesCommand}"
Content=">>" />
ViewModel
private ObservableCollection<SelectableItemWrapper<states_approved>> _selectedStates;
public ObservableCollection<SelectableItemWrapper<states_approved>> SelectedStates
{
get { return _selectedStates; }
set
{
_selectedStates = value;
OnPropertyChanged();
}
}
private void AddSelectedStates(object obj)
{
var selected = SelectedStates.Where(s => s.IsSelected)
.Select(s => s.Item)
.ToList();
StatesApproved = selected;
}
public CustomCommand AddSelectedStatesCommand
{
get
{
return new CustomCommand(AddSelectedStates, CanExecute);
}
}
Selected Item Wrapper
public class SelectableItemWrapper<T>
{
public bool IsSelected { get; set; }
public T Item { get; set; }
}
ListView has internal property to determine which item is selected and it also has SelectedItems to determine multiple selected items. However, this plural SelectedItems of ListView is not bindable. So, the solution is to pass them as a CommandParameter.
<ListView x:Name="lvAvailableStates"
ItemsSource="{Binding AvailableStates, Mode=TwoWay}"
SelectedItem="{Binding SelectedStates, Mode=TwoWay}" => remove this!
...
<Button Command="{Binding AddSelectedStatesCommand}"
CommandParameter="{Binding SelectedItems, Mode=OneWay, ElementName=lvAvailableStates}" => add this!
...
In the VM
private void AddSelectedStates(IEnumerable<SelectableItemWrapper<states_approved>> selectedItems)
{
StatesApproved = selectedItems
.Select(s => s.Item) // only retrieve the Item
.ToList();
}
As you can see at this point, you don't even really need the SelectableItemWrapper to set/unset the IsSelected property to begin with. You should just remove the wrapper and life will be easier.
Related
In my WPF MVVM application, when the (sub-)menu of a MenuItem opens, I want to populate the (sub-)menu.
E.g. there is a MenuItem "Logs". Only when the submenu opens, I want to search for corresponding log files on disk and display their filenames in the SubMenu afterwards.
Populating submenu dynamically
MainWindow.xaml
<Grid>
<Menu ItemsSource="{Binding MenuItems}">
<Menu.Resources>
<HierarchicalDataTemplate DataType="{x:Type local:MyMenuItemViewModel}"
ItemsSource="{Binding Children}">
<local:CustomMenuItemControl/>
</HierarchicalDataTemplate>
</Menu.Resources>
</Menu>
</Grid>
MyMenuItemViewModel.cs
public class MyMenuItemViewModel : ObservableObject
{
public string Text { get; set; }
public ObservableCollection<MyMenuItemViewModel> Children { get; set; }
public MyMenuItemViewModel(string item)
{
Text = item;
Children = new ObservableCollection<MyMenuItemViewModel>();
}
}
My application is significantly larger, for illustrative purposes I have removed most of it.
I work with a ViewModel that contains a Text and an ObservableCollection "Children" for SubMenus.
It is displayed with a CustomControl that only displays the text.
However, I am already failing to get a trigger when the SubMenu is opened.
I've already tried adding event handler to HierarchicalDataTemplate and CustomMenuItemControl and
a DependencyProperty to the control and tried binding events in XAML, but apparently not in the right place.
Where exactly do I need to define the trigger or handler that executes code when the SubMenu is opened?
I'll answer my own question. Whether it's the best way to solve my problem, I don't know, but that's how I went about it:
In the ViewModel I created a property "IsSubMenuOpen". In the setter, I query whether the text matches "Logs". If yes, I fill the list with the log files.
private bool _isSubMenuOpen;
public bool IsSubMenuOpen
{
get => _isSubMenuOpen;
set
{
if(Text == "Logs")
{
Children.Clear();
foreach (var file in Directory.GetFiles(#"C:\MyDir", "*.log"))
{
Children.Add(new MyMenuItemViewModel(Path.GetFileName(file)));
}
}
SetProperty(ref _isSubMenuOpen, value);
}
}
In the MainWindow I have created a setter for the property "IsSubMenuopen", and bound my VM property in the value.
<Grid>
<Menu ItemsSource="{Binding MenuItems}">
<Menu.ItemContainerStyle>
<Style TargetType="{x:Type MenuItem}">
<Setter Property="IsSubmenuOpen" Value="{Binding IsSubMenuOpen}"></Setter>
</Style>
</Menu.ItemContainerStyle>
<Menu.Resources>
<HierarchicalDataTemplate DataType="{x:Type local:MyMenuItemViewModel}"
ItemsSource="{Binding Children}">
<local:CustomMenuItemControl/>
</HierarchicalDataTemplate>
</Menu.Resources>
</Menu>
</Grid>
OK so this is definitely a newbie question that unfortunately could not figure/find the answer to.
Essentially binding a list of objects to a Combobox, when the Disabled property on the object is set to true I want the text colour of the Combobox item to be set to gray.
This is what I have so far:
Combobox item datatype
public class ListItem
{
public ListItem(string text)
{
Text = text;
}
public string Text { get; set; }
public bool Disabled { get; set; }
}
Viewmodel setup
public class MainPageViewModel : ReactiveObject
{
// In ReactiveUI, this is the syntax to declare a read-write property
// that will notify Observers, as well as WPF, that a property has
// changed. If we declared this as a normal property, we couldn't tell
// when it has changed!
private ListItem _selectedItem;
public ListItem SelectedItem
{
get => _selectedItem;
set => this.RaiseAndSetIfChanged(ref _selectedItem, value);
}
public List<ListItem> Items { get; set; }
public MainPageViewModel()
{
Items = new List<ListItem>
{
new ListItem ("A Cat"),
new ListItem ("A Dog"),
new ListItem ("A Mouse"),
new ListItem ("A Frog") { Disabled = true }
};
}
}
ReactiveUI Binding
public MainPage()
{
InitializeComponent();
ViewModel = new MainPageViewModel();
this.WhenActivated(d =>
{
this.OneWayBind(ViewModel, vm => vm.Items, v => v.MyComboBox.ItemsSource)
.DisposeWith(d);
this.Bind(ViewModel, vm => vm.SelectedItem, v => v.MyComboBox.SelectedItem)
.DisposeWith(d);
});
}
Xaml markup
<ComboBox
Name="MyComboBox"
Margin="0,0,0,20"
Foreground="black">
<ComboBox.ItemTemplate>
<DataTemplate>
<TextBlock Text="{Binding}" />
</DataTemplate>
</ComboBox.ItemTemplate>
<ComboBox.ItemContainerStyle>
<Style TargetType="ComboBoxItem">
<Style.Triggers>
<DataTrigger Binding="{Binding Path=Disabled}" Value="True">
<Setter Property="Foreground" Value="Gray" />
</DataTrigger>
</Style.Triggers>
</Style>
</ComboBox.ItemContainerStyle>
</ComboBox>
Any help is appreciated let me know if you need more information.
Solution: It looks like in future I need to test the example code before puttin it up - our actual code had the Disabled property set as a readonly which must mess with WPF binding. Changing it to public set and get solved the first issue of not seeing it greyed out! It would seem staring at a problem for so long blinds you and it really is that simple.
As for graying out the selected item I will try it out and see.
The last item in the dropdown already has its text grayed out, so I assume you're asking about the selected item. The ComboBox uses separate data templates for the selected item and the items in the dropdown. You can use a DataTemplateSelector to set both.
public class ComboBoxTemplateSelector : DataTemplateSelector
{
public DataTemplate SelectedItemTemplate { get; set; }
public DataTemplate DropdownItemsTemplate { get; set; }
public override DataTemplate SelectTemplate(object item, DependencyObject container)
{
var itemToCheck = container;
// Search up the visual tree, stopping at either a ComboBox or a ComboBoxItem (or null).
// This will determine which template to use.
while (itemToCheck is not null and not ComboBox and not ComboBoxItem)
itemToCheck = VisualTreeHelper.GetParent(itemToCheck);
// If you stopped at a ComboBoxItem, you're in the dropdown.
return itemToCheck is ComboBoxItem ? DropdownItemsTemplate : SelectedItemTemplate;
}
}
Xaml markup
<StackPanel>
<StackPanel.Resources>
<Style x:Key="GrayedOutText" TargetType="TextBlock">
<Style.Triggers>
<DataTrigger Binding="{Binding Disabled}" Value="True">
<Setter Property="Foreground" Value="Gray" />
</DataTrigger>
</Style.Triggers>
</Style>
<local:ComboBoxTemplateSelector x:Key="ComboBoxTemplateSelector">
<local:ComboBoxTemplateSelector.SelectedItemTemplate>
<DataTemplate>
<TextBlock Text="{Binding Text}" Style="{StaticResource GrayedOutText}" />
</DataTemplate>
</local:ComboBoxTemplateSelector.SelectedItemTemplate>
<local:ComboBoxTemplateSelector.DropdownItemsTemplate>
<DataTemplate>
<TextBlock Text="{Binding Text}" Style="{StaticResource GrayedOutText}" />
</DataTemplate>
</local:ComboBoxTemplateSelector.DropdownItemsTemplate>
</local:ComboBoxTemplateSelector>
</StackPanel.Resources>
<ComboBox
Name="MyComboBox"
Margin="0,0,0,20"
ItemTemplateSelector="{StaticResource ComboBoxTemplateSelector}">
</ComboBox>
</StackPanel>
We have some repetition in the DataTemplate definitions, but these tend to grow apart in production code.
Resources
Can I use a different Template for the selected item in a WPF ComboBox than for the items in the dropdown part?
https://www.reactiveui.net/docs/getting-started/compelling-example
I'm assuming your problem is that ComboBoxItems do not get grayed once the app is running.
I'm not familiar with ReactiveUI, but since I found a problem in your code, I tried it in a CommunityToolkit.Mvvm version of your code and verified my theory.
Bottom of line, you need to implement the ReactiveUI version of INotifyPropertyChanged to the Disabled property.
If you are interested in, I can post the CommunityToolkit.Mvvm version of this code.
Here is an approach that worked in my tests:
Combobox item datatype:
//-- Unchanged
public class ListItem
{
public ListItem( string text )
{
Text = text;
}
public string Text { get; set; }
public bool Disabled { get; set; }
}
Viewmodel setup:
public class MainPageViewModel : INotifyPropertyChanged
{
private ListItem? _selectedItem;
public event PropertyChangedEventHandler? PropertyChanged;
public ListItem? SelectedItem
{
get => _selectedItem;
set
{
//-- I didn't had the "RaiseAndSetIfChanged" method, so I just implemented the functionality manually
if( value != _selectedItem )
{
//-- Update the value ...
_selectedItem = value;
//-- ... AND inform everyone (who is interested) about the change
this.PropertyChanged?.Invoke( this, new PropertyChangedEventArgs( nameof( this.SelectedItem ) ) );
}
}
}
//-- Use always an ObservableCollection when you want to achieve reactivity
public ObservableCollection<ListItem> Items
{ get; } = new ObservableCollection<ListItem>();
public MainPageViewModel()
{
//-- Add some test data
this.Items.Add( new ListItem( "A Cat" ) );
this.Items.Add( new ListItem( "A Dog" ) );
this.Items.Add( new ListItem( "A Mouse" ) );
this.Items.Add( new ListItem( "A Frog" ) { Disabled = true } );
//-- Just select the first item
this.SelectedItem = this.Items[0];
}
}
Main page:
public MainPage()
{
//-- Define the DataContext BEFORE the UI will be initialized ;)
this.DataContext = new MainPageViewModel();
InitializeComponent();
//-- Never saw such code before -> just don't do that ;)
//this.WhenActivated( d =>
//{
// this.OneWayBind( ViewModel, vm => vm.Items, v => v.MyComboBox.ItemsSource )
// .DisposeWith( d );
// this.Bind( ViewModel, vm => vm.SelectedItem, v => v.MyComboBox.SelectedItem )
// .DisposeWith( d );
//} );
}
Xaml markup:
<DockPanel>
<ComboBox
DockPanel.Dock="Top"
Name="MyComboBox"
Margin="0,0,0,20"
Foreground="black"
ItemsSource="{Binding Items}"
SelectedItem="{Binding SelectedItem, Mode=TwoWay}">
<ComboBox.ItemTemplate>
<DataTemplate>
<TextBlock Text="{Binding Text}" />
</DataTemplate>
</ComboBox.ItemTemplate>
<ComboBox.ItemContainerStyle>
<Style TargetType="ComboBoxItem">
<Style.Triggers>
<DataTrigger Binding="{Binding Path=Disabled}" Value="True">
<Setter Property="Foreground" Value="Gray" />
</DataTrigger>
</Style.Triggers>
</Style>
</ComboBox.ItemContainerStyle>
</ComboBox>
<!-- Details View -->
<StackPanel>
<!-- name -->
<StackPanel Orientation="Horizontal">
<Label Content="Item Name" />
<TextBox Text="{Binding SelectedItem.Text}" />
</StackPanel>
<!-- disabled flag -->
<StackPanel Orientation="Horizontal">
<Label Content="IsDisabled" />
<CheckBox IsChecked="{Binding SelectedItem.Disabled}" />
</StackPanel>
</StackPanel>
</DockPanel>
I hope this will satisfy your requirements. Have fun :)
So I have a ListBox with CheckBox-es that have the IsChecked property bound to the Item's property called IsSelected. That produces a weird behavior where if I click on the item itself it checks the checkbox (good) and sets the property on the item (good), but doesn't actually select the item in the list box, ie. the highlighting isn't there. I am guessing that the ListBox IsSelected property needs to be set as well for that right?
Now, I am trying to get the multi-select behavior to work so I changed the SelectionMode to Extended. Now, I can select only Items, not the checkboxes. What happens is that if I use SHIFT + click by pointing at the area next to the item, not the item itself, then it select multiple items, but clicking on the items themselves doesn't do the trick of multi-selection not does it check the checkboxes. What is going on in here?
I would like to be able to select multiple items by holding shift etc, and have that trigger the property on the Elevation item so I know which ones are checked. Any help is appreciated.
Here's my XAML:
<ListBox x:Name="LevelsListBox"
ItemsSource="{Binding Elevations, UpdateSourceTrigger=PropertyChanged}"
SelectionMode="Extended"
BorderThickness="0">
<ListBox.ItemTemplate>
<DataTemplate>
<CheckBox IsChecked="{Binding IsSelected}" Content="{Binding Name}"/>
</DataTemplate>
</ListBox.ItemTemplate>
</ListBox>
My View Model:
public class AxoFromElevationViewModel : ViewModelBase
{
public AxoFromElevationModel Model { get; }
public RelayCommand CheckAll { get; }
public RelayCommand CheckNone { get; }
public AxoFromElevationViewModel(AxoFromElevationModel model)
{
Model = model;
Elevations = Model.CollectElevations();
CheckAll = new RelayCommand(OnCheckAll);
CheckNone = new RelayCommand(OnCheckNone);
}
private void OnCheckNone()
{
foreach (var e in Elevations)
{
e.IsSelected = false;
}
}
private void OnCheckAll()
{
foreach (var e in Elevations)
{
e.IsSelected = true;
}
}
/// <summary>
/// All Elevation Wrappers.
/// </summary>
private ObservableCollection<ElevationWrapper> _elevations = new ObservableCollection<ElevationWrapper>();
public ObservableCollection<ElevationWrapper> Elevations
{
get { return _elevations; }
set { _elevations = value; RaisePropertyChanged(() => Elevations); }
}
}
Finally my Elevation Class:
public sealed class ElevationWrapper : INotifyPropertyChanged
{
public string Name { get; set; }
public ElementId Id { get; set; }
public object Self { get; set; }
private bool _isSelected;
public bool IsSelected
{
get { return _isSelected; }
set { _isSelected = value; RaisePropertyChanged("IsSelected"); }
}
public ElevationWrapper(View v)
{
Name = v.Name;
Id = v.Id;
Self = v;
IsSelected = false;
}
public event PropertyChangedEventHandler PropertyChanged;
private void RaisePropertyChanged(string propname)
{
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propname));
}
}
You should bind the IsSelected property of your ListBoxItems to the IsSelected property of your view model. This way CheckBoxes will trigger the selection and when you select an item, the related CheckBox will be checked.
<ListBox.ItemContainerStyle>
<Style TargetType="ListBoxItem">
<Setter Property="IsSelected" Value="{Binding IsSelected}"/>
</Style>
</ListBox.ItemContainerStyle>
It seems to me you want to sync 3 properties ListBoxItem.IsSelected, CheckBox.IsChecked and your models IsSelected.
My advice is that only one of the templates/styles should bind to the underlying model so I will add Yusuf answer as I will use the ListBoxItem style to bind to your model property.
After that you should bind the Checkbox.IsChecked to the ListBoxItem.IsSelected and your ListBox should look like this:
<ListBox x:Name="LevelsListBox"
ItemsSource="{Binding Elevations, UpdateSourceTrigger=PropertyChanged}"
SelectionMode="Extended"
BorderThickness="0">
<ListBox.ItemContainerStyle>
<Style TargetType="ListBoxItem">
<Setter Property="IsSelected" Value="{Binding IsSelected}"/>
</Style>
</ListBox.ItemContainerStyle>
<ListBox.ItemTemplate>
<DataTemplate>
<CheckBox IsChecked="{Binding IsSelected, RelativeSource={RelativeSource AncestorType={x:Type ListBoxItem}}}" Content="{Binding Name}"/>
</DataTemplate>
</ListBox.ItemTemplate>
</ListBox>
Always try to bind XAML properties in a chain way, e.g. model.A binds to Model.B binds to Model.C, doing this should help you keep updates consistent and avoid wierd cases.
There is an issue with this code though, after you select multiple items and click one check box it will only unselect that item but if you click another item it will unselect all except that item.
I have strange problem, my Textbox in ListView's DataTemplate wont update its data. Data are setted in my property "LastValue" but it was never return.
Here is my ViewModel code (only important parts of this class):
public interface ISignal : IValue, IChartItem, INotifyPropertyChanged
{
string SignalName { get; set; }
}
[Serializable]
public class Signal : ObservableObject, ISignal
{
public Signal()
: this(new ModelsDialogService())
{
LastValue = 0.0;
}
public Signal(IDialogService dialog)
{
dialogService = dialog;
VisibleInGraph = true;
RefreshRate = 1000;
Include = true;
Color = ColorList.FirstOrDefault();
LastValue = 0.0;
}
private readonly List<SignalValue> values = new List<SignalValue>();
[XmlIgnore]
public IEnumerable<SignalValue> Values
{
get
{
return values;
}
}
private double lastValue;
[XmlIgnore]
public double LastValue
{
get
{
return lastValue;
}
set
{
Set(ref lastValue, value);
//RaisePropertyChanged(() => LastValue);
}
}
public void AddValue(SignalValue val)
{
values.Add(val);
ValueAdded(this, new ValueAddedEventArgs(val));
LastValue = Convert.ToDouble(((XYValue)val).Value);
}
}
And my XAML:
<ListView ItemsSource="{Binding SignalGroup.Signals}" SelectedValue="{Binding SelectedSignal}" FontWeight="Normal" BorderThickness="0" Foreground="white" HorizontalAlignment="Stretch" HorizontalContentAlignment="Stretch" DockPanel.Dock="Top" Background="#FF5B5A5A" Margin="10" >
<ListView.ItemContainerStyle>
<Style TargetType="ListViewItem">
<Style.Resources>
<SolidColorBrush x:Key="{x:Static SystemColors.HighlightBrushKey}"
Color="{StaticResource MetroBlueColor}"/>
</Style.Resources>
<Setter Property="ContextMenu">
<Setter.Value>
<ContextMenu ItemsSource="{Binding CommandList}">
<ContextMenu.ItemTemplate >
<DataTemplate>
<MenuItem Header="{Binding DisplayName}" Command="{Binding ContextMenuCommand}" />
</DataTemplate>
</ContextMenu.ItemTemplate>
</ContextMenu>
</Setter.Value>
</Setter>
<Setter Property="HorizontalAlignment" Value="Stretch" />
</Style>
</ListView.ItemContainerStyle>
<ListView.ItemTemplate>
<DataTemplate>
<DockPanel HorizontalAlignment="Stretch">
<TextBlock Text="{Binding SignalName}" DockPanel.Dock="Left" />
<TextBlock Text="{Binding LastValue}" TextAlignment="Right" Margin="10,0,10,0" DockPanel.Dock="Right"/>
</DockPanel>
</DataTemplate>
</ListView.ItemTemplate>
</ListView>
Thanks for any idea.
Oh, i found interesting fact. Binding doesn't work only after deserialization. When I create new structure etc.. it works but when I serialize this structure to XML using XMLSerializer and then deserialize every binding in this class doesen't work, so I can change all values but its not updated in GUI... Weird
I have implemented a very small MVVM example.
Lets assume that the Signal class has only the two attributes you want to bind to. Then Signal looks very clean and easy:
public class Signal
{
public string SignalName { get; set; }
public double LastValue { get; set; }
}
Obviously Signal is your Model!
Now you need the ViewModel which has the name name in my small test application:
public class ViewModel
{
public ViewModel()
{
this.Signals = new ObservableCollection<Signal>();
this.Signals.Add(new Signal() { LastValue = 12432.33, SignalName = "First Signal"} );
this.Signals.Add(new Signal() { LastValue = 2.123, SignalName = "Second Signal"});
}
public ObservableCollection<Signal> Signals { get; set; }
}
An ObservableCollection is like a list with the difference that the View is notified when the Collection changes. The collection changes with operations like .Add(...) and .Remove(...).
The View looks similar to yours. i have choosen a GridView, because it is much more handy because it supports features like sorting and editing of the elements:
<DataGrid ItemsSource="{Binding Signals}" AutoGenerateColumns="False">
<DataGrid.Columns>
<DataGridTextColumn Header="LastName" Binding="{Binding SignalName}" />
<DataGridTextColumn Header="LastName" Binding="{Binding LastValue}" />
</DataGrid.Columns>
</DataGrid>
You may want to set the IsReadOnly to True when you are using the GridView
A solution with a ListView should look the same!
The Result:
Make sure, that you use the MVVM Pattern correctly. The Model only holds the data. The ViewModel is for all the business logic and the View only shows the data.
I would also recommend to create a folder structure so you have better overview over your solution. It also makes it easier to follow the MVVM pattern.
Hope it helps and clarified MVVM
Thank you for your request, it is really useful, but I´ve found where the problem is.
I have two collections in my application. I add Signals to first and when user wants to monitor some of these signals, selected signals are putted to the second collection too(but only its reference). Serialization creates the XML from this structure and deserialization overlooks the references and creates a new object of signal in first and also second collection. And here we go! :-D
I feel really stupid and dumb after dicovering this. I must refactor it till I forgot it. I spent a lot of time by searching the cause of this bug.
Thank for your request anyway!
I have an application with a WPF treeview with a node hierarchy. I have to display a context menu for the one or more selected nodes. When one or more nodes are selected, a collection in my viewmodel gets populated with all those selected nodes.
I have a collection of menuitems binded to my treeview contextmenu. I only want this binding to be evaluated when user right clicks on the node(or nodes).
To be bit more specific here is what I want:
User clicks on one or more menu items to select them
He right clicks for bringing up the contextmenu, I need my contextmenu biding(MenuItems) to be evaluated at this point in time and not while the user clicks on each menu itmes as is happening now.
Below is my code:
<TreeView MinWidth="100" ItemsSource="{Binding Nodes}">
<i:Interaction.Behaviors>
<Behaviors1:BindableSelectedItemBehavior SelectedItem="{Binding SelectedItem, Mode=TwoWay}" />
</i:Interaction.Behaviors>
<TreeView.ContextMenu>
<ContextMenu ItemsSource="{Binding MenuItems}" Visibility="{Binding ShowContextMenu, Converter={StaticResource VisibilityConverter}}">
<ContextMenu.ItemContainerStyle>
<Style TargetType="MenuItem">
<Setter Property="Header" Value="{Binding Name}"/>
<Setter Property="IsEnabled" Value="{Binding IsEnabled}"/>
<Setter Property="Command" Value="{Binding MenuCommand}"/>
</Style>
</ContextMenu.ItemContainerStyle>
</ContextMenu>
</TreeView.ContextMenu>
</TreeView>
And my ViewModel:
internal class MyViewModel : NotificationObject
{
private readonly IContextMenuProvider _contextMenuProvider;
public MyViewModel(IContextMenuProvider contextMenuProvider)
{
_contextMenuProvider = contextMenuProvider;
}
public ObservableCollection<IMenuItem> MenuItems
{
get
{
System.Diagnostics.Debug.WriteLine("Getting menu items");
return GetMenuItems();
}
}
private ObservableCollection<INodeViewModel> _selectedNodes;
public ObservableCollection<INodeViewModel> SelectedNodes
{
get { return _selectedNodes; }
set
{
_selectedNodes = value;
System.Diagnostics.Debug.WriteLine("Setting selected nodes");
foreach (var nodeViewModel in _selectedNodes)
{
System.Diagnostics.Debug.WriteLine(nodeViewModel.Name);
}
RaisePropertyChanged(() => SelectedNodes);
RaisePropertyChanged(() => ShowContextMenu);
RaisePropertyChanged(() => MenuItems);
}
}
public bool ShowContextMenu
{
get
{
var canDisplay = _contextMenuProvider.GetMenuItemsByNodeContext(SelectedNodes);
return !canDisplay.IsNullOrEmpty();
}
}
private ObservableCollection<IMenuItem> GetMenuItems()
{
var items = _contextMenuProvider.GetAllMenuItems(SelectedNodes);
var menuItems = new ObservableCollection<IMenuItem>(items);
return menuItems;
}
}
The issues I'm facing is: I dont know at which point should I fetch the menu items, should I do it while selectednodes collection is getting populated or on right click by user? I want either one of them happening ideally during the right click, question how do I refresh my treeview contextmenu bindings while right clicking a node on the treeview?
Note: I have a selected property on the NodeViewModel for selection purposes.
Thanks,
-Mike