Solved

GetWindowText CMFCMaskedEdit?

Posted on 2009-07-08
23
2,454 Views
Last Modified: 2013-11-20
Hi all,
I got a new problem CMFCMaskedEdit. I have a edit field, and for this edit field, only 16 characters are allowed, the first 6 stays unchanged, the rest is numbers. And I did this using CMFCMaskedEdit.
But now there is a problem, whenever I change the  content of this edit field, and using GetWindowText to get it, it always returns the previous value. For example:
At start, my string is DESTLE, I change it to DESTLE1, use GetWindowText, it returns DESTLE??
then I change it to DESTLE12,  use GetWindowText, it returns DESTLE1??

And here is my code, IDC_EDIT_FLEK is the name of edit field. In function OnSelChangeLE, this line
      m_wndMaskEdit.GetWindowText(m_strFLEK);
always returns the wrong value.

void CDlgSchlagInfo::DoDataExchange(CDataExchange* pDX)
{
      CDialog::DoDataExchange(pDX);      
      DDX_Text(pDX, IDC_EDIT_FLEK, m_strFLEK);
}

BEGIN_MESSAGE_MAP(CDlgSchlagInfo, CDialog)      
      ON_EN_CHANGE(IDC_EDIT_FLEK, OnSelChangeLE)
END_MESSAGE_MAP()

CMFCMaskedEdit m_wndMaskEdit;
BOOL CDlgSchlagInfo::OnInitDialog()
{
      CDialog::OnInitDialog();
      VERIFY(m_wndMaskEdit.SubclassDlgItem( IDC_EDIT_FLEK, this ));
      if (m_strFLEK.IsEmpty())
      {
            
            m_wndMaskEdit.EnableMask(_T("      aaaaaaaaaa"), // The mask string
                  _T("DESTLE__________"), // Literal, "_" char = character entry
                  _T(' ')); // Default char

            m_wndMaskEdit.SetValidChars(_T("1234567890")); // Valid string characters
            m_wndMaskEdit.EnableGetMaskedCharsOnly(FALSE);
            m_wndMaskEdit.SetWindowText(_T("DESTLE          "));
            m_wndMaskEdit.GetWindowText(m_strFLEK);
      }
      
      return TRUE;  // return TRUE unless you set the focus to a control
      // EXCEPTION: OCX Property Pages should return FALSE
}

void CDlgSchlagInfo::OnSelChangeLE()
{
      UpdateData(TRUE);
      bool bLE = false;
      if (m_wndMaskEdit)
            m_wndMaskEdit.GetWindowText(m_strFLEK);

      CString strFLEK;
      CEdit *pEDit = (CEdit *)GetDlgItem(IDC_EDIT_FLEK);      

      if (m_strFLEK.Find(" ") > -1/)
            bLE = true;
      else
            bLE = false;
}

Does anybody have an idea why and give me a suggestion?
Thank you very much.
Regards,
0
Comment
Question by:rambovn
  • 10
  • 7
  • 6
23 Comments
 
LVL 30

Expert Comment

by:Zoppo
ID: 24802432
Hi  rambovn,

sorry, I don't really have an idea, I tested your code in a blank dialog-application an it works fine for me.

But I guess the string you see is not the previous result but for any reason the last character is retrieved as '??' - please try what happens if you replace 'DESTLE12' with 'DESTLE22' by just replacing the '1' with a '2' - if my guess is correct after you entered the second one you should retrieve 'DESTLE2??' instead of 'DESTLE1??'.

If so it maybe a character set related issue, maybe somehow a mixed use of UNICODE and none-UNICODE code. This could IMO declare why you retrieve two '??' instead of the real last character.

But as told, it's just a guess - I wasn't able to reproduce this even when 'playing around' with UNICODE settings.

ZOPPO
0
 
LVL 8

Author Comment

by:rambovn
ID: 24802482
Hi Zoppo, thank you for your quick reply
But because of my text is not so clear, so you misunderstood about the '??' :-) That is my, not from program :-)

I explain here again.
At start, my string is DESTLE, I change it to DESTLE1, use GetWindowText in function OnSelChangeLE(), it returns DESTLE.
then I change it to DESTLE12,  use GetWindowText, it returns DESTLE1, and when I change to DESTLE125, it returns DESTLE12, it always get the previous value :-(

I hope that it's clear now.
0
 
LVL 30

Expert Comment

by:Zoppo
ID: 24802767
Hm - ok, then my guess is wrong ...

Unfortunateley I'm still not able to reproduce this - BTW: IMO you don't need to use 'GetWindowText' at all here. Due to the 'DDX_Text' in 'DoDataExchange' the 'm_strFLEK' should contain the correct text after the call to 'UpdateData()'

IMO in your case the function 'CMFCMaskedEdit::SetValue' doesn't work correct for any reason. You could try debugging it. Just search the file '<Program files>\Microsoft Visual Studio 9.0\VC\atlmfc\src\mfc\afxmaskededit.cpp' and set a breakpoint in 'CMFCMaskedEdit::SetValue' - as soon as you change the text you can debug what happens - check what the 'm_str' is when the end of the function is reached (or if it isn't reached check where the function returns in between).

ZOPPO
0
 
LVL 8

Author Comment

by:rambovn
ID: 24803075
I am trying to debug in afxmaskededit.cpp, but my program goes to function CMFCMaskedEdit::SetValue only when I open the dialog (OnInitDialog), when I change the text, it doesn't go there ............
0
 
LVL 39

Expert Comment

by:itsmeandnobodyelse
ID: 24803265
>>>>      UpdateData(TRUE);
UpdateData(TRUE) gets data from screen to members by calling DoDataExchange. In your case it should fill the new value typed in the edit field to the m_strFLEK member variable.

If that is correct, I don't know why you want to call GetWindowText which hardly could retrieve a different text.

I don't think UpdateData should be called in a handler of a notification of a customized edit. The UpdataData itself sends and posts window messages. All messages send by PostMessage couldn't be processed as you currently are in a handler called from the message queue. So, you must care to not dead-lock and should omit unnecessary calls.

>>>> CMFCMaskedEdit m_wndMaskEdit;
Why is the m_wndMaskEdit a global variable and not a member of CDlgSchlagInfo? Simply add a member variable by using the wizard and have CEdit as class for the control. After that you could replace CEdit by CMFCMaskedEdit in the dialog header and it work. You can omit the subclassing in OnInitDialog cause that was done by DoDataExchange.

>>>> if (m_strFLEK.Find(" ") > -1/)
What is the purpose of the spaces? You shouldn't work with trailing spaces but get rid of them as soon as possible. It only makes trouble.
0
 
LVL 39

Expert Comment

by:itsmeandnobodyelse
ID: 24803340
>>>> I am trying to debug in afxmaskededit.cpp
can you post that source?

Note, any handlers for the customized edit only get called if there is a handler

UINT CMFCMaskedEdit::OnGetDlgCode()
{
    return CEdit::OnGetDlgCode() | DLGC_WANTALLKEYS;
}

and an entry

     ON_WM_GETDLGCODE()

in the message map of the edit class.



 
0
 
LVL 30

Expert Comment

by:Zoppo
ID: 24803440
ok, maybe I found the reason: Could it be that the edit control in the resources is a RichEditControl?

In this case I now was able to reproduce the problem - there it seems that the EN_UPDATE message is sent too late (compared with a normal EditControl). This way I was able to reproduce the behavior you described.

And, fortunateley I found a workaround - just add the attached line before the call to 'GetWindowText' in OnSelChangeLE.

Hope that helps,

ZOPPO




m_wndMaskEdit.SendMessage( WM_COMMAND, MAKEWPARAM( IDC_EDIT_FLEK, EN_UPDATE ), (LPARAM)m_wndMaskEdit.m_hWnd );

Open in new window

0
 
LVL 39

Expert Comment

by:itsmeandnobodyelse
ID: 24803475
>>>> there it seems that the EN_UPDATE message is sent too late
It is the EN_CHANGE notification message (which always is sent after the screen was refreshed).
0
 
LVL 30

Expert Comment

by:Zoppo
ID: 24803497
@itsmeandnobodyelse, JFI: CMFCMaskedEdit (from afxmaskededit.cpp) is part of Microsoft's MFCFeaturePack, so it's no source the developer can change. Unfortunateley some time ago I noticed that this feature pack is designed/developed in a way it doesn't work correctly for some kind of none-standard constructs which worked fine with 'old' MFC.

This CMFCMaskedEdit has it's own 'SetWindowText' and 'GetWindowText' - 'GetWindowText' just returns a cached value which is updated in a 'EN_UPDATE' handler.

Anyway, I agree that in the posted code a 'GetWindowText' isn't needed since the 'UpdateData' fills the 'm_strFLEK' correctly ...

ZOPPO
0
 
LVL 39

Accepted Solution

by:
itsmeandnobodyelse earned 350 total points
ID: 24803929
>>>> CMFCMaskedEdit (from afxmaskededit.cpp) is part of Microsoft's MFCFeaturePack
Thanks, I already wondered about the naming ...

>>>> This CMFCMaskedEdit has it's own 'SetWindowText' and 'GetWindowText'
???
You can overcome this by calling

   GetDlgItem(IDC_EDIT_FLEK)->GetWindowText(strFLEK);

Here the GetDlgItem returns a CWnd* and as GetWindowText isn't a virtual function it will return the text already shown at screen.


Anyway, the CMFCMaskedEdit variable should no made a global but a member of the dialog and the subclassing should be done by DoDataExchange.

To catch edit messages of the edit control you must derive your own edit class from CMFCMaskedEdit and have a OnGetDlgCode as I described for CMFCMaskedEdit. Instead of replacing CEdit by CMFCMaskedEdit, you then would use your edit class instead.

Again, The OnEnChange handler isn't the right place to calling UpdateData(TRUE) as it was called for each character entered. You better use the OnKillFocus which was called when the focus changes from the edit to any other field.

   



0
 
LVL 30

Assisted Solution

by:Zoppo
Zoppo earned 150 total points
ID: 24804227
> >>>> This CMFCMaskedEdit has it's own 'SetWindowText' and 'GetWindowText'
> ???

Yes, that's a bit annoying ...

> GetDlgItem(IDC_EDIT_FLEK)->GetWindowText(strFLEK);
 This works fine at this place

> ... the CMFCMaskedEdit variable should no made a global but a member of the dialog and the subclassing should be done by DoDataExchange.

I absoluteley agree ...

> ...OnEnChange handler isn't the right place to calling UpdateData(TRUE) as it was called for each character entered.

I guess 'rambovn' wants to validate the entered string before the focus is moved to another control, i.e. to enable/disable a button depending on the entered text.


As mentioned before: anything 'rambovn' did works fine as long as the CMFCMaskedEdit is used with a normal edit-control, so I think the 'OnGetDlgCode' thing isn't needed. The problem IMO comes from a different functionality in rich-edit-controls.

Here's a short snippet of CMFCMaskedEdit's EN_UPDATE handler:

>       CString str;
>      CWnd::GetWindowText(str);
>
>      if (m_str != str && !m_bPasteProcessing)
>     { // here CMFCMaskedEdit::SetValue is called

Here 'm_str' is the above mentioned cached string. So, the function checks if the text actually set in the control differs from the cached string and, if so calls 'SetVallue' to evaluate the modified text.

It seems that in case of a rich-edit-control the text it contains is set with SetWindowText after(!) the EN_UPDATE is sent. Therefor the 'm_str != str' is true after the second change, but 'str' always contains the text of the control before(!) the last change occured.


Anyhow, any of these three solution would work:

1. Directly use 'm_strFLEK' after 'UpdateData' without calling 'GetWindowText'
2. Call 'GetWindowText' for a base class (i.e. CWnd, you did show the sample 'GetDlgItem(IDC_EDIT_FLEK)->GetWindowText(strFLEK);')
3. Sending this 'EN_UPDATE' message which I suggested first.

I would prefer 1. because it's quite usual for MFC ...

ZOPPO


0
Do You Know the 4 Main Threat Actor Types?

Do you know the main threat actor types? Most attackers fall into one of four categories, each with their own favored tactics, techniques, and procedures.

 
LVL 39

Expert Comment

by:itsmeandnobodyelse
ID: 24804730
>>>> I would prefer 1. because it's quite usual for MFC ...

I would prefer  2. as it exactly gets the text wanted and not the whole screen.

BTW, what does that CMFCMaskedEdit ? Is it an editor for structured license keys or similar?
0
 
LVL 39

Expert Comment

by:itsmeandnobodyelse
ID: 24804794
>>>> wants to validate the entered string before the focus is moved to another control, i.e. to enable/disable a button depending on the entered text.
The OnKillFocus was called before a button or a next control gets the focus. So you could disable the next button even if it was the one which should get the focus.

0
 
LVL 30

Expert Comment

by:Zoppo
ID: 24804868
ok, 2. is good also ...


CMFCMaskedEdit can be used to define a special edit-mask which is then handled automatically by the control.

In the sample code above this control handles following:

- The first six characters of the control are not editable and always show 'DESTLE'
- The next ten characters are editable but can't be anything else than numbers
- For editable character the developer can decide which character is shown if none of the valid characters is set at that position

The control can optionally return the complete content with 'GetWindowText' or just the editable part.

You can see documentation (which is quite short) here: http://msdn.microsoft.com/en-us/library/bb982230.aspx


About the focus thing I have to say I never liked dialogs which were implemented like this - the user can click on a button which then suddenly does nothing expected (and may be disabled either allthough it wasn't before) - IMO this is nearly as annoying as the DDV_... macros which with pressing 'OK' bring up a dialog with something like 'This must be a number between x and y' or similar. But I guess that's a matter of peronal preference ...


ZOPPO
0
 
LVL 39

Expert Comment

by:itsmeandnobodyelse
ID: 24805381
>>>> the user can click on a button which then suddenly does nothing expected
You are right, but catching each single char is not so much better. You never know whether it is the last char or not and posting error messages at any typo surely isn't the ultimo ratio.  

>>>> (and may be disabled either allthough it wasn't before)
Hmm. If the logic behind enabling/disabling isn't a mystery, that rarely should happen. I mostly let the buttons enabled but check the contents of all edits before allowing any further action.
0
 
LVL 8

Author Comment

by:rambovn
ID: 24811144
Wow I am impressed with the knowledge of both of you. I am just a newbie, if I had such a knowledge, this question maybe wasn't here :-) I learned a lot from yr discussions. Thank you very much for yr time and patience.
@Zoppo: you understood exactly correct what I did and explained to itsmeandnobodyelse like you asked the question. Thank you very much. You're right, there are another 2 comboboxes depending on this edit field.
For yr 1st solution, I checked it again, and my edit field is editcontrol not richeditcontrol. I also tried yr code, but it didn't work. Now I am using KillFocus like itsmeandnobodyelse wrote. I have to test it very carefully because yesterday there're already some unexpected errors when using it. For me, like you, I find this KillFocus is not so comfortable, but it took me already one day for this error, so I have to use it.
@itsmeandnobodyelse: first, thank you very much.
- After UpdateData(), m_strFLEK is wrong, that's why I tried to use GetWindowText, but it's also wrong.
- Thanks for your suggestion, I already used _ instead of space. And space is annoying, you're right.
- I am using KillFocus, hope everything will work well.

And again, thank you very much to both of you. And I let this topic open 1-2 days more in case I have problem/ question related to this.
0
 
LVL 39

Expert Comment

by:itsmeandnobodyelse
ID: 24811773
>>>> that's why I tried to use GetWindowText, but it's also wrong.
Did you try

  GetDlgItem(IDC_EDIT_FLEK)->GetWindowText(strFLEK);

?

As Zoppo explained, the customized edit control has a *own* GetWindowText which retrieves the value from a member variable rather than from the screen. By using GetDlgItem(IDC_EDIT_FLEK) you should get the *original* and so the correct value.

You can make it totally sure that the :: GetWindowText wasn't called by

    GetDlgItem(IDC_EDIT_FLEK)->CWnd::GetWindowText(strFLEK);  // I WANT THAT ONE !!!

>>>> I am using KillFocus, hope everything will work well.

Note, there are two possible handlers for KillFocus:

(A) Handling the EN_KILLFOCUS notification message send to the dialog when the edit control looses the focus.
You create that handler by right-clicking on your edit-control in the resource editor and choose 'Add event handler ...'. Then selec EN_KILLFOCUS notification from combobox.

sample:

void RecipeDlg::OnKillFocusPrdAmount()
 
{
    CString strProdAmount = m_strAmount;  // save old value stored as member
    UpdateData(TRUE);   // get updates from screen

    if (strProdAmount != m_strAmount)   // check if changed
    {


Note, when the focus was lost, the UpdateData(TRUE) *must* bring the new value as the editing was definitively terminated (contrary to EN_CHANGE or EN_UPDATE notification).

(B) If you have a own edit control class derived from  CMFCMaskedEdit you can handle the WM_KILLFOCUS message.

In the header of your edit control class specify the member function

class MyEditCtrl : public CMFCMaskedEdit
{
     ....
public:
     afx_msg void OnKillFocus( CWnd* pNewWnd );
     ....
};

In the cpp of the edit control add ON_WM_KILLFOCUS to the message map


BEGIN_MESSAGE_MAP(MyEditCtrl , CMFCMaskedEdit)
      //{{AFX_MSG_MAP(CMFCMaskedEdit)
                ...
      ON_WM_KILLFOCUS()
      //}}AFX_MSG_MAP
END_MESSAGE_MAP()

and implement it like

void MyEditCtrl::OnKillFocus(CWnd* pNewWnd)
{
    CString str;
    CWnd::GetWindowText( str );

    if(pNewWnd == GetDlgItem(IDOK))
    {
         ...
    }
    // finally call the baseclass
    CMFCMaskedEdit::OnKillFocus(pNewWnd);
}

The (B) has the advantage that it tells you what control is going to get the focus

>>>> ... the CMFCMaskedEdit variable should no made a global
>>>>  but a member of the dialog and the subclassing should be done by DoDataExchange

Did you process on that? Using global variables really is a bad workaround. You will get problems with it sooner or later.

0
 
LVL 8

Author Comment

by:rambovn
ID: 24814448
>>>> ... the CMFCMaskedEdit variable should no made a global
>>>>  but a member of the dialog and the subclassing should be done by DoDataExchange

Did you process on that? Using global variables really is a bad workaround. You will get problems with it sooner or later.

--> m_wndMaskEdit is at the beginning a variable of CDlgSchlagInfo class, I defined it in dlgschlaginfo.h, maybe I should learn how to make a name for a variable, IMO, for the class variable using m_..., for the function variable without m_? Is that the correct way for naming in programming?
But one more question, I didn't really get what you meant with 'subclassing should be done by DoDataExchange'?? That meant not in OnInitDialog like I did? Can you explain me a little bit more?

Did you try

  GetDlgItem(IDC_EDIT_FLEK)->GetWindowText(strFLEK);?

--> Yes, I did, but it didn't work.

Now I am using KillFocus like yr second way, luckily till now it works quite fine.


0
 
LVL 39

Expert Comment

by:itsmeandnobodyelse
ID: 24814985
>>>> That meant not in OnInitDialog like I did?

Yes, the DoDataExchange has three tasks:

1. subclassing the controls defined as members
2. put data from class to screen
3. put data from screen to class

For (1):

If you define a control as a member (using class wizard) you'll get a new member of type CEdit in the dialog class header and a new entry into DoDataExchange like


        DDX_Control(pDX, IDC_EDIT_FLEK, m_editFLEK);

When your dialog was created it calls DoDataExchange with a mode (stored in the pDX)  that causes the DDX_Control function to do the subclassing for the control with IDC_EDIT_FLEK and member m_editFLEK. It would use a similar sequence as the one you used in OnInitDialog.

If you now change the CEdit to CMFCMaskEdit in your dialog class header, the subclassing would be made for CMFCMaskEdit (and not for CEdit only). Subclassing means that for message handling the windows procedure of CMFCMaskEdit was called and not the one of CEdit. That is a precondition that the messages for the edit window were directed to the right class

>>>> GetDlgItem(IDC_EDIT_FLEK)->GetWindowText(strFLEK)
>>>> .--> Yes, I did, but it didn't work.
Hmmm. Not quite convinced as the GetWindowText isn't a virtual function. If you got the old value when calling this, it would mean that the screen has the old value as well ...

You might try

  GetDlgItem(IDC_EDIT_FLEK)->CWnd::GetWindowText(strFLEK);  // I WANT THAT ONE !!!

in if you got some time.


>>>> OnSelChange

Rereading your initial question I recognized that your handler was the OnSelChange (handling EN_SEL_CHANGE notification) and not OnChange (handling EN_CHANGE notification) as I thought. Then it makes more sense that the internal string wasn't updated cause the EN_SEL_CHANGE was a notification of a customized edit control that a 'selection' was changed what may happen *before* the full change is made on screen.


>>> Is that the correct way for naming in programming?

the naming convention of MFC is similar to that named as 'hungarian'. It is not widely accepted by developers. I personaly find it senseful to know member variables from local variables and were using the m_ or the my prefix. With nowadays code I rarely use type prefixes like in strText, dblTotal but I normally use a p or ptr prefix for pointers. For arrays and maps I either use the plural like in  string suppliers[MAX_SUPP]; or use the suffix Arr or Map. But my (only) goal with this is that I have a fair chance to see from a variable what purpose it has even if I look at it some years later and - when newly developing - that I know the name of a variable even if it was defined in a header or 1000 lines above in my source.


 
0
 
LVL 8

Author Comment

by:rambovn
ID: 24821056
itsmeandnobodyelse: Thanks a lot for yr patience again. I already used the subsclassing with DoDataExchange. It worked well.

You might try

 GetDlgItem(IDC_EDIT_FLEK)->CWnd::GetWindowText(strFLEK);  // I WANT THAT ONE !!!

in if you got some time.

----> It also didn't work.

>>>> OnSelChange
Rereadingyour initial question I recognized that your handler was theOnSelChange (handling EN_SEL_CHANGE notification) and not OnChange(handling EN_CHANGE notification) as I thought. Then it makes moresense that the internal string wasn't updated cause the EN_SEL_CHANGEwas a notification of a customized edit control that a 'selection' waschanged what may happen *before* the full change is made on screen.
-----> No, I don't think so, because when I used normal CEdit, it worked perfectly, it worked until I changed to CMFCMaskedEdit. So I think there're something wrong with this class, not with my code.


0
 
LVL 39

Expert Comment

by:itsmeandnobodyelse
ID: 24821325
>>>> because when I used normal CEdit, it worked perfectly

The OnSelChange is - in my docs - only supported for rich edit controls. It seems that the CMFCMaskedEdit (and like you told) also the CEdit nevertheless do send the notification though they were *NOT* rich edit controls. But as the EN_SELCHANGE not really was supported you hardly could complain if it doesn't work or works differently in CMFCMaskedEdit and CEdit.

I would like to know whether the EN_CHANGE notification works correctly as it explicitly was *defined* as to run *after* screen update. The problem with EN_CHANGE was that it will be called after each single character typed. So you can't easily take it as a replacement for OnKillFocus or even OnSelChange as the edit string you retrieve may not be complete. But it would be interesting to know whether the CWnd::GetWindowText would return the initial value or the new value.


>>>> I already used the subsclassing with DoDataExchange. It worked well.
Good. Very good. I love success messages ;-)
0
 
LVL 8

Author Comment

by:rambovn
ID: 24821421
Now I have a little bit time to play with the code so I read my code again and find out that I already used ON_EN_CHANGE, and in my property page for this edit field, there're no events for SEL_CHANGE or sth similar, there're only SEL_CHANGE for combobox, maybe the way I named the function made you confused :-) Or did I misunderstand something?

0
 
LVL 8

Author Comment

by:rambovn
ID: 24846907
Thank you very much again for all of yr help. I will close my question :-)
0

Featured Post

How to improve team productivity

Quip adds documents, spreadsheets, and tasklists to your Slack experience
- Elevate ideas to Quip docs
- Share Quip docs in Slack
- Get notified of changes to your docs
- Available on iOS/Android/Desktop/Web
- Online/Offline

Join & Write a Comment

Introduction: Dynamic window placements and drawing on a form, simple usage of windows registry as a storage place for information. Continuing from the first article about sudoku.  There we have designed the application and put a lot of user int…
Introduction: Database storage, where is the exe actually on the disc? Playing a game selected randomly (how to generate random numbers).  Error trapping with try..catch to help the code run even if something goes wrong. Continuing from the seve…
This video will show you how to get GIT to work in Eclipse.   It will walk you through how to install the EGit plugin in eclipse and how to checkout an existing repository.
This video shows how to remove a single email address from the Outlook 2010 Auto Suggestion memory. NOTE: For Outlook 2016 and 2013 perform the exact same steps. Open a new email: Click the New email button in Outlook. Start typing the address: …

758 members asked questions and received personalized solutions in the past 7 days.

Join the community of 500,000 technology professionals and ask your questions.

Join & Ask a Question

Need Help in Real-Time?

Connect with top rated Experts

21 Experts available now in Live!

Get 1:1 Help Now