Acces dialog bar in CPreviewView

Intrebari legate de programarea cu biblioteci precum MFC, ATL, WTL si GDI+.
mesajflaviu
Membru++
Membru++
Posts: 687
Joined: 10 Sep 2008, 21:40
Judet: Cluj

Acces dialog bar in CPreviewView

Post by mesajflaviu » 29 Nov 2012, 12:53

Incerc sa customizez CPreviewView in care sa pun un dialog bar custom. Toate bune si frumoase, dar nu stiu cum sa "prind" evenimentele date de controalele din acest dialogbar. Din CPreviewView pot accesa controlele in dialogbar, dar invers, nu:

Code: Select all

CPreviewViewDlgBar* GetToolBar(){return (CPreviewViewDlgBar*)m_pToolBar;}
si apoi:

Code: Select all

int CPreviewViewExt::OnCreate(LPCREATESTRUCT lpCreateStruct) 
{
	if(CPreviewView::OnCreate(lpCreateStruct) == -1)return -1;

	// TODO: Add your specialized creation code here

	m_pToolBar->EnableToolTips(TRUE);

	CButton* pCheck = (CButton*)GetToolBar()->GetDlgItem(IDC_CHECK1);
	pCheck->SetCheck(TRUE);

	return 0;
}
bun, dar nu stiu cum sa accesez CPreviewViewExt din dialog bar:

Code: Select all

void CPreviewViewDlgBar::OnCheck1() 
{
	// TODO: Add your control notification handler code here

	MessageBox(_T("You have just clicked on check box"));
}
ce-i drept, acest dialogbar nu pare sa se creeze in CPreviewViewDlgBar::OnInitDialog:

Code: Select all

LRESULT CPreviewViewDlgBar::OnInitDialog(WPARAM wParam, LPARAM lParam)
{
	BOOL bRet = HandleInitDialog(wParam, lParam);

	if(! UpdateData(FALSE))
	{
		TRACE0("Warning: UpdateData failed during dialog init.\n");
	}

	// TODO: Add extra initialization here

	return (LRESULT)bRet;
}
am butoane in dialogbar, pe care daca le-am denumit la fel cu cele din dialogbar-ul standard (AFX_ID_PREVIEW_PRINT sau AFX_ID_PREVIEW_CLOSE), functioneaza ... dar nu stiu cum sa fac cu controalele puse de mine ... ma puteti ajuta ?

Bine, as putea-o lua pe dupa sura, sa trimit mesaje ferestrei principale, si apoi mai departe CTestPPView-ului, dar nu imi place ideea ...

Pentru o exemplificare cat mai buna, am facut si o aplicatie de test ...
Attachments
TestPP.zip
(91.89 KiB) Downloaded 367 times



mesajflaviu
Membru++
Membru++
Posts: 687
Joined: 10 Sep 2008, 21:40
Judet: Cluj

Re: Acces dialog bar in CPreviewView

Post by mesajflaviu » 29 Nov 2012, 13:24

Am gasit ceva asemanator aici, sa vad ce iese ...

User avatar
Ovidiu Cucu
Fondator
Fondator
Posts: 3778
Joined: 11 Jul 2007, 16:10
Judet: Iaşi
Location: Iasi
Contact:

Re: Acces dialog bar in CPreviewView

Post by Ovidiu Cucu » 29 Nov 2012, 15:02

Daca ai pus acea functie CPreviewViewExt::GetToolBar in care-l castuiesti pe m_pToolBar la CPreviewViewDlgBar* nu inseamna ca ai subclasat fereastra toolbar-ului (de fapt un dialogbar) in clasa CPreviewViewDlgBar.
S-ar putea face giumbuslucuri pentru asta insa nu are rost pentru ca, pe scurt si fara prea multe alte comentarii: cu exceptia cazului cand trebuie facute cinestiece cuztomizari bengoase sau se apuca de programat cu MFC Papa OOP al II-lea, NU este nevoie de derivat din CDialogBar.
De ce?
Simplu, pentru ca odata creat, un dialogbar isi vede de treaba lui si, in cazul ca userul da click pe vreun control, trimite notificari la parinte.
NU ramane decat sa prindem si sa tratam acele notificari in clasa parintelui.

Iata un exemplu pentru demo-ul tau:

Code: Select all

class CPreviewViewExt : public CPreviewView
{
   // ...
   afx_msg void OnCheck1Click();
   DECLARE_MESSAGE_MAP()
};

Code: Select all

   ON_BN_CLICKED(IDC_CHECK1, OnCheck1Click)
END_MESSAGE_MAP()

void CPreviewViewExt::OnCheck1Click()
{
   CButton* pCheck = (CButton*)m_pToolBar->GetDlgItem(IDC_CHECK1);
   ASSERT_VALID(pCheck);
   // just for demo purpose: 
   UINT nState = pCheck->GetState() & 0x0003;
   switch(nState)
   {
   case 0:
      TRACE0("unchecked\n");
      break;
   case 1:
      TRACE0("checked\n");
      break;
   case 2:
      TRACE0("3-state\n");
      break;
   }
   // Do the rest.....
}
[ later edit ]
Nu mai pierde vremea prin Codeproject, ca multi au sarit pe-acolo gardul... :D

mesajflaviu
Membru++
Membru++
Posts: 687
Joined: 10 Sep 2008, 21:40
Judet: Cluj

Re: Acces dialog bar in CPreviewView

Post by mesajflaviu » 29 Nov 2012, 15:25

Brici ! Merge super fain ! Thank you.

mesajflaviu
Membru++
Membru++
Posts: 687
Joined: 10 Sep 2008, 21:40
Judet: Cluj

Re: Acces dialog bar in CPreviewView

Post by mesajflaviu » 15 Mar 2013, 17:07

Ma chinui de ceva vreme sa pun in dialogbar-ul din print-preview un slider ... dar nu cred ca pot sa-i "prind" mesajele :

Code: Select all

BEGIN_MESSAGE_MAP(CPreviewViewExt, CPreviewView)
...
	//{{AFX_MSG_MAP(CPreviewViewExt)
	ON_WM_HSCROLL()
	//}}AFX_MSG_MAP
END_MESSAGE_MAP()
....
si

Code: Select all

void CPreviewViewExt::OnHScroll(UINT nSBCode, UINT nPos, CScrollBar* pScrollBar) 
{
	// TODO: Add your message handler code here and/or call default

	TRACE("Aici nu pot sa prind slider-ul\n");

	CPreviewView::OnHScroll(nSBCode, nPos, pScrollBar);
}
Ar fi totusi vreo posibilitate sa stiu cum/cand se misca slider-ul pe dialogbar-ul meu ?

User avatar
Ovidiu Cucu
Fondator
Fondator
Posts: 3778
Joined: 11 Jul 2007, 16:10
Judet: Iaşi
Location: Iasi
Contact:

Re: Acces dialog bar in CPreviewView

Post by Ovidiu Cucu » 15 Mar 2013, 21:31

Am zis ca un dialogbar timite notificarile primite de la copii mai departe parintelui.
Este vorba de notificarile primite via WM_COMMAND (cum ar fi BN_CLICKED) sau despre notificarile primite via WM_NOTIFY (de exemplu NM_RELEASEDCAPTURE).
Nu se intampla acelasi lucru si cu mesajele WM_HSCROLL sau WM_VSCROLL.
Pentru un slider control, ai putea sa te multumesti sa prinzi NM_RELEASEDCAPTURE in clasa CChildFrame.
O mica paranteza: "parintele" la care trimite un controlbar este frame-ul si nu view-ul. Se intampla sa prinzi de exemplu BN_CLICKED in clasa view din cauza modului cum ruteaza MFC-ul comenzile. Nu acelasi lucru intampla si cu notificarile trimise via WM_NOTIFY.

Deci, daca te multumesti cu NM_RELEASEDCAPTURE, atunci scapi usor. Cred ca nu, presupunand ca vrei sa poti varia ceva in mod continuu atunci cand userul trage de slider.
Asa ca, vrand-nevrand trebuie sa faci ceva care in mod uzual nu e nevoie: sa derivezi din CDialogBar.
Tratezi acolo WM_HSCROLL (si/sau WM_VSCROLL) si trimiti mai departe la frame-ul parinte.
Ceva de genul:

Code: Select all

void CMyDialogBar::OnHScroll(UINT nSBCode, UINT nPos, CScrollBar* pScrollBar)
{
   CFrameWnd* pParentFrame = GetParentFrame();
   WPARAM wParam = MAKEWPARAM(nSBCode, nPos);
   LPARAM lParam = (LPARAM)pScrollBar->GetSafeHwnd();
   pParentFrame->SendMessage(WM_HSCROLL, wParam, lParam); 
}
In clasa frame-ului vei avea:

Code: Select all

void CChildFrame::OnHScroll(UINT nSBCode, UINT nPos, CScrollBar* pScrollBar)
{
   CSliderCtrl* pSlider = (CSliderCtrl*)pScrollBar; // ugly but... that's it.
   switch(nSBCode)
   {
   case SB_PAGELEFT:
      // ...
      break;
   case SB_PAGERIGHT:
      // ...
      break;
   // s.a.m.d.
   }
   // s.a.m.d.
}
Sau poti face altceva. De exemplu, poti face toata traba in CMyDialogBar::OnHScroll si in cele din urma trimiti mesaje custom direct view-ului.
Depinde ce vrei si cum iti place.

Acuma partea ce mai urata este sa "subclasezi" dialogbar-ul in CMyDialogBar. CPreviewView are membri destul de bine "ascunsi" pentru ca probabil ca nu s-au gandit ca cineva ar vrea neaparat sa faca chestii gen pus control slide sau cinestie ce alte cularay in afara de butonele si eventual edit-uri. Asa ca, daca de exemplu te gandesti sa dai cu bardita in CView::DoPrintPreview, o sa te ia cu dureri de cap.

Am gasit totusi o cale nu foarte eleganta insa care merge.
Adaugi la clasa derivata din CPreviewView un membru de tipul derivat din CDialogBar.

Code: Select all

class CPreviewViewExt : public CPreviewView
{
   CMyDialogBar m_dlgBar; 
   // ...
};
Pe handlerul lui WM_CREATE, distrugi dialogbar-ul creat de framework, il creezi pe al tau, apoi ii bagi adresa in CPreviewView::m_pToolBar.
Cam asa:

Code: Select all

int CPreviewViewExt::OnCreate(LPCREATESTRUCT lpCreateStruct) 
{
   if(CPreviewView::OnCreate(lpCreateStruct) == -1)
      return -1;

   CWnd* pWndFrame = GetParentFrame();
   VERIFY(m_dlgBar.Create(pWndFrame, IDD_DIALOGBAR_PREVIEW, CBRS_TOP, AFX_IDW_PREVIEW_BAR)); 

   m_pToolBar->DestroyWindow();
   m_pToolBar = &m_dlgBar;

   // alte cularai aici...
   return 0;
}

User avatar
Ovidiu Cucu
Fondator
Fondator
Posts: 3778
Joined: 11 Jul 2007, 16:10
Judet: Iaşi
Location: Iasi
Contact:

Re: Acces dialog bar in CPreviewView

Post by Ovidiu Cucu » 17 Mar 2013, 15:03

O mica rectificare la exemplul din postarea precedenta:
Functioneaza bine atata timp cat dialogbar-ul nu este flotant (asa cum e in implementarea default pentru print preview).
Daca insa vrei sa mergi mai departe cu customizarea si vrei sa faci dialogbar-ul sa poata fi si flotant, atunci obtii intotdeauna frame-ul MDI child nu cu GetParentFrame ci cu GetOwner.
Adica:

Code: Select all

void CMyDialogBar::OnHScroll(UINT nSBCode, UINT nPos, CScrollBar* pScrollBar)
{
   CWnd* pOwner = GetOwner();
   WPARAM wParam = MAKEWPARAM(nSBCode, nPos);
   LPARAM lParam = (LPARAM)pScrollBar->GetSafeHwnd();
   pOwner->SendMessage(WM_HSCROLL, wParam, lParam); 
}

mesajflaviu
Membru++
Membru++
Posts: 687
Joined: 10 Sep 2008, 21:40
Judet: Cluj

Re: Acces dialog bar in CPreviewView

Post by mesajflaviu » 20 Mar 2013, 13:38

N-am apucat sa incerc codul de mai sus, dar voi ajunge si la acel pas ... am o nelamurire: am un control (CPreviewViewExt) derivat din CPreviewView ... acolo am o metoda OnDraw unde desenez in print preview datele din document, iar acolo am nevoie de niste valori din dialogbar-ul custom creat in CPreviewViewExt ... dar ce fac cand printez efectiv ?
Pentru ca pentru print-ul efectiv se apeleaza metoda CMyView::OnDraw, nu CPreviewViewExt::OnDraw (e logic), dar si in acest caz as avea nevoie de valorile din dialogbar-ul printpreview-ului ... unde se implementeaza corect desenarea pentru printpreview si print ? In CMyView::OnDraw sau in CPreviewViewExt::OnDraw ? Daca e corect al doilea caz, cand fac print, cum iau date din dialogbar-ul custom pe care-l am in CPreviewViewExt ?

mesajflaviu
Membru++
Membru++
Posts: 687
Joined: 10 Sep 2008, 21:40
Judet: Cluj

Re: Acces dialog bar in CPreviewView

Post by mesajflaviu » 21 Mar 2013, 09:47

In aplicatia de test atasata la inceputul post-ului desenez totul in CMyView::OnDraw, si datele din dialogbar le tin intr-o structura de date care apartine CMyView (si e si statica, sa fie la fel in orice view deschis), nu stiu daca e o solutie buna, insa acum trebuie sa accesez dialogbar-ul print preview-ului din CMyView(::OnDraw), si este cam nasol ... asta ma face sa cred ca nu e cea mai buna solutie aleasa sa tin datele dialogbar-ului intr-o structura ...

Apoi am modificat aplicatia sa deseneze in CPreviewViewExt::OnDraw, si totul devine mai simplu, dar la OnPrint se apeleaza CMyView::OnDraw si nu CPreviewViewExt::OnDraw ...

User avatar
Ovidiu Cucu
Fondator
Fondator
Posts: 3778
Joined: 11 Jul 2007, 16:10
Judet: Iaşi
Location: Iasi
Contact:

Re: Acces dialog bar in CPreviewView

Post by Ovidiu Cucu » 21 Mar 2013, 14:46

Din nou: locul ideal in care poti prinde notificarile controalelor din dialogbar, atat cele trimise via WM_COMMAND, cat si cele trimise via WM_NOTIFY, este frame-ul (in cazul tau, avand o aplicatie MDI, in clasa CChildFrame derivata din CMDIChildWnd). Tot acolo, facand artificiul din postarile mele anterioare, prinzi si alte mesaje venite de la controale, cum ar fi WM_HSCROLL si WM_VSCROLL, venite sa zicem, de la un control slider. Pana aici, punct si de la capat.
Din frame, nu-i apoi mare lucru sa obtii un pointer la view-ul continut si sa apelezi de acolo ce metoda vrea muschii tai, sa-i trimiti mesaje, s.a.m.d.

User avatar
Ovidiu Cucu
Fondator
Fondator
Posts: 3778
Joined: 11 Jul 2007, 16:10
Judet: Iaşi
Location: Iasi
Contact:

Re: Acces dialog bar in CPreviewView

Post by Ovidiu Cucu » 21 Mar 2013, 15:05

...tot din frame controlezi, daca e nevoie, si dialogbar-ul (sau orice fel de alt control bar) al carui owner este si nu din view, mai ales, Doamne frereste, din OnDraw care-i pentru desenat, atat si nimic mai mult.

User avatar
Ovidiu Cucu
Fondator
Fondator
Posts: 3778
Joined: 11 Jul 2007, 16:10
Judet: Iaşi
Location: Iasi
Contact:

Re: Acces dialog bar in CPreviewView

Post by Ovidiu Cucu » 21 Mar 2013, 16:23

Acuma, cum apelez o metoda din view-ul pentru print preview?
E simplu. Undeva in clasa frame-ului, sa zicem pe handlerul lui WM_HSCROLL, fac ceva de genul.

Code: Select all

void CChildFrame::OnHScroll(UINT nSBCode, UINT nPos, CScrollBar* pScrollBar)
{
// ...
   CView* pView = GetActiveView();
   if(pView->IsKindOf(RUNTIME_CLASS(CPreviewViewExt)))
   {
      // we are in print preview mode
      ((CPreviewViewExt*)pView)->HandleSliderPosChanged();
   }
// ...
}
Iar in CPreviewViewExt am ceva de genul:

Code: Select all

class CPreviewViewExt : public CPreviewView
{
// Attributes
private:
   int m_nSliderPos;

// Operations
public:
   void HandleSliderPosChanged(int nNewValue);
// ...
};

Code: Select all

void CPreviewViewExt::HandleSliderPosChanged()
{
   CSliderCtrl* pSlider = (CSliderCtrl*)m_dlgBar.GetDlgItem(IDC_SLIDER1);
   ASSERT_VALID(pSlider);
   m_nSliderPos = pSlider->GetPos();

   Invalidate();
}
De notat ca, nu am apelat direct CPreviewViewExt::OnDraw direct (sa nu faci asta niciodata) ci Invalidate dintr-o alta metoda.
Posibil insa, ca desenarea in CPreviewViewExt::OnDraw sa nu fie cea mai fericita solutie ci, ar trebui lasata in view-ul de baza (in cazul tau, CTestPPView).
Asa ca, faci altfel:
Pui in CPreviewViewExt o metoda care sa-ti introarca pozitia sliderului.

Code: Select all

class CPreviewViewExt : public CPreviewView
{
// Attributes
private:
   CMyDialogBar m_dlgBar;
// Operations
public:
   int GetSliderControlPos();

Code: Select all

int CPreviewViewExt::GetSliderControlPos()
{
   CSliderCtrl* pSlider = (CSliderCtrl*)m_dlgBar.GetDlgItem(IDC_SLIDER1);
   ASSERT_VALID(pSlider);
   return pSlider->GetPos();
}
Hadlerul lui WM_HSCROLL devine

Code: Select all

void CChildFrame::OnHScroll(UINT nSBCode, UINT nPos, CScrollBar* pScrollBar)
{
   // ...
   CView* pView = GetActiveView();
   if(pView->IsKindOf(RUNTIME_CLASS(CPreviewViewExt)))
   {
      int nSliderPos = ((CPreviewViewExt*)pView)->GetSliderControlPos();
      CDocument* pDoc = GetActiveDocument();
      POSITION pos = pDoc->GetFirstViewPosition();
      while(NULL != pos)
      {
         CView* pView = pDoc->GetNextView(pos);
         if(pView->IsKindOf(RUNTIME_CLASS(CTestPPView)))
         {
            ((CTestPPView*)pView)->HandleSliderPosChanged(nSliderPos);
            pView->Invalidate();
            break;
         }
      }
   }
   // ...
}
Si in fine, in CTestPPView ai

Code: Select all

class CTestPPView : public CScrollView
{
// Attributes
private:
   int m_nSliderPos;
// Operations
public:
   void HandleSliderPosChanged(int nNewValue)
   {
      m_nSliderPos = nNewValue;
   }
// ...
}
Pare imbarligat, insa cred ca asa-i cel mai bine, lasand fiecare clasa sa-si faca traba ei, si ca sa n-o ametesti si mai tare.
De asemenea am scris si testat exemplele astea la repezeala. Poti, de exemplu, scrie mai frumos in CChildFrame niste functii in ajutatoare private care sa-ti intoarca pointer la view-ul pentru print preview car si cel al view-ului care apare cand nu esti in modul print preview.
De asemenea, am lasat sa completezi tu OnHScroll cu switch-ul de rigoare.

User avatar
Ovidiu Cucu
Fondator
Fondator
Posts: 3778
Joined: 11 Jul 2007, 16:10
Judet: Iaşi
Location: Iasi
Contact:

Re: Acces dialog bar in CPreviewView

Post by Ovidiu Cucu » 21 Mar 2013, 18:03

Am pornit, de-acum greu ma mai opresc... :)
Altfel, cred eu ceva mai elegant, mai simplu, care te scuteste de IsKindOf, de include-uri incrucisate si forward declarations, este sa "jonglezi" cu mesaje custom.

Cam asa:

Code: Select all

// TestPP.h
enum e_AppMessages
{
   WM_APP_ISPRINTPREVIEWMODE = WM_APP + 1,
   WM_APP_GETPPSLIDERPOS, 
   WM_APP_PPSLIDERPOSCHANGED,
};

Code: Select all

// PreviewViewExt.h

class CPreviewViewExt : public CPreviewView
{
   // ...
   afx_msg LRESULT OnAppIsPrintPreviewMode(WPARAM wParam, LPARAM lParam);
   afx_msg LRESULT OnAppGetPPSliderPos(WPARAM wParam, LPARAM lParam);
};

Code: Select all

// PreviewViewExt.cpp
   // ...
   ON_MESSAGE(WM_APP_ISPRINTPREVIEWMODE, OnAppIsPrintPreviewMode)
   ON_MESSAGE(WM_APP_GETPPSLIDERPOS, OnAppGetPPSliderPos)
END_MESSAGE_MAP()

LRESULT CPreviewViewExt::OnAppIsPrintPreviewMode(WPARAM wParam, LPARAM lParam)
{
   return TRUE; // just return non-zero to confirm it was handled.
}
LRESULT CPreviewViewExt::OnAppGetPPSliderPos(WPARAM wParam, LPARAM lParam)
{
   CSliderCtrl* pSlider = (CSliderCtrl*)m_dlgBar.GetDlgItem(IDC_SLIDER1);
   ASSERT_VALID(pSlider);
   return pSlider->GetPos();
}

Code: Select all

// ChildFrm.cpp
// ...
void CChildFrame::OnHScroll(UINT nSBCode, UINT nPos, CScrollBar* pScrollBar)
{
   CDocument* pDoc = GetActiveDocument();
   POSITION pos = pDoc->GetFirstViewPosition();
   while(NULL != pos)
   {
      CView* pView = pDoc->GetNextView(pos);
      if(pView->SendMessage(WM_APP_ISPRINTPREVIEWMODE))
      {
         int nNewPos = pView->SendMessage(WM_APP_GETPPSLIDERPOS);
         pView->Invalidate(); 
         pos = pDoc->GetFirstViewPosition();
         while(NULL != pos)
         {
            CView* pView = pDoc->GetNextView(pos);
            if(!pView->SendMessage(WM_APP_ISPRINTPREVIEWMODE))
            {
               pView->SendMessage(WM_APP_PPSLIDERPOSCHANGED, (WPARAM)nNewPos);
               break;
            }
         }
         break;
      }
   }
}

Code: Select all

TestPPView.h

class CTestPPView : public CScrollView
{
// Attributes
private:
   int m_nSliderPos;
// ...
// ...
   afx_msg LRESULT OnAppPPSliderPosChanged(WPARAM wParam, LPARAM lParam);
   DECLARE_MESSAGE_MAP()
};

Code: Select all

// TestPPView.cpp
// ...
      ON_MESSAGE(WM_APP_PPSLIDERPOSCHANGED, OnAppPPSliderPosChanged)
   END_MESSAGE_MAP()

// ...

LRESULT CTestPPView::OnAppPPSliderPosChanged(WPARAM wParam, LPARAM lParam)
{
   m_nSliderPos = (int)wParam;
   return 0;
}
Sper ca-i destul de clar din cod. Daca necesita explicatii suplimentare, no problem, spune-mi si explic.
Oricum, atasez aplicatia ta de test modificata, ca sa poti vedea/testa "pe viu" ce-am facut.
TestPP.zip
(29.61 KiB) Downloaded 365 times
Banui ca vrei sa faci un zoom continuu la print preview. Asa-i?

mesajflaviu
Membru++
Membru++
Posts: 687
Joined: 10 Sep 2008, 21:40
Judet: Cluj

Re: Acces dialog bar in CPreviewView

Post by mesajflaviu » 22 Mar 2013, 10:08

Banui ca vrei sa faci un zoom continuu la print preview. Asa-i?
Da, asta incerc sa fac.

Ovidiu, ne coplesesti (n-o spun doar in numele meu) cu atata cod (sau bunavointa :!: ) ... acum ma apuc de lucru, sa vad ce iese ...

User avatar
Ovidiu Cucu
Fondator
Fondator
Posts: 3778
Joined: 11 Jul 2007, 16:10
Judet: Iaşi
Location: Iasi
Contact:

Re: Acces dialog bar in CPreviewView

Post by Ovidiu Cucu » 22 Mar 2013, 13:17

Ne distram si noi in timpul liber... :)
E bine ca am incercat mai multe solutii, pentru ca asa mai prindem cate ceva din ce se intampla in framework.
Totusi, tragand mai cu atentie un ochi in implemetarea default pentru print preview, am vazut ca s-ar putea si mult mai simplu.
Acum am un pic de treaba asa ca las totusi pe mai tarziu. Probabil ca o sa pun ceva pe blog...

Post Reply