I am trying to add an ItemsSource to a MenuItem while keeping the Command bound to my ViewModel (my Window's DataContext). So far, I haven't figured out a way to make it work. Before the ItemsSource is added, the binding is fine. The collection that I am trying to bind comes from a StaticResource. Can anybody help me out with this?
<MenuItem Command="{Binding OpenTeamPage}"
DisplayMemberPath="Name"
Header="Teams"
ItemsSource="{Binding Teams,
Source={StaticResource Container}}" />
I have tried using this and variations of it with no luck:
Command="{Binding OpenTeamPage,
RelativeSource={RelativeSource AncestorType=Window},
Mode=Default}"
If anybody could tell me how to use this ItemsSource while still binding my Command to my ViewModel, I would greatly appreciate it. I suppose I could put the Command in my Team model, but I would like to avoid that if possible.
EDIT : To clarify my problem, with the ItemsSource in place, the command in the ViewModel doesn't fire at all. Without the ItemsSource, the command fires. I would like to be able to have the ItemsSource and still be able to fire the command.
EDIT:
public class GameContainer
{
static GameContainer()
{
Teams = new ObservableCollection<Team>();
}
public static ObservableCollection<Team> Teams { get; set; }
}
In App.xaml:
<data:GameContainer x:Key="Container" />
The collection is populated when the program is started.
My goal once I get this working is to pass the selected team to the Viewmodel, hopefully via CommandParameter, and display info regarding the selected team.
EDIT: I was mistaken in my original post. A bound collection coming from the Viewmodel does not work either.
This is the behaviour of MenuItem, Item having Child MenuItem won't fire Command and it also should not as it does not make sense. But if you still want to fire a command on Parent Item click,there are two options
You can use Interactivity Triggers on your MenuItem to call command on MouseDown event like
<MenuItem
DisplayMemberPath="Name"
Header="Teams"
ItemsSource="{Binding Teams,
Source={StaticResource Container}}">
<i:Interaction.Triggers>
<i:EventTrigger EventName="MouseDown">
<cmd:EventToCommand Command="{Binding OpenTeamPage}" />
</i:EventTrigger>
</i:Interaction.Triggers>
</MenuItem>
you can define a Attached Property for command and define the MenuItem MouseDown behaviour like
public static class MouseCommandBehavior
{
public static readonly DependencyProperty MouseDownCommandProperty =
DependencyProperty.RegisterAttached("MouseDownCommand",
typeof(ICommand),
typeof(MouseCommandBehavior),
new FrameworkPropertyMetadata(null, (obj, e) => OnMouseCommandChanged(obj, (ICommand)e.NewValue, false)));
public static ICommand GetMouseDownCommand(DependencyObject d)
{
return (ICommand)d.GetValue(MouseDownCommandProperty);
}
public static void SetMouseDownCommand(DependencyObject d, ICommand value)
{
d.SetValue(MouseDownCommandProperty, value);
}
private static void OnMouseCommandChanged(DependencyObject d, ICommand command)
{
if (command == null) return;
var element = (FrameworkElement)d;
element.PreviewMouseDown += (obj, e) => command.Execute(null);
}
}
}
and you can set this Property value on your menuItem
<MenuItem local:MouseCommandBehavior.MouseDownCommand="{Binding OpenTeamPage}"
DisplayMemberPath="Name"
Header="Teams"
ItemsSource="{Binding Teams,
Source={StaticResource Container}}">
MenuItem will not execute its command if it's not a leaf node. Only menu items that are leafs (items with no children) are executing a command.
This is probably done due to convention - when you click an items that has children you get the children shown immediately, otherwise there's a delay from mouse hover till children shown.
Although it's probably a bad idea (from UX point of view) to have command on a parent, it's possible:
<MenuItem DisplayMemberPath="Name"
Header="{Binding OpenTeamPage}"
ItemsSource="{Binding Teams, Source={StaticResource Container}}" >
<MenuItem.HeaderTemplate>
<DataTemplate>
<!--Probably need to make this button transparent-->
<Button Content="Teams"
Command="{Binding }"/>
</DataTemplate>
</MenuItem.HeaderTemplate>
<!--This style is for the children to fire the same command as the parent-->
<MenuItem.ItemContainerStyle>
<Style TargetType="{x:Type MenuItem}">
<Setter Property="Command"
Value="{Binding RelativeSource={RelativeSource FindAncestor, AncestorType={x:Type MenuItem}}, Path=Header}"/>
</Style>
</MenuItem.ItemContainerStyle>
</MenuItem>
Depending upon your design, you'd might need to style the button to be transparent.
Related
I have a WPF DataGrid in a Window with associated View(*.xaml.cs) and ViewModel that is successfully executing a bunch of functionality. However, functions that modify items instead of altering the collection do not update until a sort, resize, etc.
I've found a bunch of search results suggesting the solution is to make the item type implement INotifyPropertyChanged and add/subtract event handlers as appropriate for every item in the collection. I tried for a bit using those examples without success, but frankly it doesn't seem like a great option.
The item type is declared elsewhere in the app and is sharing the object instances with other modules, so I would like not to modify that class. It also seems like a poor design to tie the implementation of the DataGrid's ItemsSource to the item type within; the list container is already an ObservableCollection invoking OnPropertyChanged as needed already, so why should that not be sufficient?
I'm able to update via DataGrid.Items.Refresh() - which unfortunately does not seem to have an overload for specific items/properties instead of updating the entire list, but that's a minor issue - but only my View has a reference to the DataGrid itself (per MVVM), whereas the Command binding is in the ViewModel.
I would actually like to put those Command bindings in the View, and I don't understand why convention is to put those in the VM and thereby bypass the View during a UI event. For example, to delete items I can select them and either press Delete (KeyUp handler is in the View, which then passes selected items as a list to the VM) or select Delete in the context menu (Binding is to an ICommand in the VM, which routes to the same function invoked by the View). Why would it not be more desirable to bind both to the View's event handler (or two handlers both in the View)?
I've seen some results that use a RelativeSource for the Command binding to an ancestor of type UserControl...I've tried with type Window to try to bind to the View's method for naught. As of now my best option is to put a delegate event RefreshListItems on the VM and subscribe to it from the view with a function that invokes DataGrid.Items.Refresh().
This is workable, but in the interest of edification I wondered if anyone could tell me how to bind Command properties to the View (a Window) instead of the ViewModel, and/or how to notify the control bound to my ObservableCollection to refresh either specific or all items from the ViewModel without implementing the INotifyPropertyChanged scheme on every list item?
Edit per mm8's suggestion:
I tried your code for the ICommand in my view, but I get this error:
System.Windows.Data Error: 4 : Cannot find source for binding with reference 'RelativeSource FindAncestor, AncestorType='System.Windows.Window', AncestorLevel='1''. BindingExpression:Path=SetService; DataItem=null; target element is 'MenuItem' (Name=''); target property is 'Command' (type 'ICommand')
That's with each of these attempts, with and without CommandParameter, bound to an ICommand of type either RelayCommand or DelegateCommand:
<MenuItem Header="Set Service" Command="{Binding SetService, RelativeSource={RelativeSource AncestorType=Window}}"/>
<MenuItem Header="Set Service" Command="{Binding SetService, RelativeSource={RelativeSource AncestorType={x:Type Window}}}"/>
<MenuItem Header="Set Service" Command="{Binding SetService, RelativeSource={RelativeSource Mode=FindAncestor, AncestorType=Window}}"/>
.xaml:
<Window>
…
<DataGrid x:Name="TheGrid" ItemsSource="{Binding Source={StaticResource MessageItems}}" KeyUp="MessageList_KeyUp" AutoGenerateColumns="False" IsReadOnly="True" ColumnWidth="Auto">
…
<DataGrid.ContextMenu>
<ContextMenu>
<MenuItem Header="Set Service" Command="{Binding SetService, RelativeSource={RelativeSource AncestorType=Window}}"/>
</ContextMenu>
</DataGrid.ContextMenu>
</DataGrid>
…
</Window>
.xaml.cs:
public partial class TheGridView : Window
{
TheGridViewModel _viewModel;
public ICommand SetService;
[ImportingConstructor]
public TheGridView(TheGridViewModel vm)
{
DataContext = _viewModel = vm;
vm.RefreshListItems += () => TheGrid.Items.Refresh();
InitializeComponent();
Closing += Window_Closing;
SetService = new RelayCommand(SetSvc);
}
private void SetSvc(object selectedItem)
{
// Doesn't get here
}
}
This is workable, but in the interest of edification I wondered if anyone could tell me how to bind Command properties to the View (a Window) instead of the ViewModel?
There is nothing that stops you from defining ICommand properties in the code-behind of the view and bind to them like this (assuming your view is a Window):
Command="{Binding YourCommandProperty, RelativeSource={RelativeSource AncestorType=Window}}"
Edit:
A ContextMenu resides in its own visual tree but you should be able to bind to the parent window through the Tag property of the DataGrid, something like this:
<DataGrid x:Name="TheGrid" ItemsSource="{Binding Source={StaticResource MessageItems}}" KeyUp="MessageList_KeyUp" AutoGenerateColumns="False" IsReadOnly="True" ColumnWidth="Auto">
<DataGrid.Tag>
<Binding Path="." RelativeSource="{RelativeSource AncestorType=Window}" />
</DataGrid.Tag>
<DataGrid.ContextMenu>
<ContextMenu>
<MenuItem Header="Set Service" Command="{Binding PlacementTarget.Tag.SetService, RelativeSource={RelativeSource AncestorType=ContextMenu}}"/>
</ContextMenu>
</DataGrid.ContextMenu>
</DataGrid>
...and/or how to notify the control bound to my ObservableCollection to refresh either specific or all items from the ViewModel without implementing the INotifyPropertyChanged scheme on every list item?
You can't do this unless you refresh the entire control (for example using DataGrid.Items.Refresh()). That's why you should implement INotifyPropertyChanged.
If you currently bind to some class that is shared across several modules and you don't want to modify this class, you could create a new client-specifc wrapper class that does implement INotifyPropertyChanged and bind to this one instead of binding to the common class, e.g.:
public class Wrapper : INotifyPropertyChanged
{
private readonly SharedModel _model;
public Wrapper(SharedModel model)
{
_model = model;
}
private string _property;
public string MyProperty
{
get { return _property; }
set { _property = value; OnPropertyChanged(); }
}
//...
public event PropertyChangedEventHandler PropertyChanged;
}
Situation: In MVVM pattern, I have some inputbindings on a listview which work only when the listview is focused. However, whenever user clicks, the listview goes out of focus and user is unable to execute the inputbindings.
Problem: I want to bring the focus on the listview (on button click) in a way that the inputbindings work.
What I tried:
I tried using attached property IsFocused (where I focus using UIElement.Focus() and/or Keyboard.Focus()) and binding it to a bool variable in the ViewModel which I would set using an ICommand.
I also tried a separate example where I can use the System.Windows.Input.Keyboard.Focus(item) method in the code behind (I mean the .xaml.cs file with the same name) to focus the listview and it works! But, I don't know how to implement the similar thing in a ViewModel which is connected using a d:DesignInstance attribute.
I believe that the mouseclick event is bubbled up and handled somewhere else which causes the list to unfocus as soon as I click it. Like, if I find a way to set the event as handled that will help, but again I don't know how to do that in a viewmodel. Here is my attached property :
FocusExtension.cs
public static class FocusExtension {
public static bool GetIsFocused(DependencyObject obj) {
return (bool)obj.GetValue(IsFocusedProperty);
}
public static void SetIsFocused(DependencyObject obj, bool value) {
obj.SetValue(IsFocusedProperty, value);
}
public static readonly DependencyProperty IsFocusedProperty =
DependencyProperty.RegisterAttached(
"IsFocused", typeof(bool), typeof(FocusExtension),
new UIPropertyMetadata(false, OnIsFocusedPropertyChanged));
private static void OnIsFocusedPropertyChanged(
DependencyObject d,
DependencyPropertyChangedEventArgs e) {
var uie = (UIElement)d;
if ((bool)e.NewValue) {
uie.Focus();
}
}
}
XAML File:
<ListView
x:Name="lv"
Grid.Column="2" Margin="2" MinWidth="250" Height="400" ToolTip="the List"
HorizontalContentAlignment="Stretch"
ItemsSource="{Binding ListBindingInVM}"
ScrollViewer.HorizontalScrollBarVisibility="Auto"
ScrollViewer.CanContentScroll="False"
dd:DragDrop.IsDragSource="True"
dd:DragDrop.IsDropTarget="True"
dd:DragDrop.DropHandler="{Binding }"
behaviour:ListViewAutoScroll.AutoScrollToEnd="True"
ScrollViewer.VerticalScrollBarVisibility="Visible"
>
<ListView.Style>
<Style TargetType="ListView" >
<Setter Property="ViewModels:FocusExtension.IsFocused" Value="{Binding ListFocused, Mode=TwoWay}"></Setter>
<!--The one below is not clean, but worked. However, list goes out of focus on click. -->
<Style.Triggers>
<Trigger Property="IsMouseOver" Value="True">
<Setter Property="ViewModels:FocusExtension.IsFocused" Value="True"></Setter>
</Trigger>
</Style.Triggers>
</Style>
</ListView.Style>
<i:Interaction.Triggers>
<i:EventTrigger EventName="MouseDown">
<!--This command sets the ListFocused to true-->
<i:InvokeCommandAction Command="{Binding BringListToFocus }"></i:InvokeCommandAction>
</i:EventTrigger>
</i:Interaction.Triggers>
<ListView.InputBindings>
<!-- Bindings that don't work when list is not focused-->
<KeyBinding Modifiers="Control" Key="C" Command="{Binding CopyCommand}"/>
<KeyBinding Modifiers="Control" Key="V" Command="{Binding PasteCommand}"/>
</ListView.InputBindings>
<ListView.ContextMenu>
<ContextMenu>
<MenuItem Header="Copy" Command= "{Binding CopyCommand}"></MenuItem>
<MenuItem Header="Paste" Command= "{Binding PasteCommand}"></MenuItem>
</ContextMenu>
</ListView.ContextMenu>
The focus behavior that you describe is easily implemented from the codebehind, and doing so does not violate the MVVM pattern. Consider Josh Smith's post, below:
https://msdn.microsoft.com/en-us/magazine/dd419663.aspx#id0090097
The use of a ViewModel here makes it much easier to create a view that
can display a Customer object and allow for things like an
"unselected" state of a Boolean property. It also provides the ability
to easily tell the customer to save its state. If the view were bound
directly to a Customer object, the view would require a lot of code to
make this work properly. In a well-designed MVVM architecture, the
codebehind for most Views should be empty, or, at most, only contain
code that manipulates the controls and resources contained within that
view. Sometimes it is also necessary to write code in a View's
codebehind that interacts with a ViewModel object, such as hooking an
event or calling a method that would otherwise be very difficult to
invoke from the ViewModel itself.
I have a simple WPF page with a custom control (BrowsingPanel) containing a ListBox, and another control (ItemDataSheet) which displays data related to the element which is selected in the ListBox. When I click on an item in the ListBox, a command is sent to the BrowsingPanelViewModel, which sends a message. The message is received by the ItemDataSheetViewModel, which updates the ItemDataSheet view.
This is my BrowsingPanel.xaml:
<Grid>
<ListBox x:Name="itemsList"
ItemsSource="{Binding MyItems}"
Background="DarkGray">
<i:Interaction.Triggers>
<i:EventTrigger EventName="SelectionChanged">
<i:InvokeCommandAction Command="{Binding SelectedItemChangedCommand}"
CommandParameter="{Binding ElementName=itemsList, Path=SelectedItem}"/>
</i:EventTrigger>
</i:Interaction.Triggers>
</ListBox>
</Grid>
It works well, except that I would like the first ListBox item to be selected by default. To do so, I've tried two things:
First, I've tried to select the first item in the BrowsingPanelViewModel's constructor as shown below.
public RelayCommand<MyItem> SelectedItemChangedCommand { get; private set; }
public BrowsingPanelViewModel()
{
SelectedItemChangedCommand = new RelayCommand<MyItem>(SelectedItemChanged);
MyItems = new ObservableCollection<MyItem>();
MyItems.Add(ParsetemFromResourceName("Resources/toto.txt"));
MyItems.Add(ParseItemFromResourceName("Resources/tata.txt"));
MyItems.Add(ParseItemFromResourceName("Resources/titi.txt"));
//Select the first item if there's one
if (MyItems.Any())
SelectedItemChanged(MyItems.First());
}
void SelectedItemChanged(MyItem selectedItem)
{
Messenger.Default.Send(new NotificationMessage<MyItem>(selectedItem, Notification.SelectedMyChanged));
}
This works fine, the ItemDataSheetViewModel displays the data corresponding to this item, but the item is not (visually) selected in the ListBox.
Then, I've tried to select the first item from the BrowsingPanel view. In the code behind, I have a handler for itemsList_Loaded which looks like this:
private void itemsList_Loaded(object sender, RoutedEventArgs e)
{
//Select the first item by default
itemsList.Focus();
if (itemsList.Items.Count > 0)
itemsList.SelectedItem = itemsList.Items[0];
}
And this is where I get a weird behavior. This selects the item correctly in the ListBox, but the SelectedItemChanged command is not triggered. And I don't understand why.
The funny part is that if I replace my EventTrigger with a SelectionChanged event that I put in the code behind as shown below, then the callback function is called.
<Grid>
<ListBox x:Name="itemsList"
ItemsSource="{Binding MyItems}"
Background="DarkGray"
Loaded="itemsList_Loaded"
SelectionChanged="itemsList_SelectionChanged"> <!-- This is called when changing SelectedItem in the Loaded -->
</ListBox>
</Grid>
Obviously, by combining the 2 solutions I have mentioned, it works: the bit in the view model constructor displays the appropriate data in the ItemDataSheet view, while the bit in the itemsList_Loaded visually selects the item in the List. But I don't find this very elegant...
It seems to me that programmatically changing the ListBox's SelectedIndex should trigger the SelectionChanged command, but it doesn't.
Any help will be appreciated!
Bare in mind this is a solution for a single select listbox.
You really don't need most of that code.
It can be as simple as only needing the SelectedValue property on the listbox:
<Grid>
<ListBox x:Name="itemsList"
ItemsSource="{Binding MyItems}"
SelectedValue="{Binding Path=MySelectedItem, Mode=TwoWay}"
Background="DarkGray">
</ListBox>
</Grid>
This can then be bound to your BrowsingPanelViewModel with the MySelectedItem property:
private MyItem m_MySelectedItem;
public MyItem MySelectedItem
{
get
{
return m_MySelectedItem;
}
set
{
m_MySelectedItem = value;
NotifyPropertyChanged("MySelectedItem");
}
}
The notifypropertychanged in the setter is key here.
You can then from your viewmodel select the first list item by assigning this property.
You will also need a DataTemplate for your MyItem object in the scope of your ListBox which can be as simple as:
<DataTemplate DataType={x:Type MyItem}>
<Textblock Text="{Binding Path=MyItemDescription"/>
</DataTemplate>
Or whatever.
This is my xaml
<ListBox x:Name="HistoryList"
ItemsSource="{Binding HistoryCollection}"
>
<ListBox.ItemTemplate >
</DataTemplate>
<CheckBox x:Name="UpCheckBox" Height="50" Width="50" >
<interactivity:Interaction.Triggers>
<interactivity:EventTrigger EventName="Checked">
<interactivity:InvokeCommandAction Command="{Binding UpCheckedCommad}" CommandParameter="{Binding ElementName=UpCheckBox}"></interactivity:InvokeCommandAction>
</interactivity:EventTrigger>
</interactivity:Interaction.Triggers>
</CheckBox>
</DataTemplate>
</ListBox.ItemTemplate >
</ListBox >
In ViewModel I have used GalasoftMVVM Command Binding
public ICommand UpCheckedCommad
{
get { return new RelayCommand<Object>(x => { PerformUpforTracks(x); }); }
}
void PerformUpforTracks(object x)
{
//TODO
}
I used a CheckBox inside a ListBox ItemTemplate.But am not getting the Checked Event of CheckBox in the ViewModel .
I wanted to get the Checked Event from my ViewModel.Can anyone have any idea to resolve this issue?
Each instance of your ListBox.ItemTemplate is automatically given "the current item in the collection" as its DataContext. In your case, that is each individual item in the HistoryCollection. In your example, the EventTrigger is searching for the "ThumbsUpCheckedCommad" inside your current instance of the HistoryItem.
In order to force the EventTrigger to search in your desired ViewModel, you need to specify the "Source" property of your command binding. I suggest using the RelativeSource syntax, to search up the tree for the last Control to have your ViewModel as its DataContext.
It would look something like this.
{Binding Path=ThumbsUpCheckedCommand, RelativeSource={RelativeSource AncestorType={x:Type ListBox}}}
I got it By Binding Command by this way
Command="{Binding Path=DataContext.UpCheckedCommad,
ElementName=HistoryList}"
I am using WPF and PRISM framework for my application. The pattern I am using is MVVM (Model - View - ViewModel) and I am trying to bring the MouseLeftButtonUp event from the code-behind in the View to the ViewModel (so the event will be according the MVVM rules). For now I have this:
View.xaml:
<DataGrid x:Name="employeeGrid" Height="250" Margin="25,0,10,0" ItemsSource="{Binding DetacheringenEmployeesModel}" IsReadOnly="True" ColumnHeaderStyle="{DynamicResource CustomColumnHeader}" AutoGenerateColumns="False" RowHeight="30">
<i:Interaction.Triggers>
<i:EventTrigger EventName="MouseLeftButtonUp">
<i:InvokeCommandAction Command="{Binding EmployeeGrid_MouseLeftButtonUp}" />
</i:EventTrigger>
</i:Interaction.Triggers>
<DataGrid.Columns>
View.xaml.cs (code-behind):
public partial class UC1001_DashBoardConsultants_View
{
public UC1001_DashBoardConsultants_View(UC1001_DashboardConsultantViewModel viewModel)
{
InitializeComponent();
DataContext = viewModel;
}
}
ViewModel.cs:
public void EmployeeGrid_MouseLeftButtonUp(object sender, MouseButtonEventArgs e)
{
// insert logic here
}
The main idea is, when I click on a cell in the DataGrid, the event will fire. I first tried it in the code behind, and it worked. I got so far with the EventTriggers, but when I debug and click on a cell, my debugger doesn't come into the method.
Does anyone have an idea how to fix this? Thanks in advance!
PS: Does it also work with the (object sender) parameter when I do it like that? Because I need the DataGrid in my ViewModel to get the ActiveCell I just clicked on.
EDIT:
The event-binding worked with the Command!
I have this in my DataGrid:
<DataGridTextColumn Header="Okt" Width="*" x:Name="test" >
<DataGridTextColumn.ElementStyle>
<Style TargetType="{x:Type TextBlock}">
<Setter Property="Tag" Value="{Binding Months[9].AgreementID}"/>
How can I bind the Tag property to the ViewModel? I know it's already bound from the ViewModel, but as you can see the value comes from an Array/List and per column the value is different.
InvokeCommandAction requires the ICommand to be bound not an event handler as you've bound (EmployeeGrid_MouseLeftButtonUp).
So you can introduce a command in ViewModel and bind to it:
View Model:
public ICommand SomeActionCommand { get; set; }
XAML:
<i:InvokeCommandAction Command="{Binding SomeActionCommand}" />