WPF DataGrid SelectedCellsChanged event fires twice - c#

I have subscribed via
_itemsGrid.SelectedCellsChanged += Handle_SelectedCellsChangedEvent;
to the WPF DataGrid SelectedCellsChanged event and when logging via
private void Handle_SelectedCellsChangedEvent (object sender, SelectedCellsChangedEventArgs args)
{
Logger.DoLogging("args.AddedCells.Count={0}, args.RemovedCells.Count={1}.", args.AddedCells.Count, args.RemovedCells.Count);
...
}
I see that sometimes for one row selection change there are two calls:
1. AddedCells.Count = 4, RemovedCells.Count = 0
2. AddedCells.Count = 0, RemovedCells.Count = 4
Now this behavior might be unhandy or unexpected, but is in itself no problem.
My real problem is that in certain conditions the data grid forgets to unselect the removed cells.
This means after the event handler are called and processed, the former selected cells are still visually selected.
My assumption is that this forgetting is related to a dialog that I'm displaying to the user.
Has anybody else seen the data grid "forgetting" to unselect cells?
Thanks
Frank

The event not only encapsulates the action of notifying of cells being added to the selection (i.e. the SelectedCells), but it also notifies of cells being removed from the selection. This allows you to take action on both or either selection change types.
I believe this corresponds with the fact that NotifyCollectionChangedEventArgs has a single Action. So to show a change in the SelectedCells collection of the grid, items need to be both added and removed. The notification of this addition and removal is separated into to distinct notifications since the event does not support showing them as one.

Related

How can I skip the CellValueChanging state of DevExpress ColumnView?

There are 2 distinct UI concept: CellValueChanging vs CellValueChanged.
Quoted from DevExpress Documentation:
The CellValueChanging event is raised each time the edited value is being changed (the user types or deletes a character, chooses a value from the dropdown list, etc.).
On the other hand, CellValueChanged is raised when user had done cell editing by hitting enter or clicking outside of the active cell.
Now my problem is, I have a combobox type column, and I want to always skip the CellValueChanging and make the change final. The current behavior is when user select an item from the combobox, the change doesn't take effect immediately(e.g, the view will not be resorted as per the change). The change is not accepted until user hit enter or click outside of the cell.
---------------07/26/2013 2:25PM update---------------
Sorry that my previous question description misled everybody, so I'll rephrase it.
Here is the current behavior:
Pic1: beginning state. Rows are sorted alphabetically by Target.
Pic2: Change the 2nd row value from B to D
Pic3: After a single mouse left click on item D, the dropdown disappears and the cell value changes to D. However, the rows are not resorted
Pic4: After clicking outside the cell or hitting enter, the rows are resorted.
What I want to achieve is to skip the step in Pic3. In other word, I want any changes committed immediately, without having to make an extra kepboard or mouse click.
What I am showing here is a simplified example of my application. I can't move my CellValueChanged event handler logic to CellValueChanging or EditValueChanged because it would cause some errors.
You want to make some UI change once user edit the cell value, right? I think generally you have to give up handling the CellValueChanged event, but use the CellValueChanging Event instead:
pseudocode:
HandleCellValueChanging(object sender, CellValueChangingEventArgs e)
{
// Get underlying object
// and write the value direct into the object
var rowObj = (YourType)gridview.GetRow(gridView.FocusedRowHandle);
rowObj.TargetField = e.NewValue; // e.Value or e.NewValue, not sure
// Then Do your UI effect
}
Another option is to use RepositoryItemEditor and handle the key-up and/or Mouse-up event instead.
If you want to to catch the moment when an end-user changes some value in gridview's cell editor (e.g. select item in combobox), please handle the EditValueChanged event of a corresponding RepositoryItem. To immediately post this value (make the changes final), you need to call the PostEditor method of a corresponding container:
repositoryItemComboBox1.EditValueChanged += repositoryItemComboBox1_EditValueChanged;
//...
void repositoryItemComboBox1_EditValueChanged(object sender, EventArgs e) {
gridView1.PostEditor();
// do something else if it needed
}
Related example: E3234 - How to update a row's style immediately after a an inplace editor's value is changed

DataGridView row is being cleared at validation

I have a DataGridView which I need to run a CellValidating event to ensure that only valid values are selected from a ComboBox. This is needed as the ComboBox contains dummy rows used to display the category, with the fields the user can select listed underneath each category.
Whilst I have the validation code working fine, there is an unwelcome side-effect that all values are being wiped from the row being validated. I have stripped the code in the Event handler down to this, and the issue still occurs:
private void dgvInformation_CellValidating(object sender, DataGridViewCellValidatingEventArgs e)
{
DataGridView dgv = this.dgvInformation;
DataGridViewCell cell = dgv.Rows[e.RowIndex].Cells[e.ColumnIndex];
}
If I remove the
DataGridViewCell cell = dgv.Rows[e.RowIndex].Cells[e.ColumnIndex];
line then the issue does not occur.
The DGV is unbound which I believe is causing the issue. As a test I have made a simple form and populated the DGV values unbound, and each time the CellValidating event fires that row is wiped out, but when I create a List<> and use that as the DataSource the values are not wiped out. Could this be a bug with unbound DGVs?
Many thanks
I have problems with the sentense: "to ensure that only valid values are selected from a ComboBox." Using a combobox should actually prevent wrong values beeing tipped in controls like textbox, why dont you just show valid values in the combobox, or validate after all sellections have been made if you need to validate a combination of comboboxes sellection, therefor you may need somthing like a Submit button to run the validation routine.
If you still think you have to validate the combobox after each sellection then you ahve to run the validation somehow on a SelectionChanged event of the combobox.

What exactly is ListView.RetrieveVirtualItem Event - C#

With respect to a Virtual ListView control in a Winforms App, what are ALL the functions of RetrieveVirtualItem event?
Scenario: There is a Dictionary object which acts as my ListView cache. And its items are displayed on the ListView. On click of a column, the cache dictionary is sorted in memory and the ListView is Refresh()ed. The effect on UI, the ListView is sorted well. :)
But I want to understand what the role of RetrieveVirtualItem Event is, which gets triggered on Refresh().
In the RetrieveVirtualItem event handler [ Someone else's code, :( ], following are done:
Based on RetrieveVirtualItemEventArgs.ItemIndex, get the message from Cache
Set RetrieveVirtualItemEventArgs.Item = retreived item above
It appears that what's done in the event handler is important because, if I take it out, ListView cries. What is the significance of this event?
EDIT
Or let me re-phrase the question... My concern is, Why, after Sorting (and RetrieveVirtualItem event handler), the selected item remains at the location where it was before sorting. I.e, If I select item #5 and sort, and if sorting makes this item as the last item, I would prefer the LAST item to be selected after Sort. But in my case 5th item is selected after sort. So what is the default behavior of RetrieveVirtualItem WRT selected item?
A virtual ListView should only call RetreiveVirtualItem for the rows currently visible on the screen.
As you navigate in the ListView, for example, you press the page down key, the ListView will calculate what should now be the index of the top row and will then call RetrieveVirtualItem so that your code can provide the item to use at each row index.
Unless you cache or otherwise store the items you are providing via RetrieveVirtualItem, they will no longer exist once they are scrolled out of the listview.
This is what the Virtual in VirtualListView means - there aren't any real rows, the rows are virtual. That is how it could display a list containing hundreds of thousands of rows - because it will ever only actually contain how ever many rows are visible on screen.
In effect, the ListView is like a window that is moving up and down your internal list of data - the RetreiveVirtualItem method is what it calls to move items into that window as it moves along. It says, hey I just moved to row 15 - give me the item for that row. It will proceed to call RetreiveVirtualItem for each row index which would be visible. If the ListView was 5 rows in height on the screen, you would receive 5 calls to RetrieveVirtualItem - even if the actual data backing the listview had 3000 items. Each time the top row of the ListView changed (because of navigation), you would receive 5 calls to RetrieveVirtualItem (this is not always the case, but it is the right idea - for example, if you scroll down one row, it will simply ask you for the new last row - it will also simply discard the data that was used for the old top row that scrolled out of view).
I guess it might be even easier to explain if we assume the ListView was only one row high on the display (meaning only a single row is ever actually visible on the screen) - as you move the ListView up and down your list of data (i.e. the user navigates the ListView), it would call RetrieveVirtualItem exactly one time every time it moves to a new row.
Hope that helps...
Good Luck
Virtual listviews only deal with indices. So, if the 5th item is selected before the sort, the 5th item will still be selected after the sort. The control itself has no way of knowing that the data that used to be on the 5th row is now on the first row.
You will have to program that for yourself in your sorting method:
remember which items were selected (remember: you can't use SelectedItems property when in virtual mode)
do the sort
find the indices of the previously selected item now live
select those indices
You can see all this in action in ObjectListView -- a wrapper around a standard .NET ListView.
The RetrieveVirtualItem event is only used when the ListView is put into virtual mode. Rather than retaining ListViewItems in the Items collection (as when not in virtual mode), it creates the ListViewItems dynamically, when needed.
If you don't handle the RetrieveVirtualItem event then no ListViewItems will be added to your ListView. I've attached some sample code for a typical method written to handle the event:
//Dynamically returns a ListViewItem with the required properties; in this case, the square of the index.
void listView1_RetrieveVirtualItem(object sender, RetrieveVirtualItemEventArgs e)
{
//Check if cache has been instantiated yet, and if so, whether the item corresponding to the index requested has been added to the cache already
if (myCache != null && e.ItemIndex >= firstItem && e.ItemIndex < firstItem + myCache.Length)
{
//Return cached item for index
e.Item = myCache[e.ItemIndex - firstItem];
}
else
{
//When item not in cache (or cache not available) return a new ListViewItem
int x = e.ItemIndex * e.ItemIndex;
e.Item = new ListViewItem(x.ToString());
}
}
This example is taken from MSDN (http://msdn.microsoft.com/en-us/library/system.windows.forms.listview.virtualmode(v=vs.90).aspx) where further details can be found.

DataGridView that always has one row selected

I'm using a DGV to show a list of images with text captions as a picklist. Their must always be a one and only one selection made in the list. I can't find a way to prevent the user from clearing the selection with a control-click on the selected row.
Is there a property in the designer I'm missing that could do this?
If I have to override the behavior in the mouse click events are there other ways the user could clear the current selection that need covered as well?
Is there a third approach I could take that's less cumbersome than my second idea?
The easiest way is to catch the SelectionChanged event and check to see if the user has unselected all rows. If so, reselect the previously selected row. Essentially, you're intercepting their action and switching the selection back. Something like this (code untested but you will get the idea):
DataGridViewRow last_selected_row;
private void dgv_SelectionChanged(object sender, EventArgs e)
{
if (dgv.SelectedRows.Count == 0)
last_selected_row.Selected = true;
else
last_selected_row = dgv.SelectedRows[0];
}
Depending on your application, it might be better to store the row index rather than a reference to the row itself. Also be sure to initialize last_selected_row and update it if you delete any rows.
Any other controls that hook the SelectionChanged event will need to safely handle the case that no rows are selected, in case they fire before the event that switches it back. They can just return immediately though, safe in the knowledge that SelectionChanged will fire again momentarily.
You could also subclass DataGridView and override the OnSelectionChanged method. Then you could reselect the last selected row before the event fires (it will fire when you call base.OnSelectionChanged).
A DGV got a property called multiselect, if you set it to false only one cell/row can be selected at a time.
Just handle the DataBindingComplete event of the datagridview like this:
private void datagridview1_DataBindingComplete(System.Object sender, System.Windows.Forms.DataGridViewBindingCompleteEventArgs e)
{
datagridview1.ClearSelection();
}

How do I figure out which items' selected state was changed in a DataGridView?

I have a DataGridView that has MultiSelect turned on. When the SelectionChanged event is fired, I'd like to know what items were newly selected and which items were newly deselected. For instance, if you have multiple items selected (by Ctrl-clicking) and then you release the Ctrl key and select a single item, I'd like to know which items were deselected. I could keep track of the previous selected items collection, but I just wanted to make sure I wasn't thinking too hard.
The event does not tell you exactly which things changed. If you need to know for some reason, you will have to keep track of the previous selection.
What are you trying to do in response to this event? There may be a much easier way to accomplish your real goal.
That information should be in the event arguments.
Use the RowStateChanged event. the DataGridViewRowStateChangedEventArgs will contain the row that was clicked. If a user selects/deselects multiple rows, the event will be called once for each row selected/deselected.
e.Row.Selected will yield whether the row is now selected or deselected.
This information is not inherently available for a DataGridView. However you could write a wrapper around the DataGridView which provides this information.
public static void OnSelectionChanged(
this DataGridView view,
Action<List<DataGridViewRow>,List<DataGridViewRow>> handler) {
var oldSelection = view.SelecetedRows.Cast<DataGridViewRow>.ToList();
view.SelectedChanged += (sender,e) {
var newSelection = view.SelectedRows.Cast<DataGridViewRow>.ToList();
handler(oldSelection,newSelection);
oldSelection = newSelection;
};
}
Use Case
void HandleSelectionChanged(List<DataGridViewRow> oldRows, List<DataGridViewRow> newRows) {
..
}
void FormLoaded() {
myDataGridView.OnSelectionChanged(HandleSelectionChanged);
}

Categories