I have a nested Listbox (collection of objects in the main object list) that needs to delete the underlying items.
When the item is removed, I could reset the ItemsSource of the main list, but the main list will have tons of items and each time a item is removed from its underlying collection the main scrollbar would be also reset, making users willing to kill me in a very painful way.
My question: How can i find the container of the item which button has been clicked and how can i find the item itself so i can kill the #&!$*&#$# (cursing onomatopoeia)?
Here's the XAML exemple of my lists:
<ListView Name="mainList">
<ListView.View>
<GridView>
<GridViewColumn Header="Column 1" />
<GridViewColumn Header="Column 2" />
<GridViewColumn Header="Column 3" />
<GridViewColumn Header="Collection column">
<GridViewColumn.CellTemplate>
<DataTemplate>
<ListBox ItemsSource="{Binding BindingCollectionProperty}">
<ListBox.ItemTemplate>
<DataTemplate>
<Grid>
<TextBlock Grid.Column="0" Text="{Binding Item.Property}" />
<TextBlock Grid.Column="1" Text="{Binding Item.AnotherProperty}" />
<Button Content="remove" Grid.Column="2" Click="DeleteClickEvent" />
</Grid>
</DataTemplate>
</ListBox.ItemTemplate>
</ListBox>
</DataTemplate>
</GridViewColumn.CellTemplate>
</GridViewColumn>
</GridView>
</ListView.View>
</ListView>
How's my DeleteClickEvent(object sender, RoutedEventArgs e) should be like?
You could have a command and pass it your item as parameter instead of a click handler:
<Button ... Command="{Binding RelativeSource={RelativeSource FindAncestor, AncestorType={x:Type YOURCUSTOMCONTROL}}, Path=DataContext.YOURCOMMAND}"
CommandParameter="{Binding RelativeSource={RelativeSource FindAncestor, AncestorType={x:Type ListBoxItem}}}" />
If you want to keep your event handler you could use VisualTreeHelper.GetParent twice on the sender.
ListBoxItem item = VisualTreeHelper.GetParent(VisualTreeHelper.GetParent((DependencyObject)sender)) As ListBoxItem;
BindingCollectionProperty.Remove(item);
Related
I have an establishment collection that I display in a combobox. One of the properties is "IsSelected", which allows me to select multiple items in a combobox.
<ComboBox Name="CmbEtabTout"
ItemsSource="{Binding EtablissementsUtilisateur}"
Grid.IsSharedSizeScope="True"
Grid.Column="2"
Grid.ColumnSpan="3"
Grid.Row="2"
Height="25"
Width="250">
<ComboBox.ItemTemplate>
<DataTemplate>
<Grid>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="30" />
<ColumnDefinition SharedSizeGroup="AgentA" Width="auto" />
<ColumnDefinition Width="5" />
<ColumnDefinition SharedSizeGroup="AgentB" Width="auto" />
</Grid.ColumnDefinitions>
<CheckBox IsChecked="{Binding IsSelected}" Grid.Column="0"/>
<TextBlock Text="{Binding IdEtablissement}" Grid.Column="1"/>
<TextBlock Text="{Binding Nom}" Grid.Column="3"/>
</Grid>
</DataTemplate>
</ComboBox.ItemTemplate>
</ComboBox>
<ListView x:Name="LVAgent"
ScrollViewer.HorizontalScrollBarVisibility="Disabled"
ItemsSource="{Binding Agents}" Grid.ColumnSpan="2" Margin="150,0,42,0" Grid.Column="2" Grid.Row="4" Grid.RowSpan="5" >
<ListView.View>
<GridView>
<GridViewColumn>
<GridViewColumn.CellTemplate>
<DataTemplate>
<CheckBox IsChecked="{Binding IsSelected}"
Command="{Binding DataContext.SelectAgentCommand, RelativeSource={RelativeSource Mode=FindAncestor, AncestorType=Window}}"
CommandParameter="{Binding}"/>
</DataTemplate>
</GridViewColumn.CellTemplate>
<CheckBox IsChecked="{Binding SelectAllAgents}"
IsEnabled="True"/>
</GridViewColumn>
<GridViewColumn Header="Matricule"
Width="110"
DisplayMemberBinding="{Binding Matricule}"/>
<GridViewColumn Header="Nom"
Width="120"
DisplayMemberBinding="{Binding Nom}"/>
<GridViewColumn Header="Prénom"
Width="120"
DisplayMemberBinding="{Binding Prenom}"/>
</GridView>
</ListView.View>
</ListView>
My combobox collection :
private ObservableCollection<Etablissement> _EtablissementsUtilisateur;
public ObservableCollection<Etablissement> EtablissementsUtilisateur
{
get
{
return _EtablissementsUtilisateur;
}
set
{
if (value != _EtablissementsUtilisateur)
{
_EtablissementsUtilisateur = value;
RaisePropertyChanged(nameof(EtablissementsUtilisateur));
}
}
}
I'm trying to find out how to bind these comboboxes to refresh a list: if I choose three establishments, the list displays the agents of these three establishments. With a command maybe?
<CheckBox IsChecked="{Binding IsSelected}" Grid.Column="0"/>
Look like this :
Since I already bind my checkbox with "IsSelected" (used for SelectAll), I don't know how to bind to refresh the list of Agent behind, when I check, without having to press a button like "validate".
Edit : My problem now is if I want to do something like this, for exemple :
<CheckBox IsChecked="{Binding IsSelected}" Command="{Binding }" Grid.Column="0" />
I can only bind to the Etablissement Class and not to the ViewModel. (because of the itemSource of combobox i think)
the goal is, "when any checkbox is checked or unchecked, if I choose three establishments, the list displays the agents of these three establishments".
"when any checkbox is checked or unchecked" => property changed event handler
"if I choose three establishments" => if statement
"the list displays the agents of these three establishments" => method call
assuming Etablissement : INotifyPropertyChanged, we could add the event handler to each Etablissement.PropertyChanged. the other option is to add the handler to CheckBox.Checked and CheckBox.Unchecked.
bonus: event handlers can be async, so if "method call" is async, you're right at home to await it, meaning your UI remains responsive and does not lock up.
i would add the handlers inside a Loaded event for your UserControl or perhaps the ComboBox
Loaded += delegate
{
PropertyChangedEventHandler propertyChanged = delegate
{
//if number of checked items != 3
//return;
//update agents
};
foreach (var etablissement in EtablissementsUtilisateur)
etablissement.PropertyChanged += propertyChanged;
}
I've a little Problem. On my Window, i've a GridView and on every Row of the GridView is a Button (The Button is Part of the GridViewColumn.CellTemplate). Now i want, that when i press the Button, a Popup should be open under the pressed Button.
But how i can bind the Popup on the Button in a Grid because the Button is dynamic (for every Row one Button).
<Popup Name="popup_Zuordnungen">
<controls:Anlagenzuordnung Grid.Row="3" x:Name="VertragsAnlagenPopup" Margin="0,20,0,0"> </controls:Anlagenzuordnung>
</Popup>
<ListView Grid.Row="1" Name="lv_Leistungserbringer" DataContextChanged="lv_Leistungserbringer_DataContextChanged" SelectionChanged="lv_Leistungserbringer_SelectionChanged" ItemsSource="{Binding RelativeSource={RelativeSource AncestorType={x:Type local:VertragsLeistungserbringerZuordnung}}, Path=DataSource}" Height="150" VerticalAlignment="Bottom">
<ListView.View>
<GridView>
<GridViewColumn Header="ID">
<GridViewColumn.CellTemplate>
<DataTemplate>
<TextBlock Text="{Binding Path=ID}" Width="40" />
</DataTemplate>
</GridViewColumn.CellTemplate>
</GridViewColumn>
<GridViewColumn Header="Bezeichnung">
<GridViewColumn.CellTemplate>
<DataTemplate>
<TextBlock Text="{Binding Path=Beschreibung}" Width="500" />
</DataTemplate>
</GridViewColumn.CellTemplate>
</GridViewColumn>
<GridViewColumn>
<GridViewColumn.CellTemplate>
<DataTemplate>
<Button Content="Zuordnungen anzeigen" Name="cmd_Zuordnungen"/>
</DataTemplate>
</GridViewColumn.CellTemplate>
</GridViewColumn>
</GridView>
</ListView.View>
</ListView>
The Button is in the last Column.
Cab anywhere help me?
This is a Window with code-behind?
In that case, you can simply add a click event to your button (in the DataTemplate), and handle the popup placement in the event handler:
XAML:
...
<DataTemplate>
<Button Content="Zuordnungen anzeigen" Name="cmd_Zuordnungen"
Click="cmd_Zuordnungen_Click" />
</DataTemplate>
Code:
private void cmd_Zuordnungen_Click(object sender, RoutedEventArgs e)
{
popup_Zuordnungen.IsOpen = false;
popup_Zuordnungen.PlacementTarget = (Button)sender;
popup_Zuordnungen.IsOpen = true;
}
How do I access a control in XAML that is nested in a GridViewColumn.CellTemplate? By accessing the combo box I want to set its ItemsSource in the code behind.
Code:
<GridViewColumn Width="80">
<GridViewColumnHeader Content="UseCLUT"/>
<GridViewColumn.CellTemplate>
<DataTemplate>
<Grid>
<TextBlock Text="{Binding Path=UseCLUT}" Style="{StaticResource GridBlockStyle}"/>
<ComboBox x:Name="combTrueFalse" SelectedItem="{Binding Path=UseCLUT}" Style="{StaticResource GridEditStyle}" />
</Grid>
</DataTemplate>
</GridViewColumn.CellTemplate>
</GridViewColumn>
I have named the comboBox as combTrueFalse and tried to referenced it in the code behind but it could not be found.
I find a work around to the problem.
I have set the collection of the combo box to a class which contains the collection for the combox's selection.
The mainWindow class contains the data class's variable.
ItemsSource="{Binding ElementName=mainWindow, Path=data.comboxTFSmall}"
Meaning to say I have set the combox's item towards a Class which contains the collection and not from the code behind.
<GridViewColumn Width="80" >
<GridViewColumnHeader Content="UseCLUT"/>
<GridViewColumn.CellTemplate>
<DataTemplate>
<Grid>
<TextBlock Text="{Binding Path=UseCLUT, Mode=TwoWay}" Style="{StaticResource GridBlockStyle}"/>
<ComboBox ItemsSource="{Binding ElementName=mainWindow, Path=data.comboxTFSmall}" SelectedValue="{Binding Path=UseCLUT}" Style="{StaticResource GridEditStyle}" />
</Grid>
</DataTemplate>
</GridViewColumn.CellTemplate>
</GridViewColumn>
Please do correct me if you find that my explanation is misleading thanks.
regards
I have a this code:
<ListView Height="238"
HorizontalAlignment="Left"
Name="listView1"
VerticalAlignment="Top"
Width="503"
ItemsSource="{Binding}"
IsSynchronizedWithCurrentItem="True">
<ListView.View>
<GridView>
<GridView.Columns>
<GridViewColumn>
<GridViewColumn.CellTemplate>
<DataTemplate>
<CheckBox Tag="{Binding ID}"/>
</DataTemplate>
</GridViewColumn.CellTemplate>
</GridViewColumn>
<GridViewColumn DisplayMemberBinding="{Binding ID}" Header="ID" />
<GridViewColumn DisplayMemberBinding="{Binding Name}" Header="Name" />
</GridView.Columns>
</GridView>
</ListView.View>
</ListView>
That produce this window:
How do I know how many checkboxes are selected, and get the value Tag of each CheckBox that is selected?
i know it's old but for posterity if people stubble upon this here's the solution
<ListView Height="238"
HorizontalAlignment="Left"
Name="listView1"
VerticalAlignment="Top"
Width="503"
ItemsSource="{Binding}"
IsSynchronizedWithCurrentItem="True"
SelectionChanged="listView1_SelectionChanged">
<ListView.View>
<GridView>
<GridView.Columns>
<GridViewColumn>
<GridViewColumn.CellTemplate>
<DataTemplate>
<CheckBox Tag="{Binding ID}" IsChecked="{Binding RelativeSource={RelativeSource AncestorType={x:Type ListViewItem}}, Path=IsSelected}" />
</DataTemplate>
</GridViewColumn.CellTemplate>
</GridViewColumn>
<GridViewColumn DisplayMemberBinding="{Binding ID}" Header="ID" />
<GridViewColumn DisplayMemberBinding="{Binding Name}" Header="Name" />
</GridView.Columns>
</GridView>
</ListView.View>
</ListView>
then in the cs file code this in the listView1_SelectionChanged
private List<MyObject> lstMyObject = new List<MyObject>();
private void listView1_SelectionChanged(object sender, SelectionChangedEventArgs e)
{
foreach (MyObject item in e.RemovedItems)
{
lstMyObject.Remove(item);
}
foreach (MyObject item in e.AddedItems)
{
lstMyObject.Add(item);
}
}
lstMyObject should be same type as your object binded to the list. and the code will simply add and remove reference to items of the original list to that list.
Now all you will have to do is loop through that list which will contain only the actually selected items. this works for single selection only except that the lstMyObject will just contain 1 record all the time.
It should be as simple as binding the IsChecked property of the CheckBox to a property on the ViewModel (you may need to add a new property if it doesn't already exist). Then, once the button is clicked, you would just iterate over all the items in the collection, and delete the ones that are checked (based on the value of the property on the ViewModel).
A suggestion...
Just like the Tag property in all Windows controls, I've always had a Tag property in all my data models for general purpose use at run-time. I use that property to hold checked state of the item in a ListView. In other circumstance, I use them to hold complex objects as well.
I have the following XAML class:
<ListView Controls:ListViewColumns.Stretch="true"
Name="myListItems" SelectionMode="Single">
<ListView.View>
<GridView>
<GridViewColumn Width="50">
<GridViewColumn.Header>
</GridViewColumn.Header>
<GridViewColumn.CellTemplate>
<DataTemplate>
<Button Name="btnDelete"
HorizontalContentAlignment="Center"
VerticalContentAlignment="Center"
Padding="0"
Margin="0"
IsEnabled="{Binding Converter={StaticResource inverseBoolConverter},
RelativeSource={RelativeSource FindAncestor, AncestorType={x:Type WO:OpCompTab}}, Path=DisableAll}"
Click="btnDelete_Click" Tag="{Binding Path=.}">
<Image Source="/Win7;component/Images/Icons/Remove.png"
Width="{StaticResource IconWidth}"
Height="{StaticResource IconHeight}" />
</Button>
</DataTemplate>
</GridViewColumn.CellTemplate>
</GridViewColumn>
</GridView>
</ListView.View>
</ListView>
The listview gets populated by hooking an observable collection up to its ItemSource property. This generates a neat looking column of Delete buttons for each item in the observable collection.
But this presents a quandary: How do I access the "btnDelete" of an individual row of the ListView? I'd like to change its icon on a per-row basis. Is it just a matter of binding the observable collection to the cell template somehow?
Instead of hardcoding the Image.Source, bind it to a property in your row object. Another way is to create a value converter and pass in the row or some property if you can't or don't want to modify the row object to add an ImageSource property. Have the value converter return the correct image source based on the passed in property or some other logic.