Chào mừng đến với Diễn đàn lập trình - Cộng đồng lập trình.
Kết quả 1 đến 6 của 6
  1. #1
    Ngày tham gia
    Sep 2015
    Bài viết
    0

    Code của bài KeyView1 trong tài liệu của Charles Petzold. Giải thích giúp mình

    Mình đang đọc cuốn Windows Programming của Charles Petzold 5th. Đọc tới bài ví dụ KeyView1 chapter 6 thì hết hiểu nổi luôn. Ngâm cứu cả ngày hôm qua, google quá trời mà không thấy có bài nào giải thích về code hết. Mình bê nguyên đoạn code lên đây để mọi người giải thích hộ. Các câu hỏi mình ghi chú trong code, các bạn vừa chạy chương trình vừa giải thích giúp mình nhé.
    Các bạn giải thích về ý tưởng của tác giả đối với bài này luôn thì càng tốt.

    Mã:
    /*--------------------------------------------------------   KEYVIEW1.C -- Displays Keyboard and Character Messages                 (c) Charles Petzold, 1998  --------------------------------------------------------*/ #include <windows.h> LRESULT CALLBACK WndProc (HWND, UINT, WPARAM, LPARAM) ; int WINAPI WinMain (HINSTANCE hInstance, HINSTANCE hPrevInstance,                    PSTR szCmdLine, int iCmdShow){     static TCHAR szAppName[] = TEXT ("KeyView1") ;     HWND         hwnd ;     MSG          msg ;     WNDCLASS     wndclass ;          wndclass.style         = CS_HREDRAW | CS_VREDRAW ;     wndclass.lpfnWndProc   = WndProc ;     wndclass.cbClsExtra    = 0 ;     wndclass.cbWndExtra    = 0 ;     wndclass.hInstance     = hInstance ;     wndclass.hIcon         = LoadIcon (NULL, IDI_APPLICATION) ;     wndclass.hCursor       = LoadCursor (NULL, IDC_ARROW) ;     wndclass.hbrBackground = (HBRUSH) GetStockObject (WHITE_BRUSH) ;     wndclass.lpszMenuName  = NULL ;     wndclass.lpszClassName = szAppName ;      if (!RegisterClass (&wndclass))     {          MessageBox (NULL, TEXT ("This program requires Windows NT!"),                       szAppName, MB_ICONERROR) ;          return 0 ;     }          hwnd = CreateWindow (szAppName, TEXT ("Keyboard Message Viewer #1"),                          WS_OVERLAPPEDWINDOW,                          CW_USEDEFAULT, CW_USEDEFAULT,                          CW_USEDEFAULT, CW_USEDEFAULT,                          NULL, NULL, hInstance, NULL) ;          ShowWindow (hwnd, iCmdShow) ;     UpdateWindow (hwnd) ;          while (GetMessage (&msg, NULL, 0, 0))     {          TranslateMessage (&msg) ;          DispatchMessage (&msg) ;     }     return msg.wParam ;} LRESULT CALLBACK WndProc (HWND hwnd, UINT message, WPARAM wParam, LPARAM lParam){     static int   cxClientMax, cyClientMax, cxClient, cyClient, cxChar, cyChar ;     static int   cLinesMax, cLines ;     static PMSG  pmsg ;/* Một phần tử pmsg là một mảng chứa các phần tử message MSG đúng không?*/     static RECT  rectScroll ;     static TCHAR szTop[] = TEXT ("Message        Key       Char     ")                            TEXT ("Repeat Scan Ext ALT Prev Tran") ;     static TCHAR szUnd[] = TEXT ("_______        ___       ____     ")                            TEXT ("______ ____ ___ ___ ____ ____") ;      static TCHAR * szFormat[2] = {                          TEXT ("%-13s %3d %-15s%c%6u %4d %3s %3s %4s %4s"),               TEXT ("%-13s            0x%04X%1s%c %6u %4d %3s %3s %4s %4s") } ;              /*0x%04X: Canh dữ liệu kiểu gì ngộ vậy?*/     static TCHAR * szYes  = TEXT ("Yes") ;     static TCHAR * szNo   = TEXT ("No") ;     static TCHAR * szDown = TEXT ("Down") ;     static TCHAR * szUp   = TEXT ("Up") ;      static TCHAR * szMessage [] = {                          TEXT ("WM_KEYDOWN"),    TEXT ("WM_KEYUP"),                          TEXT ("WM_CHAR"),       TEXT ("WM_DEADCHAR"),                          TEXT ("WM_SYSKEYDOWN"), TEXT ("WM_SYSKEYUP"),                          TEXT ("WM_SYSCHAR"),    TEXT ("WM_SYSDEADCHAR") } ;     HDC          hdc ;     int          i, iType ;     PAINTSTRUCT  ps ;     TCHAR        szBuffer[128], szKeyName[32] ;     TEXTMETRIC   tm ;          switch (message)     {     case WM_CREATE:     case WM_DISPLAYCHANGE:                    // Get maximum size of client area           cxClientMax = GetSystemMetrics (SM_CXMAXIMIZED) ;          cyClientMax = GetSystemMetrics (SM_CYMAXIMIZED) ;               // Get character size for fixed-pitch font           hdc = GetDC (hwnd) ;           SelectObject (hdc, GetStockObject (SYSTEM_FIXED_FONT)) ;          GetTextMetrics (hdc, &tm) ;          cxChar = tm.tmAveCharWidth ;          cyChar = tm.tmHeight ;           ReleaseDC (hwnd, hdc) ;                // Allocate memory for display lines           if (pmsg)               free (pmsg) ;           cLinesMax = cyClientMax / cyChar ;          pmsg = malloc (cLinesMax * sizeof (MSG)) ;          cLines = 0 ;                                   // fall through     case WM_SIZE:          if (message == WM_SIZE)/*   Sao còn phải kiểm tra điều kiện này nữa, rõ ràng là đang thực hiện  công việc với case WM_SIZE mà.*/          {               cxClient = LOWORD (lParam) ;               cyClient = HIWORD (lParam) ;          }               // Calculate scrolling rectangle           rectScroll.left   = 0 ;          rectScroll.right  = cxClient ;          rectScroll.top    = cyChar ;          rectScroll.bottom = cyChar * (cyClient / cyChar) ;          InvalidateRect (hwnd, NULL, TRUE) ;          return 0 ;               case WM_KEYDOWN:     case WM_KEYUP:     case WM_CHAR:     case WM_DEADCHAR:     case WM_SYSKEYDOWN:     case WM_SYSKEYUP:     case WM_SYSCHAR:     case WM_SYSDEADCHAR:                 // Rearrange storage array           for (i = cLinesMax - 1 ; i > 0 ; i--)          {               pmsg[i] = pmsg[i - 1] ;/*    Mình không hiểu chỗ này: Khi  chương trình mới hoạt động, khi ta nhấn 1 phím sinh ra thông điệp thuộc một trong 8 dạng CHAR như trên thì trong mảng chưa có phần tử nào. Do đó lệnh dời các phần tử từ vị trí ClinesMax-1 đến vị trí 1 xem như công cốc. Hơn nữa, nếu giả sử thực hiện được lệnh này thì số phần tử trong mảng sẽ giảm đi 1.    Các Message đến sẽ được lưu giữ tại phần tử pmsg[0]. Như vậy, message đến sau sẽ đè lên Message trước đó (đoạn code dưới đây). Cuối cùng là trong mảng chỉ có một phần tử pmsg[0] là chứa thông điệp, cho dù ta có nhấn bao nhiêu phím đi chăng nữa. (Mình biết mình đang hiểu sai đoạn code này của tác giả, bạn chỉ giúp mình nhé).    */          }               // Store new message           pmsg[0].hwnd = hwnd ;          pmsg[0].message = message ;          pmsg[0].wParam = wParam ;          pmsg[0].lParam = lParam ;           cLines = min (cLines + 1, cLinesMax) ;//Đặt lệnh này để làm gì nhỉ?                // Scroll up the display           ScrollWindow (hwnd, 0, -cyChar, &rectScroll, &rectScroll) ;    /* Nếu thay bằng ScrollWindow (hwnd, 0, -cyChar,NULL, NULL) ; thì dữ liệu in ra rất kì cục. Tại sao vậy, rõ ràng là cuộn toàn màn hình mà?*/          break ;        // ie, call DefWindowProc so Sys messages work               case WM_PAINT:          hdc = BeginPaint (hwnd, &ps) ;           SelectObject (hdc, GetStockObject (SYSTEM_FIXED_FONT)) ;          SetBkMode (hdc, TRANSPARENT) ;          TextOut (hdc, 0, 0, szTop, lstrlen (szTop)) ;          TextOut (hdc, 0, 0, szUnd, lstrlen (szUnd)) ;           for (i = 0 ; i < min (cLines, cyClient / cyChar - 1) ; i++) /* Tại sao lại chọn i<min(cLines, cyClient/cyChar-1), nếu viết lại i<min(cLines+1,cyClient/cyChar-1 thì bị lỗi lúc run-time? */          {               iType = pmsg[i].message == WM_CHAR ||                       pmsg[i].message == WM_SYSCHAR ||                       pmsg[i].message == WM_DEADCHAR ||                       pmsg[i].message == WM_SYSDEADCHAR ;    /*Khi có một phím sinh mã CHAR thì iType sẽ =1*/                GetKeyNameText (pmsg[i].lParam, szKeyName,                                sizeof (szKeyName) / sizeof (TCHAR)) ;/* Lúc thì pmsg[i].message, lúc thì pmsg[i].lParam. Rối quá, pmsg[i].message là cái gì, pmsg[i].lParam là cái gì?*/               TextOut (hdc, 0, (cyClient / cyChar - 1 - i) * cyChar, szBuffer,                        wsprintf (szBuffer, szFormat [iType],                             szMessage [pmsg[i].message - WM_KEYFIRST],              /*        Câu này dễ điên nhất, pmsg[i].message - WM_KEYFIRST có ý nghĩa gì?        */                                          pmsg[i].wParam,                             (PTSTR) (iType ? TEXT (" ") : szKeyName),                             (TCHAR) (iType ? pmsg[i].wParam : ' '),                             LOWORD (pmsg[i].lParam),                             HIWORD (pmsg[i].lParam) & 0xFF,                  /*HIWORD(pmsg[i].lParam)&0xFF nghĩa là gì vậy?*/                                  0x01000000 & pmsg[i].lParam ? szYes  : szNo,              /* Kiểm tra xem các phím ALT và Control bên phải có được bấm không?*/                             0x20000000 & pmsg[i].lParam ? szYes  : szNo,        /*Kiểm tra xem phím ALT có được bấm không, sao lại là 0x20000000 nhỉ?*/                             0x40000000 & pmsg[i].lParam ? szDown : szUp,                             0x80000000 & pmsg[i].lParam ? szUp   : szDown)) ;              }          EndPaint (hwnd, &ps) ;          return 0 ;      case WM_DESTROY:          PostQuitMessage (0) ;          return 0 ;     }     return DefWindowProc (hwnd, message, wParam, lParam) ;}
    Còn đây là file đính kèm. Chữ có màu sắc luôn.
    Cám ơn các bạn.

  2. #2
    Ngày tham gia
    Sep 2015
    Đang ở
    Hà Nội
    Bài viết
    0
    Mã:
     /*--------------------------------------------------------   KEYVIEW1.C -- Displays Keyboard and Character Messages                 (c) Charles Petzold, 1998  --------------------------------------------------------*/ #include <windows.h> LRESULT CALLBACK WndProc (HWND, UINT, WPARAM, LPARAM) ; int WINAPI WinMain (HINSTANCE hInstance, HINSTANCE hPrevInstance,                    PSTR szCmdLine, int iCmdShow){     static TCHAR szAppName[] = TEXT ("KeyView1") ;     HWND         hwnd ;     MSG          msg ;     WNDCLASS     wndclass ;          wndclass.style         = CS_HREDRAW | CS_VREDRAW ;     wndclass.lpfnWndProc   = WndProc ;     wndclass.cbClsExtra    = 0 ;     wndclass.cbWndExtra    = 0 ;     wndclass.hInstance     = hInstance ;     wndclass.hIcon         = LoadIcon (NULL, IDI_APPLICATION) ;     wndclass.hCursor       = LoadCursor (NULL, IDC_ARROW) ;     wndclass.hbrBackground = (HBRUSH) GetStockObject (WHITE_BRUSH) ;     wndclass.lpszMenuName  = NULL ;     wndclass.lpszClassName = szAppName ;      if (!RegisterClass (&wndclass))     {          MessageBox (NULL, TEXT ("This program requires Windows NT!"),                       szAppName, MB_ICONERROR) ;          return 0 ;     }          hwnd = CreateWindow (szAppName, TEXT ("Keyboard Message Viewer #1"),                          WS_OVERLAPPEDWINDOW,                          CW_USEDEFAULT, CW_USEDEFAULT,                          CW_USEDEFAULT, CW_USEDEFAULT,                          NULL, NULL, hInstance, NULL) ;          ShowWindow (hwnd, iCmdShow) ;     UpdateWindow (hwnd) ;          while (GetMessage (&msg, NULL, 0, 0))     {          TranslateMessage (&msg) ;          DispatchMessage (&msg) ;     }     return msg.wParam ;} LRESULT CALLBACK WndProc (HWND hwnd, UINT message, WPARAM wParam, LPARAM lParam){     static int   cxClientMax, cyClientMax, cxClient, cyClient, cxChar, cyChar ;     static int   cLinesMax, cLines ;     static PMSG  pmsg ;/* Một phần tử pmsg là một mảng chứa các phần tử message MSG đúng không?Nó là con trỏ trỏ đến 1 MSG. Trong trường hợp này thì có thể coi nó là 1 mảng.*/     static RECT  rectScroll ;     static TCHAR szTop[] = TEXT ("Message        Key       Char     ")                            TEXT ("Repeat Scan Ext ALT Prev Tran") ;     static TCHAR szUnd[] = TEXT ("_______        ___       ____     ")                            TEXT ("______ ____ ___ ___ ____ ____") ;      static TCHAR * szFormat[2] = {                          TEXT ("%-13s %3d %-15s%c%6u %4d %3s %3s %4s %4s"),               TEXT ("%-13s            0x%04X%1s%c %6u %4d %3s %3s %4s %4s") } ;              /*0x%04X: Canh dữ liệu kiểu gì ngộ vậy?              VD có số là 10 thì sẽ hiển thị ra 0x000A              */     static TCHAR * szYes  = TEXT ("Yes") ;     static TCHAR * szNo   = TEXT ("No") ;     static TCHAR * szDown = TEXT ("Down") ;     static TCHAR * szUp   = TEXT ("Up") ;      static TCHAR * szMessage [] = {                          TEXT ("WM_KEYDOWN"),    TEXT ("WM_KEYUP"),                          TEXT ("WM_CHAR"),       TEXT ("WM_DEADCHAR"),                          TEXT ("WM_SYSKEYDOWN"), TEXT ("WM_SYSKEYUP"),                          TEXT ("WM_SYSCHAR"),    TEXT ("WM_SYSDEADCHAR") } ;     HDC          hdc ;     int          i, iType ;     PAINTSTRUCT  ps ;     TCHAR        szBuffer[128], szKeyName[32] ;     TEXTMETRIC   tm ;          switch (message)     {     case WM_CREATE:     case WM_DISPLAYCHANGE:                    // Get maximum size of client area           cxClientMax = GetSystemMetrics (SM_CXMAXIMIZED) ;          cyClientMax = GetSystemMetrics (SM_CYMAXIMIZED) ;               // Get character size for fixed-pitch font           hdc = GetDC (hwnd) ;           SelectObject (hdc, GetStockObject (SYSTEM_FIXED_FONT)) ;          GetTextMetrics (hdc, &tm) ;          cxChar = tm.tmAveCharWidth ;          cyChar = tm.tmHeight ;           ReleaseDC (hwnd, hdc) ;                // Allocate memory for display lines           if (pmsg)               free (pmsg) ;           cLinesMax = cyClientMax / cyChar ;          pmsg = malloc (cLinesMax * sizeof (MSG)) ;          cLines = 0 ;                                   // fall through     case WM_SIZE:          if (message == WM_SIZE)/*   Sao còn phải kiểm tra điều kiện này nữa, rõ ràng là đang thực hiện  công việc với case WM_SIZE mà.   Bạn để ý case WM_DISPLAYCHANGE: ko có break. Tức là message ở đây cũng có thể là WM_DISPLAYCHANGE chứ ko hẳn WM_SIZE*/          {               cxClient = LOWORD (lParam) ;               cyClient = HIWORD (lParam) ;          }               // Calculate scrolling rectangle           rectScroll.left   = 0 ;          rectScroll.right  = cxClient ;          rectScroll.top    = cyChar ;          rectScroll.bottom = cyChar * (cyClient / cyChar) ;          InvalidateRect (hwnd, NULL, TRUE) ;          return 0 ;               case WM_KEYDOWN:     case WM_KEYUP:     case WM_CHAR:     case WM_DEADCHAR:     case WM_SYSKEYDOWN:     case WM_SYSKEYUP:     case WM_SYSCHAR:     case WM_SYSDEADCHAR:                 // Rearrange storage array           for (i = cLinesMax - 1 ; i > 0 ; i--)          {               pmsg[i] = pmsg[i - 1] ;/*    Mình không hiểu chỗ này: Khi  chương trình mới hoạt động, khi ta nhấn 1 phím sinh ra thông điệp thuộc một trong 8 dạng CHAR như trên thì trong mảng chưa có phần tử nào. Do đó lệnh dời các phần tử từ vị trí ClinesMax-1 đến vị trí 1 xem như công cốc. Hơn nữa, nếu giả sử thực hiện được lệnh này thì số phần tử trong mảng sẽ giảm đi 1.    Các Message đến sẽ được lưu giữ tại phần tử pmsg[0]. Như vậy, message đến sau sẽ đè lên Message trước đó (đoạn code dưới đây). Cuối cùng là trong mảng chỉ có một phần tử pmsg[0] là chứa thông điệp, cho dù ta có nhấn bao nhiêu phím đi chăng nữa. (Mình biết mình đang hiểu sai đoạn code này của tác giả, bạn chỉ giúp mình nhé).        > Các phần tử trong mảng ko phải chuyển về đầu, mà về cuối. Bạn có thể tạm hình dung như này:    khởi tạo    [][][][]    nhấn A:    [A][][][]    nhấn B:    -> dịch các phần tử về cuối 1 ô: [][A][][]    -> gán phần tử đầu tiên bằng B: [B][A][][]    */          }               // Store new message           pmsg[0].hwnd = hwnd ;          pmsg[0].message = message ;          pmsg[0].wParam = wParam ;          pmsg[0].lParam = lParam ;           cLines = min (cLines + 1, cLinesMax) ;//Đặt lệnh này để làm gì nhỉ?                // Scroll up the display           ScrollWindow (hwnd, 0, -cyChar, &rectScroll, &rectScroll) ;    /* Nếu thay bằng ScrollWindow (hwnd, 0, -cyChar,NULL, NULL) ; thì dữ liệu in ra rất kì cục. Tại sao vậy, rõ ràng là cuộn toàn màn hình mà?        > Cuộn toàn màn hình thì bạn có thể dùng tham số thứ 4 là NULL. Nhưg tham số thứ 5 thì có nhiệm vụ để vẽ lại. Bạn gán nó là NULL thì nó sẽ ko vẽ lại và gây ra hiện tượng đè hình.    */          break ;        // ie, call DefWindowProc so Sys messages work               case WM_PAINT:          hdc = BeginPaint (hwnd, &ps) ;           SelectObject (hdc, GetStockObject (SYSTEM_FIXED_FONT)) ;          SetBkMode (hdc, TRANSPARENT) ;          TextOut (hdc, 0, 0, szTop, lstrlen (szTop)) ;          TextOut (hdc, 0, 0, szUnd, lstrlen (szUnd)) ;           for (i = 0 ; i < min (cLines, cyClient / cyChar - 1) ; i++)           /* Tại sao lại chọn i<min(cLines, cyClient/cyChar-1), nếu viết lại i<min(cLines+1,cyClient/cyChar-1 thì bị lỗi lúc run-time?                     > cLines + 1 có thể lớn hơn cLinesMax và sẽ khiến cho i vượt quá phạm vi của mảng.          */                    {               iType = pmsg[i].message == WM_CHAR ||                       pmsg[i].message == WM_SYSCHAR ||                       pmsg[i].message == WM_DEADCHAR ||                       pmsg[i].message == WM_SYSDEADCHAR ;    /*Khi có một phím sinh mã CHAR thì iType sẽ =1    > Đây là biểu thức điều kiện OR. Chỉ cần 1 điều kiện đúng sẽ dẫn đến kết quả cuối cùng là đúng.    */                GetKeyNameText (pmsg[i].lParam, szKeyName,                                sizeof (szKeyName) / sizeof (TCHAR)) ;/* Lúc thì pmsg[i].message, lúc thì pmsg[i].lParam. Rối quá, pmsg[i].message là cái gì, pmsg[i].lParam là cái gì? > message là thông điệp dc gửi đến (vd WM_CHAR,...)lParam chứa thông tin thêm cho thông điệp đấy. (vd repeat count, scan code, extended-key flag, context code, previous key-state flag)*/               TextOut (hdc, 0, (cyClient / cyChar - 1 - i) * cyChar, szBuffer,                        wsprintf (szBuffer, szFormat [iType],                             szMessage [pmsg[i].message - WM_KEYFIRST],              /*        Câu này dễ điên nhất, pmsg[i].message - WM_KEYFIRST có ý nghĩa gì?                > Bạn lên trên xem mảng szMessage. WM_KEYDOWN > WM_SYSDEADCHAR có thứ tự tăng dần (0x100, 0x101).        WM_KEYFIRST tương đương với WM_KEYDOWN.        Giả sử bạn nhận dc 1 message là: WM_KEYUP.         WM_KEYUP - WM_KEYFIRST = 0x101 - 0x100 = 1.        Mà szMessage[1] = "WM_KEYUP" -> ra dc tên message.        */                                          pmsg[i].wParam,                             (PTSTR) (iType ? TEXT (" ") : szKeyName),                             (TCHAR) (iType ? pmsg[i].wParam : ' '),                             LOWORD (pmsg[i].lParam),                             HIWORD (pmsg[i].lParam) & 0xFF,                  /*HIWORD(pmsg[i].lParam)&0xFF nghĩa là gì vậy?                                    > lParam chứa rất nhiều thông tin thêm chỉ trong 1 biến (như đã nói trên), vì vậy bạn cần dùng các phép toán bit để lấy các giá trị đó (cái này bạn cần tìm hiểu thêm các phép &, |, ~,... để hiểu thêm)                  */                                  0x01000000 & pmsg[i].lParam ? szYes  : szNo,              /* Kiểm tra xem các phím ALT và Control bên phải có được bấm không?             (cái này bạn cần tìm hiểu thêm các phép &, |, ~,... để hiểu thêm)             */                             0x20000000 & pmsg[i].lParam ? szYes  : szNo,        /*Kiểm tra xem phím ALT có được bấm không, sao lại là 0x20000000 nhỉ?        (cái này bạn cần tìm hiểu thêm các phép &, |, ~,... để hiểu thêm)        */                             0x40000000 & pmsg[i].lParam ? szDown : szUp,                             0x80000000 & pmsg[i].lParam ? szUp   : szDown)) ;              }          EndPaint (hwnd, &ps) ;          return 0 ;      case WM_DESTROY:          PostQuitMessage (0) ;          return 0 ;     }     return DefWindowProc (hwnd, message, wParam, lParam) ;}

  3. #3
    Ngày tham gia
    Sep 2015
    Bài viết
    0
    @chủ thớt :
    Mục đích của bài này là tác giả muốn bạn viết 1 chương trình mà khi ấn 1 phím xuống và thả phím đó ra hay giữ lì.
    Sẽ hiện các thông số như scancode , repeatcount , contextCode , virtual key ... mấy thông số này sẽ lấy từ message VM_KEYDOW và VM_CHAR
    Muốn làm bài này thì phải hiểu về số Hex và BitWise Operator.

  4. #4
    Ngày tham gia
    Sep 2015
    Bài viết
    0
    Còn một câu hỏi nữa nè bạn, vì có nhiều hình nên post lâu, mình up lên mediafire. Bạn vui lòng down về rồi giải đáp giúp mình nhé.
    Mã:
    http://www.mediafire.com/download.php?cjbhed4piduzzt3

  5. #5
    Ngày tham gia
    Sep 2015
    Bài viết
    0
    LOWORD giá trị nó trong khoảng từ bit 0->15 HIWORD 16 -> 31 đối với win32 . Con win64 tớ chưa cài bao giờ nên ko dám chắc. Chỉ dám doán là Low từ 0->31 Hi từ 32->63

  6. #6
    Ngày tham gia
    Sep 2015
    Bài viết
    0
    Mình bị lỗi "undefined reference to `GetStockObject@4'". Bạn nào biết, hướng dẫn mình với!
    Cám ơn!

 

 

Quyền viết bài

  • Bạn Không thể gửi Chủ đề mới
  • Bạn Không thể Gửi trả lời
  • Bạn Không thể Gửi file đính kèm
  • Bạn Không thể Sửa bài viết của mình
  •