I'm having trouble getting the value of a textbox of a popped up dialog. I've followed the advice from other StackOverflow questions that said to create a public variable in program.cs:
public static string cashTendered { get; set; }
Then I created my dialog like this:
Cash cashform = new Cash();
cashform.ShowDialog();
And when the user presses the button on the dialog, this is called:
if (isNumeric(textBox1.Text, System.Globalization.NumberStyles.Float))
{
Program.cashTendered = textBox1.Text;
this.Close();
}
else
{
MessageBox.Show("Please enter a valid amount of cash tendered. E.g. '5.50'");
}
Yet Program.cashTendered stays null. Am I doing something wrong? Thanks!
For starters your form called Cash should use an object oriented design. It should have a public property called CashEntered or something similar of type decimal instead of string. You would call the Form like so:
using (var cashDialog = new CashDialog())
{
// pass a reference to the Form or a control in the Form which "owns" this dialog for proper modal display.
if (cashDialog.ShowDialog(this) == DialogResult.OK)
{
ProcessTender(cashDialog.CashEntered);
}
else
{
// user cancelled the process, you probably don't need to do anything here
}
}
Using a static variable to hold the results of a temporary dialog is a bad practice. Here is the better implementation of a dialog:
public class CashDialog : Form
{
public decimal CashEntered { get; private set; }
private void ok_btn_Clicked
{
decimal value;
if (Decimal.TryParse(cashEntered_txt.Text, out value))
{
// add business logic here if you want to validate that the number is nonzero, positive, rounded to the nearest penny, etc.
CashEntered = value;
DialogResult = DialogResult.OK;
}
else
{
MessageBox.Show("Please enter a valid amount of cash tendered. E.g. '5.50'");
}
}
}
On your main form that you want to get the value for, you'd have some sort of code like this;
var cashTendered;
using (var frm = new Cash())
{
if (frm.ShowDialog() == DialogResult.OK)
cashTendered = frm.GetText()
}
Then on your dialog form, you'd have something like this:
public string GetText()
{
return textBox1.Text;
}
public void btnClose_Click(object sender, EventArgs e)
{
this.DialogResult = DialogResult.OK;
this.Close();
}
public void btnCancel_Click(object sender, EventArgs e)
{
this.Close();
}
Alternatively, you can just perform those lines in the btnClose_Click event in the FormClosing event instead if you don't have a button for them to click to 'submit' their value.
Edit You might want to add some sort of validation on your textbox inside the btnClose event, such as:
decimal myDecimal;
if (decimal.TryParse(textBox1.Text, out myDecimal))
{
this.DialogResult = DialogResult.OK;
this.Close();
}
else
{
MessageBox.Show("Invalid entry", "Error");
textBox1.SelectAll();
}
Related
so I'm trying to make a simple pop-up box in which you can insert multiple different inputs
(first name, last name, etc...) info and it will be saved as a string. I tried using built-in text boxes but was unable to understand how it really works. Is there an easier way of doing so?
I'm using win forms if that's relevant.
It does not make sense to simply return a string, instead consider creating a class to represent your data and override ToString as per below to have the option to have a string representation of the data or to have properties for each element to collect.
You need to create a form (in this case UserInputForm), place labels and controls on the form to collect input. Validation may be done in this form or by the calling form.
Simple example, we want first, last name and birth date, add text boxes for first and last name and a DateTimePicker for birth date. A submit/ok button and a cancel button. For the Cancel button set DialogResult to Cancel in the property window.
Create a class to represent the data to be collected e.g.
public class Person
{
public string FirstName { get; set; }
public string LastName { get; set; }
public DateTime BirthDate { get; set; }
}
In the form to collect data. Note form level variable for returning data of type Person which the calling form uses if the sumbit button is pressed.
public partial class UserInputForm : Form
{
public readonly Person Person = new Person();
public UserInputForm()
{
InitializeComponent();
}
private void SubmitButton_Click(object sender, EventArgs e)
{
Person.FirstName = FirstNameTextBox.Text;
Person.LastName = LastNameTextBox.Text;
Person.BirthDate = BirthDateTimePicker.Value;
DialogResult = DialogResult.OK;
}
}
In the form to call the above form
private void GetPersonDetailsButton_Click(object sender, EventArgs e)
{
UserInputForm f = new UserInputForm();
try
{
if (f.ShowDialog() == DialogResult.OK)
{
MessageBox.Show($"{f.Person.FirstName} {f.Person.LastName} {f.Person.BirthDate:d}");
}
else
{
MessageBox.Show("Cancelled");
}
}
finally
{
f.Dispose();
}
}
Then there is validation which can be done in the submit button event or in the calling form.
Simple example for in form validation.
private void SubmitButton_Click(object sender, EventArgs e)
{
if (!string.IsNullOrWhiteSpace(FirstNameTextBox.Text) & !string.IsNullOrWhiteSpace(LastNameTextBox.Text) & BirthDateTimePicker.Value < DateTime.Now)
{
Person.FirstName = FirstNameTextBox.Text;
Person.LastName = LastNameTextBox.Text;
Person.BirthDate = BirthDateTimePicker.Value;
DialogResult = DialogResult.OK;
}
else
{
// present user with a dialog to correct input
}
}
And if you really want a string alter the last to override ToString
public class Person
{
public string FirstName { get; set; }
public string LastName { get; set; }
public DateTime BirthDate { get; set; }
public override string ToString() => $"{FirstName},{LastName},{BirthDate:d}";
}
Usage
if (f.ShowDialog() == DialogResult.OK)
{
MessageBox.Show(f.Person.ToString());
}
else
{
MessageBox.Show("Cancelled");
}
Full source
I am looking for a solution to not close my dialog window when there is a duplicate ID. I guess it has to do with DialogResult, however, I cannot make neither true or false work.
I am opening the "New Store"-window from a button click in my main window.
NewStoreWindow newStore = new NewStoreWindow();
newStore.ShowDialog();
As you can see on the screenshots below, you can fill in the ID and the Name. I have successfully made an input validation, so you cannot press "OK" when the 2 textboxes are empty.
When you press "OK" and the ID is already in my listview (gridview), it will give the user the below error message. However, it will also close the "New Store"-window. As said, I would like that the window is kept open until it successfully is added, so that the user can just edit the ID instead of having to open the "New Store"-window again and typing it again.
It should only close the window when it is successfully added or cancel/X is pressed.
I have tried to play around with closing-event for "New Store", but it does not seem to work.
Is this just the designed behavior? And is there a way to bypass/work around it?
CS
public partial class NewStoreWindow : Window
{
public static bool itemAlreadyAdded;
//public MainWindow mainWin = new MainWindow();
public bool IsDefault { get; set; }
public NewStoreWindow()
{
InitializeComponent();
//SetButtonState();
}
// Data Binding
public static readonly DependencyProperty SidProperty = DependencyProperty.Register("Sid", typeof(string), typeof(NewStoreWindow), new UIPropertyMetadata(String.Empty));
public string Sid
{
get { return (string)GetValue(SidProperty); }
set { SetValue(SidProperty, value); }
}
// Data Binding
public static readonly DependencyProperty SNameProperty = DependencyProperty.Register("SName", typeof(string), typeof(NewStoreWindow), new UIPropertyMetadata(String.Empty));
public string SName
{
get { return (string)GetValue(SNameProperty); }
set { SetValue(SNameProperty, value); }
}
private void cmdOK_Click(object sender, RoutedEventArgs e)
{
DialogResult = true;
MainWindow mainWin = new MainWindow();
// Check if any items exist in listview
if (MainWindow._list.Count == 0)
{
MainWindow._list.Add(new MainWindow.Data() { Sid = Sid, SName = SName });
}
else // items exist
{
itemAlreadyAdded = false; // use for boolean checking
foreach (var item in mainWin.lvStores.Items.OfType<MainWindow.Data>()) // loop through all items in listview
{
if (item.Sid == Sid) // Check if new item already exists
{
itemAlreadyAdded = true;
}
if (itemAlreadyAdded) // Show messagebox if it exists
{
MessageBox.Show("ID needs to be unique, please respecify!", "Duplicate ID",
MessageBoxButton.OK, MessageBoxImage.Error);
break;
}
}
if (!itemAlreadyAdded) // If it does not already exist, add it
{
// if it does not exist, create it from the textbox values
MainWindow._list.Add(new MainWindow.Data() { Sid = Sid, SName = SName });
// Refresh listview
mainWin.lvStores.Items.Refresh();
// Close Window
Close();
}
}
}
private void Window_Closing(object sender, System.ComponentModel.CancelEventArgs e)
{
e.Cancel = true;
this.Hide();
}
public void Close()
{
this.Closing -= Window_Closing;
base.Close();
}
I want to create a simple input dialog. so I added a Windows From to my application and in that window I've put a label, a textbox and a button; For now it's easy to fill the required proprieties of my controls with the predefined values using constructors and return the entered value with result;
public partial class GeneralInputForm : Form
{
public string result = "";
public GeneralInputForm(string label, string defaultValue = "")
{
InitializeComponent();
label1.Text = label;
textBox1.Text = defaultValue;
}
private void button1_Click(object sender, EventArgs e)
{
result = textBox1.Text;
Close();
}
}
And now creating an instance of the from and filling the parameters with the required values;
GeneralInputForm formInput = new GeneralInputForm("Please enter your age:");
formInput.ShowDialog();
//formInput.result holds the value
My questions raises here; What if I want to check for the validity of the value that the user has entered in the textbox in the GeneralInputForm itself (and not later when instantiating the form with checking the result field)? I mean is there such a functionality in C# (delegates maybe) so that the user himself can define a method and pass a it as one of the parameters of the constructor which that method checks the textbox1.text value to be sure that for example it's successfully can be parsed into an int or float or string?
I don't want to add an ugly string parameter called variableType to my constructor and ask user to say if he requires a 'int' or 'float' or 'string' or whatever and then I myself by checking the value of variableType, wrote different statements for making sure that the entered text can be successfully parsed into int or float or string (it would be an ugly and limited solution)
Change GeneralInputForm to GeneralInputForm<T>. Is a generic class, just like List<String>.
You can use two field Func one to cast the Text to the object you want and one to check the return just like this:
public partial class GeneralInputForm<T> : Form
{
public T result = default(T);
Func<T, Boolean> check = (input) => true; // Default check
Func<String, T> cast = null; // Default cast
public GeneralInputForm(string label, string defaultValue = "", Func<String, T> cast, Func<T, Boolean> check)
{
InitializeComponent();
label1.Text = label;
textBox1.Text = defaultValue;
if(check != null)
this.check = check;
this.cast = cast;
}
private void button1_Click(object sender, EventArgs e)
{
if(cast != null)
{
T casted = cast(textBox1.Text);
if(casted != null && check(casted)){
result = textBox1.Text;
Close();
}
}
}
}
And you can use it like this:
Func<String, int> cast = (input) => int.Parse(input);
Func<int, Boolean> check = (input) => input > 0;
GeneralInputForm<int> form = new GeneralInputForm<int>("Enter a number:", "1", cast, check);
form.ShowDialog();
//etc....
More info about Generics.
This way of doing thing is named Dependecy Injection, and this approach is the constructor injection type. More info.
You don't need to pass it as a parameter. The validation function can be overridden using inheritance.
public partial class GeneralInputForm : Form
{
// Returns true if this text is valid for a textbox
protected virtual bool IsValid(string text)
{
return true; // default behaviour
}
private void button1_Click(object sender, EventArgs e)
{
if (IsValid(textBox1.Text))
{
result = textBox1.Text; // ok
Close();
}
else
{
// Show error message
}
}
}
Now you can create a form which custom-validates textboxes, by overriding (ie. replacing) the IsValid() function:
public partial class SpecificInputForm : GeneralInputForm
{
// Blank textboxes are not valid, and are restricted to 31 characters
protected override bool IsValid(string text)
{
if (string.IsNullOrWhitespace(text))
return false;
return (text.Length < 32);
}
}
While #Oscar's answer would work, making forms generic is a bit of a bad idea because the Winforms designer has aversion to them (try to inherit from that form and you'll see).
I'd probably make it so that it's not the form that is generic, but a method:
public partial class GeneralInputForm : Form
{
private GeneralInputForm() // Private constructor
{
InitializeComponent();
}
public static bool TryGetValue<T>(
string label,
string defaultValue,
Func<T, bool> check,
out T result
) where T : IConvertible
{
result = default(T);
using (var frm = new GeneralInputForm())
{
frm.label1.Text = label;
frm.textBox1.Text = defaultValue;
frm.Closing += (o, e) =>
{
var myForm = (GeneralInputForm) o;
// User closed not clicking the button?
if (myForm.DialogResult != DialogResult.OK)
return;
try
{
var checkval = (T) Convert.ChangeType(myForm.textBox1.Text, typeof (T));
if (!check(checkval))
e.Cancel = true;
}
catch
{
e.Cancel = true;
}
};
if (frm.ShowDialog() == DialogResult.OK)
{
result = (T)Convert.ChangeType(frm.textBox1.Text, typeof (T));
return true;
}
}
return false;
}
private void button1_Click(object sender, EventArgs e)
{
// Don't use `Close()` on modal forms, it's always better to
// return a dialog result. This will close the form aswell
DialogResult = DialogResult.OK;
}
}
Then use it like:
int result;
if(GeneralInputForm.TryGetValue("Enter an integer over 10",
"10",
(x) => x > 10,
out result))
MessageBox.Show(result.ToString());
else
/* User closed the form by other means */
Or:
string result;
if (GeneralInputForm.TryGetValue("Enter \"Hello\"",
"Goodbye",
(x) => x == "Hello",
out result))
This will not let you close the form by clicking button1 unless the value is correct, but will let you close it by other means (alt+f4, clicking the x, etc.). It'll return true if it was closed by the button, or false if it wasn't (you can put a Cancel button there).
You could also make the defaultValue parameter be of type T and assign defaultValue.ToString() to the textbox
A messagebox can be shown to the user in the Closing delegate if the value is not valid, or you can make your own logic
This requires that T is IConvertible, but about any type having a Parse method is
Thanks to everyone for the answers; I ended up to this simple and clear solution using delegates;
public partial class SimpleInputForm : Form
{
public string Value = "";
public delegate bool VerifyDel(string input);
private VerifyDel Methods;
public SimpleInputForm(string lable, VerifyDel method)
{
InitializeComponent();
label1.Text = lable;
Methods = new VerifyDel(method);
}
private void button1_Click(object sender, EventArgs e)
{
if (Methods(textBox1.Text))
{
Value = textBox1.Text;
Close();
}
}
}
and now I show the form with user defined functionality for the evaluation of input;
SimpleInputForm simpleInput = new SimpleInputForm("Enter your age:", (input => {
try
{
int age = int.Parse(input);
return true;
}
catch
{
MessageBox.Show("Invalid value for age!");
return false;
}
}));
//simpleInput.Value holds the input value
In our app we use WPF and Caliburn Micro. We use a custom WindowManager:
public class OurWindowManager : Caliburn.Micro.WindowManager
{
protected override Window EnsureWindow(object model, object view, bool isDialog)
{
var window = base.EnsureWindow(model, view, isDialog);
if (isDialog) window.ResizeMode = ResizeMode.NoResize;
window.Icon = new BitmapImage(new Uri("pack://application:,,,/NWB.ico"));
// TODO: Change to dynamic minWidth/minHeight based on window
window.MinWidth = 600;
new WindowSettingsBehavior().Attach(window);
return window;
}
}
In our code we mostly use this WindowManager like so:
public void SomeMethod()
{
var result = _windowManager.ShowDialog(new ConfirmDialogViewModel("some title",
"some text"));
if(result == true){ // if OK is pressed
// do something on OK
}
// do nothing
}
In one of my recent methods I want to do the following (in semi pseudo-code):
public void SomeOtherMethod()
{
_windowManager.ShowDialog(new ConfirmDialogViewModel("some title", "some text"));
//if window is closed without pressing any of the buttons
return; // do nothing
//if OK is pressed {
// do something on OK
}
// if Cancel is pressed: do something else
}
Unfortunately, ShowDialog also returns false if the Window is closed (even though the ShowDialog returns a Nullable bool (bool?)).
So, what I did so far is just completely remove the Close Button by making a new Window-Behavior, and I've added it to the OurWindowManager class inside the if(isDialog):
if (isDialog)
{
window.ResizeMode = ResizeMode.NoResize;
new WindowHideBarBehavior().Attach(window);
}
This works, and I now got a Window with just a title, and without a Close (X) button. Unfortunately, the window can still be closed with Alt+F4 and such. I thought about catching Alt+F4 and cancel the closing, but since Alt+F4 is standard Window behavior, I don't think users will appreciate it a lot and I find it a bit unintuitive for the users to disable it..
So, my question: How can I accomplish the pseudo-code in SomeOtherMethod mentioned above. Is there a way to get the difference between closing a Dialog or canceling a Dialog. (NOTE: As mentioned above, keep in mind we use Caliburn.Micro.WindowManager, not the default C# WPF one. Don't know if there are a lot of differences, but I guess there are at least some.)
EDIT:
I also know I can catch the closing event and cancel the closing:
window.Closing -= DisableDialogClosing;
if (isDialog)
{
window.ResizeMode = ResizeMode.NoResize;
new WindowHideBarBehavior().Attach(window);
window.Closing += DisableDialogClosing;
}
...
private static void DisableDialogClosing(object sender, CancelEventArgs e)
{
e.Cancel = true;
}
But then it also cancels the closing when I want it to close (for example when the Cancel/OK button is pressed). Maybe I can add some kind of Property-flag to this overridden Closing-EventHandler, but perhaps you guys/girls have other suggestions to accomplish the same results.
You can fulfil your requirements if you just implement your own dialog Window by extending the Window class. From inside your custom Window, you can handle the Closed event and set the Window.DialogResult property to null in that case. For the normal Ok and Cancel states, you can simply attach Click handlers to those Buttons and set the Window.DialogResult property to true and false accordingly.
private void CustomDialogWindow_Close(object sender, RoutedEventArgs e)
{
DialogResult = null;
}
private void OkButton_Click(object sender, RoutedEventArgs e)
{
DialogResult = true;
}
private void CancelButton_Click(object sender, RoutedEventArgs e)
{
DialogResult = false;
}
You could then check for the Window closed state like this:
if (CustomDialogWindow.DialogResult == null) DoSomethingUponDialogWindowClose();
You can find further helpful information in the following pages on MSDN:
Dialog Boxes Overview
Window.DialogResult Property
After #Sinatr's suggestion I've added a ClosedBy property to my ConfirmDialogViewModel:
(before):
public sealed class ConfirmDialogViewModel : Screen
{
public ConfirmDialogViewModel(string title, string message)
{
DisplayName = title;
Message = message;
}
public string Message { get; set; }
public void Ok()
{
TryClose(true);
}
public void Cancel()
{
TryClose(false);
}
}
(after):
public sealed class ConfirmDialogViewModel : Screen
{
public ClosedBy CloseReason { get; private set; }
public ConfirmDialogViewModel(string title, string message)
{
DisplayName = title;
Message = message;
CloseReason = ClosedBy.Other;
}
public string Message { get; set; }
public void Ok()
{
CloseReason = ClosedBy.Ok;
TryClose(true);
}
public void Cancel()
{
CloseReason = ClosedBy.Cancel;
TryClose(false);
}
}
public enum ClosedBy
{
Other,
Ok,
Cancel
}
And I now use it like so:
public void SomeOtherMethod()
{
var confirmDialog = new ConfirmDialogViewModel("some title", "some text");
var result = _windowManager.ShowDialog(confirmDialog);
if(result == null || confirmDialog.CloseReason == ClosedBy.Other) return;
if(result == true && confirmDialog.CloseReason == ClosedBy.Ok){
// Do something on OK
}
// Do something on cancel
}
I still kept the behavior to remove the close button, and also added window.ShowInTaskbar = false; to the OurWindowManager inside the if(isDialog).
I have a custom form that's returning the values to the main form but it's not seeing the variables. I don't think I'm making this very clear so I'll include the links to the examples of what I'm trying to do.
Return values from dialog box
too long to display
I know I'm probably overlooking something very easy and or obvious but here is what I have.
form1.cs:
private void addTime_Click(object sender, EventArgs e)
{
Form add = new addTime(false, new string[] { "", "" });
if (add.ShowDialog(this) == DialogResult.OK)
{
// the line not working
Label1.Text = add.Details;
// reports with:'System.Windows.Forms.Form' does not contain a
// definition for 'Details' and no extension method 'Details' accepting
// a first argument of type 'System.Windows.Forms.Form' could be found (are you
// missing a using directive or an assembly reference?)
}
}
addTime.cs:
internal class addTime : Form
{
//..
private string _details;
public string Details
{
get { return _details; }
private set { _details = value; }
}
private string _goalTime;
public string GoalTime
{
get { return _goalTime; }
private set { _goalTime = value; }
}
private void applybtn_Click(object sender, EventArgs e)
{
Details = detailslbl.Text;
GoalTime = goalTimelbl.Text;
}
}
Your 'add' variable is of type Form, not addTime and the Form type does not have a Details property.
Try this line instead:
addTime add = new addTime(false, new string[] { "", "" });
You need to set the DialogResult property of the child form
DialogResult = DialogResult.OK
in the button click .
you need to set the form's dialogResult property to OK. You haven't specified it in your code.
After the correct criteria have been met you would set it like this.
If (//condition)
{
this.DialogResult = DialogResult.OK;
This.Close();
}