⭐ 欢迎来到虫虫下载站! | 📦 资源下载 📁 资源专辑 ℹ️ 关于我们
⭐ 虫虫下载站

📄 3paint.html

📁 C ++ in action
💻 HTML
📖 第 1 页 / 共 4 页
字号:
                ::ReleaseCapture ();
        }
        bool <b>HasCapture</b> () const
        {
            return ::GetCapture () == _h;
        }
        // Window visibility
        void Show (int cmdShow = SW_SHOW) 
        { 
            ::ShowWindow (_h, cmdShow); 
        }
        void Hide () 
        { 
            ::ShowWindow (_h, SW_HIDE); 
        }
        void Update () 
        { 
            ::UpdateWindow (_h); 
        }
        void Display (int cmdShow)
        {
            Show (cmdShow);
            Update ();
        }
    private:
        HWND _h;
    };
}</pre>
</td></tr></table><!-- End Code -->

<p>The typical procedure for capturing a mouse goes like this:
<ul>
<li>Capture mouse when the user presses the appropriate mouse button.
<li>While processing mouse-move messages, check if you have the capture. If so, implement dragging behavior.
<li>Release capture when the user releases the mouse button.
<li>Finish up dragging in response to <var>WM_CAPTURECHANGED</var> message.
</ul>

<p>It's important to wrap up dragging inside the <var>WM_CAPTURECHANGED</var> handler, rather than directly in response to button release. Your window may lose capture for unrelated reasons and it's important to clean up also in that case. (An example of externally initiated mouse capture change is when the system pops up an alert message.)

<p>We will apply this procedure by overriding the handlers for <var>WM_LBUTTONDOWN</var>, <var>WM_LBUTTONUP</var> and <var>WM_CAPTURECHANGED</var>. (Notice that after all this talk, we leave the implementation of <var>OnCaptureChange</var> empty. That's because we don't have any dragging wrap-up.)

<!-- Code --><table width=100% cellspacing=10><tr>    <td class=codetable>
<pre>bool TopController::OnLButtonDown 
        (int x, int y, Win::KeyState kState) throw ()
{
    _h.CaptureMouse ();
    _model.AddPoint (x, y, <b>true</b>); // starting point
    Win::UpdateCanvas canvas (_h);
    _view.MoveTo (canvas, x, y, <b>false</b>); // don't draw
    return true;
}

bool TopController::OnLButtonUp 
        (int x, int y, Win::KeyState kState) throw ()
{
    // ReleaseMouse will send WM_CAPTURECHANGED
    _h.ReleaseMouse ();
    return true;
}

bool TopController::OnCaptureChanged 
        (HWND hwndNewCapture) throw ()
{
    return true;
}
</pre>
</td></tr></table><!-- End Code -->

<p>The implementation of <var>OnLButtonDown</var> (that's Left Button Down) has some interesting points. Since the user now has the option to "lift the pen," we must be able to draw (and re-draw) non-continuous lines. The <var>MoveTo</var> method of <var>View</var> must be able to shift current position without drawing a line and the <var>Model</var> has to somehow mark the starting point of a new line. That's the meaning of the two Boolean flags in <var>OnLButtonDown</var>. We'll come back to that. Now let's examine the new implementation of the <var>OnMouseMove</var> method.

<!-- Code --><table width=100% cellspacing=10><tr>    <td class=codetable>
<pre>bool TopController::OnMouseMove 
        (int x, int y, Win::KeyState kState) throw ()
{
    Win::UpdateCanvas canvas (_h);
    if (_h.<b>HasCapture</b> ())
    {
        _model.AddPoint (x, y);
        _view.MoveTo (canvas, x, y);
    }
    else
        _view.PrintPos (canvas, x, y);
    return true;
}</pre>
</td></tr></table><!-- End Code -->

<p>Notice that we're recording the mouse position only if we have capture, otherwise we only update the display of mouse coordinates (and, since we don't have the mouse capture, we stop it when the mouse leaves the window boundaries).

<p>Repainting the window gets slightly more complicated, because of the possibility of having multiple disconnected lines. I chose to store a Boolean flag with each remembered point, and set it to <var>true</var> only for points that are starting a new line (see the <var>OnLButtonDown</var> implementation, above). We have now outgrown our simple representation of a point as a <var>std::pair</var>. In fact, to save space, I decided to store coordinates as <var>short</var>s (obviously, we'll have to rewrite this program when screen resolutions overcome the 32k pixels-per-line barrier).

<!-- Code --><table width=100% cellspacing=10><tr>    <td class=codetable>
<pre>class Model
{
public:
    class Point
    {
    public:
        Point (int x = 0, int y = 0, bool isStart = false)
            : _x (static_cast&lt;short&gt; (x)), 
              _y (static_cast&lt;short&gt; (y)), 
              _isStart (isStart)
        {} 
        int X () const { return _x; }
        int Y () const { return _y; }
        bool IsStart () const { return _isStart; }
    private:
        short _x;
        short _y;
        bool  _isStart;
    };

    typedef std::deque&lt;Point&gt;::const_iterator iter;
    ...
};</pre>
</td></tr></table><!-- End Code -->

<p>Notice the default values for all arguments of the <var>Point</var> contructor. It turns out that <var>Point</var> needs a default (no-arguments) constructor if we want to store it in a <var>deque</var>. The <var>deque</var> must be able to allocate and initialize new blocks of <var>Point</var>s when it grows internally. It does it by calling the default constructors. 

<p>The implementation of <var>View::Paint</var> doesn't really change much, except that <var>PrintPos</var> now takes the values of coordinates to be displayed (and also updates the remembered position).

<!-- Code --><table width=100% cellspacing=10><tr>    <td class=codetable>
<pre>void View::Paint (Win::Canvas &amp; canvas, 
                  Model::iter beg, 
                  Model::iter end)
{
    PrintPos (canvas, _x, _y);
    if (beg != end)
    {
        DrawLine draw (canvas, *beg);
        ++beg;
        std::for_each (beg, end, draw);
    }
}
</pre>
</td></tr></table><!-- End Code -->

<p>The relevant change is in the implementation of the <var>DrawLine</var> functor. It doesn't draw lines that lead to starting points of line segments. It just quietly moves the current position.

<!-- Code --><table width=100% cellspacing=10><tr>    <td class=codetable>
<pre>class DrawLine
{
public:
    DrawLine (Win::Canvas &amp; canvas, Model::Point const &amp; p)
        : _canvas (canvas)
    {
        _canvas.MoveTo (p.X (), p.Y ());
    }
    void operator () (Model::Point const &amp; p)
    {
        if (!p.IsStart ())
            _canvas.LineTo (p.X (), p.Y ());
        else
            _canvas.<b>MoveTo</b> (p.X (), p.Y ());
    }
private:
    Win::Canvas &amp; _canvas;
};</pre>
</td></tr></table><!-- End Code -->

<h3>Adding Colors and Frills</h3>

<p>Let's have some fun with our program. For instance, how does one draw colored lines? The <var>LineTo</var> API doesn't have any arguments that could be used to specify color. 
<p>It's the device context, or Canvas, that stores all the parameters used in drawing. We have to instruct the canvas to change the drawing color. And, like good citizens, we should change it back after we're done with our drawing.

<p>Various drawing and printing modes are conveniently grouped in GDI objects (GDI means Graphical Device Interface, in Winspeak). The one that controls the drawing of lines is called a <i>pen</i>. You change the properties of the DC by <i>selecting</i> and object into it. Again, if you're a good citizen, you deselect it afterwards, usually by selecting back the one you have displaced.
<p>This object selection mechanism is best encapsulated in various Resource Management objects. Let's start with the most common type of object, the <i>stock object</i>. Windows conveniently provides a whole bunch of most commonly used device context settings in the form of predefined objects. We'll see in a moment how well stocked the Windows storeroom is. Right now we want to have a nice encapsulator for such objects.

<!-- Code --><table width=100% cellspacing=10><tr>    <td class=codetable>
<pre>namespace Win
{
    class <b>StockObject</b>
    {
    public:
        StockObject (int type)
            : _obj (::<b>GetStockObject</b> (type))
        {}
        operator HGDIOBJ () const { return _obj; }
    protected:
        HGDIOBJ _obj;
    };

    class <b>StockObjectHolder</b>
    {
    public:
        StockObjectHolder (HDC hdc, int type)
          : _hdc (hdc)
        {
            _hObjOld = ::<b>SelectObject</b> (_hdc, StockObject (type));
        }

        ~StockObjectHolder ()
        {
            ::<b>SelectObject</b> (_hdc, _hObjOld);
        }
    private:
        HGDIOBJ  _hObjOld;
        HDC      _hdc;
    };
}</pre>
</td></tr></table><!-- End Code -->

<p>The constructor of <var>Win::StockObjectHolder</var> takes a handle to device context (which means we can pass it any <var>Canvas</var> object) and selects a particular stock object into it. The <var>SelectObject</var> API returns the previous object of that type, which we diligently remember for later. We also remember the DC, so that, in the destructor, we can restore the previous object into its proper place in that DC. 

<p>That's the Resource Management part. The various types of stock objects are identified by predefined integer ids. Given such an id, we use <var>GetStockObject</var> to retrieve the proper object from the Windows storeroom. We'll mostly use <var>Win::StockObjectHolder</var> as a base class for specific stock object holders.

<p>There are several stock pens that might be of use. For instance, there is a black pen and a white pen. We'll enclose all these in a new nemespace <var>Pen</var>, as embedded classes of a more general class <var>Pen::Holder</var>. This class, for the moment, will be otherwise empty--but that will change soon. 
<p>This kind of class embedding gives rise to a rather convenient naming convention. Not only do we have classes like <var>Pen::Holder::White</var> and <var>Pen::Holder::Black</var>, but also a more general class <var>Pen::Holder</var>, that will soon makes sense in itself. 

<!-- Code --><table width=100% cellspacing=10><tr>    <td class=codetable>
<pre>namespace Pen
{
    class Holder
    {
    public:
        class White : public Win::StockObjectHolder
        {
        public:
            White (HDC hdc): Win::StockObjectHolder (hdc, WHITE_PEN) {}
        };
    
        class Black : public Win::StockObjectHolder
        {
        public:
            Black (HDC hdc): Win::StockObjectHolder (hdc, BLACK_PEN) {}
        };
    }
}</pre>
</td></tr></table><!-- End Code -->

<p>For simplicity, I didn't nest the <var>Pen</var> namespace inside <var>Win</var>; so instead of <var>Win::Pen::Holder::Black</var>, you simply call it <var>Pen::Holder::Black</var>. If I were to design a commercial library, I'd probably nest the namespaces, to avoid naming conflicts.

<p>The way to use a stock pen is to define a holder in a given scope. For instance to draw white lines, youd write code like this:
<!-- Code --><table width=100% cellspacing=10><tr>    <td class=codetable>
<pre>{
    Pen::Holder::White <b>wp</b> (canvas);
    // do the drawing
}</pre>
</td></tr></table><!-- End Code -->

<p>The change in pen color applies to all line-drawing operations between the definition of <var>wp</var> and the end of scope, unless overridden by another pen change. End of scope always restores the pre-scope pen (automatic destructors!).
<p>Beware of a common error of defining a stock pen holder without naming it. The code:

<!-- Code --><table width=100% cellspacing=10><tr>    <td class=codetable>
<pre>{
    Pen::Holder::White (canvas);
    // do the drawing
}</pre>
</td></tr></table><!-- End Code -->
<p class="continue">is also valid, although it doesn't produce the desired result. The compiler will create an anonymous temporary object and then immediately destroy it, without waiting for the end of the scope. 

<p>If you want to use <i>real</i> colors, not just black and white, you have to create your own GDI objects. A color pen is created by calling <var>CreatePen</var> and destroyed using <var>DeleteObject</var>. You specify the color by splitting it into three components: red, green and blue. Each of them can take values between 0 and 255. It doesn't necessarily mean that your computer can display different colors for all 256x256x256 combinations. Unless you set your display mode to at least 24-bit color (True Color, in Winspeak) the system will attempt to find the closest available match. 

<!-- Code --><table width=100% cellspacing=10><tr>    <td class=codetable>
<pre>namespace Pen
{
    class Color
    {
    public:
        Color (int r, int g, int b, int style = PS_SOLID)
        {
            _hPen = ::<b>CreatePen</b> (style, 0, RGB (r, g, b));
        }
        ~Color ()
        {
            ::<b>DeleteObject</b> (_hPen);
        }
        operator HPEN () { return _hPen; }
    private:
        HPEN    _hPen;
    };
}</pre>
</td></tr></table><!-- End Code -->

⌨️ 快捷键说明

复制代码 Ctrl + C
搜索代码 Ctrl + F
全屏模式 F11
切换主题 Ctrl + Shift + D
显示快捷键 ?
增大字号 Ctrl + =
减小字号 Ctrl + -