1 using System; 2 using System.Collections.Generic; 3 using System.ComponentModel; 4 using System.Data; 5 using System.Drawing; 6 using System.Text; 7 using System.Windows.Forms; 8 using System.Threading; 9 using System.Diagnostics; 10 using System.IO; 11 using System.Reflection; 12 13 namespace distribution_explorer 14 { 15 /// <summary> 16 /// Main distribution explorer. 17 /// </summary> 18 public partial class DistexForm : Form 19 { 20 21 EventLog log = new EventLog(); 22 /// <summary> 23 /// Main form 24 /// </summary> DistexForm()25 public DistexForm() 26 { 27 if (!EventLog.SourceExists("EventLogDistex")) 28 { 29 EventLog.CreateEventSource("EventLogDistex", "Application"); 30 } 31 log.Source = "EventLogDistex"; 32 log.WriteEntry("DistexForm"); 33 34 InitializeComponent(); 35 36 Application.DoEvents(); 37 } 38 Form_Load(object sender, EventArgs e)39 private void Form_Load(object sender, EventArgs e) 40 { // Load distribution & parameters names, and default values. 41 try 42 { 43 // Create and show splash screen: 44 this.Hide(); 45 distexSplash frmSplash = new distexSplash(); 46 frmSplash.Show(); 47 frmSplash.Update(); 48 // Now load our data while the splash is showing: 49 if (boost_math.any_distribution.size() <= 0) 50 { 51 MessageBox.Show("Problem loading any distributions, size = " + boost_math.any_distribution.size().ToString()); 52 } 53 for (int i = 0; i < boost_math.any_distribution.size(); ++i) 54 { 55 distribution.Items.Add(boost_math.any_distribution.distribution_name(i)); 56 } 57 distribution.SelectedIndex = 0; // 1st in array, but could be any other. 58 // All parameters are made zero by default, but updated from chosen distribution. 59 parameter1.Text = boost_math.any_distribution.first_param_default(0).ToString(); 60 parameter2.Text = boost_math.any_distribution.second_param_default(0).ToString(); 61 parameter3.Text = boost_math.any_distribution.third_param_default(0).ToString(); 62 // 63 // Sleep and then close splash; 64 Thread.Sleep(3000); 65 frmSplash.Close(); 66 this.Visible = true; 67 } 68 catch 69 { // 70 log.WriteEntry("DistexForm_load exception!"); 71 MessageBox.Show("Problem loading distributions, size = " + boost_math.any_distribution.size().ToString()); 72 } 73 } 74 distribution_SelectedIndexChanged(object sender, EventArgs e)75 private void distribution_SelectedIndexChanged(object sender, EventArgs e) 76 { 77 int i = distribution.SelectedIndex; // distribution tab. 78 parameter1Label.Text = boost_math.any_distribution.first_param_name(i); 79 parameterLabel1.Text = boost_math.any_distribution.first_param_name(i); // properties tab. 80 parameter2Label.Text = boost_math.any_distribution.second_param_name(i); 81 parameter3Label.Text = boost_math.any_distribution.third_param_name(i); 82 if (boost_math.any_distribution.first_param_name(i).Length.CompareTo(0) != 0) 83 { // Actually all the distributions have at least one parameters, 84 parameter1.Visible = true; // so should always be true. 85 parameterLabel1.Visible = true; 86 } 87 else 88 { // If distribution chosen has no parameter name(s) then hide. 89 parameter1.Visible = false; 90 parameterLabel1.Visible = false; 91 } 92 parameter1.Text = boost_math.any_distribution.first_param_default(i).ToString(); 93 // Update parameter default to match distribution. 94 if (boost_math.any_distribution.second_param_name(i).Length.CompareTo(0) != 0) 95 { 96 parameter2.Visible = true; 97 parameterLabel2.Visible = true; 98 parameter2ValueLabel.Visible = true; 99 } 100 else 101 { // hide 102 parameter2.Visible = false; 103 parameterLabel2.Visible = false; 104 parameter2ValueLabel.Visible = false; 105 106 } 107 parameter2.Text = boost_math.any_distribution.second_param_default(i).ToString(); 108 if (boost_math.any_distribution.third_param_name(i).Length.CompareTo(0) != 0) 109 { 110 parameter3.Visible = true; 111 parameterLabel3.Visible = true; 112 parameter3ValueLabel.Visible = true; 113 } 114 else 115 { // hide 116 parameter3.Visible = false; 117 parameterLabel3.Visible = false; 118 parameter3ValueLabel.Visible = false; 119 } 120 parameter3.Text = boost_math.any_distribution.third_param_default(i).ToString(); 121 // Update tool tips to show total and supported ranges. 122 PropertiesTabPage.ToolTipText = "Shows properties and ranges of chosen distribution."; 123 } 124 125 private boost_math.any_distribution dist; 126 dataGridView1_CellEndEdit(object sender, DataGridViewCellEventArgs e)127 private void dataGridView1_CellEndEdit(object sender, DataGridViewCellEventArgs e) 128 { // Display a grid of pdf, cdf... values from user's random variate x value. 129 try 130 { 131 if (e.ColumnIndex == 0) 132 { // Clicked on left-most random variate x column to enter a value. 133 int i = e.RowIndex; 134 string s = CDF_data.Rows[i].Cells[0].Value.ToString(); 135 double x = double.Parse(s); // Get value of users random variate x. 136 double pdf = dist.pdf(x); // Compute pdf values from x 137 double cdf = dist.cdf(x); // & cdf 138 double ccdf = dist.ccdf(x); // & complements. 139 CDF_data.Rows[i].Cells[1].Value = pdf; // and display values. 140 CDF_data.Rows[i].Cells[2].Value = cdf; 141 CDF_data.Rows[i].Cells[3].Value = ccdf; 142 } 143 } 144 catch (SystemException se) 145 { 146 MessageBox.Show("Error in random variable value: " + se.Message, "Calculation Error"); 147 } 148 } 149 tabPage2_Enter(object sender, EventArgs e)150 private void tabPage2_Enter(object sender, EventArgs e) 151 { // Properties tab shows distribution's mean, mode, median... 152 try 153 { // Show chosen distribution name, and parameter names & values. 154 int i = distribution.SelectedIndex; 155 distributionValueLabel.Text = boost_math.any_distribution.distribution_name(i).ToString(); 156 parameterLabel1.Text = boost_math.any_distribution.first_param_name(i).ToString(); 157 parameter1ValueLabel.Text = double.Parse(parameter1.Text).ToString(); 158 parameterLabel2.Text = boost_math.any_distribution.second_param_name(i).ToString(); 159 parameter2ValueLabel.Text = double.Parse(parameter2.Text).ToString(); 160 parameterLabel3.Text = boost_math.any_distribution.third_param_name(i).ToString(); 161 parameter3ValueLabel.Text = double.Parse(parameter3.Text).ToString(); 162 163 // Show computed properties of distribution. 164 try 165 { 166 mean.Text = dist.mean().ToString(); 167 } 168 catch 169 { 170 mean.Text = "Undefined."; 171 } 172 try 173 { 174 mode.Text = dist.mode().ToString(); 175 } 176 catch 177 { 178 mode.Text = "Undefined."; 179 } 180 try 181 { 182 median.Text = dist.median().ToString(); 183 } 184 catch 185 { 186 median.Text = "Undefined."; 187 } 188 try 189 { 190 variance.Text = dist.variance().ToString(); 191 } 192 catch 193 { 194 variance.Text = "Undefined."; 195 } 196 try 197 { 198 standard_deviation.Text = dist.standard_deviation().ToString(); 199 } 200 catch 201 { 202 standard_deviation.Text = "Undefined."; 203 } 204 try 205 { 206 skewness.Text = dist.skewness().ToString(); 207 } 208 catch 209 { 210 skewness.Text = "Undefined."; 211 } 212 try 213 { 214 kurtosis.Text = dist.kurtosis().ToString(); 215 } 216 catch 217 { 218 kurtosis.Text = "Undefined."; 219 } 220 try 221 { 222 kurtosis_excess.Text = dist.kurtosis_excess().ToString(); 223 } 224 catch 225 { 226 kurtosis_excess.Text = "Undefined."; 227 } 228 try 229 { 230 coefficient_of_variation.Text = dist.coefficient_of_variation().ToString(); 231 } 232 catch 233 { 234 coefficient_of_variation.Text = "Undefined."; 235 } 236 237 rangeLowestLabel.Text = dist.lowest().ToString(); 238 rangeGreatestLabel.Text = dist.uppermost().ToString(); 239 supportLowerLabel.Text = dist.lower().ToString(); 240 supportUpperLabel.Text = dist.upper().ToString(); 241 cdfTabPage.ToolTipText = "Random variate can range from " + rangeLowestLabel.Text 242 + " to " + rangeGreatestLabel.Text 243 + ",\nbut is said to be supported from " + supportLowerLabel.Text 244 + " to " + supportUpperLabel.Text 245 + "\nWithin this supported range the PDF and CDF have values between 0 and 1,\nbut below " + supportLowerLabel.Text + " both are zero, and above " 246 + supportUpperLabel.Text + " both are unity"; 247 } 248 catch (SystemException se) 249 { 250 MessageBox.Show(se.Message, "Calculation Error!"); 251 } 252 } 253 properties_tab_Deselecting(object sender, TabControlCancelEventArgs e)254 private void properties_tab_Deselecting(object sender, TabControlCancelEventArgs e) 255 { 256 try 257 { 258 if (e.TabPageIndex == 0) 259 { // Update selected distribution object: 260 double x = double.Parse(parameter1.Text); 261 double y = double.Parse(parameter2.Text); 262 double z = double.Parse(parameter3.Text); 263 int i = distribution.SelectedIndex; 264 dist = new boost_math.any_distribution(i, x, y, z); 265 // Clear existing CDF data (has to be a better way?): 266 while (CDF_data.Rows.Count > 1) 267 { 268 CDF_data.Rows.Remove(CDF_data.Rows[0]); 269 } 270 // Clear existing quantile data (has to be a better way?): 271 while (QuantileData.Rows.Count > 1) 272 { 273 QuantileData.Rows.Remove(QuantileData.Rows[0]); 274 } 275 } 276 } 277 catch (SystemException se) 278 { 279 MessageBox.Show(se.Message + 280 " Please check the distribution's parameters and try again.", "Distribution Error"); 281 this.propertiesTab.SelectedIndex = 0; 282 e.Cancel = true; 283 } 284 } 285 QuantileData_CellEndEdit(object sender, DataGridViewCellEventArgs e)286 private void QuantileData_CellEndEdit(object sender, DataGridViewCellEventArgs e) 287 { // aka Risk & critical values tab. 288 try 289 { 290 if (e.ColumnIndex == 0) 291 { 292 int i = e.RowIndex; 293 string s = QuantileData.Rows[i].Cells[0].Value.ToString(); 294 double x = double.Parse(s); 295 // Remember x is alpha: 1 - the probability: 296 double lcv = dist.quantile(x); 297 double ucv = dist.quantile_c(x); 298 QuantileData.Rows[i].Cells[1].Value = lcv; 299 QuantileData.Rows[i].Cells[2].Value = ucv; 300 } 301 } 302 catch (SystemException se) 303 { 304 // TODO add some proper handling here! 305 MessageBox.Show("Error in probability value: " + se.Message, "Calculation Error"); 306 } 307 } 308 QuantileTab_Enter(object sender, EventArgs e)309 private void QuantileTab_Enter(object sender, EventArgs e) 310 { // Evaluate critical values (quantiles) for pre-chosen risk level. 311 // and then, optionally, for other user-provided risk levels. 312 try 313 { 314 if (QuantileData.Rows.Count == 1) 315 { 316 // Add some defaults: 317 QuantileData.Rows.Add(5); // 5 Risk levels. 318 QuantileData.Rows[0].Cells[0].Value = "0.001"; // Risk values as text, 319 QuantileData.Rows[0].Cells[1].Value = dist.quantile(0.001); // & as double. 320 QuantileData.Rows[0].Cells[2].Value = dist.quantile_c(0.001); 321 QuantileData.Rows[1].Cells[0].Value = "0.01"; 322 QuantileData.Rows[1].Cells[1].Value = dist.quantile(0.01); // 99% confidence. 323 QuantileData.Rows[1].Cells[2].Value = dist.quantile_c(0.01); 324 QuantileData.Rows[2].Cells[0].Value = "0.05"; 325 QuantileData.Rows[2].Cells[1].Value = dist.quantile(0.05); 326 QuantileData.Rows[2].Cells[2].Value = dist.quantile_c(0.05); 327 QuantileData.Rows[3].Cells[0].Value = "0.1"; 328 QuantileData.Rows[3].Cells[1].Value = dist.quantile(0.1); 329 QuantileData.Rows[3].Cells[2].Value = dist.quantile_c(0.1); 330 QuantileData.Rows[4].Cells[0].Value = "0.33333333333333333"; 331 QuantileData.Rows[4].Cells[1].Value = dist.quantile(0.33333333333333333); 332 QuantileData.Rows[4].Cells[2].Value = dist.quantile_c(0.33333333333333333); 333 } 334 } 335 catch (SystemException se) 336 { 337 // TODO add some proper handling here! 338 MessageBox.Show(se.Message, "Calculation Error"); 339 } 340 } 341 342 properties_tab_SelectedIndexChanged(object sender, EventArgs e)343 private void properties_tab_SelectedIndexChanged(object sender, EventArgs e) 344 { 345 } 346 tabPage1_Click(object sender, EventArgs e)347 private void tabPage1_Click(object sender, EventArgs e) 348 { 349 } 350 CDF_data_CellContentClick(object sender, DataGridViewCellEventArgs e)351 private void CDF_data_CellContentClick(object sender, DataGridViewCellEventArgs e) 352 { 353 } 354 355 distexAboutBox DistexAboutBox = new distexAboutBox(); 356 aboutToolStripMenuItem_Click(object sender, EventArgs e)357 private void aboutToolStripMenuItem_Click(object sender, EventArgs e) 358 { 359 DistexAboutBox.ShowDialog(); 360 } 361 DistexForm_Activated(object sender, EventArgs e)362 private void DistexForm_Activated(object sender, EventArgs e) 363 { 364 } 365 366 /// get AssemblyDescription 367 public string AssemblyDescription 368 { 369 get 370 { 371 // Get all Description attributes on this assembly 372 object[] attributes = Assembly.GetExecutingAssembly().GetCustomAttributes(typeof(AssemblyDescriptionAttribute), false); 373 // If there aren't any Description attributes, return an empty string 374 if (attributes.Length == 0) 375 return ""; 376 // If there is a Description attribute, return its value 377 return ((AssemblyDescriptionAttribute)attributes[0]).Description; 378 } 379 } 380 saveFileDialog1_FileOk(object sender, CancelEventArgs e)381 private void saveFileDialog1_FileOk(object sender, CancelEventArgs e) 382 { 383 using (StreamWriter sw = new StreamWriter(this.saveFileDialog.FileName)) 384 { // Write distribution info and properties to file. 385 sw.WriteLine( AssemblyDescription); 386 sw.WriteLine("Version " + Assembly.GetExecutingAssembly().GetName().Version.ToString()); 387 // Get parameter names (null "" if no parameter). 388 int i = distribution.SelectedIndex; 389 distributionValueLabel.Text = boost_math.any_distribution.distribution_name(i).ToString(); 390 sw.WriteLine(distributionValueLabel.Text + " distribution"); 391 parameterLabel1.Text = boost_math.any_distribution.first_param_name(i).ToString(); 392 parameterLabel2.Text = boost_math.any_distribution.second_param_name(i).ToString(); 393 parameterLabel3.Text = boost_math.any_distribution.third_param_name(i).ToString(); 394 string separator = "\t "; // , or tab or space? 395 // Write parameter name & value. 396 sw.WriteLine(parameterLabel1.Text + separator + this.parameter1.Text); 397 if (boost_math.any_distribution.second_param_name(i).Length.CompareTo(0) != 0) 398 { // Is a 2nd parameter. 399 sw.WriteLine(parameterLabel2.Text + separator + this.parameter2.Text); 400 } 401 if (boost_math.any_distribution.third_param_name(i).Length.CompareTo(0) != 0) 402 { // Is a 3rd parameter. 403 sw.WriteLine(parameterLabel3.Text + separator + this.parameter3.Text); 404 } 405 sw.WriteLine(); 406 sw.WriteLine("Properties"); 407 // Show computed properties of distribution. 408 double x = double.Parse(parameter1.Text); 409 double y = double.Parse(parameter2.Text); 410 double z = double.Parse(parameter3.Text); 411 dist = new boost_math.any_distribution(i, x, y, z); 412 // Note global dist might not have been calculated yet if no of the tabs clicked. 413 try 414 { 415 mean.Text = dist.mean().ToString(); 416 } 417 catch 418 { 419 mean.Text = "Undefined"; 420 } 421 sw.WriteLine("Mean" + separator + mean.Text); 422 try 423 { 424 mode.Text = dist.mode().ToString(); 425 } 426 catch 427 { 428 mode.Text = "Undefined"; 429 } 430 sw.WriteLine("mode" + separator + mode.Text); 431 try 432 { 433 median.Text = dist.median().ToString(); 434 } 435 catch 436 { 437 median.Text = "Undefined"; 438 } 439 sw.WriteLine("Median" + separator + median.Text); 440 try 441 { 442 variance.Text = dist.variance().ToString(); 443 } 444 catch 445 { 446 variance.Text = "Undefined"; 447 } 448 sw.WriteLine("Variance" + separator + variance.Text); 449 try 450 { 451 standard_deviation.Text = dist.standard_deviation().ToString(); 452 } 453 catch 454 { 455 standard_deviation.Text = "Undefined"; 456 } 457 sw.WriteLine("Standard Deviation" + separator + standard_deviation.Text); 458 try 459 { 460 skewness.Text = dist.skewness().ToString(); 461 } 462 catch 463 { 464 skewness.Text = "Undefined"; 465 } 466 sw.WriteLine("Skewness" + separator + skewness.Text); 467 try 468 { 469 coefficient_of_variation.Text = dist.coefficient_of_variation().ToString(); 470 } 471 catch 472 { 473 coefficient_of_variation.Text = "Undefined"; 474 } 475 sw.WriteLine("Cofficient of variation" + separator + coefficient_of_variation.Text); 476 try 477 { 478 kurtosis.Text = dist.kurtosis().ToString(); 479 } 480 catch 481 { 482 kurtosis.Text = "Undefined"; 483 } 484 sw.WriteLine("Kurtosis" + separator + kurtosis.Text); 485 try 486 { 487 kurtosis_excess.Text = dist.kurtosis_excess().ToString(); 488 } 489 catch 490 { 491 kurtosis_excess.Text = "Undefined"; 492 } 493 sw.WriteLine("Kurtosis excess" + separator + kurtosis_excess.Text); 494 sw.WriteLine(); 495 496 sw.WriteLine("Range from" + separator + dist.lowest().ToString() + separator + 497 "to" + separator + dist.uppermost().ToString()); 498 sw.WriteLine("Support from " + separator + dist.lower().ToString() +separator+ 499 "to " + separator + dist.upper().ToString()); 500 sw.WriteLine(); 501 502 // 503 sw.WriteLine("Quantiles"); 504 if (QuantileData.Rows.Count == 1) 505 { // Add some defaults: 506 QuantileData.Rows.Add(5); // 5 Risk levels. 507 QuantileData.Rows[0].Cells[0].Value = "0.001"; // Risk values as text, 508 QuantileData.Rows[0].Cells[1].Value = dist.quantile(0.001); // & as double. 509 QuantileData.Rows[0].Cells[2].Value = dist.quantile_c(0.001); 510 QuantileData.Rows[1].Cells[0].Value = "0.01"; 511 QuantileData.Rows[1].Cells[1].Value = dist.quantile(0.01); // 99% confidence. 512 QuantileData.Rows[1].Cells[2].Value = dist.quantile_c(0.01); 513 QuantileData.Rows[2].Cells[0].Value = "0.05"; 514 QuantileData.Rows[2].Cells[1].Value = dist.quantile(0.05); 515 QuantileData.Rows[2].Cells[2].Value = dist.quantile_c(0.05); 516 QuantileData.Rows[3].Cells[0].Value = "0.1"; 517 QuantileData.Rows[3].Cells[1].Value = dist.quantile(0.1); 518 QuantileData.Rows[3].Cells[2].Value = dist.quantile_c(0.1); 519 QuantileData.Rows[4].Cells[0].Value = "0.33333333333333333"; 520 QuantileData.Rows[4].Cells[1].Value = dist.quantile(0.33333333333333333); 521 QuantileData.Rows[4].Cells[2].Value = dist.quantile_c(0.33333333333333333); 522 } 523 // else have already been calculated by entering the quantile tab. 524 for (int r = 0; r < QuantileData.Rows.Count-1; r++) 525 { // Show all the rows of quantiles, including any optional user values. 526 sw.WriteLine(QuantileData.Rows[r].Cells[0].Value.ToString() + separator + 527 QuantileData.Rows[r].Cells[1].Value.ToString() + separator + 528 QuantileData.Rows[r].Cells[2].Value.ToString()); 529 } 530 sw.WriteLine(); 531 sw.WriteLine("PDF, CDF & complement(s)"); 532 for (int r = 0; r < CDF_data.Rows.Count-1; r++) 533 { // Show all the rows of pdf, cdf, including any optional user values. 534 sw.WriteLine(CDF_data.Rows[r].Cells[0].Value.ToString() + separator + // x value. 535 CDF_data.Rows[r].Cells[1].Value.ToString() + separator + // pdf 536 CDF_data.Rows[r].Cells[2].Value.ToString() + separator + // cdf 537 CDF_data.Rows[r].Cells[3].Value.ToString());// cdf complement. 538 } 539 sw.WriteLine(); 540 } 541 542 } // saveFileDialog1_FileOk 543 saveToolStripMenuItem_Click(object sender, EventArgs e)544 private void saveToolStripMenuItem_Click(object sender, EventArgs e) 545 { 546 this.saveFileDialog.ShowDialog(); 547 } 548 saveAsToolStripMenuItem_Click(object sender, EventArgs e)549 private void saveAsToolStripMenuItem_Click(object sender, EventArgs e) 550 { // Same as Save. 551 this.saveFileDialog.ShowDialog(); 552 } 553 contentsToolStripMenuItem_Click(object sender, EventArgs e)554 private void contentsToolStripMenuItem_Click(object sender, EventArgs e) 555 { // In lieu of proper help. 556 string helpText = "\n" + AssemblyDescription + 557 "\nVersion " + Assembly.GetExecutingAssembly().GetName().Version.ToString() + 558 "\nA Windows utility to show the properties of distributions " + 559 "\nand permit calculation of probability density (or mass) function (PDF) " + 560 "\nand cumulative distribution function (CDF) and complements from values provided." + 561 "\nQuantiles are also calculated for typical risk (alpha) probabilities" + 562 "\nand for probabilities provided by the user." + 563 "\n" + 564 "\nResults can be saved to text files using Save or SaveAs." + 565 "\nAll the values on the four tabs are output to the file chosen," + 566 "\nand are tab separated to assist input to other programs," + 567 "\nfor example, spreadsheets or text editors." + 568 "\nNote: when importing to Excel, by default only 10 decimal digits are shown by Excel:" + 569 "\nit is necessary to format all cells to display the full 15 decimal digits," + 570 "\nalthough not all computed values will be as accurate as this." + 571 "\n\nValues shown as NaN cannot be calculated for the value given," + 572 "\nmost commonly because the value is outside the range for the distribution." + 573 "\n" + 574 "\nFor more information, including downloads, see " + 575 "\nhttp://sourceforge.net/projects/distexplorer/" + 576 "\n(Note that .NET framework 4.0 and VC Redistribution X86 are requirements for this program.)" + 577 "\n\nCopyright John Maddock & Paul A. Bristow 2007, 2009, 2010, 2012"; 578 579 MessageBox.Show("Statistical Distribution Explorer\n" + helpText); 580 } 581 newToolStripMenuItem_Click(object sender, EventArgs e)582 private void newToolStripMenuItem_Click(object sender, EventArgs e) 583 { 584 MessageBox.Show("New is not yet implemented."); 585 } 586 openToolStripMenuItem_Click(object sender, EventArgs e)587 private void openToolStripMenuItem_Click(object sender, EventArgs e) 588 { 589 MessageBox.Show("Open is not yet implemented."); 590 } 591 printToolStripMenuItem_Click(object sender, EventArgs e)592 private void printToolStripMenuItem_Click(object sender, EventArgs e) 593 { 594 MessageBox.Show("Print is not yet implemented." + 595 "\nSave all values to a text file and print that file."); 596 } 597 printPreviewToolStripMenuItem_Click(object sender, EventArgs e)598 private void printPreviewToolStripMenuItem_Click(object sender, EventArgs e) 599 { 600 MessageBox.Show("Print Preview is not yet implemented." + 601 "\nSave all values to a text file and print that file."); 602 } 603 604 exitToolStripMenuItem_Click(object sender, EventArgs e)605 private void exitToolStripMenuItem_Click(object sender, EventArgs e) 606 { // exit DistexForm 607 this.Close(); 608 } 609 } // class DistexForm 610 } // namespace distribution_explorer