After doing a quick search I can't find the answer to this seemingly simple thing to do.
How do I Manually Select An Item in an Asp.Net ListView?
I have a SelectedItemTemplate, but I don't want to use an asp:button or asp:LinkButton to select an item. I want it to be done from a URL. Like a QueryString, for example.
The way I imagine would be on ItemDataBound, check a condition and then set it to selected if true, but how do I do this?
For example:
protected void lv_ItemDataBound(object sender, ListViewItemEventArgs e) {
using (ListViewDataItem dataItem = (ListViewDataItem)e.Item) {
if (dataItem != null) {
if( /* item select condition */ ) {
// What do I do here to Set this Item to be Selected?
// edit: Here's the solution I'm using :
((ListView)sender).SelectedIndex = dataItem.DisplayIndex;
// Note, I get here and it gets set
// but the SelectedItemTemplate isn't applied!!!
}
}
}
}
I'm sure it's one or two lines of code.
EDIT: I've updated the code to reflect the solution, and it seems that I can select the ListView's SelectedItemIndex, however, it's not actually rendering the SelectedItemTemplate. I don't know if I should be doing this in the ItemDataBound event as suggested below.
I looked at some of what's going on in ListView under the hood and think this is probably the best approach.
void listView_ItemCreated(object sender, ListViewItemEventArgs e)
{
// exit if we have already selected an item; This is mainly helpful for
// postbacks, and will also serve to stop processing once we've found our
// key; Optionally we could remove the ItemCreated event from the ListView
// here instead of just returning.
if ( listView.SelectedIndex > -1 ) return;
ListViewDataItem item = e.Item as ListViewDataItem;
// check to see if the item is the one we want to select (arbitrary) just return true if you want it selected
if (DoSelectDataItem(item)==true)
{
// setting the SelectedIndex is all we really need to do unless
// we want to change the template the item will use to render;
listView.SelectedIndex = item.DisplayIndex;
if ( listView.SelectedItemTemplate != null )
{
// Unfortunately ListView has already a selected a template to use;
// so clear that out
e.Item.Controls.Clear();
// intantiate the SelectedItemTemplate in our item;
// ListView will DataBind it for us later after ItemCreated has finished!
listView.SelectedItemTemplate.InstantiateIn(e.Item);
}
}
}
bool DoSelectDataItem(ListViewDataItem item)
{
return item.DisplayIndex == 0; // selects the first item in the list (this is just an example after all; keeping it simple :D )
}
NOTES
ListView selects the template an item will use after it's DataBinding event fires. So if the SelectedIndex is set before then, no more work is necessary
Setting the SelectedIndex anywhere after DataBinding works, you just don't get the SelectedItemTemplate. For that you have either rebind the data; or reinstantiate the SelectedItemTemplate on the ListViewItem. be sure to clear the ListViewItem.Controls collection first!
UPDATE I have removed most of my original solution, since this should work better and for more cases.
You can set the ListViews SelectedIndex
list.SelectedIndex = dataItem.DisplayIndex; // don't know which index you need
list.SelectedIndex = dataItem.DataItemIndex;
Update
If your loading the data on page load you may have to traverse the data to find the index then set the SelectedIndex value before calling the DataBind() method.
public void Page_Load(object sender, EventArgs e)
{
var myData = MyDataSource.GetPeople();
list.DataSource = myData;
list.SelectedIndex = myData.FirstIndexOf(p => p.Name.Equals("Bob", StringComparison.InvariantCultureIgnoreCase));
list.DataBind();
}
public static class EnumerableExtensions
{
public static int FirstIndexOf<T>(this IEnumerable<T> source, Predicate<T> predicate)
{
int count = 0;
foreach(var item in source)
{
if (predicate(item))
return count;
count++;
}
return -1;
}
}
list.SelectedIndex = list.Items.IndexOf(item);
Expanding on #Jeremy and #bendewey's answers, you shouldn't need to do this in ItemDataBound. You only need to have the ListView binding already have taken place before you set the SelectedValue. You should be able to do this during PreRender. See this page life cycle docs for more information on when binding takes place.
Related
I use an ObservableCollection in my ViewModel to add a new record in my DataGrid, so I don't have an access to this control. I wanted to scroll to the bottom of the DataGrid every time a new item is added.
Normally I can just hook into INotifyCollectionChanged from my View, then scroll to the bottom, something like;
public MyView(){
InitializeComponent();
CollectionView myCollectionView = (CollectionView)CollectionViewSource.GetDefaultView(MyDataGrid.Items);
((INotifyCollectionChanged)myCollectionView).CollectionChanged += new NotifyCollectionChangedEventHandler(DataGrid_CollectionChanged);
}
private void DataGrid_CollectionChanged(object? sender, NotifyCollectionChangedEventArgs e){
if (MyDataGrid.Items.Count > 0){
if (VisualTreeHelper.GetChild(MyDataGrid, 0) is Decorator border){
if (border.Child is ScrollViewer scroll) scroll.ScrollToEnd();
}
}
}
My problem now is that I have a function to Duplicate and Delete an item, this whole thing is being done in my ViewModel. With the approach above, the DataGrid will always scroll to the bottom even if I deleted or duplicate an item in any position which I don't want to happen. Scrolling to the bottom should only be working for the newly added items.
What should be the approach for this?
You can try to check if NotifyCollectionChangedEventArgs.NewStartingIndex is at the end of your collection. You should scroll to the end only if the change has happened at the end.
private void DataGrid_CollectionChanged(object? sender, NotifyCollectionChangedEventArgs e){
if (MyDataGrid.Items.Count > 0 && e.NewStartingIndex == MyDataGrid.Items.Count - 1){
if (VisualTreeHelper.GetChild(MyDataGrid, 0) is Decorator border){
if (border.Child is ScrollViewer scroll) scroll.ScrollToEnd();
}
}
}
You can use the passed NotifyCollectionChangedEventArgs to identify whether a Duplicate or Delete has occurred.
Delete is easy, simply:
if (e.Action == NotifyCollectionChangedAction.Remove)
{
// An item has been removed from your collection. Do not scroll.
}
Duplicate depends on your definition of what a "duplicate" or "copy" is exactly, but most likely you can use a quick Linq check like:
if (e.Action == NotifyCollectionChangedAction.Add)
{
// An item has been added, let's see if it's a duplicate.
CollectionView changedCollection = (CollectionView)sender;
foreach (myRecordType record in e.NewItems)
{
if (changedCollection.Contains(record))
{
// This was likely a duplicate added
}
}
}
It's worth noting that EventArgs of any type are really there for this very purpose. They'll generally provide you with more information regarding the event for exactly this kind of logical handling.
You have two options, first is to compare the difference between the old and new items. Then simply get that item and using DataGrid.ScrollIntoView(item) to scroll into that specific position.
private void DataGrid_CollectionChanged(object? sender, NotifyCollectionChangedEventArgs e){
if (YourDataGrid.Items.Count > 0 && e.Action.Equals(NotifyCollectionChangedAction.Add)){ //Do this only when new item is added.
if (sender is not CollectionView oldItems) return;
foreach (var oldItem in oldItems) {
foreach (var newItem in (e.NewItems)??new ObservableCollection<TransactionItem>()){
if (!newItem.Equals(oldItem)){
YourDataGrid.ScrollIntoView(newItem); //Scroll into this specific item.
return; //No need to do further checking
}
}
}
}
}
}
However, the same behavior with the answer of #Paolo Iommarini will be expected when you duplicate the last item. Making his approach much better compared to this in terms of performance.
The second option and much better is by using Events. You simply need to define it into your ViewModel, then invoke the event every time a new item is added passing the argument as the newly added item. From your View, subscribe into that event and use DataGrid.ScrollIntoView(item) to scroll.
Here is a more detailed example, in your ViewModel.
public class ViewModel{
//define the event in the ViewModel.
public delegate void MyEventAction(YourObjectModelItem item);
public event MyEventAction? MyEvent;
private void AddNewItem(){ //Assuming this function is being called to add the item into your ObservableCollection.
var new_item = .... //(1) create your item.
YourObServableCollection.Add(new_item); //(2) add the item into your collection.
MyEvent?.Invoke(new_item); //(3) invoke the event and pass the new item as the argument.
}
}
Then in your View.
public YourView(){
InitializeComponent();
...
this.Loaded += (s, a) => { //You should subscribe only when the view is loaded, otherwise you might get a null issue with your DataContext. You may also use DataContextChanged if you want.
var vm = (ViewModel)DataContext; //(1) Get the ViewModel from your DataContext.
vm.MyEvent += (item) =>{ //(2) Subscribe to the event from the ViewModel.
YourDataGrid.ScrollIntoView(item); //(3) Scroll to that item.
};
};
}
With this approach, you don't have to make any comparison. You just need to know which item you will be scrolling.
I have a winform with a group of comboboxes, all of the comboboxes with the same list items in them.
I need a way to confirm that when the user is done selecting a value in each box that they only selected each list value once.
Ex:
cbox1 cbox2 cbox 3
Item A Item B Item A (this needs to flag an error since Item A is already selected in cbox1)
I was thinking trying to use the selectedvaluecommited action (as after i populate the list I change the selected index to -1 so they all show "empty" to start) but the loop to make it work seems to be eluding me.
background: this is choosing fields to build a spreadsheet and the user needs to choose the field order.
You can do it like this (quick and dirty):
Add SelectedIndexChanged handler for all three comboboxes (in Form_Load in example)
comboBox1.SelectedIndexChanged += CheckComboBoxes;
comboBox2.SelectedIndexChanged += CheckComboBoxes;
comboBox3.SelectedIndexChanged += CheckComboBoxes;
in CheckComboBoxes method do your checking:
private void CheckComboBoxes(object sender, EventArgs e)
{
if (comboBox1.SelectedIndex == comboBox2.SelectedIndex ||
comboBox1.SelectedIndex == comboBox3.SelectedIndex ||
comboBox2.SelectedIndex == comboBox3.SelectedIndex)
MessageBox.Show("comboboxes are not unique");
}
EDIT:
this is approach when having n comboboxes. Put all items into list, select distinct values and compare that distinct count with items count... Something like this:
private void CheckComboBoxes(object sender, EventArgs e)
{
List<string> comboValues = new List<string>();
foreach (Control c in this.Controls)
{
if (c is ComboBox && !string.IsNullOrEmpty((c as ComboBox).SelectedItem.ToString()))
comboValues.Add((c as ComboBox).SelectedItem.ToString());
}
if (comboValues.Distinct().ToList().Count < comboValues.Count)
MessageBox.Show("not all combos are unique");
}
Here's an approach you can take.
To make the affected comboboxes easy to distinguish, put them all in a GroupBox container.
Write a validation method for your group box.
Subscribe to the group box Validating event by attaching it to your validation method.
In your validation method, loop through all the ComboBox controls in the group box and check if there are any duplicates, and issue an error if so.
For example, assuming the group box is called groupBox1:
private void GroupBox1_Validating(object sender, CancelEventArgs e)
{
base.OnValidating(e);
var selectedIndices = groupBox1.Controls.OfType<ComboBox>().Select(item => item.SelectedIndex);
var anyDuplicates = selectedIndices.GroupBy(x => x).Any(x => x.Count() > 1);
if (!anyDuplicates)
return;
MessageBox.Show("There are duplicates!");
e.Cancel = true;
}
And subscribe to the group box Validating event in the Form1 constructor:
public Form1()
{
InitializeComponent();
groupBox1.Validating += GroupBox1_Validating;
}
Sometimes when validating like this, you need to prevent the validation logic from executing if the user clicks the Cancel button. You're supposed to be able to set the CausesValidation property of the Cancel button to false to prevent this, but I find that it doesn't work for me.
Instead, I just use a bool cancelling field which I set to true in the Cancel button handler:
private void cancelButton_Click(object sender, EventArgs e)
{
cancelling = true;
this.Close();
}
bool cancelling;
And then add the following to the start of GroupBox1_Validating():
if (cancelling)
return;
If it is possible to have different UI design then my suggestion goes as under:
Alternative UI Design - A
Create One ListBox ListFieldsOriginal and populate
Create Second ListBox ListUserSelection, keep it empty initially
Provide buttons as under:
Button '>' means add currently selected item from ListFieldsOrginial to ListUserSelection at end; and remove that item from ListFieldsOriginal
Button '<' means remove currenly selected item from lstUserSelection; and add that item back to ListFieldsOriginal (of course at end)
NOTE: If adding item back to ListFieldsOriginal is your requirement then extra coding is required to find its appropriate index in the ListFieldsOriginal.
Alternative UI Design - B
Create One CheckedListBox ListFieldsOriginal and populate
Create one ListBox ListUserSelection, keep it empty initially
Define ItemCheck event handler for ListFieldsOriginal to add/remove items to/from ListUserSelected.
if (e.CurrentValue==CheckState.Unchecked)
{
string item = ListFieldsOriginal.Items[item];
ListUserSelection.Items.Add(item);
}
else
{
string item = ListFieldsOriginal.Items[item];
ListUserSelection.Items.Remove(item);
}
I want to use listview to populate it with data and then use mouseclick event to fill some textboxes with data. I looked up an example in msdn:
ListViewItem theClickedOne = listView1.GetItemAt(e.X, e.Y);
ListViewItem theClickedtwo = listView1.FocusedItem;
if (theClickedOne != null)
{
MessageBox.Show(theClickedtwo.ToString());
//do your thing here.
//there is a reference to the listview item we clicked on
//in our theClickedOne variable.
}
but I couldn't think about a way to use it in order to differentiate the listviewitems I use since the fist Column in my program is the same and it will only give me a string with it's name(first Column).I want to have something similar to next example but for treeview.
void treeView1_NodeMouseClick(Object sender, TreeNodeMouseClickEventArgs e)
{
MessageBox.Show(e.Node.Text);
}
When populating you ListView, set the Tag property of the items, e.g.
newItem.Tag = "Item 1";
The Tag property has type object, so you can use anything you want here to identify the item. When handling the mouse click event simply check the Tag value again:
if((string)(clickedItem.Tag) == "Item 1")
{
// do stuff for this specific item.
}
I am trying to get the index of a listview item that has just being checked and update a database based on the item that was just checked not considering other items that were checked before
I am trying to use the checkbox to indicate a user wants a notification or not, so when a user checks the checkbox, i want to use the index of the item and set notification for that item to true but i can only get all the checkeditems indexes at once.
Any help please.
I was able to call the itemcheck event function but it considers items checked initially as well as items checked by user.
I managed to separate the items checked initially using a boolean function "Item_checked by user"
` private static bool checked_by_user;
private void courseworks_ItemCheck(object sender, ItemCheckEventArgs e)
{
if (checked_by_user == true)
{ //do something
}
else ;//do nothing
}`
Now, I want to be able to take the bar_ref_id of only the line that was just checked, My list view is created from a database as below
foreach (var item2 in CW_query2)//for each CW
{
if (item.Status == true)
{
ListViewItem items = new ListViewItem(new[] {//adds items into list view, per column
item2.Module_Code, item2.Title, item2.Due_Date.ToString("dd/MM/yy"),"Submitted",item.Bar_Ref_ID
});
courseworks.Items.Add(items);
}
else
{
ListViewItem items = new ListViewItem(new[] {//adds items into list view, per column
item2.Module_Code, item2.Title, item2.Due_Date.ToString("dd/MM/yy"),"Not-Submitted",item.Bar_Ref_ID
});
courseworks.Items.Add(items);
}
I hope my added info helps.
Thanks in advance
If you're already getting the subscriptions from the database and setting the Checked property for each item according to the the user's subscription, wouldn't it be easiest to use the checkboxes' CheckedChanged Event? It's hard to tell what your implementation might be, but you should be able to have one function for when the box is unchecked (removing a subscription), and another function for when the box is checked (adding a subscription).
If you are able to provide some code, I might be able to be more specific.
More Specific
In your ItemChecked event, .NET exposes the object sender and ItemCheckEventArgs e as the parameters of the event. Inside that function, you can look at the sender to get the ListViewItem that has been checked/unchecked, and you can look in e to retrieve the index of that item in the ListView (in case you can use the index to easily change data in your database). Here's a brief example I'm going to almost steal straight from Microsoft:
private void ListView1_ItemCheck1(object sender, ItemCheckEventArgs e)
{
ListViewItem item = (ListViewItem)sender
if (e.CurrentValue==CheckState.Unchecked)
{
Unsubscribe(e.Index, currentUserID);
/*You can pass the Index of the ListViewItem that caused the event
to a method that will update your database (I would find it easier
to program a function that takes the current user's ID as a parameter)*/
Unsubscribe(item.Name, currentUserID);
/*OR this might be a better way for you to reference what subscription
should be cancelled (again, in addition to a UserID)*/
}
else if((e.CurrentValue==CheckState.Checked))
{
Subscribe(e.Index, currentUserID);
}
}
private void Unsubscribe(int index, int userID)
{
//unsubscribe the referenced userID from the subscription at index
}
private void Unsubscribe(string subscriptionName, int userID)
{
//unsubscribe the referenced userID from the subscription called subscriptionName
}
I am unable to provide a more specific example for your second bit of code, because I'm not quite certain what it's doing. It looks like you might be doing something more complicated than the code example above can handle, but perhaps the code will assist you.
I have a combo box (winform). This combo box has some items (eg. 1,2,3,4).
Now, when I change the selection within this combo, I wish to know the old index and the new index.
How do I get this?
Possible approaches that I wish to AVOID.
Add an enter event, cache the current index and then on selection index change get the new index.
Using the selected text/selected item property received by the sender of the event.
What I ideally want:
In the event args that are received, I want something like:
e.OldIndex;
e.newIndex;
Right now the event args which are received in the SelectionIndex Change event are totally useless.
I don't want to use more than one event.
If C#, does not offer this, can I have my event which passes the old index and new index as event args?
Seems like this is a possible duplicate
ComboBox SelectedIndexChanged event: how to get the previously selected index?
There is nothing built in, you will need to listen for this event and keep track in a class variable.
But this answer seems to suggest a sensible way of extending the combobox to keep track of the previous index
https://stackoverflow.com/a/425323/81053
1-Make a List of integers
2-Bind a Button to switch to previous Screen (button Name "prevB")
3-change the ComboBox Index as Per described in the code
//initilize List and put current selected index in it
List<int> previousScreen = new List<int>();
previousScreen.Add(RegionComboBox.SelectedIndex);
//Button Event
private void prevB_Click(object sender, EventArgs e)
{
if (previousScreen.Count >= 2)
{
RegionComboBox.SelectedIndex = previousScreen[previousScreen.Count - 2];
}
}
You will need to replace the ComboBox with the following control:
public class AdvancedComboBox : ComboBox
{
private int myPreviouslySelectedIndex = -1;
private int myLocalSelectedIndex = -1;
public int PreviouslySelectedIndex { get { return myPreviouslySelectedIndex; } }
protected override void OnSelectedIndexChanged(EventArgs e)
{
myPreviouslySelectedIndex = myLocalSelectedIndex;
myLocalSelectedIndex = SelectedIndex;
base.OnSelectedIndexChanged(e);
}
}
Now you can get the PreviouslySelectedIndex property.
You can use YourComboBox.Tag (or other unused string/int property) to store old selected index...
I use such pair
comboBox.SelectedItem new item
comboBox.SelectionBoxItem old item