Добавил:
Опубликованный материал нарушает ваши авторские права? Сообщите нам.
Вуз: Предмет: Файл:

Visual CSharp .NET Developer's Handbook (2002) [eng]

.pdf
Скачиваний:
42
Добавлен:
16.08.2013
Размер:
4.94 Mб
Скачать

if (ClipboardData.GetDataPresent(DataFormats.Text))

{

// Get the data and place it in a string.

PasteData = ClipboardData.GetData(DataFormats.Text).ToString();

//Paste it in the current data grid cell. AddrDataGrid[AddrDataGrid.CurrentCell] = PasteData;

//Begin the editing process.

Column = AddrDataGrid.CurrentCell.ColumnNumber;

DGCS = AddrDataGrid.TableStyles[0].GridColumnStyles[Column]; AddrDataGrid.BeginEdit(DGCS, AddrDataGrid.CurrentRowIndex);

}

}

private void AddrDataGrid_CursorChanged(object sender, System.EventArgs e)

{

int Row; // Current data grid row IDataObject ClipboardData; // Contents of the clipboard.

// Get the current row.

Row = AddrDataGrid.CurrentCell.RowNumber;

//Ensure everything is enabled to start. mnuEditCut.Enabled = true; mnuEditCopy.Enabled = true; mnuEditPaste.Enabled = true; GridViewToolbar.Buttons[11].Enabled = true; GridViewToolbar.Buttons[12].Enabled = true; GridViewToolbar.Buttons[13].Enabled = true;

//Determine if the row is selected. If so, then turn off the cut

//and paste buttons since we can't perform either task on a row. if (AddrDataGrid.IsSelected(Row))

{

mnuEditPaste.Enabled = false; mnuEditCut.Enabled = false; GridViewToolbar.Buttons[11].Enabled = false; GridViewToolbar.Buttons[13].Enabled = false;

}

//We also need to check for an empty clipboard. An empty clipboard

//means we can't paste anything. It's also possible the clipboard

//contains an incompatible data type, so we need to check that as

//well.

ClipboardData = Clipboard.GetDataObject(); if (ClipboardData == null ||

!ClipboardData.GetDataPresent(DataFormats.Text))

{

mnuEditPaste.Enabled = false; GridViewToolbar.Buttons[13].Enabled = false;

}

}

The DoCut() method only works with a single cell. It uses the Clipboard.SetDataObject() method call to place the current text on the clipboard. Notice how this example uses a cell, rather than individual row and column references for the AddrDataGrid indexer. After it

saves the contents of the cell to the clipboard, DoCut() places a null string in the current cell. However, placing the value in the cell doesn't make it permanent—an update must occur before that happens. The final three lines of code place the current cell in the editing mode. These steps ensure the user notices a change in the appearance of AddrDataGrid. At this point, if the user presses Escape, the change on AddrDataGrid is reversed. However, the clipboard will still retain the new string value.

Because the DoCopy() method has to work for both single cells and entire rows, the code is actually a little more complex than DoCut(). The code initially determines if the user has selected an entire row or a single cell. The selection of a single cell means the DoCopy() method can simply place the value of the current cell on the clipboard without further processing. However, when working with a row of data, you must create a string containing all of the column values first. The default copy function places a tab between each field entry, but you can alter this technique to suit any requirements your application might have.

This is another situation where you can improve the performance of your application using a StringBuilder object in place of a standard string. The code appends each column value to SelectedText. If the code detects that this isn't the last column, it also adds a tab to SelectedText. The processing continues until all of the columns have been processed and DoCopy() places the value in SelectedText on the clipboard.

Creating the DoPaste() method is the most difficult part of the coding process. Placing data on the clipboard isn't hard because you don't have to worry about problems such as data type— the clipboard takes care of the issue for you. However, when you're pasting data from the clipboard, you need to know that the data is the correct type. The first task the DoPaste() method performs is checking the data type of the clipboard data. If the data type isn't text, then the method exits without doing anything else.

Note Notice that ClipboardData is of the IDataObject type. Most developers who have worked with COM know that this is a common COM interface. However, the IDataObject in the .NET Framework isn't a substitute for the COM interface. It does work in this situation, but probably won't work if you're creating a DLL to interact with COM in some way.

The DoPaste() method obtains the current clipboard contents using the GetData() method. Notice that you must specify a data format using the DataFormats enumeration. DoPaste() follows a process similar to DoCut() at this point. It makes the current cell value equal to the PasteData value obtained from the clipboard, and then places the cell in edit mode. If the user presses Escape, AddrDataGrid will reverse the change.

There's one last problem to consider for this scenario. The user depends on visual cues from the menu and toolbar to determine when it's possible to cut, copy, or paste data. This user assumption means you need some way to enable and disable both the toolbar buttons and the menu commands. You could select a number of ways to detect changes in the settings, but I chose to attach the code to the AddrDataGrid CursorChanged() event.

The code begins by enabling all of the affected buttons and menu entries. This step ensures the menu command and toolbar buttons are accessible. The code then checks for a selected row. If the user has selected a row, the code disables the cut and paste options because they only work with a single cell. Finally, the code checks the data type of the clipboard data. If the

clipboard is empty or it contains data of the wrong type, then the code disables the paste function. The application can only accept text as input.

Coding the Detail View

The Detail view found in Chapter 11 is functional, but barely. You can move between records and edit existing records. Any change in the Detail view is also synchronized with the Grid view. However, if you want to add or remove records, you need to do it in the Grid view. Printer support is likewise limited.

This section of the chapter discusses a few enhancements you can add to the Detail view to make it more functional. We'll discuss what you need to do in order to add and remove records. You'll also learn about a single-record print routine that's based on data in the Detail view, instead of the data found in the Grid view. These customizations move the Detail view from barely functional to quite practical.

Adding and Removing Records in Detail View

As with other database operations for the Detail view, the Grid view already has the required code. What you really need is a means of accessing that code through the Detail view controls. Of course, the first task is to add the required controls to the form so that the user can click them as needed. Make sure you change the Modifiers property to Public so the button's Click() event is accessible from the Grid view.

All of the code for btnAdd and btnDelete appear in FrmGridView.cs file, not the FrmDetailView.cs file as you might expect. The first task is to create a connection to the Detail view buttons by adding event handlers. The following code appears in the AddrDataGrid_DoubleClick() method to ensure the event handlers aren't created unless actually needed.

// Create event handlers for adding and removing records. DetailDlg.btnAdd.Click += new EventHandler(DoDetailAdd); DetailDlg.btnDelete.Click += new EventHandler(DoDetailDelete);

The event handlers have to do more than their Grid view counterparts. We discussed the synchronization issues in the "Synchronizing the Grid View and Detail View" section of Chapter 11. In addition to synchronization issues, you also need to consider data update issues. The event handler must ensure that the data that the Detail view sees is the same as the data in the Grid view. Here's the code for the two event handlers.

private void DoDetailAdd(object sender, System.EventArgs e)

{

//Add a new record. DoAddRecord();

//Ensure the record is recorded. AddressDA.Update(addressDataset1);

//Fill the AddrDataGrid variables. ExchangeFill();

//Fire the DataChanged event. DataChanged();

}

private void DoDetailDelete(object sender, System.EventArgs e)

{

//Remove the current record. DoDeleteRecord();

//Ensure the record is recorded. AddressDA.Update(addressDataset1);

//Fill the AddrDataGrid variables. ExchangeFill();

//Fire the DataChanged event. DataChanged();

}

As you can see, the code calls upon the same DoAddRecord() or DoDeleteRecord() methods used by the Grid view to add or remove a record. The code then calls AddressDA.Update() to ensure the dataset contains the correct information. This might seem like overkill, but the need is real. The last two steps place data in a location where the Detail view can see it and then fires an event that will force the Detail view to perform an update.

Printing in Detail View

We've already looked at quite a few printing techniques in the book. However, it's important to realize that users will anticipate certain types of print jobs when looking at specific views of their data. For example, when looking at the Detail view, users are likely to want an individual record printout, rather than a list of contacts in the database. The example code contains such a report. We'll discuss some of the highlights of that report in this section.

Individual report records are somewhat easier to print than multiple record reports because you don't need to consider any complex looping structures. All you need is the data for a single record. Of course, the user is likely to want complete information for that single record, so you'll need to address the full range of data input. The formatting is going to be different as well. Using a structure similar to the Detail view for this example works well in most cases.

One of the first changes that you'll notice in this version of the print routine is that the heading is smaller and relies on both bold and italic font attributes. It's easy to combine font attributes by or-ing them together. Here's an example of multiple attribute use:

headFont = new Font("Arial", 14, FontStyle.Bold | FontStyle.Italic);

Another problem with individual record reports is that you need to print items on single lines. This change from the tabular view used in the Grid view portion of the application means you'll calculate the yPos value more often. Rather than incur the cost of constant recalculation of the incremental difference between lines, you should calculate it once as shown here:

// Determine the yIncrement value. yIncrement = columnFont.GetHeight() + 10;

Users will also expect friendly text in the report. For example, you wouldn't want to place a check box on the printed report for the BUSINESS field. Likewise, a value of true or false is

unattractive. You can solve the problem by using a simple comparison and printing out the desired text as shown here:

ev.Graphics.DrawString("Business Contact?", columnFont, Brushes.Black,

20,

yPos); if (cbBusiness.Checked)

ev.Graphics.DrawString("Yes",

docFont,

Brushes.Black,

180,

yPos);

else ev.Graphics.DrawString("No",

docFont,

Brushes.Black,

180,

yPos);

yPos = yPos + yIncrement;

Finally, you'll need some large text areas to contain notes and other memo fields in your database. The way to perform this task is to specify a rectangular area as part of the DrawString() call as shown here:

ev.Graphics.DrawString("Notes:",

columnFont,

Brushes.Black,

20,

yPos);

ev.Graphics.DrawString(txtNotes.Text,

docFont,

Brushes.Black, new Rectangle(

180,

(Int32)yPos, (Int32)ev.Graphics.VisibleClipBounds.Width –

180, (Int32)ev.Graphics.VisibleClipBounds.Height –

(Int32)yPos));

yPos = yPos + yIncrement;

Because the print routine only needs the rectangle one time, the code declares the rectangle directly, rather than as a separate object. This technique tends to reduce resource usage and the time before the CLR frees the rectangle. Notice how the various rectangle boundaries are calculated. Make sure you take any offsets into consideration when designing your print routine.

Importing and Exporting XML Example

As applications develop into distributed systems, the need for some method of data exchange becomes more important. Microsoft has become convinced that various forms of XML are the answer, so they're making every effort to make their applications and application development platforms XML-enabled. This XML development extends Visual Studio .NET and the .NET

Framework. You'll find a gold mine of XML development capabilities that you can easily access using C#.

This section provides a quick overview of one XML capability—the ability to import and export data in XML format using a dataset. We'll study XML in more detail in Chapter 16. However, this section is important now because it creates the first link between the world of distributed applications and the world of the desktop. This section provides you with a glimpse of the possibilities for applications that you wouldn't normally associate with the Internet. You'll find this example in the \Chapter 13\XMLInOut folder on the CD.

Writing the Code

The example performs two tasks. First, it enables the user to export the data found in the Movie database used earlier in the chapter to an XML file. Second, it enables the user to import data from a second XML file named SampleData.XML. The input and output files will appear in the \Chapter 13\XMLInOut\bin\Debug folder on the CD. Listing 13.5 shows the code we'll use in this case.

Listing 13.5: Importing and Exporting XML is Easy in .NET

private void btnImport_Click(object sender, System.EventArgs e)

{

// Create a data stream.

StreamReader

SR = new StreamReader("SampleData.xml");

// Create a dataset and associated table.

DataTable

DT = new DataTable("NewDataElement");

DataSet

DS = new DataSet("NewData");

// Configure the table.

DT.Columns.Add(new DataColumn("Data1", typeof(string))); DT.Columns.Add(new DataColumn("Data2", typeof(string))); DT.Columns.Add(new DataColumn("Data3", typeof(string)));

// Configure the dataset. DS.Tables.Add(DT);

// Import the data. DS.ReadXml(SR);

// Display the new data. dataGrid1.DataSource = DS; dataGrid1.DataMember = "NewDataElement"; dataGrid1.Update();

// Close the stream. SR.Close();

}

private void btnExport_Click(object sender, System.EventArgs e)

{

// Create a data stream.

StreamWriter SW = new StreamWriter("MovieExport.xml");

// Output the data. movieDS1.WriteXml(SW);

//Close the stream. SW.Close();

//Create a second data stream.

SW = new StreamWriter("MovieExportSchema.xml");

//Output the schema. movieDS1.WriteXmlSchema(SW);

//Close the second stream. SW.Close();

}

The btnImport_Click() method begins by creating a StreamReader object (SR). SR will open the XML file for reading. The code then creates the DataTable (DT) and the DataSet (DS) objects used to cache the data locally. Configuring the table consists of adding three columns, one of each of the columns in the sample XML file. Once DT is configured, the code adds it to DS. Reading the data is as easy as calling the ReadXml() method.

As we'll see later, the name of the DataTable must match the name of the XML elements within the sample file. Likewise, the column names must match the names of the XML child elements used to store the data. Otherwise, you'll find that the reading process works as anticipated, but you won't see any data on screen.

The final part of the process is to modify the DataSource and DataMember properties of dataGrid1. The call to Update() ensures the new data appears on screen, as shown in Figure 13.6.

Figure 13.6: Modifying the DataSource and DataMember properties enables dataGrid1 to display the imported data.

The btnExport_Click() method begins by creating a StreamWriter object (SW). The WriteXml() method of movieDS1 outputs the data in XML format to the file opened by SW. Note that all you need to change to output the dataset schema instead of the data is call WriteXmlSchema().

Using the Microsoft XML Notepad

XML is almost, but not quite, readable by the average human. Reading simple files is almost trivial, but once the data gets nested a few layers deep, reading it can become tiresome. That's

why you should have a tool for reading XML in your developer toolkit. The only problem is that some of these tools cost quite a bit for the occasional user. Microsoft has remedied this problem a little with the introduction of XML Notepad (http://msdn.microsoft.com/library/default.asp?url=/library/enus/dnxml/html/xmlpaddownload.asp). This utility is free for the price of a download and does a reasonable job of reading most XML files. (Microsoft hasn't bothered to update the date for this site, but be assured that XML Notepad runs fine under both Windows 2000 and Windows XP.)

When you start XML Notepad, you'll see a blank project. Use the File Open command to display an Open dialog that allows you to open XML files from a local drive or from a website. All you need is a filename (and path) or a URL to get started.

Figure 13.7 shows the output of the sample XML application. Notice that the name of the elements matches the name of the table for the movie database. Likewise, each of the child elements matches the name of one of the fields within the table. The right pane shows the data contained within each one of the child elements.

Figure 13.7: The names of the elements are important when working with exported data in XML format.

Creating new data for testing purposes is relatively painless once you see the exported data from an existing database. Create a blank project using the File New command. Type the name of the dataset in the Root object. Rename the first element to reflect the new table. Add a new child element using the options on the Insert menu. You'll notice that the first element changes into a folder. Type the name of the first data column in this element. Add additional columns as needed until you complete one record's worth of entries. Finally, type values for each of the child elements.

Now that you have one complete record, you can use the Duplicate command to create copies of it. Each copy will become one record within the XML database. Figure 13.8 shows the structure and contents of the SampleData.XML file.

Figure 13.8: Creating an XML database using XML Notepad is relatively easy as long as you follow a few rules.

As you can see, XML Notepad doesn't have some of the bells and whistles of high-end products such as XML Spy (http://www.xmlspy.com/), but it's a good alternative if you only use an XML editor occasionally and don't want to spend a lot of money. The important consideration is that you have an XML editor that you can use to view the output from your applications.

Using StringBuilder to Improve Performance

Strings are unusual objects in C# in that they're immutable—you can't change them. Whenever you assign a new value to a string, C# deletes the old string and creates a new one. This includes any additions to the string. For example, the following event handler actually contains eight unique copies of MyString, even though all of the strings have the same name and you can't access them individually. (You'll find the test application shown in this section in the \Chapter 13\StringBuilder folder on the CD.)

private void btnTest1_Click(object sender, System.EventArgs e)

{

// Create some objects and values.

string

MyString = "Hello";

string

NewString = " World";

int

NewInt = 3;

float

NewFloat = 4.5F;

bool

NewBool = true;

//Append values to the StringBuilder. MyString = MyString + NewString;

MyString = MyString + "\r\nInteger Value: "; MyString = MyString + NewInt.ToString(); MyString = MyString + "\r\nFloat Value: "; MyString = MyString + NewFloat.ToString(); MyString = MyString + "\r\nBoolean Value: "; MyString = MyString + NewBool.ToString();

//Display the result.

MessageBox.Show(MyString.ToString(), "Standard String Output", MessageBoxButtons.OK, MessageBoxIcon.Information);

}

C# will destroy the copy of MyString on the right side of the equation before it creates the new copy of MyString on the left side in each case. Your code only sees one MyString, but eight copies actually exist at different times, which makes this form of the code relatively resource intensive. In short, making string assignments could become costly if you perform this task more than a few times in your code. While the Garbage Collector will automatically recover the memory used by the old versions of a string, creating, deleting, and managing the strings does incur a performance penalty.

It's unlikely that you'll ever see the performance penalty of using strings in a desktop application (unless you literally handle hundreds of strings in the application), but the performance cost is real enough that you can notice the penalty in a server application. A StringBuilder object helps you get around this performance problem by enabling your application to use a single object to handle string manipulations. For example, the following code uses one copy of MyString in place of the eight that we used earlier.

private void btnTest2_Click(object sender, System.EventArgs e)

{

// Create some

objects and values.

StringBuilder

MyString = new StringBuilder("Hello");

string

NewString = " World";

int

NewInt = 3;

float

NewFloat = 4.5F;

bool

NewBool = true;

//Append values to the StringBuilder. MyString.Append(NewString); MyString.Append("\r\nInteger Value: "); MyString.Append(NewInt); MyString.Append("\r\nFloat Value: "); MyString.Append(NewFloat); MyString.Append("\r\nBoolean Value: "); MyString.Append(NewBool);

//Display the result. MessageBox.Show(MyString.ToString(),

"StringBuilder Output",

MessageBoxButtons.OK,

MessageBoxIcon.Information);

}

It's interesting to note that a StringBuilder object also has greater flexibility than a standard string. You must convert all other objects to a string before you can add them to a standard string. Notice that the StringBuilder example has no such requirement, you can Append() any of data types that StringBuilder supports directly (which includes most value types). StringBuilder supports the AppendFormat() method that enables you to add other object types to the string without prior conversion. For example, the following code adds a number in several formats to a StringBuilder object.

private void btnTest3_Click(object sender, System.EventArgs e)

{