开发者

Periodically update silverlight view with MVVM

开发者 https://www.devze.com 2023-03-03 02:28 出处:网络
I am trying to use MVVM in Silverlight, but I am quite new to it so I am 开发者_开发技巧not quite sure on some things. I have a silverlight page which displays the progress of some server side operati

I am trying to use MVVM in Silverlight, but I am quite new to it so I am 开发者_开发技巧not quite sure on some things. I have a silverlight page which displays the progress of some server side operation. The current progress comes from a web service and should be refreshed every few seconds (lets say 10 seconds for the sake of the argument).

What is the best way to implement this? The options I could think of was:

  1. Initalize a DispatcherTimer in the Initalize method of my ViewModel and refresh the view from the DispatcherTimer event (putting the timer details in the ViewModel)

  2. Create a wrapper arround DispatcherTimer (e.g. PeriodicCommandExecutor) which would be a Control or resource similar to the Timer control in WindowsForms with a command property that I bind to a Refresh command in the ViewModel (putting the timer details in the View)

I think the second option is preferred, because it makes the ViewModel easier to test and DispatcherTimer is an UI implementation detail which I don't want in my ViewModel propably. Do you agree?

If yes, how would you create such a wrapper. I started doing an DependencyObject with attached properties, but I am not sure how to forward the property values like Interval to the internal DispatcherTimer. Silverlight doesn't seem to provide any events when the dependency properties change and DispatcherTimer is not a DependencyObject so I can't databind directly to its properties.

Thanks!


Why use a DispatcherTimer? Why not use an ordinary System.Threading.Timer, which will fire its callback on a background thread?

If you put your UI progress update somewhere inconspicious (i.e. not in the centre of the UI, maybe in a bottom corner or status bar), then have the background timer chugging away while the user carries on with what they were doing. The progress value can be populated into the viewmodel, and shown on the UI using binding. This way you don't have to tie up the UI thread making web service calls.


At the end I solved my dillema creating a behavior which periodically executes a refresh command on the ViewModel which you can specify.

The code for the behavior is like this (sorry for VB code):

Option Strict On

Imports System.Windows.Threading
Imports System.Windows.Interactivity

Namespace View.Behaviors

    Public Class RefreshBehavior
        Inherits Behavior(Of FrameworkElement)



        Public Property Command As ICommand
            Get
                Return DirectCast(GetValue(CommandProperty), ICommand)
            End Get

            Set(ByVal value As ICommand)
                SetValue(CommandProperty, value)
            End Set
        End Property

        Public Shared ReadOnly CommandProperty As DependencyProperty = _
                                   DependencyProperty.Register("Command", _
                                                               GetType(ICommand), GetType(RefreshBehavior), _
                                                               New PropertyMetadata(Nothing))


        Public Property CommandParameter As Object
            Get
                Return GetValue(CommandParameterProperty)
            End Get

            Set(ByVal value As Object)
                SetValue(CommandParameterProperty, value)
            End Set
        End Property

        Public Shared ReadOnly CommandParameterProperty As DependencyProperty = _
                                   DependencyProperty.Register("CommandParameter", _
                                                               GetType(Object), GetType(RefreshBehavior), _
                                                               New PropertyMetadata(Nothing))




        Public Property Interval As TimeSpan
            Get
                Return DirectCast(GetValue(IntervalProperty), TimeSpan)
            End Get

            Set(ByVal value As TimeSpan)
                SetValue(IntervalProperty, value)
            End Set
        End Property

        Public Shared ReadOnly IntervalProperty As DependencyProperty = _
                                   DependencyProperty.Register("Interval", _
                                                               GetType(TimeSpan), GetType(RefreshBehavior), _
                                                               New PropertyMetadata(TimeSpan.Zero, AddressOf OnIntervalUpdate))



        Public Property Enabled As Boolean
            Get
                Return DirectCast(GetValue(EnabledProperty), Boolean)
            End Get

            Set(ByVal value As Boolean)
                SetValue(EnabledProperty, value)
            End Set
        End Property

        Public Shared ReadOnly EnabledProperty As DependencyProperty = _
                               DependencyProperty.Register("Enabled", _
                               GetType(Boolean), GetType(RefreshBehavior), _
                               New PropertyMetadata(False, AddressOf OnEnabledUpdate))




        Dim WithEvents timer As New DispatcherTimer()

        Private Shared Sub OnEnabledUpdate(ByVal d As DependencyObject, ByVal e As DependencyPropertyChangedEventArgs)
            Dim enable As Boolean = CType(e.NewValue, Boolean)
            Dim executor As RefreshBehavior = CType(d, RefreshBehavior)
            If Not executor.attached Then Return

            Dim timer As DispatcherTimer = executor.timer

            If enable AndAlso Not timer.IsEnabled Then
                timer.Start()
            ElseIf Not enable AndAlso Not timer.IsEnabled Then
                timer.Stop()
            End If
        End Sub

        Private Shared Sub OnIntervalUpdate(ByVal d As DependencyObject, ByVal e As DependencyPropertyChangedEventArgs)
            Dim executor As RefreshBehavior = CType(d, RefreshBehavior)

            Dim timer As DispatcherTimer = executor.timer
            timer.Interval = CType(e.NewValue, TimeSpan)
        End Sub

        Private WithEvents attachedObject As FrameworkElement

        Private Sub OnUnload(ByVal sender As Object, ByVal e As EventArgs) Handles attachedObject.Unloaded
            timer.Stop()
        End Sub

        Private attached As Boolean = False
        Protected Overrides Sub OnAttached()
            attached = True
            attachedObject = AssociatedObject

            If Enabled Then timer.Start()
            MyBase.OnAttached()
        End Sub

        Protected Overrides Sub OnDetaching()
            timer.Stop()
            attached = False
            attachedObject = Nothing
            MyBase.OnDetaching()
        End Sub

        Private Sub OnTick(ByVal sender As Object, ByVal e As EventArgs) Handles Timer.Tick
            Dim cmd = Command
            Dim parameter = CommandParameter
            If Interval < TimeSpan.MaxValue AndAlso cmd IsNot Nothing AndAlso cmd.CanExecute(parameter) Then
                cmd.Execute(parameter)
            End If
        End Sub
    End Class
End Namespace

You can use it like this:

<i:Interaction.Behaviors>
    <Behaviors:RefreshBehavior Enabled="True" Interval="0:0:10" Command="{Binding RefreshPageCommand}" />
</i:Interaction.Behaviors>

I hope it helps someone with a similar problem.

0

精彩评论

暂无评论...
验证码 换一张
取 消