How to filter the CollectionContainer - c#

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>

Related

WPF MVVM : Bind a command which is on a ViewModel in a Child ItemSource

Im using WPF with MVVM design-patern, and I need to bind a command which is of a ItemsSource binding parent :
View :
<Window x:Class="TaskSupervisor.MainWindow"
...
...>
<Window.DataContext>
<local:TacheGroupeViewModel/>
</Window.DataContext>
<Grid>
<TreeView x:Name="TachesTree" ItemsSource="{Binding TachesGroupes.GroupesConfig}">
<TreeView.ItemTemplate>
<HierarchicalDataTemplate ItemsSource="{Binding Value.Tasks}">
<HierarchicalDataTemplate.ItemTemplate>
<DataTemplate>
<Grid>
<CheckBox x:Name="InProgress"
IsChecked="{Binding ModifInProgress}">
</CheckBox>
<StackPanel Grid.Row="2" Grid.Column="2" Orientation="Horizontal">
...
</StackPanel>
<StackPanel Grid.Row="3" Grid.Column="2" Orientation="Horizontal">
...
</StackPanel>
</Grid>
</DataTemplate>
</HierarchicalDataTemplate.ItemTemplate>
<TextBlock Text="{Binding Value.DisplayGroupe, Mode=OneWay}"/>
</HierarchicalDataTemplate>
</TreeView.ItemTemplate>
</TreeView>
</Grid>
</Window>
ViewModel :
class TacheGroupeViewModel : INotifyPropertyChanged {
public Model.TachesGroupes TachesGroupes { get; set; }
public TacheGroupeViewModel() {
this.TachesGroupes = new Model.TachesGroupes();
// return a Dictionary<string, CustomClass> Object
// CustomClass => Contain some field and a List Of custom class
}
private ICommand _cmdEnCoursDeTraitement;
public ICommand CmdEnCoursDeTraitement {
get {
return _cmdEnCoursDeTraitement ?? ( _cmdEnCoursDeTraitement = new RelayCommand(x => {Test(); }) );
}
}
public event PropertyChangedEventHandler PropertyChanged;
private void RaisePropertyChanged(string propertyName) {
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
}
public void Test() {
var test = "xD";
}
}
I want to make a bind on my Checkbox object : How can I do this while the Items source of the HierarchicalDataTemplate is set to "Tasks" I can only binding on my "Tasks" filds.
How can get the parent of the ItemSource ?
instead of finding the right parent and view model...I had a Forward pattern mainly for context menu...
All my view model have this command
private ICommand forwardCommand;
public ICommand ForwardCommand
{
get
{
if (forwardCommand == null)
forwardCommand = new RelayCommand<string>(
delegate(string param)
{
Messenger.Default.Send<CommandContext>(GetForwardCommandContext(param),
ViewModelBaseMessages.ContextCommand );
},
delegate(string param)
{
return CanForwardCommand(param);
});
return forwardCommand;
}
}
it is then forwarded to the view model that have registered to a forward_message
Messenger.Default.Register<CommandContext>(this, ViewModelBaseMessages.ContextCommand,
(CommandContext o) =>
{
ExecuteDistantCommand( o );
} );
you can find this code in my project on github
please, use a mvvm nuget like mvvmlight and do not invent the wheel...;-)

ComboBox Selected Item not updating

Problem
I am trying to bind a ComboBox's SelectedItem to a custom class but this does not update when the property is changed.INotifyPropertyChanged is implemented.
The DataContext
The DataContext is a custom class which contains many properties, but an extract of this is below. You can see it implements INotifyPropertyChanged and this called when the two properties are changed.
public class BctsChange : INotifyPropertyChanged
{
#region declarations
private byContact _Engineer;
public byContact Engineer
{
get { return _Engineer; }
set
{
_Engineer = value;
NotifyPropertyChanged("Engineer");
OnEngineerChanged();
}
}
private BctsSvc.DOSets _LeadingSet;
public BctsSvc.DOSets LeadingSet
{
get { return _LeadingSet; }
set { _LeadingSet = value; NotifyPropertyChanged("LeadingSet"); }
}
#endregion
#region INotify
public event PropertyChangedEventHandler PropertyChanged;
private void NotifyPropertyChanged(String propertyName = "")
{
if (PropertyChanged != null)
{
PropertyChanged(this, new PropertyChangedEventArgs(propertyName));
}
}
#endregion
public BctsChange()
{
Engineer = new byContact(Environment.UserName);
}
private void OnEngineerChanged()
{
if (Engineer != null)
{
BctsSvc.DOSets leadSet = GetLeadingSetFromDeptCode(Engineer.DeptCode);
if (leadSet == null) return;
LeadingSet = leadSet;
}
}
private static BctsSvc.DOSets GetLeadingSetFromDeptCode(string DeptCode)
{
BctsSvc.BctsServiceSoapClient svc = new BctsSvc.BctsServiceSoapClient();
BctsSvc.DOSets setX = svc.GetSetFromDeptCode(DeptCode);
return setX;
}
}
The Window XAML
I have several controls on the window, but to keep the code simple I believe the following extract will suffice.
<Window x:Class="MyNamespace.wdSubmit"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:my="clr-namespace:MyNamespace"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
x:Name="ucReqForm"
Title="wdSubmit" >
<StackPanel Orientation="Vertical" HorizontalAlignment="Stretch">
<GroupBox Header="Engineer Details" Name="grpOwnerDetails" >
<StackPanel Orientation="Vertical">
<Grid VerticalAlignment="Top">
<Grid.ColumnDefinitions>
<ColumnDefinition Width="*"/>
<ColumnDefinition Width="35"/>
</Grid.ColumnDefinitions>
<Label Content="{Binding Engineer.FullName, FallbackValue='Please select an engineer by clicking →', Mode=OneWay}" Margin="5,0" IsEnabled="True" FontStyle="Italic" />
<Button Content="{StaticResource icoSearch}" HorizontalContentAlignment="Center" VerticalContentAlignment="Center" Grid.Column="1" Height="23" Name="btnSelectEngineer" Margin="0,0,5,0" HorizontalAlignment="Stretch" ToolTip="Search for an engineer responsible" Click="btnSelectEngineer_Click" />
</Grid>
<ComboBox Height="23" x:Name="ddSet2" Margin="5,0" ItemsSource="{Binding LeadingSets, Mode=OneWay, RelativeSource={RelativeSource Mode=FindAncestor, AncestorType=Window}}" SelectedItem="{Binding LeadingSet, Mode=TwoWay,UpdateSourceTrigger=PropertyChanged,NotifyOnTargetUpdated=True}" >
<ComboBox.ItemTemplate>
<DataTemplate>
<TextBlock Text="{Binding SetName}" ToolTip="{Binding HelpInfo}"/>
</DataTemplate>
</ComboBox.ItemTemplate>
</ComboBox>
<my:LabelledDropdown Height="23" x:Name="ddSet" Margin="5,0" ItemsSource="{Binding LeadingSets, Mode=OneWay, RelativeSource={RelativeSource Mode=FindAncestor, AncestorType=Window}}" SelectedItem="{Binding LeadingSet, Mode=TwoWay,NotifyOnTargetUpdated=True,NotifyOnSourceUpdated=True}" Label="e.g. BodyHardware">
<my:LabelledDropdown.ItemTemplate>
<DataTemplate>
<TextBlock Text="{Binding SetName}" ToolTip="{Binding HelpInfo}"/>
</DataTemplate>
</my:LabelledDropdown.ItemTemplate>
</my:LabelledDropdown>
</StackPanel>
</GroupBox>
</StackPanel>
</Window>
The above extract contains:
A Label that contains a contact's name, and a button to search for a contact, bound to the FullName of the Engineer
A ComboBox that contains departments within the company, bound to an ObservableCollection<DOSets>, which contains a list of departments
Two ComboBoxes, one which is a custom one and the other which is temporary to ensure the bug is not within the control. These are Databound to LeadingSet
Window Code Behind
In the code behind I set the DataContext to CurrentChange. When the user wants to select a different Engineer then this will update the selected department for the engineer in CurrentChange.
When the user changes the engineer, the data binding for the engineer is updated, but the selected department (Leading Set) isn't.
//Usings here
namespace MyNamespace
{
public partial class wdSubmit : Window, INotifyPropertyChanged
{
private BctsSvc.BctsServiceSoapClient svc;
private BctsChange _CurrentChange;
public BctsChange CurrentChange
{
get { return _CurrentChange; }
set { _CurrentChange = value; OnPropertyChanged("CurrentChange"); }
}
private List<BctsSvc.DOSets> _LeadingSets;
public List<BctsSvc.DOSets> LeadingSets
{
get
{
return _LeadingSets;
}
}
public wdSubmit()
{
InitializeComponent();
svc = new BctsSvc.BctsServiceSoapClient();
_LeadingSets = svc.GetLeadSets().ToList();
OnPropertyChanged("LeadingSets");
this._CurrentChange = new BctsChange();
this.DataContext = CurrentChange;
CurrentChange.PropertyChanged += new PropertyChangedEventHandler(CurrentChange_PropertyChanged);
}
void CurrentChange_PropertyChanged(object sender, PropertyChangedEventArgs e)
{
OnPropertyChanged("CurrentChange");
OnPropertyChanged(e.PropertyName);
}
private void btnSelectEngineer_Click(object sender, RoutedEventArgs e)
{
byContact newContact = new frmSearchEngineer().ShowSearch();
if (newContact != null)
{
CurrentChange.Engineer = newContact;
PropertyChanged(CurrentChange, new PropertyChangedEventArgs("LeadingSet"));
PropertyChanged(CurrentChange.LeadingSet, new PropertyChangedEventArgs("LeadingSet"));
}
}
public event PropertyChangedEventHandler PropertyChanged;
protected virtual void OnPropertyChanged(string propertyName)
{
PropertyChangedEventHandler handler = PropertyChanged;
if (handler != null) handler(CurrentChange, new PropertyChangedEventArgs(propertyName));
}
}
}
I've realised the problem may be due to the LeadingSet, returned when the engineer is changed, being a different instance to that in the ObservableCollection.

View, ViewModel and DataContext

In order to solve a navigation issue in my application I have used an Event Aggregator which has solved the problem but has created an other one.
To navigate between different UserControls I used the Rachel's code you can find here which was working fine until I made some changes.
On the side of my screen I have a Main Menu (HomeViewModel()), by clicking on the items I switch between UserControls and in each of these UserControls there is a another menu bar where I can switch between other UserControls.
But this second menu (CateringMenuViewModel()) doesn't work anymore. The UserControl is displayed but nothing is happening when I am clicking in the menu bar.
At the first sight I thought it's because there is no DataContext.
So I added it in the code behind like this:
public CateringMenuView()
{
InitializeComponent();
this.DataContext = new CateringMenuViewModel(ApplicationService.Instance.EventAggregator);
}
But it still doesn't work.
I don't understand, the property Name is well bounded because the names are displayed in the menu but the command ChangePageCommand is not.
HomeViewModel
public class HomeViewModel : ObservableObject
{
#region Fields
private ICommand _changePageCommand;
private IPageViewModel _currentPageViewModel;
private List<IPageViewModel> _pageViewModels;
#endregion
public HomeViewModel()
{
// Add available pages
PageViewModels.Add(new HomeOrderViewModel());
PageViewModels.Add(new CateringMenuViewModel(ApplicationService.Instance.EventAggregator));
PageViewModels.Add(new HomeAdminViewModel());
// Set starting page
CurrentPageViewModel = PageViewModels[0];
}
#region Properties / Commands
}
CateringMenuViewModel
public class CateringMenuViewModel : ObservableObject, IPageViewModel
{
protected readonly IEventAggregator _eventAggregator;
public CateringMenuViewModel(IEventAggregator eventAggregator)
{
this._eventAggregator = eventAggregator;
PageViewModels.Add(new NewRegularOrderViewModel(ApplicationService.Instance.EventAggregator));
PageViewModels.Add(new NewDeliveryComOrderViewModel());
PageViewModels2.Add(new FillOrderViewModel());
// Set starting page
CurrentUserControl = PageViewModels[0];
this._eventAggregator.GetEvent<GoToFillOrder>().Subscribe(GoToFillOrder);
}
public string Name
{
get
{
return "Catering";
}
}
public string imageSource
{
get
{
return "catering.ico";
}
}
#region Fields
private List<IUserContentViewModel> _pageViewModels;
public List<IUserContentViewModel> PageViewModels
{
get
{
if (_pageViewModels == null)
_pageViewModels = new List<IUserContentViewModel>();
return _pageViewModels;
}
}
private IUserContentViewModel _currentUserControl;
public IUserContentViewModel CurrentUserControl
{
get { return _currentUserControl; }
set
{
if (value != _currentUserControl)
{
_currentUserControl = value;
OnPropertyChanged("CurrentUserControl");
}
}
}
#region Methods
private void ChangeViewModel(IUserContentViewModel viewModel)
{
if (!PageViewModels.Contains(viewModel))
PageViewModels.Add(viewModel);
CurrentUserControl = PageViewModels
.FirstOrDefault(vm => vm == viewModel);
var x = this.GetHashCode();
}
#endregion
private ICommand _changePageCommand;
#endregion
public ICommand ChangePageCommand
{
get
{
if (_changePageCommand == null)
{
_changePageCommand = new RelayCommand(
p => ChangeViewModel((IUserContentViewModel)p),
p => p is IUserContentViewModel);
}
return _changePageCommand;
}
}
private void GoToFillOrder(int i)
{
CurrentUserControl = PageViewModels2[0];
}
}
CateringMenuView
<UserControl.Resources>
<DataTemplate DataType="{x:Type cvm:NewDeliveryComOrderViewModel}">
<cv:NewDeliveryComOrderView/>
</DataTemplate>
<DataTemplate DataType="{x:Type cvm:NewRegularOrderViewModel}">
<cv:NewRegularOrderView/>
</DataTemplate>
<DataTemplate DataType="{x:Type cvm:FillOrderViewModel}">
<cv:FillOrderView/>
</DataTemplate>
</UserControl.Resources>
<Grid Margin="5">
<Grid>
<StackPanel>
<Menu>
<MenuItem Header="New Order">
<ItemsControl ItemsSource="{Binding PageViewModels}" Width="168" >
<ItemsControl.ItemTemplate>
<DataTemplate>
<TextBlock>
<Hyperlink Command="{Binding ChangePageCommand, Mode=OneWay}" CommandParameter="{Binding}" TextDecorations="{x:Null}">
<InlineUIContainer>
<TextBlock Text="{Binding Name}"/>
</InlineUIContainer>
</Hyperlink>
</TextBlock>
</DataTemplate>
</ItemsControl.ItemTemplate>
</ItemsControl>
</MenuItem>
</Menu>
</StackPanel>
</Grid>
<ContentControl Content="{Binding CurrentUserControl}"/>
</Grid>
Two problems here.
First off, you do not want to set the .DataContext of your UserControl manually because you want to use the CateringMenuViewModel from PageViewModels[1], not create a new instance of it.
So definitely remove the line of code
DataContext = new CateringMenuViewModel(ApplicationService.Instance.EventAggregator);
Second problem is why your event is not firing. I took a look at your code in your question's version history, and I do not see you broadcasting the event anywhere.
This line of code is correct to say "any time an event of type GoToFillOrder is broadcast, run the method GoToFillOrder"
_eventAggregator.GetEvent<GoToFillOrder>().Subscribe(GoToFillOrder);
however I don't see any code which actually broadcasts that event. You need a line of code like the following to broadcast the GoToFillOrder message to throughout your application :
_eventAggregator.GetEvent<GoToFillOrder>().Publish();
I finally found the solution.
In CateringMenuView(), I have replaced
<Hyperlink Command="{Binding ChangePageCommand, Mode=OneWay}"
CommandParameter="{Binding}"
TextDecorations="{x:Null}">
by
<Hyperlink Command="{Binding DataContext.ChangePageCommand, RelativeSource={RelativeSource AncestorType={x:Type UserControl}}}"
CommandParameter="{Binding}"
TextDecorations="{x:Null}">
Big thanks to Rachel!

WPF MVVM moving a UserControl from one ObservableCollection to another by event

I have a checklist view that has 2 ScrollViewers. One checklist is for incomplete items, the other is for complete items. They are populated by 2 separate observable collections and bound to by ItemsControls.
The UserControl has a button, when clicked will move that 'check' to the other collection.
Currently the way I have this setup is in the ViewModel that's the DataContext for the UserControl there is a public event that is subscribed to by the main window's VM by using:
((CheckItemVM) ((CheckListItem) cli).DataContext).CompleteChanged += OnCompleteChanged;
where cli is the checklist item.
then the OnCompleteChanged finds the appropriate View object by using:
foreach (object aCheck in Checks)
{
if (aCheck.GetType() != typeof (CheckListItem)) continue;
if (((CheckListItem) aCheck).DataContext == (CheckItemVM) sender)
{
cliToMove = (CheckListItem) aCheck;
break;
}
}
It's pretty obvious this breaks MVVM and I'm looking for a way around it (CheckListItem is the View, and CheckItemVM is it's DataContext ViewModel). Reasoning for the boxed type is I've got another UserControl that will have instances inside both, which are basically section labels, and I need to be able to sort my observable collections where there is an association between the checklistitem to a specific section by name.
This can be done in MVVM using commands, and bindings....
The idea that I propouse here is to create a command in the Windows view model, that manage the check command, and this command to receive the item view model in the params, then manage the the things in the command. I'm going to show you a simple example, using MvvmLight library:
The model:
public class ItemViewModel : ViewModelBase
{
#region Name
public const string NamePropertyName = "Name";
private string _name = null;
public string Name
{
get
{
return _name;
}
set
{
if (_name == value)
{
return;
}
RaisePropertyChanging(NamePropertyName);
_name = value;
RaisePropertyChanged(NamePropertyName);
}
}
#endregion
#region IsChecked
public const string IsCheckedPropertyName = "IsChecked";
private bool _myIsChecked = false;
public bool IsChecked
{
get
{
return _myIsChecked;
}
set
{
if (_myIsChecked == value)
{
return;
}
RaisePropertyChanging(IsCheckedPropertyName);
_myIsChecked = value;
RaisePropertyChanged(IsCheckedPropertyName);
}
}
#endregion
}
A simple model with two property, one for the name (an identifier) and another for the check status.
Now in the Main View Model, (or Windows view model like you want)....
First the Collections, one for the checked items, and another for the unchecked items:
#region UncheckedItems
private ObservableCollection<ItemViewModel> _UncheckedItems;
public ObservableCollection<ItemViewModel> UncheckedItems
{
get { return _UncheckedItems ?? (_UncheckedItems = GetAllUncheckedItems()); }
}
private ObservableCollection<ItemViewModel> GetAllUncheckedItems()
{
var toRet = new ObservableCollection<ItemViewModel>();
foreach (var i in Enumerable.Range(1,10))
{
toRet.Add(new ItemViewModel {Name = string.Format("Name-{0}", i), IsChecked = false});
}
return toRet;
}
#endregion
#region CheckedItems
private ObservableCollection<ItemViewModel> _CheckedItems;
public ObservableCollection<ItemViewModel> CheckedItems
{
get { return _CheckedItems ?? (_CheckedItems = GetAllCheckedItems()); }
}
private ObservableCollection<ItemViewModel> GetAllCheckedItems()
{
var toRet = new ObservableCollection<ItemViewModel>();
foreach (var i in Enumerable.Range(11, 20))
{
toRet.Add(new ItemViewModel { Name = string.Format("Name-{0}", i), IsChecked = true });
}
return toRet;
}
#endregion
And the command:
#region CheckItem
private RelayCommand<ItemViewModel> _CheckItemCommand;
public RelayCommand<ItemViewModel> CheckItemCommand
{
get { return _CheckItemCommand ?? (_CheckItemCommand = new RelayCommand<ItemViewModel>(ExecuteCheckItemCommand, CanExecuteCheckItemCommand)); }
}
private void ExecuteCheckItemCommand(ItemViewModel item)
{
//ComandCode
item.IsChecked = true;
UncheckedItems.Remove(item);
CheckedItems.Add(item);
}
private bool CanExecuteCheckItemCommand(ItemViewModel item)
{
return true;
}
#endregion
The magic here could be in the Data binding, in this case I used command parameter and the FindAncestor binding, check the Data Template:
<DataTemplate x:Key="UncheckedItemDataTemplate">
<Grid>
<StackPanel Orientation="Horizontal">
<TextBlock HorizontalAlignment="Left" TextWrapping="Wrap" Text="{Binding Name}" VerticalAlignment="Top"/>
<CheckBox HorizontalAlignment="Left" VerticalAlignment="Top" IsChecked="{Binding IsChecked}" IsEnabled="False"/>
<Button Content="Check" Width="75" Command="{Binding DataContext.CheckItemCommand, RelativeSource={RelativeSource FindAncestor, AncestorType={x:Type local:MainWindow}}}" CommandParameter="{Binding Mode=OneWay}"/>
</StackPanel>
</Grid>
</DataTemplate>
<DataTemplate x:Key="CheckedItemDataTemplate">
<Grid>
<StackPanel Orientation="Horizontal">
<TextBlock HorizontalAlignment="Left" TextWrapping="Wrap" Text="{Binding Name}" VerticalAlignment="Top"/>
<CheckBox HorizontalAlignment="Left" VerticalAlignment="Top" IsChecked="{Binding IsChecked}" IsEnabled="False"/>
</StackPanel>
</Grid>
</DataTemplate>
One data template for checked items, and another for unchecked items. Now the usage, this is simpler:
<ListBox Grid.Row="2" Margin="5" ItemsSource="{Binding UncheckedItems}" ItemTemplate="{DynamicResource UncheckedItemDataTemplate}"/>
<ListBox Grid.Row="2" Margin="5" Grid.Column="1" ItemsSource="{Binding CheckedItems}" ItemTemplate="{DynamicResource CheckedItemDataTemplate}"/>
This is a cleaner solution, hope is helps.

ICommand Button binding in Itemcontrol

Have xaml.cs file containing my ObservableCollection of my ViewModel. I have now implemented a command binding to a button click which invokes my function inside the viewmodel. The problem is that I do not get the item of my list in my button click function
xaml
<ItemsControl ItemsSource="{Binding ConditionList}" AlternationCount="{Binding ConditionList.Count}">
<ItemsControl.ItemTemplate>
<DataTemplate>
<WrapPanel>
<Button Content="{Binding}" Command="{Binding DataContext.DeleteCondition,
RelativeSource={RelativeSource AncestorType={x:Type ItemsControl}}}" CommandParameter="{Binding}" />
</WrapPanel>
</DataTemplate>
</ItemsControl.ItemTemplate>
</ItemsControl>
Please note my button is in ItemControl
VM
private void DoDeleteCondition(object parameter)
{
// if (parameter != null)
// ...
}
public ICommand DeleteCondition
{
get
{
if (_DeleteCondition == null)
_DeleteCondition = new RelayCommand(o => DoDeleteCondition(o));
return _DeleteCondition;
}
}
You need to create a RelayCommand<T> where T is the Item in the ConditionList. Then you will get your parameter in the execute method.
I have a feeling that your binding is set a little backwards.
In your ItemsControl do you want to have:
the items from your collection and one command that will execute when you click on the single item
or the list of possible commands you want to execute on a single item that you have elsewhere (meaning the collection is displayed on some parent element, so you can bind to the single item somehow)?
... or maybe you have a separate command defined for every item in your collection ...? (then, how are the elements in your collection implemented?)
Depending on your answer:
1:
<ItemsControl ItemsSource="{Binding Path=MyObservableCollection}" >
<ItemsControl.ItemTemplate>
<DataTemplate>
<WrapPanel>
<Button Content="{Binding}"
Command="{Binding Path=DataContext.DeleteCondition, RelativeSource={RelativeSource AncestorType=AncestorWithYourViewModelAsDataContext}}"
CommandParameter="{Binding}" />
</WrapPanel>
</DataTemplate>
</ItemsControl.ItemTemplate>
</ItemsControl>
2:
<ItemsControl ItemsSource="{Binding Path=ConditionList}" >
<ItemsControl.ItemTemplate>
<DataTemplate>
<WrapPanel>
<Button Content="{Binding}"
Command="{Binding Path=MyConditionalCommand}"
CommandParameter="{BindingToTheElementOfYourCllectionThatYouWantToActUpon}" />
</WrapPanel>
</DataTemplate>
</ItemsControl.ItemTemplate>
</ItemsControl>
example implementation in your ViewModel:
private List<ConditionalCommand> _ConditionList;
public List<ConditionalCommand> ConditionList
{
get { return _ConditionList; }
set
{
if (_ConditionList != value)
{
_ConditionList = value;
OnPropertyChanged("ConditionList");
}
}
}
...
class ConditionalCommand
{
public ICommand MyConditionalCommand { get; set; }
public string Name { get; set; }
public override string ToString()
{
return Name;
}
}
...
this.ConditionList = new List<ConditionalCommand>();
this.ConditionList.Add(new ConditionalCommand{ MyConditionalCommand = DeleteCondition , Name="Delete"});
this.ConditionList.Add(new ConditionalCommand{ MyConditionalCommand = DeleteSpecial, Name="Delete special" });
....
private void DoDeleteCondition(object parameter)
{
// if (parameter != null)
// ...
}
public ICommand DeleteCondition
{
get
{
if (_DeleteCondition == null)
_DeleteCondition = new RelayCommand(o => DoDeleteCondition(o));
return _DeleteCondition;
}
}
// DeleteSpecial implemented in similar manner...

Categories