0

I am trying to build a progressbar using MVVM. Basically, I have a main xaml and 2 UserControls, 1 for progressbar 1 for datagrid. I am bit new and I followed this question and answer but I havent got any success. Below is my ViewModel code and Xaml code. Basically I have 2 problems,

1-How to bind CustomerModels or if even possible CustomerViewModel? I tried to use Itemsource binding direcly with ObservableCollection which I am filling with my delegateCommand that runs with a backgroundworker but no success. I tried without delegate and backgroundworker,simply using as below.

Me.myLoadCommand = New Commands.LoadCustomerModels()

What am I doing wrong?

<UserControl.Resources>    
        <vm:CustomerModelsVM x:Key="Customerobj"></vm:CustomerModelsVM>
    </UserControl.Resources>
<Grid >
 <DataGrid x:Name="grdData"  ItemsSource="{Binding Path=CustomerModels}"/>
</Grid>

2-How to bind CurrentProgressBar? I tried to bind the progress bar status same way but I believe my ViewModel and Xaml somehow has no connection.

<UserControl x:Class="ucProgressBar"
         xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
         xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
         xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006" 
         xmlns:d="http://schemas.microsoft.com/expression/blend/2008" 
         mc:Ignorable="d" 
                    >
<Grid>
    <ProgressBar Value="{Binding CurrentProgress, Mode=OneWay}"  Visibility="{Binding ProgressVisibility}"></ProgressBar>
    <TextBlock Text="{Binding ElementName=myProgressBar, Path=Value, StringFormat={}{0:0}%}" HorizontalAlignment="Center" VerticalAlignment="Center" />
</Grid>

Namespace ViewModels

Public Class CustomerModelsVM
    Implements ICustomerModelsVM
    Implements INotifyPropertyChanged

    Public Event PropertyChanged As PropertyChangedEventHandler _
     Implements INotifyPropertyChanged.PropertyChanged

    Private ReadOnly worker As BackgroundWorker
    Private m_currentProgress As Integer
    Private _CustomerModels As New ObservableCollection(Of Models.CustomerModel)
    Private mySaveCommand As ICommand
    Private myLoadCommand As ICommand

    Public Sub New()
        Me.worker = New BackgroundWorker()
        Me.myLoadCommand = New DelegateCommand(Sub() Me.worker.RunWorkerAsync(), AddressOf Progressisbusy)
       ' _CustomerModels = getCustomerModels()
        Me.worker = New BackgroundWorker()
        AddHandler Me.worker.DoWork, AddressOf Me.DoWork
        AddHandler Me.worker.ProgressChanged, AddressOf Me.ProgressChanged

    End Sub

    Private Sub ProgressChanged(sender As Object, e As ProgressChangedEventArgs)
        Me.CurrentProgress = e.ProgressPercentage
    End Sub

    Private Function Progressisbusy() As Boolean
        Return Not Me.worker.IsBusy
    End Function

    Private Sub OnPropertyChanged(Optional ByVal propertyName As String = Nothing)
        RaiseEvent PropertyChanged(Me, New PropertyChangedEventArgs(propertyName))
    End Sub

    Public ReadOnly Property CustomerModels() As ObservableCollection(Of Models.CustomerModel)
        Get
            Return _CustomerModels
        End Get
    End Property

    Public ReadOnly Property btnClick() As ICommand
        Get
            Return myLoadCommand
        End Get
    End Property

    Public Property CurrentProgress() As Integer
        Get
            Return Me.m_currentProgress
        End Get
        Private Set(value As Integer)
            If Me.m_currentProgress <> value Then
                Me.m_currentProgress = value
                OnPropertyChanged(Me.CurrentProgress)
            End If
        End Set
    End Property

    Private Sub DoWork(sender As Object, e As DoWorkEventArgs)
            _CustomerModels = getCustomerModels()
    End Sub


    Function getCustomerModels() As ObservableCollection(Of Models.CustomerModel) Implements ICustomerModelsVM.GetCustomerModels

        If _CustomerModels Is Nothing OrElse _CustomerModels.Count = 0 Then myLoadCommand.Execute(_CustomerModels)

        Return _CustomerModels
    End Function
Community
  • 1
  • 1
Emil
  • 6,411
  • 7
  • 62
  • 112
  • Try using more words to explain your problem. *How to bind* is a very poor description of your problem/requirements. – Sheridan Jan 21 '15 at 15:38
  • @Sheridan I added it as possible as I can. As I said I followed the url simply. I underestand what is happening in the other question but I cant understand why my code isnt working. not sure If it is C# to VB conversiton problem as I am not so good in VB but I need to write it in VB. thanks – Emil Jan 21 '15 at 15:59
  • 1
    Also see my answer to the [How to correctly implement a BackgroundWorker with ProgressBar updates?](http://stackoverflow.com/questions/19334583/how-to-correctly-implement-a-backgroundworker-with-progressbar-updates/19334879#19334879) question on Stack Overflow and the [Data Binding Overview](https://msdn.microsoft.com/en-us/library/ms752347(v=vs.110).aspx) page on MSDN. – Sheridan Jan 21 '15 at 16:14
  • Actually I recognized an error which appears only when i open my Xaml file although built returns successful. error message is "in the application configuration file has no connection string with the name 'CustomerContext' found." I am using "Code First from DB" as automatically generated classes and DbContext. App.Config has the connection string correctly. I have only 1 project (there were same problems by 2 or more projects but I cant find any item with 1 project) Do you know why do I experience this error?i believe this is the problem to get my viewmodel loaded – Emil Jan 21 '15 at 17:40
  • bind the same viewmodel to the new window, but make sure to do the progressbar ticking update in a backgroundworker so it does not freeze the UI. – user853710 Jan 21 '15 at 20:58
  • @batmaci, that's a different question altogether, but check out what the error is complaining about. Do you have a `CustomerContext` in your config file? – Sheridan Jan 22 '15 at 09:00
  • Yes, I do have and namespaces are all fine. I believe it is related to my original question but In case it gets more complicated, i will open a new question. – Emil Jan 22 '15 at 09:18
  • @Sheridan I could finally fix the problem and made the progressbar running according to your example. But in the example, you are doing a simple work and not using any ICommand. if I understand correctly, I need to use Icommand and INotifyPropertyChanged when I pull something from database and if I want this to be reflected on my Xaml. how to achieve this? Do I really need Icommands , INotifyPropertyChanged and my binding object as ObservableCollection as it is above in my example? – Emil Jan 22 '15 at 11:50

2 Answers2

3

You can add the viewmodel as DataContext of the main window which is holding the two user controls. Please refer the below code.

<UserControl x:Class="UserControl1"
             xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
             xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
             xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006" 
             xmlns:d="http://schemas.microsoft.com/expression/blend/2008" 
             mc:Ignorable="d" 
             d:DesignHeight="300" d:DesignWidth="300">
    <Grid>
        <DataGrid x:Name="grdData" Height="200"  ItemsSource="{Binding Path=CustomerModels}"/>
    </Grid>
</UserControl>

<UserControl x:Class="UserControl2"
             xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
             xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
             xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006" 
             xmlns:d="http://schemas.microsoft.com/expression/blend/2008" >
    <StackPanel>
        <Button Command="{Binding LoadCommand}">Test</Button>
        <ProgressBar Value="{Binding CurrentProgress, Mode=TwoWay,UpdateSourceTrigger=PropertyChanged}" Height="20" Width="200"
                     Visibility="{Binding ProgressVisibility}"></ProgressBar>        
    </StackPanel>
</UserControl>

<Window x:Class="MainWindow"
    xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
    xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
        xmlns:local="clr-namespace:StakOveflw"
    Title="MainWindow" Height="350" Width="525">
    <Grid>
        <StackPanel>
            <local:UserControl1/>
            <local:UserControl2/>
        </StackPanel>
    </Grid>
</Window>

Class MainWindow 
    Public Sub New()
        InitializeComponent()
        Me.DataContext = New CustomerModelsVM()
    End Sub
End Class

Imports System.ComponentModel
Imports System.Collections.ObjectModel
Imports Microsoft.Practices.Prism.Commands
Imports System.Threading

Public Class CustomerModelsVM
    Implements INotifyPropertyChanged

    Public Event PropertyChanged As PropertyChangedEventHandler _
     Implements INotifyPropertyChanged.PropertyChanged

    Private ReadOnly worker As BackgroundWorker
    Private m_currentProgress As Integer

    Private mySaveCommand As ICommand


    Private myLoadCommand As ICommand
    Public Property LoadCommand() As ICommand
        Get
            Return myLoadCommand
        End Get
        Set(ByVal value As ICommand)
            myLoadCommand = value
        End Set
    End Property


    Public Sub New()
        Me.worker = New BackgroundWorker()

        _CustomerModels = New ObservableCollection(Of CustomerModel)()
        AddHandler Me.worker.DoWork, AddressOf Me.DoWork
        AddHandler Me.worker.ProgressChanged, AddressOf Me.ProgressChanged
        Me.worker.WorkerReportsProgress = True
        myLoadCommand = New DelegateCommand(AddressOf LoadClick)

        ' _CustomerModels = getCustomerModels()
    End Sub
    Private Sub LoadClick()
        Me.worker.RunWorkerAsync()
    End Sub
    Private Sub ProgressChanged(sender As Object, e As ProgressChangedEventArgs)
        CurrentProgress = e.ProgressPercentage

    End Sub

    Private Function Progressisbusy() As Boolean
        Return Not Me.worker.IsBusy
    End Function
    Private Function CalculateProgress(total As Integer, complete As Integer) As Integer
        ' avoid divide by zero error
        If total = 0 Then
            Return 0
        End If
        ' calculate percentage complete
        Dim result = CDbl(complete) / CDbl(total)
        Dim percentage = result * 100.0
        ' make sure result is within bounds and return as integer;
        Return Math.Max(0, Math.Min(100, CInt(Math.Truncate(percentage))))
    End Function

    Private Sub OnPropertyChanged(Optional ByVal propertyName As String = Nothing)
        RaiseEvent PropertyChanged(Me, New PropertyChangedEventArgs(propertyName))
    End Sub

    Private _CustomerModels As ObservableCollection(Of CustomerModel)
    Public Property CustomerModels() As ObservableCollection(Of CustomerModel)
        Get
            Return _CustomerModels
        End Get
        Set(ByVal value As ObservableCollection(Of CustomerModel))
            _CustomerModels = value
        End Set
    End Property

    Public Sub GetCustomers()
        Dim total As Integer
        total = 10000
        For index = 1 To total
            Dim a As CustomerModel = New CustomerModel()
            a.NewProperty = "test" + index.ToString()

            Application.Current.Dispatcher.Invoke(Windows.Threading.DispatcherPriority.Background, Function()
                                                                                                       _CustomerModels.Add(a)
                                                                                                   End Function)
            worker.ReportProgress(CalculateProgress(total, index))
        Next
    End Sub

    Public ReadOnly Property btnClick() As ICommand
        Get
            Return myLoadCommand
        End Get
    End Property

    Public Property CurrentProgress() As Integer
        Get
            Return Me.m_currentProgress
        End Get
        Private Set(value As Integer)

            Me.m_currentProgress = value
            OnPropertyChanged("CurrentProgress")

        End Set
    End Property

    Private Sub DoWork(sender As Object, e As DoWorkEventArgs)
        _CustomerModels = getCustomerModels()
    End Sub


    Function getCustomerModels() As ObservableCollection(Of CustomerModel)
        GetCustomers()

        'Application.Current.Dispatcher.BeginInvoke(Windows.Threading.DispatcherPriority.Normal, New Action(Of Integer)(AddressOf GetCustomers), 3)
        Return _CustomerModels
    End Function
End Class

Public Class CustomerModel
    Private newPropertyValue As String
    Public Property NewProperty() As String
        Get
            Return newPropertyValue
        End Get
        Set(ByVal value As String)
            newPropertyValue = value
        End Set
    End Property

End Class
Sheridan
  • 68,826
  • 24
  • 143
  • 183
Ayyappan Subramanian
  • 5,348
  • 1
  • 22
  • 44
  • On this website, that should be a comment... answers are reserved for proper answers that consist of more than a single line. – Sheridan Jan 21 '15 at 16:11
  • I am new to Stack Overflow. I will take care of it going forward. – Ayyappan Subramanian Jan 21 '15 at 17:07
  • *I will take care of it going forward*... You can take care of it now by editing your answer and improving it, in which case I'd be happy to remove my down vote, or you could delete it, in which case you'd also delete the down vote. Please note that I am just trying to encourage you to follow the guidelines of this website. – Sheridan Jan 22 '15 at 12:03
  • @Sheridan I have given the full answer. Please check. – Ayyappan Subramanian Jan 22 '15 at 20:16
  • @Ganesh first of all, it doesnt accept twoway binding as progressbar is readonly, i believe. when I change to oneway, it will not complain and your code is running when i debug, i can see that it itterates customers object but I dont see any refresh on datagrid. It will just remain empty and not binded. same for progressbar. Progressbar is working only if I add the line OnPropertyChanged("CurrentProgress") into progresschanged function. I dont know what is the difference? any idea of this 2 issues? – Emil Jan 26 '15 at 12:14
0

I would like answer my question with a working solution. In my case problem was simply, I had to use dispatcher to clear my oberservable collection. So my Do_work function looks like as following. I didnt clear the observable collection before I start binding. Adding this simple line makes my code working.

    Private Sub DoWork(sender As Object, e As DoWorkEventArgs)
        Application.Current.Dispatcher.BeginInvoke(Sub() Me.CustomerModels.Clear())

        ' Me.CustomerModels.Clear()
        For index = 1 To 100

            Dim CustomerModel As New CustomerModel With { _
                .age = 30 + index, _
                .name = "testName" & index, _
                .surname = "testSurname" & index, _
                .Id = index}

            Application.Current.Dispatcher.BeginInvoke(Sub() CustomerModels.Add(CustomerModel))
            '  CustomerModels.Add(CustomerModel)
            Thread.Sleep(100)
            worker.ReportProgress(CalculateProgress(100, index))
        Next


    End Sub
Emil
  • 6,411
  • 7
  • 62
  • 112