WTL for MFC Programmers, Part II - WTL GUI BASE CLASSES

zhaozj2021-02-08  229

WTL for MFC Programmers, Part II - WTL GUI BASE CLASSES

Original: Michael Dunn [English] Translation: Orbit (Star Rails Orbit) [http://www.winmsg.com/cn/orbit.htm]

Download Demonstration Code

This chapter

The overall impression of the second part will start writing the WTL program WTL to enhance the message mapping chain from the WTL application generation wizard.

Use the entire process of the wizard to view the generated code CMessageloop's internal implementation of CFRAMEWINDOWIMPL to return to the front clock program UI status automatic update

Add control clock menu Use the UIENABLE () function message mapping chain (Message Maps), where the next stop, 1995 modified record

Introduction to the second part

Ok, now I officially start introducing WTL! In this part I'm hereby include generating some of the friendly improvements provided by a basic main window and WTL, such as an update to the UI interface (such as the selection tag on the menu) and a better message mapping mechanism. In order to better master the content of this chapter, you should install the WTL and add the header file directory of the WTL library to the VC search directory, and copy the WTL's application generated wizard to the correct location. WTL's publishing version has a document specifically how to do these settings, if you encounter difficulties, you can view these documents.

WTL overall impression

WTL's class can be roughly divided into several types:

Achieve the main frame window - Encapsulated CFrameWindowImpl, CMDIFrameWindowImpl control - CButton, packaging CListViewCtrl GDI object - CDC, CMenu special interface properties - CSplitterWindow, CUpdateUI, CDialogResize, CCustomDraw useful tools and macro - CString, CRect, BEGIN_MSG_MAP_EX

This article will inversely introduce the framework window, and will briefly explain the relevant interface characteristics and tools. Most of these interface features and tool classes are independent classes, although some are embedded, For example: CDialogResize.

Start writing WTL programs

If you don't have a WTL application generation wizard, it doesn't matter (I will introduce this guide using this guide), the code structure of the WTL program is very like ATL's program, which is different from the example of the first chapter. It is mainly to show the characteristics of WTL, and there is no practical value.

In this section we will add code on the code generated by the WTL, generate a new program, and the client area of ​​the program main window displays the current time. The code of Stdafx.h is as follows:

#define STRICT #define WIN32_LEAN_AND_MEAN #define _WTL_USE_CSTRING #include // basic ATL class #include // basic WTL class extern CAppModule _Module; // WTL CComModule derived version #include // ATL window class #include // WTL Main Framework window class #include // WTL utility class, for example: cstring #include // WTL enhancement Message macro

Atlapp.h is the first-included header file in your project, which defines the class and CAPPMODULE related to message processing, and CAPPModule is classified from CCommodule. If you plan to use the CString class, you need to manually define the _wtl_use_cstring label, because the CString class is defined in AtlMisc.h, and many headers that are included in Atlmisc.h will be used, defined. After _wtl_use_cstring, atlapp. H will declare the CSTRING class forward, and other headers know the existence of the CString class, so that the compiler is very strange. Next, define the frame window. Our SDI window is derived from CfraMewindowImpl and uses declare_frame_wnd_class when defining window classes instead of Declare_Wnd_Class used in front. At the beginning of the window defined in myWindow.h:

class CMyWindow: public CFrameWindowImpl {public: DECLARE_FRAME_WND_CLASS (_T ( "First WTL window"), IDR_MAINFRAME); BEGIN_MSG_MAP (CMyWindow) CHAIN_MSG_MAP (CFrameWindowImpl ) END_MSG_MAP ()};

Declare_frame_wnd_class has two parameters, window class names (class names can be null, ATL will generate a class name for you) and resource IDs, and WTL when creating a window with this ID load icon, menu and acceleration key table. We also have to link messages into the window of the window like message processing (such as WM_SIZE and WM_DESTROY messages) in CFraMewindowImpl.

Now let's take a look at the Winmain () function, which is almost the same as the winmain () function in the example code in the first part, just the code of creating the window portion is slightly different.

// main.cpp: #include "stdafx.h" #include "MyWindow.h" CAppModule _Module; int APIENTRY WinMain (HINSTANCE hInstance, HINSTANCE hPrevInstance, LPSTR lpCmdLine, int nCmdShow) {_Module.Init (NULL, hInstance); CMyWindow Wndmain; msg msg; // create the main window if (null == wndmain.createex ()) Return 1; // window creation failed // show the window wndmain.showwindow (ncmdshow); wNDMain.UpdateWindow (); //// Standard Win32 Message Loop While (& MSG, NULL, 0, 0)> 0) {TranslateMessage (& MSG); DispatchMessage (& MSG);} _Module.Term (); return msg.wparam;}

The parameter of the CREATEEX () function in CFraMewindowIMPL uses a commonly used default, so we don't need to specify any parameters. As mentioned earlier, CFraMewindowIMPL will handle the resource loading, you only need to use idR_mainframe as ID to define your resource (Translator Note: Mainly icon, menu and acceleration key table), you can also use the example code from this chapter. . If you run the program now, you will see the main frame window, in fact it doesn't do anything. We need to manually add some messages, so we now introduce the best time for the message mapping macro of the WTL.

WTL enhancements to message mapping

The Wi32 API will be restored to WPARAM and LPARAM data passed through message and is very easy to make mistakes. Unfortunately, ATL does not provide us with more help, we still need to restore this data from the message, of course Except for WM_COMMAND and WM_NOTIFY messages. But the emergence of WTL saved this!

WTL's enhancement message mapping macro is defined in Atlcrack.h. (This name is from the "Message Decryption Man", is the same term used with a HongOWSX.H macro) first transforms begin_msg_map to begin_msg_map_ex, with the version _ex version generates the code for "decrypt" messages.

Class CMYWINDOW: Public CFrameWindowImpl {public: begin_msg_map_ex (cmyWindow) CHAIN_MSG_MAP (CFRAMEWINDOWIMPL ) END_MSG_MAP ()};

For our clock programs, we need to handle the WM_CREATE message to set the timer, the WTL message processing uses the MSG_ as a prefix, which is the message name, such as MSG_WM_CREATE. These macros only represent the name of the message response processing, now let's add the response to the WM_CREATE message:

class CMyWindow: public CFrameWindowImpl {public: BEGIN_MSG_MAP_EX (CMyWindow) MSG_WM_CREATE (OnCreate) CHAIN_MSG_MAP (CFrameWindowImpl ) END_MSG_MAP () // OnCreate (...)?};

WTL's message response processing looks a bit like MFC, and each processing function has different prototypes depending on the parameters passing. Since we don't have a wizard to add a message response, we need to find the correct message handler. Fortunately, the VC can help us, press the F12 key on the text of the mouse light to "MSG_WM_CREATE" macro, and can come to this macro defined code. If you use this function for the first time, the VC will request a new compilation all file to establish a browse info database, after the database is created, the VC opens ATLCRACK.H and position the code to the definition position of MSG_WM_CREATE:

#define msg_wm_create (func) / if (umsg == wm_create) / {/ setmsghand (true); / lresult = (LRESULT) FUNC ((LPCReatestruct) LPARAM); / if (ismsgHandled ()) / return true; /} tag It is very important to call the actual message response function here, which tells us that the message response function has a parameter of the LPCReateStruct type, and the type of return value is LRESULT. Please note that there is no BHANDLED parameter used by the ATL's macro.

Now add an onCreate () response function for our window class:

class CMyWindow: public CFrameWindowImpl {public: BEGIN_MSG_MAP_EX (CMyWindow) MSG_WM_CREATE (OnCreate) CHAIN_MSG_MAP (CFrameWindowImpl ) END_MSG_MAP () LRESULT OnCreate (LPCREATESTRUCT lpcs) {SetTimer (1, 1000); SetMsgHandled (false); return 0 ;}};

CfraMewindowImpl is derived from the CWindow class, so it inherits all methods of all CWindow classes, such as SetTimer (). This makes the call to the window API a bit like the MFC code, just MFC uses the CWND class package these APIs.

We use the setTimer () function to create a timer, which triggers every other second (1000 milliseconds). Since we need to make CFraMewindowImpl handle the WM_CREATE message, we call SETMSGHAND (FALSE), let the message entered the base class via a CHAIN_MSG_MAP macro chain, this call replaces the BHANDLED parameter used by the ATL macro. (Even if the CFraMewindowImpl class does not need to handle the WM_CREATE message, call SetMSGHandLED (false) to make messages into the base class is a good habit, because so we don't have to remember which news requires that the base class does not require the base class, this and The code generated by the VC class wizard is similar, and the beginning or end of the message processing function of most derived classes will call the message processing function of the base class).

In order to be able to stop the timer, we also need to respond to the WM_DESTROY message, the process of adding a message response, like the front, the definition of the MSG_WM_DESTROY macro is like this:

#define MSG_WM_DESTROY (FUNC) / IF (UMSG == WM_DESTROY) / {/ setmsghand (true); / func (); / lresult = 0; / IF (ismsghand ()) / return true; /}

The onDestroy () function does not have a parameter nor return value, and CFraMewindowImpl also handles the WM_DESTROY message, so I want to call SETMSGHAND (FALSE):

class CMyWindow: public CFrameWindowImpl {public: BEGIN_MSG_MAP_EX (CMyWindow) MSG_WM_CREATE (OnCreate) MSG_WM_DESTROY (OnDestroy) CHAIN_MSG_MAP (CFrameWindowImpl ) END_MSG_MAP () void OnDestroy () {KillTimer (1); SetMsgHandled (false);}} Next, it is called once every second in response to the processing function of the WM_TIMER message. You should know how to use the F12 key, so I directly give the response function code:

class CMyWindow: public CFrameWindowImpl {public: BEGIN_MSG_MAP_EX (CMyWindow) MSG_WM_CREATE (OnCreate) MSG_WM_DESTROY (OnDestroy) MSG_WM_TIMER (OnTimer) CHAIN_MSG_MAP (CFrameWindowImpl ) END_MSG_MAP () void OnTimer (UINT uTimerID, TIMERPROC pTimerProc) {if (1 ! = utimerid) setmsghand (false); else redrawwindow ();}};

This response function is just a client area of ​​the window when triggered each timer. Finally, we have to respond to the WM_ERASEBKGND message, show the current time in the upper left corner of the window customer area.

class CMyWindow: public CFrameWindowImpl {public: BEGIN_MSG_MAP_EX (CMyWindow) MSG_WM_CREATE (OnCreate) MSG_WM_DESTROY (OnDestroy) MSG_WM_TIMER (OnTimer) MSG_WM_ERASEBKGND (OnEraseBkgnd) CHAIN_MSG_MAP (CFrameWindowImpl ) END_MSG_MAP () LRESULT OnEraseBkgnd (HDC hdc) {CDCHandle dc (HDC); CRECT RC; SYSTEMTIME ST; CSTRING Stime; // Get Our Window's Client Area. getClientRect (RC); // Build The String to show in the window. getlocaltime (& st); stime.format (_t ("the Time IS% D:% 02D:% 02D "), St.WHOUR, ST.WMINUTE, ST.WSECOND; // SET UP The DC AND DRAW The text. dc.savedc (); dc.setbkcolor (RGB (255, 153 , 0); DC.SettextColor (RGB (0, 0)); DC.ExtTextout (0, 0, ETO_OPAQUE, RC, STIME, STIME.GETLENGTH (), NULL); // RESTORE THE DC. Dc.restoredc (-1); RETURN 1; // We ERASED THE BACKGROUND (EXTTEXTOUT DID IT)}; this message handler not only uses CRECT and CSTRING classes, but also uses a GDI package CDCHANDLE. For the CString class, I want to say that it is equivalent to the CString class with the MFC. I will introduce these packaging classes in the following article. Now you only need to know that cdchandle is a simple package for HDC, how to use the MFC CDC The class is similar, just the instance of CDCHANDLE, does not destroy the device context it operates after the scope.

All work is complete, now look at what our window looks like:

The WM_COMMAND response menu message is also used in the example code. I don't introduce here, but you can view the example code to see how the WTL's command_id_handler_ex macro works.

What is obtained from the WTL application

The WTL release version comes with a great application generation wizard, let us take a look at what it is for example as an SDI application.

The entire process of using the wizard

In the IDE environment of the VC, click the File | New menu, select ATL / WTL AppWizard from the list, we have to override the clock, so use WTLClock as the name of the project:

On the next page, you can choose the type of item, SDI, MDI or dialog-based applications, of course, there are other options, set these options as shown below, then click "Next": In the last page you can choose whether With Toolbar, Rebar, and Status Bar, cancel these options in order to simply, click "End".

View the generated code

After the wizard is completed, there are three classes in the generated code: CMAINFRAME, CABOUTDLG, and CWTLCLOCKVIEW, you can guess these classes from the name. Although there is also a view class, it is just a simple window class that is derived from CWindowImpl, and there is no document / view structure like MFC.

There is also a _twinmain () function, which initializes the COM environment, public controls, and _Module, and then call the global function run (). The run () function creates the main window and starts the message loop.

Caboutdlg is a derived class of CDialogImpl, which corresponds to ID IDD_ABOUTBOX resources, I have already introduced the dialog in the first part, so you should understand the CaboutDLG code.

CWTLCLOCKVIEW is the view class of our program, just like the MFC's view class, there is no title bar, overwriting the client area of ​​the entire main window. The CWTLClockView class has a pretranslateMessage () function, also the same function with the same name in the MFC, and a message response function for WM_Paint. There is nothing special in these two functions, but we will fill in the onpaint () function to display time.

Finally, our CMAINFRAME class, which has many interesting new things, this is the definition of this class.

class CMainFrame: public CFrameWindowImpl , public CUpdateUI , public CMessageFilter, public CIdleHandler {public: DECLARE_FRAME_WND_CLASS (NULL, IDR_MAINFRAME) CWTLClockView m_view; virtual BOOL PreTranslateMessage (MSG * pMsg); virtual BOOL OnIdle (); BEGIN_UPDATE_UI_MAP (CMainFrame ) End_update_ui_map () begin_msg_map (cmainframe) // ... chain_msg_map (CFRAMEWINDOWIMPL ) END_MSG_MAP ()};

CMessageFilter is an embedded class that provides a PretranslateMessage () function, CIDEHandler is also an embedded class, which provides onIdle () functions. CMessageloop, CidleHandler and CUPDATEUI three classes completed the status update of interface elements, just like the ON_UPDATE_COMMAND_UI macro in the MFC. CMAINFRAME :: OnCreate () creates a view window and saves the handle of this window that changes the size of the main window change the size view window. The oncreate () function also adds the CMAINFRAME object to the message filter queue and idle processing queue maintained by CAPPModule, and I will introduce these later.

LRESULT CMainFrame :: OnCreate (UINT / * uMsg * /, WPARAM / * wParam * /, LPARAM / * lParam * /, BOOL & / * bHandled * /) {m_hWndClient = m_view.Create (m_hWnd, rcDefault, NULL, | WS_CHILD | WS_VISIBLE | WS_CLIPSIBLINGS | WS_CLIPCHILDREN, WS_EX_CLIENTEDGE); // register object for message filtering and idle updates CMessageLoop * pLoop = _Module.GetMessageLoop (); pLoop-> AddMessageFilter (this); pLoop-> AddIdleHandler (this); return 0;}

M_hWndClient is a member variable of the CFraMewindowImpl object, which will change when the main window size changes.

Processing of File | New, File | EXIT, and HELP | ABOUT menu messages is also added in the generated CMAINFRAME. Our clock procedures do not need these default menu items, but now they are not hurt in the code. You can now compile and run the wizard creative code, but this program does not use. If you are interested, you can go deep into the CMAINFRAME :: CreateEx () function to see how the main window and its resources are loaded and created.

Our next WTL trip is CMessageloop, which is a message pump and idle processing.

CMESSAGELOOP's internal implementation

CMessageloop provides a message pump for our application, except for a standard DispatchMessage / TranslateMessage loop, it also implements a message filtering mechanism by calling the pretranslateMessage () function, and implements the idle processing function by calling OnIdle (). Here is the pseudo code of the run () function:

INT Run () {msg msg; for (;;) {while (! PeekMessage (& msg)) DoidleProcessing (); if (0 == getMessage (& MSG)) Break; // wm_quit retrieved from the queue if (! PretranslateMessage) & msg) {TranslateMessage (& MSG); DISPATCHMESSAGE (& MSG);}}} Return Msg.wParam;} Those classes that need to filter messages only need to call the cMainframe :: oncreate () function, just call the cMAInframe :::: el::, CMessageLoop will know that the PretranslateMessage () function is called, and if you need idle processing, call the cMessageLoop :: addIdleHandler () function.

It should be noted that the translateAccelerator () or isDialogMessage () function is not called in this message cycle, because CFraMewindowImpl has been processed before this, but if you use a non-Mode dialog box in the program, you need to be in CMAInframe: : The call to the isDialogMessage () function is added to the PretranslateMessage () function.

CFRAMEWINDOWIMPL internal implementation

CfraMewindowImpl and its base class CFrameWindowImplBase provides a bridging help for Toolbars, Rebars, Status Bars, Toolbar Buttons, and Menu items, these are the basic features of the MFC's CFrameWnd class. I will gradually introduce these characteristics, and the complete discussion of the CFrameWindowImpl class needs to write two articles, but now see how CFraMewindowImpl handles WM_SIZE and its client area is enough. Need to remember the foregoing things, M_HWndClient is a member variable of the CFrameWindowImplBase class, which stores the handle of the View window in the main window.

The CFraMewindowImpl class handles the WM_SIZE message:

LResult OnSize (uint / * umsg * /, wparam wparam, lparam / * lparam * /, bool & bhand) {if (wparam! = Size_minimized) {t * pt = static_cast (this); Pt-> UpdateLayout } bhand = false; return 1;}

It first checks if the window is minimized. If you don't call UpDateLayout (), below is updateLayout ():

void UpdateLayout (BOOL bResizeBars = TRUE) {RECT rect; GetClientRect (& rect); // position bars and offset their dimensions UpdateBarsPosition (rect, bResizeBars); // resize client window if (! m_hWndClient = NULL) :: SetWindowPos (m_hWndClient, Null, Rect.Lep, Rect.top, Rect.Right - Rect.top, Rect.Bottom - Rect.top, SWP_NOZORDER | SWP_NOACTIVATE); It may be any window that is not limited to this window. This is unlike MFC, MFC needs CVIEW derived classes (such as separated window classes) in many cases. If you look back at CMAINFRAME :: OnCreate (), you will see that it creates a view window and assigns M_HWndClient, by m_hWndClient to make sure the view window is set to the correct size.

Back to the front clock program

Now we have seen some details of the main window class, now return to our clock program. The view window is used to respond to timer messages and is responsible for displaying clocks, just like the CMYWINDOW class in front. Here is part of this class definition:

class CWTLClockView: public CWindowImpl {public: DECLARE_WND_CLASS (NULL) BOOL PreTranslateMessage (MSG * pMsg); BEGIN_MSG_MAP_EX (CWTLClockView) MESSAGE_HANDLER (WM_PAINT, OnPaint) MSG_WM_CREATE (OnCreate) MSG_WM_DESTROY (OnDestroy) MSG_WM_TIMER (OnTimer) MSG_WM_ERASEBKGND (OnEraseBkgnd) END_MSG_MAP ()}

After replacing Begin_MSG_MAP_EX, the ATL's message mapping macro can be used with the WTL macro, the previous example is displayed (painted) the clock in OneRaseBkGnd (), and is now moved to onpaint (). The new window looks like this:

Finally, add a UI UpDating function for our program. To demonstrate these usage, we add a Start menu for the window to start and stop clocks, and the Start menu and STOP menu will be properly set to available and unavailable.

Automatic update of interface elements (Updating)

The interface update of idle time is a few things collaborative results: CMessageloop objects, embedding CIDEHANDAL and CUPDATEUI, CMAINFRAME classes inherit these two embedded classes, of course, the Update_UI_MAP macro in the CMAINFRAME class. CUPDATEUI can operate 5 different interface elements: Top-class menu items (that is, menu bar itself), pop-up menu menu items, toolbar buttons, status strip's lattice and sub-windows (such as controls in the dialog). Each interface element corresponds to a constant of the CuPDateuibase class:

Menu bar item: UPDUI_MENUBAR pop-up menu items: UPDUI_MENUPOPUP toolbar buttons: UPDUI_TOOLBAR status bar grid: UPDUI_STATUSBAR child window: UPDUI_CHILDWINDOWCUpdateUI can set the enabled state, checked state, and text (of course not all of the interface elements are supported by all states, if a child window It is an edit box that it cannot be CHECK. The menu item can be set to the default state so that its text will be increased.

To do four things to use UI Updating:

The main window needs to inherit CupdateUI and CIDLEHANDLER to link CMAINFRAME messages into CUPDATEUI to add a main window to the module's idle processing queue Add Update_UI_MAP Macro in the main window

The code-creative code has been three things for us, and now we only need to decide that the menu item needs to update and how they can be used when it is available.

Add a new menu item for controlling the clock

Add a clock menu to the menu bar, there are two menu items: IDC_Start and IDC_Stop:

Then add an entry for each menu item in the Update_UI_MAP macro:

class CMainFrame: public ... {public: // ... BEGIN_UPDATE_UI_MAP (CMainFrame) UPDATE_ELEMENT (IDC_START, UPDUI_MENUPOPUP) UPDATE_ELEMENT (IDC_STOP, UPDUI_MENUPOPUP) END_UPDATE_UI_MAP () // ...};

We only need to call CUPDATEUI :: UieNable () When you change any of these two menu items. UieNable () has two parameters, one is the ID of the interface element, the other is the BOOL type variable that the flag interface element is available (True represents available, FALSE is not available).

This set of system is awkward than the MFC's on_update_command_ui system, in the MFC, we only need to write a process function, and the MFC selects the display state of the interface element. In the WTL, we need to tell the WTL interface element when changing. Of course, these two libraries are the change in the menu status when the menu will be displayed.

Call UieNable ()

Now return to the oncreate () function to see how the initial state of the Clock menu is set.

Lresult CMAINFRAME :: OnCreate (uint / * umsg * /, wparam / * wparam * /, lparam / * lparam * /, bool & / * bhand * /) {m_hWndclient = m_view.create (...); // register object Object For message filtering and idle updates // [omitted for clarity] // set the initial state of the clock menu items: uienable; uienable (idc_stop, true); return 0;}

The clock menu is like this:

CMAINFRAME now needs to handle two new menu items, and process functions that need to flip both menu items when the view class calls them and stop clock. This is one of the MFC's built-in messages that cannot be imagined. In the MFC program, all interface updates and command messages must be completely placed in the view class, but in the WTL, the main window class and the view class communicate in some way; the menu is owned by the main window, the main window is obtained Menu messages and do appropriate processing, either respond to these messages or sends to the view class. This communication is done by PretranslateMessage (), of course, CMAINFRAME still wants to call UieNable (). CMAINFRAME can pass the THIS pointer to the view class, so that the view class can also call UieNable () by this pointer. This solution I have chosen in this example causes the main window and the view to become a tight coupled, but I found this easy (and explanation!).

class CMainFrame: public ... {public: BEGIN_MSG_MAP_EX (CMainFrame) // ... COMMAND_ID_HANDLER_EX (IDC_START, OnStart) COMMAND_ID_HANDLER_EX (IDC_STOP, OnStop) END_MSG_MAP () // ... void OnStart (UINT uCode, int nID, HWND hwndCtrl ); void OnStop (UINT uCode, int nID, HWND hwndCtrl);}; void CMainFrame :: OnStart (UINT uCode, int nID, HWND hwndCtrl) {// Enable Stop and disable Start UIEnable (IDC_START, false); UIEnable (IDC_STOP , true); // Tell the view to start its clock m_view.StartClock ();.} void CMainFrame :: OnStop (UINT uCode, int nID, HWND hwndCtrl) {// Enable Start and disable Stop UIEnable (IDC_START, true) Uienable (IDC_Stop, False); // Tell The View to Stop ITS Clock. M_view.stopclock ();

Each processing function updates the Clock menu and then calls a method in the view class to select it in the view class because the clock is controlled by the view class. StartClock () and stopclock () have not been listed, but they can be found in this project.

Places that you need to pay attention to in the message mapping chain

If you use VC 6, you will notice that ClassView looks some messy:

This happens because ClassView cannot explain the Begin_MSG_MAP_EX macro, which is to all WTL message mapping macro is a function definition. You can change the macro back to Begin_MSG_MAP and add this two lines of code at the end of the stdafx.h file to solve this problem:

#undef begin_msg_map # define begin_msg_map (x) Begin_MSG_MAP_EX (X)

Next stop, 1995

We now just set off a WTL's corner. In the next article, I will add some Windows 95 interface standards, such as toolbar and status, and experience CupdateUi in the next article. For example, try to use uisetcheck () instead of uienable () to see what the menu item changes. Modify record

On March 26, 2003, this paper was first published.

转载请注明原文地址:https://www.9cbs.com/read-2296.html

New Post(0)