I want to bind a list of buttons in combo boxes. Each Combobox will contains buttons of one category and so on. As referred in attached image.
Below is my code:
<ItemsControl x:Name="iNumbersList">
<ItemsControl.ItemsPanel>
<ItemsPanelTemplate>
<WrapPanel HorizontalAlignment="Center" VerticalAlignment="Center" Orientation="Horizontal" MaxWidth="930"/>
</ItemsPanelTemplate>
</ItemsControl.ItemsPanel>
<ItemsControl.ItemTemplate>
<DataTemplate>
<Button Click="ItemButtonClick"
Tag="{Binding ItemTag}"
HorizontalContentAlignment="Center"
VerticalContentAlignment="Center"
Height="100" Width="300">
<TextBlock TextAlignment="Center" Foreground="Red"
HorizontalAlignment="Center" FontWeight="SemiBold"
FontSize="25" TextWrapping="Wrap"
Text="{Binding ItemDisplayMember}"/>
</Button>
</DataTemplate>
</ItemsControl.ItemTemplate>
public class NumberModel
{
public string ItemDisplayMember { get; set; }
public object ItemTag { get; set; }
public string ItemCategory { get; set; }
}
How can I group by ItemCategory property and bind it on GUI, a ComboBox for each ItemCategory and then multiple buttons in it?
Probably you don't need a ComboBox but Expander because the objective is reachable using it. ComboBox is needed when you have to filter something inside it or use dropdown displayed above Windows content.
I wrote a simple example using MVVM programming pattern. There will be many new classes but most of it you need add to the project only once. Let's go from the scratch!
1) Create class NotifyPropertyChanged to implement INotifyPropertyChanged interface. It needed to make able Binding to update the layout dynamically in Runtime.
NotifyPropertyChanged.cs
public class NotifyPropertyChanged : INotifyPropertyChanged
{
public event PropertyChangedEventHandler PropertyChanged;
protected virtual void OnPropertyChanged([CallerMemberName]string propertyName = null)
=> PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
}
2) Create MainViewModel class derived from NotifyPropertyChanged. It will be used for Binding target Properties.
MainViewModel.cs
public class MainViewModel : NotifyPropertyChanged
{
public MainViewModel()
{
}
}
3) Attach MainViewModel to MainWindow's DataContext. One of ways - doing it in xaml.
MainWindow.xaml
<Window.DataContext>
<local:MainViewModel/>
</Window.DataContext>
4) Derive the data class NumberModel from NotifyPropertyChanged and add OnPropertyChanged call for each Property. This will give wonderful effect: when you change any Property in runtime, You'll immediately see the changes in UI. MVVM Magic called Binding :)
NumberModel.cs
public class NumberModel : NotifyPropertyChanged
{
private string _itemDisplayMember;
private object _itemTag;
private string _itemCategory;
public string ItemDisplayMember
{
get => _itemDisplayMember;
set
{
_itemDisplayMember = value;
OnPropertyChanged();
}
}
public object ItemTag
{
get => _itemTag;
set
{
_itemTag = value;
OnPropertyChanged();
}
}
public string ItemCategory
{
get => _itemCategory;
set
{
_itemCategory = value;
OnPropertyChanged();
}
}
}
5) When button is clicked I will not handle the Click event but call a Command. For easy use of Commands I suggest this relaying its logic class (grabbed here).
RelayCommand.cs
public class RelayCommand : ICommand
{
private readonly Action<object> _execute;
private readonly Func<object, bool> _canExecute;
public event EventHandler CanExecuteChanged
{
add { CommandManager.RequerySuggested += value; }
remove { CommandManager.RequerySuggested -= value; }
}
public RelayCommand(Action<object> execute, Func<object, bool> canExecute = null)
{
_execute = execute;
_canExecute = canExecute;
}
public bool CanExecute(object parameter) => _canExecute == null || _canExecute(parameter);
public void Execute(object parameter) => _execute(parameter);
}
6) All ready to fill the MainViewModel with code. I've added there a command and some items to collection for test.
MainViewModel.cs
public class MainViewModel : NotifyPropertyChanged
{
private ObservableCollection<NumberModel> _itemsList;
private ICommand _myCommand;
public ObservableCollection<NumberModel> ItemsList
{
get => _itemsList;
set
{
_itemsList = value;
OnPropertyChanged();
}
}
public ICommand MyCommand => _myCommand ?? (_myCommand = new RelayCommand(parameter =>
{
if (parameter is NumberModel number)
MessageBox.Show("ItemDisplayMember: " + number.ItemDisplayMember + "\r\nItemTag: " + number.ItemTag.ToString() + "\r\nItemCategory: " + number.ItemCategory);
}));
public MainViewModel()
{
ItemsList = new ObservableCollection<NumberModel>
{
new NumberModel { ItemDisplayMember = "Button1", ItemTag="Tag1", ItemCategory = "Category1" },
new NumberModel { ItemDisplayMember = "Button2", ItemTag="Tag2", ItemCategory = "Category1" },
new NumberModel { ItemDisplayMember = "Button3", ItemTag="Tag3", ItemCategory = "Category1" },
new NumberModel { ItemDisplayMember = "Button4", ItemTag="Tag4", ItemCategory = "Category2" },
new NumberModel { ItemDisplayMember = "Button5", ItemTag="Tag5", ItemCategory = "Category2" },
new NumberModel { ItemDisplayMember = "Button6", ItemTag="Tag6", ItemCategory = "Category2" },
new NumberModel { ItemDisplayMember = "Button7", ItemTag="Tag7", ItemCategory = "Category3" },
new NumberModel { ItemDisplayMember = "Button8", ItemTag="Tag8", ItemCategory = "Category4" },
new NumberModel { ItemDisplayMember = "Button9", ItemTag="Tag9", ItemCategory = "Category4" }
};
}
}
7) The main answer to your main question is: use IValueConverter to filter the list with requred criteria. I wrote 2 converters. First for Categories, second for Buttons.
Converters.cs
public class CategoryConverter : IValueConverter
{
public object Convert(object value, Type targetType, object parameter, CultureInfo culture)
{
if (value is ObservableCollection<NumberModel> collection)
{
List<NumberModel> result = new List<NumberModel>();
foreach (NumberModel item in collection)
{
if (!result.Any(x => x.ItemCategory == item.ItemCategory))
result.Add(item);
}
return result;
}
return null;
}
public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture)
{
throw new NotImplementedException();
}
}
public class ItemGroupConverter : IMultiValueConverter
{
public object Convert(object[] values, Type targetType, object parameter, CultureInfo culture)
{
if (values[0] is ObservableCollection<NumberModel> collection && values[1] is string categoryName)
{
List<NumberModel> result = new List<NumberModel>();
foreach (NumberModel item in collection)
{
if (item.ItemCategory == categoryName)
result.Add(item);
}
return result;
}
return null;
}
public object[] ConvertBack(object value, Type[] targetTypes, object parameter, CultureInfo culture)
{
throw new NotImplementedException();
}
}
8) Now everything is ready to fill the markup. I post full markup here to make everything clear.
Note: I faced Visual Studio 2019 16.5.4 crash while setting a MultiBinding in ItemsSource and applied the workaround.
MainWindow.xaml
<Window x:Class="WpfApp1.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:local="clr-namespace:WpfApp1"
Title="MainWindow" Height="600" Width="1000" WindowStartupLocation="CenterScreen">
<Window.DataContext>
<local:MainViewModel/>
</Window.DataContext>
<Window.Resources>
<local:CategoryConverter x:Key="CategoryConverter"/>
<local:ItemGroupConverter x:Key="ItemGroupConverter"/>
</Window.Resources>
<Grid>
<ItemsControl ItemsSource="{Binding ItemsList, Converter={StaticResource CategoryConverter}}">
<ItemsControl.ItemsPanel>
<ItemsPanelTemplate>
<WrapPanel Orientation="Vertical"/>
</ItemsPanelTemplate>
</ItemsControl.ItemsPanel>
<ItemsControl.ItemTemplate>
<DataTemplate DataType="{x:Type local:NumberModel}">
<Expander Header="{Binding ItemCategory}">
<ItemsControl DataContext="{Binding DataContext,RelativeSource={RelativeSource AncestorType=Window}}">
<ItemsControl.Style>
<Style TargetType="ItemsControl">
<Setter Property="ItemsSource">
<Setter.Value>
<MultiBinding Converter="{StaticResource ItemGroupConverter}">
<Binding Path="ItemsList"/>
<Binding Path="Header" RelativeSource="{RelativeSource AncestorType=Expander}"/>
</MultiBinding>
</Setter.Value>
</Setter>
</Style>
</ItemsControl.Style>
<ItemsControl.ItemsPanel>
<ItemsPanelTemplate>
<WrapPanel HorizontalAlignment="Left" VerticalAlignment="Center" Orientation="Horizontal" MaxWidth="930"/>
</ItemsPanelTemplate>
</ItemsControl.ItemsPanel>
<ItemsControl.ItemTemplate>
<DataTemplate DataType="{x:Type local:NumberModel}">
<Button Tag="{Binding ItemTag}"
HorizontalContentAlignment="Center"
VerticalContentAlignment="Center"
Height="100" Width="300"
Command="{Binding DataContext.MyCommand,RelativeSource={RelativeSource AncestorType=Window}}"
CommandParameter="{Binding}">
<TextBlock TextAlignment="Center" Foreground="Red"
HorizontalAlignment="Center" FontWeight="SemiBold"
FontSize="25" TextWrapping="Wrap"
Text="{Binding ItemDisplayMember}"/>
</Button>
</DataTemplate>
</ItemsControl.ItemTemplate>
</ItemsControl>
</Expander>
</DataTemplate>
</ItemsControl.ItemTemplate>
</ItemsControl>
</Grid>
</Window>
Job is done with MVVM. Enjoy. :)
P.S. Ah, yes, I forgot to show you the code-behind class. Here it is!
MainWindow.xaml.cs
public partial class MainWindow : Window
{
public MainWindow()
{
InitializeComponent();
}
}
Related
I can add texts to the DataGrid and by clicking the button (edit) I can drag the data from the DataGrid back into the text boxes, but my problem is if I change the data after dragging it over, it is not updated in the Datagrid.
<Canvas x:Name="CV_Projekte" Grid.Column="1" Background="White" Visibility="Visible">
<Label Content="Projektnumber:" Canvas.Left="44" Canvas.Top="84" FontSize="14" FontWeight="Bold"/>
<Label Content="Name:" Canvas.Left="43" Canvas.Top="113" FontSize="14" FontWeight="Bold" />
<Label Content="Unterposition:" Canvas.Left="44" Canvas.Top="146" FontSize="14" FontWeight="Bold" />
<Label Content="Describe:" Canvas.Left="44" Canvas.Top="182" FontSize="14" FontWeight="Bold" />
<TextBox Canvas.Left="183" Canvas.Top="89" TextWrapping="Wrap" Width="120" Text="{Binding ProjectNumber}" />
<TextBox Canvas.Left="44" Canvas.Top="216" TextWrapping="Wrap" Width="273" Height="155"/>
<TextBox Canvas.Left="183" Canvas.Top="151" TextWrapping="Wrap" Width="120"/>
<TextBox Canvas.Left="183" Canvas.Top="118" TextWrapping="Wrap" Width="120" Text="{Binding ProjectName}"/>
<Button Content="Add" Canvas.Left="383" Canvas.Top="167" Width="75" Command="{Binding StartCommand}" Opacity="0.2"/>
<Button Content="Edit" Canvas.Left="383" Canvas.Top="201" Width="75" Opacity="0.2" Command="{Binding UpdateCommand}" />
<DataGrid Height="575" Canvas.Left="616" Width="303" ItemsSource="{Binding CareList}" SelectedItem="{Binding SelectedCare}" >
<DataGrid.Columns>
<DataGridTextColumn Header="Number" Binding="{Binding Projektnummer}"/>
<DataGridTextColumn Header="Projektname" Width="184" Binding="{Binding Name}"/>
<DataGridTextColumn Header="Delete"/>
</DataGrid.Columns>
</DataGrid>
</Canvas>
This is the CareViewModel.cs
public class CareViewModel :INotifyPropertyChanged
{
private int _ProtjectNumber;
private string _ProjectName;
private Visibility _TrackingVisibility;
private Visibility _CarreVisibility;
private Care _selectedCare;
public int ProjectNumber
{
get
{
return _ProtjectNumber;
}
set
{
_ProtjectNumber = value;
OnPropertyChanged(nameof(ProjectNumber));
}
}
public string ProjectName
{
get
{
return _ProjectName;
}
set
{
_ProjectName = value;
OnPropertyChanged(nameof(ProjectName));
}
}
public Visibility TrackingVisibility
{
get
{
return _TrackingVisibility;
}
set
{
_TrackingVisibility = value;
OnPropertyChanged(nameof(_TrackingVisibility));
}
}
public Visibility CareVisibility
{
get
{
return _CarreVisibility;
}
set
{
_CarreVisibility = value;
OnPropertyChanged(nameof(CareVisibility));
}
}
public void TrackingView(RoutedEventArgs e)
{
TrackingVisibility = Visibility.Visible;
CareVisibility = Visibility.Collapsed;
}
public void CareView(RoutedEventArgs e)
{
TrackingVisibility = Visibility.Collapsed;
CareVisibility = Visibility.Visible;
}
public ObservableCollection<Care> CareList { get; set; }
public CareViewModel()
{
CareList = new ObservableCollection<Care>(); //List
StartCommand = new OurCommand<RoutedEventArgs>(Setting);
TrackingCommand = new OurCommand<RoutedEventArgs>(TrackingView);
CareCommand = new OurCommand<RoutedEventArgs>(CareView);
UpdateCommand = new OurCommand<RoutedEventArgs>(Bearbeitung);
TrackingVisibility = Visibility.Visible;
CareVisibility = Visibility.Collapsed;
}
public ICommand StartCommand { get; set; }
public ICommand TrackingCommand { get; set; }
public ICommand CareCommand { get; set; }
public ICommand UpdateCommand { get; set; }
public Care SelectedCare
{
get
{
return _selectedCare;
}
set
{
_selectedCare = value;
OnPropertyChanged(nameof(SelectedCare));
}
}
public void Setting(RoutedEventArgs e) // Add the Values to Datagrid
{
var projects = new Care();
projects.Projektnummer = _ProtjectNumber;
projects.Name = _ProjectName;
ProjectNumber = 0;
ProjectName = null;
SelectedCare = projects;
CareList.Add(projects);
}
public void Bearbeitung(RoutedEventArgs e) //Editing the value button
{
ProjectNumber = SelectedCare.Projektnummer;
ProjectName = SelectedCare.Name;
}
public event PropertyChangedEventHandler PropertyChanged;
private void OnPropertyChanged(string propertyName)
{
if(PropertyChanged != null)
{
PropertyChanged(this, new PropertyChangedEventArgs(propertyName));
}
}
Class Care
public class Care
{
private int _Projektnummer;
private string _Name;
public int Projektnummer
{
get { return _Projektnummer;}
set { _Projektnummer = value;}
}
public string Name
{
get { return _Name;}
set { _Name = value;}
}
}
Thanks to everyone who wants to help me.
I've created some demo project from source you provided.
Selection of the row fills TextBoxes automatically
Add Button creates new row and make it able to edit
Save button applies changes in TextBoxes
X button deletes the row
0. MVVM Helpers
I don't know what is OurCommand but I'm using almost unchanged this one.
public class RelayCommand : ICommand
{
private readonly Action<object> _execute;
private readonly Func<object, bool> _canExecute;
public event EventHandler CanExecuteChanged
{
add { CommandManager.RequerySuggested += value; }
remove { CommandManager.RequerySuggested -= value; }
}
public RelayCommand(Action<object> execute, Func<object, bool> canExecute = null)
{
_execute = execute;
_canExecute = canExecute;
}
public bool CanExecute(object parameter) => _canExecute == null || _canExecute(parameter);
public void Execute(object parameter) => _execute(parameter);
}
It supports command parameters and everything else related to ICommand.
To be able not to implement INotifyPropertyChanged in every View Model, I moved the implementation to the base class.
public class NotifyPropertyChanged : INotifyPropertyChanged
{
public event PropertyChangedEventHandler PropertyChanged;
protected virtual void OnPropertyChanged([CallerMemberName] string propertyName = null)
=> PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
}
[CallerMemberName] is a compiler trick that allows to call OnPropertyChanged() without arguments. Compiler will fill it automatically with Caller Member Name :) - Property name.
1. Markup
Use Grid and relative positioning of controls. It will help you to make responsive design where layout looks fine for almost any area size.
<Window x:Class="WpfApp1.MainWindow"
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:local="clr-namespace:WpfApp1"
mc:Ignorable="d"
Title="MainWindow" Width="1200" Height="600">
<Window.DataContext>
<local:CareViewModel/>
</Window.DataContext>
<Window.Resources>
<local:BoolToVisibilityConverter x:Key="BoolToVisibilityConverter"/>
</Window.Resources>
<Grid>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="0.5*"/>
<ColumnDefinition/>
</Grid.ColumnDefinitions>
<Grid>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="Auto"/>
<ColumnDefinition/>
</Grid.ColumnDefinitions>
<Grid.RowDefinitions>
<RowDefinition Height="Auto"/>
<RowDefinition/>
<RowDefinition Height="Auto"/>
</Grid.RowDefinitions>
<Grid.Resources>
<Style TargetType="TextBlock">
<Setter Property="MinHeight" Value="20"/>
<Setter Property="Margin" Value="5"/>
<Setter Property="FontSize" Value="14"/>
<Setter Property="FontWeight" Value="Bold"/>
</Style>
<Style TargetType="TextBox">
<Setter Property="MinHeight" Value="20"/>
<Setter Property="Margin" Value="5"/>
<Style.Triggers>
<DataTrigger Binding="{Binding EditingCare}" Value="{x:Null}">
<Setter Property="IsEnabled" Value="False"/>
</DataTrigger>
</Style.Triggers>
</Style>
<Style TargetType="Button">
<Setter Property="Width" Value="75"/>
<Setter Property="Margin" Value="5"/>
</Style>
</Grid.Resources>
<StackPanel>
<TextBlock Text="Projektnumber:"/>
<TextBlock Text="Name:"/>
<TextBlock Text="Unterposition:"/>
<TextBlock Text="Describe:"/>
</StackPanel>
<StackPanel Grid.Column="1">
<TextBox Text="{Binding EditingCare.ProjectNumber}"/>
<TextBox Text="{Binding EditingCare.Name}"/>
<TextBox Text="{Binding EditingCare.Subtitle}"/>
</StackPanel>
<TextBox TextWrapping="Wrap" Grid.Row="1" Grid.ColumnSpan="2" Text="{Binding EditingCare.Description}"/>
<DockPanel Margin="5" HorizontalAlignment="Right" Grid.Row="2" Grid.ColumnSpan="2">
<Button Content="Add" Command="{Binding AddCommand}" />
<Button Content="Save" Command="{Binding UpdateCommand}" />
</DockPanel>
</Grid>
<DataGrid Grid.Column="1" ItemsSource="{Binding CareList}" SelectedItem="{Binding SelectedCare}" AutoGenerateColumns="False" CanUserAddRows="False">
<DataGrid.Columns>
<DataGridTextColumn Header="Number" Binding="{Binding ProjectNumber}"/>
<DataGridTextColumn Header="Projektname" Binding="{Binding Name}"/>
<DataGridTextColumn Header="Unterposition" Binding="{Binding Subtitle}" Width="*"/>
<DataGridTemplateColumn Header="Delete">
<DataGridTemplateColumn.CellTemplate>
<DataTemplate>
<Button Content="X" Command="{Binding DataContext.DeleteCommand,RelativeSource={RelativeSource AncestorType=DataGrid}}" CommandParameter="{Binding}"/>
</DataTemplate>
</DataGridTemplateColumn.CellTemplate>
</DataGridTemplateColumn>
</DataGrid.Columns>
</DataGrid>
</Grid>
</Window>
2. Data
To fix the initial problem I've implemented INotifyPropertyChanged for the Care class.
public class Care : NotifyPropertyChanged
{
private int _projectNumber;
private string _name;
private string _description;
private string _subtitle;
public int ProjectNumber
{
get => _projectNumber;
set
{
_projectNumber = value;
OnPropertyChanged();
}
}
public string Name
{
get => _name;
set
{
_name = value;
OnPropertyChanged();
}
}
public string Subtitle
{
get => _subtitle;
set
{
_subtitle = value;
OnPropertyChanged();
}
}
public string Description
{
get => _description;
set
{
_description = value;
OnPropertyChanged();
}
}
public Care() { }
public Care(Care care)
{
ProjectNumber = care.ProjectNumber;
Name = care.Name;
Subtitle = care.Subtitle;
Description = care.Description;
}
}
2. View Model
I played a lot with code and came up with following one.
public class CareViewModel : NotifyPropertyChanged
{
private bool _trackingVisible;
private bool _careVisible;
private Care _selectedCare;
private Care _editingCare;
private ObservableCollection<Care> _careList;
private ICommand _addCommand;
private ICommand _trackingCommand;
private ICommand _careCommand;
private ICommand _updateCommand;
private ICommand _deleteCommand;
public ObservableCollection<Care> CareList
{
get => _careList;
set
{
_careList = value;
OnPropertyChanged();
}
}
public bool TrackingVisibility
{
get => _trackingVisible;
set
{
_trackingVisible = value;
OnPropertyChanged();
}
}
public bool CareVisibility
{
get => _careVisible;
set
{
_careVisible = value;
OnPropertyChanged();
}
}
public Care SelectedCare
{
get => _selectedCare;
set
{
_selectedCare = value;
EditingCare = value is Care care ? new Care(care) : value;
OnPropertyChanged();
}
}
public Care EditingCare
{
get => _editingCare;
set
{
_editingCare = value;
OnPropertyChanged();
}
}
public ICommand TrackingCommand => _trackingCommand ?? (_trackingCommand = new RelayCommand(parameter =>
{
TrackingVisibility = true;
CareVisibility = false;
}));
public ICommand CareCommand => _careCommand ?? (_careCommand = new RelayCommand(parameter =>
{
TrackingVisibility = false;
CareVisibility = true;
}));
// Add the Values to Datagrid
public ICommand AddCommand => _addCommand ?? (_addCommand = new RelayCommand(parameter =>
{
Care newItem = new Care();
CareList.Add(newItem);
SelectedCare = newItem;
}));
//Editing the value button
public ICommand UpdateCommand => _updateCommand ?? (_updateCommand = new RelayCommand(parameter =>
{
int index = CareList.IndexOf(SelectedCare);
CareList[index] = EditingCare;
SelectedCare = CareList[index];
}, parameter => EditingCare != null && SelectedCare != null));
public ICommand DeleteCommand => _deleteCommand ?? (_deleteCommand = new RelayCommand(parameter =>
{
if (parameter is Care care)
{
CareList.Remove(care);
}
}));
public CareViewModel()
{
CareList = new ObservableCollection<Care>();
TrackingVisibility = true;
CareVisibility = false;
}
}
3. Visibility
Among other changes pay attention to former Visibility properties now they are bool. And to attach it to controls here's converter that is already meintoined in Markup.
MVVM doesn't suggest to use UI-related types in View Model or Model unless you need it for special purpose.
public class BoolToVisibilityConverter : IValueConverter
{
public object Convert(object value, Type targetType, object parameter, CultureInfo culture)
=> value is bool v && v ? Visibility.Visible : Visibility.Collapsed;
public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture)
=> null;
}
Usage of converter
<Button Visibility="{Binding TrackingVisibility, Converter={StaticResource BoolToVisibilityConverter}}"/>
I have a problem to perform filtering of ICollectionView in combination with usage of CompositeCollection.
The illustration describes what I want to achieve:
Window with filter text box, items collection and "Add" button
Requirements:
The "Add" button must be the part of the WrapPanel
The filtering should be performed via ICollectionView.View.Filter
DebugWindow.xaml:
<StackPanel>
<TextBox Text="{Binding TextBoxText, UpdateSourceTrigger=PropertyChanged}" Margin="5" BorderBrush="Black"/>
<ItemsControl BorderBrush="Gray">
<!-- Resources -->
<ItemsControl.Resources>
<CollectionViewSource x:Key="ColVSKey"
Source="{Binding MyCollection}"/>
</ItemsControl.Resources>
<!-- Items Source -->
<ItemsControl.ItemsSource>
<CompositeCollection>
<CollectionContainer Collection="{Binding Source={StaticResource ColVSKey}}"/>
<Button Content="Add another one" Margin="5"/>
</CompositeCollection>
</ItemsControl.ItemsSource>
<!-- Item Template -->
<ItemsControl.ItemTemplate>
<DataTemplate>
<TextBlock Text="{Binding}"
Background="Gray"
Margin="10"
Padding="5"/>
</DataTemplate>
</ItemsControl.ItemTemplate>
<!-- Items Panel -->
<ItemsControl.ItemsPanel>
<ItemsPanelTemplate>
<WrapPanel Orientation="Horizontal"/>
</ItemsPanelTemplate>
</ItemsControl.ItemsPanel>
</ItemsControl>
</StackPanel>
DebugWindow.xaml.cs:
public partial class DebugWindow : Window, INotifyPropertyChanged
{
private string _textBoxText = "";
public ObservableCollection<string> MyCollection { get; set; }
public Predicate<object> FilterFunction { get; set; }
public event PropertyChangedEventHandler PropertyChanged;
private ICollectionView view;
public string TextBoxText
{
get
{
return _textBoxText;
}
set
{
if (value == _textBoxText)
return;
_textBoxText = value;
if (PropertyChanged != null)
PropertyChanged(this, new PropertyChangedEventArgs(nameof(TextBoxText)));
if (view != null)
view.Refresh();
}
}
public DebugWindow()
{
InitializeComponent();
MyCollection = new ObservableCollection<string>() { "one", "two", "Three", "four", "five", "six", "seven", "Eight" };
FilterFunction = new Predicate<object>((o) => Filter(o));
view = CollectionViewSource.GetDefaultView(MyCollection);
if (view != null)
view.Filter = new Predicate<object>((o) => Filter(o));
this.DataContext = this;
}
public bool Filter(object v)
{
string s = (string)v;
bool ret = false;
if (s.IndexOf(TextBoxText) != -1)
ret = true;
return ret;
}
}
The problem is, that the view = CollectionViewSource.GetDefaultView(MyCollection); is the View associated with the CollectionViewSource defined within Resources and not the View of the CollectionContainer. So the wrong View is refreshed and the displayed View is not refreshed at all.
I was able to achieve desired behaviour with extending the CollectionContainer, hooking up to CollectionChanged event:
public class MyCollectionContainer : CollectionContainer
{
private ICollectionView _view;
public ICollectionView View
{
get
{
return _view;
}
}
public MyCollectionContainer()
{
this.CollectionChanged += MyCollectionContainer_CollectionChanged;
}
private void MyCollectionContainer_CollectionChanged(object sender, System.Collections.Specialized.NotifyCollectionChangedEventArgs e)
{
if (_view == null && Collection != null && MyFilter != null)
{
_view = CollectionViewSource.GetDefaultView(Collection);
_view.Filter += MyFilter;
}
}
}
and exposing it to the code-behind:
...in XAML...
<CompositeCollection>
<local:MyCollectionContainer x:Name="MyCollectionContainer" Collection="{Binding Source={StaticResource ColVSKey}}"/>
<Button Content="Add another one" Margin="5"/>
</CompositeCollection>
...in constructor...
MyCollectionContainer.MyFilter = new Predicate<object>((o) => Filter(o));
...in TextBoxText property set...
if(MyCollectionContainer.View!=null)
MyCollectionContainer.View.Refresh();
Questions:
Is there a way how to achieve the behaviour I require without exposing the control to code-behind?
Is it possible to bind a MVVM to View of the CollectionContainer?
Thanks in advance and sorry for long post.
I have found an elegant solution to my problem. Thanks to article on Thomas Levesque's .NET blog which was referenced in this answer: CollectionContainer doesn't bind my Collection I can simply perform binding and the ICollectionView obtained by
CollectionViewSource.GetDefaultView(MyCollection); is the right one to be refreshed.
BindingProxy.cs (credit goes to Thomas Levesque)
public class BindingProxy : Freezable
{
#region Overrides of Freezable
protected override Freezable CreateInstanceCore()
{
return new BindingProxy();
}
#endregion
public object MyData
{
get { return (object)GetValue(MyDataProperty); }
set { SetValue(MyDataProperty, value); }
}
public static readonly DependencyProperty MyDataProperty =
DependencyProperty.Register("MyData", typeof(object), typeof(BindingProxy), new UIPropertyMetadata(null));
}
The updated XAML:
<ItemsControl>
<ItemsControl.Resources>
<local:BindingProxy x:Key="proxy" MyData="{Binding Path=MyCollection}"/>
</ItemsControl.Resources>
<ItemsControl.ItemsSource>
<CompositeCollection>
<CollectionContainer Collection="{Binding Source={StaticResource proxy}, Path=MyData}"/>
<Button Content="Add another one" Margin="5"/>
</CompositeCollection>
</ItemsControl.ItemsSource>
...
</ItemsControl>
I´m new with WPF and the MVVM pattern, i have a view with 2 ComboBox, 1 Datapicker, 1 textbox and 1button, the intention is when i hit the button i obtain the data from this view in the ViewModel, to try this i've been based on this question in StackOverflow: https://stackoverflow.com/questions/27447042/xaml-button-comand-to-pass-to-date-picker-properties-to-method
The problema is that in the XAML in
Window.Resources PedidosRetraso:ICommandMultiDateConverter x:Key="multiDateConverter"/>
Give me an error, NameSpace prefix "PedidosRetraso" not defined, and i don´t know why, the name of the namespace is correct,¿What i´m doing wrong?.
Also I would like to know how i can verify that the ComboBox have some value selected to disable the button in case of not selected value, i think i shoul do it in CanExecute but i dont know how i know the value of the ComboBox.
This is my XAML code
<Window x:Class="PedidosRetraso.MainWindow"
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:local="clr-namespace:PedidosRetraso"
mc:Ignorable="d"
Title="MainWindow" Height="700" Width="700"
>
<Window.Resources>
<PedidosRetraso:ICommandMultiDateConverter x:Key="multiDateConverter"/>
</Window.Resources>
<Grid>
<ComboBox x:Name="comboBox" HorizontalAlignment="Left" Margin="22,59,0,0" VerticalAlignment="Top" Width="120" SelectionChanged="comboBox_SelectionChanged">
</ComboBox>
<Button x:Name="button" Content="Button" HorizontalAlignment="Left" Margin="573,66,0,0" VerticalAlignment="Top" Width="75" >
<Button.CommandParameter>
<MultiBinding Converter="{StaticResource ResourceKey=multiDateConverter}">
<Binding ElementName="textBox" Path="Text"></Binding>
<Binding ElementName="comboBox" Path="Text"></Binding>
<Binding ElementName="comboBox1" Path="Text"></Binding>
<Binding ElementName="Fecha" Path="Text"></Binding>
</MultiBinding>
</Button.CommandParameter>
<Button.Command>
<Binding Path="GetAllActionLogsBetweenDatesCommand"></Binding>
</Button.Command>
</Button>
<ComboBox x:Name="comboBox1" HorizontalAlignment="Left" Margin="183,59,0,0" VerticalAlignment="Top" Width="120" ItemsSource="{Binding _combo}">
<ComboBox.ItemTemplate>
<DataTemplate>
<StackPanel Orientation="Horizontal">
<TextBlock Text="Situación: "></TextBlock>
<TextBlock Text="{Binding Path=Nombre}" Width="80"></TextBlock>
</StackPanel>
</DataTemplate>
</ComboBox.ItemTemplate>
</ComboBox>
And this is my ViewModel:
public class RelayCommand : ICommand
{
private Predicate<object> m_canExecute;
private Action<object> m_execute;
public RelayCommand(Action<object> execute)
{
m_execute = execute;
}
public RelayCommand(Predicate<object> canExecute, Action<object> execute)
{
m_canExecute = CanExecute;
m_execute = execute;
}
public bool CanExecute(object parameter)
{
if (m_canExecute == null)
{
return true;
}
return m_canExecute(parameter);
}
public event EventHandler CanExecuteChanged;
public void Execute(object parameter)
{
if (m_execute != null)
{
m_execute(parameter);
}
}
}
public class ICommandMultiDateConverter : IMultiValueConverter
{
public object Convert(object[] values, Type targetType, object parameter, System.Globalization.CultureInfo culture)
{
return new string[] { values[0].ToString(), values[1].ToString(), values[2].ToString(),values[3].ToString() };
}
public object[] ConvertBack(object value, Type[] targetTypes, object parameter, System.Globalization.CultureInfo culture)
{
throw new NotImplementedException();
}
}
class ViewModelMain
{
public ICommand GetAllActionLogsBetweenDatesCommand { get; set; }
public ObservableCollection<PocoCombo> _combo { get; set; }
public ViewModelMain()
{
GetAllActionLogsBetweenDatesCommand = new RelayCommand(GetAllActionLogsBetweenDates_Execute);
_combo = new ObservableCollection<PocoCombo> { new PocoCombo { Id = 20, Nombre = "Enviado" }, new PocoCombo { Id = 25, Nombre = "DIF" }, new PocoCombo { Id = 30, Nombre = "Confirmado" }, new PocoCombo { Id = 40, Nombre = "RP" }, new PocoCombo { Id = 50, Nombre = "Cerrado" }, new PocoCombo { Id = 60, Nombre = "C" } };
}
private void GetAllActionLogsBetweenDates_Execute(object parameter)
{
try
{
var stringList = parameter as string[];
string proveedor = stringList[0];
string empresa = stringList[1];
string situacion = stringList[2];
DateTime fecha = DateTime.Parse(stringList[3]);
// Aqui la consulta SQL
}
catch (Exception ex)
{
}
}
EDIT i put a photo
Thanks.
You have defined the namespace here xmlns:local="clr-namespace:PedidosRetraso", so use it!
Change
<PedidosRetraso:ICommandMultiDateConverter x:Key="multiDateConverter"/>
To
<local:ICommandMultiDateConverter x:Key="multiDateConverter"/>
EDIT
You have added a picture. ICommandMultiDataConverter isn't in namespace PedidosRetraso as I thought but PedidosRetraso.viewModel
Change xmlns:local="clr-namespace:PedidosRetraso" to xmlns:local="clr-namespace:PedidosRetraso.viewModel"
I'm developing a UWP App, with Mvvm Light and Behaviours SDK. I defined a multi selectable ListView:
<ListView
x:Name="MembersToInviteList"
IsMultiSelectCheckBoxEnabled="True"
SelectionMode="Multiple"
ItemsSource="{Binding Contacts}"
ItemTemplate="{StaticResource MemberTemplate}">
</ListView>
I'd like, with a button binded to a MVVM-Light RelayCommand, to obtain a list with the selected items:
<Button
Command="{Binding AddMembersToEvent}"
CommandParameter="{Binding ElementName=MembersToInviteList, Path=SelectedItems}"
Content="Ok"/>
The RelayCommand (of MVVM-Light framework):
private RelayCommand<object> _addMembersToEvent;
public RelayCommand<object> AddMembersToEvent
{
get
{
return _addMembersToEvent
?? (_addMembersToEvent = new RelayCommand<object>(
(selectedMembers) =>
{
// Test
// selectedMembers is always null!
}));
}
}
I put a breakpoint inside the command, and I notice that selectedMembers is always null, although I select various items. By the console output I don't see any binding error or something else.
Also, if I pass as CommandParameter the whole list, and I put a breakpoint inside command's definition, i notice that I can't access to SelectedItems nor SelecteRanges value.
<DataTemplate x:Name="MemberTemplate">
<Viewbox MaxWidth="250">
<Grid Width="250"
Margin="5, 5, 5, 5"
Background="{StaticResource MyLightGray}"
BorderBrush="{StaticResource ShadowColor}"
BorderThickness="0, 0, 0, 1"
CornerRadius="4"
Padding="5">
<Grid.ColumnDefinitions>
<ColumnDefinition Width="Auto" />
<ColumnDefinition Width="1*" />
</Grid.ColumnDefinitions>
<Grid Grid.Column="0"
Width="45"
Height="45"
Margin="5,0,5,0"
VerticalAlignment="Center"
CornerRadius="50">
<Grid.Background>
<ImageBrush AlignmentX="Center"
AlignmentY="Center"
ImageSource="{Binding Image.Url,
Converter={StaticResource NullGroupImagePlaceholderConverter}}"
Stretch="UniformToFill" />
</Grid.Background>
</Grid>
<TextBlock Grid.Column="1"
Margin="3"
VerticalAlignment="Center"
Foreground="{StaticResource ForegroundTextOverBodyColor}"
Style="{StaticResource LightText}"
Text="{Binding Alias}" />
</Grid>
</Viewbox>
</DataTemplate>
What's the reason? How can I obtain such list?
One of the solutions to pass SelectedItems from ListView in ViewModel (with RelayCommands) is described in igralli's blog.
Pass ListView SelectedItems to ViewModel in Universal apps
Try the following code to get the selected objects from the parameter.
private RelayCommand<IList<object>> _addMembersToEvent;
public RelayCommand<IList<object>> AddMembersToEvent
{
get
{
return _addMembersToEvent
?? (_addMembersToEvent = new RelayCommand<IList<object>>(
selectedMembers =>
{
List<object> membersList = selectedMembers.ToList();
}));
}
}
I've made a small example for your case without MVVM-Light and it works perfect.
Maybe the problem is within the RelayCommand-class. I've written the following:
public class RelayCommand<T> : ICommand
{
private readonly Action<T> execute;
private readonly Predicate<T> canExecute;
public RelayCommand(Action<T> execute, Predicate<T> canExecute = null )
{
if (execute == null)
throw new ArgumentNullException("execute");
this.execute = execute;
this.canExecute = canExecute;
}
public bool CanExecute(object parameter)
{
if (canExecute == null)
return true;
return canExecute((T) parameter);
}
public void Execute(object parameter)
{
execute((T) parameter);
}
public event EventHandler CanExecuteChanged
{
add { CommandManager.RequerySuggested += value; }
remove { CommandManager.RequerySuggested -= value; }
}
}
Thanks to Roman's answer I figured out how to solve the issue:
First of all, as Roman suggested:
private RelayCommand<IList<object>> _addMembersToEvent;
public RelayCommand<IList<object>> AddMembersToEvent
{
get
{
return _addMembersToEvent
?? (_addMembersToEvent = new RelayCommand<IList<object>>(
selectedMembers =>
{
List<object> membersList = selectedMembers.ToList();
}));
}
}
Then, the XAML:
<Button
Command="{Binding AddMembersToEvent}"
CommandParameter="{Binding ElementName=MembersToInviteList, Converter={StaticResource ListViewSelectedItemsConverter}}"
Content="Ok"/>
The difference here is that I passed the whole list as parameter, not it's SelectedItems property. Then, using an IValueConverter I converted from the ListView object to IList<object> of SelectedMember:
public class ListViewSelectedItemsConverter : IValueConverter
{
public object Convert(object value, Type targetType, object parameter, string language)
{
var listView = value as ListView;
return listView.SelectedItems;
}
public object ConvertBack(object value, Type targetType, object parameter, string language)
{
throw new NotImplementedException();
}
}
In this way the RelayCommand<IList<object>> got the right list and not a null value.
I can't find a reason but you can easily skirt the issue altogether by having an "IsSelected" property on your Contact object and putting a check box on your template:
<Grid Background="{ThemeResource ApplicationPageBackgroundThemeBrush}">
<Grid.RowDefinitions>
<RowDefinition Height="*"></RowDefinition>
<RowDefinition Height="Auto"></RowDefinition>
<RowDefinition Height="Auto"></RowDefinition>
</Grid.RowDefinitions>
<ListView ItemsSource="{Binding Contacts}">
<ListView.ItemTemplate>
<DataTemplate>
<Grid>
<CheckBox Content="{Binding Name}" IsChecked="{Binding IsSelected, Mode=TwoWay}"></CheckBox>
</Grid>
</DataTemplate>
</ListView.ItemTemplate>
</ListView>
<TextBlock Grid.Row="1" Text="{Binding SelectedItemsOutput}"></TextBlock>
<Button Grid.Row="2" Content="What is checked?" Command="{Binding GoCommand}"></Button>
</Grid>
and VMs etc:
public class MainViewModel : INotifyPropertyChanged
{
private string _selectedItemsOutput;
public ObservableCollection<Contact> Contacts { get; set; } = new ObservableCollection<Contact> { new Contact() { Id = 1, Name = "Foo" }, new Contact() { Id = 2, Name = "Bar" } };
public ICommand GoCommand => new RelayCommand(Go);
public string SelectedItemsOutput
{
get { return _selectedItemsOutput; }
set
{
if (value == _selectedItemsOutput) return;
_selectedItemsOutput = value;
OnPropertyChanged();
}
}
void Go()
{
SelectedItemsOutput = string.Join(", ", Contacts.Where(x => x.IsSelected).Select(x => x.Name));
}
public event PropertyChangedEventHandler PropertyChanged;
}
public class Contact : INotifyPropertyChanged
{
private bool _isSelected;
public int Id { get; set; }
public string Name { get; set; }
public bool IsSelected
{
get { return _isSelected; }
set
{
if (value == _isSelected) return;
_isSelected = value;
OnPropertyChanged();
}
}
public override string ToString()
{
return Name;
}
public event PropertyChangedEventHandler PropertyChanged;
}
public sealed partial class MainPage : Page
{
public MainPage()
{
this.InitializeComponent();
DataContext = new MainViewModel();
}
}
Just my five cents and might be an absolutely long shot, but you should check this:
If the ItemsSource implements IItemsRangeInfo, the SelectedItems collection is not updated based on selection in the list. Use the SelectedRanges property instead.
I am just guessing based on Tomtom's answer, since he says he did almost exactly as you and got a working solution. Maybe the difference is in this little detail. I haven't checked it myself yet.
So let's say I have a simple little WPF app that looks like this:
-------------------------------
Amount Left: 1,000
[Subtract 1]
[Subtract 5]
[Subtract 15]
[Subtract 30]
-------------------------------
"Amount Left:" and "1,000" are stand alone TextBlocks.
The "Subtract x" are all buttons, inside a ListView, inside a DataTemplate. Each time a button is clicked, the amount of the button is subtracted from the 1,000. All of that I have working.
Here's what I can't figure out. When the Amount Left falls below 30, the last button needs to become disabled. When the amount falls below 15, the second to last button becomes disabled. Etc and so on, until the Amount Left is Zero and all buttons are disabled. I can not figure out how to disable the buttons.
This example I'm giving here is not exactly what I'm trying to do, but it's a greatly simplified example that will make this post a lot shorter and simpler. Here, in essence, is what I have now.
XAML:
<DockPanel>
<TextBlock Text="Amount Left:" />
<TextBlock x:Name="AmountLeft" Text="1,000.00" />
</DockPanel>
<DockPanel>
<ListBox x:Name="AuthorListBox">
<ListView.ItemTemplate>
<DataTemplate>
<Button x:Name="SubButtom" Content="{Binding SubtractAmount}" Click="clickSubtract" />
<DataTemplate>
</ListBox>
</DockPanel>
XAML.cs
private void clickSubtract(object sender, RoutedEventArgs e)
{
Button button = sender as Button;
Int32 SubtractAmount = ((Data.AppInformation)button.DataContext).SubtractAmount; // This is the amount to be subtracted
// logic to update the amount remaining. This works.
// What I need to figure out is how to disable the buttons
}
You can accomplish using MVVM, by having an IsEnabled property for your Button ViewModels. With this approach, you will not need any 'code behind' as you currently have using a click event handler.
Xaml:
<Grid>
<StackPanel>
<StackPanel Orientation="Horizontal">
<TextBlock Text="Amount Left:" />
<TextBlock Text="{Binding CurrentAmount}" />
</StackPanel>
<ListBox ItemsSource="{Binding Buttons}">
<ListBox.ItemTemplate>
<DataTemplate>
<Button Command="{Binding SubtractCommand}" Width="200" Height="75" x:Name="SubButtom" Content="{Binding SubtractAmount}" IsEnabled="{Binding IsEnabled}"/>
</DataTemplate>
</ListBox.ItemTemplate>
</ListBox>
</StackPanel>
</Grid>
Xaml.cs:
public partial class MainWindow : Window
{
public MainWindow()
{
InitializeComponent();
DataContext = new ViewModel();
}
}
We want the main ViewModel that will have a list of Button ViewModels.
ButtonViewModel.cs:
namespace WpfApplication1
{
public class ButtonViewModel : INotifyPropertyChanged
{
private bool _isEnabled;
private ViewModel _viewModel;
public bool IsEnabled
{
get { return _isEnabled; }
set
{
_isEnabled = value;
OnPropertyChanged();
}
}
public int SubtractAmount { get; set; }
public ICommand SubtractCommand { get; private set; }
public ButtonViewModel(ViewModel viewModel)
{
_viewModel = viewModel;
IsEnabled = true;
SubtractCommand = new CommandHandler(() =>
{
_viewModel.CurrentAmount -= SubtractAmount;
}, true);
}
public event PropertyChangedEventHandler PropertyChanged;
protected virtual void OnPropertyChanged([CallerMemberName] string propertyName = null)
{
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
}
}
public class CommandHandler : ICommand
{
private readonly Action _action;
private readonly bool _canExecute;
public CommandHandler(Action action, bool canExecute)
{
_action = action;
_canExecute = canExecute;
}
public bool CanExecute(object parameter)
{
return _canExecute;
}
public event EventHandler CanExecuteChanged;
public void Execute(object parameter)
{
_action();
}
}
}
and now the main ViewModel.
ViewModel.cs:
namespace WpfApplication1
{
public class ViewModel : INotifyPropertyChanged
{
private int _currentAmount;
public int CurrentAmount
{
get { return _currentAmount; }
set
{
_currentAmount = value;
OnPropertyChanged();
if (Buttons != null)
{
foreach (var button in Buttons)
{
if ((value - button.SubtractAmount) <= 0)
{
button.IsEnabled = false;
}
}
}
}
}
public List<ButtonViewModel> Buttons { get; private set; }
public ViewModel()
{
CurrentAmount = 1000;
Buttons = new List<ButtonViewModel>
{
new ButtonViewModel(this)
{
SubtractAmount = 1
},
new ButtonViewModel(this)
{
SubtractAmount = 5
},
new ButtonViewModel(this)
{
SubtractAmount = 15
},
new ButtonViewModel(this)
{
SubtractAmount = 30
}
};
}
public event PropertyChangedEventHandler PropertyChanged;
protected virtual void OnPropertyChanged([CallerMemberName] string propertyName = null)
{
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
}
}
}
As you can see, each Button ViewModel will decrement the CurrentAmount using a Command (the preferred method over a click event). Whenever the CurrentAmount is changed, some simple logic is done by the main ViewModel that will disable associated buttons.
This is tested and works. Let me know if you have any questions.
I would go ahead creating a Converter and bind the IsEnabled property of the button. pass the value and do the logic.
Namespace
System.Windows.Data
System.Globalization
CODE
public class IsEnabledConverter : IValueConverter
{
public object Convert(object value, Type targetType,
object parameter, CultureInfo culture)
{
// Do the logic
}
public object ConvertBack(object value, Type targetType,
object parameter, CultureInfo culture)
{
// Do the Logic
}
}
XAML
Add the resurce like this
<Window.Resources>
<local:IsEnabledConverter x:Key="converter" />
</Window.Resources>
<Button x:Name="SubButtom" IsEnabled="{Binding Value, Converter= {StaticResource converter}}" Content="{Binding SubtractAmount}" Click="clickSubtract" />
You can learn about converters from below link
http://wpftutorial.net/ValueConverters.html
When you build the class with Converter all Xaml Errors will go off.
Your best option would be to use a command on the viewmodel instead of a click event handler:
public ICommand SubtractCommand = new DelegateCommand<int>(Subtract, i => i <= AmountLeft);
private void Subtract(int amount)
{
AmountLeft = AmountLeft - amount;
}
XAML:
<ListBox x:Name="AuthorListBox">
<ListBox.ItemTemplate>
<DataTemplate>
<Button x:Name="SubButtom" Content="{Binding SubtractAmount}"
Command="{Binding SubtractCommand, RelativeSource={RelativeSource AncestorType=DataGrid}}"
CommandParameter="{Binding SubtractAmount}"/>
</DataTemplate>
</ListBox.ItemTemplate>
</ListBox>