Any easy way to alert to changes in multiple text boxes? - c#

I'm fairly new to programming so sorry if this is simple, but I might just be missing the obvious! I have the following form which is automatically populated on load from settings stored in an INI file:
The whole form is working just as I want it to apart form one small part. The 'Close' button currently just closes the form so that if any values have changed in the text boxes since the form was loaded, the changes are lost. I want to instead prompt the user to use the Save button instead otherwise the changes will be lost.
I've been trying to do something along these lines on my close button where the value of the text boxes are checked against the variable values that they were originally populated with:
private void btnClose_Click(object sender, EventArgs e)
{
if (txtName.Text != name || txtSchName.Text != schname || txtServer1.Text != svr1 etc etc etc)
{
Warn User changes will be lost, ask user if really OK to close?
if (User chooses to to close)
{
this.Close();
}
else
{
Go back to the config form;
}
}
else
{
this.Close();
}
With over 21 text fields, I was not sure if this was the most "tidy way" of checking for changes? Any pointers would be appreciated.

You just add a global boolean variable and write an handler for the TextChanged event
// Declared at the form level
private bool _modified = false;
public Form1()
{
InitializeComponent();
// This could be done in the form designer of course
// o repeated here for every textbox involved....
txtName.TextChanged += OnBoxesChanged;
......
}
private void Form1_Load(object sender, EventArgs e)
{
.....
// Code that initializes the textboxes could raise the TextChanged event
// So it is better to reset the global variable to an untouched state
_modified = false;
}
private void OnBoxesChanged(object sender, EventArgs e)
{
// Every textbox will call this event handler
_modified = true;
}
private void btnClose_Click(object sender, EventArgs e)
{
if(_modified)
{
// Save, warning, whatever in case of changes ....
}
}
Just set the event handler OnBoxesChanged for every textbox you want to trigger the condition. You could do it through the Designer or manually after the InitializeComponent call

What you are looking for is Dirty Tracking. Dirty tracking is used to track states of your control. Here is a simple reusable approach to track your controls
public class SimpleDirtyTracker
{
private Form _frmTracked;
private bool _isDirty;
public SimpleDirtyTracker(Form frm)
{
_frmTracked = frm;
AssignHandlersForControlCollection(frm.Controls);
}
// property denoting whether the tracked form is clean or dirty
public bool IsDirty
{
get { return _isDirty; }
set { _isDirty = value; }
}
// methods to make dirty or clean
public void SetAsDirty()
{
_isDirty = true;
}
public void SetAsClean()
{
_isDirty = false;
}
private void SimpleDirtyTracker_TextChanged(object sender, EventArgs e)
{
_isDirty = true;
}
private void SimpleDirtyTracker_CheckedChanged(object sender, EventArgs e)
{
_isDirty = true;
}
// recursive routine to inspect each control and assign handlers accordingly
private void AssignHandlersForControlCollection(
Control.ControlCollection coll)
{
foreach (Control c in coll)
{
if (c is TextBox)
(c as TextBox).TextChanged
+= new EventHandler(SimpleDirtyTracker_TextChanged);
if (c is CheckBox)
(c as CheckBox).CheckedChanged
+= new EventHandler(SimpleDirtyTracker_CheckedChanged);
// ... apply for other desired input types similarly ...
// recurively apply to inner collections
if (c.HasChildren)
AssignHandlersForControlCollection(c.Controls);
}
}
and in your mainform
public partial class Form1 : Form
{
private SimpleDirtyTracker _dirtyTracker;
private void Form1_Load(object sender, EventArgs e)
{
_dirtyTracker = new SimpleDirtyTracker(this);
_dirtyTracker.SetAsClean();
}
private void Form1_FormClosing(object sender, FormClosingEventArgs e)
{
// upon closing, check if the form is dirty; if so, prompt
// to save changes
if (_dirtyTracker.IsDirty)
{
DialogResult result
= (MessageBox.Show(
"Would you like to save changes before closing?"
, "Save Changes"
, MessageBoxButtons.YesNoCancel
, MessageBoxIcon.Question));
}
}

If you want to save for example usersettings you could create Settings in your Properties.
You can get them like this:
string anyProperty = WindowsFormsApplication1.Properties.Settings.Default.TestSetting;
You save them using the Save-method:
WindowsFormsApplication1.Properties.Settings.Default.TestSetting = "Hello World";
WindowsFormsApplication1.Properties.Settings.Default.Save();
You can call the Save-method after you have clicked the close-button.
For saving several string properties you could create a property of the type System.Collections.Specialized.StringCollection, so that you just create one property and not 21.

One disadvantage of dirtytracer is, that it returns "dirty" even when changes revert to original state. If You use object in DataContext as Your data model, all this task simplifies to:
bool changed = dataContext.SomeTables.GetModifiedMembers(someRow).Any();

Related

Drag an item in listview to a form

I have two forms; one of them is containing listview, and another one is just a form.
I want to make a thing :
If I drag an item in listview to a Form, a messagebox would be pop up.
and the message would be text of the item.
However I don't know why 'SelectedItem' is null. When I trace the SelectedItem, it was null.
I found I have to use MouseDown and DragDrop events, but I have no idea how to use.
First one is the listview's code :
rListCtrl.MouseDown += rListCtrl_MouseDown;
rListCtrl.DragDrop += rListCtrl_DragDrop;
private void rListCtrl_MouseDown(object sender, MouseEventArgs e)
{
StringBuilder sb = new STringBuilder();
sb.Append(radListView1.SelectedItem.ToString());
testName = sb.ToString();
}
private void rListCtrl_DragDrop(object sender, DragEventArgs e){
{
MessageBox.Show(testName);
}
radListView1 is the name of listview.
The reason why SelectedItem is null, is that the Item only gets selected when you actually perform a click, not a mere MouseDown.
You can, however, use the IndexFromPoint method to get the Item the mouse had been placed on when the MouseDown Event was evoked:
private void radListView1_MouseDown(object sender, MouseEventArgs e)
{
int index = radListView1.IndexFromPoint(e.Location);
radListView1.SelectedIndex = index;
testName = radListView1.Items[index].ToString();
}
private void rListCtrl_DragDrop(object sender, DragEventArgs e){
{
MessageBox.Show(testName);
}
Form1:
public partial class Form1 : Form
{
Form2 f = new Form2();
public Form1()
{
InitializeComponent();
}
private void Form1_Load(object sender, EventArgs e)
{
f.Show();
}
private void Form1_MouseEnter(object sender, EventArgs e)
{
if(f.data!= string.Empty)
{
MessageBox.Show(f.data);
f.data = string.Empty;
}
}
}
Form2:
public partial class Form2 : Form
{
public string data = string.Empty;
public Form2()
{
InitializeComponent();
listView1.ItemDrag += doDaragItem;
}
private void doDaragItem(Object sender, ItemDragEventArgs e)
{
data = e.Item.ToString();
}
}
Ron,
RadListView from the Telerik UI from WinForms suite handles the whole drag and drop operation by its ListViewDragDropService. Its PreviewDragOver event allows you to control on what targets the item(s) being dragged can be dropped on. The PreviewDragDrop event allows you to get a handle on all the aspects of the drag and drop operation, the source (drag) list view, the destination (target) control, as well as the item being dragged. Additional information is available in the following help article: https://docs.telerik.com/devtools/winforms/controls/listview/drag-and-drop/listviewdragdropservice
https://docs.telerik.com/devtools/winforms/controls/listview/drag-and-drop/drag-and-drop-using-raddragdropservice
You can also combine the RadDragDropService and OLE drag-and-drop functionality: https://docs.telerik.com/devtools/winforms/controls/listview/drag-and-drop/combining-raddragdropservice-and-ole-drag-and-drop
As to the specific code snippet, indeed, if you don't have a selected item in RadListView, the code in the MouseDown event won't extract the item's text. You need to get the element under the mouse and set the item as selected:
private void radListView1_MouseDown(object sender, MouseEventArgs e)
{
SimpleListViewVisualItem elementUnderMouse = this.radListView1.ElementTree.GetElementAtPoint(e.Location) as SimpleListViewVisualItem;
if (elementUnderMouse != null)
{
this.radListView1.SelectedItem = elementUnderMouse.Data ;
}
StringBuilder sb = new StringBuilder();
sb.Append(radListView1.SelectedItem.Text.ToString());
{
testName = sb.ToString();
}
MessageBox.Show(testName);
}
I hope this information helps.

How can I prevent a Form_activated event from looping?

I'd like the activated event to only run once. I've tried using an If condition but the Reload variable doesn't set to false and thus it keeps looping endlessly. Is there a way around this?
Form1.cs code:
private void Form1_Activated(object sender, EventArgs e)
{
if (Class1.Reload == true) {
Class1.Reload = false;
}
}
Class1.cs code:
public class Class1 {
public static void Refresh() { Reload = true; }
public static bool Reload { get; set; }
Just unsubscribe from the event the first time it is triggered.
private void Form1_Activated(object sender, EventArgs e)
{
this.Activated -= Form1_Activated;
// Do other stuff here.
}
While CathalMF's solution is valid, I'll post the solution I implemented, whose aim was to refresh a DatagridView when I come back to the main form.
private void Form1_Activated(object sender, EventArgs e) {
if (Class1.Reload == true) {
Activated -= Form1_Activated;
Class1.Reload = false;
//Here I implement the code to refresh a DatagridView
Activated += Form1_Activated;
}
}
Class1.cs stays the same.

Add item to listview from another form

I've stomped with a problem I've spent some hours trying to solve, with my very limited knowledge.
I have a listview in my form1 called listMachine
And I have a method in form1.cs such as
private void máquinaToolStripMenuItem_Click(object sender, EventArgs e)
{
machinename open = new machinename();
open.Show();
}
machinename.cs is another form, and I use that method to open my other form, with an object called open.
the machinename button is a simple form which just serves as an input receiver, it asks a name, we have to type it into the textbox, press a button and it receives the input.
This is the code that runs when you press the button
public void buttonAceitarnome_Click(object sender, EventArgs e)
{
if (textBoxnomenova.TextLength == 0)
{
toolTipEmptyname.Show("O nome da máquina não pode estar vazio", textBoxnomenova);
}
else
{
Variables.var = textBoxnomenova.Text;
//MessageBox.Show(Variables.var); debug purpose, the messagebox does carry variables.var text
obj.listMachine.Items.Add(Variables.var); //If I change the variables.var to "test" it will NOT add the item.
this.Close();
}
}
Also, I forgot to mention my Variables.cs class, I created it because it was the only way I found to pass variables from a class to another (machinename.cs to form1.cs), but still, the items are not added into the listview.
This is my variables.cs code
public static class Variables
{
public static string var;
}
The comments I added to the code also give you some extra debug info..
I didn't want to ask for online help, but couldn't solve this on my own :(
If I were you, I would first remove the Variables class.
Then, you'r first form/class is called obj.cs, am I right? Or is it form1.cs?
I made it look like this:
public partial class obj : Form
{
public static string text; //This is a variable that can be reached from
public obj()
{
InitializeComponent();
}
private void máquinaToolStripMenuItem_Click(object sender, EventArgs e)
{
machinename open = new machinename();
open.ShowDialog(); //I put ShowDialog instead of Show
addItem(); //This method is called when the showed dialog is closed (machinename.cs)
}
private void addItem()
{
listMachine.Items.Add(text);
}
}
and the machinename.cs class like this:
public partial class machinename : Form
{
public machinename()
{
InitializeComponent();
}
private void buttonAceitarnome_Click(object sender, EventArgs e) //This one can be private
{
if (textBoxnomenova.TextLength == 0)
{
//Something here
}
else
{
obj.text = textBoxnomenova.Text; //Initializing the public static variable
this.Close(); //Closes the form, next step will be to run the method in obj.cs
}
}
}
If I understood your question correctly, you wanted to add an item to the ListView called "listMachine" via a button in the form "machinename.cs". This code will do that. I hope it helps you.
Change the click event from private to protected.
protected void máquinaToolStripMenuItem_Click(object sender, EventArgs e)

How can I init items of a tab?

I have a form with 2 tabs on it. I can chose the tab viewed after initialization and I need some initial code every time after the tab2 is initialized:
public partial class SetupComponent : Form
{
public SetupComponent(bool tab2)
{
InitializeComponent();
if (tab2)
{
this.tabControl1.SelectedTab = tabPage2;
}
}
private void tabControl1_SelectedIndexChanged(object sender, EventArgs e)
{
textBox1.SelectionStart = textBox1.Text.Length;
textBox1.ScrollToCaret();
textBox2.SelectionStart = textBox2.Text.Length;
textBox2.Focus();
}
}
if I call this class with tab2=false and then click onto tab2, tabControl1_SelectedIndexChanged is called.
But if I select the tab2=true during SetupComponent, I find no possibility to do that code. All the TabControl1_Events I found are too early and I don`t find a matching TabPage2_Event.
How can I manage it?
I managed this problem using the Paint_Event:
bool activated = false;
private void tabPage2_Paint(object sender, PaintEventArgs e)
{
if (!activated)
{
tabControl1_SelectedIndexChanged(null, null);
activated = true;
}
}
I use the variable because the Paint_Event is called many times.

How to check from which button I called form in C#?

I have "formA" and 2 buttons on it (button1 and button2). What I want to do is:
When I click on button1 to call "formB" display text written in label1.
When I click button2 to call the same form ("formB") hide label1 and display label2.
The problem is that I don't know how to check what button is clicked on "formA".
Edit: Thanks very much folks for the quick answer. Problem is solved!
This is where events come in handy:
public class FormA
{
private void button1_Click(object sender, EventArgs e)
{
formB.Button1WasClicked();
}
private void button2_Click(object sender, EventArgs e)
{
formB.Button2WasClicked();
}
}
public class FormB
{
public void Button1WasClicked()
{
label2.Visible = false;
label1.Visible = true;
label1.Text = "Button 1 was clicked!";
}
public void Button2WasClicked()
{
label1.Visible = false;
label2.Visible = true;
label2.Text = "Button 2 was clicked!";
}
}
button1 and button2 have their own separate Click event handlers. This way we can differentiate the two when they are clicked.
If you have the same event handler for both buttons (as mentioned in one of the comments), you can identify them with the sender parameter using:
Object.ReferenceEquals(sender, button1);
or
Object.ReferenceEquals(sender, button2);
Then your code would look like this:
private void button_Click(object sender, EventArgs e)
{
if (Object.ReferenceEquals(sender, button1))
{
formB.Button1WasClicked();
}
else
{
formB.Button2WasClicked();
}
}
FormB can't find out, the buttons are a private implementation details of FormA. They might not even be a button, surely you are going to add a menu or a toolbar to FormA some day.
The workaround becomes much simpler if you stop thinking of "calling a form". You never call a form, you create an instance of it. And then you make it visible by calling its Show() method. Lots of things you can do in between those two steps.
Add a public method to FormB. For lack of a better name:
public void MakeLabel2Visible() {
this.label1.Visible = false;
this.label2.Visible = true;
}
Now it becomes simple. Implement button2's Click event handler like this:
private void button2_Click(object sender, EventArgs e) {
var frm = new FormB();
frm.MakeLabel2Visible();
frm.Show();
}
Adding another constructor to a form that lets you initialize it differently is another very common approach. These are just classes, standard programming techniques are appropriate.
Because you are using winforms you can do all this very easily due to the fact that you have a stateful environment.
Assuming a very basic set up with:
event handlers in the code behind of form a
a reference to an instance of form b in form a (or the button click creating such an instance)
a method to use in form b to pass it data
Your code will be something like this:
public partial class FormA : Form
{
private FormB formB;
public FormA()
{
InitializeComponent();
}
private void button1_Click(object sender, EventArgs e)
{
if (formB == null || formB.IsDisposed)
{
formB = new FormB();
}
formB.UpdateLabel("Button A");
formB.Show();
}
private void button2_Click(object sender, EventArgs e)
{
if (formB == null || formB.IsDisposed)
{
formB = new FormB();
}
formB.UpdateLabel("Button B");
formB.Show();
}
}
public partial class FormB : Form
{
public FormB()
{
InitializeComponent();
}
public void UpdateLabel(string message)
{
label1.Text = message;
}
}
Of course, there are lots of improvements to this - using events and alerts more intelligently and refactoring to remove duplication, but this is a basic example of the sort of things you can do.

Categories

Resources