Page 1 of 2 12 LastLast
Results 1 to 20 of 22

Thread: Cannot get Progress Bar to Update while running tasks in WPF

  1. #1
    CodeLiftsleep is offline VB.NET Forum Newbie
    .NET Framework
    .NET 4.5
    Join Date
    Mar 2016
    Posts
    20
    Reputation
    15

    Cannot get Progress Bar to Update while running tasks in WPF

    Not sure what I am doing wrong, but I can't get it to work. I am doing a CPU intensive task where I am creating a large number of players for a football game I am making, and I am using the Task.Factory.StartNew method to run it on. I then create a separate task for the Progress updater but I can't seem to get it to work.

    I have a WPF progressbar which I have bound to an INotifyPropertyChanged Property, however it does not update and the UI appears frozen, which I thought tasks were supposed to eliminate...

    Code:
        TimeIt(Sub() ReallyGenNewPlayers())
    
        Private Sub TimeIt(MyAction As Action)
            Dim SW As New Stopwatch
            SW.Start()
            MyAction()
            Console.WriteLine($"Total Time Generating Players: {SW.Elapsed} seconds")
            SW.Stop()
        End Sub
    
    
        Private Sub ReallyGenNewPlayers()
            Task.Factory.StartNew(Sub() GenNewPlayersASync()).Wait()
        End Sub
    
    
         Private Sub GenNewPlayersASync()
            Dim x As Integer = 0
            'Generate the Players on an Async Thread
    
            For i As Integer = 1 To NumPlayers
                x = i
                Task.Factory.StartNew(Sub() CollegePlayers.GenDraftPlayers(x, MyDraft, DraftDT, DraftClass, PosCount)).Wait()
                Dim mytask As Task(Of Double) = Task(Of Double).Factory.StartNew(Function() UpdateProgressBar(x, NumPlayers))
                ProgBarValue = mytask.Result
            Next i
    
        End Sub
    
        Private Function UpdateProgressBar(ByVal playernum As Integer, ByVal TotalPlayers As Integer) As Double
            Dim myval As Double
            Return myval = (playernum / TotalPlayers) * 100
        End Function
    Any help would be appreciated, and if you could let me know the how's and why's I would appreciate it as well so I can learn...

  2. #2
    jmcilhinney's Avatar
    jmcilhinney is offline VB.NET Forum Moderator
    .NET Framework
    .NET 4.0
    Join Date
    Aug 2004
    Location
    Sydney, Australia
    Posts
    13,798
    Reputation
    1704
    Any changes you make to a control must be done on the UI thread. Yes, use a background task to perform your long-running operation but then any updates to the UI during that task must be marshalled to the UI thread to be performed. Here's a WPF-specific example I added to a thread specifically on updating the UI from a secondary thread:

    Accessing Controls from Worker Threads

  3. #3
    CodeLiftsleep is offline VB.NET Forum Newbie
    .NET Framework
    .NET 4.5
    Join Date
    Mar 2016
    Posts
    20
    Reputation
    15
    Quote Originally Posted by jmcilhinney View Post
    Any changes you make to a control must be done on the UI thread. Yes, use a background task to perform your long-running operation but then any updates to the UI during that task must be marshalled to the UI thread to be performed. Here's a WPF-specific example I added to a thread specifically on updating the UI from a secondary thread:

    Accessing Controls from Worker Threads
    Isn't Creating another task for the ProgressBar giving it its own thread? Or is it simply adding another task to the same thread?

    Is it possible to do this via a Task instead of a background worker?

    Is it not possible to simply do this via a UI Binding to a INotififyPropertyChanged Property without using a background worker or a task?

  4. #4
    jmcilhinney's Avatar
    jmcilhinney is offline VB.NET Forum Moderator
    .NET Framework
    .NET 4.0
    Join Date
    Aug 2004
    Location
    Sydney, Australia
    Posts
    13,798
    Reputation
    1704
    Quote Originally Posted by CodeLiftsleep View Post
    Isn't Creating another task for the ProgressBar giving it its own thread? Or is it simply adding another task to the same thread?
    A Task is executed on a thread pool thread. If you create a new Task then you're still executing it on a secondary thread. As I said, any modification of a control must be done specifically on the UI thread. It doesn;t matter which secondary thread you use, a secondary thread is not the UI thread.
    Quote Originally Posted by CodeLiftsleep View Post
    Is it possible to do this via a Task instead of a background worker?
    Of course. The use of the BackgroundWorker was simply convenient for the example but is irrelevant to the principle. The principle is that if you are executing code on a secondary thread and you want to update the UI then you need to marshal a method call to the UI thread to do that. A Task and a BackgroundWorker are simply two different mechanisms to execute code on a secondary thread but both end up using a thread pool thread so there's not even that difference. Stop looking at the irrelevant differences and focus on the relevant similarities.
    Quote Originally Posted by CodeLiftsleep View Post
    Is it not possible to simply do this via a UI Binding to a INotififyPropertyChanged Property without using a background worker or a task?
    The problem is that updating a property on a secondary thread is going to raise the corresponding event on that same secondary thread, so if you've bound a control then you're still trying to modify that control on that same secondary thread. At some point, you need to marshal a method call to the UI thread. If you're going to be using multi-threading then you could do that in your ViewModel and use the SynchronizationContext class to ensure that the event is raised on the UI thread. There's an example of using the SynchronizationContext class in that same thread I linked to earlier.

  5. #5
    CodeLiftsleep is offline VB.NET Forum Newbie
    .NET Framework
    .NET 4.5
    Join Date
    Mar 2016
    Posts
    20
    Reputation
    15
    Quote Originally Posted by jmcilhinney View Post
    A Task is executed on a thread pool thread. If you create a new Task then you're still executing it on a secondary thread. As I said, any modification of a control must be done specifically on the UI thread. It doesn;t matter which secondary thread you use, a secondary thread is not the UI thread.

    Of course. The use of the BackgroundWorker was simply convenient for the example but is irrelevant to the principle. The principle is that if you are executing code on a secondary thread and you want to update the UI then you need to marshal a method call to the UI thread to do that. A Task and a BackgroundWorker are simply two different mechanisms to execute code on a secondary thread but both end up using a thread pool thread so there's not even that difference. Stop looking at the irrelevant differences and focus on the relevant similarities.

    The problem is that updating a property on a secondary thread is going to raise the corresponding event on that same secondary thread, so if you've bound a control then you're still trying to modify that control on that same secondary thread. At some point, you need to marshal a method call to the UI thread. If you're going to be using multi-threading then you could do that in your ViewModel and use the SynchronizationContext class to ensure that the event is raised on the UI thread. There's an example of using the SynchronizationContext class in that same thread I linked to earlier.
    OK gotcha...using a background worker per your example...

    One last issue I am having is that I need to get the current value of "x" in the player generation sub to be able to know the value that should be updated, so I would need to call the worker from inside the secondary thread...however inside the BackgroundWorker1_DoWork event, I have no parameters to supply the Me.UpdateProgressBar since I don't know what they are at that point...


    There is also no "InvokeRequired" property on a progress bar going by your code that returns a value, so I am unsure what to put there...
    Code:
    Private Sub GenNewPlayersASync()
            Dim x As Integer = 0
            'Generate the Players on an Async Thread
    
            For i As Integer = 1 To NumPlayers
                x = i
                Task.Factory.StartNew(Sub() CollegePlayers.GenDraftPlayers(x, MyDraft, DraftDT, DraftClass, PosCount)).Wait()
    
           ---->'Call Worker UpdateProgressBar(x, NumPlayers)
    
                
            Next i
    
        End Sub
    Last edited by CodeLiftsleep; 08-09-2016 at 10:25 AM.

  6. #6
    Herman is offline VB.NET Forum Idol
    .NET Framework
    .NET 4.0
    Join Date
    Oct 2011
    Location
    Montreal, QC, CA
    Posts
    866
    Reputation
    1212
    In WPF, the correct way to do this is to create a view model with a property that has change notification enabled through INotifyPropertyChanged. Your task would update the view model property (let's say PercentDone), and the UI progressbar would be bound to that property. You are not supposed to EVER have to directly interact with the UI in WPF.

    Code:
    <Window
        xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
        xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
        xmlns:local="clr-namespace:WpfApplication1" x:Class="MainWindow"
        Title="MainWindow" Height="350" Width="525">
    
        <Window.DataContext>
            <local:MyViewModel/>
        </Window.DataContext>
    
        <Grid>
            <ProgressBar  Minimum="0" Maximum="100" Value="{Binding PercentDone}" />
        </Grid>
    </Window>
    Public Class MyViewModel
    Implements INotifyPropertyChanged

    Private _percentDone As Decimal = 0
    Public Property PercentDone As Decimal
    Get
    Return _percentDone
    End Get
    Set(value As Decimal)
    If value > 100 Then value = 0
    _percentDone = value
    NotifyPropertyChanged()
    End Set
    End Property

    Public Sub New()
    Task.Factory.StartNew(Sub() LoopPercent())
    End Sub

    Private Sub LoopPercent()
    While True
    PercentDone += 1
    Thread.Sleep(500)
    End While
    End Sub

    #Region "INotifyPropertyChanged"
    Public Sub NotifyPropertyChanged(<CallerMemberName> Optional ByVal propName As String = Nothing)
    RaiseEvent PropertyChanged(Me, New PropertyChangedEventArgs(propName))
    End Sub

    Public Event PropertyChanged(sender As Object, e As PropertyChangedEventArgs) Implements INotifyPropertyChanged.PropertyChanged
    #End Region

    End Class
    Last edited by Herman; 08-09-2016 at 3:33 PM.

  7. #7
    CodeLiftsleep is offline VB.NET Forum Newbie
    .NET Framework
    .NET 4.5
    Join Date
    Mar 2016
    Posts
    20
    Reputation
    15
    Quote Originally Posted by Herman View Post
    In WPF, the correct way to do this is to create a view model with a property that has change notification enabled through INotifyPropertyChanged. Your task would update the view model property (let's say PercentDone), and the UI progressbar would be bound to that property. You are not supposed to EVER have to directly interact with the UI in WPF.

    Yes, I realize that, I actually created a separate window for the Progress Bar, but the issue occurs because I'm still getting the result from inside the secondary thread which blocks it from being able to use it...

    It's the first foray into multithreading so I know I am asking a lot of dumb questions, but please bear with me. I typically go through this stage when I'm learning a new concept and after struggling with it for a while I all of a sudden have a light bulb go off in my head and then I quickly figure it out and learn various other ways to do it...

    I know I'm frustrating some of the more experienced users here with some of this, and I apologize for it, but it's the way I learn...I ask lot's of questions and then I keep working at it until I get it working properly. Then I go back and review things I could do to make it more efficient or better, and then I end up having a good understanding of it...

  8. #8
    Herman is offline VB.NET Forum Idol
    .NET Framework
    .NET 4.0
    Join Date
    Oct 2011
    Location
    Montreal, QC, CA
    Posts
    866
    Reputation
    1212
    Look at the example I added... The view model is the ONLY part of code that connects to your XAML. You can have as many threads as you like, as long as the property you defined in your view model is visible from your XAML, it will work.

    I will add that you should probably start multithreading without WPF, to really understand how to do it manually. WPF and its awesome databinding hides much of it away. If you were in WinForm, then your issue would matter, and you would need to test your UI controls through .InvokeRequired and then invoke the form to change the control value. In WPF, you just tell the form control where it should go find its value, relative to the data context, and then you just make sure that value is updated. You don't have to push the value into the control, neither should you be trying to. That's the biggest thing to get your head around in WPF. Once you understand that, you get the basics of MVVM. The view is bound to the view model, and the view model exposes data from the model.
    Last edited by Herman; 08-09-2016 at 3:42 PM.

  9. #9
    CodeLiftsleep is offline VB.NET Forum Newbie
    .NET Framework
    .NET 4.5
    Join Date
    Mar 2016
    Posts
    20
    Reputation
    15
    Quote Originally Posted by Herman View Post
    Look at the example I added... The view model is the ONLY part of code that connects to your XAML. You can have as many threads as you like, as long as the property you defined in your view model is visible from your XAML, it will work.

    I will add that you should probably start multithreading without WPF, to really understand how to do it manually. WPF and its awesome databinding hides much of it away. If you were in WinForm, then your issue would matter, and you would need to test your UI controls through .InvokeRequired and then invoke the form to change the control value. In WPF, you just tell the form control where it should go find its value, relative to the data context, and then you just make sure that value is updated. You don't have to push the value into the control, neither should you be trying to. That's the biggest thing to get your head around in WPF. Once you understand that, you get the basics of MVVM. The view is bound to the view model, and the view model exposes data from the model.
    Awesome! Thanks so much...

    Now my question is going to be how do I get the value out of the secondary thread, because I need the value from there to know what percent the progress bar should display... Here is the Secondary thread where I need to take the value of x and NumPlayers to calculate a percent...ie x is the number created so far out of NumPlayers total... I have to pass this to the ViewModel somehow for it to know the value of the ProgressBar so it can display it properly...Can I just call the ViewModel property directly from there? Or do I need to use Dispatcher.Invoke?




    Code:
    Private Sub GenNewPlayersASync()
            Dim x As Integer = 0
            'Generate the Players on an Async Thread
    
            For i As Integer = 1 To NumPlayers
                x = i
                Task.Factory.StartNew(Sub() CollegePlayers.GenDraftPlayers(x, MyDraft, DraftDT, DraftClass, PosCount)).Wait()
    
           ---->'Call Worker UpdateProgressBar(x, NumPlayers)
    
                
            Next i
    
        End Sub
    Also....Since this is in a loop, should the sub I'm creating be the entire loop? Otherwise am I not wasting significant processing creating a new Task each time it goes through the loop?
    Something Like This?

    Code:
    Private Sub GenNewPlayersASync()
         Dim x As Integer = 0
          'Generate the Players on an Async Thread
         Task.Factory.StartNew(Sub()
                               For i As Integer = 1 To NumPlayers
                               x = i
                               CollegePlayers.GenDraftPlayers(x, MyDraft, DraftDT, DraftClass, PosCount)).Wait()
    
                         ---->'Call Worker UpdateProgressBar(x, NumPlayers)
    
                
                               Next i
                               End Sub)
    
    End Sub
    And it doesn't help that in comparison to C#, VB.net is a nightmare for this type of stuff, lol
    Last edited by CodeLiftsleep; 08-09-2016 at 4:09 PM.

  10. #10
    Herman is offline VB.NET Forum Idol
    .NET Framework
    .NET 4.0
    Join Date
    Oct 2011
    Location
    Montreal, QC, CA
    Posts
    866
    Reputation
    1212
    Well... In what class is GenNewPlayersASync? Is it in your viewmodel (it shouldn't be...)?

    You should NOT have to call updateprogressbar. Just generate the players and stick them into a property. That is it. Your actual data should have no connection AT ALL to your UI. If you need to SHOW some data, then you show it through properties of your view model. Assuming the percentage done is relative to the number of players processed, that means you know in advance how many players there will be. So that also needs to be accessible from the ViewModel. So the view model can take the expected total number of players and do Players.Count / TotalPlayerCount, and then bind the form to that property.

    I don't really see what happens in the rest of your code, so it's kind of hard to answer. I think you need to start with a simpler example and work from there. Starting from existing code will just give you the impression what is already there is good code, which it might not be. To work MVVM properly, you MUST have a good understanding of what is a good object oriented architecture. If you start with goofy procedural code and try to migrate to MVVM you will have issues.

  11. #11
    CodeLiftsleep is offline VB.NET Forum Newbie
    .NET Framework
    .NET 4.5
    Join Date
    Mar 2016
    Posts
    20
    Reputation
    15
    Quote Originally Posted by Herman View Post
    Well... In what class is GenNewPlayersASync? Is it in your viewmodel (it shouldn't be...)?

    You should NOT have to call updateprogressbar. Just generate the players and stick them into a property. That is it. Your actual data should have no connection AT ALL to your UI. If you need to SHOW some data, then you show it through properties of your view model. Assuming the percentage done is relative to the number of players processed, that means you know in advance how many players there will be. So that also needs to be accessible from the ViewModel. So the view model can take the expected total number of players and do Players.Count / TotalPlayerCount, and then bind the form to that property.

    I don't really see what happens in the rest of your code, so it's kind of hard to answer. I think you need to start with a simpler example and work from there. Starting from existing code will just give you the impression what is already there is good code, which it might not be. To work MVVM properly, you MUST have a good understanding of what is a good object oriented architecture. If you start with goofy procedural code and try to migrate to MVVM you will have issues.
    This was designed to be more of a quick and dirty type project to give the testers something to work with so they could test out the results from the player generation process and give me feedback, so I suppose I am trying to reverse engineer it to work in a way that I shouldn't be...I use MVVM in the main project and have it working pretty well. Figure I would test it out in this first because if I screw something up its not that big of a deal since its a lot less code, lol...
    The GeneratePlayersAsync is in the GeneratePlayers Class, not the viewmodel...

    OK, so basically I create a PlayerCount property in the viewmodel and assign it the value and then it kind of bypasses the issues with calling it inside of a UI thread? The number of players is entered in a textbox by the user.
    Last edited by CodeLiftsleep; 08-09-2016 at 4:25 PM.

  12. #12
    Herman is offline VB.NET Forum Idol
    .NET Framework
    .NET 4.0
    Join Date
    Oct 2011
    Location
    Montreal, QC, CA
    Posts
    866
    Reputation
    1212
    OK, so basically I create a PlayerCount property in the viewmodel and assign it the value and then it kind of bypasses the issues with calling it inside of a UI thread?
    Yup! That's really all there is to it.

  13. #13
    CodeLiftsleep is offline VB.NET Forum Newbie
    .NET Framework
    .NET 4.5
    Join Date
    Mar 2016
    Posts
    20
    Reputation
    15
    Quote Originally Posted by Herman View Post
    Yup! That's really all there is to it.
    Well, its still not working. Both UI and the new ProgressBar Window are unresponsive.

    The ProgressBar task is running, the PlayerCount and PercentDone is updating properly, its just not displaying the updated values on the ProgressBar.

    Here is the code in the GeneratePlayers Class---moving the task to encompass the entire block of code caused it to go much quicker, 45 seconds when it was creating a new task every iteration to 16-18 seconds now, but it still isn't working properly.

    Code:
     Private Sub GenNewPlayersASync()
            Dim x As Integer = 0
            'Generate the Players on an Async Thread
            Me.IsEnabled = False
            MyVM.Show()
            Dim mytask As Task = Task.Factory.StartNew(Sub()
                                                           For i As Integer = 1 To MyVM.TotalPlayers
                                                               x = i
                                                               CollegePlayers.GenDraftPlayers(x, MyDraft, DraftDT, DraftClass, PosCount)
                                                               MyVM.PlayerCount = x
                                                           Next i
                                                       End Sub)
            mytask.Wait()
            MyVM.Close()
            Me.IsEnabled = True
        End Sub
    Here is the VM code:

    Code:
    Public Sub New()
    
    
            ' This call is required by the designer.
            InitializeComponent()
            Dim mytask As Task = Task.Factory.StartNew(Sub() LoopPercent())
            ' Add any initialization after the InitializeComponent() call.
            'mytask.Wait()
    
    
    End Sub
    
    
    Public Property ProgressValue As Integer
            Get
                Return _ProgressValue
            End Get
            Set(value As Integer)
                _ProgressValue = value
                NotifyPropertyChanged("ProgressValue")
            End Set
        End Property
    
    
        Public Property PlayerCount As Integer
            Get
                Return _PlayerCount
            End Get
            Set(value As Integer)
                _PlayerCount = value
                NotifyPropertyChanged("PlayerCount")
            End Set
        End Property
    
    
        Public Property TotalPlayers As Integer
            Get
                Return _TotalPlayers
            End Get
            Set(value As Integer)
                _TotalPlayers = value
                NotifyPropertyChanged("TotalPlayers")
            End Set
        End Property
    
    
        Public Property PercentDone As Decimal
            Get
                Return _PercentDone
            End Get
            Set(value As Decimal)
                _PercentDone = value
                NotifyPropertyChanged("PercentDone")
            End Set
        End Property
    
    Private Sub LoopPercent()
            'Dim mytask As Task(Of Decimal)
            While PercentDone < 100
                If PlayerCount > 0 Then
                    PercentDone = CInt(CDbl(PlayerCount / TotalPlayers) * 100)
                    ProgressText = PercentDone.ToString()
                    Thread.Sleep(500)
                End If
            End While
        End Sub
    And the XAML Code:

    Code:
    <Window x:Class="ProgressBarDialog"
            xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
            xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
            xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
            xmlns:local="clr-namespace:WPFGeneration"
            xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
            Title="Generating Players..."
            Width="300"
            Height="115"
            WindowStartupLocation="CenterOwner"
            WindowStyle="ToolWindow"
            mc:Ignorable="d">
        <Grid>
            <StackPanel Background="SlateGray">
                <Label x:Name="lblProgress"
                       HorizontalAlignment="Center"
                       Content="{Binding ProgressText}"
                       FontSize="16" />
                <ProgressBar x:Name="progress"
                             Height="25"
                             Background="DodgerBlue"
                             IsIndeterminate="False"
                             Value="{Binding PerecentDone}" />
                <StackPanel Margin="0,5,3,0"
                            HorizontalAlignment="Right"
                            Orientation="Horizontal">
                    <Button x:Name="btnCancel" Click="btnCancel_Click">Cancel</Button>
                </StackPanel>
            </StackPanel>
        </Grid>
    </Window>
    Do I need to "Wait" the task in the VM?

  14. #14
    jmcilhinney's Avatar
    jmcilhinney is offline VB.NET Forum Moderator
    .NET Framework
    .NET 4.0
    Join Date
    Aug 2004
    Location
    Sydney, Australia
    Posts
    13,798
    Reputation
    1704
    Having glanced at some of Herman's posts, I'm wondering whether I may have misled you. I have used WPF some but not a lot and not in a multi-threaded environment. If modifying a bound property on a secondary thread does not create any issues then I'm sorry for the misinformation. Regardless, I'll leave you in Herman's hands as he seems to know more about WPF than I do.

  15. #15
    CodeLiftsleep is offline VB.NET Forum Newbie
    .NET Framework
    .NET 4.5
    Join Date
    Mar 2016
    Posts
    20
    Reputation
    15
    Quote Originally Posted by jmcilhinney View Post
    Having glanced at some of Herman's posts, I'm wondering whether I may have misled you. I have used WPF some but not a lot and not in a multi-threaded environment. If modifying a bound property on a secondary thread does not create any issues then I'm sorry for the misinformation. Regardless, I'll leave you in Herman's hands as he seems to know more about WPF than I do.
    Its no problem, I am always wanting to learn everything I can, so I will keep this for use in a Winforms or non-WPF environment...

    I'm still confused as to why the UI thread is unresponsive even as I have a separate Task running for it...

  16. #16
    Herman is offline VB.NET Forum Idol
    .NET Framework
    .NET 4.0
    Join Date
    Oct 2011
    Location
    Montreal, QC, CA
    Posts
    866
    Reputation
    1212
    Ok, a couple of issues here...

    1- Why is your viewmodel calling InitializeComponents? The ViewModel is NOT the form designer. The viewmodel is a separate class. Create a new class and call it MyViewModel.vb or something. This is what should be in it, and NOTHING ELSE:

    Imports System.ComponentModel
    Imports System.Runtime.CompilerServices

    Public Class MyViewModel
    Implements INotifyPropertyChanged

    Private _PlayerCount As Integer = 0
    Private _TotalPlayers As Integer = 0

    ' This property should calculate itself, no point in writing to it, so make it read only.
    Public ReadOnly Property PercentDone As Integer
    Get
    ' Return the calculated percent every time.
    Return CInt((PlayerCount / TotalPlayers) * 100)
    End Get
    End Property

    ' This property sets the current count as set in the GenNewPlayersASync
    ' Note that having your external class dig around properties in your view model
    ' is not ideal, but it should work.
    Public Property PlayerCount As Integer
    Get
    Return _PlayerCount
    End Get
    Set(value As Integer)
    _PlayerCount = value
    NotifyPropertyChanged("PlayerCount")
    ' Every time you change the count, the percentage changes too, so notify the form.
    NotifyPropertyChanged("PercentDone")
    End Set
    End Property

    Public Property TotalPlayers As Integer
    Get
    Return _TotalPlayers
    End Get
    Set(value As Integer)
    _TotalPlayers = value
    NotifyPropertyChanged("TotalPlayers")
    End Set
    End Property

    #Region "INotifyPropertyChanged"
    Public Event PropertyChanged As PropertyChangedEventHandler Implements INotifyPropertyChanged.PropertyChanged

    Public Sub NotifyPropertyChanged(<CallerMemberName> Optional ByVal propname As String = Nothing)
    RaiseEvent PropertyChanged(Me, New PropertyChangedEventArgs(propname))
    End Sub
    #End Region

    End Class


    2- Same goes for the MyVM.Show() call in your GeneratePlayers class. If MyVM is the form, then call it MyForm (MyForm.xaml and MyForm.xaml.vb). In the form .VB file, all you should have is glue logic for the UI, event handlers, etc.. So that call needs to go, or change. In your loop you use its reference to set your properties. That reference should be to an instance of your viewmodel. So declare your view model somewhere appropriate. Private myVM = New MyViewModel().

    3- I don't see where in your XAML you connect the form to the view model. Either add the datacontext in the XAML like I did above, or in the code behind set the form's DataContext property to your instance of your view model. Form1.DataContext = myVM.

    4- Normally, the code in your GeneratePlayers class should not even be aware that a viewmodel exists, and vice versa. The form (XAML) connects to the view model, and the view model connects to the model (model is a generic term for "the program"). The model should NOT depend on the view model. So there are still some architectural issues that should be fixed.

    One way to separate your GeneratePlayers class and view model is through events. Instead of writing directly to your view model from the GeneratePlayers class, have your async method raise an event every time a player is generated.
    Last edited by Herman; 08-09-2016 at 10:47 PM.

  17. #17
    CodeLiftsleep is offline VB.NET Forum Newbie
    .NET Framework
    .NET 4.5
    Join Date
    Mar 2016
    Posts
    20
    Reputation
    15
    Quote Originally Posted by Herman View Post
    Ok, a couple of issues here...

    1- Why is your viewmodel calling InitializeComponents? The ViewModel is NOT the form designer. The viewmodel is a separate class. Create a new class and call it MyViewModel.vb or something. This is what should be in it, and NOTHING ELSE:

    Imports System.ComponentModel
    Imports System.Runtime.CompilerServices

    Public Class MyViewModel
    Implements INotifyPropertyChanged

    Private _PlayerCount As Integer = 0
    Private _TotalPlayers As Integer = 0

    ' This property should calculate itself, no point in writing to it, so make it read only.
    Public ReadOnly Property PercentDone As Integer
    Get
    ' Return the calculated percent every time.
    Return CInt((PlayerCount / TotalPlayers) * 100)
    End Get
    End Property

    ' This property sets the current count as set in the GenNewPlayersASync
    ' Note that having your external class dig around properties in your view model
    ' is not ideal, but it should work.
    Public Property PlayerCount As Integer
    Get
    Return _PlayerCount
    End Get
    Set(value As Integer)
    _PlayerCount = value
    NotifyPropertyChanged("PlayerCount")
    ' Every time you change the count, the percentage changes too, so notify the form.
    NotifyPropertyChanged("PercentDone")
    End Set
    End Property

    Public Property TotalPlayers As Integer
    Get
    Return _TotalPlayers
    End Get
    Set(value As Integer)
    _TotalPlayers = value
    NotifyPropertyChanged("TotalPlayers")
    End Set
    End Property

    #Region "INotifyPropertyChanged"
    Public Event PropertyChanged As PropertyChangedEventHandler Implements INotifyPropertyChanged.PropertyChanged

    Public Sub NotifyPropertyChanged(<CallerMemberName> Optional ByVal propname As String = Nothing)
    RaiseEvent PropertyChanged(Me, New PropertyChangedEventArgs(propname))
    End Sub
    #End Region

    End Class


    2- Same goes for the MyVM.Show() call in your GeneratePlayers class. If MyVM is the form, then call it MyForm (MyForm.xaml and MyForm.xaml.vb). In the form .VB file, all you should have is glue logic for the UI, event handlers, etc.. So that call needs to go, or change. In your loop you use its reference to set your properties. That reference should be to an instance of your viewmodel. So declare your view model somewhere appropriate. Private myVM = New MyViewModel().

    3- I don't see where in your XAML you connect the form to the view model. Either add the datacontext in the XAML like I did above, or in the code behind set the form's DataContext property to your instance of your view model. Form1.DataContext = myVM.

    4- Normally, the code in your GeneratePlayers class should not even be aware that a viewmodel exists, and vice versa. The form (XAML) connects to the view model, and the view model connects to the model (model is a generic term for "the program"). The model should NOT depend on the view model. So there are still some architectural issues that should be fixed.

    One way to separate your GeneratePlayers class and view model is through events. Instead of writing directly to your view model from the GeneratePlayers class, have your async method raise an event every time a player is generated.
    Created a new ViewModel and have it exactly as you do, bound using <Window.DataContext>. Previously had it set in the code behind which I didn't show in my example, sorry for the confusion. Still not working. I did get the Progress Bar Window to be responsive by Calling the Button Click Event Async and then changing

    Code:
    TimeIt(GenSubAsync()) to Await Task.Run(Sub() TimeIt(GenSubAsync())
    .

    However, even though I can now drag the Progress Bar Window around on the screen after it pops up when I enable it as it runs, the values still won't update even though they are bound to the Properties.
    Property values are updating properly, just not showing in their respective places.

    I'm at a loss right now...this is supposed to something really simple and I've literally been working at this a day and a half and its still not working...beyond frustrating.

  18. #18
    JohnH's Avatar
    JohnH is offline VB.NET Forum Moderator
    .NET Framework
    .NET 4.5
    Join Date
    Dec 2005
    Location
    Norway
    Posts
    15,083
    Reputation
    2803
    I'm not really into WPF either, but I noticed in your post 13 there was spelling mistake in your binding: {Binding PerecentDone}
    How to format posts with code blocks etc - present the problem/post properly

    Visual Studio Community 2017

  19. #19
    CodeLiftsleep is offline VB.NET Forum Newbie
    .NET Framework
    .NET 4.5
    Join Date
    Mar 2016
    Posts
    20
    Reputation
    15
    Quote Originally Posted by JohnH View Post
    I'm not really into WPF either, but I noticed in your post 13 there was spelling mistake in your binding: {Binding PerecentDone}
    It's correct in the actual code, its just a typo in here...wish that was the problem tho...lol

  20. #20
    jmcilhinney's Avatar
    jmcilhinney is offline VB.NET Forum Moderator
    .NET Framework
    .NET 4.0
    Join Date
    Aug 2004
    Location
    Sydney, Australia
    Posts
    13,798
    Reputation
    1704
    Quote Originally Posted by CodeLiftsleep View Post
    It's correct in the actual code, its just a typo in here...wish that was the problem tho...lol
    Why aren't you copying and pasting your actual code?

Page 1 of 2 12 LastLast

Tags for this Thread

Bookmarks

Posting Permissions

  • You may not post new threads
  • You may not post replies
  • You may not post attachments
  • You may not edit your posts
  •