Consider a form with a dataGridView bound to a strongly typed dataset. A combo box exists on the top to allow the user to select an employee. Once selected, the gataGridView's dataset is filled based on the employee's ID.
Some data entry is done on the dataGridView and once completed the user saves. In order to keep the user from accidently moving to another employee and deleting what they keyed, I prompt them to save is the dataset HasChanges() is true.
I was catching this piece of code in the SelectedIndexChanged event of the combo box but this also raises the event in the SelectedValueChanged
private void checkSaveChanges()
{
this.Validate();
this.laborTicketBindingSource.EndEdit();
if (dP_LaborTicket.HasChanges())
{
DialogResult result = MessageBox.Show("Do you want to save your changes?", "Save before closing?", MessageBoxButtons.YesNo, MessageBoxIcon.Question, MessageBoxDefaultButton.Button2);
if (result == DialogResult.Yes)
this.tableAdapterManager.UpdateAll(this.dP_LaborTicket);
}
}
Here is a snippet of the event that gets raised:
private void cmbEmployeeID_SelectedIndexChanged(object sender, EventArgs e)
{
checkSaveChanges();
if (cmbTransactionDate != null && cmbEmployeeID.SelectedValue != null)
{
string selectedDate = cmbTransactionDate.Value.ToString("MM/dd/yyyy");
string selectedEmployee = cmbEmployeeID.SelectedValue.ToString();
this.laborTicketTableAdapter.Fill(this.dP_LaborTicket.LaborTicket, selectedEmployee, selectedDate);
}
}
So stepping into this once I change a record and move to another employee, it steps into checkSaveChanges. Successfully validates and ends the edit and checks if it has changes (which it does).
After executing MessageBox.Show, the program is suddenly sent back to the cmbEmployeeID_SelectedIndexChanged event, where it calls checkSaveChanges yet again. This only happens twice though, and even if they say yes or no, the code within the prompting block runs.
How in the world is MessageBox.Show calling this twice? If I comment that line out this does not happen...
EDIT
As a bonus piece of information, the combo box AutoCompleteMode was set to SuggestAppend (Shows a dropdown as your typing) and DropDownStyle is set to DropDownList.
I changed the AutoCompleteMode to Append and this doesn't happen anymore, it seems to only happen with the SuggestAppend Mode...
I think Sven could be right that the MessageBox.Show will cause the dropdown to loose focus.
One way to work around this problem could be to detect if the event is already executing using a private boolean in the class. You'd then get something like this:
private bool fIsInEvent = false;
private void cmbEmployeeID_SelectedIndexChanged(object sender, EventArgs e)
{
if (!fIsInEvent)
{
fIsInEvent = true;
try
{
// your code
}
finally { fIsInEvent = false; }
}
}
Related
I would like to ask the user when he/she clicks the closebutton: save the file, discard the changes, or go back IF the RichTextBox content changed. Like Windows Notepad or any other text editor does. How could I do that?
You need to read about events and how they work. In this case, you are interested in the TextChanged event of the RichTextBox and the FormClosing event of the form.
TextChanged Event : MSDN
The TextChanged event is raised whenever the contents of the textbox are modified. One way to track changes in the textbox is simply to use a boolean value. Be sure to set it to false when you are loading data into the textbox. Then, when the user changes the text the TextChanged event will fire and you can set the _textChanged (in the example below) value to true.
Similarly, making use of the FormClosing event allows you to react to a user attempting to close the form.
FormClosing Event : MSDN
This event passes a FormClosingEventArgs value e that allows you to cancel the closing of the form (in this case if the user selects to cancel when prompted about the text having been changed). It also allows you to perform any other action before the form is closed.
To create the message dialog you can use an appropriate overload of MessageBox.Show - this function returns a DialogResult indicating which of the buttons the user clicked. This allows you to take different actions depending on the selection the user made.
MessageBox.Show : MSDN
If you don't know how to connect these events, then I suggest you read through some of the basic documentation and examples. This is pretty elementary stuff that you will need to understand to get much of anything done in C#. The examples here are winforms since you have not indicated otherwise.
This is a trivial example :
public partial class Form1 : Form
{
private bool _textChanged;
public Form1()
{
InitializeComponent();
// load data to richtextbox, then ....
_textChanged = false;
}
private void richTextBox1_TextChanged(object sender, EventArgs e)
{
_textChanged = true;
}
private void Form1_FormClosing(object sender, FormClosingEventArgs e)
{
if (_textChanged)
{
DialogResult rslt = MessageBox.Show("save changes?", "some caption",
MessageBoxButtons.YesNoCancel);
if (rslt == DialogResult.Yes)
{
// save changes and exit
}
else if (rslt == DialogResult.Cancel)
{
e.Cancel = true;
// cancel close, return to form
}
// else do not save and continue closing form
}
}
}
I have a Winform application I'm modifying for a friend. It has a listview and wants me to add a checkbox to each row and make them mutually exclusive. So in my testing of how to make it work I found a strange behavior and as hopping someone could tell me what I'm missing.
If I display the List view with no checkbox checked. When I click right on the checkbox I cannot get it to check, but the row dose get selected. If I click on the item (the name in this case) in the column it does get checked and selected.
No matter where I click on a row, any checkboxes in rows not selected will be uncheck. Here is my little test program. I’m using .NET 4
public partial class Form1 : Form {
public Form1() {
InitializeComponent();
}
private void Form1_Load(object sender, EventArgs e) {
TestListView.Items.Add("Bob");
TestListView.Items.Add("Ann");
TestListView.Items.Add("Frank");
}
void TestListView_MouseClick(object sender, System.Windows.Forms.MouseEventArgs e) {
ListViewItem currentItem = TestListView.GetItemAt(e.X, e.Y);
if (currentItem != null) {
foreach (ListViewItem item in TestListView.Items) {
if (item.Text == currentItem.Text) {
item.Checked = true;
item.Selected = !currentItem.Selected;
}
else
item.Checked = false;
}
}
}
}
Seems that WinForms is checking the checkbox when you click on the checkbox directly, and then your code immediately undoes the checking, so you never see it.
Perhaps instead of MouseClick you should use the ItemCheck or ItemChecked event. The first is fired before the Checked property changes, the second after.
I'm currently having a very similar issue as well, however in response to Timwi it's not the code doing the unchecking. I've been stepping through it very slowly and as the code fires when clicking on a checkbox, it states that it has checked it. But when the form resumes, it is unchecked again. After reading Timwi post, he lead me onto the answer. It's not the code doing the unchecked, but the winforms event firing afterwards that unchecks the box. This fixes it:
My code is:
private bool allowCheck = false;
private bool preventOverflow = true;
private void lstvwRaiseLimitStore_MouseClick(object sender, MouseEventArgs e)
{
preventOverflow = false;
ListViewItem item = lstvwRaiseLimitStore.HitTest(e.X, e.Y).Item;
if (item.Checked)
{
allowCheck = true;
item.Checked = false;
}
else
{
allowCheck = true;
item.Checked = true;
}
}
private void lstvwRaiseLimitStore_ItemChecked(object sender, ItemCheckedEventArgs e)
{
if (!preventOverflow)
{
if (!allowCheck)
{
preventOverflow = true;
e.Item.Checked = !e.Item.Checked;
}
else
allowCheck = false;
}
}
So what it is doing, first I have to set a flag to prevent overflow, otherwise when the form is built or when you uncheck windows checking the box, it keeps looping the code and will eventually stack overflow. Next flag is the actual allow checking of the checkbox via your code and not via another method.
Clicking on them item, it locates where the click was and then sets the flag to allow a check to be done. The code then checks the box and the item checked section kicks off, becuase it was done by our code, it does nothing but reset the allowcheck flag.
If you clicked on a line, it does nothing else, however if it was a checkbox, at the end of our code, the Windows function kicks off and tries to check the box, becuase the allowcheck flag is false, the code first sets a flag to say I'm already preventing a check and then resets the check back to it's intial status. Becuase this is done, the itemchecked event kicks off again, but the code has set the flag to prevent it from doing anything. Then the code finishes and it has prevented windows from checking the check box and only allowed our code.
Hope it helps!
Here is the problem: I have a Windows Forms application that I'm developing, and in one segment I'm using a ListView control.
What I'm trying can be simply stated as: on event ListViewItemSelectionChange show a MessageBox for user to confirm the change, if not confirmed change to let's say the first item. This change to the first item would again fire ListViewItemSelecionChange, so I unregister and re-register the event handler method, so everything should be good, right?
What actually happens is that the handler method is called twice (actually ListView should fire two events on Selection change, one for deselect, other for newly selected item, but I have an e.IsSelected statement at the beginning to catch only selected items, so actually you could say that there are four events fired).
The problem is, if I generated the first event with mouse click on ListView item, and I've unsubscribed before programatically changing to the first item, what generates the second event firing? Is it some focus change because of the MessageBox call? Is there any way to prevent the second event to fire?
I have a simple example solution here, it can't be more simlified (25 SLOC), so if you can, please take a look. Note that commenting the line "if (ShowMessageBox())" stops the second event from firing, is this some focus change problem?
http://www.filedropper.com/listviewtestwithmsgbox
Edit: the relevant code:
private void listViewWithSelection1_ItemSelectionChanged(object sender, ListViewItemSelectionChangedEventArgs e)
{
// listview actually generates two ItemSelectionChanged events,
// one for deselect of a item, and another event for a newly selected item (which we want here).
if (e.IsSelected)
{
if (ShowMessageBox())
Button1_Click(null, EventArgs.Empty);
label1.Text += "item selected ";
}
}
private bool ShowMessageBox()
{
return MessageBox.Show("Change to first item instead?", "test", MessageBoxButtons.YesNo) == DialogResult.Yes;
}
private void Button1_Click(object sender, EventArgs e)
{
// change ti first ListView item
listView1.ItemSelectionChanged -= listViewWithSelection1_ItemSelectionChanged;
listView1.Items[0].Selected = true;
listView1.ItemSelectionChanged += listViewWithSelection1_ItemSelectionChanged;
}
Hmm, can you describe how the selection is being changed to begin with? If it's by the user clicking to select an item, perhaps catch the Click or DoubleClick event rather than the ItemSelectionChanged event? I have this snippet I'm using on a program currently. If the user double clicks the list box (listView, in your case), do something with the selected item.
public partial class Form1 : Form
{
public Form1()
{
InitializeComponent();
}
private bool ShowMessageBox()
{
return MessageBox.Show("Change to first item instead?", "test", MessageBoxButtons.YesNo) == DialogResult.Yes;
}
private void listView1_Click(object sender, EventArgs e)
{
if (ShowMessageBox())
listView1.TopItem.Selected = true;
label1.Text += "item selected ";
}
}
Edited to include relevant code.
One way to do this is to have a flag which says should the on change code run.
In your ListViewItemSelecionChange code you check the value of the flag and run code accordingly.
I have a Windows forms application in C# and I have a form that when the user closes it I ask, "do you want to save the changes"? How can I get the changes in my form? Here is some code:
public partial class DepartEdit : Form
{
string _nameDep; //This variavel get value textbox when form load
{
InitializeComponent();
}
private void DepartamentEdit_FormClosing(object sender, FormClosingEventArgs e)
{
if (txtNameDepart.Text != _nameDep && codDepartament > 0)//Here i compare
{
DialogResult dlg = MessageBox.Show("Save changes?", "Question", MessageBoxButtons.YesNo);
if (dlg == DialogResult.Yes)
{
saveDepart(); // Metod save depart
e.Cancel = false;
}
if(dlg ==DialogResult.No)
{
e.Cancel = false;
}
}
}
There are a lot of textboxs and combo boxes? Is there any other way to get the changes in the form?
A lot will depending on where the information is held.
It you are using DataBinding you should be just monitoring the listChanged event or calling dataTable.GetChanges() if you are using a DataTable.
If the information comes from a class the implements ICloneable and IComparable, then you can take just take a backup copy when intialising the form (and after saving) and when closing you prepare you class for saving and compare it with the original.
Otherwise you can do something like
Declare a private variable
private bool requiresSaving =false;
Declare an event
private void SomethingChanged(object sender, EventArgs e)
{
requiresSaving = true;
}
Hook up this event to the various changed events, eg
this.txtNameDepart.TextChanged += new System.EventHandler(this.SomethingChanged);
(The actual event is sometimes called something different , eg ValueChanged, SelectedIndexChanged , but they can all point to SomethingChanged unless you need a particular event to do something else.)
Check this variable when you are closing the form
private void DepartamentEdit_FormClosing(object sender, FormClosingEventArgs e)
{
if (requiresSaving)
{
....
You also need to set requiresSaving false in the saveDepart method.
Alternatively I have seem code where, when the control is being intialised, the tag value is also set, and the formclosing event loops through each control and compares the original values (in the tag object) with the current values.
Create a string (or string[] I guess) within the Form_Load event and initialise them with the values present when the form first opens.
eg
string oName = nameTextBox.Text;
string oCompany = companyComboBox.Text;
Then during the Form_Closing() event you can check these against the current values
private void Contact_FormClosing(object sender, FormClosingEventArgs e)
{
if (oName!=nameTextBox.Text||oCompany!=companyComboBox.Text)
{
DialogResult result = MessageBox.Show("Would you like to save your changes",
"Save?",
MessageBoxButtons.YesNoCancel,
MessageBoxIcon.Stop);
if (result == DialogResult.Yes)
{
SaveFormValues();
}
else if (result == DialogResult.Cancel)
{
// Stop the closing and return to the form
e.Cancel = true;
}
else
{
this.Close();
}
}
}
Loop the forms controls, and add your event watchers. These events will call a function in the form that will keep a Hashtable or some other various collection up to date w/ the status of any changes to a particular control.
like I have, Hashtable changes;
then each time my event is called, i say, Add Control.Name, and then a change status, whatever you want. Then you have a list of controls that have been updated.
I can go into more detail if need be, hopefully this will get you started.
Well I think the theory is good. There are some problems with the implementation.
if (dlg == DialogResult.Yes)
{
saveDepart(); // Metod save depart
e.Cancel = false;
}
if(dlg ==DialogResult.No)
{
e.Cancel = false;
}
I think it would be a lot similier to write this:
if(dlg == DialogResult.Yes)
{
saveDepart();
}
// You don't need to change e.Cancel to false here unless you set it to true previously.
It's simple
Declare a global variable count=0
Now on your form u might already have a Save button so just increment count on save button click event
Now on form close event just check for the value of count if it's 0 ask user to save info either by reminding by message box
If it's 1 or greater than1 just close the form
I hope u get it
I have a DataGridView whose DataSource is a DataTable.
This DataTable has a boolean column, which is interpreted as a checkbox in the DataGridView.
employeeSelectionTable.Columns.Add("IsSelected", typeof(bool));
...
employeeSelectionTable.RowChanged += selectionTableRowChanged;
dataGridViewSelectedEmployees.DataSource = employeeSelectionTable;
...
private void selectionTableRowChanged(object sender, DataRowChangeEventArgs e)
{
if ((bool)e.Row["IsSelected"])
{
Console.Writeline("Is Selected");
}
else
{
Console.Writeline("Is Not Selected");
}
break;
}
When the user single-clicks on a checkbox, it gets checked, and selectionTableRowChanged will output "Is Selected."
Similarly, when the user checks it again, the box gets cleared, and selectionTableRowChanged outputs "Is Not Selected."
Here's where I have the problem:
When the user double-clicks on the checkbox, the checkbox gets checked, the RowChanged event gets called ("Is Selected"), and then the checkbox is cleared, and no corresponding RowChanged event gets called. Now the subscriber to the the RowChanged event is out of sync.
My solution right now is to subclass DataGridView and override WndProc to eat WM_LBUTTONDBLCLICK, so any double-clicking on the control is ignored.
Is there a better solution?
The reason that making an empty DoubleClick event method would not help would be that is executed in addition to the other operations that happen when a double click occurs.
If you look at the windows generated code or examples of programatically adding event handlers, you use += to assign the event handler. This means you are adding that event handler in addition to the others that already exist, you could have multiple event handlers being triggered on the save event.
My instinct would have been to override the DataGridView class, then override the OnDoubleClick method and not call the base OnDoubleClick method.
However, I have tested this real quick and am seeing some interesting results.
I put together the following test class:
using System;
using System.Windows.Forms;
namespace TestApp
{
class DGV : DataGridView
{
private string test = "";
protected override void OnDoubleClick(EventArgs e)
{
MessageBox.Show(test + "OnDoubleClick");
}
protected override void OnCellMouseDoubleClick(System.Windows.Forms.DataGridViewCellMouseEventArgs e)
{
MessageBox.Show(test + "OnCellMouseDoubleClick");
}
protected override void OnCellMouseClick(System.Windows.Forms.DataGridViewCellMouseEventArgs e)
{
if (e.Clicks == 1)
{
// Had to do this with a variable as using a MessageBox
// here would block us from pulling off a double click
test = "1 click ";
base.OnCellMouseClick(e);
}
else
{
MessageBox.Show("OnCellMouseClick");
}
}
}
}
Then inserted this into a windows form, adding a checkbox column and ran the program.
On a fresh run, double clicking on the checkbox causes the messagebox display to say "1 click OnDoubleClick".
This means that OnCellMouseClick executed on the first part of the double click and then OnDoubleClick executed on the second click.
Also, unfortunately, the removal of the call to the base methods doesn't seem to be preventing the checkbox from getting the click passed to it.
I suspect that for this approach to work it may have to be taken further and override the DataGridViewCheckBoxColumn and DataGridViewCheckBoxCell that ignores the double click. Assuming this works, you would be able to stop double click on the checkbox but allow it still on your other column controls.
I have posted an answer on another question that talks about creating custom DataGridView columns and cells at here.
In case you want a checkbox column inside a DataGridView, create something like this:
DataGridViewCheckBoxCell checkBoxCell = new MyDataGridViewCheckBoxCell();
...
DataGridViewColumn col = new DataGridViewColumn(checkBoxCell);
...
col.Name = "colCheckBox";
...
this.dgItems.Columns.Add(col);
where dgItems is DataGridView instance.
As you can see I have a MyDataGridViewCheckBoxCell class which is a subclass of the DataGridViewCheckBoxCell class. In this subclass I define:
protected override void OnContentDoubleClick(DataGridViewCellEventArgs e)
{
//This the trick to keep the checkbox in sync with other actions.
//base.OnContentDoubleClick(e);
}
When the user now double clicks a checkbox in the checkbox column the checkbox will behave as if it was single clicked. This should solve your sync problem.
This isn't exactly an elegant solution, but why not simply do the following?
private void dgv_CellDoubleClick(object sender, DataGridViewCellEventArgs e)
{
dgv_CellClick(sender, e);
}
This would explicitly force the second CellClick event and avoid out of sync.
Is there some reason it needs to be done that low level? Can the DoubleClick Method just be an empty method that eats it?
I already tried overriding the OnDoubleClick method in the DataGridView subclass to do nothing, but it still allowed the checkbox to be changed a second time.
Ideally I was looking to have the DataTable's RowChanged event get called twice.
Is there a way to affect the underlying DataTable via the overridden OnDoubleClick method?
Why not just leave IsSelected column unbounded? Use CellContentClick event to push the data to underlying datatable and leave CellDoubleClick or RowChange event alone.
private void dgv_CellContentClick(object sender, DataGridViewCellEventArgs e)
{
if(e.ColumnIndex == <columnIndex of IsSelected>)
{
string value = dgv[e.ColumnIndex, e.RowIndex].EditedFormattedValue;
if( value == null || Convert.ToBoolean(value) == false)
{
//push false to employeeSelectionTable
}
else
{
//push true to employeeSelectionTable
}
}
}
Don't know why i had to, but i was able to put in a timer tick event attached to a datagridview refresh that just refreshed the dgv after the second click.
In cell click event
** _RefreshTimer = new Timer();
_RefreshTimer.Tick += new EventHandler(RefreshTimer_Tick);
_RefreshTimer.Interval = 100;
_RefreshTimer.Start();
}
}
}
void RefreshTimer_Tick(object sender, EventArgs e)
{
dgv.Refresh();
_RefreshTimer.Stop();
_RefreshTimer = null;
}**