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

Visual CSharp .NET Programming (2002) [eng]

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

Note In the code shown in Listing 10.9, it is assumed that the user has selected a file to write to, using a common dialog, with the name saved in the variable theFile.

First, a stream is instantiated, this time for the sake of variety using the File.OpenWrite method:

theStream = File.OpenWrite(theFile);

Using a FileStream, the only type we get to work with is bytes. So let's change our text to bytes!

An array of bytes is created, and the text to be saved is converted into the array using an instance of the ASCIIEncoding class (found in the System.Text namespace):

byte [] buffer = new Byte[SizeBuff]; ASCIIEncoding byteConverter = new ASCIIEncoding(); buffer = byteConverter.GetBytes (txtToSave.Text);

Finally, the byte array is written to the stream:

theStream.Write (buffer, 0, buffer.Length);

and the stream closed:

theStream.Close();

This is actually considerably simpler using a BinaryWriter-we can just directly write the string type to the file:

theStream = File.OpenWrite(theFile); bw = new BinaryWriter (theStream); bw.Write (txtToSave.Text);

Listing 10.10 shows the click event (once again, without the common dialog portion) that uses a BinaryWriter to save text to a binary file.

Listing 10.10: Writing Text to a Binary File Using a BinaryWriter

using System.IO;

...

Stream theStream; BinaryWriter bw;

const int SizeBuff = 2048;

...

private void btnSaveStream_Click(object sender, System.EventArgs e) { string theFile;

... // Common dialog stuff

theFile = saveFileDialog1.FileName; try {

theStream = File.OpenWrite(theFile); bw = new BinaryWriter (theStream); bw.Write (txtToSave.Text);

catch (Exception excep) { MessageBox.Show (excep.Message);

}

finally { bw.Flush(); bw.Close(); theStream.Close();

}

}

Listing 10.11 shows how you can use a BinaryReader's ReadString method to read a binary file containing string data (once again, the material related to the common dialog is left out of the listing for clarity).

Listing 10.11: Reading String Data from a Binary File Using a BinaryReader

using System.IO;

...

Stream theStream; BinaryReader br;

const int SizeBuff = 2048;

private void btnRetrieveStr_Click(object sender, System.EventArgs e) { string theFile;

... // Common Dialog code omitted theFile = openFileDialog1.FileName; try {

theStream = File.OpenRead(theFile); br = new BinaryReader (theStream); txtToSave.Text = br.ReadString();

}

catch (Exception excep) { MessageBox.Show (excep.Message);

}

finally { br.Close(); theStream.Close();

}

}

Listing 10.12 shows how you could achieve the same thing using the FileStream without the BinaryReader, and reading the text into a byte array and then converting it to a string.

Listing 10.12: Using a FileStream to Read a Byte Array, and Converting It to String

using System.IO; using System.Text;

...

Stream theStream;

const int SizeBuff = 2048;

private void btnRetrieveStr_Click(object sender, System.EventArgs e) { string theFile;

... // Common Dialog code omitted theFile = openFileDialog1.FileName;

try {

theStream = File.OpenRead(theFile); byte [] buffer = new Byte[SizeBuff]; FileInfo fi = new FileInfo (theFile); int byteCount = (int) fi.Length; theStream.Read (buffer, 0, byteCount);

ASCIIEncoding byteConverter = new ASCIIEncoding(); txtToSave.Text = byteConverter.GetString (buffer);

}

catch (Exception excep) { MessageBox.Show (excep.Message);

}

finally { theStream.Close();

}

}

Web Streams

It's quite easy to read a web page as a stream using the classes in the System.Net namespace and a StreamReader. (This is a process sometimes called "screen scraping.")

To start with, use the WebRequest.Create method to create an HttpWebRequest object (using a URL as an argument):

HttpWebRequest wreq = (HttpWebRequest) WebRequest.Create(theURL);

Note WebRequest.Create is a static method that returns different object instances depending on what is passed in. Since the return type of the method is WebRequest, it must be cast to HttpWebRequest.

The HttpWebRequest statement establishes a connection to a web page. Next, the GetResponse method of the HttpWebRequest object is used to load the connected page into an HttpWebResponse:

HttpWebResponse wres = (HttpWebResponse) wreq.GetResponse();

Using the HttpWebResponse object, a StreamReader can be created:

sr = new StreamReader (wres.GetResponseStream(), Encoding.Default);

This StreamReader can be used like any other StreamReader. Specifically, its ReadToEnd method can be used to load the contents of the web page into a TextBox:

txtResults.Text = sr.ReadToEnd()

The results of reading a web page into the TextBox are shown in Figure 10.11, and the complete click event code for doing so is shown in Listing 10.13.

Figure 10.11: You can use a FileStream to read the contents of a web page. Listing 10.13: Reading a Web Page as a Stream

using System.IO; using System.Net; using System.Text;

...

StreamReader sr; HttpWebRequest wreq;

private void btnEngage_Click(object sender, System.EventArgs e) { string theURL = txtURL.Text;

txtResults.Text = String.Empty; try {

HttpWebRequest wreq = (HttpWebRequest) WebRequest.Create(theURL); HttpWebResponse wres = (HttpWebResponse) wreq.GetResponse();

sr = new StreamReader (wres.GetResponseStream(), Encoding.Default); txtResults.Text = sr.ReadToEnd();

}

catch (Exception excep) { MessageBox.Show (excep.Message);

}

finally { sr.Close();

}

}

Warning Obviously, using this technique, you can 'screen scrape' any web page. I generally hate hectoring adults. However, please be sure that you have copyright permission to use the text and HTML of any page that you choose to retrieve this way.

Asynchronous I/O

In Chapter 3, 'Windows Uses Web Services, Too!,' I showed you the general design pattern for asynchronous invocation, in the context of web services.

You'll probably not be surprised to learn that the FileStream class supports asynchronous invocation as well-using the same design pattern explained in Chapter 3. This means that

FileStream provides BeginRead and BeginWrite methods that reference an AsyncCallback object. These methods return an IAsyncResult object that can be queried to determine the status of the asynchronous operation.

Asynchronous I/O operations can be performed using any class derived from Stream. In particular, you might want to initiate asynchronous invocation of a FileStream if you had a big file to read and wanted your program to be able to do other things while reading besides twiddling its virtual thumbs.

Listing 10.14 shows an example of asynchronous use of a FileStream in the context of the web streaming example from the last section. While the web page is being read by the FileStream, a display counter is incremented. As shown in Figure 10.12, when the asynchronous read operation completes, a message box is displayed.

Figure 10.12: FileStreams support asynchronous as well as synchronous operation. Listing 10.14: Asynchronously Using a FileStream to Read a Web Page

using System.IO; using System.Net; using System.Text;

...

StreamReader sr; HttpWebRequest wreq;

private void btnAsynch_Click(object sender, System.EventArgs e) { string theURL = txtURL.Text;

long counter = 0; txtResults.Text = String.Empty; try {

AsyncCallback cb = new AsyncCallback (IOCallback); wreq = (HttpWebRequest) WebRequest.Create(theURL); IAsyncResult ar = wreq.BeginGetResponse(cb, null); while (!ar.IsCompleted){

Application.DoEvents(); counter ++;

lblCounter.Text = counter.ToString();

}

MessageBox.Show ("We can do other things!", "Asynch Demo", MessageBoxButtons.OK, MessageBoxIcon.Exclamation);

}

catch (Exception excep) { MessageBox.Show (excep.Message);

}

finally { sr.Close();

}

}

private void IOCallback (IAsyncResult ar){

HttpWebResponse wres = (HttpWebResponse) wreq.EndGetResponse(ar); sr = new StreamReader (wres.GetResponseStream(), Encoding.Default); txtResults.Text = sr.ReadToEnd();

sr.Close();

}

Conclusion

In this chapter, I showed you how to use the .NET Framework classes that provide information about files and the file system. Next, I showed you how to store and retrieve initialization data from the system Registry and isolated storage. Finally, I moved on to the large topic of input and output and streams, and showed you how to read and write information to and from files and other sources.

These topics may not be the most exciting on the planet, but the relationship of your programs to files, the file system, and other sources of streaming data is very important. In addition, use of appropriate initialization information gives you a chance to get your program going with the 'right foot forward' and give a good first impression.

Almost every program needs these things. So I've included this chapter as a good first step towards connecting your projects with the rest of the world.

Chapter 11: Messaging

Overview

Subclassing Windows messages

Understanding MessageQueues

Creating queues and messages

Receiving and replying to messages

Message queues and application architecture

Asynchronous peeking and receiving

With a nod to the singer Madonna, we live in a world that is both material and asynchronous. This is probably a good thing; who among us would want their every activity dictated by a central mechanism, comparable to linear, top-down code execution? At the same time, mechanisms are needed to keep us in touch and to facilitate teamwork. These distributed, peer-to-peer mechanisms are usually asynchronous in real life (for example, you call someone and ask them if they would do something for you).

Successful software starts by mimicking the real-world activity it is intended to aid or replace. (Put differently, if you don't understand the real-world problem, you certainly can't write good

programs to help with it.) This leads to the supposition that programs of any complexity-or groups of programs-ought to operate asynchronously, as real-life processes do. Only the simplest linear, non-event-driven program can really be expected to behave synchronouslymeaning, to always execute commands in a predictable, top-to-bottom fashion, waiting for one command to finish processing before embarking on the next.

Under Windows and .NET, you have several tools that enable asynchronous intra-program design. The principal mechanism is firing events and writing the code that responds to these events. (A less-optimal mechanism is to throw and respond to exceptions, but this is not a recommended practice.)

As you probably know, the mechanism behind Windows itself-and any form that you use to create a Windows application-is a giant, asynchronous messaging loop. When the user moves the mouse, this causes a message to be sent. All the messages that are floating around a Windows application are processed in a giant switch statement, which is always asynchronously waiting for messages-with appropriate action taken when a particular message is encountered.

As an illustration of how a complex program can use messaging to enable asynchronous execution, I'll start this chapter by showing you how to look at the Windows message stream. I'll briefly show you how to intercept these messages-a process called subclassing, although it should not be confused with deriving a class via inheritance.

The main topic of this chapter, then, is how to use the classes in the System.Messaging namespace to add messages to a queue, to retrieve messages from a queue, and, generally, to facilitate asynchronous program design, both intraand inter-program.

Observing Windows Messages

Subclassing is a technique that lets you intercept, observe, and process messages going from Windows to a form or windowing control.

Note The Message class used with Windows, System.Windows.Forms.Message, is different from the Message class used with message queues, System.Messaging.Message, discussed in other sections of this chapter. To avoid an ambiguous reference, you'll need to qualify the Message class when you use it, so the compiler knows which one you are talking about if both the System.Windows.Forms and System.Messaging namespaces are referenced in your project.

In earlier versions of Visual Basic, subclassing was needed as a 'down and dirty' tool to get around limitations of the language. Many things could not be accomplished without subclassing; for example, it was required in order to enforce limits on form size.

There should rarely be a need to use subclassing in the more powerful and rigorous Visual Basic .NET; for example, form size limits can be enforced using the form class MinimumSize and MaximumSize properties. But in some languages and in some situations, subclassing is still an instructive technique for having a look at what is going on behind the curtains in Windows.

The System.Windows.Forms.Form class exposes the WndProc method, which is invoked for every message sent by Windows to the form. Using the Object Browser, as shown in Figure 11.1, we can determine that WndProc is a member of Control, the class from which Form is ultimately derived, and that it is marked protected and virtual-meaning that you can override it in any class derived from Form.

Figure 11.1: Using the Object Browser, one can see that the WndProc procedure has been marked with the protected and virtual attributes.

The Object Browser also tells us (Figure 11.2) that the Message object that is the parameter of WndProc exposes properties. These include Msg, which is a numerical ID; HWnd, which is the handle-or identification-of the window; and LParam and WParam, which are messagespecific arguments. (Another property, Result, is the value that will be returned to Windows.)

Figure 11.2: The members of the System.Windows .Forms.Message class include an ID number.

Listing 11.1 shows the code necessary to display the Windows messages sent to a form's WndProc method in the Output window.

Listing 11.1: Viewing Windows Messages in the Output Window

protected override void WndProc(ref Message m){ base.WndProc(ref m);

Debug.WriteLine(m);

}

Note Be sure to include a using System.Diagnostics directive so that you can use the Debug object.

In most cases, you'll want to start with a call to the base class WndProc method, so that the messages will be processed normally:

base.WndProc(ref m);

If you start this code in debug mode, and open the Output window if it isn't already visible, you'll see many Windows messages (Figure 11.3)-for example, one for every time you move the mouse over the form.

Figure 11.3: Windows messages sent by the application can be viewed in the Output window.

Subclassing a Message

Supposing you do want to do something with a Windows message, it is easy enough. To

see how, let's write a very short override to WndProc that intercepts the WM_MOUSEMOVE and WM_MOUSELEAVE messages. (The first of these is sent when the mouse moves on a window, the second when the mouse cursor leaves a window.)

The value of the ID stored in the Msg parameter for WM_MOUSEMOVE is hexadecimal 0x200, and WM_MOUSELEAVE is 0x2a3. (This can be seen in the Debug output shown in Figure 11.3.) Translating these values to decimals, I can set up constants that are their equivalent:

const int WM_MOUSEMOVE = 512; const int WM_MOUSELEAVE = 675;

It's no problem now to write our own switch statement that intercepts each message, changing the form's text and cursor on a mouse movement (WM_MOUSEMOVE) and changing them once again when a WM_MOUSELEAVE is intercepted, as shown in Listing 11.2. If you run this code, you'll see that the text and cursor do, indeed, change back and forth to reflect the message subclassing.

Listing 11.2: Subclassing Mouse-Move and Mouse-Leave Messages

protected override void WndProc(ref Message m) { base.WndProc(ref m);

const int WM_MOUSEMOVE = 512; const int WM_MOUSELEAVE = 675; switch (m.Msg) {

case WM_MOUSEMOVE:

this.Text = "Mouse is moving..."; this.Cursor = Cursors.Hand; break;

case WM_MOUSELEAVE:

this.Text = "Hello, again!"; this.Cursor = Cursors.Default; break;

}

}

Warning Once again, the code shown in Listing 11.2 started by invoking the base class WndProc method, so that messages could get processed normally. If you don't invoke the base class method, the window won't even display, and attempting to open the window by running the form class will throw an exception ("Error creating window handle").

Note Since both MouseLeave and MouseMove are events supplied with a form, you could place code in the related event handler procedures-implying that there is no reason you'd ever need to subclass for these two messages.

If messaging works to organize a program that is as complex as Windows, why not consider it as architecture for your own solutions?

MessageQueue Preliminaries

As you'll recall from the discussion of the Queue class in Chapter 7, "Arrays, Indexers, and Collections," a queue is a first in, first out data structure. An object can be enqueued (or pushed), in which case it is placed on the end of the queue. An object can be dequeued (or popped), which means it is retrieved-and removed-from the front of the queue. Alternatively, peeking at a queue means to have a look at the front object on the queue without removing it.

Message queues, as implemented in the System.Messaging.MessageQueue class, work in exactly this fashion-although, as you'll see, the terminology differs a bit. The objects on the queue are messages as implemented in the System.Messaging.Messages class. (As noted earlier, these are quite different from objects of the System.Windows.Forms.Message class.)

A good way to think of these messages is as an analog to e-mail (or instant messaging) that people use to communicate-except that these messages are used for communication within or between software applications.

Message queues under the Windows operating systems come in three varieties:

Public queues Allow machines on a network to send and receive messages across the network.