This is not a question about coding per se but about good practice in code structure. I'm currently building a WinForms application and already after a few hours in, my main form contains 130 lines of code. That might not be much but this only includes event handling - since I tried to avoid this exact situation by having seperate class files for basically everything ... but right now all the controls and events just make my main code pretty hard to read.
Now this is a topic I could find surprisingly little on and I do have some ideas on how to tackle this, like creating custom controls and splitting up the form into big portions. Is there a sort of best practice for this? How do you keep your main form clean when 80% of user interaction takes place here? Also is there a basic guideline on how to structure a project (not code) you can recommend?
(Hope this qualifies as a valid question)
Thanks!
EDIT: I decided to add in the code. See anything redundand?
public partial class MainForm : Form
{
string currentFilter = "all";
public MainForm()
{
InitializeComponent();
}
private void MainForm_Load(object sender, System.EventArgs e)
{
RefreshGenres();
RefreshMovies(currentFilter);
}
private void addToolStripMenuItem_Click(object sender, System.EventArgs e)
{
var fAdd = new AddNewForm();
fAdd.SetDesktopLocation(MousePosition.X, MousePosition.Y);
fAdd.ShowDialog();
}
private void refreshToolStripMenuItem_Click(object sender, System.EventArgs e)
{
RefreshGenres();
RefreshMovies(currentFilter);
}
private void addCategoryToolStripMenuItem_Click(object sender, System.EventArgs e)
{
tvCategories.Nodes.Add(new TreeNode("category"));
tvCategories.Nodes[tvCategories.Nodes.Count - 1].BeginEdit();
}
private void tvCategories_AfterLabelEdit(object sender, NodeLabelEditEventArgs e)
{
var genre = e.Label;
var writer = new Writer();
writer.AddGenre(e.Label);
}
private void everythingToolStripMenuItem_Click(object sender, System.EventArgs e)
{
EraseData("all");
}
private void clearGenres_Click(object sender, System.EventArgs e)
{
EraseData("genres");
}
private void clearMovies_Click(object sender, System.EventArgs e)
{
EraseData("movies");
}
private void EraseData(string eraseThis)
{
DialogResult r = MessageBox.Show("Are you sure?\nLost data can NOT be retrieved.", "Clear Data", MessageBoxButtons.YesNo, MessageBoxIcon.Warning);
var cmdText = "";
if (r == DialogResult.Yes)
{
switch (eraseThis)
{
case "all":
cmdText = "TRUNCATE TABLE MOVIES GENRES";
break;
case "movies":
cmdText = "TRUNCATE TABLE MOVIES";
break;
case "genres":
cmdText = "TRUNCATE TABLE GENRES";
break;
}
conn.Open();
using (var cmd = conn.CreateCommand())
{
cmd.CommandText = cmdText;
cmd.ExecuteNonQuery();
}
conn.Close();
}
}
private void RefreshGenres()
{
tvCategories.Nodes.Clear();
var reader = new Reader();
var genres = reader.GetGenreList();
foreach (string str in genres)
{
tvCategories.Nodes.Add(str);
}
}
private void RefreshMovies(string filter)
{
lvMovies.Items.Clear();
var reader = new Reader();
var movies = reader.GetMovieList(filter);
foreach (ListViewItem item in movies)
{
lvMovies.Items.Add(item);
}
reader.conn.Close();
}
private void tvCategories_NodeMouseClick(object sender, TreeNodeMouseClickEventArgs e)
{
currentFilter = e.Node.Text;
RefreshMovies(currentFilter);
}
}
I think that the problem here is not the length of your code, but the fact that you are mixing up many different things and that you are not dealing with your events in a consistent way.
These are the main things that I would change:
EraseData: this method opens and uses a DB connection, and this is something that you usually should avoid (changing something in the DB would cause you to modify the UI code), your WinForm code should not know where the data it is exposing come from. Here you'd be better adding an EraseData to your Writer class.
RefreshMovies: it is quite different from RefreshGenres, even if both of these methods do essentially the same thing: they are getting lists of data which are added to some UI controls. RefreshGenres seems ok, but looking at RefreshMovies you immediately see that you are closing a connection that was not opened in this method. Probably you opened it in the Reader class, but that's the place where you should have closed it, too. In fact, it'd be better for conn to be private (remember, UI doesn't have to know whether data come from a DB, a text file, or a user input). Furthermore GetGenreList of Reader returns a list of strings, which is ok, but GetMovieList returns a list of ListViewItem, which is not good because this is something strongly related to your specific implementation of UI. This means that your implementation of Reader could not be used in a WPF or web application. the ListViewItems should be created in RefreshMovies using plain data got from the Reader.
Related
I'm working on a GUI for an admin interface for management of a student complex. Currently the GUI has a listbox with predefined 6 rules for the students. In the beginning of the code, I add them to a list
private void Form1_Load(object sender, EventArgs e)
{
foreach (string rule in lbRules.Items)
ruleList.Add(rule);
}
Then, the GUI provides the admin with an option to modify the rules. To do so he selects a rule from the listbox and clicks a "Modify" button, which opens another form:
private void BtnModify_Click(object sender, EventArgs e)
{
if (lbRules.SelectedItems.Count > 0)
{
selectedRule = lbRules.SelectedItem.ToString();
selectedIndex = lbRules.SelectedIndex;
selectedRuleNumber = selectedRule.Substring(0, 3);
selectedRule = selectedRule.Substring(6);
var rulesForm = new Rules();
rulesForm.Show();
}
}
On the second form load event I get the rule's text and number:
private void Rules_Load(object sender, EventArgs e)
{
tbRule.Text = Form1.selectedRuleNumber;
tbModifyRule.Text = Form1.selectedRule;
}
The text gets added to a RichTextBox, from where the rule can be edited.
Then the admin clicks a "Save" button, which gets the edited text from the RichTextBox(tbModifyRule) and adds it to a static ruleList in form1, sets a static boolean from form1 to true. Afterwards the second form gets closed:
private void BtnSave_Click(object sender, EventArgs e)
{
saveRule = Form1.selectedRuleNumber + " - " + tbModifyRule.Text;
Form1.ruleList.Insert(Form1.selectedIndex, saveRule);
Form1.ruleList.RemoveAt(Form1.selectedIndex+1);
Form1.formOpen = true;
this.Dispose();
}
At this point we are back to form1, in which we have a timer with timer_tick event. In there we check whether the boolean formOpen is true (which it is set before closing form2). Inside the if statement we clear the listbox and add each rule from the ruleList (previously edited in form2) to the listbox, then sets the formOpen back to false so it doesn't get executed all the time:
if (formOpen)
{
lbRules.Items.Clear();
foreach (string item in ruleList)
lbRules.Items.Add(item);
}
formOpen = false;
Now this is really weird, and at this point makes absolutely no sense to me, since I tried debugging it for over an hour, trying different ways, which also led me to mysterious wonders of WHY TF IT WORKS WHENEVER IT WANTS...
So this works randomly, like it would work the first time, the second and third times it won't. Or vice versa. It's all random.
Strangely, I tried adding a breakpoint on the
lbRules.Items.Add(item);
in the foreach loop, so it stops on each item. And I actually saw the changed rule getting added from the ruleList into the listBox, however in the end it was not there.
And weirdly enough, I also tried adding the text from form2 in the listBox in form1, without using a list, but for whatever odd reason, I use the int selectedIndex, which gets the index of the selected item from the BtnModify_Click event to insert the text in that particular index, but this very index gets RANDOMLY set to bloody 0 after form2 closes.
hence, it again works from time to time, because at some tries it doesn't get set to 0 and it works.
if (formOpen)
{
selectedRule = Rules.saveRule;
lbRules.Items.Insert(selectedIndex, selectedRule);
lbRules.Items.RemoveAt(selectedIndex+1);
}
formOpen = false;
I don't assign value to this integer ANYWHERE else in the code.
I really tried digging some sense, but I hit a solid hard rock.
Any help appreciated!
And thanks for the time!
edit1:
as requested - rest of the timer method
private void Timer1_Tick(object sender, EventArgs e)
{
foreach (string text in ws.messages)
message = text;
if (ws.messages.Count > 0)
{
if (message.Contains("comp"))
{
Complaints();
message = String.Empty;
ws.messages.Clear();
}
}
if (formOpen)
{
lbRules.Items.Clear();
foreach (string item in ruleList)
lbRules.Items.Add(item);
}
formOpen = false;
}
I would change your code to the following:
if (formOpen)
{
formOpen = false;
lbRules.Items.Clear();
foreach (string item in ruleList)
lbRules.Items.Add(item);
}
The issue with having the formOpen = false; outside the if statement is that there is a chance that once the user clicks the Save button the timer could be about to execute the formOpen = false instruction setting it to false making the code inside the If statement to never be executed.
I truly believe this is not random but just a timing issue due to complicated logic.
If I were you, I'd do a couple things:
Use a separate class for data exchange between forms, avoid using public static (I assume) form members for this.
Instead of a timer, subscribe to the Form.Closed event of RulesForm
This might make code flow a bit more predictable and allow you to find errors more easily.
Better yet, use the following pattern:
class Form1
{
private void BtnModify_Click(object sender, EventArgs e)
{
var ruleData = ..... //get current rule data
var rulesForm = new Rules();
rulesForm.SetData(ruleData); //pass initial state to the form
rulesForm.SaveChanges = this.ApplyRules; //pass a method which will be called on save
rulesForm.Show();
}
private bool ApplyRules(RuleData ruleData)
{
//do whatever you like with the rules here
return true;
}
}
class RuleForm
{
public void SetData(RuleData ruleData)
{
//initialize fields, etc
}
public Func<RuleData, bool> SaveChanges { get; set; }
private void BtnSave_Click(object sender, EventArgs e)
{
var ruleData = .... //get data from form fields
if(this.SaveChanges(ruleData))
this.Close();
}
}
class RuleData
{
//whatever data you need
}
to me, the question in the title seems like a regular thing one might do or have problems with, but I could not find anything on that matter.
I wrote a small program for a machine that I'm building and want to translate the UI-Elements, warning messages etc. into different languages. (German and English to start with). For that, I want to set the language in a "Settings"-form with a dropdown combobox.
I already found a working example on here how to do general localization of a form, and VisualStudio does not make it fairly difficult. How do I proceed from here? Do I need to pass some variable to the form I open, or can I set the language as a global variable and load it while opening other new forms.In total there are like 6 or 7 forms that will be opened and closed during usage.
Example code of two forms:
namespace Test
{
public partial class MES : Form
{
public MES()
{
InitializeComponent();
}
private void Messen_Load(object sender, EventArgs e)
{
comboBox1.Items.Add("English");
comboBox1.Items.Add("Deutsch");
comboBox1.SelectedIndex = 0;
}
private void comboBox1_SelectedIndexChanged(object sender, EventArgs e)
{
if (comboBox1.SelectedItem.ToString() == "English")
{
ChangeLanguage("en");
}
else
{
ChangeLanguage("de-DE");
}
}
private void ChangeLanguage(string lang)
{
foreach (Control c in this.Controls)
{
ComponentResourceManager resources = new ComponentResourceManager(typeof(Messen));
resources.ApplyResources(c, c.Name, new CultureInfo(lang));
}
}
private void button2_Click(object sender, EventArgs e)
{
NewForm NF = new NewForm();
NF.Show();
this.Close();
}
}
Changing the label and button names in the MES Form does work, but nothing changes in the NF form (Localization is set to true there as well, and languages set accordingly).
Thanks for any hints and tips in advance.
Use the below method . Source StackOverflow
You can Pass this as reference in the localizeForm.
private static void localizeForm(Form frm) {
var manager = new ComponentResourceManager(frm.GetType());
manager.ApplyResources(frm, "$this");
applyResources(manager, frm.Controls);
}
private static void applyResources(ComponentResourceManager manager, Control.ControlCollection ctls) {
foreach (Control ctl in ctls) {
manager.ApplyResources(ctl, ctl.Name);
applyResources(manager, ctl.Controls);
}
}
I am struggling to pass data between two forms (all I want to do is have a textbox in Form1, and show that textbox value in textbox1, which is located in Form2). How would I go about this, using WPF? Have looked at quite a few solutions, but cannot seem to get any of them at all to work.
For the form in which I'm wanting to display the values (in tbd.Text), here is the code:
namespace test
{
/// <summary>
/// Interaction logic for OptionDisplayWindow.xaml
/// </summary>
public partial class OptionDisplayWindow : Window
{
public OptionDisplayWindow()
{
InitializeComponent();
tbd.Text = "k"; //want to change this value based on "s" in the other form
}
The form in which the text is transferred from (want to display the string):
public void Button1_Click(object sender, RoutedEventArgs e)
{
string s = "testText"
}
I have tried every single other answer on SO (spent the past 6 hours trying) and have had absolutely no luck.
EDIT 2: Using the method listed as the best answer here Send values from one form to another form I've come up with this code for Form1:
private void ttbtn_Click(object sender, RoutedEventArgs e)
{
using (Form2 form2 = new Form2())
{
tbd.Text = form2.TheValue;
}
}
And the code for Form2:
public string TheValue
{
get { return arrayTest.Text; }
}
However, I'm getting the error 'Form 2': type used in a using statement must be implicitly convertible to 'System.IDisposable'.
The code that you put in the sample project (that you provided as a link in the comments) should be in your question. Given that it becomes much easier to understand what you're trying to do and to give you a workable solution.
I would suggest creating a "DataTransferObject" and pass that between each form.
public class Dto
{
public string Text;
}
The code in MainWindow would then look like this:
private void button1_Click(object sender, RoutedEventArgs e)
{
var dto = new Dto();
window2 win2 = new window2();
win2.Dto = dto;
win2.ShowDialog();
textBox1.Text = dto.Text;
}
And the code in window2 would look like this:
public Dto Dto;
private void textBox2_TextChanged(object sender, TextChangedEventArgs e)
{
if (this.Dto != null)
{
this.Dto.Text = textBox2.Text;
}
}
That is one way - out of about a million - of transferring data between forms. An advantage of using a data transfer object is that it begins you on the road of separating your data from your UI, and that is generally a very good thing to do.
Another simple way to pass data between forms is using your application's settings.
Step 1: Create a setting, open the "Project" menu and pick "test Properties..."
this will take you to the settings page, create a setting name it however you want, I named mine "PassString" and make sure it's a string type and the Scope is set to user.
Step 2. Lets set the string setting to your textbox.text property, add these changes to the code:
private void button1_Click(object sender, RoutedEventArgs e)
{
Properties.Settings.Default.PassString = textBox1.Text;
window2 win2 = new window2();
win2.ShowDialog();
}
Step 3. Update the text on your second window Initialization Process.
public OptionDisplayWindow()
{
InitializeComponent();
tbd.Text = Properties.Settings.Default.PassString;
}
P.S. you may have to add a reference to reach your application settings.
using test.Properties;
I know, i know there are lots and lots of questions asking on here about this error, each with their own response, but its easier to work off a response regarding your own code rather than someone else's
I have been working on this program for some time for a college assignment, and as soon as i started putting in the class to calculate the totals of things it now crashes,
I don't know where to look so i'll post my main code
enter code here
namespace Till
public partial class MainWindow : Window
{
Calculator calc = new Calculator();
public MainWindow()
{
InitializeComponent();
}
public bool User;
public bool tillopen = false;
private void button_Click(object sender, RoutedEventArgs e)
{
//button clone thingy
Button btn = (Button)sender;
label.Content = label.Content + btn.Content.ToString();
Console.Beep(); // makes the buttons beep
}
private void clear_Click(object sender, RoutedEventArgs e)
{
// Clear
label.Content = "";
}
private void Button_Submit_Click(object sender, RoutedEventArgs e)
{
// submit
listView.Items.Add(label.Content);
label.Content = "";
calc.setSoldItems(Convert.ToInt32(label.Content)); /// it breaks on this line///
}
private void button13_Click(object sender, RoutedEventArgs e)
{
//void sale
label.Content = "";
listView.Items.Clear();
}
private void button15_Click(object sender, RoutedEventArgs e)
{
//pound
label.Content = "1.00";
}
private void button12_Click(object sender, RoutedEventArgs e)
{
//till open close
tillopen = true;
}
private void button16_Click(object sender, RoutedEventArgs e)
{
Login m = new Login();
m.Show();
this.Close();
}
private void button14_Click(object sender, RoutedEventArgs e)
{
label.Content = "2.00"; // 2 pound
}
private void button17_Click(object sender, RoutedEventArgs e)
{
label.Content = calc.finish();
}
}
I have tried to re-create the error in another WPF (converting a to an int32) and it works fine, i know this is an issue with my code itself, i have tried using other machine and using different versions of visual studio itself, so we came to the assumption its this code itself and not a broken dll file
So before I sit down with my Teacher and spend all day going though my code step by step im asking around for help in order to save both of our time, This assignment is due in in 3 weeks, and it decides to break on me now.
thankies
To replicate this error, i press a number button on my Windows form, them hit the submit button i created (which starts the conversion) If a copy of my class which handles all of this is needed im happy to post it
In the method button_click, you have assigned value as
label.Content = label.Content + btn.Content.ToString();
which is a string value to the label and the values are concatenated instead of add.
and when you are reading it, you are converting it in Int32. which will give exception as it will not contain any integer value to it.
You can add the value like this:
label.Content = (Convert.ToInt32(label.Content) + Convert.ToInt32(btn.Content)).ToString();
and check before converting if the label has blank values in it, if it has do not convert them, and only convert the value if it has some values it will not give any error. Also do not assign any values other that numerical digits.
You are calculating:
Convert.ToInt32(label.Content)
but on the line before you set:
label.Content = "";
so this means you are calculating
Convert.ToInt32("")
which gives you a FormatException.
Perhaps you should use the value of label.Content before you overwrite it?
I struggled with what to title this as but hopefully I can explain a little better here. I am trying to write a program that will track an assembly through a 6 station assembly line. At each station, the operator will hit a button (such as station1start, station1stop, station2start, etc) and the button press event will send the timestamp to a database and also update the form visually by moving the traveling id number to the next station. I have this all working for the first couple of stations but I'm wondering if there is a way to use the same method for each station. For example have a method such as
void updateStart(int station_num)
where the station ID would be an argument but otherwise the method could be used for all of the stations. I know that variables in C# cannot be dynamically changed but am curious if there is another way to make this code cleaner. It seems like bad programming to have 6 methods almost identical. Especially if we were to add another 6 stations. See the screenshot of the form and my example code below of the button that the operator would hit when they started at station 2. Any help would be greatly appreciated!
http://i.stack.imgur.com/Ddxww.png
private void Station2Start_Click(object sender, EventArgs e)
{
Station2Label.Text = Station1Label.Text;
Station1Label.Text = "";
Station1Status.Text = "";
Station2Status.Text = "IN PROGRESS";
addTimeToDb(2);
}
The question is somewhat unclear but I believe it is:
I have the following code:
private void Station2Start_Click(object sender, EventArgs e)
{
Station2Label.Text = Station1Label.Text;
Station1Label.Text = "";
Station1Status.Text = "";
Station2Status.Text = "IN PROGRESS";
addTimeToDb(2);
}
private void Station3Start_Click(object sender, EventArgs e)
{
Station3Label.Text = Station2Label.Text;
Station2Label.Text = "";
Station2Status.Text = "";
Station3Status.Text = "IN PROGRESS";
addTimeToDb(2);
}
And so on, repeated many times with minor substitutions. How do I "DRY out" this code? (That is Don't Repeat Yourself.)
When you create the labels and status boxes put them in an array:
private Label[] stationLabels;
private Label[] statusLabels;
...
// in your form initialization after the creation of the labels:
stationLabels = new [] { Station1Label, Station2Label, Station3Label, ...
// and similarly for status labels.
Now write
private void StationClick(int station)
{
stationLabels[station-1].Text = stationLabels[station-2].Text;
... and so on
And then each method becomes
private void Station2Start_Click(object sender, EventArgs e)
{
StationClick(2);
}
private void Station3Start_Click(object sender, EventArgs e)
{
StationClick(3);
}
And so on.