IdentifiantMot de passe
Loading...
Mot de passe oublié ?Je m'inscris ! (gratuit)
Navigation

Inscrivez-vous gratuitement
pour pouvoir participer, suivre les réponses en temps réel, voter pour les messages, poser vos propres questions et recevoir la newsletter

MFC Discussion :

Pb Handle, GetDlgItem() et TimerProc


Sujet :

MFC

  1. #1
    Membre confirmé
    Inscrit en
    Juin 2006
    Messages
    126
    Détails du profil
    Informations forums :
    Inscription : Juin 2006
    Messages : 126
    Par défaut Pb Handle, GetDlgItem() et TimerProc
    Bonjour à tous!

    J'aimerai utiliser la fonction GetDlgItem dans une de mes fonctions CALLBACK timerproc. Comme il faut lui donner en argument le handle de la fenêtre j'ai chercher à utiliser la fonction GetSafeHwnd().

    Alors j'ai fait comme ca:
    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
     
    InstantResult::InstantResult(CWnd* pParent /*=NULL*/)
    	: CDialog(InstantResult::IDD, pParent)
    {
    	HWND hwnd;
    	hwnd = GetSafeHwnd();
    	m_IdTimer=::SetTimer(hwnd,0,1000,TimerProc);  // création d'un timer qui bat à la seconde
    	m_IdTimer2=::SetTimer(hwnd,1,500,TimerProc2);  // création d'un timer qui bat à la seconde
     
    }
     
    VOID CALLBACK TimerProc(HWND /*hwnd*/,UINT /*uMsg*/,UINT_PTR /*idEvent*/,DWORD /*dwTime*/)
    {
      // timer function
     
    	unsigned char NoAuthmov[1]={141}; // not allowed to move command
    			WriteFile(hcom,NoAuthmov, 1, &m, NULL);
     
    }
     
    VOID CALLBACK TimerProc2(HWND hwnd ,UINT /*uMsg*/,UINT_PTR /*idEvent*/,DWORD /*dwTime*/)
    {
     
    			CCom::PurgeTx();
    			CCom::ReadBuffer();
     
    			CEdit* editFiD=(CEdit*) GetDlgItem(hwnd,IDC_EDIT4);
    			editFiD->SetWindowText(data);
     
    }
    Je ne comprends pas ce que j'ai fait de travers...

    A l'exécution j'ai un .exe has triggered a breakpoint et la ligne surlignée est Debug Break;
    }

    et

    CWnd::SetWindowtext
    ENSURE(this)

    Voyez vous sinon un autre moyen d'utiliser GetDlgItem dans mon timerproc ?
    Merci d'avance

  2. #2
    Rédacteur
    Avatar de farscape
    Homme Profil pro
    Développeur informatique
    Inscrit en
    Novembre 2003
    Messages
    9 055
    Détails du profil
    Informations personnelles :
    Sexe : Homme
    Localisation : France, Alpes Maritimes (Provence Alpes Côte d'Azur)

    Informations professionnelles :
    Activité : Développeur informatique
    Secteur : High Tech - Éditeur de logiciels

    Informations forums :
    Inscription : Novembre 2003
    Messages : 9 055
    Par défaut
    salut,
    tu es dans une CDialog , alors pourquoi tu n'utilises pas la gestion du timer associé a une fenêtre MFC ?
    http://cpp.developpez.com/faq/vc/ind...=CWnd#SetTimer
    note: cette utilisation résoudra ton problème d'accès a GetDlgItem...

  3. #3
    Membre confirmé
    Inscrit en
    Juin 2006
    Messages
    126
    Détails du profil
    Informations forums :
    Inscription : Juin 2006
    Messages : 126
    Par défaut
    Euhhh je veux bien réessayer mais dans un post, stephdin m'avait dit ca :

    utilises la fonction globale SetTimer() en indiquant une procédure et non la méthode SetTimer() de la classe CWnd

    Code :

    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
     
    m_IdTimer=::SetTimer(NULL,0,1000,TimerProc);  // création d'un timer qui bat à la seconde
     
    ...
     
    KillTimer(m_IdTimer);  // pour arreter le timer
     
    ...
     
    VOID CALLBACK TimerProc(HWND /*hwnd*/,UINT /*uMsg*/,UINT_PTR /*idEvent*/,DWORD /*dwTime*/)
    {
      // ici la procédure du timer
    }

  4. #4
    Membre confirmé
    Inscrit en
    Juin 2006
    Messages
    126
    Détails du profil
    Informations forums :
    Inscription : Juin 2006
    Messages : 126
    Par défaut
    J'ai donc fait ca en m'inspirant de ta faq:
    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
     
    InstantResult::InstantResult(CWnd* pParent /*=NULL*/)
    	: CDialog(InstantResult::IDD, pParent)
    {
    	OnStartTimer();
    	OnTimer(1);
     
    }
    void InstantResult::OnStartTimer() 
    {
    	m_nTimer =SetTimer(1, 2000, 0);
    }
     
    void InstantResult::OnStopTimer() 
    {
        KillTimer(m_nTimer);  
    }
     
    void InstantResult::OnTimer(UINT nIDEvent) 
    {
        switch(nIDEvent)
        {
            case 1:AfxMessageBox(_T("cakette"));
            default:break;
        }    
        // Call base class handler.
        InstantResult::OnTimer(nIDEvent);
    }
    et j'ai le problème Debug Assertion Failed qui pointe dans afxwin2.inl sur la ligne SetTimer.
    Si je fais continuer lors de l'erreur j'ai bien mon message.
    D'où cela peut il venir ?

  5. #5
    Rédacteur
    Avatar de farscape
    Homme Profil pro
    Développeur informatique
    Inscrit en
    Novembre 2003
    Messages
    9 055
    Détails du profil
    Informations personnelles :
    Sexe : Homme
    Localisation : France, Alpes Maritimes (Provence Alpes Côte d'Azur)

    Informations professionnelles :
    Activité : Développeur informatique
    Secteur : High Tech - Éditeur de logiciels

    Informations forums :
    Inscription : Novembre 2003
    Messages : 9 055
    Par défaut
    mais dans ce dernier cas tu n'étais pas dans une fenêtre MFC

    tu n'as toujours pas compris qu'il y a deux manières de voir les timers:
    soit par l'api win32 : la solution dont tu parles.
    soit ce que propose les fenêtres MFC qui n'est qu'un encapsulation de l'api win32.
    dans ton cas ici présent c'est cette solution qui est préférable.
    pourquoi s'embêter avec une callback alors que la fenetre MFC le gère ?

  6. #6
    Rédacteur
    Avatar de farscape
    Homme Profil pro
    Développeur informatique
    Inscrit en
    Novembre 2003
    Messages
    9 055
    Détails du profil
    Informations personnelles :
    Sexe : Homme
    Localisation : France, Alpes Maritimes (Provence Alpes Côte d'Azur)

    Informations professionnelles :
    Activité : Développeur informatique
    Secteur : High Tech - Éditeur de logiciels

    Informations forums :
    Inscription : Novembre 2003
    Messages : 9 055
    Par défaut
    normal...
    une fenêtre est valide graphiquement a partir du déclenchement de la méthode:
    OnInitDialog pour une boîte de dialogue.
    OnInitialUpdate pour une vue.
    pas dans le constructeur, m_hwnd est null....
    initialise ton timer dans OnInitDialog.

  7. #7
    Membre confirmé
    Inscrit en
    Juin 2006
    Messages
    126
    Détails du profil
    Informations forums :
    Inscription : Juin 2006
    Messages : 126
    Par défaut
    Ok j'ai fait alors ma fonction OnInitDialog :
    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
     
    BOOL InstantResult::OnInitDialog()
    {
     
    	CCom::ClosePortCom();
    	CCom::InitPortCom();
    	OnStartTimer();
     
    	OnTimer(1);
     
    	return(TRUE);
    }
    Mais c'est bizarre pcq ma MessageBox est affichée avant que je ne sois sur ma fenêtre. Est ce normal ?

  8. #8
    Rédacteur
    Avatar de farscape
    Homme Profil pro
    Développeur informatique
    Inscrit en
    Novembre 2003
    Messages
    9 055
    Détails du profil
    Informations personnelles :
    Sexe : Homme
    Localisation : France, Alpes Maritimes (Provence Alpes Côte d'Azur)

    Informations professionnelles :
    Activité : Développeur informatique
    Secteur : High Tech - Éditeur de logiciels

    Informations forums :
    Inscription : Novembre 2003
    Messages : 9 055
    Par défaut
    quelle messagebox ?

  9. #9
    Membre confirmé
    Inscrit en
    Juin 2006
    Messages
    126
    Détails du profil
    Informations forums :
    Inscription : Juin 2006
    Messages : 126
    Par défaut
    Rahhh bon je craque un peu la !!
    Juste tu peux me dire pourquoi selon toi je n'arrive pas à envoyer à écrire sur mon port (alors que j'arrive à écrire coucou):

    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    41
    42
    43
    44
    45
    46
    47
    48
    49
    50
    51
    52
    53
    54
    55
    56
    57
    58
    59
    60
    61
    62
    63
    64
    65
    66
    67
    68
    69
    70
    71
    72
    73
    74
    75
    76
    77
    78
    79
     
    // InstantResult.cpp : implementation file
    //
     
    #include "stdafx.h"
    #include "InstantResult.h"
    #include "global.h"
    #include "Comm.h"
     
     
    // InstantResult dialog
     
    IMPLEMENT_DYNAMIC(InstantResult, CDialog)
     
     
     
    InstantResult::InstantResult(CWnd* pParent /*=NULL*/)
    	: CDialog(InstantResult::IDD, pParent)
    {
     
     
    }
     
    BOOL InstantResult::OnInitDialog()
    {
     
    	CCom::ClosePortCom();
    	CCom::InitPortCom();
    	OnStartTimer();
    	OnTimer(1);
    	return(TRUE);
    }
     
    InstantResult::~InstantResult()
    {
    }
     
    void InstantResult::DoDataExchange(CDataExchange* pDX)
    {
    	CDialog::DoDataExchange(pDX);
     
    }
     
     
    BEGIN_MESSAGE_MAP(InstantResult, CDialog)
    END_MESSAGE_MAP()
     
     
    void InstantResult::OnStartTimer() 
    {
    	m_nTimer =SetTimer(1, 1000, 0);
    }
     
     
    void InstantResult::OnStopTimer() 
    {
        KillTimer(m_nTimer);  
    }
     
    void InstantResult::OnTimer(UINT nIDEvent) 
    {
    	unsigned char NoAuthmov[1]={141}; // not allowed to move command
    	CEdit* editFiD=(CEdit*) GetDlgItem(IDC_EDIT4);
     
        switch(nIDEvent)
        {
     
    	case 1://AfxMessageBox(_T("coucou"));
    		//editFiD->SetWindowText("coucou"); 
     
    			WriteFile(hcom,NoAuthmov, 1, &m, NULL);
    			break;
     
     
            default:break;
        }    
        // Call base class handler.
        //InstantResult::OnTimer(nIDEvent);
    }

  10. #10
    Membre confirmé
    Inscrit en
    Juin 2006
    Messages
    126
    Détails du profil
    Informations forums :
    Inscription : Juin 2006
    Messages : 126
    Par défaut
    J'ai fait un test avec une AfxMessageBox qui s'ouvrait avant d'arriver sur la fenêtre mais ce n'est pas bien grave

  11. #11
    Membre confirmé
    Inscrit en
    Juin 2006
    Messages
    126
    Détails du profil
    Informations forums :
    Inscription : Juin 2006
    Messages : 126
    Par défaut
    Bon j'ai tout refait et j'arrive bien à envoyer des données sur mon port. Par contre est ce à cause de la ligne
    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    //CDialog::OnTimer(nIDEvent);
    que je n'arrive pas a envoyer en continue toutes les 100 ms. Je n'arrive qu'à en envoyer un.

    Le problème est que si je dé commente cette ligne j'ai une erreur à l'exécution.

  12. #12
    Membre confirmé
    Inscrit en
    Juin 2006
    Messages
    126
    Détails du profil
    Informations forums :
    Inscription : Juin 2006
    Messages : 126
    Par défaut
    En fait si ca l'envoie plusieurs fois... mais ca s'arrête ensuite avec une erreur pendant la compilation du style violation ...

    C'est à cause de quoi ?

  13. #13
    Rédacteur
    Avatar de farscape
    Homme Profil pro
    Développeur informatique
    Inscrit en
    Novembre 2003
    Messages
    9 055
    Détails du profil
    Informations personnelles :
    Sexe : Homme
    Localisation : France, Alpes Maritimes (Provence Alpes Côte d'Azur)

    Informations professionnelles :
    Activité : Développeur informatique
    Secteur : High Tech - Éditeur de logiciels

    Informations forums :
    Inscription : Novembre 2003
    Messages : 9 055
    Par défaut
    c'est confus tout ça ...
    c'est pendant l'exécution je suppose pas pendant la compilation...
    de toute façon comment veux tu que l'on réponde avec si peu de précisions ?
    si c'est une violation d'accès il suffit de remonter la pile des appels pour trouver la ligne de code responsable.

  14. #14
    Membre confirmé
    Inscrit en
    Juin 2006
    Messages
    126
    Détails du profil
    Informations forums :
    Inscription : Juin 2006
    Messages : 126
    Par défaut
    Désolée je suis trop dans mon programme j'ai pas assez de recul.

    Je reprends.
    J'ai suivi tes conseils et j'ai maintenant le programme suivant :
    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    41
    42
    43
    44
    45
    46
    47
    48
    49
    50
    51
    52
    53
    54
    55
    56
    57
    58
    59
    60
    61
    62
    63
    64
    65
    66
    67
    68
    69
    70
    71
    72
     
    // InstantResult.cpp : implementation file
    //
     
    #include "stdafx.h"
    #include "InstantResult.h"
    #include "Comm.h"
     
     
    // InstantResult dialog
     
    IMPLEMENT_DYNAMIC(InstantResult, CDialog)
     
     
     
    InstantResult::InstantResult(CWnd* pParent /*=NULL*/)
    	: CDialog(InstantResult::IDD, pParent)
    {
     
     
    }
    BOOL InstantResult::OnInitDialog()
    {
     
    	OnStartTimer();
    	OnTimer(1);
    	OnStopTimer();
    	return(TRUE);
    }
    InstantResult::~InstantResult()
    {
    }
     
    void InstantResult::DoDataExchange(CDataExchange* pDX)
    {
    	CDialog::DoDataExchange(pDX);
     
    }
     
    BEGIN_MESSAGE_MAP(InstantResult, CDialog)
    END_MESSAGE_MAP()
     
    // InstantResult message handlers
     
    void InstantResult::OnStartTimer()
    {
    	m_nTimer = SetTimer(1,2000,0);
    }
     
    void InstantResult::OnStopTimer()
    {
    	KillTimer(m_nTimer);
    }
     
    void InstantResult::OnTimer(UINT nIDEvent) 
    {
    	unsigned char NoAuthmov[1]={141}; // not allowed to move command
     
        switch(nIDEvent)
        {
     
    	case 1:     WriteFile(hcom,NoAuthmov, 1, &m, NULL);
    			Sleep(1000);
    			CCom::PurgeTx();
    			break;
     
     
            default:break;
        }    
        // Call base class handler.
    	CDialog::OnTimer(nIDEvent);
    }
    Mon premier problème était que je n'arrivais pas à envoyer toutes les 2 s la variable. je crois avoir résolu ce problème.

    Mon problème actuel est que je n'accède pas à ma fenêtre InstantResult. Je reçoie bien les valeurs toutes les 2s mais je n'accède pas à ma fenêtre.
    Tu as une idée ?

  15. #15
    Rédacteur
    Avatar de farscape
    Homme Profil pro
    Développeur informatique
    Inscrit en
    Novembre 2003
    Messages
    9 055
    Détails du profil
    Informations personnelles :
    Sexe : Homme
    Localisation : France, Alpes Maritimes (Provence Alpes Côte d'Azur)

    Informations professionnelles :
    Activité : Développeur informatique
    Secteur : High Tech - Éditeur de logiciels

    Informations forums :
    Inscription : Novembre 2003
    Messages : 9 055
    Par défaut
    Mon problème actuel est que je n'accède pas à ma fenêtre InstantResult. Je reçoie bien les valeurs toutes les 2s mais je n'accède pas à ma fenêtre.
    ça veut dire quoi?
    tu es dans la fenêtre dans le timer.

  16. #16
    Membre confirmé
    Inscrit en
    Juin 2006
    Messages
    126
    Détails du profil
    Informations forums :
    Inscription : Juin 2006
    Messages : 126
    Par défaut
    En fait je suis sur une fenêtre "ForceSettings" sur laquelle je demande des paramêtres à l'utilisateur.Aprés cette fenêtre, l'utilisateur arrive sur une autre fenêtre "InstantResult" et c'est la que je veux que le timer se déclenche.
    Pour le moment il se déclenche avant d'arriver à la fenêtre "InstantResult"
    Donc en fait si je teste un faisant qu'un envoie il envoie puis il ouvre la fenêtre.

    Il faut pas que j'appel OnStartTimer et OnTimer dans OnInitDialog ?

  17. #17
    Rédacteur
    Avatar de farscape
    Homme Profil pro
    Développeur informatique
    Inscrit en
    Novembre 2003
    Messages
    9 055
    Détails du profil
    Informations personnelles :
    Sexe : Homme
    Localisation : France, Alpes Maritimes (Provence Alpes Côte d'Azur)

    Informations professionnelles :
    Activité : Développeur informatique
    Secteur : High Tech - Éditeur de logiciels

    Informations forums :
    Inscription : Novembre 2003
    Messages : 9 055
    Par défaut
    , je ne comprends plus rien ....
    ce n'est pas déjà le cas ?
    c'est bien toi qui a ecris:
    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    3
    4
    5
    6
    7
    8
    BOOL InstantResult::OnInitDialog()
    {
        OnStartTimer();
        OnTimer(1);
        OnStopTimer();
        return(TRUE);
    }
    arf je viens de voir la boulette...
    pourquoi tu mets en place le timer et après tu le stop avec OnStopTimer ?

  18. #18
    Membre confirmé
    Inscrit en
    Juin 2006
    Messages
    126
    Détails du profil
    Informations forums :
    Inscription : Juin 2006
    Messages : 126
    Par défaut
    Si si c'est moi mais justement ça ne marche pas :/

    En fait je voulais mettre OnStopTimer pour l'arréter et si j'enlève OnStopTimer c'est pareil j'ai le même problème il lance le timer donc j'envoie plein de valeurs en boucles sur le port mais je n'arrive jamais sur ma fenêtre "InstantResult"

    Tu vois ce que je veux dire ?

  19. #19
    Membre chevronné Avatar de stephdim
    Profil pro
    Inscrit en
    Août 2007
    Messages
    462
    Détails du profil
    Informations personnelles :
    Localisation : France

    Informations forums :
    Inscription : Août 2007
    Messages : 462
    Par défaut
    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    3
    4
     
    WriteFile(hcom,NoAuthmov, 1, &m, NULL);
    Sleep(1000);
    CCom::PurgeTx();
    tu fais un timer, et puis après un Sleep() !!!

    ton application reste figée alors 1 seconde sur deux

    Tu fais un timer d'une seconde, et tu alternes entre WriteFile() et PurgeTx(), ça ira déjà bcp mieux ...

    @+

  20. #20
    Membre confirmé
    Inscrit en
    Juin 2006
    Messages
    126
    Détails du profil
    Informations forums :
    Inscription : Juin 2006
    Messages : 126
    Par défaut
    Bon ben ca marche... pour tout te dire je sais pas trop pourquoi. En tout cas j'ai suivi tes conseils! Merci beaucoup.
    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    41
    42
    43
    44
    45
    46
    47
    48
    49
    50
    51
    52
    53
    54
    55
    56
    57
    58
    59
    60
    61
    62
    63
    64
    65
    66
    67
    68
    69
    70
    71
    72
    73
    74
    75
    76
    77
    78
    79
    80
     
    // InstantResult.cpp : implementation file
    //
     
    #include "stdafx.h"
    #include "MMenu.h"
    #include "MMenuSettings.h"
    #include "InstantResult.h"
    #include "ForceSettings.h"
    #include "HeigthSettings.h"
    #include "global.h"
    #include "Comm.h"
     
     
    // InstantResult dialog
     
    IMPLEMENT_DYNAMIC(InstantResult, CDialog)
     
     
     
    InstantResult::InstantResult(CWnd* pParent /*=NULL*/)
    	: CDialog(InstantResult::IDD, pParent)
    {
     
     
    }
    BOOL InstantResult::OnInitDialog()
    {
    	CDialog::OnInitDialog();
     
    	OnStartTimer();
    	OnTimer(1);	
     
    	return(TRUE);
    }
    InstantResult::~InstantResult()
    {
    }
     
     
    void InstantResult::DoDataExchange(CDataExchange* pDX)
    {
    	CDialog::DoDataExchange(pDX);
     
    }
     
     
    BEGIN_MESSAGE_MAP(InstantResult, CDialog)
    	ON_WM_TIMER()
    END_MESSAGE_MAP()
     
     
    void InstantResult::OnStartTimer()
    {
    	m_nTimer = SetTimer(1,2000,0);
    }
     
    void InstantResult::OnStopTimer()
    {
    	KillTimer(m_nTimer);
    }
     
    void InstantResult::OnTimer(UINT nIDEvent) 
    {
    	unsigned char NoAuthmov[1]={141}; // not allowed to move command
     
        switch(nIDEvent)
        {
     
    	case 1:      WriteFile(hcom,NoAuthmov, 1, &m, NULL);
    			Sleep(1000);
    			CCom::PurgeTx();
    			break;
     
     
            default:break;
        }    
        // Call base class handler.
     
    }

Discussions similaires

  1. Réponses: 5
    Dernier message: 05/07/2003, 11h52
  2. Manipulation des handle contexte
    Par rockbiker dans le forum DirectX
    Réponses: 1
    Dernier message: 09/05/2003, 18h51
  3. Question sur les handles et les couleurs...
    Par MrDuChnok dans le forum C++Builder
    Réponses: 7
    Dernier message: 29/10/2002, 08h45
  4. pb de récup de handle à partir d'une dll
    Par yokito dans le forum Langage
    Réponses: 2
    Dernier message: 20/08/2002, 12h29
  5. pb de handle en C
    Par deedje dans le forum C
    Réponses: 3
    Dernier message: 21/06/2002, 16h19

Partager

Partager
  • Envoyer la discussion sur Viadeo
  • Envoyer la discussion sur Twitter
  • Envoyer la discussion sur Google
  • Envoyer la discussion sur Facebook
  • Envoyer la discussion sur Digg
  • Envoyer la discussion sur Delicious
  • Envoyer la discussion sur MySpace
  • Envoyer la discussion sur Yahoo