I have this subtle program regarding the behavior of listbox. My listbox is binded with an observable list in the viewmodel. There are 2 ways in addding an item in the listbox. First is ADD a single item then that item would be selected directly. This works fine.
The second way was LOAD which by its name will be adding more than 1 item in the lisbox. Now the problem is when loading items more than the listbox can accomodate in the view, those items that are not in view (items at the bottom thus need to be scrolled in order for it to be viewed) was not automatically selected...
Only the items that are by default viewed are the ones selected:
private void ItemContainerGenerator_StatusChanged(object sender, EventArgs e)
{
if (listBoxAddresses.ItemContainerGenerator.Status != GeneratorStatus.ContainersGenerated) return;
for (int i = 0; i < TestSetting.DeviceSettings.Count; i++)
{
ListBoxItem myListBoxItem = (ListBoxItem)(listBoxAddresses.ItemContainerGenerator.ContainerFromItem(TestSetting.DeviceSettings[i]));
if (myListBoxItem != null)
{
myListBoxItem.IsSelected = true;
}
}
listBoxAddresses.ItemContainerGenerator.StatusChanged -= ItemContainerGenerator_StatusChanged;
}
I wonder if this is just a natural behavior for listbox.
I just realize this now...setting my listbox to :
VirtualizingStackPanel.IsVirtualizing="False"
did all the trick. Thanks to Dr.WPF for the idea. Though there are consequences for turning off virtualization (performance) but it won't matter that much.
Related
I am a student learning xamarin forms, I am trying to create a basic chat app in this I want to know how to get position of current item in listview that's user watching. When a new message received i want to know if user is at bottom or not if at bottom focus the new and if not at the bottom then just add not by adding focus to it.
you get the selected item from the Xamarin.Forms.ListView.SelectedItem property of your ListView.
If your ListView.ItemSource is of a type that allows using IndexOf you can now do something like
int position = (yourlistview.ItemSource as ObservableCollection<your type>).IndexOf(yourlistview.SelectedItem)
Update:
ok I think i understood what you want.
In most cases more than one item is currently shown when using a listview. So their exists not a single index
but i think you just want to know if the last item of the list is visible/the user has scrolled to the end?
If so ListView has an ItemAppearing event. I use it for example to load more data from an websource if the user scrolled through the first 100 items.
You could do something like this
listview.ItemAppearing += listviewItemAppearing;
listview.ItemDisappearing += listviewItemDisappearing;
bool m_scrolledToEnd;
private void listviewItemDisappearing(object sender, ItemVisibilityEventArgs e)
{
if(e.Item == yourlastiem)
m_scrolledToEnd = false;
}
private void listviewItemAppearing(object sender, ItemVisibilityEventArgs e)
{
if(e.Item == yourlastiem)
m_scrolledToEnd = true;
}
if you realy need to know if a specific index is shown you could create a List<int> m_idxlist;
and in the appearing event add the index of the item to the list
and in the disappearing event remove the index of the item from the list.
Then you will have a list where all indexes of the items currently shown are stored.
From the Documentation
ListView supports selection of one item at a time. Selection is on by
default. When a user taps an item, two events are fired: ItemTapped
and ItemSelected. Note that tapping the same item twice will not fire
multiple ItemSelected events, but will fire multiple ItemTapped
events. Also note that ItemSelected will be called if an item is
deselected.
To detect selecting an item, you can add a method, onSelection:
void OnSelection (object sender, SelectedItemChangedEventArgs e)
{
if (e.SelectedItem == null) {
return; //ItemSelected is called on deselection, which results in SelectedItem being set to null
}
DisplayAlert ("Item Selected", e.SelectedItem.ToString (), "Ok");
//((ListView)sender).SelectedItem = null; //uncomment line if you want to disable the visual selection state.
}
To disable selection just set the selectedItem to null:
SelectionDemoList.ItemSelected += (sender, e) => {
((ListView)sender).SelectedItem = null;
};
Okay, so I have four listBox controls. I want to select the same index on all four listBox when one item is clicked on any of them. To be mentioned, I do change the index sometimes in the program. I tryed using a method listSelectChange (int index) and adding for each listBox an event for selectIndexChange, but it would activate the event even if the select is made by the program and not by user-control.
Please don't use classes, just a brute method would be fine!
You can unsubscribe from selectedIndexChanged before you update the ListBox and re-subscribe to it immediately after that. It's a common practice.
Since you gave no code example I'm doing some guessing here.
// Enumerable of all the synchronized list boxes
IEnumerable<ListBox> mListBoxes = ...
...
public void OnSelectedIndexChanged(object sender, EventArgs e) {
var currentListBox = (ListBox)sender;
// Do this for every listbox that isn't the one that was just updated
foreach(var listBox in mListBoxes.Where(lb => lb != currentListBox)) {
listBox.SelectedIndexChanged -= OnSelectedIndexChanged;
listBox.SelectedIndex = currentListBox.SelectedIndex;
listBox.SelectedIndexChanged += OnSelectedIndexChanged;
}
}
I have two DataGrids above one another, and selecting a row in the top grid, OrdersGrid, displays some details in the bottom grid, DetailsGrid, about the selected row from OrdersGrid.
I would like the NewItemPlaceholderPosition to be AtBeginning for both grids. This is easy enough for OrdersGrid because I can just set it in my UserControl subclass constructor:
((IEditableCollectionView)OrdersGrid.Items).NewItemPlaceholderPosition = NewItemPlaceholderPosition.AtBeginning;
But the problem is that the ItemsSource for DetailsGrid is a member of the currently selected item in OrdersGrid.
If I set NewItemPlaceholderPosition for DetailsGrid as above then it works until I click on a new row in OrdersGrid when it goes back to the default of being at the bottom since DetailsGrid reloads from its new ItemsSource.
The ItemsSource for OrdersGrid is an ObservableCollection called Orders, which contains Objects of type Order, and the ItemsSource for DetailsGrid is Order.Details, also an ObservableCollection, for the current Order.
I'm thinking I want something like an ItemsSourceChanged event for DetailsGrid, but I'm not sure if this is the correct approach or even how to go about this. Please help!
I was able to solve this problem by adding the following to my UserControl subclass constructor:
((IEditableCollectionView)DetailsGrid.Items).NewItemPlaceholderPosition = NewItemPlaceholderPosition.AtBeginning;
var dpd = DependencyPropertyDescriptor.FromProperty(ItemsControl.ItemsSourceProperty, typeof(DataGrid));
if (dpd != null)
{
dpd.AddValueChanged(DetailsGrid, DetailsSourceChanged);
}
with the DetailsSourceChanged method defined as followed:
private void DetailsSourceChanged(object sender, EventArgs e)
{
if (DetailsGrid.Items.Count > 0)
{
((IEditableCollectionView)DetailsGrid.Items).NewItemPlaceholderPosition = NewItemPlaceholderPosition.AtBeginning;
}
}
I am unable to get ListBoxItem from ListBox. I have created ListBox dynamically; it is not in XAML. I just set ItemsSource and I have values in all the items but cannot access/convert each item as ListBoxItem.
for (int i = 0; i < listBox.Items.Count; i++)
{
ListBoxItem item = (ListBoxItem)listBox.ItemContainerGenerator.ContainerFromItem(listBox.Items[i]);
// item is null after above statement
}
Note: I just checked 'listBox.ItemContainerGenerator.Status' . listBox.ItemContainerGenerator.Status is 'notStarted'.
What to do now?
It sounds like you are not giving WPF enough time to render the <ListBoxItem> objects before calling your method.
A common way of accessing the ListBoxItems right after its Items property is set is to use the ItemContainerGenerator.StatusChanged event, like this:
void MyConstructor()
{
listBox.ItemsSource = someCollection;
listBox.ItemContainerGenerator.StatusChanged +=
ItemContainerGenerator_StatusChanged;
}
void ItemContainerGenerator_StatusChanged(object sender, EventArgs e)
{
// If containers have been generated
if (listBox.ItemContainerGenerator.Status ==
System.Windows.Controls.Primitives.GeneratorStatus.ContainersGenerated)
{
// Remove event
listBox.ItemContainerGenerator.StatusChanged -=
ItemContainerGenerator_StatusChanged;
// Do whatever here
foreach(var item in listBox.Items)
{
var item = (ListBoxItem)listBox.ItemContainerGenerator.ContainerFromItem(item);
// do whatever you want with the item
}
}
}
WPF runs code at different DispatcherPriorities. Code run in the constructor or on load is run at Normal priority, while the generation of ListBoxItem objects doesn't occur until Render priority, which runs after all Normal priority items have finished running.
You could alternatively use the Dispatcher to run your code at a later dispatcher priority than Render as well.
Why are you casting the listbox to a listboxitem?
Here is a simular question about getting the selected items
listbox selected items in winform
You should be able to get the item by it's index
ListBox1.Items.Item(index)
I have a WPF window that manages sets of configurations and it allows users to edit a configuration set (edit button) and to remove a configuration set (remove button). The window has a ListBox control that lists the configuration sets by name and its ItemsSource has a binding set to a list of configuration sets.
I'm trying to remove the item in the code behind file for the window..
private void RemoveButton_Click(object sender, RoutedEventArgs e)
{
var removedItems = configSetListBox.SelectedItems;
foreach(ConfigSet removedItem in removedItems)
{
configSetListBox.Items.Remove(removedItem);
}
}
My code yields an invalid operation exception stating "Access and modify elements with ItemsControl.ItemsSource instead." What property should I be accessing to properlyremove items from the ListBox? Or is there possibly a more elegant way to handle this in WPF? My implementation is a bit WinForm-ish if you will :)
Solution
private void RemoveButton_Click(object sender, RoutedEventArgs e)
{
foreach(ConfigSet removedItem in configSetListBox.SelectedItems)
{
(configSetListBox.ItemsSource as List<ConfigSet>).Remove(removedItem);
}
configSetListBox.Items.Refresh();
}
In my case I had a List as the ItemSource binding type so I had to cast it that way. Without refreshing the Items collection, the ListBox doesn't update; so that was necessary for my solution.
use:
private void RemoveButton_Click(object sender, RoutedEventArgs e)
{
foreach(ConfigSet item in this.configSetListBox.SelectedItems)
{
this.configSetListBox.ItemsSource.Remove(item); // ASSUMING your ItemsSource collection has a Remove() method
}
}
Note: my use of this. is just so it as it is more explicit - it also helps one see the object is in the class namespace as opposed to variable in the method we are in - though it is obvious here.
This is because , you are modifying a collection while iterating over it.
if you have binded item source of listbox than try to remove the items from the source
this was answered here already.
WPF - Best way to remove an item from the ItemsSource
You will need to implement an ObservableCollection and then whatever you do to it will be reflected in your listbox.
I Used this logic to preceed. And it worked.
you may want to try it.
private void RemoveSelectedButton_Click(object sender, RoutedEventArgs e) {
if (SelectedSpritesListBox.Items.Count <= 0) return;
ListBoxItem[] temp = new ListBoxItem[SelectedSpritesListBox.SelectedItems.Count];
SelectedSpritesListBox.SelectedItems.CopyTo(temp, 0);
for (int i = 0; i < temp.Length; i++) {
SelectedSpritesListBox.Items.Remove(temp[i]);
}
}
for (int i = lstAttachments.SelectedItems.Count - 1; i >= 0; i--)
{
lstAttachments.Items.Remove(lstAttachments.SelectedItems[i]);
}
Simplest way to remove items from a list you iterate through is going backwards because it does not affect the index of items you are moving next to.