﻿<?xml version="1.0" encoding="utf-8" standalone="yes"?><rss version="2.0" xmlns:dc="http://purl.org/dc/elements/1.1/" xmlns:trackback="http://madskills.com/public/xml/rss/module/trackback/" xmlns:wfw="http://wellformedweb.org/CommentAPI/" xmlns:slash="http://purl.org/rss/1.0/modules/slash/"><channel><title>BlogJava-weidagang2046的专栏-文章分类-Windows</title><link>http://www.blogjava.net/weidagang2046/category/3039.html</link><description>物格而后知致</description><language>zh-cn</language><lastBuildDate>Fri, 02 Mar 2007 07:33:19 GMT</lastBuildDate><pubDate>Fri, 02 Mar 2007 07:33:19 GMT</pubDate><ttl>60</ttl><item><title>WebBrowser设置Cookie</title><link>http://www.blogjava.net/weidagang2046/articles/95745.html</link><dc:creator>weidagang2046</dc:creator><author>weidagang2046</author><pubDate>Wed, 24 Jan 2007 07:40:00 GMT</pubDate><guid>http://www.blogjava.net/weidagang2046/articles/95745.html</guid><wfw:comment>http://www.blogjava.net/weidagang2046/comments/95745.html</wfw:comment><comments>http://www.blogjava.net/weidagang2046/articles/95745.html#Feedback</comments><slash:comments>0</slash:comments><wfw:commentRss>http://www.blogjava.net/weidagang2046/comments/commentRss/95745.html</wfw:commentRss><trackback:ping>http://www.blogjava.net/weidagang2046/services/trackbacks/95745.html</trackback:ping><description><![CDATA[
		<span id="_ctl0_MainContent_PostFlatView">
				<span>
						<font face="Courier New" size="2">public partial class WebBrowserControl : Form<br />    {<br />        private String url;<br /><br />        [DllImport("wininet.dll", CharSet = CharSet.Auto, SetLastError = true)]<br />        public static extern bool InternetSetCookie(string lpszUrlName, string lbszCookieName, string lpszCookieData);<br /><br />        public WebBrowserControl(String path)<br />        {<br />            this.url = path;<br />            InitializeComponent();<br /><br />            // set cookie<br />            InternetSetCookie(url, "JSESSIONID", Globals.ThisDocument.sessionID);  <br /><br />            // navigate<br />            webBrowser.Navigate(url);  <br />        }<br />        ...<br />}<br /><br />from: http://forums.microsoft.com/MSDN/ShowPost.aspx?PostID=523069&amp;SiteID=1</font>
						<br />
				</span>
		</span>
<img src ="http://www.blogjava.net/weidagang2046/aggbug/95745.html" width = "1" height = "1" /><br><br><div align=right><a style="text-decoration:none;" href="http://www.blogjava.net/weidagang2046/" target="_blank">weidagang2046</a> 2007-01-24 15:40 <a href="http://www.blogjava.net/weidagang2046/articles/95745.html#Feedback" target="_blank" style="text-decoration:none;">发表评论</a></div>]]></description></item><item><title>Safe, Simple Multithreading in Windows Forms</title><link>http://www.blogjava.net/weidagang2046/articles/92207.html</link><dc:creator>weidagang2046</dc:creator><author>weidagang2046</author><pubDate>Sun, 07 Jan 2007 05:49:00 GMT</pubDate><guid>http://www.blogjava.net/weidagang2046/articles/92207.html</guid><wfw:comment>http://www.blogjava.net/weidagang2046/comments/92207.html</wfw:comment><comments>http://www.blogjava.net/weidagang2046/articles/92207.html#Feedback</comments><slash:comments>0</slash:comments><wfw:commentRss>http://www.blogjava.net/weidagang2046/comments/commentRss/92207.html</wfw:commentRss><trackback:ping>http://www.blogjava.net/weidagang2046/services/trackbacks/92207.html</trackback:ping><description><![CDATA[
		<p>Chris Sells</p>
		<p>June 28, 2002</p>
		<p>
				<a href="http://download.microsoft.com/download/.netframesdk/Code/1.0/WIN98MeXP/EN-US/AsynchCalcPi.exe">Download the AsynchCalcPi.exe sample</a>.</p>
		<p>It all started innocently enough. I found myself needing to calculate the area of a circle for the first time in .NET. This called, of course, for an accurate representation of pi. System.Math.PI is handy, but since it only provides 20 digits of precision, I was worried about the accuracy of my calculation (I really needed 21 digits to be absolutely comfortable). So, like any programmer worth their salt, I forgot about the problem I was actually trying to solve and I wrote myself a program to calculate pi to any number of digits that I felt like. What I came up with is shown in Figure 1.</p>
		<p class="fig">
				<img alt="" src="http://msdn.microsoft.com/library/en-us/dnforms/html/winforms06112002-fig01.jpg" border="0" />
		</p>
		<p class="label">
				<b>Figure 1. Digits of Pi application</b>
		</p>
		<h2 class="dtH1">Progress on Long-Running Operations</h2>
		<p>While most applications don't need to calculate digits of pi, many kinds of applications need to perform long-running operations, whether it's printing, making a Web service call, or calculating interest earnings on a certain billionaire in the Pacific Northwest. Users are generally content to wait for such things, often moving to something else in the meantime, so long as they can see that progress is being made. That's why even my little application has a progress bar. The algorithm I'm using calculates pi nine digits at a time. As each new set of digits are available, my program keeps the text updated and moves the progress bar to show how we're coming along. For example, Figure 2 shows progress on the way to calculating 1000 digits of pi (if 21 digits are good, than 1000 must be better).</p>
		<p class="fig">
				<img alt="" src="http://msdn.microsoft.com/library/en-us/dnforms/html/winforms06112002-fig02.jpg" border="0" />
		</p>
		<p class="label">
				<b>Figure 2. Calculating pi to 1000 digits</b>
		</p>
		<p>The following shows how the user interface (UI) is updated as the digits of pi are calculated:</p>
		<pre class="code">
				<code>
						<b class="cfe">void ShowProgress(string pi, int totalDigits, int digitsSoFar) {
  _pi.Text = pi;
  _piProgress.Maximum = totalDigits;
  _piProgress.Value = digitsSoFar;
}

</b>
				</code>void CalcPi(int digits) {
  StringBuilder pi = new StringBuilder("3", digits + 2);

<code><b class="cfe">  // Show progress</b></code><code><b class="cfe">  ShowProgress(pi.ToString(), digits, 0);</b></code>

  if( digits &gt; 0 ) {
    pi.Append(".");

    for( int i = 0; i &lt; digits; i += 9 ) {
      int nineDigits = NineDigitsOfPi.StartingAt(i+1);
      int digitCount = Math.Min(digits - i, 9);
      string ds = string.Format("{0:D9}", nineDigits);
      pi.Append(ds.Substring(0, digitCount));

<code><b class="cfe">      // Show progress</b></code><code><b class="cfe">      ShowProgress(pi.ToString(), digits, i + digitCount);</b></code>
    }
  }
}
</pre>
		<p>Everything was going along fine until, in the middle of actually calculating pi to 1000 digits, I switched away to do something else and then switched back. What I saw is shown in Figure 3.</p>
		<p class="fig">
				<img alt="" src="http://msdn.microsoft.com/library/en-us/dnforms/html/winforms06112002-fig03.jpg" border="0" />
		</p>
		<p class="label">
				<b>Figure 3. No paint event for you! </b>
		</p>
		<p>The problem, of course, is that my application is single-threaded, so while the thread is calculating pi, it can't also be drawing the UI. I didn't run into this before because when I set the <b>TextBox.Text</b> and <b>ProgressBar.Value</b> properties, those controls would force their painting to happen immediately as part of setting the property (although I noticed that the progress bar was better at this than the text box). However, once I put the application into the background and then the foreground again, I need to paint the entire client area, and that's a Paint event for the form. Since no other event is going to be processed until we return from the event we're already processing (that is, the <b>Click</b> event on the Calc button), we're out of luck in terms of seeing any further progress. What I really needed to do was free the UI thread for doing UI work and handle the long-running process in the background. For this, I need another thread.</p>
		<h2 class="dtH1">Asynchronous Operations</h2>
		<p>My current synchronous <b>Click</b> handler looked like this:</p>
		<pre class="code">void _calcButton_Click(object sender, EventArgs e) {
  CalcPi((int)_digits.Value);
}
</pre>
		<p>Recall that the issue is until <b>CalcPi</b> returns, the thread can't return from our <b>Click</b> handler, which means the form can't handle the <b>Paint</b> event (or any other event, for that matter). One way to handle this is to start another thread, like so:</p>
		<pre class="code">using System.Threading;
Ã‚Â…
int _digitsToCalc = 0;

void CalcPiThreadStart() {
  CalcPi(_digitsToCalc);
}

void _calcButton_Click(object sender, EventArgs e) {
  _digitsToCalc = (int)_digits.Value;
<code><b class="cfe">  Thread piThread = new Thread(new ThreadStart(CalcPiThreadStart));            </b></code><code><b class="cfe">  piThread.Start();</b></code>
}
</pre>
		<p>Now, instead of waiting for <b>CalcPi</b> to finish before returning from the button <b>Click</b> event, I'm creating a new thread and asking it to start. The <b>Thread.Start</b> method will schedule my new thread as ready to start and then return immediately, allowing our UI thread to get back to its own work. Now, if the user wants to interact with the application (put it in the background, move it to the foreground, resize it, or even close it), the UI thread is free to handle all of those events while the worker thread calculates pi at its own pace. Figure 4 shows the two threads doing the work.</p>
		<p class="fig">
				<img alt="" src="http://msdn.microsoft.com/library/en-us/dnforms/html/winforms06112002-fig04.jpg" border="0" />
		</p>
		<p class="label">
				<b>Figure 4. Naive multithreading</b>
		</p>
		<p>You may have noticed that I'm not passing any arguments to the worker thread's entry point—<b>CalcPiThreadStart</b>. Instead, I'm tucking the number of digits to calculate into a field, <code class="ce">_digitsToCalc</code>, calling the thread entry point, which is calling <b>CalcPi</b> in turn. This is kind of a pain, which is one of the reasons that I prefer delegates for asynchronous work. Delegates support taking arguments, which saves me the hassle of an extra temporary field and an extra function between the functions I want to call.</p>
		<p>If you're not familiar with delegates, they're really just objects that call static or instance functions. In C#, they're declared using function declaration syntax. For example, a delegate to call <b>CalcPi</b> looks like this:</p>
		<pre class="code">delegate void CalcPiDelegate(int digits);
</pre>
		<p>Once I have a delegate, I can create an instance to call the <b>CalcPi</b> function synchronously like so:</p>
		<pre class="code">void _calcButton_Click(object sender, EventArgs e) {
<code><b class="cfe">  CalcPiDelegate  calcPi = new CalcPiDelegate(CalcPi);</b></code><code><b class="cfe">  calcPi((int)_digits.Value);</b></code>
}
</pre>
		<p>Of course, I don't want to call <b>CalcPi</b> synchronously; I want to call it asynchronously. Before I do that, however, we need to understand a bit more about how delegates work. My delegate declaration above declares a new class derived from <b>MultiCastDelegate</b> with three functions, <b>Invoke</b>, <b>BeginInvoke</b>, and <b>EndInvoke</b>, as shown here:</p>
		<pre class="code">class <code><b class="cfe">CalcPiDelegate</b></code> : MulticastDelegate {
<code><b class="cfe">  public void Invoke(int digits);</b></code><code><b class="cfe">  public void BeginInvoke(int digits, AsyncCallback callback,</b></code><code><b class="cfe">                          object asyncState);</b></code><code><b class="cfe">  public void EndInvoke(IAsyncResult result);</b></code>
}
</pre>
		<p>When I created an instance of the <b>CalcPiDelegate</b> earlier and then called it like a function, I was actually calling the synchronous <b>Invoke</b> function, which in turn called my own <b>CalcPi</b> function. <b>BeginInvoke</b> and <b>EndInvoke</b>, however, are the pair of functions that allow you to invoke and harvest the results of a function call asynchronously. So, to have the <b>CalcPi</b> function called on another thread, I need to call <b>BeginInvoke</b> like so:</p>
		<pre class="code">void _calcButton_Click(object sender, EventArgs e) {
  CalcPiDelegate  calcPi = new CalcPiDelegate(CalcPi);
<code><b class="cfe">  calcPi.BeginInvoke((int)_digits.Value, null, null);</b></code>
}
</pre>
		<p>Notice that we're passing nulls for the last two arguments of <b>BeginInvoke</b>. These are needed if we'd like to harvest the result from the function we're calling at some later date (which is also what <b>EndInvoke</b> is for). Since the <b>CalcPi</b> function updates the UI directly, we don't need anything but nulls for these two arguments. If you'd like the details of delegates, both synchronous and asynchronous, see <a href="http://www.sellsbrothers.com/writing/#delegates">.NET Delegates: A C# Bedtime Story</a>.</p>
		<p>At this point, I should be happy. I've got my application to combine a fully interactive UI that shows progress on a long-running operation. In fact, it wasn't until I realized what I was really doing that I became unhappy.</p>
		<h2 class="dtH1">Multithreaded Safety</h2>
		<p>As it turned out, I had just gotten lucky (or unlucky, depending on how you characterize such things). Microsoft Windows® XP was providing me with a very robust implementation of the underlying windowing system on which Windows Forms is built. So robust, in fact, that it gracefully handled my violation of the prime directive of Windows programming—<i>Though shalt not operate on a window from other than its creating thread</i>. Unfortunately there's no guarantee that other, less robust implementations of Windows would be equally graceful given my bad manners.</p>
		<p>The problem, of course, was of my own making. If you remember Figure 4, I had two threads accessing the same underlying window at the same time. However, because long-running operations are so common in Windows application, each UI class in Windows Forms (that is, every class that ultimately derives from System.Windows.Forms.Control) has a property that you can use from any thread so that you can access the window safely. The name of the property is <b>InvokeRequired</b>, which returns true if the calling thread needs to pass control over to the creating thread before calling a method on that object. A simple Assert in my <b>ShowProgress</b> function would have immediately shown me the error of my ways:</p>
		<pre class="code">using System.Diagnostics;

void ShowProgress(string pi, int totalDigits, int digitsSoFar) {
<code><b class="cfe">  // Make sure we're on the right thread</b></code><code><b class="cfe">  Debug.Assert(_pi.InvokeRequired == false);</b></code>
  ...
}
</pre>
		<p>In fact, the .NET documentation is quite clear on this point. It states, "There are four methods on a control that are safe to call from any thread: <b>Invoke</b>, <b>BeginInvoke</b>, <b>EndInvoke</b>, and <b>CreateGraphics</b>. For all other method calls, you should use one of the invoke methods to marshal the call to the control's thread." So, when I set the control properties, I'm clearly violating this rule. And from the names of the first three functions that I'm allowed to call safely (<b>Invoke</b>, <b>BeginInvoke</b>, and <b>EndInvoke</b>), it should be clear that I need to construct another delegate that will be executed in the UI thread. If I were worried about blocking my worker thread, like I was worried about blocking my UI thread, I'd need to use the asynchronous <b>BeginInvoke</b> and <b>EndInvoke</b>. However, since my worker thread exists only to service my UI thread, let's use the simpler, synchronous <b>Invoke</b> method, which is defined like this:</p>
		<pre class="code">public object Invoke(Delegate method);
public object Invoke(Delegate method, object[] args);
</pre>
		<p>The first overload of <b>Invoke</b> takes an instance of a delegate containing the method we'd like to call in the UI thread, but assumes no arguments. However, the function we want to call to update the UI, <b>ShowProgress</b>, takes three arguments, so we'll need the second overload. We'll also need another delegate for our <b>ShowProgress</b> method so that we can pass the arguments correctly. Here's how to use <b>Invoke</b> to make sure that our calls to <b>ShowProgress</b>, and therefore our use of our windows, shows up on the correct thread (making sure to replace both calls to <b>ShowProgress</b> in <b>CalcPi</b>):</p>
		<pre class="code">
				<code>
						<b class="cfe">delegate
void ShowProgressDelegate(string pi, int totalDigits, int digitsSoFar);

</b>
				</code>void CalcPi(int digits) {
  StringBuilder pi = new StringBuilder("3", digits + 2);

<code><b class="cfe">  // Get ready to show progress asynchronously</b></code><code><b class="cfe">  ShowProgressDelegate showProgress =</b></code><code><b class="cfe">    new ShowProgressDelegate(ShowProgress);</b></code><code><b class="cfe">  // Show progress</b></code><code><b class="cfe">  this.Invoke(showProgress, new object[] { pi.ToString(), digits, 0});</b></code>

  if( digits &gt; 0 ) {
    pi.Append(".");

    for( int i = 0; i &lt; digits; i += 9 ) {
      ...
<code><b class="cfe">      // Show progress</b></code><code><b class="cfe">      this.Invoke(showProgress,</b></code><code><b class="cfe">        new object[] { pi.ToString(), digits, i + digitCount});</b></code>
    }
  }
}
</pre>
		<p>The use of <b>Invoke</b> has finally given me a safe use of multithreading in my Windows Forms application. The UI thread spawns a worker thread to do the long-running operation, and the worker thread passes control back to the UI thread when the UI needs updating. Figure 5 shows our safe multithreading architecture.</p>
		<p class="fig">
				<img alt="" src="http://msdn.microsoft.com/library/en-us/dnforms/html/winforms06112002-fig05.jpg" border="0" />
		</p>
		<p class="label">
				<b>Figure 5. Safe multithreading</b>
		</p>
		<h2 class="dtH1">Simplified Multithreading</h2>
		<p>The call to <b>Invoke</b> is a bit cumbersome, and because it happens twice in our <b>CalcPi</b> function, we could simplify things and update <b>ShowProgress</b> itself to do the asynchronous call. If <b>ShowProgress</b> is called from the correct thread, it will update the controls, but if it's called from the incorrect thread, it uses <b>Invoke</b> to call itself back on the correct thread. This lets us go back to the previous, simpler <b>CalcPi</b>:</p>
		<pre class="code">void ShowProgress(string pi, int totalDigits, int digitsSoFar) {
<code><b class="cfe">  // Make sure we're on the right thread</b></code><code><b class="cfe">  if( _pi.InvokeRequired == false ) {</b></code>
    _pi.Text = pi;
    _piProgress.Maximum = totalDigits;
    _piProgress.Value = digitsSoFar;
<code><b class="cfe">  }</b></code><code><b class="cfe">  else {</b></code><code><b class="cfe">    // Show progress asynchronously</b></code><code><b class="cfe">    ShowProgressDelegate showProgress =</b></code><code><b class="cfe">      new ShowProgressDelegate(ShowProgress);</b></code><code><b class="cfe">    this.Invoke(showProgress,</b></code><code><b class="cfe">      new object[] { pi, totalDigits, digitsSoFar});</b></code><code><b class="cfe">  }</b></code>
}

void CalcPi(int digits) {
  StringBuilder pi = new StringBuilder("3", digits + 2);

<code><b class="cfe">  // Show progress</b></code><code><b class="cfe">  ShowProgress(pi.ToString(), digits, 0);</b></code>

  if( digits &gt; 0 ) {
    pi.Append(".");

    for( int i = 0; i &lt; digits; i += 9 ) {
      ...
<code><b class="cfe">      // Show progress</b></code><code><b class="cfe">      ShowProgress(pi.ToString(), digits, i + digitCount);</b></code>
    }
  }
}
</pre>
		<p>Because <b>Invoke</b> is a synchronous call and we're not consuming the return value (in fact, <b>ShowProgress</b> doesn't have a return value), it's better to use <b>BeginInvoke</b> here so that the worker thread isn't held up, as shown here:</p>
		<pre class="code">BeginInvoke(showProgress, new object[] { pi, totalDigits, digitsSoFar});
</pre>
		<p>
				<b>BeginInvoke</b> is always preferred if you don't need the return of a function call because it sends the worker thread to its work immediately and avoids the possibility of deadlock.</p>
		<h2 class="dtH1">Where Are We?</h2>
		<p>I've used this short example to demonstrate how to perform long-running operations while still showing progress and keeping the UI responsive to user interaction. To accomplish this, I used one asynch delegate to spawn a worker thread and the <b>Invoke</b> method on the main form, along with another delegate to be executed back in the UI thread.</p>
		<p>One thing I was very careful never to do was to share access to a single point of data between the UI thread and the worker thread. Instead, I passed a copy of the data needed to do the work to the worker thread (the number of digits), and a copy of the data needed to update the UI (the digits calculated so far and the progress). In the final solution, I never passed references to objects that I was sharing between the two threads, such as a reference to the current StringBuilder (which would have saved me a string copy for every time I went back to the UI thread). If I had passed shared references back and forth, I would have had to use .NET synchronization primitives to make sure to that only one thread had access to any one object at a time, which would have been a lot of work. It was already enough work just to get the calls happening between the two threads without bringing synchronization into it.</p>
		<p>Of course, if you've got large datasets that you're working with you're not going to want to copy data around. However, when possible, I recommend the combination of asynchronous delegates and message passing between the worker thread and the UI thread for implementing long-running tasks in your Windows Forms applications.</p>
		<h2 class="dtH1">Acknowledgments</h2>
		<p>I'd like to thank Simon Robinson for his post on the DevelopMentor .NET mailing list that inspired this article, Ian Griffiths for his initial work in this area, Chris Andersen for his message-passing ideas, and last but certainly not least, Mike Woodring for the fabulous multithreading pictures that I lifted shamelessly for this article.</p>
		<h2 class="dtH1">References</h2>
		<ul type="disc">
				<li>This article's source code 
</li>
				<li>
						<a href="http://www.sellsbrothers.com/writing/#delegates">.NET Delegates: A C# Bedtime Story</a>
				</li>
				<li>
						<i>Win32 Multithreaded Programming</i> by Mike Woodring and Aaron Cohen </li>
		</ul>
		<hr noshade="" size="1" />
		<p>
				<b>Chris Sells</b> is an independent consultant, specializing in distributed applications in .NET and COM, as well as an instructor for DevelopMentor. He's written several books, including <i>ATL Internals</i>, which is in the process of being updated for ATL7. He's also working on <i>Essential Windows Forms</i> for Addison-Wesley and <i>Mastering Visual Studio .NET</i> for O'Reilly. In his free time, Chris hosts the Web Services DevCon and directs the Genghis source-available project. More information about Chris, and his various projects, is available at <a href="http://www.sellsbrothers.com/">http://www.sellsbrothers.com</a>.<br /><br />from: <a href="http://msdn.microsoft.com/library/default.asp?url=/library/en-us/dnforms/html/winforms06112002.asp">http://msdn.microsoft.com/library/default.asp?url=/library/en-us/dnforms/html/winforms06112002.asp</a></p>
<img src ="http://www.blogjava.net/weidagang2046/aggbug/92207.html" width = "1" height = "1" /><br><br><div align=right><a style="text-decoration:none;" href="http://www.blogjava.net/weidagang2046/" target="_blank">weidagang2046</a> 2007-01-07 13:49 <a href="http://www.blogjava.net/weidagang2046/articles/92207.html#Feedback" target="_blank" style="text-decoration:none;">发表评论</a></div>]]></description></item><item><title>通过多线程为基于.NET 的应用程序实现响应迅速的用户</title><link>http://www.blogjava.net/weidagang2046/articles/92206.html</link><dc:creator>weidagang2046</dc:creator><author>weidagang2046</author><pubDate>Sun, 07 Jan 2007 05:46:00 GMT</pubDate><guid>http://www.blogjava.net/weidagang2046/articles/92206.html</guid><wfw:comment>http://www.blogjava.net/weidagang2046/comments/92206.html</wfw:comment><comments>http://www.blogjava.net/weidagang2046/articles/92206.html#Feedback</comments><slash:comments>0</slash:comments><wfw:commentRss>http://www.blogjava.net/weidagang2046/comments/commentRss/92206.html</wfw:commentRss><trackback:ping>http://www.blogjava.net/weidagang2046/services/trackbacks/92206.html</trackback:ping><description><![CDATA[
		<p>用户不喜欢反应慢的程序。程序反应越慢，就越没有用户会喜欢它。在执行耗时较长的操作时，使用多线程是明智之举，它可以提高程序 UI 的响应速度，使得一切运行显得更为快速。在 Windows 中进行多线程编程曾经是 C++ 开发人员的专属特权，但是现在，可以使用所有兼容 Microsoft .NET 的语言来编写，其中包括 Visual Basic.NET。不过，Windows 窗体对线程的使用强加了一些重要限制。本文将对这些限制进行阐释，并说明如何利用它们来提供快速、高质量的 UI 体验，即使是程序要执行的任务本身速度就较慢。</p>
		<a name="EUB">
		</a>
		<h2>为什么选择多线程？</h2>
		<br />
		<p>多线程程序要比单线程程序更难于编写，并且不加选择地使用线程也是导致难以找到细小错误的重要原因。这就自然会引出两个问题：为什么不坚持编写单线程代码？如果必须使用多线程，如何才能避免缺陷呢？本文的大部分篇幅都是在回答第二个问题，但首先我要来解释一下为什么确实需要多线程。</p>
		<p>多线程处理可以使您能够通过确保程序“永不睡眠”从而保持 UI 的快速响应。大部分程序都有不响应用户的时候：它们正忙于为您执行某些操作以便响应进一步的请求。也许最广为人知的例子就是出现在“打开文件”对话框顶部的组合框。如果在展开该组合框时，CD-ROM驱动器里恰好有一张光盘，则计算机通常会在显示列表之前先读取光盘。这可能需要几秒钟的时间，在此期间，程序既不响应任何输入，也不允许取消该操作，尤其是在确实并不打算使用光驱的时候，这种情况会让人无法忍受。</p>
		<p>执行这种操作期间 UI 冻结的原因在于，UI 是个单线程程序，单线程不可能在等待 CD-ROM驱动器读取操作的同时处理用户输入，如图 1 所示。“打开文件”对话框会调用某些阻塞 (blocking) API 来确定 CD-ROM 的标题。阻塞 API 在未完成自己的工作之前不会返回，因此这期间它会阻止线程做其他事情。</p>
		<p>
				<img hspace="0" src="http://images.csdn.net/20060726/20060726115416.gif" align="baseline" border="0" />
		</p>
		<div style="WIDTH: 149px">
				<br />
				<p class="figureCaption">
						<b>图 1 单线程</b>
				</p>
				<div class="figureRule">
				</div>
		</div>
		<br />
		<p>在多线程下，像这样耗时较长的任务就可以在其自己的线程中运行，这些线程通常称为辅助线程。因为只有辅助线程受到阻止，所以阻塞操作不再导致用户界面冻结，如图 2 所示。应用程序的主线程可以继续处理用户的鼠标和键盘输入的同时，受阻的另一个线程将等待 CD-ROM 读取，或执行辅助线程可能做的任何操作。</p>
		<div style="WIDTH: 196px">
				<img hspace="0" src="http://images.csdn.net/20060726/20060726115459.gif" align="baseline" border="0" />
				<br />
				<p class="figureCaption">
						<b>图 2 多线程</b>
				</p>
				<div class="figureRule">
				</div>
		</div>
		<br />
		<p>其基本原则是，负责响应用户输入和保持用户界面为最新的线程（通常称为 UI 线程）不应该用于执行任何耗时较长的操作。惯常做法是，任何耗时超过 30ms 的操作都要考虑从 UI 线程中移除。这似乎有些夸张，因为 30ms 对于大多数人而言只不过是他们可以感觉到的最短的瞬间停顿，实际上该停顿略短于电影屏幕中显示的连续帧之间的间隔。</p>
		<p>如果鼠标单击和相应的 UI 提示（例如，重新绘制按钮）之间的延迟超过 30ms，那么操作与显示之间就会稍显不连贯，并因此产生如同影片断帧那样令人心烦的感觉。为了达到完全高质量的响应效果，上限必须是 30ms。另一方面，如果您确实不介意感觉稍显不连贯，但也不想因为停顿过长而激怒用户，则可按照通常用户所能容忍的限度将该间隔设为 100ms。</p>
		<p>这意味着如果想让用户界面保持响应迅速，则任何阻塞操作都应该在辅助线程中执行 — 不管是机械等待某事发生（例如，等待 CD-ROM 启动或者硬盘定位数据），还是等待来自网络的响应。</p>
		<div style="MARGIN-TOP: 3px; MARGIN-BOTTOM: 10px">
				<a href="http://csdnmanage.csdn.net/news/#top">
						<img height="9" alt="返回页首" src="http://csdnmanage.csdn.net/library/gallery/templates/MNP2.Common/images/arrow_px_up.gif" width="7" border="0" />
				</a>
				<a class="topOfPage" href="http://csdnmanage.csdn.net/news/#top">返回页首</a>
		</div>
		<a name="ESC">
		</a>
		<h2>异步委托调用</h2>
		<br />
		<p>在辅助线程中运行代码的最简单方式是使用异步委托调用（所有委托都提供该功能）。委托通常是以同步方式进行调用，即，在调用委托时，只有包装方法返回后该调用才会返回。要以异步方式调用委托，请调用 BeginInvoke 方法，这样会对该方法排队以在系统线程池的线程中运行。调用线程会立即返回，而不用等待该方法完成。这比较适合于 UI 程序，因为可以用它来启动耗时较长的作业，而不会使用户界面反应变慢。</p>
		<p>例如，在以下代码中，System.Windows.Forms.MethodInvoker 类型是一个系统定义的委托，用于调用不带参数的方法。 </p>
		<pre class="codeSample">private void StartSomeWorkFromUIThread () {
    // The work we want to do is too slow for the UI
    // thread, so let's farm it out to a worker thread.

    MethodInvoker mi = new MethodInvoker(
        RunsOnWorkerThread);
    mi.BeginInvoke(null, null); // This will not block.
}

// The slow work is done here, on a thread
// from the system thread pool.
private void RunsOnWorkerThread() {
    DoSomethingSlow();
}
</pre>
		<p>如果想要传递参数，可以选择合适的系统定义的委托类型，或者自己来定义委托。MethodInvoker 委托并没有什么神奇之处。和其他委托一样，调用 BeginInvoke 会使该方法在系统线程池的线程中运行，而不会阻塞 UI 线程以便其可执行其他操作。对于以上情况，该方法不返回数据，所以启动它后就不用再去管它。如果您需要该方法返回的结果，则 BeginInvoke 的返回值很重要，并且您可能不传递空参数。然而，对于大多数 UI 应用程序而言，这种“启动后就不管”的风格是最有效的，稍后会对原因进行简要讨论。您应该注意到，BeginInvoke 将返回一个 IAsyncResult。这可以和委托的 EndInvoke 方法一起使用，以在该方法调用完毕后检索调用结果。</p>
		<p>还有其他一些可用于在另外的线程上运行方法的技术，例如，直接使用线程池 API 或者创建自己的线程。然而，对于大多数用户界面应用程序而言，有异步委托调用就足够了。采用这种技术不仅编码容易，而且还可以避免创建并非必需的线程，因为可以利用线程池中的共享线程来提高应用程序的整体性能。</p>
		<div style="MARGIN-TOP: 3px; MARGIN-BOTTOM: 10px">
				<a href="http://csdnmanage.csdn.net/news/#top">
						<img height="9" alt="返回页首" src="http://csdnmanage.csdn.net/library/gallery/templates/MNP2.Common/images/arrow_px_up.gif" width="7" border="0" />
				</a>
				<a class="topOfPage" href="http://csdnmanage.csdn.net/news/#top">返回页首</a>
		</div>
		<a name="E3C">
		</a>
		<h2>线程和控件</h2>
		<br />
		<p>Windows 窗体体系结构对线程使用制定了严格的规则。如果只是编写单线程应用程序，则没必要知道这些规则，这是因为单线程的代码不可能违反这些规则。然而，一旦采用多线程，就需要理解 Windows 窗体中最重要的一条线程规则：除了极少数的例外情况，否则都不要在它的创建线程以外的线程中使用控件的任何成员。</p>
		<p>本规则的例外情况有文档说明，但这样的情况非常少。这适用于其类派生自 System.Windows.Forms.Control 的任何对象，其中几乎包括 UI 中的所有元素。所有的 UI 元素（包括表单本身）都是从 Control 类派生的对象。此外，这条规则的结果是一个被包含的控件（如，包含在一个表单中的按钮）必须与包含它控件位处于同一个线程中。也就是说，一个窗口中的所有控件属于同一个 UI 线程。实际中，大部分 Windows 窗体应用程序最终都只有一个线程，所有 UI 活动都发生在这个线程上。这个线程通常称为 UI 线程。这意味着您不能调用用户界面中任意控件上的任何方法，除非在该方法的文档说明中指出可以调用。该规则的例外情况（总有文档记录）非常少而且它们之间关系也不大。请注意，以下代码是非法的： </p>
		<pre class="codeSample">// Created on UI thread
private Label lblStatus;
...
// Doesn't run on UI thread
private void RunsOnWorkerThread() {
    DoSomethingSlow();
    lblStatus.Text = "Finished!";    // BAD!!
}
</pre>
		<p>如果您在 .NET Framework 1.0 版本中尝试运行这段代码，也许会侥幸运行成功，或者初看起来是如此。这就是多线程错误中的主要问题，即它们并不会立即显现出来。甚至当出现了一些错误时，在第一次演示程序之前一切看起来也都很正常。但不要搞错 — 我刚才显示的这段代码明显违反了规则，并且可以预见，任何抱希望于“试运行时良好，应该就没有问题”的人在即将到来的调试期是会付出沉重代价的。</p>
		<p>要注意，在明确创建线程之前会发生这样的问题。使用委托的异步调用实用程序（调用它的 BeginInvoke 方法）的任何代码都可能出现同样的问题。委托提供了一个非常吸引人的解决方案来处理 UI 应用程序中缓慢、阻塞的操作，因为这些委托能使您轻松地让此种操作运行在 UI 线程外而无需自己创建新线程。但是由于以异步委托调用方式运行的代码在一个来自线程池的线程中运行，所以它不能访问任何 UI 元素。上述限制也适用于线程池中的线程和手动创建的辅助线程。</p>
		<div style="MARGIN-TOP: 3px; MARGIN-BOTTOM: 10px">
				<a href="http://csdnmanage.csdn.net/news/#top">
						<img height="9" alt="返回页首" src="http://csdnmanage.csdn.net/library/gallery/templates/MNP2.Common/images/arrow_px_up.gif" width="7" border="0" />
				</a>
				<a class="topOfPage" href="http://csdnmanage.csdn.net/news/#top">返回页首</a>
		</div>
		<a name="EGD">
		</a>
		<h2>在正确的线程中调用控件</h2>
		<br />
		<p>有关控件的限制看起来似乎对多线程编程非常不利。如果在辅助线程中运行的某个缓慢操作不对 UI 产生任何影响，用户如何知道它的进行情况呢？至少，用户如何知道工作何时完成或者是否出现错误？幸运的是，虽然此限制的存在会造成不便，但并非不可逾越。有多种方式可以从辅助线程获取消息，并将该消息传递给 UI 线程。理论上讲，可以使用低级的同步原理和池化技术来生成自己的机制，但幸运的是，因为有一个以 Control 类的 Invoke 方法形式存在的解决方案，所以不需要借助于如此低级的工作方式。</p>
		<p>Invoke 方法是 Control 类中少数几个有文档记录的线程规则例外之一：它始终可以对来自任何线程的 Control 进行 Invoke 调用。Invoke 方法本身只是简单地携带委托以及可选的参数列表，并在 UI 线程中为您调用委托，而不考虑 Invoke 调用是由哪个线程发出的。实际上，为控件获取任何方法以在正确的线程上运行非常简单。但应该注意，只有在 UI 线程当前未受到阻塞时，这种机制才有效 — 调用只有在 UI 线程准备处理用户输入时才能通过。从不阻塞 UI 线程还有另一个好理由。Invoke 方法会进行测试以了解调用线程是否就是 UI 线程。如果是，它就直接调用委托。否则，它将安排线程切换，并在 UI 线程上调用委托。无论是哪种情况，委托所包装的方法都会在 UI 线程中运行，并且只有当该方法完成时，Invoke 才会返回。</p>
		<p>Control 类也支持异步版本的 Invoke，它会立即返回并安排该方法以便在将来某一时间在 UI 线程上运行。这称为 BeginInvoke，它与异步委托调用很相似，与委托的明显区别在于，该调用以异步方式在线程池的某个线程上运行，然而在此处，它以异步方式在 UI 线程上运行。实际上，Control 的 Invoke、BeginInvoke 和 EndInvoke 方法，以及 InvokeRequired 属性都是 ISynchronizeInvoke 接口的成员。该接口可由任何需要控制其事件传递方式的类实现。</p>
		<p>由于 BeginInvoke 不容易造成死锁，所以尽可能多用该方法；而少用 Invoke 方法。因为 Invoke 是同步的，所以它会阻塞辅助线程，直到 UI 线程可用。但是如果 UI 线程正在等待辅助线程执行某操作，情况会怎样呢？应用程序会死锁。BeginInvoke 从不等待 UI 线程，因而可以避免这种情况。</p>
		<p>现在，我要回顾一下前面所展示的代码片段的合法版本。首先，必须将一个委托传递给 Control 的 BeginInvoke 方法，以便可以在 UI 线程中运行对线程敏感的代码。这意味着应该将该代码放在它自己的方法中，如<a href="http://www.msdn.microsoft.com/msdnmag/issues/03/02/Multithreading/default.aspx?fig=true#fig3" target="_blank"><b>图 3</b></a> 所示。一旦辅助线程完成缓慢的工作后，它就会调用 Label 中的 BeginInvoke，以便在其 UI 线程上运行某段代码。通过这样，它可以更新用户界面。</p>
		<div style="MARGIN-TOP: 3px; MARGIN-BOTTOM: 10px">
				<a href="http://csdnmanage.csdn.net/news/#top">
						<img height="9" alt="返回页首" src="http://csdnmanage.csdn.net/library/gallery/templates/MNP2.Common/images/arrow_px_up.gif" width="7" border="0" />
				</a>
				<a class="topOfPage" href="http://csdnmanage.csdn.net/news/#top">返回页首</a>
		</div>
		<a name="EXD">
		</a>
		<h2>包装 Control.Invoke</h2>
		<br />
		<p>虽然<a href="http://www.msdn.microsoft.com/msdnmag/issues/03/02/Multithreading/default.aspx?fig=true#fig3" target="_blank"><b>图 3</b></a>中的代码解决了这个问题，但它相当繁琐。如果辅助线程希望在结束时提供更多的反馈信息，而不是简单地给出“Finished!”消息，则 BeginInvoke 过于复杂的使用方法会令人生畏。为了传达其他消息，例如“正在处理”、“一切顺利”等等，需要设法向 UpdateUI 函数传递一个参数。可能还需要添加一个进度栏以提高反馈能力。这么多次调用 BeginInvoke 可能导致辅助线程受该代码支配。这样不仅会造成不便，而且考虑到辅助线程与 UI 的协调性，这样设计也不好。对这些进行分析之后，我们认为包装函数可以解决这两个问题，如<a href="http://www.msdn.microsoft.com/msdnmag/issues/03/02/Multithreading/default.aspx?fig=true#fig4" target="_blank"><b>图 4</b></a> 所示。</p>
		<p>ShowProgress 方法对将调用引向正确线程的工作进行封装。这意味着辅助线程代码不再担心需要过多关注 UI 细节，而只要定期调用 ShowProgress 即可。请注意，我定义了自己的方法，该方法违背了“必须在 UI 线程上进行调用”这一规则，因为它进而只调用不受该规则约束的其他方法。这种技术会引出一个较为常见的话题：为什么不在控件上编写公共方法呢（这些方法记录为 UI 线程规则的例外）？</p>
		<p>刚好 Control 类为这样的方法提供了一个有用的工具。如果我提供一个设计为可从任何线程调用的公共方法，则完全有可能某人会从 UI 线程调用这个方法。在这种情况下，没必要调用 BeginInvoke，因为我已经处于正确的线程中。调用 Invoke 完全是浪费时间和资源，不如直接调用适当的方法。为了避免这种情况，Control 类将公开一个称为 InvokeRequired 的属性。这是“只限 UI 线程”规则的另一个例外。它可从任何线程读取，如果调用线程是 UI 线程，则返回假，其他线程则返回真。这意味着我可以按以下方式修改包装： </p>
		<pre class="codeSample">public void ShowProgress(string msg, int percentDone) {
    if (InvokeRequired) {
        // As before
        ...
    } else {
        // We're already on the UI thread just
        // call straight through.
        UpdateUI(this, new MyProgressEvents(msg,
            PercentDone));
    }
}
</pre>
		<p>ShowProgress 现在可以记录为可从任何线程调用的公共方法。这并没有消除复杂性 — 执行 BeginInvoke 的代码依然存在，它还占有一席之地。不幸的是，没有简单的方法可以完全摆脱它。</p>
		<div style="MARGIN-TOP: 3px; MARGIN-BOTTOM: 10px">
				<a href="http://csdnmanage.csdn.net/news/#top">
						<img height="9" alt="返回页首" src="http://csdnmanage.csdn.net/library/gallery/templates/MNP2.Common/images/arrow_px_up.gif" width="7" border="0" />
				</a>
				<a class="topOfPage" href="http://csdnmanage.csdn.net/news/#top">返回页首</a>
		</div>
		<a name="EOE">
		</a>
		<h2>锁定</h2>
		<br />
		<p>任何并发系统都必须面对这样的事实，即，两个线程可能同时试图使用同一块数据。有时这并不是问题 — 如果多个线程在同一时间试图读取某个对象中的某个字段，则不会有问题。然而，如果有线程想要修改该数据，就会出现问题。如果线程在读取时刚好另一个线程正在写入，则读取线程有可能会看到虚假值。如果两个线程在同一时间、在同一个位置执行写入操作，则在同步写入操作发生之后，所有从该位置读取数据的线程就有可能看到一堆垃圾数据。虽然这种行为只在特定情况下才会发生，读取操作甚至不会与写入操作发生冲突，但是数据可以是两次写入结果的混加，并保持错误结果直到下一次写入值为止。为了避免这种问题，必须采取措施来确保一次只有一个线程可以读取或写入某个对象的状态。</p>
		<p>防止这些问题出现所采用的方式是，使用运行时的锁定功能。C# 可以让您利用这些功能、通过锁定关键字来保护代码（Visual Basic 也有类似构造，称为 SyncLock）。规则是，任何想要在多个线程中调用其方法的对象在每次访问其字段时（不管是读取还是写入）都应该使用锁定构造。例如，请参见<a href="http://www.msdn.microsoft.com/msdnmag/issues/03/02/Multithreading/default.aspx?fig=true#fig5" target="_blank"><b>图 5</b></a>。</p>
		<p>锁定构造的工作方式是：公共语言运行库 (CLR) 中的每个对象都有一个与之相关的锁，任何线程均可获得该锁，但每次只能有一个线程拥有它。如果某个线程试图获取另一个线程已经拥有的锁，那么它必须等待，直到拥有该锁的线程将锁释放为止。C# 锁定构造会获取该对象锁（如果需要，要先等待另一个线程利用它完成操作），并保留到大括号中的代码退出为止。如果执行语句运行到块结尾，该锁就会被释放，并从块中部返回，或者抛出在块中没有捕捉到的异常。</p>
		<p>请注意，MoveBy 方法中的逻辑受同样的锁语句保护。当所做的修改比简单的读取或写入更复杂时，整个过程必须由单独的锁语句保护。这也适用于对多个字段进行更新 — 在对象处于一致状态之前，一定不能释放该锁。如果该锁在更新状态的过程中释放，则其他线程也许能够获得它并看到不一致状态。如果您已经拥有一个锁，并调用一个试图获取该锁的方法，则不会导致问题出现，因为单独线程允许多次获得同一个锁。对于需要锁定以保护对字段的低级访问和对字段执行的高级操作的代码，这非常重要。MoveBy 使用 Position 属性，它们同时获得该锁。只有最外面的锁阻塞完成后，该锁才会恰当地释放。</p>
		<p>对于需要锁定的代码，必须严格进行锁定。稍有疏漏，便会功亏一篑。如果一个方法在没有获取对象锁的情况下修改状态，则其余的代码在使用它之前即使小心地锁定对象也是徒劳。同样，如果一个线程在没有事先获得锁的情况下试图读取状态，则它可能读取到不正确的值。运行时无法进行检查来确保多线程代码正常运行。</p>
		<div style="MARGIN-TOP: 3px; MARGIN-BOTTOM: 10px">
				<a href="http://csdnmanage.csdn.net/news/#top">
						<img height="9" alt="返回页首" src="http://csdnmanage.csdn.net/library/gallery/templates/MNP2.Common/images/arrow_px_up.gif" width="7" border="0" />
				</a>
				<a class="topOfPage" href="http://csdnmanage.csdn.net/news/#top">返回页首</a>
		</div>
		<a name="E6E">
		</a>
		<h2>死锁</h2>
		<br />
		<p>锁是确保多线程代码正常运行的基本条件，即使它们本身也会引入新的风险。在另一个线程上运行代码的最简单方式是，使用异步委托调用（请参见<a href="http://www.msdn.microsoft.com/msdnmag/issues/03/02/Multithreading/default.aspx?fig=true#fig6" target="_blank"><b>图 6</b></a>）。</p>
		<p>如果曾经调用过 Foo 的 CallBar 方法，则这段代码会慢慢停止运行。CallBar 方法将获得 Foo 对象上的锁，并直到 BarWork 返回后才释放它。然后，BarWork 使用异步委托调用，在某个线程池线程中调用 Foo 对象的 FooWork 方法。接下来，它会在调用委托的 EndInvoke 方法前执行一些其他操作。EndInvoke 将等待辅助线程完成，但辅助线程却被阻塞在 FooWork 中。它也试图获取 Foo 对象的锁，但锁已被 CallBar 方法持有。所以，FooWork 会等待 CallBar 释放锁，但 CallBar 也在等待 BarWork 返回。不幸的是，BarWork 将等待 FooWork 完成，所以 FooWork 必须先完成，它才能开始。结果，没有线程能够进行下去。</p>
		<p>这就是一个死锁的例子，其中有两个或更多线程都被阻塞以等待对方进行。这里的情形和标准死锁情况还是有些不同，后者通常包括两个锁。这表明如果有某个因果性（过程调用链）超出线程界限，就会发生死锁，即使只包括一个锁！Control.Invoke 是一种跨线程调用过程的方法，这是个不争的重要事实。BeginInvoke 不会遇到这样的问题，因为它并不会使因果性跨线程。实际上，它会在某个线程池线程中启动一个全新的因果性，以允许原有的那个独立进行。然而，如果保留 BeginInvoke 返回的 IAsyncResult，并用它调用 EndInvoke，则又会出现问题，因为 EndInvoke 实际上已将两个因果性合二为一。避免这种情况的最简单方法是，当持有一个对象锁时，不要等待跨线程调用完成。要确保这一点，应该避免在锁语句中调用 Invoke 或 EndInvoke。其结果是，当持有一个对象锁时，将无需等待其他线程完成某操作。要坚持这个规则，说起来容易做起来难。</p>
		<p>在检查代码的 BarWork 时，它是否在锁语句的作用域内并不明显，因为在该方法中并没有锁语句。出现这个问题的唯一原因是 BarWork 调用自 Foo.CallBar 方法的锁语句。这意味着只有确保正在调用的函数并不拥有锁时，调用 Control.Invoke 或 EndIn-voke 才是安全的。对于非私有方法而言，确保这一点并不容易，所以最佳规则是，根本不调用 Control.Invoke 和 EndInvoke。这就是为什么“启动后就不管”的编程风格更可取的原因，也是为什么 Control.BeginInvoke 解决方案通常比 Control.Invoke 解决方案好的原因。</p>
		<p>有时候除了破坏规则别无选择，这种情况下就需要仔细严格地分析。但只要可能，在持有锁时就应该避免阻塞，因为如果不这样，死锁就难以消除。</p>
		<div style="MARGIN-TOP: 3px; MARGIN-BOTTOM: 10px">
				<a href="http://csdnmanage.csdn.net/news/#top">
						<img height="9" alt="返回页首" src="http://csdnmanage.csdn.net/library/gallery/templates/MNP2.Common/images/arrow_px_up.gif" width="7" border="0" />
				</a>
				<a class="topOfPage" href="http://csdnmanage.csdn.net/news/#top">返回页首</a>
		</div>
		<a name="EQF">
		</a>
		<h2>使其简单</h2>
		<br />
		<p>如何既从多线程获益最大，又不会遇到困扰并发代码的棘手错误呢？如果提高的 UI 响应速度仅仅是使程序时常崩溃，那么很难说是改善了用户体验。大部分在多线程代码中普遍存在的问题都是由要一次运行多个操作的固有复杂性导致的，这是因为大多数人更善于思考连续过程而非并发过程。通常，最好的解决方案是使事情尽可能简单。</p>
		<p>UI 代码的性质是：它从外部资源接收事件，如用户输入。它会在事件发生时对其进行处理，但却将大部分时间花在了等待事件的发生。如果可以构造辅助线程和 UI 线程之间的通信，使其适合该模型，则未必会遇到这么多问题，因为不会再有新的东西引入。我是这样使事情简单化的：将辅助线程视为另一个异步事件源。如同 Button 控件传递诸如 Click 和 MouseEnter 这样的事件，可以将辅助线程视为传递事件（如 ProgressUpdate 和 WorkComplete）的某物。只是简单地将这看作一种类比，还是真正将辅助对象封装在一个类中，并按这种方式公开适当的事件，这完全取决于您。后一种选择可能需要更多的代码，但会使用户界面代码看起来更加统一。不管哪种情况，都需要 Control.BeginInvoke 在正确的线程上传递这些事件。</p>
		<p>对于辅助线程，最简单的方式是将代码编写为正常顺序的代码块。但如果想要使用刚才介绍的“将辅助线程作为事件源”模型，那又该如何呢？这个模型非常适用，但它对该代码与用户界面的交互提出了限制：这个线程只能向 UI 发送消息，并不能向它提出请求。</p>
		<p>例如，让辅助线程中途发起对话以请求完成结果需要的信息将非常困难。如果确实需要这样做，也最好是在辅助线程中发起这样的对话，而不要在主 UI 线程中发起。该约束是有利的，因为它将确保有一个非常简单且适用于两线程间通信的模型 — 在这里简单是成功的关键。这种开发风格的优势在于，在等待另一个线程时，不会出现线程阻塞。这是避免死锁的有效策略。</p>
		<p>
				<a href="http://www.msdn.microsoft.com/msdnmag/issues/03/02/Multithreading/default.aspx?fig=true#fig7" target="_blank">
						<b>图 7</b>
				</a> 显示了使用异步委托调用以在辅助线程中执行可能较慢的操作（读取某个目录的内容），然后将结果显示在 UI 上。它还不至于使用高级事件语法，但是该调用确实是以与处理事件（如单击）非常相似的方式来处理完整的辅助代码。</p>
		<div style="MARGIN-TOP: 3px; MARGIN-BOTTOM: 10px">
				<a href="http://csdnmanage.csdn.net/news/#top">
						<img height="9" alt="返回页首" src="http://csdnmanage.csdn.net/library/gallery/templates/MNP2.Common/images/arrow_px_up.gif" width="7" border="0" />
				</a>
				<a class="topOfPage" href="http://csdnmanage.csdn.net/news/#top">返回页首</a>
		</div>
		<a name="EAG">
		</a>
		<h2>取消</h2>
		<br />
		<p>前面示例所带来的问题是，要取消操作只能通过退出整个应用程序实现。虽然在读取某个目录时 UI 仍然保持迅速响应，但由于在当前操作完成之前程序将禁用相关按钮，所以用户无法查看另一个目录。如果试图读取的目录是在一台刚好没有响应的远程机器上，这就很不幸，因为这样的操作需要很长时间才会超时。</p>
		<p>要取消一个操作也比较困难，尽管这取决于怎样才算取消。一种可能的理解是“停止等待这个操作完成，并继续另一个操作。”这实际上是抛弃进行中的操作，并忽略最终完成时可能产生的后果。对于当前示例，这是最好的选择，因为当前正在处理的操作（读取目录内容）是通过调用一个阻塞 API 来执行的，取消它没有关系。但即使是如此松散的“假取消”也需要进行大量工作。如果决定启动新的读取操作而不等待原来的操作完成，则无法知道下一个接收到的通知是来自这两个未处理请求中的哪一个。</p>
		<p>支持取消在辅助线程中运行的请求的唯一方式是，提供与每个请求相关的某种调用对象。最简单的做法是将它作为一个 Cookie，由辅助线程在每次通知时传递，允许 UI 线程将事件与请求相关联。通过简单的身份比较（参见<a href="http://www.msdn.microsoft.com/msdnmag/issues/03/02/Multithreading/default.aspx?fig=true#fig8" target="_blank"><b>图 8</b></a>），UI 代码就可以知道事件是来自当前请求，还是来自早已废弃的请求。</p>
		<p>如果简单抛弃就行，那固然很好，不过您可能想要做得更好。如果辅助线程执行的是进行一连串阻塞操作的复杂操作，那么您可能希望辅助线程在最早的时机停止。否则，它可能会继续几分钟的无用操作。在这种情况下，调用对象需要做的就不止是作为一个被动 Cookie。它至少还需要维护一个标记，指明请求是否被取消。UI 可以随时设置这个标记，而辅助线程在执行时将定期测试这个标记，以确定是否需要放弃当前工作。</p>
		<p>对于这个方案，还需要做出几个决定：如果 UI 取消了操作，它是否要等待直到辅助线程注意到这次取消？如果不等待，就需要考虑一个争用条件：有可能 UI 线程会取消该操作，但在设置控制标记之前辅助线程已经决定传递通知了。因为 UI 线程决定不等待，直到辅助线程处理取消，所以 UI 线程有可能会继续从辅助线程接收通知。如果辅助线程使用 BeginInvoke 异步传递通知，则 UI 甚至有可能收到多个通知。UI 线程也可以始终按与“废弃”做法相同的方式处理通知 — 检查调用对象的标识并忽略它不再关心的操作通知。或者，在调用对象中进行锁定并决不从辅助线程调用 BeginInvoke 以解决问题。但由于让 UI 线程在处理一个事件之前简单地对其进行检查以确定是否有用也比较简单，所以使用该方法碰到的问题可能会更少。</p>
		<p>请查看“代码下载”（本文顶部的链接）中的 AsyncUtils，它是一个有用的基类，可为基于辅助线程的操作提供取消功能。<a href="http://www.msdn.microsoft.com/msdnmag/issues/03/02/Multithreading/default.aspx?fig=true#fig9" target="_blank"><b>图 9</b></a> 显示了一个派生类，它实现了支持取消的递归目录搜索。这些类阐明了一些有趣的技术。它们都使用 C# 事件语法来提供通知。该基类将公开一些在操作成功完成、取消和抛出异常时出现的事件。派生类对此进行了扩充，它们将公开通知客户端搜索匹配、进度以及显示当前正在搜索哪个目录的事件。这些事件始终在 UI 线程中传递。实际上，这些类并未限制为 Control 类 — 它们可以将事件传递给实现 ISynchronizeInvoke 接口的任何类。<a href="http://www.msdn.microsoft.com/msdnmag/issues/03/02/Multithreading/default.aspx?fig=true#fig10" target="_blank"><b>图 10</b></a> 是一个示例 Windows 窗体应用程序，它为 Search 类提供一个用户界面。它允许取消搜索并显示进度和结果。</p>
		<div style="MARGIN-TOP: 3px; MARGIN-BOTTOM: 10px">
				<a href="http://csdnmanage.csdn.net/news/#top">
						<img height="9" alt="返回页首" src="http://csdnmanage.csdn.net/library/gallery/templates/MNP2.Common/images/arrow_px_up.gif" width="7" border="0" />
				</a>
				<a class="topOfPage" href="http://csdnmanage.csdn.net/news/#top">返回页首</a>
		</div>
		<a name="E6G">
		</a>
		<h2>程序关闭</h2>
		<br />
		<p>某些情况下，可以采用“启动后就不管”的异步操作，而不需要其他复杂要求来使操作可取消。然而，即使用户界面不要求取消，有可能还是需要实现这项功能以使程序可以彻底关闭。</p>
		<p>当应用程序退出时，如果由线程池创建的辅助线程还在运行，则这些线程会被终止。终止是简单粗暴的操作，因为关闭甚至会绕开任何还起作用的 Finally 块。如果异步操作执行的某些工作不应该以这种方式被打断，则必须确保在关闭之前这样的操作已经完成。此类操作可能包括对文件执行的写入操作，但由于突然中断后，文件可能被破坏。</p>
		<p>一种解决办法是创建自己的线程，而不用来自辅助线程池的线程，这样就自然会避开使用异步委托调用。这样，即使主线程关闭，应用程序也会等到您的线程退出后才终止。System.Threading.Thread 类有一个 IsBackground 属性可以控制这种行为。它默认为 false，这种情况下，CLR 会等到所有非背景线程都退出后才正常终止应用程序。然而，这会带来另一个问题，因为应用程序挂起时间可能会比您预期的长。窗口都关闭了，但进程仍在运行。这也许不是个问题。如果应用程序只是因为要进行一些清理工作才比正常情况挂起更长时间，那没问题。另一方面，如果应用程序在用户界面关闭后还挂起几分钟甚至几小时，那就不可接受了。例如，如果它仍然保持某些文件打开，则可能妨碍用户稍后重启该应用程序。</p>
		<p>最佳方法是，如果可能，通常应该编写自己的异步操作以便可以将其迅速取消，并在关闭应用程序之前等待所有未完成的操作完成。这意味着您可以继续使用异步委托，同时又能确保关闭操作彻底且及时。</p>
		<div style="MARGIN-TOP: 3px; MARGIN-BOTTOM: 10px">
				<a href="http://csdnmanage.csdn.net/news/#top">
						<img height="9" alt="返回页首" src="http://csdnmanage.csdn.net/library/gallery/templates/MNP2.Common/images/arrow_px_up.gif" width="7" border="0" />
				</a>
				<a class="topOfPage" href="http://csdnmanage.csdn.net/news/#top">返回页首</a>
		</div>
		<a name="EIH">
		</a>
		<h2>错误处理</h2>
		<br />
		<p>在辅助线程中出现的错误一般可以通过触发 UI 线程中的事件来处理，这样错误处理方式就和完成及进程更新方式完全一样。因为很难在辅助线程上进行错误恢复，所以最简单的策略就是让所有错误都为致命错误。错误恢复的最佳策略是使操作完全失败，并在 UI 线程上执行重试逻辑。如果需要用户干涉来修复造成错误的问题，简单的做法是给出恰当的提示。</p>
		<p>AsyncUtils 类处理错误以及取消。如果操作抛出异常，该基类就会捕捉到，并通过 Failed 事件将异常传递给 UI。</p>
		<div style="MARGIN-TOP: 3px; MARGIN-BOTTOM: 10px">
				<a href="http://csdnmanage.csdn.net/news/#top">
						<img height="9" alt="返回页首" src="http://csdnmanage.csdn.net/library/gallery/templates/MNP2.Common/images/arrow_px_up.gif" width="7" border="0" />
				</a>
				<a class="topOfPage" href="http://csdnmanage.csdn.net/news/#top">返回页首</a>
		</div>
		<a name="EPH">
		</a>
		<h2>小结</h2>
		<br />
		<p>谨慎地使用多线程代码可以使 UI 在执行耗时较长的任务时不会停止响应，从而显著提高应用程序的反应速度。异步委托调用是将执行速度缓慢的代码从 UI 线程迁移出来，从而避免此类间歇性无响应的最简单方式。</p>
		<p>Windows Forms Control 体系结构基本上是单线程，但它提供了实用程序以将来自辅助线程的调用封送返回至 UI 线程。处理来自辅助线程的通知（不管是成功、失败还是正在进行的指示）的最简单策略是，以对待来自常规控件的事件（如鼠标单击或键盘输入）的方式对待它们。这样可以避免在 UI 代码中引入新的问题，同时通信的单向性也不容易导致出现死锁。</p>
		<p>有时需要让 UI 向一个正在处理的操作发送消息。其中最常见的是取消一个操作。通过建立一个表示正在进行的调用的对象并维护由辅助线程定期检查的取消标记可实现这一目的。如果用户界面线程需要等待取消被认可（因为用户需要知道工作已确实终止，或者要求彻底退出程序），实现起来会有些复杂，但所提供的示例代码中包含了一个将所有复杂性封装在内的基类。派生类只需要执行一些必要的工作、周期性测试取消，以及要是因为取消请求而停止工作，就将结果通知基类。<br /><br />from: <a href="http://dotnet.csdn.net/n/20060726/92986.html">http://dotnet.csdn.net/n/20060726/92986.html</a></p>
<img src ="http://www.blogjava.net/weidagang2046/aggbug/92206.html" width = "1" height = "1" /><br><br><div align=right><a style="text-decoration:none;" href="http://www.blogjava.net/weidagang2046/" target="_blank">weidagang2046</a> 2007-01-07 13:46 <a href="http://www.blogjava.net/weidagang2046/articles/92206.html#Feedback" target="_blank" style="text-decoration:none;">发表评论</a></div>]]></description></item><item><title>.Net 下信号量(Semaphore)的一种实现</title><link>http://www.blogjava.net/weidagang2046/articles/92128.html</link><dc:creator>weidagang2046</dc:creator><author>weidagang2046</author><pubDate>Fri, 05 Jan 2007 15:06:00 GMT</pubDate><guid>http://www.blogjava.net/weidagang2046/articles/92128.html</guid><wfw:comment>http://www.blogjava.net/weidagang2046/comments/92128.html</wfw:comment><comments>http://www.blogjava.net/weidagang2046/articles/92128.html#Feedback</comments><slash:comments>0</slash:comments><wfw:commentRss>http://www.blogjava.net/weidagang2046/comments/commentRss/92128.html</wfw:commentRss><trackback:ping>http://www.blogjava.net/weidagang2046/services/trackbacks/92128.html</trackback:ping><description><![CDATA[&nbsp;&nbsp;&nbsp;&nbsp; 摘要: 动机												    从开始接触多线（进）程编程模型开始，学习的就是和信号量(Semaphore)相关的同步原语。不知道为什么				 .Net Framework 				里却没有相应的东东。要命的是, 我以前有很多久经考验的C++代码都是用她来实现的, 为了不使革命先烈的药白吃, 血白流, 只好自己生一个了。				 										什...&nbsp;&nbsp;<a href='http://www.blogjava.net/weidagang2046/articles/92128.html'>阅读全文</a><img src ="http://www.blogjava.net/weidagang2046/aggbug/92128.html" width = "1" height = "1" /><br><br><div align=right><a style="text-decoration:none;" href="http://www.blogjava.net/weidagang2046/" target="_blank">weidagang2046</a> 2007-01-05 23:06 <a href="http://www.blogjava.net/weidagang2046/articles/92128.html#Feedback" target="_blank" style="text-decoration:none;">发表评论</a></div>]]></description></item><item><title>体验Vs2005 beta2 测试工具 </title><link>http://www.blogjava.net/weidagang2046/articles/91594.html</link><dc:creator>weidagang2046</dc:creator><author>weidagang2046</author><pubDate>Wed, 03 Jan 2007 09:28:00 GMT</pubDate><guid>http://www.blogjava.net/weidagang2046/articles/91594.html</guid><wfw:comment>http://www.blogjava.net/weidagang2046/comments/91594.html</wfw:comment><comments>http://www.blogjava.net/weidagang2046/articles/91594.html#Feedback</comments><slash:comments>0</slash:comments><wfw:commentRss>http://www.blogjava.net/weidagang2046/comments/commentRss/91594.html</wfw:commentRss><trackback:ping>http://www.blogjava.net/weidagang2046/services/trackbacks/91594.html</trackback:ping><description><![CDATA[
		<font face="Courier New" size="2">在Vs2005中加入了单元测试工具,使用与NUnit差不多。但功能更加丰富了，而且使用更加方便，有利于项目的协调工作。而且还支持调试测试，(不知道NUnit支不支持,我是没用过。)方便我们调试出错代码。<br class="" />    Vs2005 Test tool 与NUnit 特性类对应<br />            NUnit                                                Vs2005 Tools<br class="" />     NUnit.Framework            </font>
		<font face="Courier New">
				<font size="2">
						<span style="COLOR: red">Microsoft.VisualStudio.QualityTools.UnitTesting.Framework<br class="" /></span>     TestFixtureAttribute                   </font>
		</font>
		<font face="Courier New">
				<font size="2">
						<span style="COLOR: red">TestClassAttribute<br class="" /></span>    TestFixtureSetUpAttribute            </font>
		</font>
		<font face="Courier New">
				<font size="2">
						<span style="COLOR: red">ClassInitializeAttribute<br class="" /></span>    TestFixtureTearDownAttribute       </font>
		</font>
		<font face="Courier New">
				<font size="2">
						<span style="COLOR: red">ClassCleanupAttribute<br class="" /></span>    SetUpAttribute                                <span style="COLOR: red">TestInitializeAttribute </span>  <br class="" />     TearDownAttribute                        </font>
		</font>
		<font face="Courier New">
				<font size="2">
						<span style="COLOR: red">TestCleanupAttribute<br class="" /></span>    <br />    创建一个测试工程。<img style="WIDTH: 660px; HEIGHT: 498px" height="498" hspace="5" src="http://www.cnblogs.com/images/cnblogs_com/hjf1223/testproject.jpg" width="685" align="baseline" /><br class="" />    打开UnitTest1.cs  它已经为我们生成了一个测试的Sample框架了.<br /></font>
		</font>
		<div style="BORDER-RIGHT: #cccccc 1px solid; PADDING-RIGHT: 5px; BORDER-TOP: #cccccc 1px solid; PADDING-LEFT: 4px; FONT-SIZE: 13px; PADDING-BOTTOM: 4px; BORDER-LEFT: #cccccc 1px solid; WIDTH: 86.24%; WORD-BREAK: break-all; PADDING-TOP: 4px; BORDER-BOTTOM: #cccccc 1px solid; HEIGHT: 613px; BACKGROUND-COLOR: #eeeeee">
				<font face="Courier New" size="2">
						<img src="http://www.cnblogs.com/Images/OutliningIndicators/None.gif" align="top" />
				</font>
				<font face="Courier New">
						<font size="2">
								<span style="COLOR: #000000">[TestClass]<br /><img src="http://www.cnblogs.com/Images/OutliningIndicators/None.gif" align="top" />    </span>
								<span style="COLOR: #0000ff">public</span>
								<span style="COLOR: #000000"> </span>
								<span style="COLOR: #0000ff">class</span>
						</font>
				</font>
				<font face="Courier New">
						<font size="2">
								<span style="COLOR: #000000"> UnitTest1<br /><img id="Codehighlighter1_43_959_Open_Image" onclick="this.style.display='none'; Codehighlighter1_43_959_Open_Text.style.display='none'; Codehighlighter1_43_959_Closed_Image.style.display='inline'; Codehighlighter1_43_959_Closed_Text.style.display='inline';" src="http://www.cnblogs.com/Images/OutliningIndicators/ExpandedBlockStart.gif" align="top" /><img id="Codehighlighter1_43_959_Closed_Image" style="DISPLAY: none" onclick="this.style.display='none'; Codehighlighter1_43_959_Closed_Text.style.display='none'; Codehighlighter1_43_959_Open_Image.style.display='inline'; Codehighlighter1_43_959_Open_Text.style.display='inline';" src="http://www.cnblogs.com/Images/OutliningIndicators/ContractedBlock.gif" align="top" />    </span>
								<span id="Codehighlighter1_43_959_Closed_Text" style="BORDER-RIGHT: #808080 1px solid; BORDER-TOP: #808080 1px solid; DISPLAY: none; BORDER-LEFT: #808080 1px solid; BORDER-BOTTOM: #808080 1px solid; BACKGROUND-COLOR: #ffffff">
										<img src="http://www.cnblogs.com/Images/dot.gif" />
								</span>
						</font>
				</font>
				<span id="Codehighlighter1_43_959_Open_Text">
						<font face="Courier New">
								<font size="2">
										<span style="COLOR: #000000">{<br /><img src="http://www.cnblogs.com/Images/OutliningIndicators/InBlock.gif" align="top" />        </span>
										<span style="COLOR: #0000ff">public</span>
								</font>
						</font>
						<span style="COLOR: #000000">
								<font face="Courier New" size="2"> UnitTest1()<br /><img id="Codehighlighter1_80_168_Open_Image" onclick="this.style.display='none'; Codehighlighter1_80_168_Open_Text.style.display='none'; Codehighlighter1_80_168_Closed_Image.style.display='inline'; Codehighlighter1_80_168_Closed_Text.style.display='inline';" src="http://www.cnblogs.com/Images/OutliningIndicators/ExpandedSubBlockStart.gif" align="top" /><img id="Codehighlighter1_80_168_Closed_Image" style="DISPLAY: none" onclick="this.style.display='none'; Codehighlighter1_80_168_Closed_Text.style.display='none'; Codehighlighter1_80_168_Open_Image.style.display='inline'; Codehighlighter1_80_168_Open_Text.style.display='inline';" src="http://www.cnblogs.com/Images/OutliningIndicators/ContractedSubBlock.gif" align="top" />      </font>
						</span>
						<span id="Codehighlighter1_80_168_Open_Text">
								<font face="Courier New">
										<font size="2">
												<span style="COLOR: #000000">{<br /><img src="http://www.cnblogs.com/Images/OutliningIndicators/InBlock.gif" align="top" />            </span>
												<span style="COLOR: #008000">//</span>
										</font>
								</font>
								<span style="COLOR: #008000">
										<br />
										<font face="Courier New" size="2">
												<img src="http://www.cnblogs.com/Images/OutliningIndicators/InBlock.gif" align="top" />            </font>
								</span>
								<span style="COLOR: #008000">
										<font face="Courier New" size="2">//</font>
								</span>
								<span style="COLOR: #008000">
										<font face="Courier New" size="2"> TODO: Add constructor logic here<br /><img src="http://www.cnblogs.com/Images/OutliningIndicators/InBlock.gif" align="top" />            </font>
								</span>
								<font face="Courier New">
										<font size="2">
												<span style="COLOR: #008000">//<br /><img src="http://www.cnblogs.com/Images/OutliningIndicators/ExpandedSubBlockEnd.gif" align="top" /></span>
												<span style="COLOR: #000000">        }</span>
										</font>
								</font>
						</span>
						<span style="COLOR: #000000">
								<br />
								<font face="Courier New" size="2">
										<img src="http://www.cnblogs.com/Images/OutliningIndicators/InBlock.gif" align="top" />
										<br />
										<img id="Codehighlighter1_179_342_Open_Image" onclick="this.style.display='none'; Codehighlighter1_179_342_Open_Text.style.display='none'; Codehighlighter1_179_342_Closed_Image.style.display='inline'; Codehighlighter1_179_342_Closed_Text.style.display='inline';" src="http://www.cnblogs.com/Images/OutliningIndicators/ExpandedSubBlockStart.gif" align="top" />
										<img id="Codehighlighter1_179_342_Closed_Image" style="DISPLAY: none" onclick="this.style.display='none'; Codehighlighter1_179_342_Closed_Text.style.display='none'; Codehighlighter1_179_342_Open_Image.style.display='inline'; Codehighlighter1_179_342_Open_Text.style.display='inline';" src="http://www.cnblogs.com/Images/OutliningIndicators/ContractedSubBlock.gif" align="top" />        </font>
						</span>
						<span id="Codehighlighter1_179_342_Closed_Text" style="BORDER-RIGHT: #808080 1px solid; BORDER-TOP: #808080 1px solid; DISPLAY: none; BORDER-LEFT: #808080 1px solid; BORDER-BOTTOM: #808080 1px solid; BACKGROUND-COLOR: #ffffff">
								<font face="Courier New" size="2">/**/</font>
						</span>
						<span id="Codehighlighter1_179_342_Open_Text">
								<font face="Courier New">
										<font size="2">
												<span style="COLOR: #808080">///</span>
												<span style="COLOR: #008000"> </span>
												<span style="COLOR: #808080">&lt;summary&gt;</span>
										</font>
								</font>
								<span style="COLOR: #008000">
										<br />
										<font face="Courier New" size="2">
												<img src="http://www.cnblogs.com/Images/OutliningIndicators/InBlock.gif" align="top" />        </font>
								</span>
								<span style="COLOR: #808080">
										<font face="Courier New" size="2">///</font>
								</span>
								<font face="Courier New">
										<font size="2">
												<span style="COLOR: #008000"> Initialize() is called once during test execution before<br /><img src="http://www.cnblogs.com/Images/OutliningIndicators/InBlock.gif" align="top" />        </span>
												<span style="COLOR: #808080">///</span>
										</font>
								</font>
								<font face="Courier New">
										<font size="2">
												<span style="COLOR: #008000"> test methods in this test class are executed.<br /><img src="http://www.cnblogs.com/Images/OutliningIndicators/ExpandedSubBlockEnd.gif" align="top" />        </span>
												<span style="COLOR: #808080">///</span>
												<span style="COLOR: #008000"> </span>
												<span style="COLOR: #808080">&lt;/summary&gt;</span>
												<span style="COLOR: #808080">
												</span>
										</font>
								</font>
						</span>
						<br />
						<font face="Courier New" size="2">
								<img src="http://www.cnblogs.com/Images/OutliningIndicators/InBlock.gif" align="top" />
						</font>
						<font face="Courier New">
								<font size="2">
										<span style="COLOR: #000000">        [TestInitialize()]<br /><img src="http://www.cnblogs.com/Images/OutliningIndicators/InBlock.gif" align="top" />        </span>
										<span style="COLOR: #0000ff">public</span>
										<span style="COLOR: #000000"> </span>
										<span style="COLOR: #0000ff">void</span>
								</font>
						</font>
						<span style="COLOR: #000000">
								<font face="Courier New" size="2"> Initialize()<br /><img id="Codehighlighter1_411_472_Open_Image" onclick="this.style.display='none'; Codehighlighter1_411_472_Open_Text.style.display='none'; Codehighlighter1_411_472_Closed_Image.style.display='inline'; Codehighlighter1_411_472_Closed_Text.style.display='inline';" src="http://www.cnblogs.com/Images/OutliningIndicators/ExpandedSubBlockStart.gif" align="top" /><img id="Codehighlighter1_411_472_Closed_Image" style="DISPLAY: none" onclick="this.style.display='none'; Codehighlighter1_411_472_Closed_Text.style.display='none'; Codehighlighter1_411_472_Open_Image.style.display='inline'; Codehighlighter1_411_472_Open_Text.style.display='inline';" src="http://www.cnblogs.com/Images/OutliningIndicators/ContractedSubBlock.gif" align="top" />      </font>
						</span>
						<span id="Codehighlighter1_411_472_Open_Text">
								<font face="Courier New">
										<font size="2">
												<span style="COLOR: #000000">{<br /><img src="http://www.cnblogs.com/Images/OutliningIndicators/InBlock.gif" align="top" />            </span>
												<span style="COLOR: #008000">//</span>
												<span style="COLOR: #008000">  TODO: Add test initialization code</span>
										</font>
								</font>
								<span style="COLOR: #008000">
										<br />
										<font face="Courier New" size="2">
												<img src="http://www.cnblogs.com/Images/OutliningIndicators/ExpandedSubBlockEnd.gif" align="top" />
										</font>
								</span>
								<span style="COLOR: #000000">
										<font face="Courier New" size="2">        }</font>
								</span>
						</span>
						<span style="COLOR: #000000">
								<br />
								<font face="Courier New" size="2">
										<img src="http://www.cnblogs.com/Images/OutliningIndicators/InBlock.gif" align="top" />
										<br />
										<img id="Codehighlighter1_483_712_Open_Image" onclick="this.style.display='none'; Codehighlighter1_483_712_Open_Text.style.display='none'; Codehighlighter1_483_712_Closed_Image.style.display='inline'; Codehighlighter1_483_712_Closed_Text.style.display='inline';" src="http://www.cnblogs.com/Images/OutliningIndicators/ExpandedSubBlockStart.gif" align="top" />
										<img id="Codehighlighter1_483_712_Closed_Image" style="DISPLAY: none" onclick="this.style.display='none'; Codehighlighter1_483_712_Closed_Text.style.display='none'; Codehighlighter1_483_712_Open_Image.style.display='inline'; Codehighlighter1_483_712_Open_Text.style.display='inline';" src="http://www.cnblogs.com/Images/OutliningIndicators/ContractedSubBlock.gif" align="top" />        </font>
						</span>
						<span id="Codehighlighter1_483_712_Closed_Text" style="BORDER-RIGHT: #808080 1px solid; BORDER-TOP: #808080 1px solid; DISPLAY: none; BORDER-LEFT: #808080 1px solid; BORDER-BOTTOM: #808080 1px solid; BACKGROUND-COLOR: #ffffff">
								<font face="Courier New" size="2">/**/</font>
						</span>
						<span id="Codehighlighter1_483_712_Open_Text">
								<font face="Courier New">
										<font size="2">
												<span style="COLOR: #808080">///</span>
												<span style="COLOR: #008000"> </span>
												<span style="COLOR: #808080">&lt;summary&gt;</span>
										</font>
								</font>
								<span style="COLOR: #008000">
										<br />
										<font face="Courier New" size="2">
												<img src="http://www.cnblogs.com/Images/OutliningIndicators/InBlock.gif" align="top" />        </font>
								</span>
								<span style="COLOR: #808080">
										<font face="Courier New" size="2">///</font>
								</span>
								<font face="Courier New">
										<font size="2">
												<span style="COLOR: #008000"> Cleanup() is called once during test execution after<br /><img src="http://www.cnblogs.com/Images/OutliningIndicators/InBlock.gif" align="top" />        </span>
												<span style="COLOR: #808080">///</span>
										</font>
								</font>
								<font face="Courier New">
										<font size="2">
												<span style="COLOR: #008000"> test methods in this class have executed unless the<br /><img src="http://www.cnblogs.com/Images/OutliningIndicators/InBlock.gif" align="top" />        </span>
												<span style="COLOR: #808080">///</span>
										</font>
								</font>
								<font face="Courier New">
										<font size="2">
												<span style="COLOR: #008000"> corresponding Initialize() call threw an exception.<br /><img src="http://www.cnblogs.com/Images/OutliningIndicators/ExpandedSubBlockEnd.gif" align="top" />        </span>
												<span style="COLOR: #808080">///</span>
												<span style="COLOR: #008000"> </span>
												<span style="COLOR: #808080">&lt;/summary&gt;</span>
												<span style="COLOR: #808080">
												</span>
										</font>
								</font>
						</span>
						<br />
						<font face="Courier New" size="2">
								<img src="http://www.cnblogs.com/Images/OutliningIndicators/InBlock.gif" align="top" />
						</font>
						<font face="Courier New">
								<font size="2">
										<span style="COLOR: #000000">        [TestCleanup()]<br /><img src="http://www.cnblogs.com/Images/OutliningIndicators/InBlock.gif" align="top" />        </span>
										<span style="COLOR: #0000ff">public</span>
										<span style="COLOR: #000000"> </span>
										<span style="COLOR: #0000ff">void</span>
								</font>
						</font>
						<span style="COLOR: #000000">
								<font face="Courier New" size="2"> Cleanup()<br /><img id="Codehighlighter1_775_829_Open_Image" onclick="this.style.display='none'; Codehighlighter1_775_829_Open_Text.style.display='none'; Codehighlighter1_775_829_Closed_Image.style.display='inline'; Codehighlighter1_775_829_Closed_Text.style.display='inline';" src="http://www.cnblogs.com/Images/OutliningIndicators/ExpandedSubBlockStart.gif" align="top" /><img id="Codehighlighter1_775_829_Closed_Image" style="DISPLAY: none" onclick="this.style.display='none'; Codehighlighter1_775_829_Closed_Text.style.display='none'; Codehighlighter1_775_829_Open_Image.style.display='inline'; Codehighlighter1_775_829_Open_Text.style.display='inline';" src="http://www.cnblogs.com/Images/OutliningIndicators/ContractedSubBlock.gif" align="top" />      </font>
						</span>
						<span id="Codehighlighter1_775_829_Open_Text">
								<font face="Courier New">
										<font size="2">
												<span style="COLOR: #000000">{<br /><img src="http://www.cnblogs.com/Images/OutliningIndicators/InBlock.gif" align="top" />            </span>
												<span style="COLOR: #008000">//</span>
												<span style="COLOR: #008000">  TODO: Add test cleanup code</span>
										</font>
								</font>
								<span style="COLOR: #008000">
										<br />
										<font face="Courier New" size="2">
												<img src="http://www.cnblogs.com/Images/OutliningIndicators/ExpandedSubBlockEnd.gif" align="top" />
										</font>
								</span>
								<span style="COLOR: #000000">
										<font face="Courier New" size="2">        }</font>
								</span>
						</span>
						<span style="COLOR: #000000">
								<br />
								<font face="Courier New" size="2">
										<img src="http://www.cnblogs.com/Images/OutliningIndicators/InBlock.gif" align="top" />
										<br />
										<img src="http://www.cnblogs.com/Images/OutliningIndicators/InBlock.gif" align="top" />        [TestMethod]<br /><img src="http://www.cnblogs.com/Images/OutliningIndicators/InBlock.gif" align="top" />        </font>
						</span>
						<font face="Courier New">
								<font size="2">
										<span style="COLOR: #0000ff">public</span>
										<span style="COLOR: #000000"> </span>
										<span style="COLOR: #0000ff">void</span>
								</font>
						</font>
						<span style="COLOR: #000000">
								<font face="Courier New" size="2"> TestMethod1()<br /><img id="Codehighlighter1_895_953_Open_Image" onclick="this.style.display='none'; Codehighlighter1_895_953_Open_Text.style.display='none'; Codehighlighter1_895_953_Closed_Image.style.display='inline'; Codehighlighter1_895_953_Closed_Text.style.display='inline';" src="http://www.cnblogs.com/Images/OutliningIndicators/ExpandedSubBlockStart.gif" align="top" /><img id="Codehighlighter1_895_953_Closed_Image" style="DISPLAY: none" onclick="this.style.display='none'; Codehighlighter1_895_953_Closed_Text.style.display='none'; Codehighlighter1_895_953_Open_Image.style.display='inline'; Codehighlighter1_895_953_Open_Text.style.display='inline';" src="http://www.cnblogs.com/Images/OutliningIndicators/ContractedSubBlock.gif" align="top" />      </font>
						</span>
						<span id="Codehighlighter1_895_953_Open_Text">
								<font face="Courier New">
										<font size="2">
												<span style="COLOR: #000000">{<br /><img src="http://www.cnblogs.com/Images/OutliningIndicators/InBlock.gif" align="top" />            Assert.IsTrue(</span>
												<span style="COLOR: #0000ff">true</span>
												<span style="COLOR: #000000">,</span>
												<span style="COLOR: #000000">"</span>
												<span style="COLOR: #000000">this is work</span>
												<span style="COLOR: #000000">"</span>
										</font>
								</font>
								<span style="COLOR: #000000">
										<font face="Courier New" size="2">);<br /><img src="http://www.cnblogs.com/Images/OutliningIndicators/ExpandedSubBlockEnd.gif" align="top" />        }</font>
								</span>
						</span>
						<span style="COLOR: #000000">
								<br />
								<font face="Courier New" size="2">
										<img src="http://www.cnblogs.com/Images/OutliningIndicators/ExpandedBlockEnd.gif" align="top" />    }</font>
						</span>
				</span>
		</div>
		<p>
				<font face="Courier New" size="2">    熟悉NUnit的一眼就会明白那些Attribute是干嘛用的了。在里面写了一句简单的断言语句。这样就是一个简单的测试实例了。<br />    接下来运行这个测试实例。找到Test菜单项,<img height="331" hspace="5" src="http://www.cnblogs.com/images/cnblogs_com/hjf1223/testmenu.jpg" width="279" align="baseline" />选择"Manage and Execute Tests。在这管理器里面可以选择在项目中已经存在的测试实例。并选择是否运行测试。</font>
		</p>
		<p>
				<font face="Courier New" size="2">
						<img style="WIDTH: 700px; HEIGHT: 388px" height="390" hspace="5" src="http://www.cnblogs.com/images/cnblogs_com/hjf1223/testmanager.jpg" width="744" align="baseline" /> <br />    单击"By Test List"右边那上工具栏按钮。就开始执行测试了。查看测试结果<br /><img style="WIDTH: 508px; HEIGHT: 193px" height="196" hspace="5" src="http://www.cnblogs.com/images/cnblogs_com/hjf1223/firstresults.jpg" width="505" align="baseline" />双击可以看到详细信息。<br />    刚才说到的还可以断点调试。很简单，只需要在测试实例设置断点，然后点击刚才的运行按扭的下拉框，可以看到有“Debug Checked tests”。这样就可以调试了。很方便！<br /><img height="186" hspace="5" src="http://www.cnblogs.com/images/cnblogs_com/hjf1223/debugTest.jpg" width="401" align="baseline" /><br />    就这么简单！<br /><br />from: </font>
				<a href="http://www.cnblogs.com/hjf1223/archive/2005/09/22/241757.aspx">
						<font face="Courier New" size="2">http://www.cnblogs.com/hjf1223/archive/2005/09/22/241757.aspx</font>
				</a>
		</p>
<img src ="http://www.blogjava.net/weidagang2046/aggbug/91594.html" width = "1" height = "1" /><br><br><div align=right><a style="text-decoration:none;" href="http://www.blogjava.net/weidagang2046/" target="_blank">weidagang2046</a> 2007-01-03 17:28 <a href="http://www.blogjava.net/weidagang2046/articles/91594.html#Feedback" target="_blank" style="text-decoration:none;">发表评论</a></div>]]></description></item><item><title>关于VSS的多人签出与合并</title><link>http://www.blogjava.net/weidagang2046/articles/90751.html</link><dc:creator>weidagang2046</dc:creator><author>weidagang2046</author><pubDate>Fri, 29 Dec 2006 07:44:00 GMT</pubDate><guid>http://www.blogjava.net/weidagang2046/articles/90751.html</guid><wfw:comment>http://www.blogjava.net/weidagang2046/comments/90751.html</wfw:comment><comments>http://www.blogjava.net/weidagang2046/articles/90751.html#Feedback</comments><slash:comments>0</slash:comments><wfw:commentRss>http://www.blogjava.net/weidagang2046/comments/commentRss/90751.html</wfw:commentRss><trackback:ping>http://www.blogjava.net/weidagang2046/services/trackbacks/90751.html</trackback:ping><description><![CDATA[
		<p>1、 多人同时签出一个文件(Check Out Multiple Files)*</p>
		<p>　　缺省状态下，一个文件只允许一个人签出，管理员可以通过修改配置，允许多人同时签出。此时，VSS将跟踪所有签出该文件的用户。每当用户签入时，VSS都将和当前存于数据库内的最新版本进行比较，若用户修改的是同一文件的不同处，VSS将进行简单的合并（Merge），否则提示用户，并且不允许签入。用户可以通过VSS提供的Visual Merge工具，比较存放于VSS数据库中的文件和本地文件的异同，手工修改本地文件，直到认为已经可以签入时，方才执行最终签入操作。（参见合并）</p>
		<p>2、 合并(Merge)*</p>
		<p>　　在VSS中，合并可能发生在3种场合下：使用Multiple Checkout的工作方式；合并原先已经Branch了的文件；获取（Get）文件。 </p>
		<p>Multiple Checkout：若多个用户同时签出一个文件，第一个用户只要简单的签入就可以了。后续用户也可以签入，但他们的更改将需要和其他所有用户的更改合并，VSS将得到完整的更改内容（参见多人同时签出一个文件）。 <br />Branch：当被Branch的文件合并到其中一个分支时，VSS将会把在另一个分支上所做的改动合并到该分支上（参见对文件和工程的Branch/Share操作）。 <br />Merge on Get：在Multiple Checkout工作方式下，当使用Get Latest Version操作时可能引发合并操作，此时保存在VSS数据库中的内容将合并到本地文件。但如果某个文件是排他性签出的，则不会引发合并操作（参见排他性签出）。 </p>
		<p>　　在完成一个合并之后，VSS遵循如下规则： </p>
		<p>如果仍有冲突，VSS维持文件的签出状态，为了使文件能顺利签入，你必须排除这些冲突。 <br />如果你使用Merge Branches命令，将一个文件合并到一个工程中，而该工程中的对应文件已被签出，该文件将继续保持签出状态（参见对文件和工程的Branch/Share操作）。 <br />在任何其他时候，VSS将会提示你，或者在合并后自动签入，或者保持文件的签出状态以使你在更新VSS数据库中内容之前再核查一边。 </p>
		<p>　　缺省情况下，当发生冲突时，VSS将启用其Visual Merge工具。</p>
		<br />
		<p>
				<br />
		</p>
		<p>在默认情况下（VSS 6.0）<br /></p>
		<p>VSS使用过程中要遵循的是lock-modify-unlock流程而不是 copy-modify-merge流程（比如CVS），即开发人员首先将自己要修改源代码和文档从VSS服务器主备份文件上checkout到本地同时锁定服务器上的源代码和文档（multi - checkout情况除外），修改完成后checkout到服务器上同时解除服务器上文件的锁定。服务器集中控制所有的源程序和文档。</p>
		<br />
		<p> </p>在VSS 2005中，创建数据库的时候可以选择是copy-modify-merge，还是copy-modify-merge。<br /><br />from: <a href="http://www.cnblogs.com/weiweictgu/archive/2006/08/15/477332.html">http://www.cnblogs.com/weiweictgu/archive/2006/08/15/477332.html</a><img src ="http://www.blogjava.net/weidagang2046/aggbug/90751.html" width = "1" height = "1" /><br><br><div align=right><a style="text-decoration:none;" href="http://www.blogjava.net/weidagang2046/" target="_blank">weidagang2046</a> 2006-12-29 15:44 <a href="http://www.blogjava.net/weidagang2046/articles/90751.html#Feedback" target="_blank" style="text-decoration:none;">发表评论</a></div>]]></description></item><item><title>多线程更新主界面上的DataGrid</title><link>http://www.blogjava.net/weidagang2046/articles/90250.html</link><dc:creator>weidagang2046</dc:creator><author>weidagang2046</author><pubDate>Wed, 27 Dec 2006 03:20:00 GMT</pubDate><guid>http://www.blogjava.net/weidagang2046/articles/90250.html</guid><wfw:comment>http://www.blogjava.net/weidagang2046/comments/90250.html</wfw:comment><comments>http://www.blogjava.net/weidagang2046/articles/90250.html#Feedback</comments><slash:comments>0</slash:comments><wfw:commentRss>http://www.blogjava.net/weidagang2046/comments/commentRss/90250.html</wfw:commentRss><trackback:ping>http://www.blogjava.net/weidagang2046/services/trackbacks/90250.html</trackback:ping><description><![CDATA[
		<p>这是一个多线程更新DataGrid的例子。场景如下：目标是将DataGrid中的数据导入到数据库，由于DataGrid中的数据不是完全正确的，所以对于错误的数据要保留下来，让用户去改，改了之后再导，导了再改，改了再导直到全部导进数据库为止。</p>
		<p>基本的思路是：主GUI上有一个DataGrid，然后新开一个线程进行导入。线程导入数据后，把收集到的错误数据一次性返给主线程，然后显示在原来这个DataGrid中，提供给用户更改并再次导入。</p>
		<p>发起一个线程很容易，这里就不讲了，直接进入主题，如果更新主界面上的DataGrid。由于在 .Net中由线程A创建的 控件是不允许其他线程直接修改的。因此，其他线程需要<strong>委托线程A</strong>，把需要更新的数据给线程A，由他自己去更新。</p>
		<p>看如何实现的：</p>
		<p>  private delegate void ReBindDataGrid_Delegate(DataTable dt);</p>
		<p>  private void ReBindDataGrid(DataTable dt)<br />  {<br />   this.dgList.DataSource = dt.DefaultView;<br />   this.dgList.Refresh();<br />  }</p>
		<p>  private void import_ThreadCompleted(object sender, ThreadCompletedArgs e)<br />  {</p>
		<p>   this.lblIntro.Text += "\n执行完成！";</p>
		<p>   if(e.ErrorRows != null)<br />   {<br />    ReBindDataGrid_Delegate dt = new ReBindDataGrid_Delegate(ReBindDataGrid);<br />    this.Invoke(dt,new object[]{e.ErrorRows.Copy()});<br />   }<br />   else<br />   {<br />    this.pBar.Value = 0;<br />    this.rtxtInfo.Text += "..Over!";<br />  }<br />   this.dgList.Enabled = true;<br />}</p>
		<p>关键在于在主线程声明一个委托：private delegate void ReBindDataGrid_Delegate(DataTable dt);然后在导入线程的完成事件中，利用这个委托，执行主线程中的方法：ReBindDataGrid，同时把参数传给他。</p>
		<p>OK，这样就完成了。</p>
		<p>关于兼讲委托，只一句话，委托就是在二个不能直接相互操作的对象之间，建立一个桥梁。例如二个线程之间。<br /><br />from: <a href="http://www.wintle.cn/article.asp?id=127">http://www.wintle.cn/article.asp?id=127</a></p>
<img src ="http://www.blogjava.net/weidagang2046/aggbug/90250.html" width = "1" height = "1" /><br><br><div align=right><a style="text-decoration:none;" href="http://www.blogjava.net/weidagang2046/" target="_blank">weidagang2046</a> 2006-12-27 11:20 <a href="http://www.blogjava.net/weidagang2046/articles/90250.html#Feedback" target="_blank" style="text-decoration:none;">发表评论</a></div>]]></description></item><item><title>C#文件读写常用类介绍</title><link>http://www.blogjava.net/weidagang2046/articles/89120.html</link><dc:creator>weidagang2046</dc:creator><author>weidagang2046</author><pubDate>Wed, 20 Dec 2006 11:49:00 GMT</pubDate><guid>http://www.blogjava.net/weidagang2046/articles/89120.html</guid><wfw:comment>http://www.blogjava.net/weidagang2046/comments/89120.html</wfw:comment><comments>http://www.blogjava.net/weidagang2046/articles/89120.html#Feedback</comments><slash:comments>0</slash:comments><wfw:commentRss>http://www.blogjava.net/weidagang2046/comments/commentRss/89120.html</wfw:commentRss><trackback:ping>http://www.blogjava.net/weidagang2046/services/trackbacks/89120.html</trackback:ping><description><![CDATA[        首先要熟悉.NET中处理文件和文件夹的操作。File类和Directory类是其中最主要的两个类。了解它们将对后面功能的实现提供很大的便利。 <br />　    本节先对和文件系统相关的两个.NET类进行简要介绍。 <br />　　System.IO.File类和System.IO.FileInfo类主要提供有关文件的各种操作，在使用时需要引用System.IO命名空间。下面通过程序实例来介绍其主要属性和方法。 <br />　　(1) 文件打开方法：File.Open ()<br />　　该方法的声明如下： <br /><font color="#3d11ee">public static FileStream Open(string path,FileMode mode) <br /></font>　　下面的代码打开存放在c:\tempuploads目录下名称为newFile.txt文件，并在该文件中写入hello。 <br /><font color="#3809f7">private void OpenFile()<br />{ <br />　FileStream.TextFile=File.Open(@"c:\tempuploads\newFile.txt",FileMode.Append);<br />　byte [] Info = {(byte)'h',(byte)'e',(byte)'l',(byte)'l',(byte)'o'};<br />　TextFile.Write(Info,0,Info.Length);<br />　TextFile.Close();<br />} <br /></font>　　(2) 文件创建方法：File.Create() <br />　　该方法的声明如下： <br /><font color="#3809f7">public static FileStream Create(string path;)</font><br />　　下面的代码演示如何在c:\tempuploads下创建名为newFile.txt的文件。 <br />　　由于File.Create方法默认向所有用户授予对新文件的完全读/写访问权限，所以文件是用读/写访问权限打开的，必须关闭后才能由其他应用程序打开。为此，所以需要使用FileStream类的Close方法将所创建的文件关闭。 <br /><font color="#0909f7">private void MakeFile()<br />{ 　<br />    FileStream NewText=File.Create(@"c:\tempuploads\newFile.txt"); <br />　NewText.Close(); <br />}</font>　<br />        (3) 文件删除方法：File.Delete() <br />　　该方法声明如下： <br /><font color="#0909f7">public static void Delete(string path);</font><br />　　下面的代码演示如何删除c:\tempuploads目录下的newFile.txt文件。 <br /><font color="#3809f7">private void DeleteFile()<br />{<br />　File.Delete(@"c:\tempuploads\newFile.txt");<br />}</font><br />　　(4) 文件复制方法：File.Copy <br /><br />　　该方法声明如下： <br /><br />public static void Copy(string sourceFileName,string destFileName,bool overwrite); <br />　　下面的代码将c:\tempuploads\newFile.txt复制到c:\tempuploads\BackUp.txt。<br />　　由于Cope方法的OverWrite参数设为true，所以如果BackUp.txt文件已存在的话，将会被复制过去的文件所覆盖。 <br /><font color="#3d11ee">private void CopyFile()<br />{<br />　File.Copy(@"c:\tempuploads\newFile.txt",@"c:\tempuploads\BackUp.txt",true);<br />} <br /></font>　　(5) 文件移动方法：File.Move <br />　　该方法声明如下： <br /><font color="#3d11ee">public static void Move(string sourceFileName,string destFileName); <br /></font>　　下面的代码可以将c:\tempuploads下的BackUp.txt文件移动到c盘根目录下。<br />　　注意： <br />　　只能在同一个逻辑盘下进行文件转移。如果试图将c盘下的文件转移到d盘，将发生错误。 <br /><font color="#0000ff">private void MoveFile()<br />{<br />　File.Move(@"c:\tempuploads\BackUp.txt",@"c:\BackUp.txt");<br />}</font><br />　(6) 设置文件属性方法：File.SetAttributes<br />　　该方法声明如下： <br /><font color="#1111ee">public static void SetAttributes(string path,FileAttributes fileAttributes);</font><br />　　下面的代码可以设置文件c:\tempuploads\newFile.txt的属性为只读、隐藏。 <br /><font color="#3d11ee">private void SetFile()<br />{<br />　File.SetAttributes(@"c:\tempuploads\newFile.txt",<br />　FileAttributes.ReadOnly|FileAttributes.Hidden);<br />}</font><br />　　文件除了常用的只读和隐藏属性外，还有Archive(文件存档状态)，System(系统文件)，Temporary(临时文件)等。关于文件属性的详细情况请参看MSDN中FileAttributes的描述。 <br />　　(7) 判断文件是否存在的方法：File.Exist <br />　　该方法声明如下： <br /><font color="#3d11ee">public static bool Exists(string path); <br /></font>　　下面的代码判断是否存在c:\tempuploads\newFile.txt文件。若存在，先复制该文件，然后其删除，最后将复制的文件移动；若不存在，则先创建该文件，然后打开该文件并进行写入操作，最后将文件属性设为只读、隐藏。 <br /><font color="#1111ee">if(File.Exists(@"c:\tempuploads\newFile.txt")) //判断文件是否存在<br />{<br />　CopyFile(); //复制文件<br />　DeleteFile(); //删除文件<br />　MoveFile(); //移动文件<br />}<br />else<br />{<br />　MakeFile(); //生成文件<br />　OpenFile(); //打开文件<br />　SetFile(); //设置文件属性<br />} <br /></font>　　此外，File类对于Text文本提供了更多的支持。 <br />　　· AppendText：将文本追加到现有文件 <br />　　· CreateText：为写入文本创建或打开新文件 <br />　　· OpenText：打开现有文本文件以进行读取 <br />　　但上述方法主要对UTF-8的编码文本进行操作，从而显得不够灵活。在这里推荐读者使用下面的代码对txt文件进行操作。 <br />　　· 对txt文件进行“读”操作，示例代码如下： <br /><font color="#3d11ee">StreamReader TxtReader = new StreamReader(@"c:\tempuploads\newFile.txt",System.Text.Encoding.Default);<br />string FileContent;<br />FileContent = TxtReader.ReadEnd();<br />TxtReader.Close();</font><br />　　· 对txt文件进行“写”操作，示例代码如下： <br /><font color="#3809f7">StreamWriter = new StreamWrite(@"c:\tempuploads\newFile.txt",System.Text.Encoding.Default);<br />string FileContent;<br />TxtWriter.Write(FileContent);<br />TxtWriter.Close(); <br /></font>　　System.IO.Directory类和System.DirectoryInfo类 <br />　　主要提供关于目录的各种操作，使用时需要引用System.IO命名空间。下面通过程序实例来介绍其主要属性和方法。 <br />　　(1) 目录创建方法：Directory.CreateDirectory <br />　　该方法声明如下： <br /><font color="#0909f7">public static DirectoryInfo CreateDirectory(string path);</font><br />　　下面的代码演示在c:\tempuploads文件夹下创建名为NewDirectory的目录。 <br /><font color="#3809f7">private void MakeDirectory()<br />{<br />　Directory.CreateDirectory(@"c:\tempuploads\NewDirectoty"); <br />}</font><br />　　(2) 目录属性设置方法：DirectoryInfo.Atttributes <br />　　下面的代码设置c:\tempuploads\NewDirectory目录为只读、隐藏。与文件属性相同，目录属性也是使用FileAttributes来进行设置的。 <br /><font color="#3809f7">private void SetDirectory()<br />{<br />　DirectoryInfo NewDirInfo = new DirectoryInfo(@"c:\tempuploads\NewDirectoty");<br />　NewDirInfo.Atttributes = FileAttributes.ReadOnly|FileAttributes.Hidden;<br />} <br /></font>　　(3) 目录删除方法：Directory.Delete <br />　　该方法声明如下： <br /><font color="#3d11ee">public static void Delete(string path,bool recursive);</font><br />　　下面的代码可以将c:\tempuploads\BackUp目录删除。Delete方法的第二个参数为bool类型，它可以决定是否删除非空目录。如果该参数值为true，将删除整个目录，即使该目录下有文件或子目录；若为false，则仅当目录为空时才可删除。 <br /><font color="#3809f7">private void DeleteDirectory()<br />{<br />　Directory.Delete(@"c:\tempuploads\BackUp",true);<br />}</font><br />　　(4) 目录移动方法：Directory.Move <br />　　该方法声明如下： <br /><font color="#0909f7">public static void Move(string sourceDirName,string destDirName);</font><br />　　下面的代码将目录c:\tempuploads\NewDirectory移动到c:\tempuploads\BackUp。 <br />private void MoveDirectory()<br />{<br />　<font color="#3300ff">File.Move(@"c:\tempuploads\NewDirectory",@"c:\tempuploads\BackUp");</font><br />} <br />　　(5) 获取当前目录下的所有子目录方法：Directory.GetDirectories <br />　　该方法声明如下： <br /><font color="#3300ff">public static string[] GetDirectories(string path;); <br /></font>　　下面的代码读出c:\tempuploads\目录下的所有子目录，并将其存储到字符串数组中。 <br /><font color="#3300ff">private void GetDirectory()<br />{<br />　string [] Directorys;<br />　Directorys = Directory. GetDirectories (@"c:\tempuploads");<br />}</font><br />　　(6) 获取当前目录下的所有文件方法：Directory.GetFiles <br />　　该方法声明如下： <br /><font color="#0909f7">public static string[] GetFiles(string path;); <br /></font>　　下面的代码读出c:\tempuploads\目录下的所有文件，并将其存储到字符串数组中。 <br /><font color="#3300ff">private void GetFile()<br />{<br />　string [] Files;<br />　Files = Directory. GetFiles (@"c:\tempuploads",);<br />}</font><br />　　(7) 判断目录是否存在方法：Directory.Exist <br />　　该方法声明如下： <br /><font color="#3300ff">public static bool Exists(<br />　string path;<br />);</font><br />　　下面的代码判断是否存在c:\tempuploads\NewDirectory目录。若存在，先获取该目录下的子目录和文件，然后其移动，最后将移动后的目录删除。若不存在，则先创建该目录,然后将目录属性设为只读、隐藏<br /><font color="#3809f7">if(File.Exists(@"c:\tempuploads\NewDirectory")) <font color="#33cc33">//判断目录是否存在<br /></font>{<br />　GetDirectory(); <font color="#33cc33">//获取子目录<br /></font>　GetFile(); <font color="#48dd22">//获取文件<br /></font>　MoveDirectory();<font color="#3dee11"> //移动目录<br /></font>　DeleteDirectory(); <font color="#3dee11">//删除目录<br /></font>}<br />else<br />{<br />　MakeDirectory(); <font color="#11ee3d">//生成目录<br /></font>　SetDirectory(); <font color="#11ee3d">//设置目录属性<br /></font>} <br /></font>　　注意： <br />　　路径有3种方式，当前目录下的相对路径、当前工作盘的相对路径、绝对路径。以C:\Tmp\Book为例(假定当前工作目录为C:\Tmp)。“Book”，“\Tmp\Book”,“C:\Tmp\Book”都表示C:\Tmp\Book。 <br />　　另外，在C#中 “\”是特殊字符，要表示它的话需要使用“\\”。由于这种写法不方便，C#语言提供了@对其简化。只要在字符串前加上@即可直接使用“\”。所以上面的路径在C#中应该表示为“Book”，@“\Tmp\Book”，@“C:\Tmp\Book”。<br /><br />from: <a href="http://www.sz-accp.com.cn/xxyd/ShowArticle.asp?ArticleID=625">http://www.sz-accp.com.cn/xxyd/ShowArticle.asp?ArticleID=625</a><img src ="http://www.blogjava.net/weidagang2046/aggbug/89120.html" width = "1" height = "1" /><br><br><div align=right><a style="text-decoration:none;" href="http://www.blogjava.net/weidagang2046/" target="_blank">weidagang2046</a> 2006-12-20 19:49 <a href="http://www.blogjava.net/weidagang2046/articles/89120.html#Feedback" target="_blank" style="text-decoration:none;">发表评论</a></div>]]></description></item><item><title>解析.Net框架下的XML编程技术</title><link>http://www.blogjava.net/weidagang2046/articles/88074.html</link><dc:creator>weidagang2046</dc:creator><author>weidagang2046</author><pubDate>Fri, 15 Dec 2006 16:09:00 GMT</pubDate><guid>http://www.blogjava.net/weidagang2046/articles/88074.html</guid><wfw:comment>http://www.blogjava.net/weidagang2046/comments/88074.html</wfw:comment><comments>http://www.blogjava.net/weidagang2046/articles/88074.html#Feedback</comments><slash:comments>0</slash:comments><wfw:commentRss>http://www.blogjava.net/weidagang2046/comments/commentRss/88074.html</wfw:commentRss><trackback:ping>http://www.blogjava.net/weidagang2046/services/trackbacks/88074.html</trackback:ping><description><![CDATA[
		<font face="Verdana">一．前言： </font>
		<p>
				<font face="Verdana">XML是微软.Net战略的一个重要组成部分，而且它可谓是XML Web服务的基石，所以掌握.Net框架下的XML技术自然显得非常重要了。本文将指导大家如何运用C#语言完成.Net框架下的XML文档的读写操作。首先，我会向大家介绍.Net框架中与XML相关的命名空间和其中的重要类。其次，我还会给出有关的实例以使读者更进一步的了解XML文档的读写操作的具体方法。 </font>
		</p>
		<p>
				<font face="Verdana">二．XML命名空间和相关类简介： </font>
		</p>
		<p>
				<font face="Verdana">在深入进行.Net框架下的XML文档的操作之前，我想很有必要向大家介绍.Net框架中与XML技术有关的命名空间和其中一些重要的类。.Net框架为我们提供了以下一些命名空间：System.Xml、System.Xml.Schema、System.Xml.Serialization、System.Xml.Xpath以及 System.Xml.Xsl来包容和XML操作相关的类。 </font>
		</p>
		<p>
				<font face="Verdana">System.Xml命名空间包含了一些最重要的XML类，其中最主要的类是和XML文档的读写操作相关的类。这些类中包括4个与读相关的类以及2个与写相关的类。它们分别是：XmlReader、XmlTextReader、XmlValidatingReader、XmlNodeReader、XmlWriter以及 XmlTextWriter。本文将重点介绍这些类，因为它们是最基本也是最重要的类。 </font>
		</p>
		<p>
				<font face="Verdana">XmlReader类是一个虚基类，它包含了读XML文档的方法和属性。该类中的Read方法是一个基本的读XML文档的方法，它以流形式读取XML文档中的节点（Node）。另外，该类还提供了ReadString、ReadInnerXml、ReadOuterXml和ReadStartElement等更高级的读方法。除了提供读XML文档的方法外，XmlReader类还为程序员提供了MoveToAttribute、MoveToFirstAttribute、MoveToContent、MoveToFirstContent、MoveToElement以及 MoveToNextAttribute等具有导航功能的方法。在本文后面介绍的实例中，我们将运用到这些方法。 </font>
		</p>
		<p>
				<font face="Verdana">XmlTextReader、XmlNodeReader以及XmlValidatingReader等类是从XmlReader类继承过来的子类。根据它们的名称，我们可以知道其作用分别是读取文本内容、读取节点和读取XML模式（Schemas）。 </font>
		</p>
		<p>
				<font face="Verdana">XmlWriter类为程序员提供了许多写XML文档的方法，它是XmlTextWriter类的基类，我在后面的实例中会给出相关的运用方法。 </font>
		</p>
		<p>
				<font face="Verdana">XmlNode类是一个非常重要的类，它代表了XML文档中的某个节点。该节点可以是XML文档的根节点，这样它就代表整个XML文档了。它是许多很有用的类的基类，这些类包括插入节点的类、删除节点的类、替换节点的类以及在XML文档中完成导航功能的类。同时，XmlNode类还为程序员提供了获取双亲节点、子节点、最后一个子节点、节点名称以及节点类型等的属性。它的三个最主要的子类包括：XmlDocument、XmlDataDocument以及XmlDocumentFragment。XmlDocument类代表了一个XML文档，它提供了载入和保存XML文档的方法和属性。这些方法包括了Load、LoadXml和Save等。同时，它还提供了添加特性（Attributes）、说明（Comments）、空间（Spaces）、元素（Elements）和新节点（New Nodes）等XML项的功能。XmlDocumentFragment类代表了一部分XML文档，它能被用来添加到其他的XML文档中。XmlDataDocument类可以让程序员更好地完成和ADO.NET中的数据集对象之间的互操作。 </font>
		</p>
		<p>
				<font face="Verdana">除了上面介绍的System.Xml命名空间中的类外，该命名空间还包括了XmlConvert、XmlLinkedNode以及XmlNodeList等类，不过这些类不是本文介绍的重点，有兴趣的读者不妨去参考相关文档资料。 </font>
		</p>
		<p>
				<font face="Verdana">System.Xml.Schema命名空间中包含了和XML模式相关的类，这些类包括XmlSchema、XmlSchemaAll、XmlSchemaXPath以及XmlSchemaType等类。 </font>
		</p>
		<p>
				<font face="Verdana">System.Xml.Serialization命名空间中包含了和XML文档的序列化和反序列化操作相关的类，XML文档的序列化操作能将XML格式的数据转化为流格式的数据并能在网络中传输，而反序列化则完成相反的操作，即将流格式的数据还原成XML格式的数据。 </font>
		</p>
		<p>
				<font face="Verdana">System.Xml.XPath命名空间包含了XPathDocument、XPathExression、XPathNavigator以及XPathNodeIterator等类，这些类能完成XML文档的导航功能。在XPathDocument类的协助下，XPathNavigator类能完成快速的XML文档导航功能，该类为程序员提供了许多Move方法以完成导航功能。 </font>
		</p>
		<p>
				<font face="Verdana">System.Xml.Xsl命名空间中的类完成了XSLT的转换功能。 </font>
		</p>
		<p>
				<font face="Verdana">三．读XML文档的方法： </font>
		</p>
		<p>
				<font face="Verdana">在介绍完.Net框架中和XML有关的命名空间和相关类后，我接着向大家介绍和XML相关的一些操作。首先，我向大家介绍的读取XML文档的方法。在下面的实例程序中，我将运用VS.net开发工具附带的"books.xml"文件来作为示例。你可以在你的机器上搜索到该文件（或请参考附录），或者你也可以运用其他的XML文件。 </font>
		</p>
		<p>
				<font face="Verdana">首先，我们用XmlTextReader类的对象来读取该XML文档。方法很简单，就是在创建新对象的构造函数中指明XML文件的位置即可。 </font>
		</p>
		<p>
				<font face="Verdana">XmlTextReader textReader = new XmlTextReader("C:\\books.xml"); </font>
		</p>
		<p>
				<font face="Verdana">一旦新对象创建完毕，你就可以调用其Read方法来读取XML文档了。调用Read方法之后，信息被存储起来，你可以通过读取该对象的Name、BaseURI、Depth、LineNumber等属性来获取这些信息。下面我给出一个完整的实例，该实例通过简单的读取"books.xml"文件，然后将其中的信息显示在控制台中。 </font>
		</p>
		<p>
				<font face="Verdana">using System;<br />using System.Xml;  </font>
		</p>
		<p>
				<font face="Verdana">namespace ReadXml<br />{<br />    class Class1<br />    {<br />        static void Main( string[] args )<br />        {<br />            // 创建一个XmlTextReader类的对象并调用Read方法来读取文件<br />            XmlTextReader textReader  = new XmlTextReader("C:\\books.xml");<br />            textReader.Read();<br />            // 节点非空则执行循环体<br />            while ( textReader.Read() )<br />            {<br />                // 读取第一个元素<br />                textReader.MoveToElement();<br />                Console.WriteLine("XmlTextReader Properties Test");<br />                Console.WriteLine("===================");  </font>
		</p>
		<p>
				<font face="Verdana">                // 读取该元素的属性并显示在控制台中<br />                Console.WriteLine("Name:" + textReader.Name);<br />                Console.WriteLine("Base URI:" + textReader.BaseURI);<br />                Console.WriteLine("Local Name:" + textReader.LocalName);<br />                Console.WriteLine("Attribute Count:" + textReader.AttributeCount.ToString());<br />                Console.WriteLine("Depth:" + textReader.Depth.ToString());<br />                Console.WriteLine("Line Number:" + textReader.LineNumber.ToString());<br />                Console.WriteLine("Node Type:" + textReader.NodeType.ToString());<br />                Console.WriteLine("Attribute Count:" + textReader.Value.ToString());<br />            }<br />        }<br />    }<br />}<br /> </font>
		</p>
		<font face="Verdana">
				<p>
						<br />XmlTextReader类中有一个很重要的属性－NodeType，通过该属性，我们可以知道其节点的节点类型。而枚举类型XmlNodeType中包含了诸如Attribute、CDATA、Element、Comment、Document、DocumentType、Entity、ProcessInstruction以及WhiteSpace等的XML项的类型。通过与XmlNodeType中的元素的比较，我们可以获取相应节点的节点类型并对其完成相关的操作。下面我就给出一个实例，该实例读取每个节点的NodeType，并根据其节点类型显示其中的内容，同时程序还记录了XML文件中每种节点类型的数目。 </p>
				<p>using System;<br />using System.Xml;  </p>
				<p>namespace ReadXML<br />{<br />    class Class2<br />    {<br />        static void Main( string[] args )<br />        {<br />            int ws = 0;<br />            int pi = 0;<br />            int dc = 0;<br />            int cc = 0;<br />            int ac = 0;<br />            int et = 0;<br />            int el = 0;<br />            int xd = 0;  </p>
				<p>            XmlTextReader textReader  = new XmlTextReader("C:\\books.xml");  </p>
				<p>            while (textReader.Read())<br />            {<br />                XmlNodeType nType = textReader.NodeType;</p>
				<p>                // 节点类型为XmlDeclaration<br />                if (nType == XmlNodeType.XmlDeclaration)<br />                {<br />                    Console.WriteLine("Declaration:" + textReader.Name.ToString());<br />                    xd = xd + 1;<br />                }           </p>
				<p>                // 节点类型为Comment<br />                if( nType == XmlNodeType.Comment)<br />                {<br />                    Console.WriteLine("Comment:" + textReader.Name.ToString());<br />                    cc = cc + 1;<br />                }           </p>
				<p>                // 节点类型为Attribute<br />                if( nType == XmlNodeType.Attribute)<br />                {<br />                    Console.WriteLine("Attribute:" + textReader.Name.ToString());<br />                    ac = ac + 1;<br />                }  </p>
				<p>                // 节点类型为Element<br />                if ( nType == XmlNodeType.Element)<br />                {<br />                    Console.WriteLine("Element:" + textReader.Name.ToString());<br />                    el = el + 1;<br />                }  </p>
				<p>                // 节点类型为Entity<br />                if ( nType == XmlNodeType.Entity )<br />                {<br />                    Console.WriteLine("Entity:" + textReader.Name.ToString());<br />                    et = et + 1;<br />                }           </p>
				<p>                // 节点类型为Process Instruction<br />                if( nType == XmlNodeType.ProcessingInstruction )<br />                {<br />                    Console.WriteLine("Process Instruction:" + textReader.Name.ToString());<br />                    pi = pi + 1;<br />                }       </p>
				<p>                // 节点类型为DocumentType<br />                if( nType == XmlNodeType.DocumentType)<br />                {<br />                    Console.WriteLine("DocumentType:" + textReader.Name.ToString());<br />                    dc = dc + 1;<br />                }  </p>
				<p>                // 节点类型为Whitespace<br />                if ( nType == XmlNodeType.Whitespace )<br />                {<br />                    Console.WriteLine("WhiteSpace:" + textReader.Name.ToString());<br />                    ws = ws + 1;<br />                }<br />            }  </p>
				<p>            // 在控制台中显示每种类型的数目<br />            Console.WriteLine("Total Comments:" + cc.ToString());<br />            Console.WriteLine("Total Attributes:" + ac.ToString());<br />            Console.WriteLine("Total Elements:" + el.ToString());<br />            Console.WriteLine("Total Entity:" + et.ToString());<br />            Console.WriteLine("Total Process Instructions:" + pi.ToString());<br />            Console.WriteLine("Total Declaration:" + xd.ToString());<br />            Console.WriteLine("Total DocumentType:" + dc.ToString());<br />            Console.WriteLine("Total WhiteSpaces:" + ws.ToString());<br />        }<br />    }<br />}<br /> </p>
				<p>
						<br />以上，我向大家介绍了如何运用XmlTextReader类的对象来读取XML文档，并根据节点的NodeType属性来取得其节点类型信息。同时XmlReader这个基类还有XmlNodeReader和XmlValidatingReader等派生类，它们分别是用来读取XML文档的节点和模式的。限于篇幅，这里就不介绍了，读者可以参考有关资料。 </p>
				<p>四．写XML文档的方法： </p>
				<p>XmlWriter类包含了写XML文档所需的方法和属性，它是XmlTextWriter类和XmlNodeWriter类的基类。该类包含了WriteNode、WriteString、WriteAttributes、WriteStartElement以及WriteEndElement等一系列写XML文档的方法，其中有些方法是成对出现的。比如你要写入一个元素，你首先得调用WriteStartElement方法，接着写入实际内容，最后是调用WriteEndElement方法以表示结束。该类还包含了WriteState、XmlLang和XmlSpace等属性，其中WriteState属性表明了写的状态。因为XmlWriter类包含了很多写XML文档的方法，所以这里只是介绍最主要的几种。下面我们通过其子类XmlTextWriter类来说明如何写XML文档。 </p>
				<p>首先，我们要创建一个XmlTextWriter类的实例对象。该类的构造函数XmlTextWriter有三种重载形式，其参数分别为一个字符串、一个流对象和一个TextWriter对象。这里我们运用字符串的参数形式，该字符串就指明了所要创建的XML文件的位置，方法如下： </p>
				<p>XmlTextWriter textWriter = New XmlTextWriter("C:\\myXmFile.xml", null);<br /> </p>
				<p>
						<br />在创建完对象后，我们调用WriterStartDocument方法开始写XML文档，在完成写工作后，就调用WriteEndDocument结束写过程并调用Close方法将它关闭。在写的过程中，我们可以调用WriteComment方法来添加说明，通过调用WriteString方法来添加一个字符串，通过调用WriteStartElement和WriteEndElement方法对来添加一个元素，通过调用WriteStartAttribute和WriteEndAttribute方法对来添加一个属性。我们还可以通过调用WriteNode方法来添加整一个节点，其它的写的方法还包括WriteProcessingInstruction和WriteDocType等等。下面的实例就是介绍如何具体运用这些方法来完成XML文档的写工作的。 </p>
				<p>using System;<br />using System.Xml;  </p>
				<p>namespace WriteXML<br />{<br /> class Class1<br /> {<br />  static void Main( string[] args )<br />  {<br />   try<br />   {<br />    // 创建XmlTextWriter类的实例对象<br />    XmlTextWriter textWriter = new XmlTextWriter("C:\\w3sky.xml", null);<br />    textWriter.Formatting = Formatting.Indented;</p>
				<p>    // 开始写过程，调用WriteStartDocument方法<br />    textWriter.WriteStartDocument();  </p>
				<p>    // 写入说明<br />    textWriter.WriteComment("First Comment XmlTextWriter Sample Example");<br />    textWriter.WriteComment("w3sky.xml in root dir");   </p>
				<p>    //创建一个节点<br />    textWriter.WriteStartElement("Administrator");<br />    textWriter.WriteElementString("Name", "formble");<br />    textWriter.WriteElementString("site", "w3sky.com");<br />    textWriter.WriteEndElement();<br />    </p>
				<p>
						<br />    // 写文档结束，调用WriteEndDocument方法<br />    textWriter.WriteEndDocument();</p>
				<p>    // 关闭textWriter<br />    textWriter.Close();</p>
				<p>   }<br />   catch(System.Exception e)<br />   {<br />    Console.WriteLine(e.ToString());<br />   }<br />  }<br /> }<br />}</p>
				<p>
						<br /> </p>
				<p>五．运用XmlDocument类： </p>
				<p>XmlDocument类的对象代表了一个XML文档，它也是一个非常重要的XML类。该类包含了Load、LoadXml以及Save等重要的方法。其中Load方法可以从一个字符串指定的XML文件或是一个流对象、一个TextReader对象、一个XmlReader对象导入XML数据。LoadXml方法则完成从一个特定的XML文件导入XML数据的功能。它的Save方法则将XML数据保存到一个XML文件中或是一个流对象、一个TextWriter对象、一个XmlWriter对象中。 </p>
				<p>下面的程序中我们用到了XmlDocument类对象的LoadXml方法，它从一个XML文档段中读取XML数据并调用其Save方法将数据保存在一个文件中。 </p>
				<p>// 创建一个XmlDocument类的对象<br />XmlDocument doc = new XmlDocument();<br />doc.LoadXml(("&lt;Student type='regular' Section='B'&gt;&lt;Name&gt;Tommy Lex&lt;/Name&gt;&lt;/Student&gt;"));</p>
				<p>// 保存到文件中<br />doc.Save("C:\\student.xml");<br /> </p>
				<p>
						<br />这里，我们还可以通过改变Save方法中参数，将XML数据显示在控制台中，方法如下： </p>
				<p>doc.Save(Console.Out);<br /> </p>
				<p>
						<br />而在下面的程序中，我们用到了一个XmlTextReader对象，通过它我们读取"books.xml"文件中的XML数据。然后创建一个XmlDocument对象并载入XmlTextReader对象，这样XML数据就被读到XmlDocument对象中了。最后，通过该对象的Save方法将XML数据显示在控制台中。 </p>
				<p>XmlDocument doc = new XmlDocument();<br />// 创建一个XmlTextReader对象，读取XML数据<br />XmlTextReader reader = new XmlTextReader("c:\\books.xml");<br />reader.Read();</p>
				<p>// 载入XmlTextReader类的对象<br />doc.Load(reader);<br />// 将XML数据显示在控制台中<br />doc.Save(Console.Out);<br /> <br />六．总结： </p>
				<p>XML技术作为.Net的基石，其重要性自然不言而喻。.Net框架包含了五个命名空间和大量的类来支持与XML技术有关的操作。其中System.Xml是最重要的一个命名空间，其中的XmlReader类和XmlWriter类以及它们的派生类完成了XML文档的读写操作，是最基本也是最重要的类。XmlDocument类代表了XML文档，它能完成与整个XML文档相关的各类操作，同时和其相关的XmlDataDocument类也是非常重要的，值得读者的深入研究。 </p>
				<p>附录 </p>
				<p>"books.xml"文件如下： </p>
				<p>&lt;?xml version='1.0'?&gt;<br />&lt;!-- This file represents a fragment of a book store inventory database --&gt;<br />&lt;bookstore&gt;<br />  &lt;book genre="autobiography" publicationdate="1981" ISBN="1-861003-11-0"&gt;<br />    &lt;title&gt;The Autobiography of Benjamin Franklin&lt;/title&gt;<br />    &lt;author&gt;<br />      &lt;first-name&gt;Benjamin&lt;/first-name&gt;<br />      &lt;last-name&gt;Franklin&lt;/last-name&gt;<br />    &lt;/author&gt;<br />    &lt;price&gt;8.99&lt;/price&gt;<br />  &lt;/book&gt;<br />  &lt;book genre="novel" publicationdate="1967" ISBN="0-201-63361-2"&gt;<br />    &lt;title&gt;The Confidence Man&lt;/title&gt;<br />    &lt;author&gt;<br />      &lt;first-name&gt;Herman&lt;/first-name&gt;<br />      &lt;last-name&gt;Melville&lt;/last-name&gt;<br />    &lt;/author&gt;<br />    &lt;price&gt;11.99&lt;/price&gt;<br />  &lt;/book&gt;<br />  &lt;book genre="philosophy" publicationdate="1991" ISBN="1-861001-57-6"&gt;<br />    &lt;title&gt;The Gorgias&lt;/title&gt;<br />    &lt;author&gt;<br />      &lt;first-name&gt;Sidas&lt;/first-name&gt;<br />      &lt;last-name&gt;Plato&lt;/last-name&gt;<br />    &lt;/author&gt;<br />    &lt;price&gt;9.99&lt;/price&gt;<br />  &lt;/book&gt;<br />&lt;/bookstore&gt;<br /><br /><br />from: <a href="http://www.w3sky.com/147.html">http://www.w3sky.com/147.html</a></p>
		</font>
<img src ="http://www.blogjava.net/weidagang2046/aggbug/88074.html" width = "1" height = "1" /><br><br><div align=right><a style="text-decoration:none;" href="http://www.blogjava.net/weidagang2046/" target="_blank">weidagang2046</a> 2006-12-16 00:09 <a href="http://www.blogjava.net/weidagang2046/articles/88074.html#Feedback" target="_blank" style="text-decoration:none;">发表评论</a></div>]]></description></item><item><title>更改窗口客户区的背景</title><link>http://www.blogjava.net/weidagang2046/articles/81593.html</link><dc:creator>weidagang2046</dc:creator><author>weidagang2046</author><pubDate>Thu, 16 Nov 2006 12:03:00 GMT</pubDate><guid>http://www.blogjava.net/weidagang2046/articles/81593.html</guid><wfw:comment>http://www.blogjava.net/weidagang2046/comments/81593.html</wfw:comment><comments>http://www.blogjava.net/weidagang2046/articles/81593.html#Feedback</comments><slash:comments>0</slash:comments><wfw:commentRss>http://www.blogjava.net/weidagang2046/comments/commentRss/81593.html</wfw:commentRss><trackback:ping>http://www.blogjava.net/weidagang2046/services/trackbacks/81593.html</trackback:ping><description><![CDATA[看多了简单的纯白色背景客户区，有没有想过让自己的程序中的客户区变成以位图（BMP）作为背景呢？那么下面提供的方法可以帮得到你。<br />接下来我会用两种方法来实现将位图加入显示到客户区中。不过首先要说的是，在此之前我们要准备好一张需要加到客户区的位图，并把它加入到程序的资源文件中。<br />第一种方法以BitBlt来实现。我<br />们新建一个单文档MFC程序，把位图加入到资源后，我们重点把代码写进C××View（此为继承于CView的类）中的OnDraw函数中。也就是说在窗口重绘时就将图片加进客户区。<br />我们看源代码： <br />void CLoadBitmapView::OnDraw(CDC* pDC) { <br /> CLoadBitmapDoc* pDoc = GetDocument(); <br /> ASSERT_VALID(pDoc); <br /> // TODO: add draw code for native data here <br /> CBitmap bitmap;<br /> //这里的IDB_BITMAP_TEXT是我的工程中的位图资源 <br /> //将位图资源加入到CBitmap的对象中 <br /> bitmap.LoadBitmap(IDB_BITMAP_TEXT); <br /> //创建一个设备对象 <br /> CDC drawDC; <br /> //为指定设备创建信息上下文。这提供了一种不创建设备上下文即获取有关设备信息的快速方式 <br /> drawDC.CreateCompatibleDC(pDC); <br /> //加入位图对象到DC对象 <br /> drawDC.SelectObject(&amp;bitmap); <br /> BITMAP bmpInfo; <br /> //获取位图信息 <br /> bitmap.GetObject(sizeof(bmpInfo),&amp;bmpInfo); <br /> //指定要执行的光栅操作。光栅操作代码定义GDC如何合并输出操作中的颜色，包括当前画刷、可能的源位图和目标位图。 <br /> pDC-&gt;BitBlt(0,0,bmpInfo.bmWidth,bmpInfo.bmHeight,&amp;drawDC,0,0,SRCCOPY);<br />}<br />测试一下，位图是不是已经加入了客户区呢？<br />第二种方法，是通过画刷来对客户区进行填充来实现的。<br />我们来看源代码：<br />void CLoadBitmapView::OnDraw(CDC* pDC)<br />{<br /> CLoadBitmapDoc* pDoc = GetDocument();<br /> ASSERT_VALID(pDoc);<br /> // TODO: add draw code for native data here<br /> //选入设备<br /> CBitmap bitmap;<br /> bitmap.LoadBitmap(IDB_BITMAP_TEXT);<br /> CBrush bru;<br /> CRgn rgn;<br /> CRect rect;<br /> //获取客户区的区域信息<br /> GetClientRect(&amp;rect);<br /> rgn.CreateRectRgnIndirect(&amp;rect);<br /> //<font size="2">位图指定的模式初始化画刷</font><br /> bru.CreatePatternBrush(&amp;bitmap);<br /> //用指定画刷和指定区域来填充<br /> pDC-&gt;FillRgn(&amp;rgn,&amp;bru);<br />}<br />运行一下，是不是同样可以实现呢？<br /><br />from: <img src ="http://www.blogjava.net/weidagang2046/aggbug/81593.html" width = "1" height = "1" /><br><br><div align=right><a style="text-decoration:none;" href="http://www.blogjava.net/weidagang2046/" target="_blank">weidagang2046</a> 2006-11-16 20:03 <a href="http://www.blogjava.net/weidagang2046/articles/81593.html#Feedback" target="_blank" style="text-decoration:none;">发表评论</a></div>]]></description></item><item><title>Creating draggable windows - SDI and dialogs</title><link>http://www.blogjava.net/weidagang2046/articles/81304.html</link><dc:creator>weidagang2046</dc:creator><author>weidagang2046</author><pubDate>Wed, 15 Nov 2006 08:42:00 GMT</pubDate><guid>http://www.blogjava.net/weidagang2046/articles/81304.html</guid><wfw:comment>http://www.blogjava.net/weidagang2046/comments/81304.html</wfw:comment><comments>http://www.blogjava.net/weidagang2046/articles/81304.html#Feedback</comments><slash:comments>0</slash:comments><wfw:commentRss>http://www.blogjava.net/weidagang2046/comments/commentRss/81304.html</wfw:commentRss><trackback:ping>http://www.blogjava.net/weidagang2046/services/trackbacks/81304.html</trackback:ping><description><![CDATA[
		<table cellspacing="0" cellpadding="0" border="0">
				<tbody>
						<tr valign="top">
								<td width="100%">
										<table width="100%">
												<tbody>
														<tr valign="top">
																<td class="SmallText" nowrap="">
																</td>
																<td nowrap="" align="right">
																		<a name="__top">
																		</a>
																		<table>
																				<tbody>
																						<tr>
																								<td class="smallText" align="right">17 votes for this article.</td>
																								<td>
																										<table cellspacing="0" cellpadding="0" border="2">
																												<tbody>
																														<tr>
																																<td>
																																		<img height="5" src="http://www.codeproject.com/script/images/red.gif" width="20" border="0" />
																																</td>
																																<td>
																																		<img height="5" src="http://www.codeproject.com/script/images/red.gif" width="20" border="0" />
																																</td>
																																<td>
																																		<img height="5" src="http://www.codeproject.com/script/images/red.gif" width="20" border="0" />
																																</td>
																																<td>
																																		<img height="5" src="http://www.codeproject.com/script/images/red.gif" width="20" border="0" />
																																</td>
																																<td>
																																		<img height="5" src="http://www.codeproject.com/script/images/red.gif" width="2" border="0" />
																																		<img height="5" src="http://www.codeproject.com/script/images/white.gif" width="18" border="0" />
																																</td>
																														</tr>
																												</tbody>
																										</table>
																								</td>
																						</tr>
																						<tr>
																								<td class="smallText" align="right" colspan="2">
																										<a title="Calculated as rating x Log10(# votes)" href="http://www.codeproject.com/script/articles/top_articles.asp?st=2">Popularity: 5.05</a>. Rating: <b>4.1</b> out of 5.</td>
																						</tr>
																				</tbody>
																		</table>
																</td>
														</tr>
												</tbody>
										</table>
								</td>
						</tr>
						<tr>
								<td class="ArticlePane">
										<span id="intelliTXT">
												<div id="contentdiv">
														<!-- Article Starts -->
														<ul class="download">
																<li>
																		<a href="http://www.codeproject.com/dialog/dragwindows/SrcDragDialog.zip">Download Dialog project - 19 Kb</a>
																</li>
																<li>
																		<a href="http://www.codeproject.com/dialog/dragwindows/SrcDragSDI.zip">Download SDI  project - 25 Kb</a>
																</li>
														</ul>
														<p>
																<img height="276" src="http://www.codeproject.com/dialog/dragwindows/dlgscrshot.jpg" width="441" border="0" />
														</p>
														<p>
																<img height="229" src="http://www.codeproject.com/dialog/dragwindows/sdiscrshot.jpg" width="378" border="0" />
														</p>
														<h2>Introduction</h2>
														<p nd="1">The standard practice for moving a window is to drag it's title bar. This is handled for us by the operating system itself. But there are some applications that allow us to move the entire window by dragging anywhere within it's body. Sometimes it is pretty annoying when that happens, but there might be occasions where this is required. For dialog based applications there is a despicable trick which we can use to achieve this. The secret is to handle <code nd="2">WM_NCHITTEST</code> and to trick the OS into thinking that the mouse click or movement was made in the title bar. I explain the technique in more detail later down the article. But in SDI applications, there is a slight issue. Because all the mouse clicks and moves are handled by the view class! And if you attempt to use the same technique you used in the case of the dialog based application, the results will be wretchedly peculiar. Of course this just means we'll have to write that much more code. I show how this is done later down the article. I haven't tried out this technique on MDI applications, but my guess is that with a little bit of adjusting it should work fine on MDI applications as well.</p>
														<p nd="3">Of course there is always more than one way to skin a cat, and it's not much different when it comes to programming. The same goes for this article too. Roman Nurik, has explained a much easier way to accomplish the same as I have. I have included this much easier method in terms of number of lines and effort at the end of the article. I could have put them on the top of the article too, but I wanted the flow of the article to be from good to better. In fact after Roman's method, I have also provided a solution offered by Albert Ling, which in my opinion is  the most innovative of all the methods discussed here.</p>
														<h2>Draggable dialogs</h2>
														<p nd="4">The <code nd="5">WM_NCHITTEST</code> message will sent to the dialog when the mouse is moved through it or a mouse click is made on it. Of course since we are using MFC, we have the <code nd="6">OnNcHitTest</code> function which handles  the <code nd="7">WM_NCHITTEST</code> message. The function returns one of several enumerated values, each of which indicates where the mouse action took place. Now one of these enumerated values is <code nd="8">HTCAPTION</code> which indicates that the mouse action took place on the title bar. So what we do is to verify if the mouse is currently within the client area of the window, and if it is within the client area of the window, we check if the mouse is down through a flag that is set and unset from the <code nd="9">OnLButtonDown</code> and <code nd="10">OnLButtonUp</code> handlers. If all our checks are passed, we return <code nd="11">HTCAPTION,</code> thus fooling the OS into thinking that the action is taking place on the title bar. It is very important to verify that the mouse action is within the dialog's client area, otherwise any buttons we have on the title bar, like the close and maximize buttons will be rendered useless.</p>
														<pre nd="12">UINT CDragDialogDlg::OnNcHitTest(CPoint point)
{
    CRect r;
    GetClientRect(&amp;r);
    ClientToScreen(&amp;r);

    <span class="cpp-comment">//Chk to see if the mouse is within </span><span class="cpp-comment">//the dialog client area</span><span class="cpp-keyword">if</span>(r.PtInRect(point))
    {       
        <span class="cpp-keyword">if</span>(m_mousedown)
        {       
            <span class="cpp-keyword">return</span> HTCAPTION;
        }
    }

    <span class="cpp-keyword">return</span> CDialog::OnNcHitTest(point);

}</pre>
														<p nd="14">
																<code nd="13">m_mousedown</code> is a <code><span class="cpp-keyword">bool</span></code> member variable which is set and unset from the <code nd="15">OnLButtonDown</code> and <code nd="16">OnLButtonUp</code> handlers. We also set <code nd="17">m_mousedown</code>  to <code><span class="cpp-keyword">true</span></code> in the <code nd="18">OnInitDialog</code> handler because otherwise the first drag attempt will fail, as <code nd="19">m_mousedown</code> will still be <code><span class="cpp-keyword">false</span></code> when the <code nd="20">OnNcHitTest</code> handler is called.</p>
														<pre nd="21">
																<span class="cpp-keyword">void</span> CDragDialogDlg::OnLButtonDown(UINT nFlags, 
                                   CPoint point)
{   
    m_mousedown = <span class="cpp-keyword">true</span>;     

    CDialog::OnLButtonDown(nFlags, point);
}

<span class="cpp-keyword">void</span> CDragDialogDlg::OnLButtonUp(UINT nFlags, 
                                 CPoint point)
{
    m_mousedown = <span class="cpp-keyword">false</span>;

    CDialog::OnLButtonUp(nFlags, point);
}</pre>
														<h2>Draggable SDI windows</h2>
														<p nd="22">As I have mentioned earlier we cannot use the technique we used for dialog based applications here. The whole issue here is that the <code nd="23">CView</code> derived class is a wrapper for the view window and not for the main window of the application. The main window of an SDI application is wrapped by the <code nd="24">CMainFrame</code> class which is derived from <code nd="25">CFrameWnd</code> by the App wizard. All mouse clicks and movements within the view are handled by the view class and not by the frame window class. Well, this time we use another solution to settle our issue. We do the most obvious thing to do in the situation, which is that we move the window using code. </p>
														<pre nd="26">
																<span class="cpp-keyword">void</span> CDragSDIView::OnLButtonDown(UINT nFlags, 
                                 CPoint point)
{
    m_mousedown = <span class="cpp-keyword">true</span>; 
    ClientToScreen(&amp;point);
    m_lastpoint = point;

    CView::OnLButtonDown(nFlags, point);
}
</pre>
														<p nd="27">Just as in the previous case, we override <code nd="28">OnLButtonDown</code>. We set the <code nd="29">m_mousedown</code> flag to <code><span class="cpp-keyword">true</span></code>. This time as you'll notice we also have a <code nd="30">CPoint</code> member variable called <code nd="31">m_lastpoint</code> in our <code nd="32">CView</code> derived class. We set <code nd="33">m_lastpoint</code> to the <code nd="34">CPoint</code> passed to us in <code nd="35">OnLButtonDown</code>. </p>
														<pre nd="36">
																<span class="cpp-keyword">void</span> CDragSDIView::OnLButtonUp(UINT nFlags, 
                               CPoint point)
{   
    m_mousedown = <span class="cpp-keyword">false</span>;

    CView::OnLButtonUp(nFlags, point);
}</pre>
														<p nd="38">
																<code nd="37">OnLButtonUp</code> is also overridden. But here we haven't done anything different from what we did in the case of the dialog based application. All our window moving work is done in the <code nd="39">OnMouseMove</code> function which we override as shown below.</p>
														<pre nd="40">
																<span class="cpp-keyword">void</span> CDragSDIView::OnMouseMove(UINT nFlags, 
                               CPoint point)
{   
    CRect r;
    GetClientRect(&amp;r);
    ClientToScreen(&amp;r);
    ClientToScreen(&amp;point);

    <span class="cpp-keyword">if</span>(r.PtInRect(point))
    {       
        <span class="cpp-keyword">if</span>(m_mousedown)
        {   
            AfxGetMainWnd()-&gt;GetWindowRect(&amp;r); 

            AfxGetMainWnd()-&gt;MoveWindow(
                r.left - (m_lastpoint.x - point.x),
                r.top - (m_lastpoint.y - point.y),
                r.Width(),r.Height());
            m_lastpoint = point;                
        }
    }

    CView::OnMouseMove(nFlags, point);
}</pre>
														<p nd="41">Well, we first check to see if the point is within the client area of the view window.  If it is, then we check the <code nd="42">m_mousedownflag</code> is <code><span class="cpp-keyword">true</span></code>. If <code nd="43">m_mousedownflag</code> is <code><span class="cpp-keyword">true</span></code>, we figure out the current window coordinates by calling <code nd="44">GetWindowRect</code> on the main frame window. Now we call <code nd="45">MoveWindow</code> on the main frame window and pass it the new values which we calculate using the <code nd="46">CPoint</code> passed to us by <code nd="47">OnMouseMove</code>, the <code nd="48">m_lastpoint</code> member variable and the <code nd="49">CRect</code> obtained by calling <code nd="50">GetWindowRect</code> on the main frame window. And finally we set <code nd="51">m_lastpoint</code> to the new <code nd="52">CPoint</code>.</p>
														<h2>A much easier way - Roman Nurik </h2>
														<p nd="53">As I have mentioned in the introduction, there are multiple ways to skin cats, though why anyone would ever want to skin cats beat me. Cat skins are not exactly useful in my opinion. Alright, let's get to Roman's method for making draggable windows. First override <code nd="54">WM_LBUTTONDOWN</code>  and then send a <code nd="55">WM_NCLBUTTONDOWN</code> message to the main window of your application. For dialog apps, this will be the dialog window itself and for SDI apps, this will be your <code nd="56">CMainFrame</code> window. The <code nd="57">WM_NCLBUTTONDOWN</code> message is sent to a window when a left mouse click is made on the non client area of the window. The <code nd="58">wParam</code> specifies the hit-test enumeration value. We pass <code nd="59">HTCAPTION</code> and the <code nd="60">lParam</code> specifies the cursor position, which we pass as a 0 so that it's sure to be in the title bar. Thus we end up with one of the following implementations.</p>
														<pre nd="61">ReleaseCapture(); <span class="cpp-comment">//This is not compulsory</span>
POINT pt; 
GetCursorPos(&amp;pt);
POINTS pts = {pt.x, pt.y}; 
::SendMessage(m_hWnd,WM_NCLBUTTONDOWN,HTCAPTION,(LPARAM)&amp;pts);</pre>
														<pre nd="62">ReleaseCapture(); <span class="cpp-comment">//This is not compulsory</span>
::SendMessage(m_hWnd,WM_NCLBUTTONDOWN,HTCAPTION,<span class="cpp-literal">0</span>);</pre>
														<p nd="64">
																<code nd="63">m_hWnd</code> is the window handle of the main application window. With MFC, you usually have a <code nd="65">CWnd*</code> and not an <code nd="66">HWND</code>. In such cases you can do an MFC <code nd="67">CWnd</code> version of the  <code nd="68">SendMessage</code> call.</p>
														<pre nd="69">pWnd-&gt;SendMesage(WM_NCLBUTTONDOWN,HTCAPTION,<span class="cpp-literal">0</span>);</pre>
														<p nd="70">Well, that was sure easier than the previously discussed techniques, wasn't it and thanks goes to Roman Nurik for this really cool tip. But then I guess each method would have it's pros and cons which may make themselves visible at random.</p>
														<h2>Another way - Albert Ling </h2>
														<p nd="71">Well, we come to that matter of cats and skins again. Here is yet another solution suggested by Albert Ling, that seems to me to be the best of all the methods we have investigated. He overrides <code nd="72">OnNcHitTest</code> and then calls the base class implementation. He checks the value returned by the base class implementation and if it is <code nd="73">HTCLIENT</code>, he returns <code nd="74">HTCAPTION</code>. This one is for dialog based applications.</p>
														<pre nd="75">UINT CYourDlg::OnNcHitTest(CPoint point)
{
    UINT hit = CDialog::OnNcHitTest(point);
    <span class="cpp-keyword">if</span> ( hit == HTCLIENT ) 
    {
        <span class="cpp-keyword">return</span> HTCAPTION;
    }
    <span class="cpp-keyword">else</span><span class="cpp-keyword">return</span> hit;
}</pre>
														<p nd="76">If you thought Albert Ling's solution for dialog based apps was cool, you are yet to see his solution for SDI apps. It was simply amazing! This is what he did. He overrides <code nd="77">OnNcHitTest</code> in the <code nd="78">CView</code> derived class and calls the base class implementation first. If the base class call returns <code nd="79">HTCLIENT</code>, he returns <code nd="80">HTTRANSPARENT</code>. Now, <code nd="81">HTTRANSPARENT</code> indicates that the mouse action was on a window that's covered by another window, and thus the message gets sent to the underlying window in the thread, which in our case would be the main frame window. Thus we override <code nd="82">OnNcHitTest</code> in <code nd="83">CMainFrame</code> and call the base class method. If the base class method returns <code nd="84">HTCLIENT</code>, then we return <code nd="85">HTCAPTION</code>.</p>
														<pre nd="86">UINT CYourView::OnNcHitTest(CPoint point)
{
    UINT hit = CView::OnNcHitTest(point);
    <span class="cpp-keyword">if</span> (hit == HTCLIENT )
        <span class="cpp-keyword">return</span> HTTRANSPARENT;
    <span class="cpp-keyword">else</span>
    {
        <span class="cpp-keyword">return</span> hit;
    }
}

UINT CMainFrame::OnNcHitTest(CPoint point)
{
    UINT hit = CFrameWnd::OnNcHitTest(point);
    <span class="cpp-keyword">if</span> ( hit == HTCLIENT ) 
    {
        <span class="cpp-keyword">return</span> HTCAPTION;
    }
    <span class="cpp-keyword">else</span><span class="cpp-keyword">return</span> hit;

}</pre>
														<h2>Conclusion</h2>
														<p nd="87">Well, when I wrote this article I was under the thoroughly mistaken impression that my method was the only way to go about doing it. That's when Roman came and proved me wrong by offering his solution which was quite simpler to implement. Just when I was trying to be complacent about all this by talking about the cat-skins in the old adage, out comes Albert Ling with yet another amazing solution. Now I live in constant fear of being bombarded with other solutions and feel like a haunted man. Heheh. No, actually I don't. Was just kidding. If any of you have other solutions, feel free to suggest it here via the forum, or email me directly so that we can make this a single point source for all methods used for creating draggable windows.</p>
														<!-- Article Ends -->
												</div>
										</span>
										<script src="/script/togglePre.js" type="text/javascript">
										</script>
										<h2>About Nishant Sivakumar</h2>
										<div style="OVERFLOW: hidden">
												<table border="0">
														<tbody>
																<tr valign="top">
																		<td class="smallText" nowrap="">
																				<img src="http://www.codeproject.com/script/profile/images/{4042092F-4729-4864-9714-03D49519AF8A}.jpg" />
																				<br />
																				<br />
																				<img src="http://www.codeproject.com/script/images/admin_modify.gif" align="absBottom" /> Editor<br /><img src="http://www.codeproject.com/script/images/sitebuild_icon.gif" /> Site Builder</td>
																		<td class="smallText">Nish is a real nice guy living in Atlanta, who has been coding since 1990, when he was 13 years old. Originally from sunny Trivandrum in India, he recently moved to Atlanta from Toronto and is a little sad that he won't be able to play in snow anymore.<br /><br />Nish has been a Microsoft Visual C++ MVP since October, 2002 - awfully nice of Microsoft, he thinks. He maintains an MVP tips and tricks web site - <a href="http://www.voidnish.com/" target="_blank">www.voidnish.com</a> where you can find a consolidated list of his articles, writings and ideas on VC++, MFC, .NET and C++/CLI. Oh, and you might want to check out his blog on C++/CLI, MFC, .NET and a lot of other stuff - <a href="http://blog.voidnish.com/" target="_blank">blog.voidnish.com</a><br /><br />Nish loves reading Science Fiction, P G Wodehouse and Agatha Christie, and also fancies himself to be a decent writer of sorts. He has authored a romantic comedy <a href="http://www.vedamsbooks.com/no23584.htm" target="_blank">Summer Love and Some more Cricket</a> as well as a programming book – <a href="http://www.amazon.com/exec/obidos/ASIN/032117352X/" target="_blank">Extending MFC applications with the .NET Framework</a>.<br /><br />Nish is currently working on a C++/CLI book for Manning Publications titled <a href="http://www.manning.com/sivakumar/" target="_blank">C++/CLI in Action</a>. You can read more about the book on his blog. 
<p class="smallText">Click <a href="http://www.codeproject.com/script/profile/whos_who.asp?vt=arts&amp;id=20248">here</a> to view Nishant Sivakumar's online profile.</p></td>
																</tr>
														</tbody>
												</table>
										</div>
								</td>
						</tr>
				</tbody>
		</table>from: <a href="http://www.codeproject.com/dialog/dragwindows.asp?print=true">http://www.codeproject.com/dialog/dragwindows.asp?print=true</a><img src ="http://www.blogjava.net/weidagang2046/aggbug/81304.html" width = "1" height = "1" /><br><br><div align=right><a style="text-decoration:none;" href="http://www.blogjava.net/weidagang2046/" target="_blank">weidagang2046</a> 2006-11-15 16:42 <a href="http://www.blogjava.net/weidagang2046/articles/81304.html#Feedback" target="_blank" style="text-decoration:none;">发表评论</a></div>]]></description></item><item><title>Windows窗口操作函数</title><link>http://www.blogjava.net/weidagang2046/articles/81245.html</link><dc:creator>weidagang2046</dc:creator><author>weidagang2046</author><pubDate>Wed, 15 Nov 2006 05:57:00 GMT</pubDate><guid>http://www.blogjava.net/weidagang2046/articles/81245.html</guid><wfw:comment>http://www.blogjava.net/weidagang2046/comments/81245.html</wfw:comment><comments>http://www.blogjava.net/weidagang2046/articles/81245.html#Feedback</comments><slash:comments>0</slash:comments><wfw:commentRss>http://www.blogjava.net/weidagang2046/comments/commentRss/81245.html</wfw:commentRss><trackback:ping>http://www.blogjava.net/weidagang2046/services/trackbacks/81245.html</trackback:ping><description><![CDATA[
		<span class="large">
				<font color="#000000"> 
<p>从VC提供的MFC类派生图中我们可以看出窗口的派生关系，<a href="http://tech.china.com/zh_cn/netschool/programme/c/656/20001207/4e_g1.gif" target="_blank">派生图</a>，所有的窗口类都是由CWnd派生。所有CWnd的成员函数在其派生类中都可以使用。本节介绍一些常用的功能给大家。 </p><p><b>改变窗口状态：</b><br />BOOL EnableWindow( BOOL bEnable = TRUE );可以设置窗口的禁止/允许状态。BOOL IsWindowEnabled( );可以查询窗口的禁止/允许状态。 <br />BOOL ModifyStyle( DWORD dwRemove, DWORD dwAdd, UINT nFlags = 0 )/BOOL ModifyStyleEx( DWORD dwRemove, DWORD dwAdd, UINT nFlags = 0 );可以修改窗口的风格，而不需要调用SetWindowLong <br />BOOL IsWindowVisible( ) 可以检查窗口是否被显示。 <br />BOOL ShowWindow( int nCmdShow );将改变窗口的显示状态，nCmdShow可取如下值： 
</p><ul><li><b>SW_HIDE</b> 隐藏窗口 
</li><li><b>SW_MINIMIZE SW_SHOWMAXIMIZED</b> 最小化窗口 
</li><li><b>SW_RESTORE</b> 恢复窗口 
</li><li><b>SW_SHOW</b> 显示窗口 
</li><li><b>SW_SHOWMINIMIZED</b> 最大化窗口 </li></ul><p></p><p><b>改变窗口位置：</b><br />void MoveWindow( LPCRECT lpRect, BOOL bRepaint = TRUE );可以移动窗口。<br />void GetWindowRect( LPRECT lpRect ) ;可以得到窗口的矩形位置。<br />BOOL IsIconic( ) ;可以检测窗口是否已经缩为图标。<br />BOOL SetWindowPos( const CWnd* pWndInsertAfter, int x, int y, int cx, int cy, UINT nFlags );可以改变窗口的Z次序，此外还可以移动窗口位置。 </p><p><b>使窗口失效，印发重绘：</b><br />void Invalidate( BOOL bErase = TRUE );使整个窗口失效，bErase将决定窗口是否产生重绘。<br />void InvalidateRect( LPCRECT lpRect, BOOL bErase = TRUE )/void InvalidateRgn( CRgn* pRgn, BOOL bErase = TRUE );将使指定的矩形/多边形区域失效。 </p><p><b>窗口查找：</b><br />static CWnd* PASCAL FindWindow( LPCTSTR lpszClassName, LPCTSTR lpszWindowName );可以以窗口的类名和窗口名查找窗口。任一参数设置为NULL表对该参数代表的数据进行任意匹配。如FindWindow("MyWnd",NULL)表明查找类名为MyWnd的所有窗口。 <br />BOOL IsChild( const CWnd* pWnd ) 检测窗口是否为子窗口。 <br />CWnd* GetParent( ) 得到父窗口指针。 <br />CWnd* GetDlgItem( int nID ) 通过子窗口ID得到窗口指针。 <br />int GetDlgCtrlID( ) 得到窗口ID值。 <br />static CWnd* PASCAL WindowFromPoint( POINT point );将从屏幕上某点坐标得到包含该点的窗口指针。 <br />static CWnd* PASCAL FromHandle( HWND hWnd );通过HWND构造一个CWnd*指针，但该指针在空闲时会被删除，所以不能保存供以后使用。 </p><p><b>时钟：</b><br />UINT SetTimer( UINT nIDEvent, UINT nElapse, void (CALLBACK EXPORT* lpfnTimer)(HWND, UINT, UINT, DWORD) );可以创建一个时钟，如果lpfnTimer回调函数为NULL，窗口将会收到WM_TIMER消息，并可以在afx_msg void OnTimer( UINT nIDEvent );中安排处理代码 <br />BOOL KillTimer( int nIDEvent );删除一个指定时钟。 </p><p><b>可以利用重载来添加消息处理的虚函数：</b><br />afx_msg int OnCreate( LPCREATESTRUCT lpCreateStruct );窗口被创建时被调用 <br />afx_msg void OnDestroy( );窗口被销毁时被调用 <br />afx_msg void OnGetMinMaxInfo( MINMAXINFO FAR* lpMMI );需要得到窗口尺寸时被调用 <br />afx_msg void OnSize( UINT nType, int cx, int cy );窗口改变大小后被调用 <br />afx_msg void OnMove( int x, int y );窗口被移动后时被调用 <br />afx_msg void OnPaint( );窗口需要重绘时时被调用，你可以填如绘图代码，对于视图类不需要重载OnPaint，所有绘图代码应该在OnDraw中进行 <br />afx_msg void OnChar( UINT nChar, UINT nRepCnt, UINT nFlags );接收到字符输入时被调用 <br />afx_msg void OnKeyDown/OnKeyUp( UINT nChar, UINT nRepCnt, UINT nFlags );键盘上键被按下/放开时被调用 <br />afx_msg void OnLButtonDown/OnRButtonDown( UINT nFlags, CPoint point );鼠标左/右键按下时被调用 <br />afx_msg void OnLButtonUp/OnRButtonUp( UINT nFlags, CPoint point );鼠标左/右键放开时被调用 <br />afx_msg void OnLButtonDblClk/OnRButtonDblClk( UINT nFlags, CPoint point );鼠标左/右键双击时被调用 <br />afx_msg void OnMouseMove( UINT nFlags, CPoint point );鼠标在窗口上移动时被调用 <br /><br />from: <a href="http://tech.china.com/zh_cn/netschool/programme/c/656/20001207/01_4e.html">http://tech.china.com/zh_cn/netschool/programme/c/656/20001207/01_4e.html</a></p></font>
		</span>
<img src ="http://www.blogjava.net/weidagang2046/aggbug/81245.html" width = "1" height = "1" /><br><br><div align=right><a style="text-decoration:none;" href="http://www.blogjava.net/weidagang2046/" target="_blank">weidagang2046</a> 2006-11-15 13:57 <a href="http://www.blogjava.net/weidagang2046/articles/81245.html#Feedback" target="_blank" style="text-decoration:none;">发表评论</a></div>]]></description></item><item><title>MFC窗口位置管理详细分析及实例</title><link>http://www.blogjava.net/weidagang2046/articles/80594.html</link><dc:creator>weidagang2046</dc:creator><author>weidagang2046</author><pubDate>Sat, 11 Nov 2006 08:55:00 GMT</pubDate><guid>http://www.blogjava.net/weidagang2046/articles/80594.html</guid><wfw:comment>http://www.blogjava.net/weidagang2046/comments/80594.html</wfw:comment><comments>http://www.blogjava.net/weidagang2046/articles/80594.html#Feedback</comments><slash:comments>0</slash:comments><wfw:commentRss>http://www.blogjava.net/weidagang2046/comments/commentRss/80594.html</wfw:commentRss><trackback:ping>http://www.blogjava.net/weidagang2046/services/trackbacks/80594.html</trackback:ping><description><![CDATA[在一般用MFC编写的程序的窗口客户区中，可能有好几个子窗口(具有WM_CHILD风格的窗口)。上边是工具栏，中间是视图窗口，下边是状态栏。三个窗口在框架的客户区里和平共处，互不重叠。主框架窗口的尺寸改变了，别的子窗口都能及时调整自己的尺寸以便保持相互位置关系不变，例如状态条窗口总能保持在主框架客户区底部，并且其宽度总能和主框架客户区宽度一致。工具栏窗口总能停靠在主框架的某一边不变，其宽度或高度总能和主框架客户区的宽度或高度一致，视图窗口总能填满主框架客户区的剩余空间。  <br /><br />假如我们自己从CWnd类派生一个窗口类并生成一个窗口，在它的客户区里要生成若干个子窗口，我们想使这些子窗口排列得规规矩矩，互不重叠，当父窗口的尺寸变了时各个子窗口能适时调整自己的尺寸和位置，使各个子窗口之间的位置大小比例关系不变。当移动其中一个或几个子窗口时，别的子窗口能及时为这个移动了的子窗口让位。当然我们可以利用api函数里管理窗口的函数来编写自己的管理子窗口的方法。可是如果在父窗口的客户区里有了工具栏，状态条等等子窗口时，你自己加进来的子窗口还能和这些mfc提供的子窗口融洽相处吗？你如何保证你的子窗口不会覆盖了能够四处停靠的工具栏？当工具栏和状态条消失后你的子窗口如何才能知道，以便及时调整自己的大小从而覆盖工具栏和状态条腾出的空间？基于文档视图构架的窗口的客户区内还有个视图，你自己硬加上的子窗口能不和视图窗口争地盘吗？  <br /><br />所以必须了解mfc的窗口管理它的客户区的方法。其实，mfc的窗口管理它的客户区的方法是非常简单的：父窗口调用一个函数，子窗口响应一个消息，就这么多。 <br /><br />CWnd::RepositionBars函数和WM_SIZEPARENT消息 <br /><br />先简述一下mfc的窗口为子窗口分配客户区空间的过程：这一过程是父窗口与子窗口共同协调完成的。父窗口先提供它的客户区内的一块区域，叫做起始可用区域。然后调用一个函数，在这个函数里，父窗口把这片区域通过一个消息提交给它的第一个子窗口，该子窗口决定自己要占用多大一块，然后在可用区域里把它将占据的部分划出去，这样可用区域就被切去了一块。父窗口再把这块剩下的可用区域通过同样的消息提交给第二个子窗口，第二个子窗口再根据自己的需要切掉一块。如此这般，每个子窗口都切去自己所需的一块。最后剩下的可用区域就给最后的子窗口使用。可以看出，除了最后一个子窗口外，其它子窗口都得在消息响应函数里有自己的算法来决定自己将在可用区域里占据多大一块，最后一个子窗口由于别无选择，所以不需要这样的算法。  <br /><br />当然，初始的可用区域是一个矩形，每次被切割后剩下的可用区域还是一个矩形，不可能是别的形状的。  <br /><br />举例说来，在一个典型单文档程序中，父窗口就是从CFrameWnd派生的主框架窗口，最后一个子窗口就是视图窗口，如果用了CSplitterWnd生成分隔条的话，最后一个子窗口就是拥有分隔条的那个窗口。其它子窗口就是工具栏窗口和状态条窗口，以及可能有的别的控件窗口。  <br /><br />在典型多文档界面程序中，父窗口就是主框架窗口，最后一个子窗口就是覆盖在主窗口客户区，背景为黑灰色，拥有包含文档的子框架窗口的那个窗口，这是个预定义了窗口类的窗口，它的窗口类名是“MDIClient”。如果用了CSplitterWnd生成分隔条的话，最后一个子窗口就是拥有分隔条的那个窗口。其它窗口就是工具栏窗口，状态条窗口以及可能有的别的控件窗口。  <br /><br />这个函数和消息是：函数CWnd::RepositionBars()以及消息WM_SIZEPARENT。这个消息是mfc自定义的，不是windows自有的。  <br /><br />先简单说明一下这个函数和消息。  <br /><br />1。函数CWnd::RepositionBars() <br /><br />这个函数不是虚函数，所以就无法在派生类里通过覆盖来编制自己的版本了，只能搞懂它的功能，以便能灵活使用。  <br /><br />简单而言，这个函数的功能是将可用的客户区区域信息放到消息WM_SIZEPARENT的消息参数里，然后枚举本窗口的所有子窗口，给每个子窗口 (除掉一个特定的子窗口，相当于上文提到的最后一个子窗口)都发送这个消息，每个响应这个消息的子窗口都会把可用客户区切去一块。最后把那个特定的子窗口的尺寸和位置调整到刚好放在最后剩下的可用区域里。  <br /><br />2。消息WM_SIZEPARENT  <br /><br />每个欲参与分配客户区的子窗口都要响应这个消息，除非这个子窗口是那个特定的子窗口。  <br /><br />响应这个消息的子窗口至少要做两件事：1，将可用的父窗口客户区切去自己所占据的一块。2，根据消息参数的指示，将自己的大小和位置调整到刚好容纳到自己所占据的区域里或不做调整。  <br /><br />下面详细介绍一下函数CWnd::RepositionBars()和消息WM_SIZEPARENT。  <br /><br />1。函数CWnd::RepositionBars() void RepositionBars( UINT nIDFirst, UINT nIDLast, UINT nIDLeftOver, UINT nFlag = CWnd::reposDefault, LPRECT lpRectParam = NULL, LPCRECT lpRectClient = NULL, BOOL bStretch = TRUE );  <br /><br />参数比较多，但还是比较好懂的。  <br /><br />（1）nIDFirst和nIDLast  <br /><br />参与分配父窗口客户区的子窗口的id范围。  <br /><br />每个WM_CHILD风格的窗口都有个id，这是在窗口创建过程中指定的。函数CWnd::Create()的第六个参数就是这个id。api函数CreateWindow和 CreateWindowEx里的那个HMENU类型的参数，当窗口的风格里有WM_CHILD时，它不是指的菜单句柄，而是该窗口的id。  <br /><br />nIDFirst和nIDLast参数指明了：如果一个子窗口的id值大于等于nIDFirst并且小于等于nIDLast，在这个函数中才会给这个子窗口发送 WM_SIZEPARENT消息，这个子窗口才能参与父窗口客户区的分配。  <br /><br />（2）nIDLeftOver  <br /><br />前面说过，有一个特定的子窗口，它不响应WM_SIZEPARENT消息。只有当其它的子窗口都分配完了，它才来捡取父窗口客户区里剩下的那块。 nIDLeftOver正是这个子窗口的id。它也必须大于等于nIDFirst并且小于等于nIDLast。  <br /><br />（3）lpRectClient  <br /><br />这是一个指向RECT结构数据的指针。这个RECT结构里存放的正是父窗口客户区的初始可用区域。随着在该函数里依次给各个子窗口发送 WM_SIZEPARENT消息，每个响应这个消息的子窗口都会切去自己所占据的部分。最后剩下的部分，就是id为nIDLeftOver的子窗口将要占据的区域了。这个参数可以为NULL，这时初始的可用区域就是整个父窗口客户区。  <br /><br />（4）nFlag和lpRectParam  <br /><br />这两个参数放在一起讲比较好。nFlag是该函数的功能标志，它可以有三个值：reposDefault，reposQuery 和reposExtra。  <br /><br />当nFlag等于reposDefault时，RepositionBars函数的功能是这样的：依次给id介于nIDFirst和nIDLast之间并且不等于nIDLeftOver的子窗口发送WM_SIZEPARENT消息，每个响应这个消息的子窗口从lpRectClient所指的结构里切去自己所占据的部分，并且将自己的大小和位置调整到自己所占据的区域的大小，最后RepositionBars函数还将id为nIDLeftOver的子窗口的大小和位置调整到被其他子窗口切剩的可用区域内，使这个子窗口正好完全覆盖最后的可用区域。这种情况下lpRectParam不用，可以为NULL。  <br /><br />当nFlag等于reposQuery 时，RepositionBars函数的功能是这样的：依次给id介于nIDFirst和nIDLast之间并且不等于nIDLeftOver的子窗口发送WM_SIZEPARENT消息，每个响应这个消息的子窗口从lpRectClient所指的结构里切去自己所占据的部分，但是他们并不调整自己的大小和位置，最后RepositionBars函数并不调整将id为nIDLeftOver的子窗口的大小和位置，而是根据bStretch的值来做动作：如果bStretch为TRUE，那么 RepositionBars函数把最后剩下的可用区域拷贝到lpRectParam指向的RECT结构里；如果bStretch为FALSE，那么RepositionBars函数把所有其他子窗口占用掉的可用区域的高和宽(要所有的子窗口都紧排在一起，形成一个大的矩形，这个值才有意义)拷贝到lpRectParam指向的RECT结构的bottom 和right成员里，其top和left成员被置零。使用这个nFlag值来调用RepositionBars的目的不是要重排子窗口，而是要看看，假如重排子窗口的话，这些子窗口将占去多大一块，最后剩下的可用区域在什么位置等等信息。  <br /><br />当nFlag等于reposExtra时，该函数的功能和nFlag等于reposDefault时差不多，有点小小的区别。此时需要用到lpRectParam。前面说过，当 nFlag等于reposDefault时，RepositionBars函数将在最后把id为nIDLeftOver的子窗口的大小和位置调整到被其他子窗口切剩的可用区域内，使这个子窗口正好完全覆盖最后的可用区域。而当nFlag等于reposExtra时，RepositionBars在调整id为nIDLeftOver的子窗口的大小和位置前，还要用 lpRectParam来对最后剩下的可用区域做修正。假设lpRect指向的是最后的可用区域，那么这个修正是这样进行的：  <br /><br /><br />lpRect-&gt;top+=lpRectParam-&gt;top; <br />lprect-&gt;left+=lpRectParam-&gt;left; <br />lpRect-&gt;right-=lpRectParam-&gt;right; <br />lpRect-&gt;bottom-=lpRectParam-&gt;bottom; <br /><br />通过这样的修正，可以使最后剩下的可用区域不被id为nIDLeftOver的子窗口占满，而是空出一些地方来留作他用。  <br />（5）bStretch  <br /><br />这个参数上面已经提到一点它的作用。它主要是提供给各个响应WM_SIZEPARENT消息的子窗口用的，子窗口例如工具栏，状态条等在决定自己将从父窗口客户区的可用空间里划走多少时，这个参数也是个判断的依据。详细可以参阅工具栏和状态条响应WM_SIZEPARENT的函数OnSizeParent()。  <br /><br />2。消息WM_SIZEPARENT  <br /><br />这是个mfc自定义的消息。在msdn里的TN024这篇技术文章里有关于这个消息的说明。  <br /><br />该消息的两个参数中wParam不用，lParam是指向一个AFX_SIZEPARENTPARAMS结构变量的指针，这个结构变量是在RepositionBars函数里定义的：  <br /><br />AFX_SIZEPARENTPARAMS layout;  <br /><br />AFX_SIZEPARENTPARAMS结构定义如下：  <br /><br />struct AFX_SIZEPARENTPARAMS <br />{ <br />HDWP hDWP;  <br />RECT rect;  <br />SIZE sizeTotal;  <br />BOOL bStretch;  <br />}; <br />这个结构变量的成员是在RepositionBars函数里填写的：它的bStretch成员就是RepositionBars的参数bStretch，它的sizeTotal成员的两个成员cx和cy都被设置为零，它的rect成员就是从RepositionBars的参数lpRectClient里拷贝过来的，就是父窗口客户区的初始可用区域嘛。每个响应这个消息的子窗口都必须修改rect成员的值，以便切去自己所占据的部分。  <br />成员hDWP是什么？这得知道三个api函数：BebinDeferWindowPos()，DeferWindowPos()和EndDeferWindowPos()。这三个api函数是用来成批设置窗口的位置和尺寸的。BebinDeferWindowPos()先通知windows分配一个将用来存贮窗口的位置和尺寸信息的结构，它不是返回这个结构的指针，而是返回代表这个结构的句柄，句柄的类型是HDWP。然后每个需要重新设置位置和尺寸的窗口都要调用DeferWindowPos()函数(该函数需要那个HDWP 类型的句柄为参数)，以便往那个结构里填写各自的窗口位置和大小信息。最后，在某个合适的时候调用EndDeferWindowPos()，windows就会根据那个结构里的信息把有关的窗口的位置和大小一次性设置好。比起针对每个窗口分别用SetWindowPos()等函数逐个设置来说，这种方法速度快。  <br /><br />好了，在RepositionBars函数里正是调用了BebinDeferWindowPos()，获得一个HDWP类型的句柄，这个句柄就被填写到了上面那个结构变量 layout的成员hDWP里。然后RepositionBars函数给每个符合条件的子窗口发送WM_SIZEPARENT消息。在每个响应WM_SIZEPARENT消息的子窗口里，要调用DeferWindowPos()来设置位置和尺寸信息。当所有的子窗口都响应完毕WM_SIZEPARENT消息后，RepositionBars函数再调用 EndDeferWindowPos()函数，这一来，除了那个id为nIDLeftOver的子窗口外，所有的子窗口都一次性排好了位置了。  <br /><br />至于该结构的sizeTotal成员的意义，它累计每个子窗口所占据掉的可用区域的长宽尺寸和。每个子窗口在响应WM_SIZEPARENT消息时一般都要把自己所占据的区域的高和宽分别累加到sizeTotal结构的cy和cx成员里。这有什么意义呢？当每个子窗口所占据的区域都是挨在一起的时候，这个 sizeTotal结构就有意义了，主框架窗口可以使nFlag等于reposQuery，使bStretch等于FALSE来调用RepositionBars函数，RepositionBars函数会把 sizeTotal结构的两个成员值拷贝到lpRectParam参数里返回给主框架类(前面也提到过)，这样主框架类就知道它的客户区内的子窗口占去了客户区内多大的一块空间。如果你的主框架窗口没有利用这个信息，那么响应WM_SIZEPARENT消息的子窗口就可以不理睬sizeTotal成员。  <br /><br />ID的分配  <br /><br />可以看到，每个子窗口都有个id，同一个父窗口的子窗口的id不能重复。mfc的一些现成的控件子窗口都有预定义的id：  <br /><br />id名 id值 意义 <br /><br />AFX_IDW_TOOLBAR 0xE800 // 主窗口的工具栏的id <br />AFX_IDW_STATUS_BAR 0xE801 // 状态栏的id <br />AFX_IDW_PREVIEW_BAR 0xE802 // PrintPreview Dialog Bar <br />AFX_IDW_RESIZE_BAR 0xE803 // OLE in-place resize bar <br />AFX_IDW_REBAR 0xE804 // COMCTL32 "rebar" Bar <br />AFX_IDW_DIALOGBAR 0xE805 // CDialogBar <br /><br />还有象单文档程序的视图窗口，多文档程序的那个MDIClient窗口，分隔条窗口，他们的id值介于下面两个id值之间：  <br />AFX_IDW_PANE_FIRST 0xE900 // <br />AFX_IDW_PANE_LAST 0xE9FF <br /><br />你要给你自己的子窗口分配id的话，别和上面的重复了。一般如果用IDE的菜单view/resource symbols项来加入自己的id的话，是不会重复的。有关id，还可以看看msdn里的TN020文章，那是专讲id的。  <br /><br />实例分析  <br /><br />1。CFrameWnd类是如何调用RepositionBars函数的  <br /><br />前面介绍了RepositionBars的各个参数和意义，现在看看CFrameWnd类是如何调用这个函数的，从中可以学习RepositionBars函数的使用方法。  <br /><br />CFrameWnd类及其派生类生成的窗口的客户区内可以有工具栏，状态条和视图窗口等子窗口。当父窗口的尺寸发生变化时，这些子窗口的各自的位置和大小比例关系保持不变，这就需要父窗口一旦在它自己的尺寸发生变化时就调用RepositionBars函数。CFrameWnd类是集中在函数 RecalcLayout里调用RepositionBars函数的。该类保证了在窗口尺寸发生变化时函数RecalcLayout都被调用，从而RepositionBars函数也能被及时调用，确保了各个子窗口都能及时调整自己的位置和大小。  <br /><br />RecalcLayout是个虚函数。该函数的功能就是在主框架的客户区内提供一个初始的可用区域，并把这个区域放在一个CRect类型的变量里。该函数大致是这样的：  <br /><br />void CFrameWnd::RecalcLayout(BOOL bNotify) <br />{ <br />if (m_bInRecalcLayout) <br />return;//这大概是在防止该函数重入 <br />m_bInRecalcLayout = TRUE; <br />.... <br />.... <br />.... <br />.... <br />if (GetStyle() &amp; FWS_SNAPTOBARS) <br />{ <br />CRect rect(0, 0, 32767, 32767); <br />RepositionBars(0, 0xffff, AFX_IDW_PANE_FIRST, reposQuery, <br />&amp;rect, &amp;rect, FALSE); <br />RepositionBars(0, 0xffff, AFX_IDW_PANE_FIRST, reposExtra, <br />&amp;m_rectBorder, &amp;rect, TRUE); <br />CalcWindowRect(&amp;rect); <br />SetWindowPos(NULL, 0, 0, rect.Width(), rect.Height(), <br />SWP_NOACTIVATE|SWP_NOMOVE|SWP_NOZORDER); <br />} <br />else <br />RepositionBars(0, 0xffff, AFX_IDW_PANE_FIRST, reposExtra, &amp;m_rectBorder); <br />m_bInRecalcLayout = FALSE; <br />} 可以看出，mfc认为这个函数是不能重入的。在编制自己的RecalcLayout()函数时也得用同样的方法来防止重入。 <br />后面的if语句检查框架窗口是否具有风格FWS_SNAPTOBARS，这个风格用在什么时候呢？我是这样认为的：通常都是在主框架窗口的尺寸改变 <br />时，子窗口在响应WM_SIZEPARENT消息时调整自己的尺寸以便跟上框架窗口的尺寸变化。有这样的情况：父窗口的客户区内的子窗口的数目是动态变 <br />化的，而且这些子窗口互相不能重叠，他们的尺寸由于某种原因不好改变。那么当子窗口的数目发生增减时，如不调整父窗口自己的尺寸，就会导 <br />致客户区留下空白或新增加的子窗口没有多余空间安排。FWS_SNAPTOBARS风格就是用在这种情况下，使父窗口能调整自己的大小以便容纳子窗口。 <br />看这个分支里的语句，似乎是这样的。 <br />一般都不会有FWS_SNAPTOBARS风格的，所以一般是执行else分支。在这个分支里简单地调用RepositionBars去重排所有的子窗口，它的参数 <br />lpRectClient 使用默认的NULL值，意思就是初始可用区域是父窗口的整个客户区。 <br />可以在自己的派生类里编写自己的RecalcLayout函数，以便用自己的方法调用RepositionBars函数。要注意的是在CFrameWnd类的窗口刚被创建 <br />时RecalcLayout函数也被调用，此时可能某些用户自己加的子窗口还未被创建出来，所以在这个函数内如果要引用某个用户自己加的子窗口的句柄 <br />的话必须先用::IsWindow()函数判断一下该窗口句柄是否可用。否则的话就会出现非法操作了。 <br /><br /><br />实战演练 <br />由于精力有限，只提供一个实战例子：将视图，工具栏和状态栏赶到右边 <br />我们要生成这样的界面：视图窗口，工具栏和状态条统统在右边，左边是个自己加的窗口。 <br />第一步：启动AppWizard生成一个单文档程序，全部使用默认设置。 <br />第二步：在CMainFrame类里增加一个成员 CWnd m_mywnd。 <br />第三步：在CMainFrame::OnCreate()函数里增加这几行： <br />m_mywnd.CreateEx <br />( <br />WS_EX_CLIENTEDGE, <br />AfxRegisterWndClass <br />( <br />CS_HREDRAW|CS_VREDRAW, <br />::LoadCursor(NULL,IDC_ARROW), <br />::CreateSolidBrush(RGB(190,190,190)) <br />), <br />"", <br />WS_VISIBLE|WS_CHILD, <br />CRect(0,0,0,0), <br />this, <br />IDC_MYPANE //用IDE的菜单view/resource symbols项加入的id。 <br />); <br /><br />第四步：启动ClassView，在CMainFrame里加上虚函数RecalcLayout()，函数体这样写： <br />void CMainFrame::RecalcLayout(BOOL bNotify) <br />{ <br />if (m_bInRecalcLayout) <br />return; <br />m_bInRecalcLayout = TRUE; <br /><br />//rect1是新加的窗口将占据的区域 <br />//rect2就是提供给工具栏，状态条和视图窗口的初始可用区域。 <br />CRect rect1,rect2; <br />GetClientRect(&amp;rect1); <br />rect1.right=rect1.right/3; <br />GetClientRect(&amp;rect2); <br />rect2.left=rect2.right/3; <br /><br />if(::IsWindow(m_mywnd.m_hWnd)) //这句是不能少的 <br />m_mywnd.MoveWindow(&amp;rect1); <br />RepositionBars(0, 0xffff, AFX_IDW_PANE_FIRST, reposExtra, CRect(0,0,0,0),&amp;rect2); <br />m_bInRecalcLayout = FALSE; <br />} <br /><br />第五步：用IDE的菜单view/resource symbols项加入一个id：IDC_MYPANE。 <br />第六步：编译并运行程序。 <br /><br />好了，在主框架窗口的左边多了一个灰色的窗口，它占主窗口客户区的三分之一。工具栏，状态条和视图都被赶到右边三分之二的地方去了。<br /><br />from: <a href="http://dev.poptool.net/soft/vc/36541.html">http://dev.poptool.net/soft/vc/36541.html</a><img src ="http://www.blogjava.net/weidagang2046/aggbug/80594.html" width = "1" height = "1" /><br><br><div align=right><a style="text-decoration:none;" href="http://www.blogjava.net/weidagang2046/" target="_blank">weidagang2046</a> 2006-11-11 16:55 <a href="http://www.blogjava.net/weidagang2046/articles/80594.html#Feedback" target="_blank" style="text-decoration:none;">发表评论</a></div>]]></description></item><item><title>超前引用</title><link>http://www.blogjava.net/weidagang2046/articles/80581.html</link><dc:creator>weidagang2046</dc:creator><author>weidagang2046</author><pubDate>Sat, 11 Nov 2006 07:16:00 GMT</pubDate><guid>http://www.blogjava.net/weidagang2046/articles/80581.html</guid><wfw:comment>http://www.blogjava.net/weidagang2046/comments/80581.html</wfw:comment><comments>http://www.blogjava.net/weidagang2046/articles/80581.html#Feedback</comments><slash:comments>0</slash:comments><wfw:commentRss>http://www.blogjava.net/weidagang2046/comments/commentRss/80581.html</wfw:commentRss><trackback:ping>http://www.blogjava.net/weidagang2046/services/trackbacks/80581.html</trackback:ping><description><![CDATA[    所谓超前引用是指一个类型在定义之前就被用来定义变量和声明函数。<br />    一般情况下，C/C++要求所有的类型必须在使用前被定义，但是在一些特殊情况下，这种要求无法满足，<br />例如，在类CMyView中保留了一个非模式对话框对象指针，该对象用于显示/修改一些信息。为了实现对话框<br />“应用”按钮，把对话框做的修改立刻更新到view界面上，为此，需要在对话框类中需要保存view类的指针，这样<br />定义关系就变成如下的代码：<br /><br />    #ifndef __MYVIEW_H__<br />    #define __MYVIEW_H__<br />    //这是view类的头函数<br />    #include "MyDialog.h"<br />    class CMyView::public CView<br />    {<br />    protected:<br />        CMyDialog * pDlg;<br />        //这里是其他定义<br />    };<br />    #endif<br />    <br />    #ifndef __MYDIALOG_H__<br />    #define __MYDIALOG_H__<br />    //这是对话框类的定义<br />    #include "MyView.h"<br />    class CMyDialog::public CDialog<br />    {<br />        protected:<br />            CMyView * pView;<br />            //其他定义<br />    };<br />    #endif<br />    <br />    从编译器角度看，编译MyDialog.CPP时，系统首先定义宏__MYDIALOG_H__，然后包含MyView.h，MyView.h中<br />的#include "MyDialog.h"由于__MYDIALOG_H__已经定义，所以不再起作用。在CMyView类的声明中，<br />        CMyDialog* pDlg ;<br />就会让编译器产生“CMyDialog"类型没有定义之类的错误，编译MyView.CPP文件出现的错误可以类似得到。<br />    <br />    更一般的情况，类A和类B需要彼此互相引用，这样必然有一个类会先被定义，而另外一个类后被定义，这样在<br />先被定义的类引用后被定义的类的时候，就导致了所谓的超前引用。<br /><br />    超前引用导致的错误有以下几种处理办法：<br />    1) 使用类声明<br />    在超前引用一个类之前，首先用一个特殊的语句说明该标识符是一个类名，即将被超前引用。其使用方法是：<br />            a)  用class ClassB；声明即将超前引用的类名<br />            b)  定义class ClassA<br />            c)  定义class ClassB;<br />            d)  编制两个类的实现代码。<br />    上述方法适用于所有代码在同一个文件中，一般情况下，ClassA和ClassB分别有自己的头文件和cpp文件，这种<br />方法需要演变成：<br />            a) 分别定义ClassA和ClassB，并在cpp文件中实现之<br />            b) 在两个头文件的开头分别用class ClassB;和class ClassA;声明对方<br />            c) 在两个cpp文件中分别包含另外一个类的头文件<br />     NOTE:这种方法切记不可使用类名来定义变量和函数的变量参数，只可用来定义引用或者指针。<br />     <br />     2) 使用全局变量<br />     由于全局变量可以避免超前引用，不用赘述。我的习惯是，把类对象的extern语句加在该类头文件的最后，大家喜欢<br />怎样写那都没有什么大问题，关键是保证不要在头文件中胡乱包含。<br />    3) 使用基类指针。<br />    这种方法是在引用超前引用类的地方一律用基类指针。而一般情况下，两个互相引用的类并不涉及其基类，因此不会造成<br />超前引用。以开始的例子说：在CMyDialog类中用CView*代替CMyView*，在CMyView类中用CDialog*代替CMyDialog*，这样必然<br />不会造成超前引用。<br /><br /><br />    说明：本文中，为了叙述方便，把class AClass;语句成为类AClass的声明，把class AClass开始的对AClass的类成员变量、<br />成员函数原型等的说明称为类的定义，而把在CPP中的部分称为类的定义。如果大家对这三个词有不同的理解，请按照自己的本意<br />把这三个词换成相应的词来理解。 <br /><br />from: <a href="http://www.vckbase.com/bbs/prime/viewprime.asp?id=431">http://www.vckbase.com/bbs/prime/viewprime.asp?id=431</a><img src ="http://www.blogjava.net/weidagang2046/aggbug/80581.html" width = "1" height = "1" /><br><br><div align=right><a style="text-decoration:none;" href="http://www.blogjava.net/weidagang2046/" target="_blank">weidagang2046</a> 2006-11-11 15:16 <a href="http://www.blogjava.net/weidagang2046/articles/80581.html#Feedback" target="_blank" style="text-decoration:none;">发表评论</a></div>]]></description></item><item><title>VC中基于 Windows 的精确定时</title><link>http://www.blogjava.net/weidagang2046/articles/80568.html</link><dc:creator>weidagang2046</dc:creator><author>weidagang2046</author><pubDate>Sat, 11 Nov 2006 04:57:00 GMT</pubDate><guid>http://www.blogjava.net/weidagang2046/articles/80568.html</guid><wfw:comment>http://www.blogjava.net/weidagang2046/comments/80568.html</wfw:comment><comments>http://www.blogjava.net/weidagang2046/articles/80568.html#Feedback</comments><slash:comments>0</slash:comments><wfw:commentRss>http://www.blogjava.net/weidagang2046/comments/commentRss/80568.html</wfw:commentRss><trackback:ping>http://www.blogjava.net/weidagang2046/services/trackbacks/80568.html</trackback:ping><description><![CDATA[中国科学院光电技术研究所 <a href="mailto:yzyseal@126.com">游志宇</a><p><a href="http://www.vckbase.com/code/downcode.asp?id=2537">示例工程下载</a><br /><br />　　在工业生产控制系统中，有许多需要定时完成的操作，如定时显示当前时间，定时刷新屏幕上的进度条，上位<wbr> 机定时向下位机发送命令和传送数据等。特别是在对控制性能要求较高的实时控制系统和数据采集系统中，就更需要精确定时操作。<br />　　众所周知，Windows 是基于消息机制的系统，任何事件的执行都是通过发送和接收消息来完成的。<wbr> 这样就带来了一些问题，如一旦计算机的CPU被某个进程占用，或系统资源紧张时，发送到消息队列<wbr> 中的消息就暂时被挂起，得不到实时处理。因此，不能简单地通过Windows消息引发一个对定时要求<wbr> 严格的事件。另外，由于在Windows中已经封装了计算机底层硬件的访问，所以，要想通过直接利用<wbr> 访问硬件来完成精确定时，也比较困难。所以在实际应用时，应针对具体定时精度的要求，采取相适 应的定时方法。<br />　　VC中提供了很多关于时间操作的函数，利用它们控制程序能够精确地完成定时和计时操作。本文详细介绍了<wbr> VC中基于Windows的精确定时的七种方式，如下图所示：<br /><br /><img src="http://www.vckbase.com/document/journal/vckbase37/images/MultiTimerDemoimg.gif" border="0" /><br />图一 图像描述 <br /><br />　　方式一：VC中的WM_TIMER消息映射能进行简单的时间控制。首先调用函数SetTimer()设置定时<wbr> 间隔，如SetTimer(0,200,NULL)即为设置200ms的时间间隔。然后在应用程序中增加定时响应函数<wbr> OnTimer()，并在该函数中添加响应的处理语句，用来完成到达定时时间的操作。这种定时方法非常<wbr> 简单，可以实现一定的定时功能，但其定时功能如同Sleep()函数的延时功能一样，精度非常低，最小<wbr> 计时精度仅为30ms，CPU占用低，且定时器消息在多任务操作系统中的优先级很低，不能得到及时响<wbr> 应，往往不能满足实时控制环境下的应用。只可以用来实现诸如位图的动态显示等对定时精度要求不高的情况。如示例工程中的Timer1。 <br />　　方式二：VC中使用sleep()函数实现延时，它的单位是ms，如延时2秒，用sleep(2000)。精度非常<wbr> 低，最小计时精度仅为30ms，用sleep函数的不利处在于延时期间不能处理其他的消息，如果时间太<wbr> 长，就好象死机一样，CPU占用率非常高，只能用于要求不高的延时程序中。如示例工程中的Timer2。<br />　　方式三：利用COleDateTime类和COleDateTimeSpan类结合WINDOWS的消息处理过程来实现秒级延时。如示例工程中的Timer3和Timer3_1。以下是实现2秒的延时代码： <br /></wbr></wbr></wbr></wbr></wbr></wbr></wbr></wbr></wbr></wbr></wbr></wbr></wbr></p><pre>      COleDateTime      start_time = COleDateTime::GetCurrentTime();
      COleDateTimeSpan  end_time= COleDateTime::GetCurrentTime()-start_time;
      while(end_time.GetTotalSeconds()&lt; 2) //实现延时2秒
     { 
              MSG   msg;
              GetMessage(&amp;msg,NULL,0,0);
              TranslateMessage(&amp;msg); 
              DispatchMessage(&amp;msg);
              
             //以上四行是实现在延时或定时期间能处理其他的消息，
　　　　　　 //虽然这样可以降低CPU的占有率，
             //但降低了延时或定时精度，实际应用中可以去掉。
             end_time = COleDateTime::GetCurrentTime()-start_time;
      }//这样在延时的时候我们也能够处理其他的消息。      </pre>　　方式四：在精度要求较高的情况下，VC中可以利用GetTickCount()函数，该函数的返回值是<wbr>  DWORD型，表示以ms为单位的计算机启动后经历的时间间隔。精度比WM_TIMER消息映射高，在较<wbr> 短的定时中其计时误差为15ms，在较长的定时中其计时误差较低，如果定时时间太长，就好象死机一样，CPU占用率非常高，只能用于要求不高的延时程序中。如示例工程中的Timer4和Timer4_1。下列代码可以实现50ms的精确定时：<br /><pre>       DWORD dwStart = GetTickCount();
       DWORD dwEnd   = dwStart;
       do
       {
          dwEnd = GetTickCount()-dwStart;
       }while(dwEnd &lt;50);</pre>为使GetTickCount()函数在延时或定时期间能处理其他的消息，可以把代码改为：<br /><pre>       DWORD dwStart = GetTickCount();
       DWORD dwEnd   = dwStart;
       do
       {
              MSG   msg;
              GetMessage(&amp;msg,NULL,0,0);
              TranslateMessage(&amp;msg); 
              DispatchMessage(&amp;msg);
              dwEnd = GetTickCount()-dwStart;
       }while(dwEnd &lt;50);</pre>虽然这样可以降低CPU的占有率，并在延时或定时期间也能处理其他的消息，但降低了延时或定时精度。<br />　　方式五：与GetTickCount()函数类似的多媒体定时器函数DWORD timeGetTime(void)，该函数定时精<wbr> 度为ms级，返回从Windows启动开始经过的毫秒数。微软公司在其多媒体Windows中提供了精确定时器的底<wbr> 层API持，利用多媒体定时器可以很精确地读出系统的当前时间，并且能在非常精确的时间间隔内完成一<wbr> 个事件、函数或过程的调用。不同之处在于调用DWORD timeGetTime(void) 函数之前必须将 Winmm.lib <wbr> 和 Mmsystem.h 添加到工程中，否则在编译时提示DWORD timeGetTime(void)函数未定义。由于使用该<wbr> 函数是通过查询的方式进行定时控制的，所以，应该建立定时循环来进行定时事件的控制。如示例工程中的Timer5和Timer5_1。<br />　　方式六：使用多媒体定时器timeSetEvent()函数，该函数定时精度为ms级。利用该函数可以实现周期性的函数调用。如示例工程中的Timer6和Timer6_1。函数的原型如下： <br /><pre>       MMRESULT timeSetEvent（ UINT uDelay, 
                               UINT uResolution, 
                               LPTIMECALLBACK lpTimeProc, 
                               WORD dwUser, 
                               UINT fuEvent ）</pre>　　该函数设置一个定时回调事件，此事件可以是一个一次性事件或周期性事件。事件一旦被激活，便调用指定的回调函数，<wbr> 成功后返回事件的标识符代码，否则返回NULL。函数的参数说明如下：<br /><pre>       uDelay：以毫秒指定事件的周期。
       Uresolution：以毫秒指定延时的精度，数值越小定时器事件分辨率越高。缺省值为1ms。
       LpTimeProc：指向一个回调函数。
       DwUser：存放用户提供的回调数据。
       FuEvent：指定定时器事件类型：
       TIME_ONESHOT：uDelay毫秒后只产生一次事件
       TIME_PERIODIC ：每隔uDelay毫秒周期性地产生事件。      </pre>　　具体应用时，可以通过调用timeSetEvent()函数，将需要周期性执行的任务定义在LpTimeProc回调函数<wbr> 中(如：定时采样、控制等)，从而完成所需处理的事件。需要注意的是，任务处理的时间不能大于周期间隔时间。另外，在定时器使用完毕后，<wbr> 应及时调用timeKillEvent()将之释放。 <br />　　方式七：对于精确度要求更高的定时操作，则应该使用QueryPerformanceFrequency()和<wbr> QueryPerformanceCounter()函数。这两个函数是VC提供的仅供Windows 95及其后续版本使用的精确时间函数，并要求计算机从硬件上支持精确定时器。如示例工程中的Timer7、Timer7_1、Timer7_2、Timer7_3。<br />QueryPerformanceFrequency()函数和QueryPerformanceCounter()函数的原型如下：<br /><pre>       BOOL  QueryPerformanceFrequency(LARGE_INTEGER ＊lpFrequency);
       BOOL  QueryPerformanceCounter(LARGE_INTEGER ＊lpCount);</pre>　　数据类型ARGE_INTEGER既可以是一个8字节长的整型数，也可以是两个4字节长的整型数的联合结构，<wbr> 其具体用法根据编译器是否支持64位而定。该类型的定义如下：<br /><pre>       typedef union _LARGE_INTEGER
       {
           struct
           {
              DWORD LowPart ;// 4字节整型数
              LONG  HighPart;// 4字节整型数
           };
           LONGLONG QuadPart ;// 8字节整型数
           
        }LARGE_INTEGER ;</pre>　　在进行定时之前，先调用QueryPerformanceFrequency()函数获得机器内部定时器的时钟频率，<wbr> 然后在需要严格定时的事件发生之前和发生之后分别调用QueryPerformanceCounter()函数，利用两次获得的计数之差及时钟频率，计算出事件经<wbr> 历的精确时间。下列代码实现1ms的精确定时：<br /><pre>       LARGE_INTEGER litmp; 
       LONGLONG QPart1,QPart2;
       double dfMinus, dfFreq, dfTim; 
       QueryPerformanceFrequency(&amp;litmp);
       dfFreq = (double)litmp.QuadPart;// 获得计数器的时钟频率
       QueryPerformanceCounter(&amp;litmp);
       QPart1 = litmp.QuadPart;// 获得初始值
       do
       {
          QueryPerformanceCounter(&amp;litmp);
          QPart2 = litmp.QuadPart;//获得中止值
          dfMinus = (double)(QPart2-QPart1);
          dfTim = dfMinus / dfFreq;// 获得对应的时间值，单位为秒
       }while(dfTim&lt;0.001);</pre>　　其定时误差不超过1微秒，精度与CPU等机器配置有关。 下面的程序用来测试函数Sleep(100)的精确持续时间：<br /><pre>       LARGE_INTEGER litmp; 
       LONGLONG QPart1,QPart2;
       double dfMinus, dfFreq, dfTim; 
       QueryPerformanceFrequency(&amp;litmp);
       dfFreq = (double)litmp.QuadPart;// 获得计数器的时钟频率
       QueryPerformanceCounter(&amp;litmp);
       QPart1 = litmp.QuadPart;// 获得初始值
       Sleep(100);
       QueryPerformanceCounter(&amp;litmp);
       QPart2 = litmp.QuadPart;//获得中止值
       dfMinus = (double)(QPart2-QPart1);
       dfTim = dfMinus / dfFreq;// 获得对应的时间值，单位为秒     </pre>　　由于Sleep()函数自身的误差，上述程序每次执行的结果都会有微小误差。下列代码实现1微秒的精确定时：<br /><pre>       LARGE_INTEGER litmp; 
       LONGLONG QPart1,QPart2;
       double dfMinus, dfFreq, dfTim; 
       QueryPerformanceFrequency(&amp;litmp);
       dfFreq = (double)litmp.QuadPart;// 获得计数器的时钟频率
       QueryPerformanceCounter(&amp;litmp);
       QPart1 = litmp.QuadPart;// 获得初始值
       do
       {
          QueryPerformanceCounter(&amp;litmp);
          QPart2 = litmp.QuadPart;//获得中止值
          dfMinus = (double)(QPart2-QPart1);
          dfTim = dfMinus / dfFreq;// 获得对应的时间值，单位为秒
       }while(dfTim&lt;0.000001);</pre>其定时误差一般不超过0.5微秒，精度与CPU等机器配置有关。(完)<br /><br />from: <a href="http://www.vckbase.com/document/viewdoc/?id=1301">http://www.vckbase.com/document/viewdoc/?id=1301</a></wbr></wbr></wbr></wbr></wbr></wbr></wbr></wbr></wbr></wbr></wbr></wbr></wbr></wbr><img src ="http://www.blogjava.net/weidagang2046/aggbug/80568.html" width = "1" height = "1" /><br><br><div align=right><a style="text-decoration:none;" href="http://www.blogjava.net/weidagang2046/" target="_blank">weidagang2046</a> 2006-11-11 12:57 <a href="http://www.blogjava.net/weidagang2046/articles/80568.html#Feedback" target="_blank" style="text-decoration:none;">发表评论</a></div>]]></description></item><item><title>动态菜单项、状态条提示、工具条提示问题</title><link>http://www.blogjava.net/weidagang2046/articles/80548.html</link><dc:creator>weidagang2046</dc:creator><author>weidagang2046</author><pubDate>Sat, 11 Nov 2006 02:07:00 GMT</pubDate><guid>http://www.blogjava.net/weidagang2046/articles/80548.html</guid><wfw:comment>http://www.blogjava.net/weidagang2046/comments/80548.html</wfw:comment><comments>http://www.blogjava.net/weidagang2046/articles/80548.html#Feedback</comments><slash:comments>0</slash:comments><wfw:commentRss>http://www.blogjava.net/weidagang2046/comments/commentRss/80548.html</wfw:commentRss><trackback:ping>http://www.blogjava.net/weidagang2046/services/trackbacks/80548.html</trackback:ping><description><![CDATA[
		<table class="big" height="57" width="100%" border="0">
				<tbody>
						<tr>
								<td class="info" align="middle" width="100%" colspan="2" height="10">
										<a href="mailto:zxn@hq.cninfo.net">赵湘宁</a>
								</td>
						</tr>
						<tr>
								<td class="font" width="100%" colspan="2" height="22">
										<a href="http://www.vckbase.com/vckbase/vckbase8/src/DynPrompt.zip">本文例子代码</a>
								</td>
						</tr>
						<tr>
								<td class="font" width="100%" colspan="2" height="5">
										<b>问题的提出：</b>
										<br />一个应用程序想要动态改变菜单项。使用CCmdUI::SetText("Menu Text")可以改变菜单文本，但是如何动态改变工具条和状态条的文本呢？<br /><br />有几种策略，避免，欺骗，面对...... <br />    首先，避免：为什么你非要动态改变菜单项？一般说来，这是个坏主意，动态菜单容易把人搞糊涂。我正在使用你的产品，本来用得好好的突然菜单项变了。不管什么时候，每当我看到一个改变菜单的应用时，都要琢麽为什么他们需要这样的用户界面设计。<br /><br />    然而，每一个规则都有例外，许多例子的动态改变菜单项都很酷。例如，在大多数面向文档的应用程序中“文件”菜单的最后一项MRU（最近使用的文件列表）。但作为一个用户，面对动态菜单项的弊端是显而易见的。我把避免动态菜单提升为设计准则。即便是采用了动态菜单的设计，也要让用户注意不到菜单项是改变，否则，It's bad design。反之，如果用户注意不到菜单项的改变，It's OK。<br /><br />    但是动态改变状态条提示又如何呢？在MRU菜单中，无论什么文件，状态条一般都提示“打开选择的文档”。这是另一个要避免的策略。只有特别本位或任性的程序员会操心实现一个动态提示的菜单，如：“打开某某文件”，而不去用完全可行并且有效的提示“打开这个文档”。你完全有权利不遵循这种惯例，也就是说，如果你非要改变状态条提示的话，那就请往下继续看吧，你会明白的。<br /><br />使用动态菜单的另一场合是当你想设置某个布尔状态时。例如，隐藏或显示工具条，当工具条可见时显示“隐藏工具条”，反之显示“显示工具条”。更为普通的方法是用单个命令以校验标记来实现，当工具条可见时显示标记（如下图）。 </td>
						</tr>
						<tr>
								<td class="font" width="100%" colspan="2" height="12">
										<p align="center">
												<img height="152" src="http://www.vckbase.com/vckbase/vckbase8/images/CFIG060001.jpg" width="349" border="0" />
										</p>
								</td>
						</tr>
						<tr>
								<td class="font" width="100%" colspan="2" height="6">    GUI的高手们常常争论哪种方法更好。可能它没有什么差别，但是即使你决定使用动态提示（如隐藏/显示工具条），你也能使用单个的命令ID，ID_VIEW_TOOLBAR，和单个的提示“隐藏或显示工具条”。我认为没有必要去实现动态提示。<br /><br />    在所有建议中，你要做的第一件事情是好好重新考虑用户的界面。你确实需要动态菜单项吗？以及你确实需要菜单的动态提示吗？除非两个问题的答案都是“是”。否则就止住，别再浪费时间。<br /><br />    要改变菜单文本是容易的。只要实现ON_UPDATE_COMMAND_UI处理器并调用CCmdUI::SetText即可：<br /><br /><code>void CFrameWnd::OnUpdateToolbar(CCmdUI* pCmdUI) <br />{<br />     BOOL bVisible = IsToolbarVisible(...);<br />     // Note same mnemonic (&amp;T) for both cmds!<br />     pCmdUI-&gt;SetText(bVisible ? "Hide &amp;Toolbar" : "Show &amp;Toolbar"); <br />} <br /><br /></code>仅此而已。下一步是提示。当你创建了一个菜单提示，你给它一个ID号。MFC使用这个ID来查找资源串获取命令提示。例如：<br /><br />STRINGTABLE DISCARDABLE <br />BEGIN<br />     ID_VIEW_TOOLBAR "Show or hide the toolbar\nToggle ToolBar" <br />END <br /><br />    如果你的菜单命令也有工具条按钮，MFC用“\n”（新行标记）后的文本作为工具条提示文本。因为MFC允许每个命令只能有一个串，如何动态改变提示呢？最简单的方法是编写一个提示在两种情况下都工作，象前面讨论的隐藏、显示工具条的例子。但这种方法显得很笨拙。<br /><br />    获得动态提示的一个方法是将命令分成几个命令-例如，ID_HIDE_ TOOLBAR 和ID_SHOW_TOOLBAR，只是一种欺骗策略。这些命令的命令处理器最终要做的事情是改变菜单项的ID为其它命令项的ID。具体实现细节我就不讲了，自己做吧。<br /><br />    使用两个ID可能是一种简单的方法，但它不适用于所有情况。例如在MRU文件菜单中，对于每个可能的文件名字你会需要不同的ID。<br /><br />    本文提供一个例子程序，DynPrompt，如下图， </td>
						</tr>
						<tr>
								<td class="font" width="100%" colspan="2" height="13">
										<p align="center">
												<img height="259" src="http://www.vckbase.com/vckbase/vckbase8/images/CFIG060002.jpg" width="451" border="0" />
										</p>
								</td>
						</tr>
						<tr>
								<td class="font" width="100%" colspan="2" height="5">状态条采用了动态提示，为了理解DynPrompt是如何工作的，你必须对MFC的菜单提示有一些研究。当用户的鼠标 移动到一个菜单项时，Windows发送WM_MENUSELECT和菜单项的ID。MFC的CFrameWnd处理如下：<br /><br />// much simplified <br />void CFrameWnd::OnMenuSelect(UINT nItemID,<br />UINT nFlags, HMENU hSysMenu)<br />{<br />     SendMessage(WM_SETMESSAGESTRING, nItemID); <br />} <br /><br />    我做了一些简化；函数的实际代码超过了60行，但基本的意思是框架发送WM_SETMESSAGESTRING消息到自身，用WPARAM传递命令ID。SETMESSAGESTRING 是MFC的一个私有消息，它在afxpriv.h中定义。这个消息在状态条窗格中设置 要显示的文本。你可以用WPARAM传递资源串的ID，或者用LPARAM传递实际的串。<br /><br />// resource string ID <br />SendMessage(WM_SETMESSAGESTRING, ID_MYSTRING);<br /><br />// string <br />SendMessage(WM_SETMESSAGESTRING, 0, (LPARAM)_T("Hello, world")); <br /><br />    所以，如果要实现动态菜单提示，必须重载CFrameWnd::OnMenuSelect和 用提示串发送WM_SETMESSAGESTRIN消息。<br /><br />void CMainFrame::OnMenuSelect(UINT nItemID, UINT nFlags,<br />HMENU hSysMenu) <br />{ <br />     if (/* nItemID has a dynamic prompt */) {<br />        CString sPrompt = // whatever you want <br />        SendMessage(WM_SETMESSAGESTRING, 0,  (LPARAM)(LPCTSTR)sPrompt); <br />        m_nIDTracking = nItemID; <br />     } else { <br />        CFrameWnd::OnMenuSelect(nItemID,  nFlags, hSysMenu); <br />     } <br />} <br /><br />    MainFrm.cpp文件中的OnMenuSelect<a href="http://www.vckbase.com/vckbase/vckbase8/code/cfigs0600_1.htm">实际代码</a>调用一连串函数从MRU菜单项来截获 文件名并建立所要的文本提示。别忘了还要调用CFrameWnd::OnMenuSelect来处理 未改变的提示的命令。<br /><br />    最后，对于如何动态改变工具提示文本的方法，CFrameWnd::OnToolTipText是MFC处理工具条通知的函数。其标准实现用匹配的命令ID加载资源串，截获“\n”后的文本并将它拷贝调用者的TOOLTIPTEXT结构。你的任务是编写自己的 代码重载这个处理器。我把这个作为家庭作业。</td>
						</tr>
				</tbody>
		</table>
		<br />from: <a href="http://www.vckbase.com/vckbase/vckbase8/vc/ctrls/menu_07/0807002.htm">http://www.vckbase.com/vckbase/vckbase8/vc/ctrls/menu_07/0807002.htm</a><img src ="http://www.blogjava.net/weidagang2046/aggbug/80548.html" width = "1" height = "1" /><br><br><div align=right><a style="text-decoration:none;" href="http://www.blogjava.net/weidagang2046/" target="_blank">weidagang2046</a> 2006-11-11 10:07 <a href="http://www.blogjava.net/weidagang2046/articles/80548.html#Feedback" target="_blank" style="text-decoration:none;">发表评论</a></div>]]></description></item><item><title>CAsyncSocket对象不能跨线程之分析 </title><link>http://www.blogjava.net/weidagang2046/articles/79683.html</link><dc:creator>weidagang2046</dc:creator><author>weidagang2046</author><pubDate>Tue, 07 Nov 2006 12:07:00 GMT</pubDate><guid>http://www.blogjava.net/weidagang2046/articles/79683.html</guid><wfw:comment>http://www.blogjava.net/weidagang2046/comments/79683.html</wfw:comment><comments>http://www.blogjava.net/weidagang2046/articles/79683.html#Feedback</comments><slash:comments>0</slash:comments><wfw:commentRss>http://www.blogjava.net/weidagang2046/comments/commentRss/79683.html</wfw:commentRss><trackback:ping>http://www.blogjava.net/weidagang2046/services/trackbacks/79683.html</trackback:ping><description><![CDATA[
		<h2>现象</h2>用多线程方法设计socket程序时，你会发现在跨线程使用CAsyncSocket及其派生类时，会出现程序崩溃。所谓跨线程，是指该对象在一个线程中调用Create/AttachHandle/Attach函数，然后在另外一个线程中调用其他成员函数。下面的例子就是一个典型的导致崩溃的过程： <pre>CAsyncSocket Socket;
UINT Thread(LPVOID)
{
       Socket.Close ();
       return 0;
}
void CTestSDlg::OnOK() 
{
       // TODO: Add extra validation here
       Socket.Create(0);
       AfxBeginThread(Thread,0,0,0,0,0);
}
</pre><p>其中Socket对象在主线程中被调用，在子线程中被关闭。 
</p><h2>跟踪分析</h2><p>这个问题的原因可以通过单步跟踪(F11)的方法来了解。我们在Socket.Create(0)处设断点，跟踪进去会发现下面的函数被调用： </p><pre>void PASCAL CAsyncSocket::AttachHandle(
          SOCKET hSocket, CAsyncSocket* pSocket, BOOL bDead)
{
    _AFX_SOCK_THREAD_STATE* pState = _afxSockThreadState;
    BOOL bEnable = AfxEnableMemoryTracking(FALSE);
    if (!bDead)
    {
             ASSERT(CAsyncSocket::LookupHandle(hSocket, bDead) == NULL);
             if (pState-&gt;m_pmapSocketHandle-&gt;IsEmpty())
             {
                  ASSERT(pState-&gt;m_pmapDeadSockets-&gt;IsEmpty());
                  ASSERT(pState-&gt;m_hSocketWindow == NULL);
                  CSocketWnd* pWnd = new CSocketWnd;
                  pWnd-&gt;m_hWnd = NULL;
                  if (!pWnd-&gt;CreateEx(0, AfxRegisterWndClass(0),
                                   _T("Socket Notification Sink"),
                                 WS_OVERLAPPED, 0, 0, 0, 0, NULL, NULL))
                 {
                       TRACE0("Warning: unable to create socket notify window!\n");
                       AfxThrowResourceException();
                 }
                 ASSERT(pWnd-&gt;m_hWnd != NULL);
                 ASSERT(CWnd::FromHandlePermanent(pWnd-&gt;m_hWnd) == pWnd);
                 pState-&gt;m_hSocketWindow = pWnd-&gt;m_hWnd;
            }
            pState-&gt;m_pmapSocketHandle-&gt;SetAt((void*)hSocket, pSocket);
    }
    else
    {
           int nCount;
           if (pState-&gt;m_pmapDeadSockets-&gt;Lookup((void*)hSocket, (void*&amp;)nCount))
                     nCount++;
           else
                     nCount = 1;
           pState-&gt;m_pmapDeadSockets-&gt;SetAt((void*)hSocket, (void*)nCount);
   }
   AfxEnableMemoryTracking(bEnable);
}
</pre><p>在这个函数的开头，首先获得了一个pState的指针指向_afxSockThreadState对象。从名字可以看出，这似乎是一个和线程相关的变量，实际上它是一个宏，定义如下：</p><pre>#define _afxSockThreadState AfxGetModuleThreadState()
</pre><p>我们没有必要去细究这个指针的定义是如何的，只要知道它是和当前线程密切关联的，其他线程应该也有类似的指针，只是指向不同的结构。</p><p>在这个函数中，CAsyncSocket创建了一个窗口，并把如下两个信息加入到pState所管理的结构中： </p><pre>    pState-&gt;m_pmapSocketHandle-&gt;SetAt((void*)hSocket, pSocket);
    pState-&gt;m_pmapDeadSockets-&gt;SetAt((void*)hSocket, (void*)nCount);
    pState-&gt;m_hSocketWindow = pWnd-&gt;m_hWnd;
    pState-&gt;m_pmapSocketHandle-&gt;SetAt((void*)hSocket, pSocket);
</pre><p>当调用Close时，我们再次跟踪，就会发现在KillSocket中，下面的函数出现错误: </p><pre>    void PASCAL CAsyncSocket::KillSocket(SOCKET hSocket, CAsyncSocket* pSocket)
    {
            ASSERT(CAsyncSocket::LookupHandle(hSocket, FALSE) != NULL);
</pre><p>我们在这个ASSERT处设置断点，跟踪进LookupHandle，会发现这个函数定义如下： </p><pre>CAsyncSocket* PASCAL CAsyncSocket::LookupHandle(SOCKET hSocket, BOOL bDead)
{
     CAsyncSocket* pSocket;
     _AFX_SOCK_THREAD_STATE* pState = _afxSockThreadState;
     if (!bDead)
     {
             pSocket = (CAsyncSocket*)
             pState-&gt;m_pmapSocketHandle-&gt;GetValueAt((void*)hSocket);
             if (pSocket != NULL)
                  return pSocket;
    }
    else
    {
             pSocket = (CAsyncSocket*)
                  pState-&gt;m_pmapDeadSockets-&gt;GetValueAt((void*)hSocket);
             if (pSocket != NULL)
                   return pSocket;
    }
    return NULL;
}
</pre><p>显然，这个函数试图从当前线程查询关于这个 socket的信息，可是这个信息放在创建这个socket的线程中，因此这种查询显然会失败，最终返回NULL。 </p><p>有人会问，既然它是ASSERT出错，是不是Release就没问题了。这只是自欺欺人。ASSERT/VERIFY都是检验一些程序正常运行必须正确的条件。如果ASSERT都失败，在Release中也许不会显现，但是你的程序肯定运行不正确，啥时候出错就不知道了。</p><h2>如何在多线程之间传递socket</h2><p>有些特殊情况下，可能需要在不同线程之间传递socket。当然我不建议在使用CAsyncSOcket的时候这么做，因为这增加了出错的风险（尤其当出现拆解包问题时，有人称为粘包，我基本不认同这种称呼）。如果一定要这么做，方法应该是： 
</p><ol type="1"><li>当前拥有这个socket的线程调用Detach方法，这样socket句柄和C++对象及当前线程脱离关系 
</li><li>当前线程把这个对象传递给另外一个线程 
</li><li>另外一个线程创建新的CAsyncSocket对象，并调用Attach </li></ol><p>上面的例子，我稍微做修改，就不会出错了： </p><pre>CAsyncSocket Socket;
UINT Thread(LPVOID sock)
{
         Socket.Attach((SOCKET)sock);
         Socket.Close ();
         return 0;
}
void CTestSDlg::OnOK() 
{
         // TODO: Add extra validation here
         Socket.Create(0);
         SOCKET hSocket = Socket.Detach ();
         AfxBeginThread(Thread,(LPVOID)hSocket,0,0,0,0);
}
<br /><br /><br />from: <a href="http://blog.vckbase.com/arong/archive/2005/12/03/15578.html">http://blog.vckbase.com/arong/archive/2005/12/03/15578.html</a></pre><img src ="http://www.blogjava.net/weidagang2046/aggbug/79683.html" width = "1" height = "1" /><br><br><div align=right><a style="text-decoration:none;" href="http://www.blogjava.net/weidagang2046/" target="_blank">weidagang2046</a> 2006-11-07 20:07 <a href="http://www.blogjava.net/weidagang2046/articles/79683.html#Feedback" target="_blank" style="text-decoration:none;">发表评论</a></div>]]></description></item><item><title>Socket API，CAsyncSocket，CSocket内幕及其用法</title><link>http://www.blogjava.net/weidagang2046/articles/79316.html</link><dc:creator>weidagang2046</dc:creator><author>weidagang2046</author><pubDate>Mon, 06 Nov 2006 01:21:00 GMT</pubDate><guid>http://www.blogjava.net/weidagang2046/articles/79316.html</guid><wfw:comment>http://www.blogjava.net/weidagang2046/comments/79316.html</wfw:comment><comments>http://www.blogjava.net/weidagang2046/articles/79316.html#Feedback</comments><slash:comments>0</slash:comments><wfw:commentRss>http://www.blogjava.net/weidagang2046/comments/commentRss/79316.html</wfw:commentRss><trackback:ping>http://www.blogjava.net/weidagang2046/services/trackbacks/79316.html</trackback:ping><description><![CDATA[
		<p>    Socket有同步阻塞方式和异步非阻塞方式两种使用，事实上同步和异步在我们编程的生涯中可能遇到了很多，而Socket也没什么特别。虽然同步好用，不费劲，但不能满足一些应用场合，其效率也很低。<br />    也许初涉编程的人不能理解“同步(或阻塞)”和“异步(或非阻塞)”，其实简单两句话就能讲清楚，同步和异步往往都是针对一个函数来说的，“同步”就是函数直到其要执行的功能全部完成时才返回，而“异步”则是，函数仅仅做一些简单的工作，然后马上返回，而它所要实现的功能留给别的线程或者函数去完成。例如，SendMessage就是“同步”函数，它不但发送消息到消息队列，还需要等待消息被执行完才返回；相反PostMessage就是个异步函数，它只管发送一个消息，而不管这个消息是否被处理，就马上返回。</p>
		<p>一、Socket API<br />    首先应该知道，有Socket1.1提供的原始API函数，和Socket2.0提供的一组扩展函数，两套函数。这两套函数有重复，但是2.0提供的函数功能更强大，函数数量也更多。这两套函数可以灵活混用，分别包含在头文件Winsock.h，Winsock2.h，分别需要引入库wsock32.lib、Ws2_32.lib。</p>
		<p>1、默认用作同步阻塞方式，那就是当你从不调用WSAIoctl()和ioctlsocket()来改变Socket IO模式，也从不调用WSAAsyncSelect()和WSAEventSelect()来选择需要处理的Socket事件。正是由于函数accept()，WSAAccept()，connect()，WSAConnect()，send()，WSASend()，recv()，WSARecv()等函数被用作阻塞方式，所以可能你需要放在专门的线程里，这样以不影响主程序的运行和主窗口的刷新。<br />2、如果作为异步用，那么程序主要就是要处理事件。它有两种处理事件的办法：<br />    第一种，它常关联一个窗口，也就是异步Socket的事件将作为消息发往该窗口，这是由WinSock扩展规范里的一个函数WSAAsyncSelect()来实现和窗口关联。最终你只需要处理窗口消息，来收发数据。<br />  第二种，用到了扩展规范里另一个关于事件的函数WSAEventSelect()，它是用事件对象的方式来处理Socket事件，也就是，你必须首先用WSACreateEvent()来创建一个事件对象，然后调用WSAEventSelect()来使得Socket的事件和这个事件对象关联。最终你将要在一个线程里用WSAWaitForMultipleEvents()来等待这个事件对象被触发。这个过程也稍显复杂。<br />二、CAsyncSocket<br />    看类名就知道，它是一个异步非阻塞Socket封装类，CAsyncSocket::Create()有一个参数指明了你想要处理哪些Socket事件，你关心的事件被指定以后，这个Socket默认就被用作了异步方式。那么CAsyncSocket内部到底是如何将事件交给你的呢？<br />    CAsyncSocket的Create()函数，除了创建了一个SOCKET以外，还创建了个CSocketWnd窗口对象，并使用WSAAsyncSelect()将这个SOCKET与该窗口对象关联，以让该窗口对象处理来自Socket的事件(消息)，然而CSocketWnd收到Socket事件之后，只是简单地回调CAsyncSocket::OnReceive()，CAsyncSocket::OnSend()，CAsyncSocket::OnAccept()，CAsyncSocket::OnConnect()等虚函数。所以CAsyncSocket的派生类，只需要在这些虚函数里添加发送和接收的代码。<br />  <br />  简化后，大致的代码为：<br />  bool CAsyncSocket::Create( long lEvent ) <a href="file://参/">file://参</a>数lEvent是指定你所关心的Socket事件<br />  {<br />   m_hSocket = socket( PF_INET, SOCK_STREAM, 0 ); <a href="file://创/">file://创</a>建Socket本身</p>
		<p>   CSocketWnd* pSockWnd = new CSocketWnd; <a href="file://创/">file://创</a>建响应事件的窗口，实际的这个窗口在AfxSockInit()调用时就被创建了。<br />   pSockWnd-&gt;Create(...);</p>
		<p>   WSAAsyncSelect( m_hSocket, pSockWnd-&gt;m_hWnd, WM_SOCKET_NOTIFY, lEvent ); <a href="file://Socket/">file://Socket</a>事件和窗口关联<br />  }<br />  <br />  static void PASCAL CAsyncSocket::DoCallBack(WPARAM wParam, LPARAM lParam)<br />  {<br />   CAsyncSocket Socket;<br />   Socket.Attach( (SOCKET)wParam ); <a href="file://wParam/">file://wParam</a>就是触发这个事件的Socket的句柄<br />   int nErrorCode = WSAGETSELECTERROR(lParam); <a href="file://lParam/">file://lParam</a>是错误码与事件码的合成<br />   switch (WSAGETSELECTEVENT(lParam))<br />   {<br />   case FD_READ:<br />    pSocket-&gt;OnReceive(nErrorCode);<br />    break;<br />   case FD_WRITE:<br />    pSocket-&gt;OnSend(nErrorCode);<br />    break;<br />   case FD_OOB:<br />    pSocket-&gt;OnOutOfBandData(nErrorCode);<br />    break;<br />   case FD_ACCEPT:<br />    pSocket-&gt;OnAccept(nErrorCode);<br />    break;<br />   case FD_CONNECT:<br />    pSocket-&gt;OnConnect(nErrorCode);<br />    break;<br />   case FD_CLOSE:<br />    pSocket-&gt;OnClose(nErrorCode);<br />    break;<br />   }<br />  }</p>
		<p>  CSocketWnd类大致为：</p>
		<p>  BEGIN_MESSAGE_MAP(CSocketWnd, CWnd)<br />   ON_MESSAGE(WM_SOCKET_NOTIFY, OnSocketNotify)<br />  END_MESSAGE_MAP()</p>
		<p>  LRESULT CSocketWnd::OnSocketNotify(WPARAM wParam, LPARAM lParam)<br />  {<br />   CAsyncSocket::DoCallBack( wParam, lParam ); <a href="file://收/">file://收</a>到Socket事件消息，回调CAsyncSocket的DoCallBack()函数<br />   return 0L;<br />  }</p>
		<p>  然而，最不容易被初学Socket编程的人理解的，也是本文最要提醒的一点是，客户方在使用CAsyncSocket::Connect()时，往往返回一个WSAEWOULDBLOCK的错误(其它的某些函数调用也如此)，实际上这不应该算作一个错误，它是Socket提醒我们，由于你使用了非阻塞Socket方式，所以(连接)操作需要时间，不能瞬间建立。既然如此，我们可以等待呀，等它连接成功为止，于是许多程序员就在调用Connect()之后，Sleep(0)，然后不停地用WSAGetLastError()或者CAsyncSocket::GetLastError()查看Socket返回的错误，直到返回成功为止。这是一种错误的做法，断言，你不能达到预期目的。事实上，我们可以在Connect()调用之后等待CAsyncSocket::OnConnect()事件被触发，CAsyncSocket::OnConnect()是要表明Socket要么连接成功了，要么连接彻底失败了。至此，我们在CAsyncSocket::OnConnect()被调用之后就知道是否Socket连接成功了，还是失败了。<br />  类似的，Send()如果返回WSAEWOULDBLOCK错误，我们在OnSend()处等待，Receive()如果返回WSAEWOULDBLOCK错误，我们在OnReceive()处等待，以此类推。<br />  还有一点，也许是个难点，那就是在客户方调用Connect()连接服务方，那么服务方如何Accept()，以建立连接的问题。简单的做法就是在监听的Socket收到OnAccept()时，用一个新的CAsyncSocket对象去建立连接，例如：</p>
		<p> void CMySocket::OnAccept( int ErrCode )<br /> {<br />       CMySocket* pSocket = new CMySocket;<br />       Accept( *pSocket );<br /> }<br />    于是，上面的pSocket和客户方建立了连接，以后的通信就是这个pSocket对象去和客户方进行，而监听的Socket仍然继续在监听，一旦又有一个客户方要连接服务方，则上面的OnAccept()又会被调用一次。当然pSocket是和客户方通信的服务方，它不会触发OnAccept()事件，因为它不是监听Socket。</p>
		<p>三、CSocket<br />   CSocket是MFC在CAsyncSocket基础上派生的一个同步阻塞Socket的封装类。它是如何又把CAsyncSocket变成同步的，而且还能响应同样的Socket事件呢？<br />  其实很简单，CSocket在Connect()返回WSAEWOULDBLOCK错误时，不是在OnConnect()，OnReceive()这些事件终端函数里去等待。你先必须明白Socket事件是如何到达这些事件函数里的。这些事件处理函数是靠CSocketWnd窗口对象回调的，而窗口对象收到来自Socket的事件，又是靠线程消息队列分发过来的。总之，Socket事件首先是作为一个消息发给CSocketWnd窗口对象，这个消息肯定需要经过线程消息队列的分发，最终CSocketWnd窗口对象收到这些消息就调用相应的回调函数(OnConnect()等)。<br />   所以，CSocket在调用Connect()之后，如果返回一个WSAEWOULDBLOCK错误时，它马上进入一个消息循环，就是从当前线程的消息队列里取关心的消息，如果取到了WM_PAINT消息，则刷新窗口，如果取到的是Socket发来的消息，则根据Socket是否有操作错误码，调用相应的回调函数(OnConnect()等)。<br />  大致的简化代码为：</p>
		<p>  BOOL CSocket::Connect( ... )<br />  {<br />   if( !CAsyncSocket::Connect( ... ) )<br />   {<br />    if( WSAGetLastError() == WSAEWOULDBLOCK ) <a href="file://由/">file://由</a>于异步操作需要时间，不能立即完成，所以Socket返回这个错误<br />    {<br />     <a href="file://进/">file://进</a>入消息循环，以从线程消息队列里查看FD_CONNECT消息，直到收到FD_CONNECT消息，认为连接成功。<br />     while( PumpMessages( FD_CONNECT ) );<br />    }<br />   }<br />  }<br />  BOOL CSocket::PumpMessages( UINT uEvent )<br />  {<br />      CWinThread* pThread = AfxGetThread();<br />      while( bBlocking ) <a href="file://bBlocking/">file://bBlocking</a>仅仅是一个标志，看用户是否取消对Connect()的调用<br />      {<br />          MSG msg;<br />          if( PeekMessage( &amp;msg, WM_SOCKET_NOTIFY ) )<br />          {<br />             if( msg.message == WM_SOCKET_NOTIFY &amp;&amp; WSAGETSELECTEVENT(msg.lParam) == uStopFlag )<br />             {<br />                 CAsyncSocket::DoCallBack( msg.wParam, msg.lParam );<br />                 return TRUE;<br />             }     <br />         }<br />         else<br />        {<br />             OnMessagePending(); <a href="file://处/">file://处</a>理消息队列里的其它消息<br />             pThread-&gt;OnIdle(-1);<br />        }<br />     }<br />  }<br />  BOOL CSocket::OnMessagePending()<br />  {<br />      MSG msg;<br />       if( PeekMessage( &amp;msg, NULL, WM_PAINT, WM_PAINT, PM_REMOVE ) )<br />       { <a href="file://这/">file://这</a>里仅关心WM_PAINT消息，以处理阻塞期间的主窗口重画<br />           ::DispatchMessage( &amp;msg );<br />           return FALSE;<br />       }<br />       return FALSE;<br />  }</p>
		<p>   其它的CSocket函数，诸如Send()，Receive()，Accept()都在收到WSAEWOULDBLOCK错误时，进入PumpMessages()消息循环，这样一个原本异步的CAsyncSocket，到了派生类CSocket，就变成同步的了。<br />  明白之后，我们可以对CSocket应用自如了。比如有些程序员将CSocket的操作放入一个线程，以实现多线程的异步Socket(通常，同步+多线程 相似于 异步 )。</p>
		<p>四、CSocketFile<br />  另外，进行Socket编程，不能不提到CSocketFile类，其实它并不是用来在Socket双方发送文件的，而是将需要序列化的数据，比如一些结构体数据，传给对方，这样，程序的CDocument()的序列化函数就完全可以和CSocketFile联系起来。例如你有一个CMyDocument实现了Serialize()，你可以这样来将你的文档数据传给Socket的另一方：</p>
		<p> CSocketFile file( pSocket );<br /> CArchive ar( &amp;file, CArchive::store );<br /> pDocument-&gt;Serialize( ar );<br /> ar.Close();</p>
		<p>  同样，接收一方可以只改变上面的代码为CArchive ar( &amp;file, CArchive::load );即可。<br />   注意到，CSocketFile类虽然从CFile派生，但它屏蔽掉了CFile::Open()等函数，而函数里仅扔出一个例外。那么也就是说，你不能调用CSocketFile的Open函数来打开一个实实在在的文件，否则会导致例外，如果你需要利用CSocketFile来传送文件，你必须提供CSocketFile类的这些函数的实现。<br />  再一点，CArchive不支持在datagram的Socket连接上序列化数据。<br /><br />from: <a href="http://www.zahui.com/html/1/2196.htm">http://www.zahui.com/html/1/2196.htm</a></p>
<img src ="http://www.blogjava.net/weidagang2046/aggbug/79316.html" width = "1" height = "1" /><br><br><div align=right><a style="text-decoration:none;" href="http://www.blogjava.net/weidagang2046/" target="_blank">weidagang2046</a> 2006-11-06 09:21 <a href="http://www.blogjava.net/weidagang2046/articles/79316.html#Feedback" target="_blank" style="text-decoration:none;">发表评论</a></div>]]></description></item><item><title>配置Csocket 操作的超时时间</title><link>http://www.blogjava.net/weidagang2046/articles/79285.html</link><dc:creator>weidagang2046</dc:creator><author>weidagang2046</author><pubDate>Sun, 05 Nov 2006 16:16:00 GMT</pubDate><guid>http://www.blogjava.net/weidagang2046/articles/79285.html</guid><wfw:comment>http://www.blogjava.net/weidagang2046/comments/79285.html</wfw:comment><comments>http://www.blogjava.net/weidagang2046/articles/79285.html#Feedback</comments><slash:comments>0</slash:comments><wfw:commentRss>http://www.blogjava.net/weidagang2046/comments/commentRss/79285.html</wfw:commentRss><trackback:ping>http://www.blogjava.net/weidagang2046/services/trackbacks/79285.html</trackback:ping><description><![CDATA[察看本文应用于的产品<br />文章编号 : 138692 <br />最后修改 : 2005年8月24日 <br />修订 : 1.1 <br />本文的发布号曾为 CHS138692<br />本页
<p>概要</p><p>更多信息</p><p>BOOL SetTimeOut(UINT uTimeOut) </p><p>BOOL KillTimeOut() </p><p>BOOL OnMessagePending() </p><p>示例代码 </p><p>参考<br />概要<br />CSocket 操作，如“接收”(Receive)、“发送”(Send) 和“连接”(Connect) 均是阻塞操作，即要等到操作成功执行完毕或套接字上出现错误后，对这些函数的调用才有返回结果。 </p><p>在某些情况下，操作可能永远不能成功完成，这将导致程序无限循环等待操作完成。一种解决方法是通过编程限制完成操作使用的时间。本文将讨论这种方法。 <br /> 回到顶端 </p><p>更多信息<br />这种方法是设置定时，让它在操作时间过长时启动。此方法的关键在于处理定时器的方式。虽然操作是“阻塞的”，但仍然可以处理到达的消息。如果通过使用 SetTimer 设置定时器，那么可以查找 WM_TIMER 消息，并在收到该消息时终止操作。该过程中涉及的主要函数有： <br />Windows API 调用函数： <br />::SetTimer <br />MFC 函数： <br />CSocket::OnMessagePending <br />CSocket::CancelBlockingCall <br />为简单起见，可以在 Csocket 衍生类中封装该功能。 </p><p>警告：在进一步阅读本文之前，请注意在某些 MFC 版本中存在错误，会在试图使用定时器并重叠 OnMessagePending 时引起问题。这一问题将在下面的 Microsoft Knowledge Base 文章中进行讨论： <br />137632 (<a href="http://support.microsoft.com/kb/137632/EN-US/">http://support.microsoft.com/kb/137632/EN-US/</a>) 错误：定时器激活时未调用 OnMessagePending <br />本文仅适用于 Visual C++ 的 1.52、1.52b、2.1 和 2.2 版本。如果使用的是这些 Visual C++ 版本之一，则还需要实施所提供的变通解决方法。 </p><p>本文最后部分显示提供这种超时功能的类的示例代码。以下内容讲述由该类实现的函数。 <br /> 回到顶端 </p><p>BOOL SetTimeOut(UINT uTimeOut) </p><p><br />调用此函数之后仅接着调用 CSocket 函数（如 Receive、Send 和 Accept）。uTimeOut 参数是以毫秒为单位指定的。之后，进行定时器的设置。如果设置定时器失败，那么函数返回 FALSE。有关详细情况，请参阅 SetTimer 函数的 Windows API 文档。 <br /> 回到顶端 </p><p>BOOL KillTimeOut() </p><p><br />在完成阻塞操作后，必须调用此函数。此函数删除用 SetTimeOut 设置的定时器。如果调用 KillTimer 失败，则返回 FALSE。有关详细情况，请参阅 KillTimer 函数的 Windows API 文档。 <br /> 回到顶端 </p><p>BOOL OnMessagePending() </p><p><br />这是一个虚拟回调函数，在等待操作完成时由 CSocket 类进行调用。此函数给您提供处理传入消息的机会。此实施过程检查用 SetTimeOut 调用函数设置的定时器的 WM_TIMER 消息。如果收到消息，则调用 CancelBlockingCall 函数。有关 OnMessagePending 和 CancelBlockingCall 函数详细的信息，请参阅 MFC 文档。请注意：调用 CancelBlockingCall 函数 将导致操作失败，而且 GetLastError 函数返回 WSAEINTR（表示操作中断）。 </p><p>下面是使用该类的一个例子： <br />   ...<br />   CTimeOutSocket sockServer;<br />   CAcceptedSocket sockAccept;</p><p>   sockServer.Create(777);<br />   sockServer.Listen();</p><p>   // Note the following sequence:<br />   //  SetTimeOut<br />   //  <br />   //  KillTimeOut</p><p>   if(!sockServer.SetTimeOut(10000))<br />   {<br />     ASSERT(FALSE);<br />     // Error Handling...for some reason, we could not setup<br />     // the timer.<br />   }</p><p>   if(!sockServer.Accept(sockAccept))<br />   {<br />     int nError = GetLastError();<br />     if(nError==WSAEINTR)<br />       AfxMessageBox("No Connections Arrived For 10 Seconds");<br />      else<br />        ; // Do other error processing.<br />   }</p><p>   if(!sockServer.KillTimeOut())<br />   {<br />     ASSERT(FALSE);<br />     // Error Handling...for some reason the timer could not<br />     // be destroyed...perhaps a memory overwrite has changed<br />     // m_nTimerID?<br />     // <br />   }<br />   ...<br /> 回到顶端 </p><p>示例代码 </p><p> </p><p>   // <br />   // HEADER FILE<br />   // <br />   class CTimeOutSocket : public CSocket<br />   {<br />   public:<br />     BOOL SetTimeOut(UINT uTimeOut);<br />     BOOL KillTimeOut();<br />   protected:<br />     virtual BOOL OnMessagePending();<br />   private:<br />     int m_nTimerID;<br />   };<br />   // <br />   // END OF FILE<br />   // </p><p><br />   // <br />   // IMPLEMENTATION FILE<br />   // <br />   BOOL CTimeOutSocket::OnMessagePending()<br />   {<br />     MSG msg;<br />     if(::PeekMessage(&amp;msg, NULL, WM_TIMER, WM_TIMER, PM_NOREMOVE))<br />     {<br />       if (msg.wParam == (UINT) m_nTimerID)<br />       {<br />         // Remove the message and call CancelBlockingCall.<br />         ::PeekMessage(&amp;msg, NULL, WM_TIMER, WM_TIMER, PM_REMOVE);<br />         CancelBlockingCall();<br />         return FALSE;  // No need for idle time processing.<br />       };<br />     };</p><p>     return CSocket::OnMessagePending();<br />   }</p><p>   BOOL CTimeOutSocket::SetTimeOut(UINT uTimeOut)<br />   {<br />     m_nTimerID = SetTimer(NULL,0,uTimeOut,NULL);<br />     return m_nTimerID;<br />   }</p><p>   BOOL CTimeOutSocket::KillTimeOut()<br />   {<br />     return KillTimer(NULL,m_nTimerID);<br />   }<br />CSOCKET的超时设置和UDP发送接收<br />    使用CSoket多次了，但对于它的block模式的理解并不是很深入。昨天使用csoket的udp多发测试(server接到数据后，需要通过某种方式将数据发送到client,使用tcp方式比较可靠，我一直这样用的，但是比较费时，需要逐一发送)，发现了问题：</p><p>  1）create(),sendto(),receivefrom()....</p><p>  2）其中，发送方一直定时发送数据无问题；</p><p>  3）而接收方，通过一个单独的接收线程实现( 注意：csocket不能跨线程使用！主线程中socket create()后，detach()并将sock作为lpvoid传入接收线程 )。代码如下：</p><p>  //处理接收数据<br />UINT CSockSvr::DealSvrRevData(LPVOID lParam)<br />{</p><p>  ......<br /> DWORD dwError;<br /> TCHAR cBuff[1000];<br /> CString sIP;<br /> UINT uPort;<br /> for(;!pDlg-&gt;m_bExit;)<br /> { <br />  ::memset( cBuff,0,sizeof(cBuff) );</p><p>  // 如果没有接到数据，一直等待。。。。<br />  // 阻塞模式的弊端：：：在退出时候，通过CancelBlockingCall<br />   int iRst=SockSvr.ReceiveFrom( cBuff,sizeof(cBuff),sIP,uPort,0 );<br />    if( iRst!=SOCKET_ERROR )<br />  {<br />   CString sTemp=cBuff;<br />   TRACE1( _T("\r\n Rev Data: %s\r\n"),sTemp );<br />  }<br />  else<br />  {<br />   dwError=GetLastError();<br />   TRACE1( _T("\r\n Rev Data Error code: %d\r\n"),dwError );<br />  }<br />  ::Sleep(200);<br /> }<br /> <br /> return 0;<br />}</p><p>  问题：如果发送方没有数据，SockSvr.ReceiveFrom()会一致处于等待状态，导致整个处理接收线程的停止，如果用户需要退出/结束程序，无法正确释放资源（无法关闭在线程函数重打开的socket--不能跨线程操作socket，无法关闭线程)，造成系统memory leak...</p><p>  针对这种问题，微软的解决办法是：<a href="http://support.microsoft.com/default.aspx?scid=kb;zh-cn;138692">http://support.microsoft.com/default.aspx?scid=kb;zh-cn;138692</a> ，经过测试，在socket进行connect()的时候不好用。后来，结合微软的办法，我通过自定义的定时器，实现了block超时间后自动退出的功能〉</p><p>  #if !defined(AFX_TIMEOUTSOCK_H__19897A81_4EAF_4005_91FD_DC3047725139__INCLUDED_)<br />#define AFX_TIMEOUTSOCK_H__19897A81_4EAF_4005_91FD_DC3047725139__INCLUDED_</p><p>#if _MSC_VER &gt; 1000<br />#pragma once<br />#endif // _MSC_VER &gt; 1000<br />// TimeOutSock.h : header file<br />//</p><p>/*<br />CSocket 操作，如接收(Receive)、发送(Send) 和连接(Connect) 均是阻塞操作，<br /> 即要等到操作成功执行完毕或套接字上出现错误后，对这些函数的调用才有返回结果。 <br />某些情况下，操作可能永远不能成功完成，这将导致程序无限循环等待操作完成。<br /> 一种解决方法是通过编程限制完成操作使用的时间。本文将讨论这种方法。 <br />*/<br />/////////////////////////////////////////////////////////////////////////////<br />// CTimeOutSock command target</p><p>class CTimeOutSock : public CSocket<br />{<br />// Attributes<br />public:</p><p>// Operations<br />public:<br /> CTimeOutSock();<br /> virtual ~CTimeOutSock();</p><p>// Overrides<br />public:<br /> // ClassWizard generated virtual function overrides<br /> //{{AFX_VIRTUAL(CTimeOutSock)<br /> public:<br /> virtual BOOL OnMessagePending();<br /> //}}AFX_VIRTUAL</p><p> // Generated message map functions<br /> //{{AFX_MSG(CTimeOutSock)<br />  // NOTE - the ClassWizard will add and remove member functions here.<br /> //}}AFX_MSG</p><p>/*<br />//定时器设置block超时 不是很好用。。。徐卫话 2006.4.19<br />CSocket 操作，如接收(Receive)、发送(Send) 和连接(Connect) 均是阻塞操作，<br /> 即要等到操作成功执行完毕或套接字上出现错误后，对这些函数的调用才有返回结果。 <br />某些情况下，操作可能永远不能成功完成，这将导致程序无限循环等待操作完成。<br /> 一种解决方法是通过编程限制完成操作使用的时间。本文将讨论这种方法。 <br />*/<br />// 自己计算时间的办法 OK<br />public:<br />     BOOL SetTimeOut(UINT uTimeOut=1000);<br />     BOOL KillTimeOut();<br />private:<br />  LONGLONG m_llDtStart;<br />  UINT  m_uTimeOut; <br />};</p><p>/////////////////////////////////////////////////////////////////////////////</p><p>//}<br />// Microsoft Visual C++ will insert additional declarations immediately before the previous line.</p><p>#endif // !defined(AFX_TIMEOUTSOCK_H__19897A81_4EAF_4005_91FD_DC3047725139__INCLUDED_)</p><p><br />  // TimeOutSock.cpp : implementation file<br />//</p><p>#include "stdafx.h"<br />#include "NetBroad.h"<br />#include "TimeOutSock.h"</p><p>#ifdef _DEBUG<br />#define new DEBUG_NEW<br />#undef THIS_FILE<br />static char THIS_FILE[] = __FILE__;<br />#endif</p><p>/////////////////////////////////////////////////////////////////////////////<br />// CTimeOutSock</p><p>CTimeOutSock::CTimeOutSock()<br />{</p><p>}</p><p>CTimeOutSock::~CTimeOutSock()<br /></p><p><br />// Do not edit the following lines, which are needed by ClassWizard.<br />#if 0<br />BEGIN_MESSAGE_MAP(CTimeOutSock, CSocket)<br /> //}AFX_MSG_MAP<br />END_MESSAGE_MAP()<br />#endif // 0</p><p>/////////////////////////////////////////////////////////////////////////////<br />// CTimeOutSock member functions</p><p>//设置超时<br />BOOL CTimeOutSock::SetTimeOut(UINT uTimeOut)<br />{   <br /> //get start cnt<br /> LARGE_INTEGER llCnt;<br /> ::QueryPerformanceCounter(&amp;llCnt);<br /> m_llDtStart=llCnt.QuadPart; <br /> m_uTimeOut=uTimeOut;</p><p>     return TRUE;<br />}</p><p>//删除超时参数<br />BOOL CTimeOutSock::KillTimeOut()<br />{<br /> m_llDtStart=0;//表明取消计时<br /> return TRUE;<br />}</p><p>//检查是否超时间<br />BOOL CTimeOutSock::OnMessagePending() <br />{<br /> // TODO: Add your specialized code here and/or call the base class<br /> /*<br /> MSG msg;<br />  if(::PeekMessage(&amp;msg, NULL, WM_TIMER, WM_TIMER, PM_NOREMOVE))<br />  {<br />   if (msg.wParam == (UINT) m_nTimerID)<br />   {<br />    // Remove the message and call CancelBlockingCall.<br />    ::PeekMessage(&amp;msg, NULL, WM_TIMER, WM_TIMER, PM_REMOVE);<br />    CancelBlockingCall();<br />    return FALSE;  // No need for idle time processing.<br />   };<br />  };<br /> */<br /> if( m_llDtStart )<br /> {<br />  LARGE_INTEGER lldtEnd;<br />  ::QueryPerformanceCounter(&amp;lldtEnd);  <br />  LARGE_INTEGER llFrq;<br />  ::QueryPerformanceFrequency(&amp;llFrq);<br />  double dbDealy=(double)(lldtEnd.QuadPart-m_llDtStart)*1000/llFrq.QuadPart;<br />  if( dbDealy&gt;m_uTimeOut )<br />  {<br />   CancelBlockingCall();<br />   return FALSE;  // No need for idle time processing.<br />  }<br /> }<br /> <br /> return CSocket::OnMessagePending();<br />}</p><p> </p><p>  经过改进后， 对处理接收线成的函数进行了部分改进：</p><p>  SockSvr.SetTimeOut(500);  int iRst=SockSvr.ReceiveFrom( cBuff,sizeofcBuff),sIP,uPort,0 );  SockSvr.KillTimeOut();</p><p> 当block超过定时后，socket自动退出block，防止接收线程停止。问题终于解决了！<br /><br />from: <a href="http://www.arm8.com/cv/1/6/197.html">http://www.arm8.com/cv/1/6/197.html</a></p><img src ="http://www.blogjava.net/weidagang2046/aggbug/79285.html" width = "1" height = "1" /><br><br><div align=right><a style="text-decoration:none;" href="http://www.blogjava.net/weidagang2046/" target="_blank">weidagang2046</a> 2006-11-06 00:16 <a href="http://www.blogjava.net/weidagang2046/articles/79285.html#Feedback" target="_blank" style="text-decoration:none;">发表评论</a></div>]]></description></item><item><title>深入 CSocket 编程之阻塞和非阻塞模式</title><link>http://www.blogjava.net/weidagang2046/articles/73315.html</link><dc:creator>weidagang2046</dc:creator><author>weidagang2046</author><pubDate>Tue, 03 Oct 2006 12:01:00 GMT</pubDate><guid>http://www.blogjava.net/weidagang2046/articles/73315.html</guid><wfw:comment>http://www.blogjava.net/weidagang2046/comments/73315.html</wfw:comment><comments>http://www.blogjava.net/weidagang2046/articles/73315.html#Feedback</comments><slash:comments>0</slash:comments><wfw:commentRss>http://www.blogjava.net/weidagang2046/comments/commentRss/73315.html</wfw:commentRss><trackback:ping>http://www.blogjava.net/weidagang2046/services/trackbacks/73315.html</trackback:ping><description><![CDATA[
		<p>　　有时，花上几个小时阅读、调试、跟踪优秀的源码程序，能够更快地掌握某些技术关键点和精髓。当然，前提是对这些技术大致上有一个了解。 <br />　　我通过几个采用 CSocket 类编写并基于 Client/Server （客户端 / 服务端）的网络聊天和传输文件的程序 ( 详见： 源代码参考 ) ，在调试这些程序的过程中，追踪深入至 CSocket 类核心源码 Sockcore.cpp ， 对于CSocket 类的运行机制可谓是一览无遗，并且对于阻塞和非阻塞方式下的 socket 程序的编写也是稍有体会。 <br /><br />阅读本文请先注意： <br /><br />　　这里的阻塞和非阻塞的概念仅适用于 Server 端 socket 程序。socket 意为套接字，它与 Socket 不同，请注意首字母的大小写。<br />　　客户端与服务端的通信简单来讲：服务端 socket 负责监听，应答，接收和发送消息，而客户端 socket 只是连接，应答，接收，发送消息。此外，如果你对于采用 CSocket 类编写 Client/Server 网络程序的原理不是很了解，请先查询一下（ 详见：参考书籍和在线帮助 ）。 <br />在此之前，有必要先讲述一下： 网络传输服务提供者， ws2_32.dll ， socket 事件 和 socket window 。 <br /><br />1、网络传输服务提供者（网络传输服务进程）， Socket 事件， Socket Window <br /><br />　　网络传输服务提供者 （ transport service provider ）是以 DLL 的形式存在的，在 windows 操作系统启动时由服务进程 svchost.exe 加载。当 socket 被创建时，调用 API 函数 Socket （在 ws2_32.dll 中）， Socket 函数会传递三个参数 : 地址族，套接字类型 ( 注 2 ) 和协议，这三个参数决定了是由哪一个类型的 网络传输服务提供者 来启动网络传输服务功能。所有的网络通信正是由网络传输服务提供者完成 , 这里将 网络传输服务提供者 称为 网络传输服务进程 更有助于理解，因为前文已提到 网络传输服务提供者 是由 svchost.exe 服务进程所加载的。 <br />　　下图描述了网络应用程序、 CSocket （ WSock32.dll ）、 Socket API(ws2_32.dll) 和 网络传输服务进程 之间的接口层次关系： <br /><br /><img height="304" src="http://www.vckbase.com/document/journal/vckbase39/images/Starlightsocket1.jpg" width="209" /><br /><br />当 Client 端 socket 与 Server 端 socket 相互通信时，两端均会触发 socket 事件。这里仅简要说明两个 socket 事件： </p>
		<ul>
				<li>FD_CONNECT: 连接事件 , 通常 Client 端 socket 调用 socket API 函数 Connect 时所触发，这个事件发生在 Client 端。 
</li>
				<li>FD_ACCEPT ：正在引入的连接事件，通常 Server 端 socket 正在接收来自 Client 端 socket 连接时触发，这个事件发生在 Server 端。 </li>
		</ul>
		<p>　　网络传输服务进程 将 socket 事件 保存至 socket 的事件队列中。此外， 网络传输服务进程 还会向 socket window 发送消息 WM_SOCKET_NOTIFY , 通知有 socket 事件 产生，见下文对 socket window 的详细说明。<br />　　调用 CSocket::Create 函数后，socket 被创建。 socket 创建过程中调用 CAsyncSocket::AttachHandle(SOCKET hSocket, CAsyncSocket* pSocket, BOOL bDead) 。该函数的作用是： </p>
		<ul>
				<li>将 socket 实例句柄和 socket 指针添加至 当前模块状态 （ 注 1 ）的一个映射表变量 m_pmapSocketHandle 中。 
</li>
				<li>在 AttachHandle 过程中，会 new 一个 CSocketWnd 实例 ( 基于 CWnd 派生 ) ，这里将这个实例称之为 socket window ，进一步理解为它是存放所有 sockets 的消息池 （ window 消息），请仔细查看，这里 socket 后多加了一个 s ，表示创建的多个 socket 将共享一个 消息池 。 
</li>
				<li>当 Client 端 socket 与 Server 端相互通信时 , 此时 网络传输服务进程 向 socket window 发送消息 WM_SOCKET_NOTIFY ，需要说明的是 CSocketWnd 窗口句柄保存在 当前模块状态 的 m_hSocketWindow 变量中。 </li>
		</ul>
		<p>2、阻塞模式<br /><br />　　阻塞模式下 Server 端与 Client 端之间的通信处于同步状态下。在 Server 端直接实例化 CSocket 类，调用 Create 方法创建 socket ，然后调用方法 Listen 开始侦听，最后用一个 while 循环阻塞调用 Accept 函数用于等待来自 Client 端的连接，如果这个 socket 在主线程（主程序）中运行，这将导致主线程的阻塞。因此，需要创建一个新的线程以运行 socket 服务。 <br />调试跟踪至 CSocket::Accept 函数源码： </p>
		<pre>while(!Accept(...))
{ 
　　　　　// The socket is marked as nonblocking and no connections are present to be accepted. 
	if (GetLastError() == WSAEWOULDBLOCK) <br />	　PumpMessage(FD_ACCEPT); 
	else 
	  return FALSE; 
}
</pre>　　它不断调用 CAsyncSocket::Accept （ CSocket 派生自 CAsyncSocket 类）判断 Server 端 socket 的事件队列中是否存在正在引入的连接事件 - FD_ACCEPT （见 1 ），换句话说，就是判断是否有来自 Client 端 socket 的连接请求。 <br />　　如果当前 Server 端 socket 的事件队列中存在正在引入的连接事件， Accept 返回一个非 0 值。否则， Accept 返回 0，此时调用 GetLastError 将返回错误代码 WSAEWOULDBLOCK ，表示队列中无任何连接请求。注意到在循环体内有一句代码： <pre>PumpMessage(FD_ACCEPT); </pre><p>　　PumpMessage 作为一个消息泵使得 socket window 中的消息能够维持在活动状态。实际跟踪进入 PumpMessage 中，发现这个消息泵与 Accept 函数的调用并不相关，它只是使很少的 socket window 消息（典型的是 WM_PAINT 窗口重绘消息）处于活动状态，而绝大部分的 socket window 消息被阻塞，被阻塞的消息中含有 WM_SOCKET_NOTIFY。<br />　　很显然，如果没有来自 Client 端 socket 的连接请求， CSocket 就会不断调用 Accept 产生循环阻塞，直到有来自 Client 端 socket 的连接请求而解除阻塞。 <br />　　阻塞解除后，表示 Server 端 socket 和 Client 端 socket 已成功连接， Server 端与 Client 端彼此相互调用 Send 和 Receive 方法开始通信。 <br /><br />3、非阻塞模式 <br /><br />　　在非阻塞模式下 利用 socket 事件 的消息机制， Server 端与 Client 端之间的通信处于异步状态下。 <br />　　通常需要从 CSocket 类派生一个新类，派生新类的目的是重载 socket 事件 的消息函数，然后在 socket 事件 的消息函数中添入合适的代码以完成 Client 端与 Server 端之间的通信，与阻塞模式相比，非阻塞模式无需创建一个新线程。 <br />　　这里将讨论当 Server 端 socket 事件 － FD_ACCEPT 被触发后，该事件的处理函数 OnAccept 是如何进一步被触发的。其它事件的处理函数如 OnConnect, OnReceive 等的触发方式与此类似。 <br />在 1 中已提到 Client/Server 端通信时， Server 端 socket 正在接收来自 Client 端 socket 连接请求，这将会触发 FD_ACCEPT 事件，同时 Server 端的 网络传输服务进程 向 Server 端的 socket window (CSocketWnd ）发送事件通知消息 WM_SOCKET_NOTIFY , 通知有 FD_ACCEPT 事件产生 , CsocketWnd 在收到事件通知消息后，调用消息处理函数 OnSocketNotify: </p><pre>LRESULT CSocketWnd::OnSocketNotify(WPARAM wParam, LPARAM lParam) 
{ 
	CSocket::AuxQueueAdd(WM_SOCKET_NOTIFY, wParam, lParam); 
	CSocket::ProcessAuxQueue(); 
	return 0L ; 
}
</pre>　　消息参数 wParam 是 socket 的句柄， lParam 是 socket 事件 。这里稍作解释一下，CSocketWnd 类是作为 CSocket 类的 友元类 ，这意味着它可以访问 CSocket 类中的保护和私有成员函数和变量， AuxQueueAdd 和 ProcessAuxQueue 是 CSocket 类的静态成员函数，如果你对友元不熟悉，请迅速找本有关 C++ 书看一下友元的使用方法吧！ <br />ProcessAuxQueue 是实质处理 socket 事件的函数，在该函数中有这样一句代码： <pre>CAsyncSocket* pSocket = CAsyncSocket::LookupHandle((SOCKET)wParam, TRUE); </pre><p>　　其实也就是由 socket 句柄得到发送事件通知消息的 socket 指针 pSocket：从 m_pmapSocketHandle 中查找（见 1 ）！ <br />　　最后， WSAGETSELECTEVENT(lParam) 会取出事件类型，在一个简单的 switch 语句中判断事件类型并调用事件处理函数。在这里，事件类型是 FD_ACCEPT ，当然就调用 pSocket-&gt;OnAccept ！ <br /><br />结束语 <br />　　Server 端 socket 处于阻塞调用模式下，它必须在一个新创建的线程中工作，防止主线程被阻塞。 <br />　　当有多个 Client 端 socket 与 Server 端 socket 连接及通信时， Server 端采用阻塞模式就显得不适合了，应该采用非阻塞模式 , 利用 socket 事件 的消息机制来接受多个 Client 端 socket 的连接请求并进行通信。 <br />　　在非阻塞模式下，利用 CSocketWnd 作为所有 sockets 的消息池，是实现 socket 事件 的消息机制的关键技术。文中存在用词不妥和可能存在的技术问题，请大家原谅，也请批评指正，谢谢！ <br /><br />注：</p><ol><li>当前模块状态——用于保存当前线程和模块状态的一个结构，可以通过 AfxGetThreadModule() 获得。AFX_MODULE_THREAD_STATE 在 CSocket 重新定义为 _AFX_SOCK_THREAD_STATE 。 
</li><li>socket 类型——在 TCP/IP 协议中， Client/Server 网络程序采用 TCP 协议：即 socket 类型为 SOCK_STREAM ，它是可靠的连接方式。在这里不采用 UDP 协议：即 socket 类型为 SOCK_DGRAM ，它是不可靠的连接方式。 </li></ol><p>源代码参考：</p><ol><li><a href="http://www.codeproject.com/internet/SocketFileTransfer.asp" target="_blank">http://www.codeproject.com/internet/SocketFileTransfer.asp</a> 采用 CSocket 类编写的基于 Client/Server 的网络文件传输程序，它是基于阻塞模式的 Client/Server 端网络程序典型示例。 
</li><li><a href="http://www.codeguru.com/Cpp/I-N/network/messaging/article.php/c5453" target="_blank">http://www.codeguru.com/Cpp/I-N/network/messaging/article.php/c5453</a> 采用 CSocket 类编写的基于 Client/Server 的网络聊天程序，它是基于非阻塞模式的 Client/Server 端网络程序典型示例。 </li></ol><p>参考资料：</p><ul><li>Microsoft MSDN Library – January 2001 
</li><li>《Windows 网络编程》 清华大学出版社 </li></ul><p>from: <a href="http://www.vckbase.com/document/viewdoc/?id=1375">http://www.vckbase.com/document/viewdoc/?id=1375</a></p><img src ="http://www.blogjava.net/weidagang2046/aggbug/73315.html" width = "1" height = "1" /><br><br><div align=right><a style="text-decoration:none;" href="http://www.blogjava.net/weidagang2046/" target="_blank">weidagang2046</a> 2006-10-03 20:01 <a href="http://www.blogjava.net/weidagang2046/articles/73315.html#Feedback" target="_blank" style="text-decoration:none;">发表评论</a></div>]]></description></item><item><title>背景位图</title><link>http://www.blogjava.net/weidagang2046/articles/73256.html</link><dc:creator>weidagang2046</dc:creator><author>weidagang2046</author><pubDate>Tue, 03 Oct 2006 02:44:00 GMT</pubDate><guid>http://www.blogjava.net/weidagang2046/articles/73256.html</guid><wfw:comment>http://www.blogjava.net/weidagang2046/comments/73256.html</wfw:comment><comments>http://www.blogjava.net/weidagang2046/articles/73256.html#Feedback</comments><slash:comments>0</slash:comments><wfw:commentRss>http://www.blogjava.net/weidagang2046/comments/commentRss/73256.html</wfw:commentRss><trackback:ping>http://www.blogjava.net/weidagang2046/services/trackbacks/73256.html</trackback:ping><description><![CDATA[
		<p>BOOL CChineseChessView::OnEraseBkgnd(CDC* pDC) <br />{<br /> return TRUE;<br /> <br /> CRect rectbk;// 客户区的大小<br /> CDC dcMen; // 内存设备描述表 <br /> CBitmap bmbk;</p>
		<p> bmbk.LoadBitmap(IDB_BITMAP1);</p>
		<p> // 得到位图bitmap的大小 <br /> BITMAP stBitmap;<br /> bmbk.GetObject(sizeof(BITMAP),&amp;stBitmap);<br /> CSize bmsize(stBitmap.bmWidth,stBitmap.bmHeight);<br /> dcMen.CreateCompatibleDC(pDC); // 创建兼容的设备描述表 <br /> // 选入新的位图对象并保存旧的位图对象 <br /> CBitmap *pold=dcMen.SelectObject(&amp;bmbk);<br /> GetClientRect(&amp;rectbk);<br /> // 取得客户区的大小 <br /> // 从内存向屏幕复制位图对象<br /> <br /> pDC-&gt;StretchBlt(rectbk.left,rectbk.top,rectbk.Width(), <br />  rectbk.Height(),&amp;dcMen,0,0,bmsize.cx,bmsize.cy,SRCCOPY);<br /> dcMen.SelectObject(pold);<br /> // 恢复旧的位图对象<br /> dcMen.DeleteDC();<br /> // 释放内存设备描述表</p>
		<p> return true;<br />}<br /></p>
<img src ="http://www.blogjava.net/weidagang2046/aggbug/73256.html" width = "1" height = "1" /><br><br><div align=right><a style="text-decoration:none;" href="http://www.blogjava.net/weidagang2046/" target="_blank">weidagang2046</a> 2006-10-03 10:44 <a href="http://www.blogjava.net/weidagang2046/articles/73256.html#Feedback" target="_blank" style="text-decoration:none;">发表评论</a></div>]]></description></item><item><title>用MFC如何高效地绘图</title><link>http://www.blogjava.net/weidagang2046/articles/73184.html</link><dc:creator>weidagang2046</dc:creator><author>weidagang2046</author><pubDate>Mon, 02 Oct 2006 03:11:00 GMT</pubDate><guid>http://www.blogjava.net/weidagang2046/articles/73184.html</guid><wfw:comment>http://www.blogjava.net/weidagang2046/comments/73184.html</wfw:comment><comments>http://www.blogjava.net/weidagang2046/articles/73184.html#Feedback</comments><slash:comments>0</slash:comments><wfw:commentRss>http://www.blogjava.net/weidagang2046/comments/commentRss/73184.html</wfw:commentRss><trackback:ping>http://www.blogjava.net/weidagang2046/services/trackbacks/73184.html</trackback:ping><description><![CDATA[显示图形如何避免闪烁，如何提高显示效率是问得比较多的问题。<br />而且多数人认为MFC的绘图函数效率很低，总是想寻求其它的解决方案。<br />MFC的绘图效率的确不高但也不差，而且它的绘图函数使用非常简单，<br />只要使用方法得当，再加上一些技巧，用MFC可以得到效率很高的绘图程序。<br />我想就我长期（呵呵当然也只有2年多）使用MFC绘图的经验谈谈<br />我的一些观点。 
<p><font size="4"><big><strong><font color="#4822dd">1、显示的图形为什么会闪烁？</font></strong></big><br /></font>    我们的绘图过程大多放在OnDraw或者OnPaint函数中，OnDraw在进行屏<br />幕显示时是由OnPaint进行调用的。当窗口由于任何原因需要重绘时，<br />总是先用背景色将显示区清除，然后才调用OnPaint，而背景色往往与绘图内容<br />反差很大，这样在短时间内背景色与显示图形的交替出现，使得显示窗口看起来<br />在闪。如果将背景刷设置成NULL，这样无论怎样重绘图形都不会闪了。<br />当然，这样做会使得窗口的显示乱成一团，因为重绘时没有背景色对原来<br />绘制的图形进行清除，而又叠加上了新的图形。<br />    有的人会说，闪烁是因为绘图的速度太慢或者显示的图形太复杂造成的，<br />其实这样说并不对，绘图的显示速度对闪烁的影响不是根本性的。<br />例如在OnDraw(CDC *pDC)中这样写：<br /> pDC-&gt;MoveTo(0,0);<br /> pDC-&gt;LineTo(100,100);<br />这个绘图过程应该是非常简单、非常快了吧，但是拉动窗口变化时还是会看见<br />闪烁。其实从道理上讲，画图的过程越复杂越慢闪烁应该越少，因为绘图用的<br />时间与用背景清除屏幕所花的时间的比例越大人对闪烁的感觉会越不明显。<br />比如：清楚屏幕时间为1s绘图时间也是为1s，这样在10s内的连续重画中就要闪<br />烁5次；如果清楚屏幕时间为1s不变，而绘图时间为9s，这样10s内的连续重画<br />只会闪烁一次。这个也可以试验，在OnDraw(CDC *pDC)中这样写：<br /> for(int i=0;i&lt;100000;i++)<br /> {<br />  pDC-&gt;MoveTo(0,i);<br />  pDC-&gt;LineTo(1000,i);<br /> }<br />呵呵，程序有点变态，但是能说明问题。<br />    说到这里可能又有人要说了，为什么一个简单图形看起来没有复杂图形那么<br />闪呢？这是因为复杂图形占的面积大，重画时造成的反差比较大，所以感觉上要<br />闪得厉害一些，但是闪烁频率要低。<br />    那为什么动画的重画频率高，而看起来却不闪？这里，我就要再次强调了，<br />闪烁是什么？闪烁就是反差，反差越大，闪烁越厉害。因为动画的连续两个帧之间<br />的差异很小所以看起来不闪。如果不信，可以在动画的每一帧中间加一张纯白的帧，<br />不闪才怪呢。</p><p><br /><font size="4"><big><strong><font color="#4822dd">2、如何避免闪烁</font></strong></big><br /></font>    在知道图形显示闪烁的原因之后，对症下药就好办了。首先当然是去掉MFC<br />提供的背景绘制过程了。实现的方法很多，<br />  * 可以在窗口形成时给窗口的注册类的背景刷付NULL<br />  * 也可以在形成以后修改背景<br /> static CBrush brush(RGB(255,0,0));<br /> SetClassLong(this-&gt;m_hWnd,GCL_HBRBACKGROUND,(LONG)(HBRUSH)brush);<br />  * 要简单也可以重载OnEraseBkgnd(CDC* pDC)直接返回TRUE<br />    这样背景没有了，结果图形显示的确不闪了，但是显示也象前面所说的一样，<br />变得一团乱。怎么办？这就要用到双缓存的方法了。双缓冲就是除了在屏幕上有<br />图形进行显示以外，在内存中也有图形在绘制。我们可以把要显示的图形先在内存中<br />绘制好，然后再一次性的将内存中的图形按照一个点一个点地覆盖到屏幕上去（这个<br />过程非常快，因为是非常规整的内存拷贝）。这样在内存中绘图时，随便用什么反差<br />大的背景色进行清除都不会闪，因为看不见。当贴到屏幕上时，因为内存中最终的图形<br />与屏幕显示图形差别很小（如果没有运动，当然就没有差别），这样看起来就不会闪。</p><p><br /><font size="4"><big><strong><font color="#2222dd">3、如何实现双缓冲</font></strong></big><br /></font>    首先给出实现的程序，然后再解释，同样是在OnDraw(CDC *pDC)中：</p><p> CDC MemDC; //首先定义一个显示设备对象<br /> CBitmap MemBitmap;//定义一个位图对象</p><p> //随后建立与屏幕显示兼容的内存显示设备<br /> MemDC.CreateCompatibleDC(NULL);<br /> //这时还不能绘图，因为没有地方画 ^_^<br /> //下面建立一个与屏幕显示兼容的位图，至于位图的大小嘛，可以用窗口的大小<br /> MemBitmap.CreateCompatibleBitmap(pDC,nWidth,nHeight);<br />  <br /> //将位图选入到内存显示设备中<br /> //只有选入了位图的内存显示设备才有地方绘图，画到指定的位图上<br /> CBitmap *pOldBit=MemDC.SelectObject(&amp;MemBitmap);</p><p> //先用背景色将位图清除干净，这里我用的是白色作为背景<br /> //你也可以用自己应该用的颜色<br /> MemDC.FillSolidRect(0,0,nWidth,nHeight,RGB(255,255,255));</p><p> //绘图<br /> MemDC.MoveTo(……);<br /> MemDC.LineTo(……);<br /> <br /> //将内存中的图拷贝到屏幕上进行显示<br /> pDC-&gt;BitBlt(0,0,nWidth,nHeight,&amp;MemDC,0,0,SRCCOPY);</p><p> //绘图完成后的清理<br /> MemBitmap.DeleteObject();<br /> MemDC.DeleteDC();</p><p>上面的注释应该很详尽了，废话就不多说了。</p><p><br /><font size="4"><big><strong><font color="#2248dd">4、如何提高绘图的效率</font></strong></big><br /></font>    我主要做的是电力系统的网络图形的CAD软件，在一个窗口中往往要显示成千上万个电力元件，而每个元件又是由点、线、圆等基本图形构成。如果真要在一次重绘过程重画这么多元件，可想而知这个过程是非常漫长的。如果加上了图形的浏览功能，鼠标拖动图形滚动时需要进行大量的重绘，速度会慢得让用户将无法忍受。怎么办？只有再研究研究MFC的绘图过程了。<br />    实际上，在OnDraw(CDC *pDC)中绘制的图并不是所有都显示了的，例如：你<br />在OnDraw中画了两个矩形，在一次重绘中虽然两个矩形的绘制函数都有执行，但是很有可能只有一个显示了，这是因为MFC本身为了提高重绘的效率设置了裁剪区。裁剪区的作用就是：只有在这个区内的绘图过程才会真正有效，在区外的是无效的，即使在区外执行了绘图函数也是不会显示的。因为多数情况下窗口重绘的产生大多是因为窗口部分被遮挡或者窗口有滚动发生，改变的区域并不是整个图形而只有一小部分，这一部分需要改变的就是pDC中的裁剪区了。因为显示（往内存或者显存都叫显示）比绘图过程的计算要费时得多，有了裁剪区后显示的就只是应该显示的部分，大大提高了显示效率。但是这个裁剪区是MFC设置的，它已经为我们提高了显示效率，在进行复杂图形的绘制时如何进一步提高效率呢？那就只有去掉在裁剪区外的绘图过程了。可以先用pDC-&gt;GetClipBox()得到裁剪区，然后在绘图时判断你的图形是否在这个区内，如果在就画，不在就不画。<br />如果你的绘图过程不复杂，这样做可能对你的绘图效率不会有提高。<br /><br />from: <a href="http://blog.yesky.com/211/arkcq/1549711.shtml">http://blog.yesky.com/211/arkcq/1549711.shtml</a></p><img src ="http://www.blogjava.net/weidagang2046/aggbug/73184.html" width = "1" height = "1" /><br><br><div align=right><a style="text-decoration:none;" href="http://www.blogjava.net/weidagang2046/" target="_blank">weidagang2046</a> 2006-10-02 11:11 <a href="http://www.blogjava.net/weidagang2046/articles/73184.html#Feedback" target="_blank" style="text-decoration:none;">发表评论</a></div>]]></description></item><item><title>GDI Resource Leaks</title><link>http://www.blogjava.net/weidagang2046/articles/73176.html</link><dc:creator>weidagang2046</dc:creator><author>weidagang2046</author><pubDate>Mon, 02 Oct 2006 01:59:00 GMT</pubDate><guid>http://www.blogjava.net/weidagang2046/articles/73176.html</guid><wfw:comment>http://www.blogjava.net/weidagang2046/comments/73176.html</wfw:comment><comments>http://www.blogjava.net/weidagang2046/articles/73176.html#Feedback</comments><slash:comments>0</slash:comments><wfw:commentRss>http://www.blogjava.net/weidagang2046/comments/commentRss/73176.html</wfw:commentRss><trackback:ping>http://www.blogjava.net/weidagang2046/services/trackbacks/73176.html</trackback:ping><description><![CDATA[
		<p class="first">Using the techniques of <a href="http://www.relisoft.com/index.htm">Resource Management</a> you can not only guarantee that your program will leak no memory, it will also leak no other resources. In a Windows program, GDI objects are an important class of resorces that should be protected using Resource Management techniques. In our own product, Code Co-op, we make extensive use of Resource Management, so we don't expect any leaks. Imagine our surprise when we found out that the Dispatcher, an important component of Code Co-op, turned out to be a voracious consumer of GDI resources.</p>
		<p>First of all, how do you find out such a thing? It turns out that the good old <b>Task Manager</b> can display GDI resources consumed by any running process. Use the Task Manager's menu item <i>View&gt;Select Columns</i> and click the <i>GDI Objects</i> checkbox. A new column <i>GDI Object</i> will appear in the <i>Processes</i> view. If a number displayed in that column keeps increasing with time, you know there is a leak.</p>
		<p>By watching the Dispatcher process, we noticed that its allotment of GDI objects kept increasing every time the Dispatcher was checking email. Now we had to pinpoint exactly where in our code these resources were leaking. The way to do it is to count the GDI object usage before and after a given operation. The easiest (and most reliable) way to do it is to define an object which counts the resources first in its constructor and then in its destructor. If the numbers differ, it displays the difference. Here's the class we designed for this purpose.</p>
		<pre>class <span class="pink"><b>DbgGuiLeak</b></span>
{
public:
    explicit DbgGuiLeak ()
    {
        _guiResCount = <span class="blue"><b>::GetGuiResources</b></span> (<span class="blue"><b>::GetCurrentProcess</b></span> (),
                                          GR_GDIOBJECTS);
    }
    ~DbgGuiLeak ()
    {
        int leaks = <span class="blue"><b>::GetGuiResources</b></span> (<span class="blue"><b>::GetCurrentProcess</b></span> (), 
                                       GR_GDIOBJECTS) - _guiResCount;
        if (leaks != 0)
        {
            std::cout &lt;&lt; "Gui Resources Leaked: " &lt;&lt; leaks &lt;&lt; std::endl;
        }
    }
private:
    unsigned _guiResCount;
};</pre>
		<p>This kind of encapsulation has many advantages. Since we are using Resource Management religiously, all our resources are encapsulated in smart objects. The resource are automatically freed in those object's destructors. So if you define any smart object within the scope of the lifetime of your <em class="blue">DbgGuiLeak</em>, that object's destructor is guaranteed to be called <i>before</i> the destructor of <em class="blue">DbgGuiLeak</em>. C++ language <i>guarantees</i> this LIFO (last-in-first-out) ordering of lifetimes of stack objects. In other words, you won't see any spurious leaks resulting from the arbitrary order of resource deallocation. Compare the two examples below. </p>
		<p>In the first example, the leak counter will see no leaks (the destructor of <em class="blue">gidObj</em> will free its GDI resource before the destructor of <em class="blue">leakCounter</em> is called).</p>
		<pre>{
    DbgGuiLeak leakCounter;
    SmartGdiObject gdiObj;
    // destructor of gdiObj
    // destructor of leakCounter
}</pre>
		<p>In the second example, a spurious leak will be displayed only because the implicit destructor of <em class="blue">gidObj</em> is called <i>after</i> the last explicit line of code is executed.</p>
		<pre>{ // bad code!
    unsigned gdiCount = <span class="blue"><b>::GetGuiResources</b></span> (<span class="blue"><b>::GetCurrentProcess</b></span> (), 
                                           GR_GDIOBJECTS);
    SmartGdiObject gdiObj;
    int leaks = <span class="blue"><b>::GetGuiResources</b></span> (<span class="blue"><b>::GetCurrentProcess</b></span> (),
                                   GR_GDIOBJECTS) - gdiCount;
    std::cout &lt;&lt; "GDI Resources Leaked: " &lt;&lt; leaks &lt;&lt; std::endl;
    // destructor of gdiObj
}</pre>
		<h3>Using a DLL</h3>
		<p>To get access to Simple MAPI, we have to use <b>mapi32.dll</b>, a dynamic-load library located in the Windows system directory. A DLL is a resource, so it has to be managed (in the RM sense, not the .NET sense). We define a simple class, <em class="blue">Dll</em>, to take care of loading and freeing any DLL. </p>
		<p>To call a function defined in a DLL, you have to retrieve a function pointer either by ordinal or, as we do it here, by name. You not only have to know the name of the function, but, because you would like to call it, you must know its signature. The name is passed to <em class="blue">Dll::GetFunction</em> as a string argument, but the signature (which is a <i>type</i>) must be passed as a template argument. Internally, we cast the typeless pointer returned by the API to a function pointer of the appropriate signature.</p>
		<pre>class <span class="pink"><b>Dll</b></span>
{
public:
    Dll (std::string const &amp; filename);
    ~Dll ();

    std::string const &amp; GetFilename () const { return _filename; }

    <b>template &lt;class T&gt;</b>
    void GetFunction (std::string const &amp; funcName, T &amp; funPointer)
    {
        funPointer = static_cast&lt;T&gt; (GetFunction (funcName));
    }

private:
    void * GetFunction (std::string const &amp; funcName) const;

    std::string const _filename;
    HINSTANCE _h;
};</pre>
		<p>The constructor of <em class="blue">Dll</em> calls the <em class="blue">LoadLibrary</em> API. (Strictly speaking, we don't need to store the name of the DLL, but it might get handy in case we wanted to display meaningful error reports.)</p>
		<pre>
				<span class="pink">
						<b>Dll::Dll</b>
				</span> (std::string const &amp; filename)
    : _filename (filename),
      _h (<span class="blue"><b>::LoadLibrary</b></span> (filename.c_str ()))
      
{
    if (_h == 0)
        throw "Cannot load dynamic link library";
}</pre>
		<p>According to the rules of Resource Management, the destructor of the <em class="blue">Dll</em> object must free the DLL resource --it does it by calling <em class="blue">FreeLibrary</em>.</p>
		<pre>
				<span class="pink">
						<b>Dll::~Dll</b>
				</span> ()
{
    if (_h != 0)
    {
        <span class="blue"><b>::FreeLibrary</b></span> (_h);
    }
}</pre>
		<p>A (typeless) function pointer is retrieved from the DLL by calling the <em class="blue">GetProcAddress</em> API.</p>
		<pre>void * <span class="pink"><b>Dll::GetFunction</b></span> (std::string const &amp; funcName) const
{
    void * pFunction = <span class="blue"><b>::GetProcAddress</b></span> (_h, funcName.c_str ());
    if (pFunction == 0)
    {
        throw "Cannot find function in the dynamic link library";
    }
    return pFunction;
}</pre>
		<p>Incidentally, when calling the template member function <em class="blue">Dll::GetFunction</em> you don't have to explicitly specify the template argument. The complier will deduce it from the type of the second argument. You'll see an example soon.</p>
		<br />
		<h3>Simple MAPI</h3>For the purpose of our test, all we need from Simple MAPI is to log ourselves on and off. The result of a logon is a <i>session</i>. Since a session is associated with internal resources (including some GDI resources, which are of particular interest to us), it has to be managed in the RM sense. We define a <em class="blue">SimpleMapi::Session</em> to encapsulate this resource. It calls <em class="blue">MAPILogon</em> in its constructor and <em class="blue">MAPILogoff</em> in its destructor. The signatures of these two functions are typedef'd inside the class definition (that's the only place we will need them). <em class="blue">Logon</em> and <em class="blue">Logoff</em> are both very specific <i>pointer-to-function</i> types. Those typedefs not only specify the return- and argument types, but also the calling conventions (FAR PASCAL in this case). <pre>namespace <span class="pink"><b>SimpleMapi</b></span>
{
    class <span class="pink"><b>Session</b></span>
    {
    public:
        Session (Dll &amp; mapi);
        ~Session ();
    private:
        Dll        &amp;_mapi;
        LHANDLE    _h;
    private:
        typedef ULONG (FAR PASCAL *<b>Logon</b>) (ULONG ulUIParam,
                                           LPTSTR lpszProfileName,
                                           LPTSTR lpszPassword,
                                           FLAGS flFlags,
                                           ULONG ulReserved,
                                           LPLHANDLE lplhSession);
        typedef ULONG (FAR PASCAL *<b>Logoff</b>) (LHANDLE session,
                                            ULONG ulUIParam,
                                            FLAGS flFlags,
                                            ULONG reserved);
    };
}</pre><p>Here is the constructor of a Simple MAPI session. Note that we call the <em class="blue">GetFunction</em> template method of <em class="blue">Dll</em> without specifying the template parameter. That's because we are giving the compiler enough information through the type <em class="blue">Logon</em> (see the typedef above) of the <em class="blue">logon</em> argument. Once the (correctly typed) function pointer is initialized, we can make a call through it using the regular function-call syntax.</p><pre>namespace <span class="pink"><b>SimpleMapi</b></span>
{
    <span class="pink"><b>Session::Session</b></span> (Dll &amp; mapi)
        : _mapi (mapi)
    {
        Logon logon; // logon is a pointer to function
        _mapi.<b>GetFunction</b> ("MAPILogon", logon);
        std::cout &lt;&lt; "Mapi Logon" &lt;&lt; std::endl;
        ULONG rCode = logon (0, // Handle to window for MAPI dialogs
                            0,  // Use default profile
                            0,  // No password
                            0,  // No flags
                            0,  // Reserved -- must be zero
                            &amp;_h); // Session handle
        if (rCode != SUCCESS_SUCCESS)
            throw "Logon failed";
    }
}</pre><p>The destructor of <em class="blue">SimpleMapi::Session</em> calls <em class="blue">MAPILogoff</em> function thus closing the session and (hopefully!) freeing all the resources allocated on its behalf.</p><pre>namespace <span class="pink"><b>SimpleMapi</b></span>
{
    <span class="pink"><b>Session::~Session</b></span> ()
    {
        Logoff logoff;
        _mapi.GetFunction ("MAPILogoff", logoff);
        std::cout &lt;&lt; "Mapi Logoff" &lt;&lt; std::endl;
        ULONG rCode = logoff (_h, 0, 0, 0);

        if (rCode != SUCCESS_SUCCESS)
            throw "Logoff failed";
    }
}</pre><h3>Testing for Leaks</h3><p>Here's our simple test. First we create a <em class="blue">DbgGuiLeak</em> object which will remember how many GUI resources are in use before the test starts. Then we create the <em class="blue">Dll</em> object which loads <em class="blue">mapi32.dll</em> into our address space. Finally, we create a <em class="blue">SimpleMapi::Session</em> which logs us into the MAPI subsystem.</p><p>What happens next is all the destructors are called in the reverse order of creation. So first the destructor of <em class="blue">SimpleMapi::Session</em> logs us off and closes the session. Then the destructor of <em class="blue">Dll</em> frees the DLL. Finally, the destructor of <em class="blue">DbgGuiLeak</em> checks if any GUI resources have been leaked.</p><pre>void <span class="pink"><b>TestMapi</b></span> ()
{
    DbgGuiLeak leak;
    Dll mapi ("mapi32.dll");
    SimpleMapi::Session session (mapi);
    // Session destructor logs off
    // Dll destructor frees library
    // DbgGuiLeak destructor prints leak info
}</pre><p>The main function of our program calls <em class="blue">TestMapi</em> three times, to get past any transcient effects (not that we think it's ok for the first logon to leak resources--but it wouldn't be such a disaster).</p><pre>int <span class="pink"><b>main</b></span> ()
{
    try
    {
        <b>TestMapi</b> ();
        <b>TestMapi</b> ();
        <b>TestMapi</b> ();
    }
    catch (char const * msg)
    {
        std::cerr &lt;&lt; msg &lt;&lt; std::endl;
    }
}</pre><p>For completeness, these are the files you have to include in the test program for it to compile:</p><pre>#include &lt;windows.h&gt;
#include &lt;mapi.h&gt;
#include &lt;iostream&gt;</pre><p>Now you are ready to compile and run the test. Results may vary, depending on what email client is installed on your computer and what security updates you have downloaded from Microsoft. Here are some typical results for a well-updated system with Microsoft Outlook Express.</p><pre>C:\Test\MapiCons\Release&gt;mapicons
Mapi Logon
Mapi Logoff
Gui Resources Leaked: 16
Mapi Logon
Mapi Logoff
Gui Resources Leaked: 2
Mapi Logon
Mapi Logoff
Gui Resources Leaked: 2</pre><p>The first logon leaks 16 GDI resources, which is bad, but not terminally so. What is <i>really</i> bad is that every subsequent logon/logoff sequence leaks two additional resources. There is no excuse for this kind of shabby programming.</p><p>Microsoft acknowledges having a similar <a class="cnav" href="http://support.microsoft.com/?id=293150" target="window">problem with their Exchange Server</a><br /><br />from: <a href="http://www.relisoft.com/win32/gdileaks.html">http://www.relisoft.com/win32/gdileaks.html</a></p><img src ="http://www.blogjava.net/weidagang2046/aggbug/73176.html" width = "1" height = "1" /><br><br><div align=right><a style="text-decoration:none;" href="http://www.blogjava.net/weidagang2046/" target="_blank">weidagang2046</a> 2006-10-02 09:59 <a href="http://www.blogjava.net/weidagang2046/articles/73176.html#Feedback" target="_blank" style="text-decoration:none;">发表评论</a></div>]]></description></item><item><title>用VC进行屏幕截取编程</title><link>http://www.blogjava.net/weidagang2046/articles/73160.html</link><dc:creator>weidagang2046</dc:creator><author>weidagang2046</author><pubDate>Sun, 01 Oct 2006 15:36:00 GMT</pubDate><guid>http://www.blogjava.net/weidagang2046/articles/73160.html</guid><wfw:comment>http://www.blogjava.net/weidagang2046/comments/73160.html</wfw:comment><comments>http://www.blogjava.net/weidagang2046/articles/73160.html#Feedback</comments><slash:comments>0</slash:comments><wfw:commentRss>http://www.blogjava.net/weidagang2046/comments/commentRss/73160.html</wfw:commentRss><trackback:ping>http://www.blogjava.net/weidagang2046/services/trackbacks/73160.html</trackback:ping><description><![CDATA[---- 屏幕截取是令人比较感兴趣的事情.虽然现在有不少应用程序如HYPERSNAP等可以用来截取你所喜欢的屏幕画面,但是如果能把这个功能加到自己的程序中,就更能利用它强大的作用. <br /><br />---- 下面用VC来逐步介绍在Windows95下的实现过程.首先我们要确定屏幕截取的区域,用LPRECT结构来定义.可以截取一个窗口,或整个屏幕.以下代码把选定的屏幕区域拷贝到位图中. <br /><br />HBITMAP CopyScreenToBitmap(LPRECT lpRect)<br />//lpRect 代表选定区域<br />{<br />HDC       hScrDC, hMemDC;      <br />// 屏幕和内存设备描述表<br />HBITMAP    hBitmap, hOldBitmap;   <br />// 位图句柄<br />int       nX, nY, nX2, nY2;      <br />// 选定区域坐标<br />int       nWidth, nHeight;      <br />// 位图宽度和高度<br />int       xScrn, yScrn;         <br />// 屏幕分辨率<br /><br />   // 确保选定区域不为空矩形<br />   if (IsRectEmpty(lpRect))<br />     return NULL;<br />   //为屏幕创建设备描述表<br />   hScrDC = CreateDC("DISPLAY", NULL, NULL, NULL);<br />   //为屏幕设备描述表创建兼容的内存设备描述表<br />   hMemDC = CreateCompatibleDC(hScrDC);<br />   // 获得选定区域坐标<br />   nX = lpRect- &gt;left;<br />   nY = lpRect- &gt;top;<br />   nX2 = lpRect- &gt;right;<br />   nY2 = lpRect- &gt;bottom;<br />   // 获得屏幕分辨率<br />   xScrn = GetDeviceCaps(hScrDC, HORZRES);<br />   yScrn = GetDeviceCaps(hScrDC, VERTRES);<br />   //确保选定区域是可见的<br />   if (nX 〈0)<br />      nX = 0;<br />   if (nY 〈  0)<br />      nY = 0;<br />   if (nX2 &gt; xScrn)<br />      nX2 = xScrn;<br />   if (nY2 &gt; yScrn)<br />      nY2 = yScrn;<br />   nWidth = nX2 - nX;<br />   nHeight = nY2 - nY;<br />   // 创建一个与屏幕设备描述表兼容的位图<br />hBitmap = CreateCompatibleBitmap<br />(hScrDC, nWidth, nHeight);<br />   // 把新位图选到内存设备描述表中<br />   hOldBitmap = SelectObject(hMemDC, hBitmap);<br />   // 把屏幕设备描述表拷贝到内存设备描述表中<br />BitBlt(hMemDC, 0, 0, nWidth, nHeight,<br />hScrDC, nX, nY, SRCCOPY);<br />   //得到屏幕位图的句柄<br />   hBitmap = SelectObject(hMemDC, hOldBitmap);<br />   //清除 <br />   DeleteDC(hScrDC);<br />   DeleteDC(hMemDC);<br />   // 返回位图句柄<br />   return hBitmap;<br />}<br />   <br />得到屏幕位图句柄以后,我们<br />可以把屏幕内容粘贴到剪贴板上.<br />    if (OpenClipboard(hWnd)) <br />     //hWnd为程序窗口句柄<br />      {<br />        //清空剪贴板<br />        EmptyClipboard();<br />        //把屏幕内容粘贴到剪贴板上,<br />        hBitmap 为刚才的屏幕位图句柄<br />        SetClipboardData(CF_BITMAP, hBitmap);<br />        //关闭剪贴板<br />        CloseClipboard();<br />      }<br />   我们也可以把屏幕内容以位图格式存到磁盘文件上.<br />      <br />int SaveBitmapToFile(HBITMAP hBitmap , <br />LPSTR lpFileName) //hBitmap 为刚才的屏幕位图句柄<br />{      //lpFileName 为位图文件名<br />HDC            hDC;         <br />    //设备描述表<br />      int            iBits;      <br />  //当前显示分辨率下每个像素所占字节数<br />WORD            wBitCount;   <br />    //位图中每个像素所占字节数<br />      //定义调色板大小， 位图中像素字节大小 ，<br />      位图文件大小 ， 写入文件字节数<br />DWORD           dwPaletteSize=0,<br />dwBmBitsSize,<br /> dwDIBSize, dwWritten;<br />BITMAP          Bitmap;        <br />//位图属性结构<br />BITMAPFILEHEADER   bmfHdr;        <br />//位图文件头结构<br />      BITMAPINFOHEADER   bi;            <br />//位图信息头结构 <br />LPBITMAPINFOHEADER lpbi;          <br />//指向位图信息头结构<br />      HANDLE          fh, hDib, hPal,hOldPal=NULL;<br />//定义文件，分配内存句柄，调色板句柄<br />  <br />   //计算位图文件每个像素所占字节数<br />   hDC = CreateDC("DISPLAY",NULL,NULL,NULL);<br />iBits = GetDeviceCaps(hDC, BITSPIXEL) * <br />GetDeviceCaps(hDC, PLANES);<br />   DeleteDC(hDC);<br />   if (iBits 〈 = 1)<br />      wBitCount = 1;<br />   else if (iBits 〈 = 4)<br />      wBitCount = 4;<br />   else if (iBits 〈  = 8)<br />      wBitCount = 8;<br />   else if (iBits 〈 = 24)<br />      wBitCount = 24;<br />   //计算调色板大小<br />   if (wBitCount 〈 = 8)<br />      dwPaletteSize = (1 〈 〈   wBitCount) *<br />      sizeof(RGBQUAD);<br />   <br />   //设置位图信息头结构<br />   GetObject(hBitmap, sizeof(BITMAP), (LPSTR)&amp;Bitmap);<br />   bi.biSize            = sizeof(BITMAPINFOHEADER);<br />   bi.biWidth           = Bitmap.bmWidth;<br />   bi.biHeight          = Bitmap.bmHeight;<br />   bi.biPlanes          = 1;<br />   bi.biBitCount         = wBitCount;<br />   bi.biCompression      = BI_RGB;<br />   bi.biSizeImage        = 0;<br />   bi.biXPelsPerMeter     = 0;<br />   bi.biYPelsPerMeter     = 0;<br />   bi.biClrUsed         = 0;<br />   bi.biClrImportant      = 0;<br /><br />   dwBmBitsSize = ((Bitmap.bmWidth *<br />    wBitCount+31)/32)* 4<br /> *Bitmap.bmHeight ;<br />   //为位图内容分配内存<br />   hDib  = GlobalAlloc(GHND,dwBmBitsSize+<br />dwPaletteSize+sizeof(BITMAPINFOHEADER));<br />   lpbi = (LPBITMAPINFOHEADER)GlobalLock(hDib);<br />   *lpbi = bi;<br />   // 处理调色板   <br />   hPal = GetStockObject(DEFAULT_PALETTE);<br />   if (hPal)<br />   {<br />      hDC  = GetDC(NULL);<br />      hOldPal = SelectPalette(hDC, hPal, FALSE);<br />      RealizePalette(hDC);<br />   }<br />   // 获取该调色板下新的像素值<br />   GetDIBits(hDC, hBitmap, 0, (UINT) Bitmap.bmHeight,<br /> (LPSTR)lpbi + sizeof(BITMAPINFOHEADER)<br />    +dwPaletteSize,<br /> (BITMAPINFOHEADER *)<br />    lpbi, DIB_RGB_COLORS);<br />   //恢复调色板   <br />   if (hOldPal)<br />   {<br />      SelectPalette(hDC, hOldPal, TRUE);<br />      RealizePalette(hDC);<br />      ReleaseDC(NULL, hDC);<br />   }<br />   //创建位图文件    <br />fh = CreateFile(lpFileName, GENERIC_WRITE, <br />0, NULL, CREATE_ALWAYS,<br />         FILE_ATTRIBUTE_NORMAL | FILE_<br />         FLAG_SEQUENTIAL_SCAN, NULL);<br />   if (fh == INVALID_HANDLE_VALUE)<br />      return FALSE;<br />   // 设置位图文件头<br />   bmfHdr.bfType = 0x4D42;  // "BM"<br />dwDIBSize    = sizeof(BITMAPFILEHEADER) <br />           + sizeof(BITMAPINFOHEADER)<br />        + dwPaletteSize + dwBmBitsSize;  <br />   bmfHdr.bfSize = dwDIBSize;<br />   bmfHdr.bfReserved1 = 0;<br />   bmfHdr.bfReserved2 = 0;<br />   bmfHdr.bfOffBits = (DWORD)sizeof<br />   (BITMAPFILEHEADER) <br />      + (DWORD)sizeof(BITMAPINFOHEADER)<br />     + dwPaletteSize;<br />   // 写入位图文件头<br />WriteFile(fh, (LPSTR)&amp;bmfHdr, sizeof<br />(BITMAPFILEHEADER), &amp;dwWritten, NULL);<br />   // 写入位图文件其余内容<br />   WriteFile(fh, (LPSTR)lpbi, dwDIBSize, <br />   &amp;dwWritten, NULL);<br />   //清除   <br />   GlobalUnlock(hDib);<br />   GlobalFree(hDib);<br />   CloseHandle(fh);<br />} <br /><br />from: <a href="http://www.cnsdn.com.cn/inc/show.asp?id=3472">http://www.cnsdn.com.cn/inc/show.asp?id=3472</a><img src ="http://www.blogjava.net/weidagang2046/aggbug/73160.html" width = "1" height = "1" /><br><br><div align=right><a style="text-decoration:none;" href="http://www.blogjava.net/weidagang2046/" target="_blank">weidagang2046</a> 2006-10-01 23:36 <a href="http://www.blogjava.net/weidagang2046/articles/73160.html#Feedback" target="_blank" style="text-decoration:none;">发表评论</a></div>]]></description></item><item><title>位图文件读写综述</title><link>http://www.blogjava.net/weidagang2046/articles/73145.html</link><dc:creator>weidagang2046</dc:creator><author>weidagang2046</author><pubDate>Sun, 01 Oct 2006 09:07:00 GMT</pubDate><guid>http://www.blogjava.net/weidagang2046/articles/73145.html</guid><wfw:comment>http://www.blogjava.net/weidagang2046/comments/73145.html</wfw:comment><comments>http://www.blogjava.net/weidagang2046/articles/73145.html#Feedback</comments><slash:comments>0</slash:comments><wfw:commentRss>http://www.blogjava.net/weidagang2046/comments/commentRss/73145.html</wfw:commentRss><trackback:ping>http://www.blogjava.net/weidagang2046/services/trackbacks/73145.html</trackback:ping><description><![CDATA[&nbsp;&nbsp;&nbsp;&nbsp; 摘要: 位图文件读写综述																						作者：																												吉林大学																																																														胡卓玮																												...&nbsp;&nbsp;<a href='http://www.blogjava.net/weidagang2046/articles/73145.html'>阅读全文</a><img src ="http://www.blogjava.net/weidagang2046/aggbug/73145.html" width = "1" height = "1" /><br><br><div align=right><a style="text-decoration:none;" href="http://www.blogjava.net/weidagang2046/" target="_blank">weidagang2046</a> 2006-10-01 17:07 <a href="http://www.blogjava.net/weidagang2046/articles/73145.html#Feedback" target="_blank" style="text-decoration:none;">发表评论</a></div>]]></description></item><item><title>VC设置对话框背景图的代码</title><link>http://www.blogjava.net/weidagang2046/articles/72780.html</link><dc:creator>weidagang2046</dc:creator><author>weidagang2046</author><pubDate>Fri, 29 Sep 2006 05:04:00 GMT</pubDate><guid>http://www.blogjava.net/weidagang2046/articles/72780.html</guid><wfw:comment>http://www.blogjava.net/weidagang2046/comments/72780.html</wfw:comment><comments>http://www.blogjava.net/weidagang2046/articles/72780.html#Feedback</comments><slash:comments>0</slash:comments><wfw:commentRss>http://www.blogjava.net/weidagang2046/comments/commentRss/72780.html</wfw:commentRss><trackback:ping>http://www.blogjava.net/weidagang2046/services/trackbacks/72780.html</trackback:ping><description><![CDATA[  //设置对话框背景图<br />  PAINTSTRUCT ps;<br />  CDC *dc = BeginPaint(&amp;ps);<br />  CDC memdc;<br />  CRect rect;<br />  CBitmap bitmap;<br />  BITMAP szbitmap;<br />  bitmap.LoadBitmap( IDB_QAM_MAIN );<br />  bitmap.GetObject( sizeof(BITMAP),&amp;szbitmap );<br />  CSize size( szbitmap.bmWidth,szbitmap.bmHeight );<br />  memdc.CreateCompatibleDC(dc);<br />  CBitmap *oldbitmap=memdc.SelectObject(&amp;bitmap);<br />  GetClientRect(&amp;rect);<br />  dc-&gt;StretchBlt(0,0,rect.Width(),rect.Height(),<br />   &amp;memdc,0,0,size.cx,size.cy,SRCCOPY);<br />  memdc.SelectObject(oldbitmap);<br />  memdc.DeleteDC();<br />  EndPaint(&amp;ps);<br /><br />from: <a href="http://www.360doc.com/showWeb/0/18/157828.aspx">http://www.360doc.com/showWeb/0/18/157828.aspx</a><img src ="http://www.blogjava.net/weidagang2046/aggbug/72780.html" width = "1" height = "1" /><br><br><div align=right><a style="text-decoration:none;" href="http://www.blogjava.net/weidagang2046/" target="_blank">weidagang2046</a> 2006-09-29 13:04 <a href="http://www.blogjava.net/weidagang2046/articles/72780.html#Feedback" target="_blank" style="text-decoration:none;">发表评论</a></div>]]></description></item><item><title>如何在改变鼠标光标时不闪烁</title><link>http://www.blogjava.net/weidagang2046/articles/72750.html</link><dc:creator>weidagang2046</dc:creator><author>weidagang2046</author><pubDate>Fri, 29 Sep 2006 03:18:00 GMT</pubDate><guid>http://www.blogjava.net/weidagang2046/articles/72750.html</guid><wfw:comment>http://www.blogjava.net/weidagang2046/comments/72750.html</wfw:comment><comments>http://www.blogjava.net/weidagang2046/articles/72750.html#Feedback</comments><slash:comments>0</slash:comments><wfw:commentRss>http://www.blogjava.net/weidagang2046/comments/commentRss/72750.html</wfw:commentRss><trackback:ping>http://www.blogjava.net/weidagang2046/services/trackbacks/72750.html</trackback:ping><description><![CDATA[    微软知识库有一篇文章Q131991描述了三种方法来改变基于MFC应用的鼠标光标。其中一种方法是重载CWnd::PreCreateWindow()函数注册自己的要改变鼠标指针的窗口类。这个方法对于要始终使用一个鼠标光标的应用程序很适合。<br /><br />    如果在应用程序中要动态改变鼠标光标，微软知识库的这篇文章建议重载CWnd::OnSetCursor()来实现。但是这种方法有一个缺点，就是当设置鼠标光标及还原时都会出现令人讨厌的光标闪烁。<br /><br />    如果应用程序中要使用几个不同的鼠标光标，为了不发生任何光标闪烁，本文介绍一种方法：首先按照微软知识库文章所说重载PreCreateWindow函数，但是不要指定要使用的鼠标光标，而是使用NULL。这样就防止了Windows或MFC针对鼠标指针做任何操作。<br /><br />BOOL CMyView::PreCreateWindow(CREATESTRUCT&amp; cs)<br />{<br />// 创建自己的窗口类，窗口不设置光标，以便根据需要进行设置 <br />if (cs.lpszClass == NULL)<br />cs.lpszClass = AfxRegisterWndClass(CS_DBLCLKS);<br />return CScrollView::PreCreateWindow(cs);<br />}<br /><br />    因为窗口类没有任何事先指定好的的鼠标指针，所以以上代码有效地派出了光标的闪烁。（注意这段代码创建的窗口类也没有背景刷，所以窗口的背景色需要自己画。为此要向函数AfxRegisterWndClass()传递第三个参数作为背景刷。）<br /><br />    光标的闪烁是消除了，但同时光标也没了！不用担心。在处理鼠标事件OnMouseMove时设置光标是很容易的事情。实践证明，如果要在应用窗口中改变鼠标指针，在OnMouseMove事件处理模块中设置光标是最方<br />便的。<br /><br />void CMyView::OnMouseMove(UINT nFlags, CPoint point)<br />{<br />// 设置光标表示当前的操作<br />if (m_nOperation == OPERATION_1)<br />::SetCursor(AfxGetApp()-&gt;LoadStandardCursor(IDC_CROSS));<br />else if (m_nOperation == OPERATION_2)<br />::SetCursor(AfxGetApp()-&gt;LoadStandardCursor( ??? ));<br />else // 普通光标指针<br />::SetCursor(AfxGetApp()-&gt;LoadStandardCursor(IDC_ARROW));<br />}<br /><br />尽管要做一点额外的工作，但它实现了应用中不同的鼠标指针变化，同时消除了闪烁。 <br /><br />from: <a href="http://www.vckbase.com/vckbase/vckbase9/vc/nonctrls/system_30/0930003.htm">http://www.vckbase.com/vckbase/vckbase9/vc/nonctrls/system_30/0930003.htm</a><img src ="http://www.blogjava.net/weidagang2046/aggbug/72750.html" width = "1" height = "1" /><br><br><div align=right><a style="text-decoration:none;" href="http://www.blogjava.net/weidagang2046/" target="_blank">weidagang2046</a> 2006-09-29 11:18 <a href="http://www.blogjava.net/weidagang2046/articles/72750.html#Feedback" target="_blank" style="text-decoration:none;">发表评论</a></div>]]></description></item><item><title>The File Dialog</title><link>http://www.blogjava.net/weidagang2046/articles/72681.html</link><dc:creator>weidagang2046</dc:creator><author>weidagang2046</author><pubDate>Thu, 28 Sep 2006 14:03:00 GMT</pubDate><guid>http://www.blogjava.net/weidagang2046/articles/72681.html</guid><wfw:comment>http://www.blogjava.net/weidagang2046/comments/72681.html</wfw:comment><comments>http://www.blogjava.net/weidagang2046/articles/72681.html#Feedback</comments><slash:comments>0</slash:comments><wfw:commentRss>http://www.blogjava.net/weidagang2046/comments/commentRss/72681.html</wfw:commentRss><trackback:ping>http://www.blogjava.net/weidagang2046/services/trackbacks/72681.html</trackback:ping><description><![CDATA[
		<table cellspacing="0" cellpadding="0" width="660" border="0">
				<tbody>
						<tr>
								<td width="100%">
										<p class="paratitle">Overview</p>
								</td>
						</tr>
						<tr>
								<td width="100%" bgcolor="#ff0000" height="2">
								</td>
						</tr>
				</tbody>
		</table>
		<table width="660" border="0">
				<tbody>
						<tr>
								<td width="100%">
										<p class="parajust">Microsoft Windows provides a series of objects called Common Dialog boxes. One of them is intended for file processing. The beauty of these dialog boxes is that they are highly configured to handle their intended job but you can still customize them as you see fit. 
</p>
										<p class="parajust">The File Dialog box is a control used to open or save a file. To use a File Dialog, you can first declare a <b>CFileDialog</b> variable using its constructor whose syntax is:</p>
								</td>
						</tr>
				</tbody>
		</table>
		<pre>CFileDialog(BOOL <i>bOpenFileDialog</i>,
	   LPCTSTR <i>lpszDefExt</i> = NULL,
	   LPCTSTR <i>lpszFileName</i> = NULL,
	   DWORD <i>dwFlags</i> = OFN_HIDEREADONLY | OFN_OVERWRITEPROMPT,
	   LPCTSTR <i>lpszFilter</i> = NULL,
	   CWnd* <i>pParentWnd</i> = NULL );</pre>
		<table width="660" border="0">
				<tbody>
						<tr>
								<td width="100%">
										<p class="parajust">The <b>CFileDialog</b> constructor requires one argument: the <i>bOpenFileDialog</i>.</p>
										<p class="parajust">The second argument, <i>lpszDefExt</i>, is the default extension that should be used or applied on the file.</p>
										<p class="parajust">The <i>dwFlags</i> parameter specifies the options to use on the dialog box.</p>
										<p class="parajust">The <i>lpszFilter</i> string specifies the types of files that will be dealt with.</p>
										<p class="parajust">The <i>pParentWnd</i> parameter is a pointer to the parent of the dialog box, if the dialog box has one.</p>
								</td>
						</tr>
				</tbody>
		</table>
		<table cellspacing="0" cellpadding="0" width="660" border="0">
				<tbody>
						<tr>
								<td width="100%">
										<p class="paratitle">File Saving</p>
								</td>
						</tr>
						<tr>
								<td width="100%" bgcolor="#ff0000" height="2">
								</td>
						</tr>
				</tbody>
		</table>
		<table width="660" border="0">
				<tbody>
						<tr>
								<td width="100%">
										<p class="parajust">One of the operations you can allow a user to perform on a file is to save it, either after creating it or after saving. If you want to save a file, pass the first argument of the <b>CFileDialog</b> constructor as <b>FALSE</b>.</p>
								</td>
						</tr>
				</tbody>
		</table>
		<table cellspacing="0" cellpadding="0" width="660" border="0">
				<tbody>
						<tr>
								<td width="100%">
										<p class="paratitle">File Opening</p>
								</td>
						</tr>
						<tr>
								<td width="100%" bgcolor="#ff0000" height="2">
								</td>
						</tr>
				</tbody>
		</table>
		<table width="660" border="0">
				<tbody>
						<tr>
								<td width="100%">
										<p class="parajust">As opposed to saing a file, you can allow the user to open one. To create a File Dialog box that the user can use to open a file, pass the first argument of the <b>CFileDialog</b> constructor as <b>TRUE</b>.</p>
								</td>
						</tr>
				</tbody>
		</table>
		<table cellspacing="0" cellpadding="0" width="660" border="0">
				<tbody>
						<tr>
								<td width="100%">
										<p class="paratitle">File Dialog Properties</p>
								</td>
						</tr>
						<tr>
								<td width="100%" bgcolor="#ff0000" height="2">
								</td>
						</tr>
				</tbody>
		</table>
		<p class="parajust">The only member variable that the File Dialog has is called <b>m_ofn</b>. It is an object derived from a Win32 class (or structure) called <b>OPENFILENAME</b>. In fact, the MFC doesn't do miracles than the Win32 would. The MFC only provides some type of "translation" of the Win32 library on this issue. 
</p>
		<p class="parajust">The <b>OPENFILENAME</b> class is defined as follows: </p>
		<pre>typedef struct tagOFN { 
  DWORD         lStructSize; 
  HWND          hwndOwner; 
  HINSTANCE     hInstance; 
  LPCTSTR       lpstrFilter; 
  LPTSTR        lpstrCustomFilter; 
  DWORD         nMaxCustFilter; 
  DWORD         nFilterIndex; 
  LPTSTR        lpstrFile; 
  DWORD         nMaxFile; 
  LPTSTR        lpstrFileTitle; 
  DWORD         nMaxFileTitle; 
  LPCTSTR       lpstrInitialDir; 
  LPCTSTR       lpstrTitle; 
  DWORD         Flags; 
  WORD          nFileOffset; 
  WORD          nFileExtension; 
  LPCTSTR       lpstrDefExt; 
  LPARAM        lCustData; 
  LPOFNHOOKPROC lpfnHook; 
  LPCTSTR       lpTemplateName; 
#if (_WIN32_WINNT &gt;= 0x0500)
  void *        pvReserved;
  DWORD         dwReserved;
  DWORD         FlagsEx;
#endif // (_WIN32_WINNT &gt;= 0x0500)
} OPENFILENAME, *LPOPENFILENAME;<br /><table cellspacing="0" cellpadding="0" width="660" border="0"><tbody><tr><td width="100%"><p class="paratitle">File Dialog Operations</p></td></tr><tr><td width="100%" bgcolor="#ff0000" height="2"></td></tr></tbody></table><table width="660" border="0"><tbody><tr><td width="100%"><p class="parajust">In order to use a File Dialog box, the user must open it. This can be done by calling the <b>CFileDialog::DoModal()</b> method. </p><p class="parajust">When it comes up, the File Dialog displays two buttons regularly available on a normal dialog box. If the file is being opened, the buttons are Open and Cancel. If the user is saving a file, the buttons are Save and Cancel. After using the dialog box, either to open or to save a file, the user can click Open/Save or Cancel. If the user clicks Open or Save, the dialog box is (partly) equipped to validate the operation. For example, if the user clicks Open, the dialog box is configured to make sure that the user selected a file. If the user clicks Save, he or she should have given a name to the file. To dismiss the dialog box without continuing with the intended operation, the user can click Cancel. If the user clicks Cancel, the dialog box throws a <b>CommDlgExtendedError</b>. This error allows you to find out what happens: maybe the user didn't click Cancel, maybe the dialog box was not even created, maybe this, maybe that. This error allows you to diagnose a potential problem and deal with it. If you don't want to go through this, you can just cancel the operation yourself. </p><p class="parajust">After displaying the File Dialog box, in other to effectively perform an intended operation, the user must provide a name for the file. To find out what name the user provided, you can call the <b>CFileDialog::GetFileName()</b> method. Its syntax is: </p></td></tr></tbody></table><pre>CString GetFileName( ) const;</pre><table width="660" border="0"><tbody><tr><td width="100%"><p class="parajust">A file name is indeed made of various sections. In the strict sense, the file name is a long string that starts from its drive all the way to its "normal" name. This is also referred to as its path. To get the path of the being dealt with, you can call the <b>CFileDialog::GetPathName()</b> method. Its syntax is: </p></td></tr></tbody></table><pre>CString GetPathName( ) const;</pre><table width="660" border="0"><tbody><tr><td width="100%"><p class="parajust">In the traditional sense, the name of a file ends with 2, 3, or 4 characters known as its extension. For example, the extension of a Microsoft Word file is doc. The extension of a Perl file is pl. After the user has dealt with a file or while dealing with it, to find out the extension of that file, you can call the <b>CFileDialog::GetFileExt()</b> method. Its syntax is: </p></td></tr></tbody></table><pre>CString GetFileExt( ) const;</pre><table cellspacing="0" cellpadding="0" width="660" border="0"><tbody><tr><td width="100%"><p class="ExoTitle"><img height="20" src="http://www.functionx.com/images/practical1.gif" width="21" border="0" /> Practical Learning: Using the File Dialog Box</p></td></tr><tr><td width="100%" bgcolor="#ff00ff" height="1"></td></tr></tbody></table><table width="698" border="0"><tbody><tr><td width="690"><ol><li>If you want to follow with this example, create a New Dialog Based application named FileProc </li><li>Design the dialog box as follows:<br />  <table width="618" border="0"><tbody><tr><td align="middle" width="610"><img height="229" src="http://www.functionx.com/visualc/controls/images/fileproc1.gif" width="370" border="0" /></td></tr><tr><td width="610"><table bordercolor="#c0c0c0" width="541" border="1"><tbody><tr><td width="94" colspan="2">Control</td><td width="128">ID</td><td width="112">Caption</td><td width="144">Additional Properties</td></tr><tr><td>Static Text</td><td><img height="22" alt="Static Text" src="http://www.functionx.com/visualc/controls/controls/statictext1.gif" width="25" border="0" /></td><td></td><td>Make: <p></p></td><td> </td></tr><tr><td width="95">Edit Box</td><td width="28" height="23" src="../controls/controls/slider1.gif" 0?=""><img height="23" src="http://www.functionx.com/visualc/controls/controls/editbox1.gif" width="26" border="0" /></td><td width="128">IDC_MAKE</td><td width="112"> </td><td width="144"> </td></tr><tr><td width="95">Static Text</td><td width="28"><img height="22" src="http://www.functionx.com/visualc/controls/controls/statictext1.gif" width="25" border="0" /></td><td width="128"> </td><td width="112">Model:</td><td width="144"> </td></tr><tr><td width="95">Edit Box</td><td width="28"><img height="23" src="http://www.functionx.com/visualc/controls/controls/editbox1.gif" width="26" border="0" /></td><td width="128">IDC_MODEL</td><td width="112"> </td><td width="144"> </td></tr><tr><td width="95">Static Text</td><td width="28"><img height="22" src="http://www.functionx.com/visualc/controls/controls/statictext1.gif" width="25" border="0" /></td><td width="128"> </td><td width="112">Year:</td><td width="144"> </td></tr><tr><td width="95">Edit Box</td><td width="28"><img height="23" src="http://www.functionx.com/visualc/controls/controls/editbox1.gif" width="26" border="0" /></td><td width="128">IDC_YEAR</td><td width="112"> </td><td width="144">Align Text: Right</td></tr><tr><td width="95">Static Text</td><td width="28"><img height="22" src="http://www.functionx.com/visualc/controls/controls/statictext1.gif" width="25" border="0" /></td><td width="128"> </td><td width="112">Mileage:</td><td width="144"> </td></tr><tr><td width="95">Edit Box</td><td width="28"><img height="23" src="http://www.functionx.com/visualc/controls/controls/editbox1.gif" width="26" border="0" /></td><td width="128">IDC_MILEAGE</td><td width="112"> </td><td width="144">Align Text: Right</td></tr><tr><td width="95">Static Text</td><td width="28"><img height="22" src="http://www.functionx.com/visualc/controls/controls/statictext1.gif" width="25" border="0" /></td><td width="128"> </td><td width="112">Owner</td><td width="144"> </td></tr><tr><td width="95">Edit Box</td><td width="28"><img height="23" src="http://www.functionx.com/visualc/controls/controls/editbox1.gif" width="26" border="0" /></td><td width="128">IDC_OWNER</td><td width="112"> </td><td width="144"> </td></tr><tr><td width="95">Button</td><td width="28"><img height="23" src="http://www.functionx.com/visualc/controls/controls/button1.gif" width="25" border="0" /></td><td width="128">IDC_SAVE_BTN</td><td width="112">&amp;Save</td><td width="144"> </td></tr><tr><td width="95">Button</td><td width="28"><img height="23" src="http://www.functionx.com/visualc/controls/controls/button1.gif" width="25" border="0" /></td><td width="128">IDC_OPEN_BTN</td><td width="112">&amp;Open</td><td width="144"> </td></tr><tr><td width="95">Button</td><td width="28"><img height="23" src="http://www.functionx.com/visualc/controls/controls/button1.gif" width="25" border="0" /></td><td width="128">IDCANCEL</td><td width="112">Close</td><td width="144"> </td></tr></tbody></table></td></tr></tbody></table></li><li>Using either the ClassWizard or the Add Member Variable Wizard, add the following member variables to the controls:<br />  <table bordercolor="#c0c0c0" width="270" border="1"><tbody><tr><td width="107">Identifier</td><td align="middle" width="141" colspan="2">Value Variable</td></tr><tr><td width="107">IDC_MAKE</td><td width="67">CString</td><td width="74">m_Make</td></tr><tr><td width="107">IDC_MODEL</td><td width="67">CString</td><td width="74">m_Model</td></tr><tr><td width="107">IDC_YEAR</td><td width="67">CString</td><td width="74">m_Year</td></tr><tr><td width="107">IDC_MILEAGE</td><td width="67">CString</td><td width="74">m_Doors</td></tr><tr><td width="107">IDC_OWNER</td><td width="67">CString</td><td width="74">m_Owner</td></tr></tbody></table></li><li>Generate a BN_CLICKED message for the IDC_SAVE_BTN button and, after accepting the suggested name of the event, implement it as follows:<br />  <table width="645" border="0"><tbody><tr><td width="637"><pre>void CFileProc1Dlg::OnSaveBtn() 
{
	// TODO: Add your control notification handler code here
	this-&gt;UpdateData();

	CFile f;

	char strFilter[] = { "BCR Files (*.bcr)|*.bcr|All Files (*.*)|*.*||" };

	CFileDialog FileDlg(FALSE, ".bcr", NULL, 0, strFilter);

	if( FileDlg.DoModal() == IDOK )
	{
	f.Open(FileDlg.GetFileName(), CFile::modeCreate | CFile::modeWrite);
		CArchive ar(&amp;f, CArchive::store);

		ar &lt;&lt; m_Make &lt;&lt; m_Model &lt;&lt; m_Year &lt;&lt; m_Mileage &lt;&lt; m_Owner;
		ar.Close();
	}
	else
		return;

	f.Close();
}</pre></td></tr></tbody></table></li><li>Generate a BN_CLICKED message for the IDC_OPEN_BTN button and, after accepting the suggested name of the event, implement it as follows:<br />  <table width="642" border="0"><tbody><tr><td width="634"><pre>void CFileProc1Dlg::OnOpenBtn() 
{
	// TODO: Add your control notification handler code here
	this-&gt;UpdateData();

	CFile f;

	char strFilter[] = { "BCR Files (*.bcr)|*.bcr|All Files (*.*)|*.*||" };

	CFileDialog FileDlg(TRUE, ".bcr", NULL, 0, strFilter);

	if( FileDlg.DoModal() == IDOK )
	{
		if( f.Open(FileDlg.GetFileName(), CFile::modeRead) == FALSE )
			return;
		CArchive ar(&amp;f, CArchive::load);

		ar &gt;&gt; m_Make &gt;&gt; m_Model &gt;&gt; m_Year &gt;&gt; m_Mileage &gt;&gt; m_Owner;
		ar.Close();
	}
	else
		return;

	f.Close();
	this-&gt;UpdateData(FALSE);
}</pre></td></tr></tbody></table></li><li><p>Test the application </p></li><li><p>Create and save a file<br /> <br /><img height="229" src="http://www.functionx.com/visualc/controls/images/fileproc2.gif" width="369" border="0" /></p></li><li><p>Close the dialog box </p></li><li><p>Test the application again </p></li><li><p>Open the previously created file </p></li></ol></td></tr></tbody></table><br />from: <a href="http://www.functionx.com/visualc/controls/filedialog.htm">http://www.functionx.com/visualc/controls/filedialog.htm</a></pre>
<img src ="http://www.blogjava.net/weidagang2046/aggbug/72681.html" width = "1" height = "1" /><br><br><div align=right><a style="text-decoration:none;" href="http://www.blogjava.net/weidagang2046/" target="_blank">weidagang2046</a> 2006-09-28 22:03 <a href="http://www.blogjava.net/weidagang2046/articles/72681.html#Feedback" target="_blank" style="text-decoration:none;">发表评论</a></div>]]></description></item><item><title>Winsock开发网络通信程序的经典入门</title><link>http://www.blogjava.net/weidagang2046/articles/72544.html</link><dc:creator>weidagang2046</dc:creator><author>weidagang2046</author><pubDate>Thu, 28 Sep 2006 05:17:00 GMT</pubDate><guid>http://www.blogjava.net/weidagang2046/articles/72544.html</guid><wfw:comment>http://www.blogjava.net/weidagang2046/comments/72544.html</wfw:comment><comments>http://www.blogjava.net/weidagang2046/articles/72544.html#Feedback</comments><slash:comments>0</slash:comments><wfw:commentRss>http://www.blogjava.net/weidagang2046/comments/commentRss/72544.html</wfw:commentRss><trackback:ping>http://www.blogjava.net/weidagang2046/services/trackbacks/72544.html</trackback:ping><description><![CDATA[对于许多初学者来说，网络通信程序的开发，普遍的一个现象就是觉得难以入手。许多概念，诸如：同步(Sync)/异步(Async)，阻塞(Block)/非阻塞(Unblock)等，初学者往往迷惑不清，只知其所以而不知起所以然。 <br /><br />　　同步方式指的是发送方不等接收方响应，便接着发下个数据包的通信方式；而异步指发送方发出数据后，等收到接收方发回的响应，才发下一个数据包的通信方式。<br /><br />　　阻塞套接字是指执行此套接字的网络调用时，直到成功才返回，否则一直阻塞在此网络调用上，比如调用recv()函数读取网络缓冲区中的数据，如果没有数据到达，将一直挂在recv()这个函数调用上，直到读到一些数据，此函数调用才返回；而非阻塞套接字是指执行此套接字的网络调用时，不管是否执行成功，都立即返回。比如调用recv()函数读取网络缓冲区中数据，不管是否读到数据都立即返回，而不会一直挂在此函数调用上。在实际Windows网络通信软件开发中，异步非阻塞套接字是用的最多的。平常所说的C/S（客户端/服务器）结构的软件就是异步非阻塞模式的。<br /><br />　　对于这些概念，初学者的理解也许只能似是而非，我将用一个最简单的例子说明异步非阻塞Socket的基本原理和工作机制。目的是让初学者不仅对Socket异步非阻塞的概念有个非常透彻的理解，而且也给他们提供一个用Socket开发网络通信应用程序的快速入门方法。操作系统是Windows 98（或NT4.0），开发工具是Visual C++6.0。<br /><br />　　MFC提供了一个异步类CAsyncSocket，它封装了异步、非阻塞Socket的基本功能，用它做常用的网络通信软件很方便。但它屏蔽了Socket的异步、非阻塞等概念，开发人员无需了解异步、非阻塞Socket的原理和工作机制。因此，建议初学者学习编网络通信程序时，暂且不要用MFC提供的类，而先用Winsock2 API，这样有助于对异步、非阻塞Socket编程机制的理解。<br /><br />　　为了简单起见，服务器端和客户端的应用程序均是基于MFC的标准对话框，网络通信部分基于Winsock2 API实现。<br /><br />　　先做服务器端应用程序。<br /><br />　　用MFC向导做一个基于对话框的应用程序SocketSever，注意第三步中不要选上Windwos Sockets选项。在做好工程后，创建一个SeverSock，将它设置为异步非阻塞模式，并为它注册各种网络异步事件，然后与自定义的网络异步事件联系上，最后还要将它设置为监听模式。在自定义的网络异步事件的回调函数中，你可以得到各种网络异步事件，根据它们的类型，做不同的处理。下面将详细介绍如何编写相关代码。<br /><br />　　在SocketSeverDlg.h文件的类定义之前增加如下定义： #define NETWORK_EVENT WM_USER+166 file://定义网络事件<br /><br /><table bordercolor="#cccccc" width="90%" align="center" bgcolor="#e3e3e3" border="1"><tbody><tr><td>SOCKET ServerSock; file://服务器端Socket</td></tr></tbody></table><br />　　在类定义中增加如下定义：<br /><br /><table bordercolor="#cccccc" width="90%" align="center" bgcolor="#e3e3e3" border="1"><tbody><tr><td>class CSocketSeverDlg : CDialog<br />{<br />　public:<br />　　SOCKET ClientSock[CLNT_MAX_NUM]; file://存储与客户端通信的Socket的数组<br /><br />　　/*各种网络异步事件的处理函数*/<br />　　void OnClose(SOCKET CurSock); file://对端Socket断开<br />　　void OnSend(SOCKET CurSock); file://发送网络数据包<br />　　void OnReceive(SOCKET CurSock); file://网络数据包到达<br />　　void OnAccept(SOCKET CurSock); file://客户端连接请求<br /><br />　　BOOL InitNetwork(); file://初始化网络函数<br />　　void OnNetEvent(WPARAM wParam, LPARAM lParam); file://异步事件回调函数<br />　　…<br />};</td></tr></tbody></table><br />　　在SocketSeverDlg.cpp文件中增加消息映射，其中OnNetEvent是异步事件回调函数名：<br /><br /><table bordercolor="#cccccc" width="90%" align="center" bgcolor="#e3e3e3" border="1"><tbody><tr><td>ON_MESSAGE(NETWORK_EVENT,OnNetEvent)</td></tr></tbody></table><br />　　定义初始化网络函数，在SocketSeverDlg.cpp文件的OnInitDialog（）中调此函数即可。<br /><br /><table bordercolor="#cccccc" width="90%" align="center" bgcolor="#e3e3e3" border="1"><tbody><tr><td>BOOL CSocketSeverDlg::InitNetwork()<br />{<br />　WSADATA wsaData;<br /><br />　//初始化TCP协议<br />　BOOL ret = WSAStartup(MAKEWORD(2,2), &amp;wsaData);<br />　if(ret != 0)<br />　{<br />　　MessageBox("初始化网络协议失败!");<br />　　return FALSE;<br />　}<br /><br />　//创建服务器端套接字<br />　ServerSock = socket(AF_INET, SOCK_STREAM, IPPROTO_TCP);<br />　if(ServerSock == INVALID_SOCKET)<br />　{<br />　　MessageBox("创建套接字失败!");<br />　　closesocket(ServerSock);<br />　　WSACleanup();<br />　　return FALSE;<br />　}<br /><br />　//绑定到本地一个端口上<br />　sockaddr_in localaddr;<br />　localaddr.sin_family = AF_INET;<br />　localaddr.sin_port = htons(8888); //端口号不要与其他应用程序冲突<br />　localaddr.sin_addr.s_addr = 0;<br />　if(bind(ServerSock ,(struct sockaddr*)&amp;localaddr,sizeof(sockaddr))= = SOCKET_ERROR)<br />　{<br />　　MessageBox("绑定地址失败!");<br />　　closesocket(ServerSock);<br />　　WSACleanup();<br />　　return FALSE;<br />　}<br /><br />　//将SeverSock设置为异步非阻塞模式，并为它注册各种网络异步事件，其中m_hWnd <br />　//为应用程序的主对话框或主窗口的句柄<br />　if(WSAAsyncSelect(ServerSock, m_hWnd, NETWORK_EVENT, FD_ACCEPT | FD_CLOSE | FD_READ | FD_WRITE) == SOCKET_ERROR)<br />　{<br />　　MessageBox("注册网络异步事件失败!");<br />　　WSACleanup();<br />　　return FALSE;<br />　}<br />　listen(ServerSock, 5); file://设置侦听模式<br />　return TRUE;<br />}</td></tr></tbody></table><br />　　下面定义网络异步事件的回调函数<br /><br /><table bordercolor="#cccccc" width="90%" align="center" bgcolor="#e3e3e3" border="1"><tbody><tr><td>void CSocketSeverDlg::OnNetEvent(WPARAM wParam, LPARAM lParam)<br />{<br />　//调用Winsock API函数，得到网络事件类型<br />　int iEvent = WSAGETSELECTEVENT(lParam);<br /><br />　//调用Winsock API函数，得到发生此事件的客户端套接字<br />　SOCKET CurSock= (SOCKET)wParam;<br /><br />　switch(iEvent)<br />　{<br />　　case FD_ACCEPT: //客户端连接请求事件<br />　　　OnAccept(CurSock);<br />　　　break;<br />　　case FD_CLOSE: //客户端断开事件:<br />　　　OnClose(CurSock);<br />　　　break;<br />　　case FD_READ: //网络数据包到达事件<br />　　　OnReceive(CurSock);<br />　　　break;<br />　　case FD_WRITE: //发送网络数据事件<br />　　　OnSend(CurSock);<br />　　　break;<br />　　default: break;<br />　}<br />}</td></tr></tbody></table><br />　　以下是发生在相应Socket上的各种网络异步事件的处理函数，其中OnAccept传进来的参数是服务器端创建的套接字，OnClose()、OnReceive()和OnSend()传进来的参数均是服务器端在接受客户端连接时新创建的用与此客户端通信的Socket。<br /><br /><table bordercolor="#cccccc" width="90%" align="center" bgcolor="#e3e3e3" border="1"><tbody><tr><td>void CSocketSeverDlg::OnAccept(SOCKET CurSock)<br />{<br />　//接受连接请求，并保存与发起连接请求的客户端进行通信Socket<br />　//为新的socket注册异步事件，注意没有Accept事件<br />}<br /><br />void CSocketSeverDlg::OnClose(SOCET CurSock)<br />{<br />　//结束与相应的客户端的通信，释放相应资源<br />}<br /><br />void CSocketSeverDlg::OnSend(SOCET CurSock)<br />{<br />　//在给客户端发数据时做相关预处理<br />}<br /><br />void CSocketSeverDlg::OnReceive(SOCET CurSock)<br />{<br />　//读出网络缓冲区中的数据包<br />} </td></tr></tbody></table><br />　　用同样的方法建立一个客户端应用程序。初始化网络部分，不需要将套接字设置为监听模式。注册异步事件时，没有FD_ACCEPT，但增加了FD_CONNECT事件，因此没有OnAccept()函数，但增加了OnConnect()函数。向服务器发出连接请求时，使用connect()函数，连接成功后，会响应到OnConnect()函数中。下面是OnConnect()函数的定义，传进来的参数是客户端Socket和服务器端发回来的连接是否成功的标志。<br /><br /><table bordercolor="#cccccc" width="90%" align="center" bgcolor="#e3e3e3" border="1"><tbody><tr><td>void CSocketClntDlg::OnConnect(SOCKET CurSock, int error)<br />{<br />　if(0 = = error)<br />　{<br />　　if(CurSock = = ClntSock)<br />　　　MessageBox("连接服务器成功!");<br />　}<br />}</td></tr></tbody></table><br />　　·定义OnReceive()函数，处理网络数据到达事件;<br /><br />　　·定义OnSend()函数，处理发送网络数据事件;<br /><br />　　·定义OnClose()函数，处理服务器的关闭事件。<br /><br />　　以上就是用基于Windows消息机制的异步I/O模型做服务器和客户端应用程序的基本方法。另外还可以用事件模型、重叠模型或完成端口模型，读者可以参考有关书籍。<br /><br />　　在实现了上面的例子后，你将对Winsock编网络通信程序的机制有了一定的了解。接下来你可以进行更精彩的编程, 不仅可以在网上传输普通数据，而且还以传输语音、视频数据，你还可以自己做一个网络资源共享的服务器软件，和你的同学在实验室的局域网里可以共同分享你的成果。 <br /><br />from: <a href="http://soft.yesky.com/165/2284165.shtml">http://soft.yesky.com/165/2284165.shtml</a><img src ="http://www.blogjava.net/weidagang2046/aggbug/72544.html" width = "1" height = "1" /><br><br><div align=right><a style="text-decoration:none;" href="http://www.blogjava.net/weidagang2046/" target="_blank">weidagang2046</a> 2006-09-28 13:17 <a href="http://www.blogjava.net/weidagang2046/articles/72544.html#Feedback" target="_blank" style="text-decoration:none;">发表评论</a></div>]]></description></item><item><title>孙鑫VC++讲座笔记-(6)菜单编程</title><link>http://www.blogjava.net/weidagang2046/articles/72142.html</link><dc:creator>weidagang2046</dc:creator><author>weidagang2046</author><pubDate>Tue, 26 Sep 2006 15:34:00 GMT</pubDate><guid>http://www.blogjava.net/weidagang2046/articles/72142.html</guid><wfw:comment>http://www.blogjava.net/weidagang2046/comments/72142.html</wfw:comment><comments>http://www.blogjava.net/weidagang2046/articles/72142.html#Feedback</comments><slash:comments>0</slash:comments><wfw:commentRss>http://www.blogjava.net/weidagang2046/comments/commentRss/72142.html</wfw:commentRss><trackback:ping>http://www.blogjava.net/weidagang2046/services/trackbacks/72142.html</trackback:ping><description><![CDATA[
		<p>1,弹出菜单（Pop-up)是不能用来作命令响应的。</p>
		<p>2，MFC中菜单项消息如果利用ClassWizard来对菜单项消息分别在上述四个类中进行响应，则菜单消息传递顺序：View类--Doc类--CMainFrame类--App类。菜单消息一旦在其中一个类中响应则不再在其它类中查找响应函数。<br />具体：<br />当点击一个菜单项的时候，最先接受到菜单项消息的是CMainFrame框架类，CMainFrame框架类将会把菜单项消息交给它的子窗口View类，由View类首先进行处理；如果View类检测到没对该菜单项消息做响应，则View类把菜单项消息交由文档类Doc类进行处理；如果Doc类检测到Doc类中也没对该菜单项消息做响应，则Doc类又把该菜单项消息交还给View类，由View类再交还给CMainFrame类处理。如果CMainFrame类查看到CMainFrame类中也没对该消息做响应，则最终交给App类进行处理。</p>
		<p>3，消息的分类：标准消息，命令消息，通告消息。<br />[标准消息]：除WM_COMMAND之外，所有以WM_开头的消息。<br />[命令消息]：来自菜单、加速键或工具栏按钮的消息。这类消息都以WM_COMMAND呈现。<br />在MFC中，通过菜单项的标识（ID）来区分不同的命令消息；在SDK中，通过消息的wParam参数识别。<br />[通告消息]：由控件产生的消息，例如，按钮的单击，列表框的选择等均产生此类消息，为的是向其父窗口（通常是对话框）通知事件的发生。这类消息也是以WM_COMMAND形式呈现。<br />说明：<br />1）从CWnd派生的类，都可以接收到[标准消息]。<br />2）从CCmdTarget派生的类，都可以接收到[命令消息]和[通告消息]。</p>
		<p>4，一个菜单拦可以有若干个子菜单，一个子菜单又可以有若干个菜单项等。对菜单栏的子菜单由左至右建立从0开始的索引。对特定子菜单的菜单项由上至下建立了从0开始的索引。访问子菜单和菜单项均可以通过其索引或标识（如果有标识的话）进行。<br />相关重要函数：<br />CMenu* GetMenu( ) ;//CWnd::GetMenu得到窗口菜单栏对象指针。<br />CMenu* GetSubMenu(  ) ;//CMenu::GetSubMenu获得指向弹出菜单对象指针<br />UINT CheckMenuItem( );//CMenu::CheckMenuItem Adds check marks to or removes check marks from menu items in the pop-up menu. <br />BOOL SetDefaultItem();//CMenu::SetDefaultItem Sets the default menu item for the specified menu.<br />BOOL SetMenuItemBitmaps( );//CMenu::SetMenuItemBitmaps 设置位图标题菜单。<br />UINT EnableMenuItem();//CMenu::EnableMenuItem使菜单项有效，无效，或变灰。<br />BOOL SetMenu( CMenu* pMenu );//CWnd::SetMenu在当前窗口上设置新菜单或移除菜单。<br />HMENU Detach( );//CMenu::Detach Detaches a Windows menu from a CMenu object and returns the handle. <br />说明：<br />1）在计算子菜单菜单项的索引的时候，分隔栏符也算索引的。<br />2）int GetSystemMetrics()获取系统信息度量。可以用它来获取菜单标题的尺寸从而设置位图标题菜单中位图的大小。<br />3）在MFC中MFC为我们提供了一套命令更新机制，所有菜单项的更新都是由这套机制来完成的。所以要想利用CMenu::EnableMenuItem来自己控制菜单使用或不使用变灰等，必须要在CMainFrame的构造函数中将变量m_bAutoMenuEnable设置为FALSE。<br />4）Create a CMenu object on the stack frame as a local, then call CMenu’s member functions to manipulate the new menu as needed. Next, call CWnd::SetMenu to set the menu to a window, followed immediately by a call to the CMenu object’s Detach member function. The CWnd::SetMenu member function sets the window’s menu to the new menu, causes the window to be redrawn to reflect the menu change, and also passes ownership of the menu to the window. The call to Detach detaches the HMENU from the CMenu object, so that when the local CMenu variable passes out of scope, the CMenu object destructor does not attempt to destroy a menu it no longer owns. The menu itself is automatically destroyed when the window is destroyed.<br />5）You can use the LoadMenuIndirect member function to create a menu from a template in memory, but a menu created from a resource by a call to LoadMenu is more easily maintained, and the menu resource itself can be created and modified by the menu editor.<br />6）EXAMPLE：<br />CMenu menu;//定义为局部对象<br />menu.LoadMenu(IDR_MAINFRAME);<br />SetMenu(&amp;menu);<br />menu.Detach();// 这里menu对象作为一个局部对象。使用Detach()从menu对象中分离窗口菜单句柄，从而当menu对象析构的时候窗口菜单资源不随之销毁。</p>
		<p>5，命令更新机制：<br />菜单项状态的维护是依赖于CN_UPDATE_COMMAND_UI消息，谁捕获CN_UPDATE_COMMAND_UI消息，MFC就在其中创建一个CCmdUI对象。<br />在后台操作系统发出WM_INITMENUPOPUP消息，然后由MFC的基类如CFrameWnd接管并创建一个CCmdUI对象和第一个菜单项相关联，调用对象成员函数DoUpdate()（注：这个函数在MSDN中没有找到说明）发出CN_UPDATE_COMMAND_UI消息，这条消息带有指向CCmdUI对象的指针。此后同一个CCmdUI对象又设置为与第二个菜单项相关联，这样顺序进行，直到完成所有菜单项。<br />更新命令UI处理程序仅应用于弹出式菜单项上的项目，不能应用于永久显示的顶级菜单项目。<br />说明：<br />1）可以手工或用ClassWizard来给菜单项添加UPDATE_COMMAND_UI消息响应，利用响应函数中传进来的CCmdUI对象指针可完成设置菜单项可使用，不可使用，变灰，设置标记菜单等操作。</p>
		<p>6，如果要想让工具栏上的某个图标与菜单项的某个菜单相关联，只需要将图标的ID设置为该菜单项的ID。<br />工具栏图标的索引记数顺序是：从做至右从0开始，分隔符也算索引号。</p>
		<p>7,利用向项目中添加VC的POPMENU控件：Project-&gt;Add to Project-&gt;Components and Controls..<br />系统增加的内容：A，一个菜单资源；B，在派生View类中增加了OnContextMenu()函数<br />说明：<br />1）CWnd::OnContextMenu Called by the framework when the user has clicked the right mouse button (right clicked) in the window. You can process this message by displaying a context menu using the TrackPopupMenu.<br />2）BOOL TrackPopupMenu( UINT nFlags, int x, int y, CWnd* pWnd, LPCRECT lpRect = NULL );<br />//CMenu::TrackPopupMenu Displays a floating pop-up menu at the specified location and tracks the selection of items on the pop-up menu. A floating pop-up menu can appear anywhere on the screen.</p>
		<p>8，利用调用TrackPopupMenu函数，手工添加弹出菜单：<br />1）用资源管理器添加一个菜单资源<br />2）在鼠标右键消息响应函数中，加载菜单资源，并获得要显示的子菜单指针，并用该指针调用TrackPopupMenu函数便完成任务（但要注意：鼠标响应函数传进来的坐标是客户区坐标，而TrackPopupMenu函数中使用的是屏幕坐标，在调用TrackPopupMenu前要调用ClientToScreen客户区坐标到屏幕坐标的转换）<br />事例代码：<br />CMenu menu;<br />menu.LoadMenu(IDR_MENU1);<br />CMenu *pPopup=menu.GetSubMenu(0);<br />ClientToScreen(&amp;point);<br />pPopup-&gt;TrackPopupMenu(TPM_LEFTALIGN | TPM_RIGHTBUTTON, point.x, point.y,this);<br />说明：<br />CWnd::ClientToScreen(..);//将一个坐标点或一个矩形区域坐标转换成屏幕坐标。<br />CMenu::TrackPopupMenu(..);//在指定位置以指定的方式显示弹出菜单。<br />CWnd::ScreenToClient(..);<br />//Converts the screen coordinates of a given point or rectangle on the display to client coordinates. </p>
		<p>9,当弹出菜单属于框架窗口的时候（可在TrackPopupMenu函数参数中设置），弹出菜单上的消息，在路由的时候，仍然遵循View-DOC-MainFrame-APP的响应顺序。</p>
		<p>10，动态菜单编程：<br />所有的资源对象都有一个数据成员保存了资源的句柄。<br />CMenu::AppendMenu //Appends a new item to the end of a menu.<br />CMenu::CreatePopupMenu //Creates an empty pop-up menu and attaches it to a CMenu object.<br />CMenu::InsertMenu <br />//Inserts a new menu item at the position specified by nPosition and moves other items down the menu. <br />CMenu::GetSubMenu //Retrieves a pointer to a pop-up menu.<br />CWnd::GetMenu //Retrieves a pointer to the menu for this window.<br />CMenu::DeleteMenu //Deletes an item from the menu. </p>
		<p>11，手动给动态菜单项添加响应函数：<br />在Resource.h中可以添加资源的ID<br />在头文件中写消息函数原型<br />在代码文件中添加消息映射和添加消息响应函数<br />说明：<br />可以先创建一个临时的菜单项，设置它的ID和动态菜单项的一致，然后对它用向导进行消息响应，然后删除临时菜单。<br />再在代码文件中把消息映射放到宏外（注意一定要放到宏外面，因为CLASSWIZARD发现菜单删除了，同时要把其宏对里的消息映射也删除掉的）</p>
		<p>12，CWnd::DrawMenuBar <br />//Redraws the menu bar. If a menu bar is changed after Windows has created the window, call this function to draw the changed menu bar</p>
		<p>CWnd::GetParent //get a pointer to a child window’s parent window (if any). <br />CWnd::Invalidate //注意其参数的作用</p>
		<p>13，集合类：<br />CStringArray，CStringArray，CDWordArray，CPtrArray，CStringArray，CUIntArray，CWordArray<br />其中成员函数：<br />CArray::GetAt <br />CArray::Add</p>
		<p>14,命令消息是到OnCommand函数的时候完成路由的。<br />由于CWnd::OnCommand 是个虚函数，可以在框架类中重写OnCommand函数，从而可以截获菜单消息使它不再往下（VIEW类）路由。<br />例：<br />BOOL CMainFrame::OnCommand(WPARAM wParam, LPARAM lParam) <br />{<br /> // TODO: Add your specialized code here and/or call the base class<br /> int MenuCmdId=LOWORD(wParam);//取命令ID<br /> CMenu2View *pView=(CMenu2View*)GetActiveView();//获取当前VIEW类指针<br /> if(MenuCmdId&gt;=IDM_PHONE1 &amp;&amp; MenuCmdId&lt;IDM_PHONE1+pView-&gt;m_strArray.GetSize())//消息范围判断<br /> {<br />  CClientDC dc(pView);<br />  dc.TextOut(0,0,pView-&gt;m_strArray.GetAt(MenuCmdId-IDM_PHONE1));<br />  return TRUE; <br />   //函数返回，避免调用CFrameWnd::OnCommand函数，在CFrameWnd::OnCommand中截获的消息会交由VIEW类处理<br /> }<br /> return CFrameWnd::OnCommand(wParam, lParam);<br />  //调用基类OnCommand函数，在CFrameWnd::OnCommand中截获的消息会交由VIEW类处理<br />}<br /> <br />MSDN说明：<br />virtual BOOL OnCommand( WPARAM wParam, LPARAM lParam );<br />//The framework calls this member function when the user selects an item from a menu, when a child control sends a notification message, or when an accelerator keystroke is translated. <br />OnCommand processes the message map for control notification and ON_COMMAND entries, and calls the appropriate member function. <br />Override this member function in your derived class to handle the WM_COMMAND message. An override will not process the message map unless the base class OnCommand is called.</p>
		<p>15，LOWORD与HIWORD宏<br />WORD LOWORD(<br />  DWORD dwValue  // value from which low-order word is retrieved<br />);<br />WORD HIWORD(<br />  DWORD dwValue  // value from which high-order word is retrieved<br />);</p>
		<p>//The LOWORD macro retrieves the low-order word from the given 32-bit value. <br />//The HIWORD macro retrieves the high-order word from the given 32-bit value. </p>
		<p>
				<br />16，CFrameWnd::GetActiveView <br />CView* GetActiveView( ) const;//获取当前视窗口指针（单文档框架中）</p>
		<p>17，源文件是单独参与编译的。<br /><br />from: <a href="http://www.manbu.net/Lib/Class9/Sub1/1/9.asp">http://www.manbu.net/Lib/Class9/Sub1/1/9.asp</a></p>
<img src ="http://www.blogjava.net/weidagang2046/aggbug/72142.html" width = "1" height = "1" /><br><br><div align=right><a style="text-decoration:none;" href="http://www.blogjava.net/weidagang2046/" target="_blank">weidagang2046</a> 2006-09-26 23:34 <a href="http://www.blogjava.net/weidagang2046/articles/72142.html#Feedback" target="_blank" style="text-decoration:none;">发表评论</a></div>]]></description></item></channel></rss>