Delete Button for each ListView Row - c#

I have a listview bound to an Observable Collection 'People', People contains persons with the property 'Name', which gives me a table of people.
I want to have a delete button for each row of this table, appearing the last column. Below is a working implementation. However the handler and the XAML are very intertwined, which isn't good. How could I pass which member of People I've deleted?
XAML:
<ListView ItemsSource="{Binding People}" Name="ListViewPeople">
<ListView.View>
<GridView>
<GridViewColumn>
<GridViewColumnHeader>Names</GridViewColumnHeader>
<GridViewColumn.CellTemplate>
<DataTemplate>
<TextBox Text="{Binding Names}"></TextBox>
</DataTemplate>
</GridViewColumn.CellTemplate>
</GridViewColumn>
<GridViewColumn>
<GridViewColumn.CellTemplate>
<DataTemplate>
<Button Content="Delete" Click="Button_Click_Delete"/>
</DataTemplate>
</GridViewColumn.CellTemplate>
</GridViewColumn>
</GridView>
</ListView.View>
</ListView>
I've managed to implement this with this handler, but as you can see it means the backing code has to know in detail about the view. This gets the index of the delete button pressed from the ListView and goes and removes the value from my observable collection.
c# code:
private void Button_Click_Delete(object sender, RoutedEventArgs e)
{
DependencyObject dep = (DependencyObject)e.OriginalSource;
while ((dep != null) && !(dep is ListViewItem))
{
dep = VisualTreeHelper.GetParent(dep);
}
if (dep == null)
return;
int index = ListViewPeople.ItemContainerGenerator.IndexFromContainer(dep);
People.RemoveAt(index);
}
Is there a way to perhaps pass which member of the observable collection I've deleted to my handler, so that the handler doesn't have to know about there being a listview etc?

Button btn = (Button)sender;
People.Remove((Person)btn.DataContext);

Related

Check/Uncheck checkbox with Space/Enter in gridview in listview - wpf

I have a GridView with CheckBox as a column and TextBlock as another column. Here is the XAML code:
<ListView.View>
<GridView>
<GridView.ColumnHeaderContainerStyle>
<Style>
<Setter Property="UIElement.Visibility"
Value="Collapsed"/>
</Style>
</GridView.ColumnHeaderContainerStyle>
<GridViewColumn>
<GridViewColumn.CellTemplate>
<DataTemplate>
<CheckBox Tag="{Binding}"
IsChecked="{Binding Path=IsFormChecked, Mode=TwoWay}"
IsEnabled="{Binding Path=IsUnRestricted}"
IsThreeState="False"
UIElement.KeyUp="CheckBox_KeyUp" />
</DataTemplate>
</GridViewColumn.CellTemplate>
</GridViewColumn>
<GridViewColumn Width="auto">
<GridViewColumn.CellTemplate>
<DataTemplate>
<TextBlock Width="600"
Tag="{Binding}"
IsEnabled="{Binding Path=IsUnRestricted}"
Text="{Binding Path=FormName}"
MouseUp="TextBlock_MouseUp">
<TextBlock.ToolTip>
<TextBlock Text="{Binding Path=FormName}"/>
</TextBlock.ToolTip>
</TextBlock>
</DataTemplate>
</GridViewColumn.CellTemplate>
</GridViewColumn>
</GridView>
</ListView.View>
When I access this GridView with the keyboard complete row is selected (CheckBox as well as TextBlock). Now at this point if i press space key, nothing happens. If I want to access the CheckBox with keyboard, I have to press tab key once more so that focus is set on checkbox and the I can check/uncheck it using space bar. What I want to do is when the focus is on the row I want to check/uncheck checkbox with only one key press of space bar.
If you have a binding on the currently selected item, you can handle the PreviewKeyUp event on your ListView:
<ListView PreviewKeyUp="OnGridKeyUp"
SelectedItem="{Binding MySelectedItem}"
SelectionMode="Single"
ItemsSource="{Binding ItemsList}">
...
</ListView>
and then handle this in code-behind:
private void OnGridKeyUp(object sender, KeyEventArgs e)
{
if(vm.mySelectedItem != null && e.Key == Key.Space)
{
vm.MySelectedItem.IsChecked = !vm.MySelectedItem.IsChecked;
e.Handled = true; //this is necessary because otherwise when the checkbox cell is selected, it will apply this keyup and also apply the default behavior for the checkbox
}
}
This obviously requires you to have a handle on your viewmodel from your code behind. This could be as easy as:
var vm = DataContext as MyViewModel;
this is not the most MVVM way, but hey...
I made a more MVVM friendly way to do this:
Step 1: Create this behavior
public class ToggleSelectOnSpace : Behavior<DataGrid>
{
public static readonly DependencyProperty toggleSelectCommand =
DependencyProperty.Register("ToggleSelectCommand",
typeof(ICommand),
typeof(ToggleSelectOnSpace));
public ICommand ToggleSelectCommand
{
get { return this.GetValue(toggleSelectCommand) as ICommand; }
set { this.SetValue(toggleSelectCommand, value); }
}
protected override void OnAttached()
{
base.OnAttached();
this.AssociatedObject.PreviewKeyUp += PreviewKeyUp;
}
protected override void OnDetaching()
{
this.AssociatedObject.PreviewKeyUp -= PreviewKeyUp;
base.OnDetaching();
}
private void PreviewKeyUp(object sender, KeyEventArgs e)
{
if (e.Key == Key.Space)
{
if (ToggleSelectCommand != null)
{
ToggleSelectCommand.Execute(this.AssociatedObject.SelectedItems);
}
}
}
}
Step 2: Implement a command like this in your View Model (method logic for the command)
private void ToggleSelectParticipant(object selectedObjects)
{
var items = (System.Collections.IList)selectedObjects;
var collection = items.Cast<MyItemType>().ToList();
bool selection = !collection.All(x => x.IsSelected);
foreach (var item in collection)
item.IsSelected = selection;
}
Step 3: Bind the behavior to the grid, and bind the command to the behavior
<DataGrid>
<i:Interaction.Behaviors>
<shared:ToggleSelectOnSpace ToggleSelectCommand="{Binding Data.ToggleSelectParticipantCommand, Source={StaticResource BindingProxy}}" />
</i:Interaction.Behaviors>
...
</DataGrid>
(Info on the BindingProxy source)

How do I only show data by filtering the properties in a WPF ListView?

I am working with a WPF control that I created and I am trying to only show certain rows of my list by values of a property. An example is the following, I have a User class that holds a property of Active. How do I tell the .xaml that the list should only show the people that are Active?
Right now I am basically using linq to generate a new list and hand it to the listview based on what I want. However, I would rather just hand the ListView my entire list and let it do the work for me.
Here is my ListView code.
<ListView ItemsSource="{Binding}" DataContext="{Binding }" >
<ListView.View>
<GridView>
<GridViewColumn>
<GridViewColumn.CellTemplate>
<DataTemplate>
<StackPanel>
<TextBlock Text="{Binding Index}"/>
<TextBlock Text=". " />
<TextBlock Text="{Binding FirstName}" />
<TextBlock Text="{Binding LastName}" />
</StackPanel>
</DataTemplate>
</GridViewColumn.CellTemplate>
</GridViewColumn>
</GridView>
</ListView.View>
</ListView>
You'll need some code behind to add a filter:
See: WPF filtering
ICollectionView view = CollectionViewSource.GetDefaultView(lstMovies.ItemsSource);
view.Filter = null;
view.Filter = new Predicate<object>(FilterMovieItem);
private bool FilterMovieItem(object obj)
{
MovieItem item = obj as MovieItem;
if (item == null) return false;
string textFilter = txtFilter.Text;
if (textFilter.Trim().Length == 0) return true; // the filter is empty - pass all items
// apply the filter
if (item.MovieName.ToLower().Contains(textFilter.ToLower())) return true;
return false;
}

GridViewColumnHeader apply sorted style

I have the follwoing listview in my xaml:
<ListView Name="listView1">
<ListView.View>
<GridView>
<GridViewColumn Width="Auto" Header="Name"
DisplayMemberBinding="{Binding nombre}" />
<GridViewColumn Width="200" Header="LastName"
DisplayMemberBinding="{Binding razonSocial}" />
// etc....
I have an ObservableCollection binded to a listview. I created the binding behind code. So any changes that I make to that collection will be reflected on the listview. Also if I want to sort the listview I will just sort the ObservableCollection.
I sort the listview when the user clicks on a gridviewcolumnheader as:
listView1.AddHandler(GridViewColumnHeader.ClickEvent, new RoutedEventHandler((a, b) =>
{
// check to see what column header was clicked
string bindingProperty =
((Binding)(((GridViewColumnHeader)(b.OriginalSource)).Column.DisplayMemberBinding)).Path.Path;
// using dyniamic linq libraries or the example located at
// http://stackoverflow.com/a/233505/637142
// I will be able to sort my collection of objects by nowing the property name
}));
Anyways I will like to apply a diferent style to the GridViewColumnHeader that was just clicked. I believe there should be already an existing template.
I am looking for something like:
GridViewColumnHeader a = "gridviewColumnHeader that was clicked"
a.Style = "orderByAscGridViewColumnTemplate"
Code Behind:
listView1.AddHandler(GridViewColumnHeader.ClickEvent, new RoutedEventHandler((a, e) =>
{
GridViewColumnHeader headerClicked =
e.OriginalSource as GridViewColumnHeader;
headerClicked.Column.HeaderTemplate =
Resources["HeaderTemplateArrowUp"] as DataTemplate;
xaml:
<UserControl.Resources>
<DataTemplate x:Key="HeaderTemplateArrowUp">
<DockPanel>
<TextBlock HorizontalAlignment="Center" Text="{Binding}"/>
<Path x:Name="arrow"
StrokeThickness = "1"
Fill = "gray"
Data = "M 5,10 L 15,10 L 10,5 L 5,10"/>
</DockPanel>
</DataTemplate>
</UserControl.Resources>

Locate position of control that was clicked in GridView (WPF)

I'm using a ListView that contains a GridView, there's a gridviewcolumn which only contains buttons. In the buttonclick event, how do I get the position (I just need the row in this case) of the button which fired the event?
<ListView.View>
<GridView>
<GridViewColumn Width="Auto">
<GridViewColumn.CellTemplate>
<DataTemplate>
<Button Width="Auto" Height="Auto" Background="#00000000" BorderBrush="#00000000" BorderThickness="0" Click="Finalizado_Click">
<Button.Content>
<Image Source="Content/okay.png" Width="8" Height="8"/>
</Button.Content>
</Button>
</DataTemplate>
</GridViewColumn.CellTemplate>
</GridViewColumn>
I know the columns are automatically autogeneratedfields, if you change them to buttonfields you might be able to get a little more information. Sorry I can't help more than that.
public ListViewItem GetRow(DependencyObject item)
{
DependencyObject dObj = VisualTreeHelper.GetParent(item);
if (dObj == null)
return null;
if (dObj is ListViewItem)
return dObj as ListViewItem;
return GetRow(dObj);
}

How to capture the focus of a TextBox inside a ListView grid

This code generates a Listview with a grid, of multiple name and emails inside TextBox control. I would like to know how can I capture the focus event on one of the TextBox of a row to have the entire ListView row to be selected.
<ListView Name="lstRecipients" ItemsSource="{Binding Path=Recipients}">
<ListView.Resources>
<DataTemplate x:Key="tbNameTemplate">
<TextBox Name="tbName" Text="{Binding Path=Username, ValidatesOnDataErrors=True}"/>
</DataTemplate>
<DataTemplate x:Key="tbEmailTemplate">
<TextBox Name="tbEmail" Text="{Binding Path=Email, ValidatesOnDataErrors=True}"/>
</DataTemplate>
</ListView.Resources>
<ListView.View>
<GridView x:Name="gvRecipients">
<GridViewColumn Header="Name" CellTemplate="{StaticResource tbNameTemplate}"/>
<GridViewColumn Header="Email" CellTemplate="{StaticResource tbEmailTemplate}"/>
</GridView>
</ListView.View>
</ListView>
You can add a handler to the GotFocus event on the TextBox that sets the selected item on the ListView. You can use ItemsControl.ContainerFromElement to get the ListViewItem and ItemContainerGenerator.ItemFromContainer to get the bound data object. In XAML:
<TextBox GotFocus="tbName_GotFocus" Name="tbName" Text="{Binding Path=Username, ValidatesOnDataErrors=True}"/>
In code-behind:
private void tbName_GotFocus(object sender, RoutedEventArgs e)
{
var container = lstRecipients.ContainerFromElement((DependencyObject)sender);
if (container != null)
{
lstRecipients.SelectedItem = lstRecipients.ItemContainerGenerator
.ItemFromContainer(container);
}
}
You could also set the handler on the ListView, since GotFocus is a routed event. You could use this to create a handler that could be shared between ListViews. In XAML:
<ListView GotFocus="lstRecipients_GotFocus" Name="lstRecipients" ItemsSource="{Binding Path=Recipients}">
In code-behind:
private void lstRecipients_GotFocus(object sender, RoutedEventArgs e)
{
var selector = sender as Selector;
if (selector != null)
{
var container = selector.ContainerFromElement
((DependencyObject)e.OriginalSource);
if (container != null)
{
selector.SelectedItem = selector.ItemContainerGenerator
.ItemFromContainer(container);
}
}
}
(If you don't want the TextBox to be editable at all, you could also just set Focusable="False" or use a TextBlock instead of a TextBox and focus would go to the ListView and select the row when the cell was clicked.)

Categories