Detect column reordering - ColumnDisplayIndexChanged raises multiple times - c#

I am working on a .net 4.6.1 C# winforms project that has a datagridview where users can change the order of columns.
I would like to store the new order in a db table, but have trouble finding the right event for detecting when a user changed the order of the columns.
After searching here, I was pointed to the DataGridView.ColumnDisplayIndexChanged event in this thread. But that one does not solve my issue. (it only gives a solution for multiple events when you fill the datagrid view, but that is answered easily by adding the handler after setting the datasource)
That sort of works, but gets fired multiple times when a user changes the order of columns (it f.e. looks like when changing columns A,B,C,D to D,A,B,C the event gets fired 3 times (probably for A,B,D,C - A,D,B,C - D,A,B,C)
I am having a hard time finding out how I can detect if the event is the final one (since I don't want to store all these new orders, only the final one)
My questions are:
Is this event the 'best' one to use for my case?
If so, how can I detect the final ColumnDisplayIndexChanged event (D,A,B,C)?

When you reorder columns, ColumnDisplayIndexChanged will raise for all the columns which their display index has been changed. For example if you move colum A to the position after C, the event will raise for all those three columns.
There is a solution to catch the last one. DataGridViewColumn has an internal property called DisplayIndexHasChanged which is true if the event should be fired for the column. The private method which raise the event, looks into list of the columns and for each column if that property is true, first sets it to false, then raises the event. You can read internal implementations here.
You can check if there is no column having DisplayIndexHasChanged with true value, you can say it's the last event in the sequence:
private void dgv_ColumnDisplayIndexChanged(object sender, DataGridViewColumnEventArgs e)
{
var g = (DataGridView)sender;
var property = typeof(DataGridViewColumn).GetProperty("DisplayIndexHasChanged",
System.Reflection.BindingFlags.NonPublic | System.Reflection.BindingFlags.Instance);
if (g.Columns.Cast<DataGridViewColumn>().Any(x => (bool)property.GetValue(x)))
return;
else
MessageBox.Show("Changed");
}
Just keep in mind, you should disable capturing that event when you add columns:
private void f_Load(object sender, EventArgs e)
{
LoadData();
}
void LoadData()
{
dgv.ColumnDisplayIndexChanged -= dgv_ColumnDisplayIndexChanged;
dgv.DataSource = null;
var dt = new DataTable();
dt.Columns.Add("A");
dt.Columns.Add("B");
dt.Columns.Add("C");
dgv.DataSource = dt;
dgv.ColumnDisplayIndexChanged += dgv_ColumnDisplayIndexChanged;
}

My suggestion would be not to do any custom logic to find out if its the last one or something along those lines. The best approach would be to save after each event but you can debounce it.
Using a debounce approach you can cancel the old event if the new event is fired right after depending on some amount of time you wish to allow inbetween calls.
Ex: write to storage only if there is no new event after lets say 1 second or 5 seconds depending on what is accepteable for your application
Say we decide to save with a debounce of 1 second
First event occurs you trigger the action which has 1 second to execute
If another event is triggered the old action is ignored and the new action now has 1 second to execute and so on for other sequential actions
public static Action Debounce(this Action func, int milliseconds = 300)
{
var last = 0;
return arg =>
{
var current = Interlocked.Increment(ref last);
Task.Delay(milliseconds).ContinueWith(task =>
{
if (current == last) func(arg);
task.Dispose();
});
};
}
Assuming the following action below for saving your data
Action a = (arg) =>
{
save my data here
};
first assign the debouncer to your action
var debouncedWrapper = a.Debounce(1000); //1 sec debounce
Then you can use it as follows
public void datagridchangeevent(object sender, Event e)
{
debouncedWrapper()
}
This will ignore sequential calls and the aciton will be executed only if nothing is called for one second

Related

How to constantly delay the execution of a method

I have a ICollectionVIew named 'CompanyView'.
I also have a Filter for it called 'CompanyFilter'.
And a Textbox bound to a 'SearchCompanyTitle' property.
As I type in a databound textbox, 'CompanyFilter' gets fired with every letter and the 'CompanyView' gets filtered to show relevant results.
That works fine.
Unfortunately the table I'm filtering has about 9 000 rows so there tends to be a notable delay between the moment you press the key on the keyboard and it showing up on screen.
So what I decided to do instead was ensure that the filter was automatically fired when the user had finished typing. Which raises the question of how does the ViewModel know when the user has finished?
What I did is the below;
// This is the property the Textbox is bound to
private string _searchCompanyTitle = "";
public string SearchCompanyTitle
{
get { return _searchCompanyTitle; }
set
{
_searchCompanyTitle = value;
OnPropertyChanged("SearchCompanyTitle");
// After a character has been typed it will fire the below method
SearchCompany();
}
}
// This method is fired by the above property everytime a character is typed into the textbox
// What this method is meant to do is wait 1000 microseconds before it fires the filter
// However I need the timer to be reset every time a character is typed,
// Even if it hasn't reached 1000 yet
// But it doesn't do that. It continues to count before triggering the filter
private async void SearchCompany()
{
bool wait = true;
while (wait == true)
{
await Task.Delay(1000);
wait = false;
}
CompanyView.Filter = CompanyFilter;
}
// And this is the filter
private bool CompanyFilter(object item)
{
company company = item as company;
return company.title.Contains(SearchCompanyTitle);
}
So that's my problem. I need the filter to fire only when the timer hits 1000 and not before. At the same time I need the timer to go back to 0 every time the method is fired by the property. Clearly I'm not doing it right. Any ideas?
Sounds like a perfect candidate for binding Delay:
<TextBox Text="{Binding SearchCompanyTitle, Delay=1000}"/>
One solution could be to use the System.Threading.Timer class.
You can give it a callback to be called when the time set is elapsed.
Put the filter method as the callback and reset the timer's time on every key stroke.
You can find an example here.
--EDIT--
I didn't see that you were using WPF, Sinatr answer is the correct one, just use binding delay

LinqDataSource Result becoming Null before OnSelected was called

I'm assigning list to e.Result in LinqDataSource OnSelecting Event result contains 5 rows in it. On Execution of OnSelected Event the result doesn't contains any rows . Why is this happening ?... I'm I missing any thing? Here is my code of Selecting event
protected new void OnDataSourceSelecting(object sender, LinqDataSourceSelectEventArgs e)
{
int AdminAccessID = 1;
List<VIEW_ManagerOwned> result = _dataContext.VIEW__ManagerOwneds.Where(ma => (ma.LastName == "West") & (ma.FirstName == "Stacie") & ma.AdminUserAccessID == Convert.ToInt32(AdminAccessID) & ma.SecurityUserID == Convert.ToInt32(1766)).ToList();
e.Result = result;
}
protected void ListLinqDataSource_Selected(object sender, LinqDataSourceStatusEventArgs e)
{
}
I'm no expert on LinqDataSourceSelectEventArgs but my suspicion is that you are expecting the value of e.Result to be passed to the next event hander, in this case ListLinqDataSource_Selected but it is likely that this handler is getting a different set of event args which would explain why you are not getting your list in e.Result from the Selecting handler.
One potential solution is to cache the value of result in a class level variable so that you can access it after you have it populated.
Again I could be wrong about the event handler not sending the previous event args to the next handler, but I believe this is the most likely cause of the issue.
ADDITIONS:
Simply regarding your code, are you sure you want to use bitwise & in your LINQ statement and not a conditional &&? There are times to use each but you may get undesired results using the & operator here. Also, consider formatting your code with less horizontal space and more vertical space, it is difficult to read when you have such a long line of code. Also generally you do not want to assign to event args the way you are doing it, as you can see it is not producing the results you are expecting. (no pun intended on results).

Determine who fired an event

Background:
In my winforms form, I have a Checked ListView and a "master" checkbox called checkBoxAll.
The behaviour of the master is as follows:
If the master is checked or unchecked, all ListViewItems must change accordingly.
If the user unchecks a ListViewItem, the master must change accordingly.
If the user checks a ListViewItem, and all other ListViewItems are checked aswell, the master must change accordingly.
I have written the following code to mimic this behaviour:
private bool byProgram = false; //Flag to determine the caller of the code. True for program, false for user.
private void checkBoxAll_CheckedChanged(object sender, EventArgs e)
{
//Check if the user raised this event.
if (!byProgram)
{
//Event was raised by user!
//If checkBoxAll is checked, all listviewitems must be checked too and vice versa.
//Check if there are any items to (un)check.
if (myListView.Items.Count > 0)
{
byProgram = true; //Raise flag.
//(Un)check every item.
foreach (ListViewItem lvi in myListView.Items)
{
lvi.Checked = checkBoxAll.Checked;
}
byProgram = false; //Lower flag.
}
}
}
private void myListView_ItemChecked(object sender, ItemCheckedEventArgs e)
{
//Get the appropiate ListView that raised this event
var listView = sender as ListView;
//Check if the user raised this event.
if (!byProgram)
{
//Event was raised by user!
//If all items are checked, set checkBoxAll checked, else: uncheck him!
bool allChecked = true; //This boolean will be used to set the value of checkBoxAll
//This event was raised by an ListViewItem so we don't have to check if any exist.
//Check all items untill one is not checked.
foreach (ListViewItem lvi in listView.Items)
{
allChecked = lvi.Checked;
if (!allChecked) break;
}
byProgram = true; //Raise flag.
//Set the checkBoxAll according to the value determined for allChecked.
checkBoxAll.Checked = allChecked;
byProgram = false; //Lower flag.
}
}
In this example, I use a flag (byProgram) to make sure an event was caused by the user or not, thereby preventing an infinite loop (one event can fire another, which can fire the first one again etc. etc.). IMHO, this is a hacky solution.
I searched around but I couldn't find a MSDN documented method to determine if an User Control Event was directly fired thanks to the user. Which strikes me as odd (again, IMHO).
I know that the FormClosingEventArgs has a field which we can use to determine if the user is closing the form or not. But as far as I know, that is the only EventArg that provides this kind of functionality...
So in summary:
Is there a way (other than my example) to determine if an event was fired directly by the user?
Please note: I don't mean the sender of an event! It won't matter if I code someCheckBox.Checked = true; or manually set someCheckBox, the sender of the event will always be someCheckBox. I want to find out if it is possible to determine whether it was through the user (click) or by the program (.Checked = true).
Aaand also: 30% of the time it took to write this question was to formulate the question and the title correctly. Still not sure if it is a 100% clear so please edit if you think you can do better :)
No, there's no practical way to determine whether the change came from GUI or was done by program (in fact, you could analyze the callstack - but that's not recommended because it's very slow and error-prone).
BTW, there's one other thing you could do instead of setting byProgram. You could remove and add the event handler prior or after, respectively, change your controls:
checkBoxAll.CheckedChanged -= checkBoxAll_CheckedChanged;
// do something
checkBoxAll.CheckedChanged += checkBoxAll_CheckedChanged;
Instead of using the changed event, you could use the clicked event to cascade the change through to the relevant controls. This would be in response to a user click, and not the value being changed programatically.
This is something I come across quite a lot and what I tend to try do is not split it between user interaction vs program interaction - I use more generic code i.e. the UI is being updated and doesn't require any events to be handled. I usually package this up through BeginUpdate/EndUpdate methods e.g.
private int updates = 0;
public bool Updating { get { return updates > 0; } }
public void BeginUpdate()
{
updates++;
}
public void EndUpdate()
{
updates--;
}
public void IndividualCheckBoxChanged(...)
{
if (!Updating)
{
// run code
}
}
public void CheckAllChanged(...)
{
BeginUpdate();
try
{
// run code
}
finally
{
EndUpdate();
}
}

What is the best way to mask an UI Event?

I am working with a DataGridView, and I use the CellValueChanged event.
I dont want this event to be triggered when I change a cell value by the code. However, I want it to be triggered when the user edits it.
That's why I enclose my cell value change operations with the following code :
void changeCellOperation()
{
dgv.CellValueChanged -= new DataGridViewCellEventHandler(dgv_CellValueChanged);
...
cell.Value = myNewCellValue
...
dgv.CellValueChanged += new DataGridViewCellEventHandler(dgv_CellValueChanged);
}
I ended to have several differents functions where my DataGridView cells are updated this way.
Because these functions are called from different places and can be nested, I cannot afford to keep this code as is to avoid event unwanted event reactivation.
So I ended up this way :
int valueChangedEventMask = 0;
void changeCellOperation()
{
valueChangedEventMask++;
...
cell.Value = myNewCellValue
...
valueChangedEventMask--;
}
void dgv_CellValueChanged(object sender, DataGridViewCellEventArgs e)
{
if (valueChangedEventMask > 0)
return
...
}
This works fine. Also when the calls are nested, including inside the event itself.
But the CellValueChanged event is now fired too many times for no reasons.
Because I often have to cope with this pattern, I am looking for a solution that can be applicable generally for Events in UIs, not only the DataGridView.
So my question is:
What is the best tip to mask UI Events correctly and avoid unnecessary Events fires ?
CellValueChanged is not an UI event, but a property changed event. That means you can not use it to distinguish user input from programmatic change. You can always use subscriber/unsucscribe or flag+/- or BeginEdit/EndEdit-similar technique, but maybe you have to find another (better) approach. To example, in case of checkbox you can use Click event instead of Changed, because (surprise!) it will tell you when the user click it and otherwise safely change value of Checked programmatically.
In case of DataGridView easiest would be to use Changed with some flag (which will be set when edit begins and reset when ends - see, CellBeginEdit/CellEndEdit ).
You could use CellEndEdit instead of CellValueChange. I don't know what your method dgv_CellValueChanged does, just be careful that CellEndEdit is fired every time you exit the edit mode for the cell, even if its value has not been changed. This means that you have to keep trace of the current values of your cells if you don't want the method to be executed when the value doesn't change.
I would avoid events related with the mouse such as CellClick because your users could use just the keyboard.
Anyway I usually avoid this kind of problems by separating the logic from the user interface, i.e. I write a separate class which is bound to the form. Take a look at MVVM (you can implement your own version in WinForms if you want) or the good old MVC.
I ended up mixing both solutions in a very simple one. I use a counter and I only hook/unhook the events I want to mask.
EventMask valueChangedEventMask;
// In the class constructor
valueChangedEventMask = new EventMask(
() => { dgv.CellValueChanged += new DataGridViewCellEventHandler(dgv_CellValueChanged); },
() => { dgv.CellValueChanged -= new DataGridViewCellEventHandler(dgv_CellValueChanged); }
);
// The value change operation I want to hide from the event
void changeCellOperation()
{
valueChangedEventMask.Push();
...
cell.Value = myNewCellValue
...
valueChangedEventMask.Pop();
}
// The class
public class EventMask
{
Action hook;
Action unHook;
int count = 0;
public EventMask(Action hook, Action unHook)
{
this.hook = hook;
this.unHook = unHook;
}
public void Push()
{
count++;
if (count == 1)
unHook();
}
public void Pop()
{
count--;
if (count == 0)
hook();
}
}

How to handle the TextChanged event only when the user stops typing?

I have a TextBox with a TextChanged event wired up. In the end it is making a query to a SQL database, so I want to limit the number of queries.
I only want to make the query if the user hasn't pressed a key in say .. 300 milliseconds or so. If for some reason the previous query is still executing, I would need to cancel that, and then issue a new query.
Create a System.Windows.Forms.Timer and reset it (e.g. stop then start it) after every keypress. If the timer event is triggered, disable the timer.
Use the Reactive Framework to trigger on a sequence of events. I'm not sure exactly how this would work, but you can read up on it here (Reactive Extensions for .NET) and see if it will fulfill your needs. There are a bunch of examples here too: Examples. The "Throttling" example may be what you're looking for.
1) Create a timer.
2) Create a handler for the Tick event of your timer. On each tick, check to see if enough idle time has elapsed, and if it has, STOP the timer and execute the query.
3) Whenever a keypress occurs on that textbox, RESTART the timer.
Add a second actionlistener that gets called whenever the user presses any key and when it gets called save the current time to a global variable. Then whenver your TextChanged event gets called it checks to see the time difference between the global variable and the current time.
If the difference is less than 300 milliseconds then start a timer to execute the query after 300 milliseconds. Then if the user presses another key it resets the timer first.
Thanks to #Brian's idea and this answer , I came up with my own version of using a timer to handle this issue. This worked fine for me. I hope it helps the others as well:
private Timer _tmrDelaySearch;
private const int DelayedTextChangedTimeout = 500;
private void txtSearch_TextChanged(object sender, EventArgs e)
{
if (_tmrDelaySearch != null)
_tmrDelaySearch.Stop();
if (_tmrDelaySearch == null)
{
_tmrDelaySearch = new Timer();
_tmrDelaySearch.Tick += _tmrDelaySearch_Tick;
_tmrDelaySearch.Interval = DelayedTextChangedTimeout;
}
_tmrDelaySearch.Start();
}
void _tmrDelaySearch_Tick(object sender, EventArgs e)
{
if (stcList.SelectedTab == stiTabSearch) return;
string word = string.IsNullOrEmpty(txtSearch.Text.Trim()) ? null : txtSearch.Text.Trim();
if (stcList.SelectedTab == stiTabNote)
FillDataGridNote(word);
else
{
DataGridView dgvGridView = stcList.SelectedTab == stiTabWord ? dgvWord : dgvEvent;
int idType = stcList.SelectedTab == stiTabWord ? 1 : 2;
FillDataGrid(idType, word, dgvGridView);
}
if (_tmrDelaySearch != null)
_tmrDelaySearch.Stop();
}

Categories

Resources