// Display a new form with a blue background. private void btnNewForm_Click(object sender, EventArgs e) { Form1 frm = new Form1(); frm.BackColor = Color.LightBlue; frm.Show(); }This code simply creates a new instance of the form, sets its background color to light blue so you can distinguish the new forms from the startup form, and displays the form. Run the program and create and close a few forms. Then create a form or two and close the startup form to see that the whole program stops. This is not really much of a problem as long as you are aware of the situation. So what can you do if you don't want the user to stop the application by closing the main form? This example uses the following FormClosing event handler to prevent the startup form from closing if any form's "Prevent Startup From Closing" CheckBox is checked.
// If this is the main form and any other forms have their // Prevent Startup From Closing boxes checked, don't close. private void Form1_FormClosing(object sender, FormClosingEventArgs e) { // See if this is the main form. if (this == Application.OpenForms[0]) // See if any form has the CheckBox checked. foreach (Form1 frm in Application.OpenForms) if (frm.chkPreventStartupFromClosing.Checked) { e.Cancel = true; break; } }If the form that is executing the FormClosing event handler is the startup form, then it is first in the Application object's OpenForms collection. If this is the startup form, then the code loops through all of the open forms. If the CheckBox on any open form is checked, then the event handler sets e.Cancel to true to prevent the form from closing.
// Position a new instance of this form // just to the right and below the button. private void btnShowForm_Click(object sender, EventArgs e) { // Get the location in this form's coordinate system. Point form_pt = new Point( btnShowForm.Left + btnShowForm.Width / 2, btnShowForm.Top + btnShowForm.Height / 2); // Translate into the screen coordinate system. Point screen_pt = this.PointToScreen(form_pt); // Create the new form. Form1 frm = new Form1(); // Make sure the form will be completely on the screen. Screen screen = Screen.FromControl(this); if (screen_pt.X < 0) screen_pt.X = 0; if (screen_pt.Y < 0) screen_pt.Y = 0; if (screen_pt.X > screen.WorkingArea.Right - frm.Width) screen_pt.X = screen.WorkingArea.Right - frm.Width; if (screen_pt.Y > screen.WorkingArea.Bottom - frm.Height) screen_pt.Y = screen.WorkingArea.Bottom - frm.Height; // Position the new form. frm.StartPosition = FormStartPosition.Manual; frm.Location = screen_pt; frm.Show(); }The code makes a Point representing the center of the button. It calls PointToScreen to translate the point's coordinates to screen coordinates. The code then creates a new form. It gets the Screen object that is displaying the form and adjusts the Point's coordinates to ensure that the form is completely on that Screen. Finally the code sets the form's position and displays the form.
private void Form1_Load(object sender, EventArgs e) { Screen screen = Screen.FromControl(this); txtDeviceName.Text = screen.DeviceName; txtIsPrimary.Text = screen.Primary.ToString(); txtWorkingArea.Text = screen.WorkingArea.ToString(); txtDeviceName.Select(0, 0); }The only non-obvious part of this code is the first statement where the program uses Screen.FromControl to get the Screen object holding the form.
// Position a new instance of this form // just to the right and below the button. private void btnShowForm_Click(object sender, EventArgs e) { // Get the location in this form's coordinate system. Point form_pt = new Point(btnShowForm.Right, btnShowForm.Bottom); // Translate into the screen coordinate system. Point screen_pt = this.PointToScreen(form_pt); // Create and position the form. Form1 frm = new Form1(); frm.StartPosition = FormStartPosition.Manual; frm.Location = screen_pt; frm.Show(); }The code creates a Point that holds the new form's desired location in this form's coordinate system. It then calls the form's PointToScreen method to convert the point into screen coordinates. The code finishes by creating, positions, and displaying a new form. The only trick here is remembering to set the new form's StartPosition property to Manual so the form is positioned where its Location property says it should be.
1 | 1 | -1 |
1 | 1 | -1 |
1 | -1 | -1 |
// Make the listener. private void Form1_Load(object sender, EventArgs e) { string trace_file = Application.StartupPath + "//trace.txt"; Trace.Listeners.Add(new TextWriterTraceListener(trace_file)); Trace.AutoFlush = true; }The code composes a file name. It then creates a TextWriterTraceListener, passing its constructor the file name, and adds the new listener to the Trace object's Listener collection. The Debug and Trace classes share Listeners collections so this listener will write both Trace and Debug messages into the file. By default the Debug and Trace classes do not flush text buffers into text files so if the program ends without you explicitly flushing the output, some or all of the text may not be written into the file. The program avoids this problem by setting the Trace object's AutoFlush property to true. If you enter a message in the program's TextBox and click Debug, the program executes the following code.
// Send a message to the Debug listeners. private void btnDebug_Click(object sender, EventArgs e) { Debug.WriteLine(txtMessage.Text); txtMessage.Clear(); txtMessage.Focus(); }This code uses the Debug.WriteLine method to send a message to the listeners. The default listener sends the text to the Console window and the other listener appends the text to the text file trace.txt.
// Recursively explore paths starting with the indicated row. // Return the path's total value. private void FindPath2(int[] test_path, int test_total, int row) { NumFindPathCalls++; // See if we have a complete solution. if (row >= NumRows) { // We have a complete solution. // See if it is better than the best solution so far. if (test_total > BestTotal2) { // This is an improvement. BestTotal2 = test_total; Array.Copy(test_path, BestPath2, NumRows); } } else { // We don't have a complete solution. // See if there's any chance we can improve // on the best solution found so far. int rows_remaining = NumRows - row; if (test_total + MaxValue * rows_remaining <= BestTotal2) return; // Try the possibilities for this row. // Get the column used in the previous row. int prev_col = test_path[row - 1]; // Column - 1 if (prev_col > 0) { int col = prev_col - 1; test_path[row] = col; FindPath2(test_path, test_total + Numbers[row, col], row + 1); } // Column test_path[row] = prev_col; FindPath2(test_path, test_total + Numbers[row, prev_col], row + 1); // Column + 1 if (prev_col < NumCols - 1) { int col = prev_col + 1; test_path[row] = col; FindPath2(test_path, test_total + Numbers[row, col], row + 1); } } }The algorithm still searches a lot of paths so this only works up to a point. For larger problems, even it isn't fast enough and you'll need to use heuristics to search for approximate paths through the array. (The example program's DisplayNumbers method also shows the branch and bound solution with red letters so you can compare it to the exhaustive solution that has a green background.)
private void btnMakeNumbers_Click(object sender, EventArgs e) { NumRows = (int)nudNumRows.Value; NumCols = (int)nudNumCols.Value; Numbers = new int[NumRows, NumCols]; Random rand = new Random(); for (int row = 0; row < NumRows; row++) { for (int col = 0; col < NumCols; col++) { Numbers[row, col] = rand.Next(1, 99); } } BestPath = null; // Display the numbers. DisplayNumbers(); // Enable the Find Path button. btnFindPath.Enabled = true; lblTotal.Text = ""; }The code reads the number of rows and columns you entered and then randomly generates the array of numbers. It then calls DisplayNumbers to display the numbers. I'll describe that method later after I explain how the program generates the best path. The following FindBestPath method finds the best path through the array.
// The best path. private int[] BestPath = null; private int BestTotal = int.MinValue; // Find the best path. // Return the path's total value. private int FindBestPath() { // Make a test path, one entry per row. int[] test_path = new int[NumRows]; // Start in row 0 and try each column. for (int col = 0; col < NumCols; col++) { test_path[0] = col; FindPath(test_path, 1); } // Return the best total. return BestTotal; }The method starts by creating a test_path array with one entry per row. It will hold the column used in each row. The program then tries using each column as the first number visited. For each column, it fills in that column number in test_path[0] and then calls the following FindPath method to explore solutions that begin with that first column.
// Recursively explore paths starting with the indicated row. // Return the path's total value. private void FindPath(int[] test_path, int row) { // See if we have a complete solution. if (row >= NumRows) { // We have a complete solution. // See if it is better than the best solution so far. int total = 0; for (int r = 0; r < NumRows; r++) total += Numbers[r, test_path[r]]; if (total > BestTotal) { // This is an improvement. BestTotal = total; Array.Copy(test_path, BestPath, NumRows); } } else { // We don't have a complete solution. // Try the possibilities for this row. // Get the column used in the previous row. int prev_col = test_path[row - 1]; // Column - 1 if (prev_col > 0) { test_path[row] = prev_col - 1; FindPath(test_path, row + 1); } // Column test_path[row] = prev_col; FindPath(test_path, row + 1); // Column + 1 if (prev_col < NumCols - 1) { test_path[row] = prev_col + 1; FindPath(test_path, row + 1); } } }The FindPath method does the most interesting work. It recursively fills in the test_path array. When the array is completely full, it checks the test solution's value to see if it is an improvement over the current best solution. The method starts by checking its row parameter to see if the test solution is complete. If row >= NumRows, then the method has been called to fill in the entry for the row after the last row in the array. That means the test array is full so the method adds up the total value of the solution, compares it to the best solution found so far, and saves it if this is an improvement. If the test solution is not complete, the method tries each of the three possible values for the column in this row and then recursively calls itself to try values for the rest of the test solution's rows. (Eventually the recursive calls fill in all of the rows' values and previous case occurs so the method checks for an improved solution.) That's all there is to finding the best solution. The following code shows how the program displays the numbers and the best solution, if there is one.
// Display the numbers and the best path. private void DisplayNumbers() { // Display the numbers. string txt = ""; for (int row = 0; row < NumRows; row++) { txt += Environment.NewLine; for (int col = 0; col < NumCols; col++) { txt += string.Format("{0,3}", Numbers[row, col]); } } txt = txt.Substring(Environment.NewLine.Length); rchNumbers.Text = txt; // Display the best path. if (BestPath == null) return; rchNumbers.Select(); rchNumbers.SelectionBackColor = Color.White; for (int row = 0; row < NumRows; row++) { int start = 1 + row * (NumCols * 3 + Environment.NewLine.Length - 1) + BestPath[row] * 3; rchNumbers.Select(start, 2); rchNumbers.SelectionBackColor = Color.LightGreen; } rchNumbers.Select(0, 0); // Display the best path in the Console window. for (int row = 0; row < NumRows; row++) Console.Write(Numbers[row, BestPath[row]] + " "); Console.WriteLine(""); }The code first loops through the numbers a builds a multiline string that contains them all. It sets the rchNumbers RichTextBox's Text property to the result. Next the code gives the RichTextBox a white background. It then loops over the rows. For each row, it gets the column that is part of the best solution and calculates the location of that row and column in the text. It selects the corresponding text and sets the RichTextBox's background color to light green for the selection. The moves the RichTextBox's selection point to the beginning of the text so nothing is shown as selected. It finishes by displaying the values selected in the best solution in the Console window so you can verify that the highlighted values match the best solution. This technique of building a solution by recursively trying every possibility for each of the required entries is quite powerful. You can use this method to solve many hard problems, although it requires you to look at a lot of potential solutions so it only works for relatively small problems. (My book talks more about this and explains some approaches you can use to solve larger problems.)
// Save into a PDF. string filename = txtFile.Text.Replace(".xlsx", ".pdf"); const int xlQualityStandard = 0; sheet.ExportAsFixedFormat( Excel.XlFixedFormatType.xlTypePDF, filename, xlQualityStandard, true, false, Type.Missing, Type.Missing, true, Type.Missing);This code converts the Excel workbook's file name into a file name with the .pdf extension. It then calls the active worksheet's ExportAsFixedFormat method to export the data into PDF format. If you open the project in Visual Studio, IntelliSense will give you the names of the parameters to ExportAsFixedFormat. The program then finishes by closing Excel.
Much of the work in this kind of Office automation is figuring out what objects in the Office object model do the things you want. For example, figuring out how to use Word's InlineShape and Shape objects to create and format the picture. If you want to do a lot of this, my book Microsoft Office Programming: A Guide for Experienced Developers may help. The code is in Visual Basic and it's a few years old but it should help you figure out how to manipulate the Word, Excel, PowerPoint, Access, and Outlook object models and those models haven't changed too much since the book was written. |
// Load the cursors from resources. private void Form1_Load(object sender, EventArgs e) { pictureBox1.Cursor = new Cursor(new MemoryStream(Properties.Resources.AddLink1)); pictureBox2.Cursor = new Cursor(new MemoryStream(Properties.Resources._4WAY03)); pictureBox3.Cursor = new Cursor(new MemoryStream(Properties.Resources.BULLSEYE)); }The MemoryStream class is in the System.IO namespace. You can make it easier to use by adding this statement at the top of the file.
using System.IO;