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

Windows Presentation Foundation Discussion :

Mettre à jour un label ou un textbox sur un second formulaire


Sujet :

Windows Presentation Foundation

Vue hybride

Message précédent Message précédent   Message suivant Message suivant
  1. #1
    Membre éclairé
    Avatar de Golard
    Homme Profil pro
    Développeur informatique
    Inscrit en
    Juin 2009
    Messages
    288
    Détails du profil
    Informations personnelles :
    Sexe : Homme
    Localisation : France, Seine et Marne (Île de France)

    Informations professionnelles :
    Activité : Développeur informatique

    Informations forums :
    Inscription : Juin 2009
    Messages : 288
    Par défaut Mettre à jour un label ou un textbox sur un second formulaire
    Bonjour,

    J'aimerais que mon formulaire principal WPF ouvre un second petit formulaire où je pourrais afficher la progression du traitement (nécessaire pour certains traitements longs).
    Pour réussir à mettre à jour mon second formulaire (libellé, pourcentage d'avancement, barre de progression ...), j'ai été obligé d'ajouter un DoEvents.

    Je pense que les experts vont HURLER !
    Je sais: il ne faut pas utiliser de DoEvents, surtout pas dans une appli WPF...
    C'est pourquoi je viens ici vous demander comment faire.

    Voici le projet complet:
    Test-X-Book-Forms-Light.zip

    Dans cet exemple très simple, vous trouverez les éléments suivants.

    Le formulaire principal avec 3 boutons:

    Nom : Test-Forms-MainWindow.png
Affichages : 1418
Taille : 28,7 Ko

    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    <Window x:Class="MainWindow"
        xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
        xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
        Title="MainWindow" Height="250" Width="525">
        <Grid>
            <StackPanel Name="myStackPanel">
                <Label Content="Mise à jour d'un label, textbox ou autre dans un deuxième formulaire" Margin="15" />
                <Button Content="Ouverture du second formulaire" Margin="5" Click="Button_Click"  />
                <Button Content="auto update (KO on voit juste 'terminé' au bout de quelques secondes)" Margin="5" Click="Button_Click_1" />
                <Button Content="auto update (OK fonctionne grace au DoEvents)" Margin="5" Click="Button_Click_2" />
                <Label Content="Comment faire la même chose SANS le DoEvents (qui n'est pas conseillé en WPF) ?" Margin="15" />
            </StackPanel>
        </Grid>
    </Window>
    Le code déclenché par l'appui sur les boutons:
    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
    Option Explicit On
    Option Strict On
     
    Class MainWindow
        Friend myWindow1 As Window1
     
        Private Sub MainWindow_Closing(sender As Object, e As ComponentModel.CancelEventArgs) Handles Me.Closing
            If Not myWindow1 Is Nothing Then myWindow1.Close()
        End Sub
     
        Private Sub Button_Click(sender As Object, e As RoutedEventArgs)
            myWindow1 = New Window1
            myWindow1.Topmost = True
            myWindow1.Show()
        End Sub
     
        Private Sub Button_Click_1(sender As Object, e As RoutedEventArgs)
            Dim inti As Integer
            For inti = 1 To 3000
                myWindow1.myLabel.Content = inti.ToString
                myWindow1.myTextBox.Text = inti.ToString
            Next inti
            myWindow1.myLabel.Content = "Terminé! " & DateTime.Now.ToString
            myWindow1.myTextBox.Text = "Terminé! " & DateTime.Now.ToString
        End Sub
     
        Private Sub Button_Click_2(sender As Object, e As RoutedEventArgs)
            Dim inti As Integer
            For inti = 1 To 3000
                myWindow1.myLabel.Content = inti.ToString
                myWindow1.myTextBox.Text = inti.ToString
                System.Windows.Forms.Application.DoEvents()
            Next inti
            myWindow1.myLabel.Content = "Terminé! " & DateTime.Now.ToString
            myWindow1.myTextBox.Text = "Terminé! " & DateTime.Now.ToString
        End Sub
    End Class
    Le second formulaire avec juste un Label et un TextBox:

    Nom : Test-Forms-Window1.png
Affichages : 1279
Taille : 9,8 Ko

    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    <Window x:Class="Window1"
        xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
        xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
        Title="Window1" Height="100" Width="300">
        <Grid>
            <StackPanel>
                <Label Name="myLabel" Content="Label à mettre à jour" />
                <TextBox Name="myTextBox" Text="TextBox à mettre à jour" />
            </StackPanel>
        </Grid>
    </Window>
    Vous constaterez clairement que le bouton sans le DoEvents ne met pas à jour le second formulaire pendant le déroulement de la boucle.
    Merci d'avance à celui ou celle qui pourra m'indiquer comment conserver ce comportement en codant "proprement"

  2. #2
    Membre Expert Avatar de jopopmk
    Homme Profil pro
    Développeur informatique
    Inscrit en
    Mars 2011
    Messages
    1 856
    Détails du profil
    Informations personnelles :
    Sexe : Homme
    Localisation : France

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

    Informations forums :
    Inscription : Mars 2011
    Messages : 1 856
    Par défaut
    Salut,

    lance ta fonction "for 3000" dans un thread, ça devrait mieux fonctionner

  3. #3
    Membre éclairé
    Avatar de Golard
    Homme Profil pro
    Développeur informatique
    Inscrit en
    Juin 2009
    Messages
    288
    Détails du profil
    Informations personnelles :
    Sexe : Homme
    Localisation : France, Seine et Marne (Île de France)

    Informations professionnelles :
    Activité : Développeur informatique

    Informations forums :
    Inscription : Juin 2009
    Messages : 288
    Par défaut
    Citation Envoyé par jopopmk Voir le message
    Salut,

    lance ta fonction "for 3000" dans un thread, ça devrait mieux fonctionner
    Peux-tu m'en dire plus ? Je ne vois pas du tout comment faire (c'est pourquoi j'ai noté le sujet [Débutant])
    Aurais-tu un exemple simple, ou mieux m'indiquer comment modifier mon exemple ?


    Je n'ai jamais codé de thread...

  4. #4
    Membre Expert Avatar de jopopmk
    Homme Profil pro
    Développeur informatique
    Inscrit en
    Mars 2011
    Messages
    1 856
    Détails du profil
    Informations personnelles :
    Sexe : Homme
    Localisation : France

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

    Informations forums :
    Inscription : Mars 2011
    Messages : 1 856
    Par défaut
    Aucun souci

    Un truc simple pour threader des bouts de code : les BackgroundWorker
    Un tit exemple maison :

    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
    // event click d'un bouton pour lancer le test
    private void btnTest_Click(object sender, EventArgs e) {
    	BackgroundWorker bgw = new BackgroundWorker();
    	bgw.DoWork += fonctionThreadee;
    	bgw.RunWorkerAsync();
    }
     
    void fonctionThreadee(object sender, DoWorkEventArgs e) {
    	// pgBar est mon ProgressBar
    	for (int i = 0; i < pgBar.Maximum; i++) {
    		// on fait quand même un Sleep pour pas que ça aille trop vite
    		System.Threading.Thread.Sleep(100);
    		pgBar.Value++;
    	}
    	MessageBox.Show("fini !");
    }

  5. #5
    Membre éclairé
    Avatar de Golard
    Homme Profil pro
    Développeur informatique
    Inscrit en
    Juin 2009
    Messages
    288
    Détails du profil
    Informations personnelles :
    Sexe : Homme
    Localisation : France, Seine et Marne (Île de France)

    Informations professionnelles :
    Activité : Développeur informatique

    Informations forums :
    Inscription : Juin 2009
    Messages : 288
    Par défaut
    Merci pour ton exemple, mais je n'ai pas réussi à le faire fonctionner.
    L'as-tu testé en C?
    Après 2 heures de recherches,je l'ai traduit et testé en VBnet: j'obtiens l'erreur suivante quand, j'essaye d'agir sur mon textbox ( l'équivalent de ta ligne pgBar.Value++; )

    Nom : ThreadAppelantNePeutPasAcceder.png
Affichages : 1274
Taille : 17,9 Ko

    J'ai aussi trouvé les infos suivantes:

    - DoWork (dans ce gestionnaire on ne place pas de code qui utilise ou mets à jour l'interface du programme au risque de causer une exception de type "CrossThread" !!!)
    Ref: http://www.developpez.net/forums/d14...oite-dialogue/

    les objets de l'interface (du thread principal) ne sont pas accessibles dans le thread d'arrière plan:
    Cela déclenche une exception si on tente d'y accéder.
    Ref: http://plasserre.developpez.com/cour...-forms5#LX-R-2

    FINALEMENT, ça a l'air "normal" que ça ne fonctionne pas puisque mon but est justement de mettre à jour l'interface du thread principal (mon second formulaire).
    Est-ce possible de mettre à jour mon second formulaire en utilisant le BackgroundWorker ?
    Ne faudrait-il pas plutôt ouvrir le second formulaire dans un BackgroundWorker et laisser le traitement dans le thread principal ?

    J'ai trouvé beaucoup de post qui parlent du BackgroundWorker sur notre forum, mais jamais de code complet et fonctionnel en VBnet. Si quelqu'un pouvait me montrer comment mettre à jour mon textbox pendant un traitement (comme décrit dans mon exemple), ce serait vraiment chouette de sa part !

  6. #6
    Membre extrêmement actif
    Inscrit en
    Avril 2008
    Messages
    2 573
    Détails du profil
    Informations personnelles :
    Âge : 65

    Informations forums :
    Inscription : Avril 2008
    Messages : 2 573
    Par défaut
    bonjour

    1/La premiere des choses à faire est mettre ton 2eme formulaire en "modal"...et de supprimer ce form_closing...puisque tu veux faire du multithreading avec
    2/apres tu as BGw mais pour un design approprie il faut t'equiper d'un class NombreInput (qui peut etre change à volonte )qui sera passe en parametre au BG vorace ...qui avale toutes sortes d'objet...
    et d'un class helper WorkerBoucle qui execute la boucle de calcul (sub static ou shared en vb.net)...
    Exemple1 avec BG:
    code .vb du simple classs NombreInput:
    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
     
    Public Class NombresInput
     
        Public Property StartNumber() As Integer
     
        Public Property EndNumber() As Integer
     
        Public Sub New(ByVal OStartNum As Integer, ByVal OEndNum As Integer)
            Me.StartNumber = OStartNum
            Me.EndNumber = OEndNum
        End Sub
    End Class
    code .vb du WorkerBoucle:
    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
     
    Imports System.ComponentModel
     
    Public Class WorkerBoucle
        Public Shared Sub ExecuteCalcul(ByVal fromNumber As Integer, ByVal toNumber As Integer, ByVal bg As BackgroundWorker)
     
            'total iterations exprimee en pour-cent 
            Dim iteration As Integer = CInt((toNumber - fromNumber + 1) / 100)
     
            For i As Integer = fromNumber To toNumber
     
     
                If i Mod iteration = 100 AndAlso bg IsNot Nothing Then
                    If bg.CancellationPending Then
                        ' Return without doing any more work.
                        Return
                    End If
                    If bg.WorkerReportsProgress Then
                        bg.ReportProgress(i / iteration)
                    End If
                End If
            Next
     
     
        End Sub
     
     
     
    End Class
    code xaml du simple Main form:
    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
     
    <Window x:Class="MainWindow"
        xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
        xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
        Title="MainWindow" Height="350" Width="525">
     
            <StackPanel Name="myStackPanel">
                <Label Content="Mise à jour d'un label, textbox ou autre dans un deuxième formulaire" Margin="15" />
                <Button Click="cmdBackgroundWorker_Click">Use the BackgroundWorker</Button>
            </StackPanel>
     
    </Window>
    son simplistic code-behind .vb:
    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
     
     
    Class MainWindow 
     
     
        Private Sub cmdBackgroundWorker_Click(ByVal sender As System.Object, ByVal e As System.Windows.RoutedEventArgs)
            Dim test As New Window1
            test.ShowDialog()
        End Sub
    End Class
    code xaml du 2eme form modal plus corse(le bg est declare en xaml) et son instance est capture dans le code-behind du meme form:
    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
     
    <Window x:Class="Window1"
        xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
        xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
        xmlns:cm="clr-namespace:System.ComponentModel;assembly=System" 
        Title="Window1" Height="300" Width="300">
        <Window.Resources>
            <cm:BackgroundWorker x:Key="backgroundWorker"
              WorkerReportsProgress="True" WorkerSupportsCancellation="True"
              DoWork="backgroundWorker_DoWork" ProgressChanged="backgroundWorker_ProgressChanged" 
              RunWorkerCompleted="backgroundWorker_RunWorkerCompleted"></cm:BackgroundWorker>
        </Window.Resources>
        <Grid>
            <Grid.RowDefinitions>
                <RowDefinition Height="auto"></RowDefinition>
                <RowDefinition Height="auto"></RowDefinition>
            </Grid.RowDefinitions>
            <StackPanel Grid.Row="0">
                <Button x:Name="cmdStartProcess"
                  Margin="5" Padding="3"
                  Click="cmdStartProcess_Click">Start Boucle Entiers</Button>
                <Button x:Name="cmdCancelProcess"
                  Margin="5" Padding="3" IsEnabled="False"
                  Click="cmdCancelProcess_Click">Cancel</Button>
            </StackPanel>
            <StackPanel Grid.Row="1">
                <Label Name="myLabel" Content="Label à mettre à jour" />
                <TextBox Name="myTextBox" Text="TextBox à mettre à jour" />
                <ProgressBar Name="progressBar"
                    Margin="5" VerticalAlignment="Bottom" MinHeight="20" 
                    Minimum="0" Maximum="100" Height="20">
                </ProgressBar>
            </StackPanel>
     
        </Grid>
    </Window>
    code behind .vb du form:
    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
     
    Imports System.ComponentModel
     
    Public Class Window1
        Private myBackgroundWorker As BackgroundWorker
        Public Sub New()
            InitializeComponent()
            myBackgroundWorker = CType(Me.FindResource("backgroundWorker"), BackgroundWorker)
        End Sub
     
        Private Sub cmdStartProcess_Click(ByVal sender As System.Object, ByVal e As System.Windows.RoutedEventArgs)
     
            cmdStartProcess.IsEnabled = False
            cmdCancelProcess.IsEnabled = True
     
            ' limite de la boucle 
            Dim fromValue, toValue As Integer
     
     
            ' Start  on another thread.
            fromValue = 1
            toValue = 100000000
            Dim nombre As New NombresInput(fromValue, toValue)
            myBackgroundWorker.RunWorkerAsync(nombre)
        End Sub
     
     
     
        Private Sub backgroundWorker_DoWork(ByVal sender As System.Object, ByVal e As System.ComponentModel.DoWorkEventArgs)
            ' Get the input values.
            Dim input As NombresInput = CType(e.Argument, NombresInput)
     
            ' Start and wait.
            WorkerBoucle.ExecuteCalcul(input.StartNumber, input.EndNumber, myBackgroundWorker)
     
            If myBackgroundWorker.CancellationPending Then
                e.Cancel = True
                Return
            End If
     
     
        End Sub
     
     
     
        Private Sub backgroundWorker_RunWorkerCompleted(ByVal sender As System.Object, ByVal e As System.ComponentModel.RunWorkerCompletedEventArgs)
            If e.Cancelled Then
                MessageBox.Show("Search cancelled.")
            ElseIf Not e.Error Is Nothing Then
                ' An error was thrown by the DoWork event handler.
                MessageBox.Show(e.Error.Message, "An Error Occurred")
            End If
            cmdStartProcess.IsEnabled = True
            cmdCancelProcess.IsEnabled = False
            progressBar.Value = 0
        End Sub
        Private Sub backgroundWorker_ProgressChanged(ByVal sender As System.Object, ByVal e As System.ComponentModel.ProgressChangedEventArgs)
            progressBar.Value = e.ProgressPercentage
            myLabel.Content = e.ProgressPercentage.ToString
            myTextBox.Text = e.ProgressPercentage.ToString
        End Sub
        Private Sub cmdCancelProcess_Click(ByVal sender As System.Object, ByVal e As System.Windows.RoutedEventArgs)
            myBackgroundWorker.CancelAsync()
        End Sub
     
    End Class
    c'est le design recommende pou le BG par MSDN....
    Exemple2 en utilisant un Thread et en passant par le Dispatcher de l'UI (du form modal et du principal )
    Non recommende ,car en cas de traitements plus complexes,sa se corse:
    code xaml du form principal(presque similaire ) :
    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
     
    <Window x:Class="MainWindow"
        xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
        xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
        Title="MainWindow" Height="350" Width="525">
            <StackPanel Name="myStackPanel">
                <Label Content="Mise à jour d'un label, textbox ou autre dans un deuxième formulaire" Margin="15" />
                <Button x:Name="cmdStartThread" Click="cmdStartThread_Click">Use Thread</Button>
            </StackPanel>
     
    </Window>
    code behind .vb du form principal:
    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    3
    4
    5
    6
    7
    8
    9
     
    Class MainWindow 
     
     
        Private Sub cmdStartThread_Click(ByVal sender As System.Object, ByVal e As System.Windows.RoutedEventArgs)
            Dim test As New Window1
            test.ShowDialog()
        End Sub
    End Class
    code xaml du 2eme form modal (qui ressemble au tiens):
    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
     
    <Window x:Class="Window1"
        xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
        xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
        Title="Window1" Height="300" Width="300">
        <StackPanel>
            <Button x:Name="btnStartThread" Click="btnStartThread_Click">Start Thread</Button>
            <TextBox Name="txt">Text in a text box.</TextBox>
            <ProgressBar Name="progressBar"
                    Margin="5" VerticalAlignment="Bottom" MinHeight="20" 
                    Minimum="0" Maximum="100" Height="20">
            </ProgressBar>
     
     
     
        </StackPanel>
    </Window>
    code behind .vb du 2eme form modal (on utilise toujours notre bon class NombreInput...mais la sub qui calcule la boucle est dans le code du 2eme form):
    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
     
    Imports System.Windows.Threading
    Imports System.Threading
     
    Public Class Window1
        Private nombre As NombresInput
        Private Sub btnStartThread_Click(ByVal sender As System.Object, ByVal e As System.Windows.RoutedEventArgs)
            ' limite de la boucle 
            Dim fromValue, toValue As Integer
            fromValue = 1
            toValue = 100000000
     
     
            ' Start  on another thread.
            nombre = New NombresInput(fromValue, toValue)
     
            Dim thread As New System.Threading.Thread(AddressOf UpdateTextRight)
            thread.Start()
        End Sub
        'right right express pour eviter le wrong ,wrong que tu as suavagement utiliser 
        'pour mettre à jour tes controls
     
        Private Sub UpdateTextRight()
            'on passe par l'autorisation du chef dispatcher
            Me.Dispatcher.BeginInvoke(DispatcherPriority.Normal, New ThreadStart(AddressOf UpdateMethod))
        End Sub
        'boucle de calcul
        Private Sub UpdateMethod()
            Dim iteration As Integer = CInt((nombre.EndNumber - nombre.StartNumber + 1) / 100)
     
            For i As Integer = nombre.StartNumber To nombre.EndNumber
     
     
                If i Mod iteration = 10 Then
     
                    progressBar.Value = i / iteration
                    txt.Text = i.ToString
                End If
            Next
     
        End Sub
    End Class
    bon code...

Discussions similaires

  1. [Débutant] ASP.NET - Mettre à jour un label dans une autre page
    Par HidanTF2 dans le forum ASP.NET
    Réponses: 8
    Dernier message: 27/11/2013, 15h04
  2. Réponses: 18
    Dernier message: 14/05/2012, 01h00
  3. mettre à jour le contenu d'un textbox ASP.net avec javascript
    Par bonagad1 dans le forum Général JavaScript
    Réponses: 1
    Dernier message: 17/11/2009, 18h55
  4. Réponses: 4
    Dernier message: 06/06/2008, 11h24
  5. Mettre à jour les if et variable toute les x seconde.
    Par Guillaume602 dans le forum C++
    Réponses: 5
    Dernier message: 06/12/2005, 19h09

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