I am writing on c# with visual interface. Part of the logic consists of a set of values changing depended on which one of them (sets) is selected in the combo box.
Changes in the value sets can be saved or not. There is a need to offer an opportunity for user to save unsaved changes or reject them when he chooses a different item (set) in the combo box. It is imperative that when the message box with yes/no is presented combo box still displayed the old value, and only after that, depending on user's choice displayed new or old.
The sequence should be:
user uses keys or drop-down to select new item -> event is fired and form stops all of its processing -> my code cancels the change or lets it go through -> (if not cancelled) combo box is redrawn with new value.
N.B. Following events were tried and proved not to be adequate:
SelectedIndexChanged
SelectedValueChanged
SelectionChangeCommitted
Validating
DropDownClosed
I think this is what you are after - whenever the value is changed the user is prompted OK/Cancel. The only limitation with this is that the combobox shows the new value while the message box is displayed and until the user clicks Cancel. Maybe by intercepting the paint messages you can prevent this.
class MyCombo : ComboBox
{
// Keep track of the previous value
int previousIndex = 0;
// Determines whether the OnSelectedIndexChanged is ignored
bool ignoreChangedEvent = false;
/// <summary>
/// Raises the <see cref="E:System.Windows.Forms.ComboBox.SelectedIndexChanged"/> event.
/// </summary>
/// <param name="e">An <see cref="T:System.EventArgs"/> that contains the event data.</param>
protected override void OnSelectedIndexChanged(EventArgs e)
{
if (!ignoreChangedEvent)
{
// Prompt the user to see if they really want to change.
if (MessageBox.Show("Change value?", Application.ProductName, MessageBoxButtons.OKCancel) == DialogResult.Cancel)
{
ignoreChangedEvent = true;
base.SelectedIndex = previousIndex;
}
else
{
previousIndex = base.SelectedIndex;
}
}
else
{
ignoreChangedEvent = false;
}
base.OnSelectedIndexChanged(e);
}
}
I have found somewhat acceptable solution, although not directly corresponding to the verbatium of the question asked.
One possible solution, though, sadly, for some reason deleted by his suggestor, was to use message filters. That, however, led to the path of manually calculating where the mouse click went and essentially substituting the winforms capabilities of translating the mouse events to the process of changing selected item in dropped list of the combo box with some crude crutches on my own. This is the path i shied from.
In the end i settled on "cosmetic" solution, with the idea being substitution of displayed text in combo box for the duration of user's decision-making on the subject of whether or not cancel the change.
So, in the SelectedIndexChanged event i've put the follofing code:
try
{
if (MyDataSets.Current.HasChanges() && !MyDataSets.Current.Name.Equals(cbChosenDataSet.Value))
{
cbChosenDataSet.DropDownStyle = ComboBoxStyle.DropDown;
cbChosenDataSet.Text = MyDataSets.Current.Name + ' ';
Application.DoEvents();
}
else return;
/*
* UserChoseToCancel is set according to user's choice
*/
if (UserChoseToCancel)
cbChosenDataSet.Value = MyDataSets.Current.Name;
else
MyDataSets.SetCurrent(cbChosenDataSet.Value);
/*
* other things
*/
}
catch(Exception e) {/* handling */}
finally
{
cbChosenDataSet.DropDownStyle = ComboBoxStyle.DropDownList;
}
The gist of the idea is this: in DropDown style ComboBox' text can be changed as needed. However, when set to one of the items in the list, a change of selection will occur. To avoid that unneededly, a space is added to the temporary text.
In case cancelling does not occurs, restoring of the style to DropDownList forces the text to be changed to the actual chosen value (which has remained the same).
In case user cancels the change, value of the combo box is set back to the old one. A check in the beginning of the handler stops the event generated by that from being processed further.
Related
I have a Winforms app where a particular textbox field ("Phone Number") gets updated entirely programmatically, as the result of a user search on another form or as queries to a database for the overall main form's (saved) data.
We'd like the textbox to display with a red background whenever the data fits certain situations (blank is one of them, but there's also another string that can show up that we need to treat as "blank"). So I rigged this up on a TextChanged event handler.
However, sometimes the user will press a "Clear" button to blank out the Person data/fields on this form, including this Phone Number textbox. And in that case, we don't want a blank to show up red. So I adjusted the TextChanged event handler to account for this. OK, so far, so good.
Yet if they have done a Clear and now another search takes place, dumping its results back into the field, if the updated data is empty string or null... well... the TextChanged event won't fire because the VALUE is not changing. It already was Null/Empty. Yet in this situation, we'd WANT what I've got in the TextChanged event handler to fire.
I can't use the Validating Event Handler, because that only engages when a USER provides the input (I think?)
So far, my work-around has been to FORCE the event handler to fire after we're basically updating that field at the end of a search (possibly updating Null/Empty with Null/Empty). And this works. But it seems like there ought to be a better way. ??
I didn't see another event handler on that control that seemed to do what I'm looking for, but I thought I'd ask the crowd.
Thanks!
Apparently you want a special kind of TextBox, one that fires the event whenever property Text is set, even if this doesn't lead to a change.
A special kind of TextBox?Sounds like a derived class:
class MyTextBox : TextBox
{
public override string Text
{
get => base.Text;
set
{
// if no change, only call OnTextChanged, otherwise call base.Text
if (this.Text.Equals(value))
{
base.OnTextChanged(EventArgs.Empty);
}
else
{
base.Text = value;
}
}
}
}
If desired, use a stringComparer, like OrdinalIgnoreCase.
public IEqualityComparer<string> TextComparer {get; set;} = StringComparer.OrdinalIgnoreCase;
public override string Text
{
get => base.Text;
set
{
if (this.TextComparer.Equals(this.Text, value))
...
The disadvantage of method is that base.Text = value will check for equality again. If you don't wan't this, look at the source code of TextBox
public string Text
{
get { return (string) GetValue(TextProperty); }
set { SetValue(TextProperty, value); }
}
If you click on SetValue, you will jump deep inside windows. I'm not sure if you ever want to get there, given the fact that you won't update the Text property several times per second.
I have tried to get an answer to this but so far no help has been able to do what I want it to.
I have this piece of code, which is meant to look at the selected row and output it's columns into the corresponding text boxes.
private void DataGridView01_SelectionChanged(object sender, EventArgs e)
{
if (DataGridView01.SelectedRows.Count > 0)
{
personIDTextBox.Text = DataGridView01.SelectedRows[0].Cells[0].Value.ToString();
comboBox1.Text = DataGridView01.SelectedRows[0].Cells[1].Value.ToString();
Txt_FirstName.Text = DataGridView01.SelectedRows[0].Cells[2].Value.ToString();
mIDDLENAMETextBox.Text = DataGridView01.SelectedRows[0].Cells[3].Value.ToString();
sURNAMETextBox.Text = DataGridView01.SelectedRows[0].Cells[4].Value.ToString();
cITYTextBox.Text = DataGridView01.SelectedRows[0].Cells[5].Value.ToString();
eMAILTextBox.Text = DataGridView01.SelectedRows[0].Cells[6].Value.ToString();
}
}
When I launch the program, I get no errors but it doesn't output the data into the textbox. Anyone know what I am doing wrong?
HOOKING UP EVENTS:
It is the most basic thing you need to learn to code in VS. In short it means that the event name, here DataGridView01_SelectionChanged is connected to the event. To do so one can either use code or one inserts it into the correct slot of the events pane of the property tab. Select the DataGridView, open the events pane (the one with the flash) and locate the SelectionChanged event! Here insert the name of the event and you are done.
(I only have the German versions of VS installed..)
The result is reflected in the form_designer.cs file and it is the same thing (in reverse) as double clicking that spot and then filling in the generated code stub..
Controls have many events; one is the default event and this can be generated by double clicking the control itself in the designer. But eventually you will need all 3 ways to generate and hook up the events, (as well as sometimes removing them.)
I use a slightly different approach when trying to get data from a datagridview.
Try doing personIDTextBox.Text = DataGridView01.SelectedCells[0].Value.ToString();
but instead of the event being on selection change, switch to CellClick and change the property of the the datagridview row selection property to full row select. after that you can change the SelectedCell[0] number to match whichever cell you want
If you want to display the datagridview selected rows into corresponding textboxes, fine the below steps ,
Step 1:
1. Change the DataGridView Selection mode to FullRowSelect in Datagridview property.
2. Create the cell click event in Data grid view using property.
enter image description here
3. Write the below code and test it, It may helpful
private void DataGridView01_CellClick(object sender,DataGridViewCellEventArgs e)
{
if (DataGridView01.Rows.Count > -1)
{
PersonIdTextBox.Text=DataGridView01.Rows[e.RowIndex].Cells[0].Value.ToString();
comboBox1.Text = DataGridView01.Rows[e.RowIndex].Cells[1].Value.ToString();
Txt_FirstName.Text = DataGridView01.Rows[e.RowIndex].Cells[2].Value.ToString();
mIDDLENAMETextBox.Text = DataGridView01.Rows[e.RowIndex].Cells[3].Value.ToString();
sURNAMETextBox.Text = DataGridView01.Rows[e.RowIndex].Cells[4].Value.ToString();
cITYTextBox.Text = DataGridView01.Rows[e.RowIndex].Cells[5].Value.ToString();
eMAILTextBox.Text = DataGridView01.Rows[e.RowIndex].Cells[6].Value.ToString();
}
}
I have tried to get an answer to this but so far no help has been able to do what I want it to.
I have this piece of code, which is meant to look at the selected row and output it's columns into the corresponding text boxes.
private void DataGridView01_SelectionChanged(object sender, EventArgs e)
{
if (DataGridView01.SelectedRows.Count > 0)
{
personIDTextBox.Text = DataGridView01.SelectedRows[0].Cells[0].Value.ToString();
comboBox1.Text = DataGridView01.SelectedRows[0].Cells[1].Value.ToString();
Txt_FirstName.Text = DataGridView01.SelectedRows[0].Cells[2].Value.ToString();
mIDDLENAMETextBox.Text = DataGridView01.SelectedRows[0].Cells[3].Value.ToString();
sURNAMETextBox.Text = DataGridView01.SelectedRows[0].Cells[4].Value.ToString();
cITYTextBox.Text = DataGridView01.SelectedRows[0].Cells[5].Value.ToString();
eMAILTextBox.Text = DataGridView01.SelectedRows[0].Cells[6].Value.ToString();
}
}
When I launch the program, I get no errors but it doesn't output the data into the textbox. Anyone know what I am doing wrong?
HOOKING UP EVENTS:
It is the most basic thing you need to learn to code in VS. In short it means that the event name, here DataGridView01_SelectionChanged is connected to the event. To do so one can either use code or one inserts it into the correct slot of the events pane of the property tab. Select the DataGridView, open the events pane (the one with the flash) and locate the SelectionChanged event! Here insert the name of the event and you are done.
(I only have the German versions of VS installed..)
The result is reflected in the form_designer.cs file and it is the same thing (in reverse) as double clicking that spot and then filling in the generated code stub..
Controls have many events; one is the default event and this can be generated by double clicking the control itself in the designer. But eventually you will need all 3 ways to generate and hook up the events, (as well as sometimes removing them.)
I use a slightly different approach when trying to get data from a datagridview.
Try doing personIDTextBox.Text = DataGridView01.SelectedCells[0].Value.ToString();
but instead of the event being on selection change, switch to CellClick and change the property of the the datagridview row selection property to full row select. after that you can change the SelectedCell[0] number to match whichever cell you want
If you want to display the datagridview selected rows into corresponding textboxes, fine the below steps ,
Step 1:
1. Change the DataGridView Selection mode to FullRowSelect in Datagridview property.
2. Create the cell click event in Data grid view using property.
enter image description here
3. Write the below code and test it, It may helpful
private void DataGridView01_CellClick(object sender,DataGridViewCellEventArgs e)
{
if (DataGridView01.Rows.Count > -1)
{
PersonIdTextBox.Text=DataGridView01.Rows[e.RowIndex].Cells[0].Value.ToString();
comboBox1.Text = DataGridView01.Rows[e.RowIndex].Cells[1].Value.ToString();
Txt_FirstName.Text = DataGridView01.Rows[e.RowIndex].Cells[2].Value.ToString();
mIDDLENAMETextBox.Text = DataGridView01.Rows[e.RowIndex].Cells[3].Value.ToString();
sURNAMETextBox.Text = DataGridView01.Rows[e.RowIndex].Cells[4].Value.ToString();
cITYTextBox.Text = DataGridView01.Rows[e.RowIndex].Cells[5].Value.ToString();
eMAILTextBox.Text = DataGridView01.Rows[e.RowIndex].Cells[6].Value.ToString();
}
}
I am developing a Kinect game in C# where the user needs to click 2 buttons at the same time by hovering over one button with each hand.
However, with my current code, when the user hovers with one hand over a button, the other buttons get disabled and the other hand can only click when the first hand stops hovering over a button.
To solve this, I'm thinking of queuing the second click while the first click is being processed. To do this, I have used the following code based on this link
private Queue<System.Windows.Controls.Button> Button_Queue = new Queue<System.Windows.Controls.Button>();
private bool isProcessing = false;
private void Button_Click((object sender, RoutedEventArgs e){
if(isProcessing){
Button_Queue.Enqueue(this);
}
else
{
isProcessing = true;
// code here
isProcessing = false;
while(Button_Queue.Count > 0){
Button_Queue.Dequeue().PerformClick();
}
}
However, the Button_Queue.Enqueue(this) line shows the error
"The best overloaded method match for Queue.Enqueue has invalid
arguments."
I'm guessing this is because the button click event cannot be queued in a queue declared with the type Button.
Do you have any suggestions for how to create this queue of button click events or another way to handle multiple clicks from the user?
You don't need to queue the event. If isProcessing is true, then the other button was already clicked, so you can handle the event for both button clicks from that point on.
You could measure the time between the two clicks, to work out if it validates as a "two buttons clicked at the same time" event.
Have you considered a more low-level approach? The first thing that came to mind was to create two hot areas instead of buttons and monitor whether the user's hands are inside those areas at the same time.
It is unclear to me why another button is disabled when you hand is hovering over another object. Without seeing the code, I would say that you are doing something that would cause that -- and there is no reason to.
Additionally, you should be using interaction concepts centered around a gesture system and not something that is written for a mouse/keyboard input. Using regular UI objects and interacting with them in ways that parallel traditional inputs will only serve to confuse the users.
Have a look at the following two examples, which use a "hover-to-click" and a "press-to-click" interaction
Basic Interaction, for SDK 1.6
Control Basics, for SDK 1.7 (in Kinect for Windows Developer Toolkit)
In both cases, you are using a hit test on custom controls to handle events. Here is an example of a hit test function I use in one of my apps:
private void HitTestHand(HandPosition hand)
{
// quick fix to null pointer exception on exit.
if (Application.Current.MainWindow == null)
return;
Point pt = new Point(hand.X, hand.Y);
IInputElement input = Application.Current.MainWindow.InputHitTest(pt);
if (hand.CurrentElement != input)
{
var inputObject = input as DependencyObject;
var currentObject = hand.CurrentElement as DependencyObject;
// If the new input is a child of the current element then don't fire the leave event.
// It will be fired later when the current input moves to the parent of the current element.
if (hand.CurrentElement != null && Utility.IsElementChild(currentObject, inputObject) == false)
{
// Raise the HandLeaveEvent on the CurrentElement, which at this point is the previous element the hand was over.
hand.CurrentElement.RaiseEvent(new HandInputEventArgs(HoverDwellButton.HandLeaveEvent, hand.CurrentElement, hand));
}
// If the current element is the parent of the new input element then don't
// raise the entered event as it has already been fired.
if (input != null && Utility.IsElementChild(inputObject, currentObject) == false)
{
input.RaiseEvent(new HandInputEventArgs(HoverDwellButton.HandEnterEvent, input, hand));
}
hand.CurrentElement = input;
}
else if (hand.CurrentElement != null)
{
hand.CurrentElement.RaiseEvent(new HandInputEventArgs(HoverDwellButton.HandMoveEvent, hand.CurrentElement, hand));
}
}
Notice that an event is being fired on the element below the hand cursor. Examples of these elements can be found in the two links above (the HoverDwellButton is what I use with the above code sample).
Two events on two different elements, or the same element, can fire at any time with this. You can easily keep track of which user is over which button, if that button is in the process of being pressed, or if it has been pressed.
The key to all this is not using a UI paradigm that isn't designed for gesture systems! Don't try to shoehorn the keyboard/mouse event structure into a gesture based system -- it will only cause you more pain in the long run and cause your users confusion.
How do I implement a Copy menu item in a Windows application written in C#/.NET 2.0?
I want to let the user to mark some text in a control and then select the Copy menu item from an Edit menu in the menubar of the application and then do a Paste in for example Excel.
What makes my head spin is how to first determine which child form is active and then how to find the control that contains the marked text that should be copied to the clipboard.
Help, please.
With the aid of some heavy pair programming a colleague of mine and I came up with this, feel free to refactor.
The code is placed in the main form. The copyToolStripMenuItem_Click method handles the Click event on the Copy menu item in the Edit menu.
/// <summary>
/// Recursively traverse a tree of controls to find the control that has focus, if any
/// </summary>
/// <param name="c">The control to search, might be a control container</param>
/// <returns>The control that either has focus or contains the control that has focus</returns>
private Control FindFocus(Control c)
{
foreach (Control k in c.Controls)
{
if (k.Focused)
{
return k;
}
else if (k.ContainsFocus)
{
return FindFocus(k);
}
}
return null;
}
private void copyToolStripMenuItem_Click(object sender, EventArgs e)
{
Form f = this.ActiveMdiChild;
// Find the control that has focus
Control focusedControl = FindFocus(f.ActiveControl);
// See if focusedControl is of a type that can select text/data
if (focusedControl is TextBox)
{
TextBox tb = focusedControl as TextBox;
Clipboard.SetDataObject(tb.SelectedText);
}
else if (focusedControl is DataGridView)
{
DataGridView dgv = focusedControl as DataGridView;
Clipboard.SetDataObject(dgv.GetClipboardContent());
}
else if (...more?...)
{
}
}
Why not extending the control, so the control itself provides the data which should be copied into the clipboard.
Take a look at ApplicationCommands documentation.
To determine which window is open, you can query the Form.ActiveMDIChild property to get a reference to the currently active window. From there, you can do one of two things:
1) If you create your own custom Form class (FormFoo for example) that has a new public member function GetCopiedData(), then inherit all of your application's child forms from that class, you can just do something like this:
((FormFoo)this.ActiveMDIChild).GetCopiedData();
Assuming the GetCopiedData function will have the form-specific implementation to detect what text should be copied to the clipboard.
or
2) You can use inheritance to detect the type of form that is active, and then do something to get the copied data depending on the type of form:
Form f = this.ActiveMDIChild;
if(f is FormGrid)
{
((FormGrid)f).GetGridCopiedData();
} else if(f is FormText) {
((FormText)f).GetTextCopiedData();
}
etc.
That should get you started with finding the active window and how to implement a copy function. If you need more help copying out of a GridView, it may be best to post another question.
If the form is tabbed and the target control is a DataGridView, it's sometimes possible for the Form's TabControl to be returned as the active control, using the above method, when the DataGridView is right clicked upon.
I got around this by implementing the following handler for my DataGridView:-
private void dataGridView_CellMouseDown(object sender, DataGridViewCellMouseEventArgs e)
{
if (e.Button == MouseButtons.Right)
{
dataGridView.Focus();
dataGridView.CurrentCell = dataGridView[e.ColumnIndex, e.RowIndex];
}
}
It seems to me that you might be better off breaking this into smaller tasks/questions.
You have a few issues you are stuck on from the way it sounds.
You have multiple 'child' windows open. Is this an MDI application?
When an action is performed on one of those child windows, it should fire an event in that window's event handlers. That is your first thing to set up. If this is a datagridview I would suggest a simple test to start. Try trapping the DataGridView.SelectionChanged event. Just throw in something like MessageBox.Show("I copied your datas!"); for now.
This should get you started where you will at least understand how this event will be raised to you.
From here, we will need to know a little more about your datagrid, and the rows and child controls in those rows. Then we can likely create events in the render events that will be raised at the appropriate times, with the appropriate scope.