I have an ItemsControl control that has an ObservableCollection as its ItemsSource. It also has a button located inside of its DataTemplate. The button's Command property is bound to a RelayCommand in the ViewModel (I'm using MVVM Light) and the CommandParameter is bound to the corresponding item in the ItemsSource.
The problem is that the command never fires, for some reason. Code-behind works fine, on the other hand. When debugging the mouse click event handler I can see that the sender (of type Button) has a CommandParameter filled with the correct data whereas Command is null.
What did I miss here?
XAML:
<ItemsControl ItemsSource="{Binding Users}"
Margin="{StaticResource ContentMargin}">
<ItemsControl.ItemsPanel>
<ItemsPanelTemplate>
<WrapPanel Orientation="Horizontal" />
</ItemsPanelTemplate>
</ItemsControl.ItemsPanel>
<ItemsControl.ItemTemplate>
<DataTemplate>
<Button Margin="{StaticResource ImageButtonMargin}"
Style="{StaticResource ImageButtonStyle}"
Command="{Binding RelativeSource={RelativeSource AncestorType={x:Type ItemsControl}}, Path=DataContext.UserSelectedCommand}"
CommandParameter="{Binding}">
<!--...-->
ViewModel:
private ObservableCollection<User> _users;
private RelayCommand<User> _userSelectedCommand;
public ObservableCollection<User> Users
{
get { return _users; }
set
{
_users = value;
RaisePropertyChanged();
}
}
public RelayCommand<User> UserSelectedCommand
{
get { return _userSelectedCommand; }
}
protected override sealed void SetCommands() // called in the constructor which is in turned called by SimpleIoc
{
userSelectedCommand = new RelayCommand<User>((user) => UserSeriesSelected(user));
}
private void UserSelected(User selectedUser)
{
}
Use named Element binding as binding source inside your data template to access commands from root data context. You can use root grid or other containers as named element. ItemsControl iteself can be used too.
<ItemsControl x:Name="MyItems" ItemsSource="{Binding Users}"
Margin="{StaticResource ContentMargin}">
<ItemsControl.ItemsPanel>
<ItemsPanelTemplate>
<WrapPanel Orientation="Horizontal" />
</ItemsPanelTemplate>
</ItemsControl.ItemsPanel>
<ItemsControl.ItemTemplate>
<DataTemplate>
<Button Margin="{StaticResource ImageButtonMargin}"
Style="{StaticResource ImageButtonStyle}"
Command="{Binding ElementName=MyItems, Path=DataContext.UserSelectedCommand}"
CommandParameter="{Binding}">
<!--...-->
You need to add "FindAncestor" to your relative source binding:
RelativeSource={RelativeSource FindAncestor, AncestorType={x:Type ItemsControl}}
In my opinion you should change your strategy, put the command in the User class and from that class notify the view-model through an event.
This is going to simplify your xaml code and, in my opinion, making your view-model more coherent.
Related
Im new in wpf programming and i have an application with MVVM. I have a StackPanel and inside the StackPanel there is a ItemsControl named ListView and the Itemsource is my ObserveCollection NewProducts which conatins ProductID and Name.
So what is my code does, well it generates buttons with Names from my Collection NewProducts and i wanna give this buttons Command that triggers when i click on them.
I try this:
<StackPanel Grid.Row="1">
<ItemsControl x:Name="ListViewProducts" ItemsSource="{Binding NewProducts}">
<ItemsControl.ItemTemplate>
<DataTemplate>
<Border Margin="10" Width="auto" Height="auto">
<StackPanel>
<Border Width="100" Height="40" CornerRadius="5">
<Button Content="{Binding Name}" Tag="{Binding ProductId}" FontWeight="Bold" Command="{BindingSaleViewModel.SendCommand}"/>
</Border>
</StackPanel>
</Border>
</DataTemplate>
</ItemsControl.ItemTemplate>
<ItemsControl.ItemsPanel>
<ItemsPanelTemplate>
<WrapPanel/>
</ItemsPanelTemplate>
</ItemsControl.ItemsPanel>
</ItemsControl>
</StackPanel>
But when i run my program and when i click on my buttons nothing happens. Its because the Command that i have in my SaleViewModel was not found in Product which is my Model class. And i wanna know if i can somehow get the path to my Command in my ViewModel.
Thanks, and please forgive me mine English, I know its bad.
Binding methods in WPF is unfortunately not that simple.
The command is actually a class, that implements the ICommand interface.
You can find an example here: How to Bind a Command in WPF.
Otherwise you can bind the click event to a method in code-behind <Button Click="Button_Click"/>
In code-behind:
private void Button_Click(object sender, RoutedEventArgs e)
{
// Add your code here...
}
Give your root element a name. eg:
<UserControl x:Class="blah"
other stuff
x:Name="root">
Then you can reference the root element in your binding, who's datacontext will (presumably?) be your SaleViewModel:
<Button Content="{Binding Name}" Tag="{Binding ProductId}" FontWeight="Bold"
Command="{Binding DataContext.SendCommand, ElementName=root}"
CommandParameter="{Binding}"/>
Also note the CommandParameter binding which will pass the button's data context (Product) through to your command, otherwise you won't know which product the command relates to.
I am trying to figure out how I can bind the ContextMenu of the Button that is being added in the ItemsControl I have. Basically, I'm wanting to be able to right click on a button and remove it from the observable collection that sits on my viewmodel. I understand that ContextMenu's are not part of the VisualTree, so using RelativeSource to walk up the tree to find my DataContext hasn't been useful to me.
The end goal of what I want to do is Bind the Command on the MenuItem to the RemoveCommand on my ViewModel and then pass in the Content property of the Button that you right click on so that I can remove it from the observable collection.
Any help on this would be greatly appreciated.
Model:
public class Preset
{
public string Name { get; set; }
}
ViewModel:
public class SettingsWindowViewModel
{
public ObservableCollection<Preset> MyPresets { get; } = new ObservableCollection<Preset>();
private ICommand _plusCommand;
public ICommand PlusCommand => _plusCommand ?? (_plusCommand = new DelegateCommand(AddPreset));
private ICommand _removeCommand;
public ICommand RemoveCommand => _removeCommand ?? (_removeCommand = new DelegateCommand<string>(RemovePreset));
private void AddPreset()
{
var count = MyPresets.Count;
MyPresets.Add(new Preset {Name = $"Preset{count+1}"});
}
private void RemovePreset(string name)
{
var preset = MyPresets.FirstOrDefault(x => string.Equals(x.Name, name, StringComparison.CurrentCultureIgnoreCase));
if (preset!= null)
{
MyPresets.Remove(preset);
}
}
}
XAML:
<Window x:Class="WpfTesting.Esper.Views.SettingsWindow"
x:Name="MainSettingsWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:viewModels="clr-namespace:WpfTesting.Esper.ViewModels"
mc:Ignorable="d"
Title="SettingsWindow" Height="470" Width="612">
<Window.DataContext>
<viewModels:SettingsWindowViewModel/>
</Window.DataContext>
<Window.Resources>
<Style BasedOn="{StaticResource {x:Type MenuItem}}" TargetType="{x:Type MenuItem}" x:Key="PopupMenuItem">
<Setter Property="OverridesDefaultStyle" Value="True"/>
<Setter Property="Template">
<Setter.Value>
<ControlTemplate TargetType="{x:Type MenuItem}">
<Border>
<ContentPresenter ContentSource="Header"/>
</Border>
</ControlTemplate>
</Setter.Value>
</Setter>
</Style>
</Window.Resources>
<Grid>
<Grid.RowDefinitions>
<RowDefinition Height="Auto"/>
<RowDefinition Height="1*"/>
<RowDefinition Height="35"/>
</Grid.RowDefinitions>
<StackPanel Grid.Row="2" Orientation="Horizontal">
<Button Width="70" Content="Load"/>
<Button Width="70" Content="Save As"/>
<ItemsControl ItemsSource="{Binding MyPresets}">
<ItemsControl.ItemsPanel>
<ItemsPanelTemplate>
<StackPanel Orientation="Horizontal"/>
</ItemsPanelTemplate>
</ItemsControl.ItemsPanel>
<ItemsControl.ItemTemplate>
<DataTemplate>
<Button Width="70" Content="{Binding Name}">
<Button.ContextMenu>
<ContextMenu>
<MenuItem Style="{StaticResource PopupMenuItem}" Header="Remove">
<!--
I need to set up binding a Command to a method on the DataContext of the Window, and I need to pass in the Content of the Button that is the parent of the ContextMenu
-->
</MenuItem>
</ContextMenu>
</Button.ContextMenu>
</Button>
</DataTemplate>
</ItemsControl.ItemTemplate>
</ItemsControl>
<Button Width="20" Background="Transparent" BorderBrush="Transparent" Content="+" FontSize="21.333" HorizontalAlignment="Center" VerticalAlignment="Center" Command="{Binding PlusCommand}"/>
</StackPanel>
</Grid>
</Window>
Using WPF: Binding a ContextMenu to an MVVM Command as an introduction to what Tags can do, I figured out how to do what I was looking for by using multiple Tags to save the Context of what I was looking for.
I first made sure to give my window a x:Name
<Window x:Name="MainSettingsWindow"
Next, on the Button inside my DataTemplate of my ItemsControl, I set a Tag and set it to my Window
<ItemsControl ItemsSource="{Binding MyPresets}">
<ItemsControl.ItemTemplate>
<DataTemplate>
<Button Width="70" Content="{Binding Name}" Tag="{Binding ElementName=MainSettingsWindow}">
Next, in the ContextMenu, I seth the DataContext of the ContextMenu to the Tag I set on the Button, I also needed to create a Tag on the ContextMenu and point it back to the Content Property of the Button so that I can pass that into the CommandParameter
<ContextMenu DataContext="{Binding Path=PlacementTarget.Tag, RelativeSource={RelativeSource Mode=Self}}" Tag="{Binding PlacementTarget.Content, RelativeSource={RelativeSource Mode=Self}}">
At this point, I can now bind my MenuItem correctly using the Command from my ViewModel and the Content Property from the Button
This is the final XAML for my ItemsControl:
<ItemsControl ItemsSource="{Binding MyPresets}">
<ItemsControl.ItemsPanel>
<ItemsPanelTemplate>
<StackPanel Orientation="Horizontal"/>
</ItemsPanelTemplate>
</ItemsControl.ItemsPanel>
<ItemsControl.ItemTemplate>
<DataTemplate>
<Button Width="70" Content="{Binding Name}" Tag="{Binding ElementName=MainSettingsWindow}">
<Button.ContextMenu>
<ContextMenu DataContext="{Binding Path=PlacementTarget.Tag, RelativeSource={RelativeSource Mode=Self}}" Tag="{Binding PlacementTarget.Content, RelativeSource={RelativeSource Mode=Self}}">
<MenuItem Header="Remove"
Style="{StaticResource PopupMenuItem}"
Command="{Binding Path=DataContext.RemoveCommand}"
CommandParameter="{Binding Path=Tag, RelativeSource={RelativeSource Mode=FindAncestor, AncestorType=ContextMenu}}"/>
</ContextMenu>
</Button.ContextMenu>
</Button>
</DataTemplate>
</ItemsControl.ItemTemplate>
</ItemsControl>
One thing to note is that I had to change the CommandParameter on my ViewModel to take an Object instead of a String. The reason I did this was because I was getting an exception on the CanExecute method in my DelegateCommand
This is the exception I was getting:
Unable to cast object of type 'MS.Internal.NamedObject' to type 'System.String'.
I'm not sure exactly what's causing that exception to throw, but changing it to Object works ok for me.
I had basically a similar problem, and the solution I found was to use the Messenger class some MVVM frameworks like Devexpress or Mvvm Light have.
Basically you can register in a viewModel to listen for incoming messages. The class itself, at least in the Devexpress implementation works with weak references, so you may not even unregister message handlers and it will not cause memory leaks.
I had used this method for removing on right click tabs from a ObservableCollection, so it was similar to your scenario.
You can have a look here :
https://community.devexpress.com/blogs/wpf/archive/2013/12/13/devexpress-mvvm-framework-interaction-of-viewmodels-messenger.aspx
and here :
https://msdn.microsoft.com/en-us/magazine/jj694937.aspx
I have a DataTemplate that is loading a list of ~7000 items in a list for a combobox. Currently the ItemsSource is bound to a property in the data context of the DataTemplate, however this means that for each instance of the DataTemplate the system is loading all 7k objects, which is slowing down the system by quite a bit.
Ideally I want to be able to load the list once and use it for all instances. The obvious solution to me is using a resource defined in the Window.Resources section. However I can't figure out how this should work, and more importantly, how that resource should be populated via the MVVM pattern.
Current code which loads the ItemsSource for each DataTemplate instance
<DataTemplate>
<ComboBox SelectedItem="{Binding SelectedItem}" ItemsSource="{Binding ItemsSource}" />
</DataTemplate>
Attempt at solving the problem:
<Window.Resources>
<ResourceDictionary>
<sys:Object x:Key="ItemItemsSource" />
</ResourceDictionary>
</Window.Resources>
<DataTemplate>
<ComboBox SelectedItem="{Binding SelectedItem}" ItemsSource="{Binding Source={StaticResource ItemItemsSource}}" />
</DataTemplate>
Update
Each DataTemplate has its own DataContext which means each instance of the data template has its own ItemsSource, which will populate at DataContext initialiser.
Update 2
The ideal way in my mind to solve this is to have a property in the DataContext/VM of the Window that they Combobox is bound too. Is this possible? Something like:
public class WindowsViewModel
{
public List<Object> SharedItemSource { get; set; }
}
<DataTemplate>
<ComboBox SelectedItem="{Binding SelectedItem}" ItemsSource="{Binding <Some Binding To SharedItemSource>}" />
</DataTemplate>
Where is the slow down ?
If it is when you show the ComboBox's popup, maybe you can try to use virtualization like this :
<DataTemplate>
<ComboBox SelectedItem="{Binding SelectedItem}" ItemsSource="{Binding ItemsSource}">
<ComboBox.ItemsPanel>
<ItemsPanelTemplate>
<VirtualizingStackPanel />
</ItemsPanelTemplate>
</ComboBox.ItemsPanel>
</ComboBox>
</DataTemplate>
Create a MainViewModel for your window or what ever control all your combobox's are in ,
cs:
public class MainViewModel
{
private List<object> _itemsSource;
public List<object> ItemsSource
{
get { return _itemsSource; }
set { _itemsSource = value; }
}
}
xaml:
<DataTemplate>
<ComboBox SelectedItem="{Binding SelectedItem}"
ItemsSource="{Binding Path=DataContext.ItemsSource,
RelativeSource={RelativeSource AncestorType=ItemsControl}}"/>
</DataTemplate>
If you have property defined in VM, then that will be loading just once when you will be instantiating it and be served as source for all the comboboxes.. not every combobox create its itemsSource.. it just consume it to generate its items.. so whether you put your itemsSource as Resource or in Datacontext is one and the same thing here.
anybody an idea why CommandParameter is always null?
The class TransactionViewModel has the collection property of TransactionCommands to be displayed in the ItemsControl. The items are of type CommandViewModel.
TransactionBrowserViewModel has the command AddJobForSelectedTransactionCommand. The command to be passed as a parameter the CommandViewModel.
View Snipp:
<ItemsControl Grid.Row="4"
ItemsSource="{Binding TransactionCommands}">
<ItemsControl.ItemTemplate>
<DataTemplate>
<telerik:RadButton Content="{Binding DisplayName}"
CommandParameter="{Binding DataContext, RelativeSource={RelativeSource Self}}"
Command="{Binding ViewModel.AddJobForSelectedTransactionCommand, ElementName=userControl}"/>
</DataTemplate>
</ItemsControl.ItemTemplate>
</ItemsControl>
Codebehind of UserControl:
[Export]
public partial class TransactionBrowserView : UserControl, IView<TransactionBrowserViewModel>
{
[ImportingConstructor]
public TransactionBrowserView()
{
InitializeComponent();
}
[Import]
public TransactionBrowserViewModel ViewModel
{
get { return (TransactionBrowserViewModel)this.DataContext; }
set { this.DataContext = value; }
}
}
OK, sorry I have found the error.
It is located on the RadButton by Telerik. I have tested the scenario with a default button. Here it works without any problems.
You have set the ComandParameter to the path of the DataContext of the RadButton, but I don't see that you have set anything to that DataContext anywhere.
Look into the Output window for information regarding your Binding errors... it should say something like 'There is no DataContext property on object XXX'.
What are you trying to bind to the CommandParameter property?
Try this binding
<ItemsControl x:Name="transactionList" Grid.Row="4" ItemsSource="{Binding TransactionCommands}">
<ItemsControl.ItemTemplate>
<DataTemplate>
<telerik:RadButton Content="{Binding DisplayName}"
CommandParameter="{Binding SelectedItem, ElementName=transactionList}"
Command="{Binding ViewModel.AddJobForSelectedTransactionCommand, ElementName=userControl}"/>
</DataTemplate>
</ItemsControl.ItemTemplate>
</ItemsControl>
Give your ItemsControl (like transactionList) and set the binding of the CommandParameter to the SelectedItem of your transactionList.
or does this not do what you want.
<telerik:RadButton Content="{Binding DisplayName}"
CommandParameter="{Binding}"
Command="{Binding ViewModel.AddJobForSelectedTransactionCommand, ElementName=userControl}"/>
i have the following XAML code
<ListBox x:Name="TrackedProgramList" Height="145" Width="605" ItemsSource=" {Binding}" >
<DataTemplate>
<StackPanel Orientation="Horizontal">
<TextBlock Text="{Binding Path=programName}"></TextBlock>
</StackPanel>
</DataTemplate>
</ListBox>
I'm binding a List to the Listbox's ItemsSource.
The List contains "FileInfo" objects.
FileInfos is an object with some attributes like "programName", "manufacturer" etc.
The problem now is that the list only displays something like:
Namespace.FileInfo
Namespace.FileInfo
Namespace.FileInfo
...
so i think that the path is incorrect.
The Error you are getting is probably:
Items collection must be empty before using ItemsSource.
There is probably no problem with binding.... your bigest problem is invalid xaml.
I am not sure what you are trying to achieve, but I guess you want to have listbox with horizonatal Stackpanel as ItemsPanel.
Then it should be like this:
<ListBox ... >
<ListBox.ItemsPanel>
<ItemsPanelTemplate>
<StackPanel Orientation="Horizontal" IsItemsHost="True"/>
</ItemsPanelTemplate>
</ListBox.ItemsPanel>
</ListBox>
And then you probably want to provide an ItemTemplate
<ListBox ... >
<ListBox.ItemsPanel>
<ItemsPanelTemplate>
<StackPanel Orientation="Horizontal" IsItemsHost="True"/>
</ItemsPanelTemplate>
</ListBox.ItemsPanel>
<ListBox.ItemTemplate>
<DataTemplate>
<Border Background="Red" Width="150" Height="100">
<TextBlock Text="{Binding Path=programName}" />
</Border>
</DataTemplate>
</ListBox.ItemTemplate>
</ListBox>
EDIT
After you edited your question it seems that you have new problem. Still... your XAML should not be working. If you used one you provided with your question. It's invalid.
If you are getting result like:
Namespace.FileInfo
Namespace.FileInfo
Namespace.FileInfo
Namespace.FileInfo
then your binding in ItemTemplate is not working correctly. Make sure programName is public property.
The properties you use as binding source properties for a binding must be public properties of your class. Explicitly defined interface properties cannot be accessed for binding purposes, nor can protected, private, internal, or virtual properties that have no base implementation.
As I said. My code works fine.
UPDATE
List<FileInfo> should be ListBox's DataContext... it probably is... since you get this result. What you should check is that in FileInfo class is programName as public property.
It should be something like this.
public class FileInfo : ObservableObject
{
private string _programName;
public string programName
{
get{ return this._programName;}
set
{
this._programName = value;
RaisePropertyChanged(() => this.programName);
}
}
}