Delete item from ItemControl - c#

My class is has a ObservableCollection of my viewmodel class and I set the itemsource of the Itemcontrol in xaml as below
<ItemsControl ItemsSource="{Binding ConditionItems}">
<ItemsControl.ItemTemplate>
<DataTemplate>
<Expander Background="#FFD0D7EB">
<StackPanel>
<Button Content="Delete" HorizontalAlignment="Right" Width="180" Margin="0,0,12,10" Command="{Binding DeleteItem}" CommandParameter="{Binding}">
</Button> </StackPanel>
</Expander>
</DataTemplate>
</ItemsControl.ItemTemplate>
</ItemsControl>
</StackPanel>
For some reason my DeleteItem is never called.
private RelayCommand _DeleteRule;
private void DoDeleteRule(object item)
{
if (item != null)
{
MessageBox.Show("in del");
}
}
public ICommand DeleteItem
{
get
{
if (_DeleteRule == null)
_DeleteRule = new RelayCommand(o => DoDeleteRule(o));
return _DeleteRule;
}
}
Am I doing anything wrong in xaml?

The ItemsControl is bound using {Binding ConditionItems}, so it expects the DeleteItem command to be inside the subitems of that list. I guess this is not the case, the DeleteItem exists on the ViewModel.
You could bind to the DataContext of the Window for example, where you can find the DeleteItem command. Or create a proxy element.

I found it. My xaml should be
<Button Content="Delete" Command="{Binding DataContext.DeleteItem,
RelativeSource={RelativeSource FindAncestor,
AncestorType={x:Type ItemsControl}}}" CommandParameter="{Binding}">
</Button>

Related

WPF CheckBox Command inside a Listview DataTemplate

My CheckBox Command does not work. I want to pass the SelectedItem of the ListView to the Command when the SelectedItem is checked or unchecked, but the Command does not execute at all. I also suspect my CommandParameter is not configured correctly?
I am pretty sure the problem is because the CheckBox is within a ListView DataTemplate.
Can someone show me how to set this up? I tried to follow examples I found, but nothing seems to work. thanks.
XAML
<ListView x:Name="lvReferralSource" ItemsSource="{Binding ReferralSourceTypeObsCollection}" Style="{StaticResource TypeListViewStyle}">
<ListView.ItemTemplate>
<DataTemplate>
<StackPanel Orientation="Vertical">
<CheckBox x:Name="ckbReferralIsChecked" Content="{Binding Value}" IsChecked="{Binding Active}" Style="{StaticResource CheckBoxStyleBase2}"
Command="{Binding CheckBoxIsChecked}" CommandParameter="{Binding RelativeSource={RelativeSource FindAncestor, AncestorType={x:Type ListView}}, Path=SelectedItem}">
</CheckBox>
</StackPanel>
</DataTemplate>
</ListView.ItemTemplate>
</ListView>
CODE
private ICommand _CheckBoxIsChecked;
public ICommand CheckBoxIsChecked
{
get
{
if (_CheckBoxIsChecked == null)
{
_CheckBoxIsChecked = new RelayCommand<object>(ExecuteCheckBoxIsChecked, CanExecuteCheckBoxIsChecked);
}
return _CheckBoxIsChecked;
}
}
public bool CanExecuteCheckBoxIsChecked(object parameter)
{
return true;
}
public void ExecuteCheckBoxIsChecked(object parameter)
{
Mouse.OverrideCursor = Cursors.Wait;
if (parameter != null)
{
//Do Stuff...
}
Mouse.OverrideCursor = Cursors.Hand;
}
Your command should get executed provided that the CheckBoxIsChecked property belongs to the data object where the Value and Active properties are defined.
If it belongs to the view model, you could bind to it using a RelativeSource:
<CheckBox x:Name="ckbReferralIsChecked" Content="{Binding Value}" IsChecked="{Binding Active}"
Style="{StaticResource CheckBoxStyleBase2}"
Command="{Binding DataContext.CheckBoxIsChecked, RelativeSource={RelativeSource AncestorType=ListView}}"
CommandParameter="{Binding}">

How update ListBox when ItemsSource changed

I have buttons "ADD" and "DEL", but "DEL" does not work. What is wrong?
count in my ObservableCollection<User> was changed but ListBox does not
sample project: https://github.com/Veselov-Dmitry/MyQuestion
view:
<StackPanel>
<Button Content="ADD"
Command="{Binding AddUsers_OASUCommand}"
CommandParameter="">
</Button>
<ListBox ItemsSource="{Binding Users_OASU}">
<ListBox.ItemTemplate>
<DataTemplate>
<WrapPanel>
<TextBlock Text="{Binding Name}" />
<TextBlock Text="{Binding Login}" />
<Button Content="DEL"
Command="{Binding DelUsers_OASUCommand}"
CommandParameter="{Binding Path=Content,
RelativeSource={RelativeSource Mode=FindAncestor ,
AncestorType={x:Type ListBoxItem}}}">
<Button.DataContext>
<local:ViewModel />
</Button.DataContext>
</Button>
</WrapPanel>
</DataTemplate>
</ListBox.ItemTemplate>
</ListBox>
</StackPanel>
I set datacontext in constructor MainView
viewvmodel:
class ViewModel
{
public event PropertyChangedEventHandler PropertyChanged;
public ObservableCollection<User> Users_OASU{get; set;}
public ICommand AddUsers_OASUCommand{get; set;}
public ICommand DelUsers_OASUCommand{get; set;}
public ViewModel()
{
Users_OASU = new ObservableCollection<User>(GetUsers());
AddUsers_OASUCommand = new Command<object>(arg => AddUsers_OASUMethod());
DelUsers_OASUCommand = new Command<object>(arg => DelUsers_OASUMethod(arg));
}
private void DelUsers_OASUMethod(object arg)
{
User find = Users_OASU.Where(x => x.Login == (arg as User).Login).FirstOrDefault();
Users_OASU.Remove(find);
}
private void AddUsers_OASUMethod()
{
Users_OASU.Add(new User("52221", "John X."));
}
private List<User> GetUsers()
{
List<User> list = new List<User>();
list.Add(new User("52222", "John W."));
list.Add(new User("52223", "John Z."));
list.Add(new User("52224", "John A."));
list.Add(new User("52225", "John M."));
return list;
}
}
"count in my ObservableCollection was changed but ListBox does not" - you have multiple instances of ViewModel, count was changed, but not in the collection which is displayed
you need to setup DataTemplate correctly to avoid that
first, each Button will get User object for DataContext (it will be provided by ListBox from ItemsSource). You mustn't declare new <Button.DataContext>
second, DelUsers_OASUCommand is declared in a ViewModel class, it is accessible on ListBox level, from DataContext. Change binding path accordingly.
<DataTemplate>
<WrapPanel>
<TextBlock Text="{Binding Name}" />
<TextBlock Text="{Binding Login}" />
<Button Command="{Binding DataContext.DelUsers_OASUCommand,
RelativeSource={RelativeSource AncestorType=ListBox}}"
CommandParameter="{Binding Path=Content,
RelativeSource={RelativeSource Mode=FindAncestor, AncestorType={x:Type ListBoxItem}}}"
Content="DEL" />
</WrapPanel>
</DataTemplate>
additionally I would change DelUsers_OASUMethod to accept User as argument
private void DelUsers_OASUMethod(object arg)
{
Users_OASU.Remove(arg as User);
}
and pass CommandParameter like this:
CommandParameter="{Binding Path=.}"
or the same, but shorter:
CommandParameter="{Binding}"

Initial Focus on selected ListViewItem

I have a simple dialog with MVVM, just a ListView and two Buttons (Ok, Cancel) and a ViewModel responsible for binding the ItemsSource and SelectedItem of the ListView.
View:
...
<StackPanel>
<ScrollViewer VerticalScrollBarVisibility="Auto">
<ListView ItemsSource="{Binding Projects}" SelectedItem="{Binding Project}" SelectionMode="Single">
<ListView.ItemTemplate>
<DataTemplate>
<TextBlock Text="{Binding Name}"/>
</DataTemplate>
</ListView.ItemTemplate>
</ListView>
</ScrollViewer>
<StackPanel Orientation="Horizontal" FlowDirection="RightToLeft">
<Button Content="Cancel" IsCancel="True"/>
<Button Content="Ok" Command="{Binding OkCommand}" CommandParameter="{Binding ElementName=Dlg}"/>
</StackPanel>
</StackPanel>
...
Viewmodel:
public class SelectProjectViewModel : ViewModelBase, IModalDialogViewModel
{
private readonly ProjectList _projectList;
public ProjectList Projects => _projectList;
public Project Project
{
get { return GetValue(() => Project); }
set { SetValue(() => Project, value);}
}
private RelayCommand _okCommand;
public RelayCommand OkCommand => _okCommand ?? (_okCommand = new RelayCommand(OkCommandCall));
public SelectProjectViewModel(ProjectList projectList, Project currentProject)
{
_projectList = projectList;
this.Project = currentProject;
}
private void OkCommandCall(object window)
{
DialogResult = true;
WindowCloseBehaviour.SetClose(window as DependencyObject, true);
}
public bool? DialogResult
{
get { return GetValue(() => DialogResult); }
private set { SetValue(() => DialogResult, value); }
}
}
I tried to achieve focusing the first Element like described here, with the accepted solution and the equivalent behaviour class. But this always sets the focus on the Cancel-Button, while I want my ListView to be focused when opening the dialog, so that the selected element is really highlighted, not just in the "inactive highlight" color. And no, I don't want to workaround with changing the colors.
Using simple FocusedElement on a parent with SelectedIndex on ListView itself works for me:
<StackPanel FocusManager.FocusedElement="{Binding ElementName=myListView}">
<ScrollViewer VerticalScrollBarVisibility="Auto">
<ListView Name="myListView" ItemsSource="{Binding Projects}" SelectedItem="{Binding Project}" SelectionMode="Single"
SelectedIndex="0">
<ListView.ItemTemplate>
<DataTemplate>
<TextBlock Text="{Binding Name}"/>
</DataTemplate>
</ListView.ItemTemplate>
</ListView>
</ScrollViewer>
...
</StackPanel>
</StackPanel>

Binding property in ContextMenu from Parent's Parent's DataContext

I wanted to bind a property in a MenuItem within an ItemsControl container. I have a hierarchical model ; Item class has a list of SubItem. The ViewModel itself has a list of Item (so there's two ItemsControl, one being in the ItemTemplate of the other).
I found several other questions on SO asking about that (this one for instance) and I learned that the Visual Tree of a ContextMenu is separated from the rest.
I managed to do it and it works (but it feels kind of hacky) by "transferring" the model's data via the Tag property.
Here's the two model classes:
public class SubItem
{
public int Current { get; set; }
public Subitem(int current)
{
Current = current;
}
}
public class Item
{
public ObservableCollection<SubItem> SubItems { get; set; }
public string Parent { get; set; }
public Item(string Parent)
{
Parent = Parent;
SubItems = new ObservableCollection<SubItem>();
}
}
Here's the view model:
public class ViewModel
{
public ObservableCollection<Item> Items { get; set; }
public ViewModel()
{
Items = new ObservableCollection<Item>();
FillData();
}
private void FillData()
{
//...
}
}
And here's the ItemsControl at the root of the page (the page's DataContext is an instance of the ViewModel class):
<ItemsControl ItemsSource="{Binding Items}">
<ItemsControl.ItemTemplate>
<DataTemplate>
<ItemsControl ItemsSource="{Binding SubItems}">
<ItemsControl.ItemTemplate>
<DataTemplate>
<TextBlock Text="{Binding Current}"
Tag="{Binding DataContext.Parent, RelativeSource={RelativeSource AncestorType={x:Type ItemsControl}, Mode=FindAncestor}}">
<TextBlock.ContextMenu>
<ContextMenu>
<MenuItem Header="{Binding Path=PlacementTarget.Tag, RelativeSource={RelativeSource AncestorType={x:Type ContextMenu}, Mode=FindAncestor}}"/>
</ContextMenu>
</TextBlock.ContextMenu>
</TextBlock>
</DataTemplate>
</ItemsControl.ItemTemplate>
</ItemsControl>
</DataTemplate>
</ItemsControl.ItemTemplate>
</ItemsControl>
My question is: is this the right way to do it? I tried many other ways to avoid binding the property to the Tag but couldn't make it work.
The ugly part is specifically:
Tag="{Binding DataContext.Parent, RelativeSource={RelativeSource AncestorType={x:Type ItemsControl}, Mode=FindAncestor}}"
Followed by:
Header="{Binding Path=PlacementTarget.Tag, RelativeSource={RelativeSource AncestorType={x:Type ContextMenu}, Mode=FindAncestor}}"
I think it's kind of ugly and I'm sure there's a better way to do that. The solution must work with .NET 4.0.
You need to add a tag to the menu's container and bind to it using placement target.
View this example:
<StackPanel x:Key="ConfigurationListItem" x:Shared="False" Tag="{Binding ElementName=UserControl}">
<StackPanel Orientation="Horizontal">
<Button>
<Button.InputBindings>
<MouseBinding Gesture="LeftDoubleClick" Command="{Binding ElementName=UserControl, Path=LaunchCommand}" CommandParameter="{Binding}" />
<MouseBinding Gesture="LeftClick" Command="{Binding ElementName=UserControl, Path=SelectCommand}" CommandParameter="{Binding}" />
</Button.InputBindings>
</StackPanel>
<StackPanel.ContextMenu>
<ContextMenu DataContext="{Binding Path=PlacementTarget.Tag, RelativeSource={RelativeSource Self}}" Tag="{Binding}">
<MenuItem Header="Sync Environment Dependencies"
Command="{Binding Parent.PlacementTarget.Tag.SyncEnvironmentCommand, RelativeSource={RelativeSource Self}}"
CommandParameter="{Binding RelativeSource={RelativeSource FindAncestor, AncestorType={x:Type ContextMenu}}, Path=PlacementTarget.DataContext}" />
</ContextMenu>
</StackPanel.ContextMenu>
</StackPanel>

How could I propagate item's Validation.Error up to ItemsControl?

WPF has turned on Validation in TextBox by default. How could I propagate a TextBox's Validation.Error up to its ItemsControl if its ItemTemplate is composed of the TextBox ? I want to bind a button's IsEnabled into the Validation.Error on an item in ItemsControl.
<ItemsControl ItemsSource="{Binding}"
x:Name="my_itemsControl">
<ItemsControl.ItemTemplate>
<DataTemplate>
<TextBox Text="{Binding FirstName}" />
</DataTemplate>
</ItemsControl.ItemTemplate>
</ItemsControl>
<Button Content="Save">
<Button.IsEnabled >
<Binding ElementName="my_itemsControl"
Path="(Validation.HasError)" />
</Button.IsEnabled>
</Button>
OK, I finally got a nice solution which uses MVVM pattern.
<UserControl x:Class="MyUserControl"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
x:Name="this_control">
<Button Content="Save"
Command="{Binding Path=SaveCommand}"
CommandParameter="{Binding ElementName=this_control}" />
</UserControl>
And the ICommand Property which I bound to on the view-model is like
public ICommand SaveCommand
{
get
{
if (_saveCommand == null)
{
_saveCommand = new RelayCommand(
param => save(),
param => IsValid((DependencyObject)param) // actually the type of param is MyUserControl
);
}
return _saveCommand;
}
}
where the IsValid is based on the fantastic trick

Categories

Resources