Tài liệu hạn chế xem trước, để xem đầy đủ mời bạn chọn Tải xuống
1
/ 50 trang
THÔNG TIN TÀI LIỆU
Thông tin cơ bản
Định dạng
Số trang
50
Dung lượng
469,28 KB
Nội dung
466 CHAPTER 14 LIST VIEWS We employ the using statement to ensure that our dialog is properly disposed of at the end of the handler. Also note how multiple exceptional handling blocks are used to catch errors that occur. You may wonder if it is expensive to perform such opera- tions, especially if you are familiar with exception-handling mechanisms in languages like C and C++ where it indeed can be an expensive proposition to call try multiple times. In C#, the exception handling is built into the language and the compiler, so checking for exceptions as we do here is not much more expensive than an if state- ment. The expense comes if an exception actually occurs, since the compiler must 7 Display an error message if the album could not be opened. Note: Here and throughout the remainder of the book, we use the simplest form of the Mes- sageBox dialog. Feel free to use an alternate form if you prefer. See chapter 8 for detailed infor- mation on the MessageBox class. if (album == null) { MessageBox.Show("The properties for " + "this album cannot be displayed."); return; } 8 Display the AlbumEditDlg if the album is opened successfully. using (AlbumEditDlg dlg = new AlbumEditDlg(album)) { 9 If any changes are made by the user, save these changes into the album file. Catch any errors that occur. if (dlg.ShowDialog() == DialogResult.OK) { // Save changes made by the user try { album.Save(); } catch (Exception) { MessageBox.Show("Unable to save " + "changes to album."); return; } 10 Also update any subitem text that might be affected by the user’s changes. // Update subitem settings item.SubItems[MainForm. AlbumTitleColumn].Text = album.Title; bool hasPwd = (album.Password != null) && (album.Password.Length > 0); item.SubItems[MainForm. AlbumPwdColumn].Text = (hasPwd ? "y" : "n"); } } 11 Dispose of the album at the end of the method. album.Dispose(); } ADD A MENU TO DISPLAY ALBUM PROPERTIES (continued) Action Result SELECTION AND EDITING 467 then construct the Exception object, unravel the call stack and clean up any objects as required, plus locate the appropriate catch block for the particular exception. The fact that exception clean up can impact a program’s performance is one more reason to ensure that you throw exceptions only for truly exceptional conditions. Common problems or situations should be handled through the use of an error code. As a case in point, this is one reason why file-related read and write methods in the .NET Framework do not raise an exception when the end of a file is reached. Back to our code, this discussion tells us that our use of try and catch here should not affect our performance very much since we do not normally expect an exception to occur other than when opening an invalid album. We could improve the performance if we kept track of the invalid albums during the OnLoad method, since then we would not need to re-open these albums again here. We will not actually do this here, but it was worth a mention. The remainder of the previous code is fairly self-explanatory. One other point worth mentioning is our use of the Tag property. This works well in our Display- AlbumProperties method since all we need to keep track of is the album’s file name. It is also possible here to assign a PhotoAlbum instance to the Tag property rather than a string instance, although this requires extra memory and other resources to maintain the album for each item in memory. An alternative approach often used to track more complex relationships is to derive a new class from the ListViewItem class. For our application, an excerpt of such a class might look something like the code shown in listing 14.1. Since this class is a ListViewItem object, instances of it can be assigned to and manipulated within the ListView control. Whenever the PhotoAlbum object for an album is required, a list view item can be downcast to the PhotoAlbumListItem class, where the Album property and other members may be used to manipulate the album. public class PhotoAlbumListItem : ListViewItem, IDisposable { private string _fileName; private PhotoAlbum _album; PhotoAlbumListItem(string file) { _fileName = file; _album = null; } public void Dispose() { // Dispose implementation . . . } public PhotoAlbum Album Listing 14.1 Example deriving a new class from ListViewItem (not our approach) 468 CHAPTER 14 LIST VIEWS { get { if (_album == null) { _album = new PhotoAlbum(); _album.Open(_fileName); } return _album; } } // Other methods as required . . . } For our purposes the use of a simple string value in the Tag property was sufficient to display the album’s properties dialog. Another feature worth supporting here is the ability to edit item labels. 14.4.2 S UPPORTING LABEL EDITS Editing an item label in place is one of the advantages the ListView class has over ListBox objects. In our application it would be nice if the user could edit the album name in order to rename an album file. This section will show how to support this feature. Label editing is disabled by default, and turned on by setting the LabelEdit property to true. An actual edit of an item is initiated by the BeginEdit method of the ListViewItem class. The corresponding ListView control receives two events during the editing process. The BeforeLabelEdit event occurs before the edit process begins, while the AfterLabelEdit event occurs when the user com- pletes the edit by pressing the Enter key or clicking outside of the edit area. Event han- dlers for both events receive the LabelEditEventArgs class as their event handler. See .NET Table 14.7 for an overview of this class. We will allow an item to be edited in two ways. The first way is through a Name menu under the top-level Edit menu, and the second way is by selecting an item and pressing the F2 key. This matches the keyboard shortcut supported by Windows Explorer, so it seems appropriate here. In a production environment, we would probably handle both events in our application. In the BeginLabelEdit event handler we would make sure the album is valid and can be successfully opened. This provides some assurance that the edit will be successful before the user begins typing. The AfterLabelEdit event handler would update the album with a new title and store the album to disk. It would also update the album file on disk with the change. SELECTION AND EDITING 469 Since we are not in a production environment, we will take the easy way out and only handle the AfterLabelEdit event. This means a user may edit an album only to find that he or she cannot save his changes, which is not the best interface from a usability perspective. The code changes required are given in the following steps: .NET Table 14.7 LabelEditEventArgs class The LabelEditEventArgs class represents the event arguments received by BeforeLa- belEdit and AfterLabelEdit event handlers for the ListView class. This class is part of the System.Windows.Forms namespace, and inherits from the System.EventArgs class. Public Properties CancelEdit Gets or sets whether the edit operation should be cancelled. This property can be set both before and after the item is edited. Item Gets the zero-based index into the list view’s Items collection of the ListViewItem to be edited. Label Gets the new text to assign to the label of the indicated item. INITIATE LABEL EDITING Action Result 1 In the MainForm.cs [Design] window, set the LabelEdit property of the ListView control to true. Item labels in the list view may now be edited. 2 Add a Name menu to the top of the Edit menu. 3 Add a Click event handler for this menu. private void menuEditLabel_Click (object sender, System.EventArgs e) { 4 Within this handler, if an item is selected, edit the item. if (listViewMain.SelectedItems.Count == 1) listViewMain.SelectedItems[0].BeginEdit(); } Note: This code only edits the label if a single item is selected. While we do not permit multiple items to be selected in our ListView control, this code establishes an appropriate behavior in case such selection is ever permitted in the future. 5 Add a KeyDown event handler for the ListView control. private void listViewMain_KeyDown (object sender, System.Windows. Forms.KeyEventArgs e) { Settings Property Value (Name) menuEditLabel Text &Name 470 CHAPTER 14 LIST VIEWS That’s all it takes to begin an edit. The actual work of interacting with the user is han- dled by the framework. When the user is finished, we can pick up the result in an AfterLabelEdit event handler. There is also a BeforeLabelEdit event that is useful for selectively permitting an edit or altering an item before the edit begins. For our purposes, the AfterLabelEdit event will suffice. 6 If the F2 key is pressed and an item is selected, edit the item. if (e.KeyCode == Keys.F2) { if (listViewMain.SelectedItems.Count == 1) { listViewMain.SelectedItems[0]. BeginEdit(); e.Handled = true; } } } INITIATE LABEL EDITING (continued) Action Result PROCESS A LABEL EDIT Action Result 7 Add an AfterLabelEdit event handler for the ListView control. private void listViewMain_AfterLabelEdit (object sender, System.Windows. Forms.LabelEditEventArgs e) { 8 If the user cancelled the edit, then we are finished. Note: For example, if the user presses the Esc key dur- ing editing, this handler is invoked with a null label. if (e.Label == null) { // Edit cancelled by the user e.CancelEdit = true; return; } 9 In this handler, locate the item to be edited. ListViewItem item = listViewMain.Items[e.Item]; 10 Update the album name, and cancel the edit if an error occurs. Note: Once again we sepa- rate the logic to operate on our album into a separate method. if (UpdateAlbumName(e.Label, item) == false) e.CancelEdit = true; } SELECTION AND EDITING 471 This code uses some methods from the Path and File classes to manipulate the file name strings and rename the album file. Our application now supports displaying album properties and editing of album labels. The next topic of discussion is item activation. 11 Add the UpdateAlbumName method to update the title of the album. private bool UpdateAlbumName (string newName, ListViewItem item) { string fileName = item.Tag as string; string newFileName = RenameFile(fileName, newName, ".abm"); if (newFileName == null) { MessageBox.Show( "Unable to rename album to this name."); return false; } // Update Tag property item.Tag = newFileName; return true; } 12 Implement the RenameFile method to construct the new name for the file. private string RenameFile (string origFile, string newBase, string ext) { string fileName = Path. GetDirectoryName(origFile) + "\\" + newBase; string newFile = Path.ChangeExtension(fileName, ext); 13 Rename the file using the Move method in the File class. try { File.Move(origFile, newFile); return newFile; } 14 Return null if an error occurs. catch (Exception) { // An error occurred return null; } } PROCESS A LABEL EDIT (continued) Action Result How-to a. Retrieve the file name from the Tag property for the item. b. Rename the file using a pri- vate method that returns the new name. c. Inform the user if the file could not be renamed. d. Otherwise, update the Tag property with the new name. How-to a. Use the GetDirecto- ryName method to retrieve the directory for the file. b. Use the ChangeExtension method to ensure the file has the correct extension. 472 CHAPTER 14 LIST VIEWS 14.5 ITEM ACTIVATION As you might expect, item activation is the means by which an item is displayed or otherwise activated by the control. Normally, activation is just a fancy way to say double-click. In our ListBox class in chapter 10, we activated an item in the list by handling the DoubleClick event and displaying the properties dialog associated with the item. Such behavior is activation. The reason for the fancy term is that the ListView class allows activation other than a double-click to be supported. The Activation property determines the type of activation supported, based on the ItemActivation enumeration. The possible values for this enumeration are shown in .NET Table 14.8. Note that the OneClick style is similar to an HTML link in a Web browser. In our program, we will stick with the standard activation. Regardless of how items are activated, an ItemActivate event occurs whenever an item is activated. The event handler for this event receives a standard System.EventArgs parameter, so the activated item is obtained from the SelectedItems collection. The activation behavior for our MyAlbumExplorer application will display the Photographs in the selected album. This is a rather complicated change, since the columns and list item behavior must now accommodate the display of both albums and photos here. The fact that we were careful to separate much of the album logic into individual methods along the way will help us keep our code straight. Figure 14.6 shows our application with photographs displayed in the ListView control. These photographs are sorted by the date each photo was taken. The icon used here might not be your first choice for a photograph icon, but it will suffice for our purposes. If you find another icon you prefer, or are feeling creative, you can use an alternate icon in your application. .NET Table 14.8 ItemActivation enumeration The ItemActivation enumeration specifies the type of activation supported by a control. This enumeration is part of the System.Windows.Forms namespace. Enumeration Values OneClick A single click activates an item. The cursor appears as a hand pointer, and the item text changes color as the mouse pointer passes over the item. Standard A double-click activates an item. TwoClick A double-click activates an item, plus the item text changes color as the mouse pointer passes over the item. ITEM ACTIVATION 473 14.5.1 H ANDLING ITEM ACTIVATION The ultimate goal here is to display either the list of albums or the list of photos in an album within our ListView control. To do this, we must keep track of whether albums or photographs are currently shown in the view, and whether the PhotoAl- bum object corresponds to the view when photographs are displayed. The following steps create private fields in our Form to track this information, and also implement an event handler for the ItemActivate event. Once these are available, we will look at the additional steps required to fully support activation. Set the version number of the MyAlbumExplorer application to 14.5. Figure 14.6 In this detailed view of Photographs, note how three dots auto- matically appear when the text length exceeds the width of the column. HANDLE THE ITEMACTIVATE EVENT FOR THE LIST VIEW Action Result 1 Add private fields to track the current ListView control contents in the MainForm.cs code window. private bool _albumsShown = true; private PhotoAlbum _album = null; 2 Add an ItemActivate event handler to the ListView control. private void listViewMain_ItemActivate (object sender, System.EventArgs e) { 3 If albums are currently shown and an item is selected, then open the album corresponding to the selected item. if (_albumsShown && listViewMain.SelectedItems.Count > 0) { ListViewItem item = listViewMain.SelectedItems[0]; string fileName = item.Tag as string; // Open the album for this item PhotoAlbum album = null; if (fileName != null) album = OpenAlbum(fileName); if (album == null) { MessageBox.Show("The photographs for " + "this album cannot be displayed."); return; } 4 If the album loads successfully, load the album’s photographs into the list view. // Switch to a photograph view LoadPhotoData(album); } } 474 CHAPTER 14 LIST VIEWS Of course, we need to implement the LoadPhotoData method that appears in this code. This method should set up the view to display photographs, including an appropriate set of columns, and reset the list of items to hold the set of photographs. Once this is done, there is also the support we created for our albums that must now be implemented for photographs. To help us keep our facts straight, let’s make a list of the tasks we need to perform here. • Define new columns for displaying photographs. • Populate the ListView control with the photographs in the album. • Support column sorting. • Display the photo properties dialog. • Support item editing on photographs. • Allow the user to select the desired view, albums or photos. We will cover each of these topics in a separate section, in the same order as shown here. 14.5.2 D EFINING NEW COLUMNS As you’ll recall, we defined the list of columns for our control using the Column- Header Collection Editor dialog in Visual Studio .NET. Now that we need to display different columns depending on what is displayed, this method no longer makes sense. Instead, we will create the columns programmatically in the LoadAlbumData method. Our new LoadPhotoData method we have yet to implement will define the columns for displaying photographs. The easiest way to add columns to a ListView control programmatically is through the Columns property. The following steps remove the columns we created in Visual Studio and will add them via the LoadAlbumData method. CREATE THE ALBUM COLUMNS PROGRAMMATICALLY Action Result 1 In the MainForm.cs [Design] window, remove the four columns currently defined for the Columns property. How-to Use the ColumnHeader Collection Editor dialog. Note: This is not strictly required since we clear the contents of the list, including the column defini- tions, as part of the next step. Reducing unneces- sary clutter in your code is always a good idea, so performing this step makes sense. 2 Modify the LoadAlbumData method to initially clear the existing contents of the control. private void LoadAlbumData(string dir) { listViewMain.Clear(); 3 Reset the fields that track the current album. _albumsShown = true; if (_album != null) { _album.Dispose(); _album = null; } ITEM ACTIVATION 475 The Columns property refers to a ColumnHeaderCollection object. This collec- tion class includes an Add method that creates a new column for the control. One version of this method simply accepts a ColumnHeader class instance. Our code uses a slightly more convenient form, with the following signature: void Add(string columnText, int width, HorizontalAlignment align); We can use this same method to add columns when photographs are displayed. The following table summarizes the columns we will use for this purpose. The following table defines constants for our new albums as well as the beginnings of our LoadPhotoData implementation. This table continues our previous steps. 4 Define the columns for the control before the album items are loaded. How-to Use the Add method available through the Columns property for the control. // Define the columns listViewMain.Columns.Add("Name", 80, HorizontalAlignment.Left); listViewMain.Columns.Add("Title", 100, HorizontalAlignment.Left); listViewMain.Columns.Add("Pwd", 40, HorizontalAlignment.Center); listViewMain.Columns.Add("Size", 40, HorizontalAlignment.Right); // Load the albums into the control . . . } CREATE THE ALBUM COLUMNS PROGRAMMATICALLY (continued) Action Result Columns for displaying photographs Column Text Description 0 Caption The caption for this photo. 1 Taken The date the photograph was taken. 2 Photographer The photographer for this photo. 3 File Name The fully qualified image file name. CREATE THE PHOTO COLUMNS PROGRAMMATICALLY Action Result 5 In the MainForm.cs code window, create constants to hold the positions of the columns when photographs are displayed. private const int PhotoCaptionColumn = 0; private const int PhotoDateTakenColumn = 1; private const int PhotoPhotographerColumn = 2; private const int PhotoFileNameColumn = 3; 6 Add a private LoadPhotoData method. private void LoadPhotoData(PhotoAlbum album) { [...]... this.treeViewMain.Nodes.AddRange(new System .Windows. Forms. TreeNode[] { new System .Windows. Forms. TreeNode("Default Albums", 5, 5, new System .Windows. Forms. TreeNode[] { new System .Windows. Forms. TreeNode("Album 1", new System .Windows. Forms. TreeNode[] { new System .Windows. Forms. TreeNode("Photo 1", 0, 3) }), new System .Windows. Forms. TreeNode("Album 2"), new System .Windows. Forms. TreeNode("Album 3") }) }); This code uses various forms of the... horizontal splitter // ( not part of our final application ) this.listViewMain.Dock = System .Windows. Forms. DockStyle.Fill; this.treeViewMain.Dock = System .Windows. Forms. DockStyle.Top; this.treeViewMain.Size = new System.Drawing.Size(392, 100); // // splitter1 // this.splitter1.Dock = System .Windows. Forms. DockStyle.Top; this.splitter1.Location = new System.Drawing.Point(100, 0); this.splitter1.MinExtra... is an excerpt of the InitializeComponent method generated by Visual Studio NET for our form private void InitializeComponent() { this.listViewMain.Dock = System .Windows. Forms. DockStyle.Fill; this.treeViewMain.Dock = System .Windows. Forms. DockStyle.Left; this.treeViewMain.Size = new System.Drawing.Size(100, 253); // // splitter1 // this.splitter1.Location = new System.Drawing.Point(100, 0);... modify the AfterLabelEdit event handler to call a new UpdatePhotoCaption method to process an edit when photographs are displayed Result private void listViewMain_AfterLabelEdit (object sender, System .Windows Forms. LabelEditEventArgs e) { if (e.Label == null) { // Edit cancelled by the user e.CancelEdit = true; return; } ListViewItem item = listViewMain.Items[e.Item]; if (this._albumsShown) e.CancelEdit... 0); this.splitter1.MinExtra = 100; this.splitter1.Size = new System.Drawing.Size(3, 253); // // MainForm // this.ClientSize = new System.Drawing.Size(392, 253); this.Controls.AddRange(new System .Windows. Forms. Control[] { this.listViewMain, this.splitter1, this.treeViewMain}); } In the AddRange call made within this code, note how the Splitter control “splits” the Control array added to the Controls... provide a graphical indication of the nature or purpose of the item Items in the tree are referred to as nodes, and each node is represented by a TreeNode class instance This class is part of the System .Windows. Forms namespace, and inherits from the Control class See NET Table 4.1 on page 104 for a list of members inherited by this class CheckBoxes HideSelection Gets or sets an ImageList to associate with... child nodes A top-level node in a TreeView object is called a root node of the tree Each TreeNode object can be contained by exactly one TreeView or TreeNode object This class is part of the System .Windows. Forms namespace, and inherits from the System.MarshalByRefObject class TreeNode Initializes a new TreeNode instance Overloads TreeNode(string label); TreeNode(string label, TreeNode[] childNodes);... represents a control that divides a container into two sections Each section contains a docked control, and the splitter permits the user to resize each section at runtime This class is part of the System .Windows. Forms namespace, and inherits from the Control class See NET Table 4.1 on page 104 for a list of members inherited by this class BorderStyle Gets or sets the border style for the control Cursor (overridden... style format when the mouse hovers over a node, as was done for the “From the Walking Path” entry in this figure The explorer-style interface shown in the figure and used by other applications such as Windows Explorer is a common use of the TreeView and ListView classes In this chapter we build such an interface by extending the MyAlbumExplorer project constructed in chapter 14 15.2 THE TREEVIEW CLASS... contain nodes, which may contain other nodes, which may contain still other nodes, and so forth Each node in the tree is represented by a TreeNode object This class is summarized in NET Table 15.3 In the Windows Explorer application, for example, each directory is represented as a tree node, and may contain other directories or files CHA PTE R 15 TREE VIEWS .NET Table 15.3 TreeNode class The TreeNode class . and AfterLabelEdit event handlers for the ListView class. This class is part of the System .Windows. Forms namespace, and inherits from the System.EventArgs class. Public Properties CancelEdit. handler for the ListView control. private void listViewMain_KeyDown (object sender, System .Windows. Forms. KeyEventArgs e) { Settings Property Value (Name) menuEditLabel Text &Name 470 CHAPTER. for the ListView control. private void listViewMain_AfterLabelEdit (object sender, System .Windows. Forms. LabelEditEventArgs e) { 8 If the user cancelled the edit, then we are finished. Note: