Skip to content

Commit

Permalink
Fixes updateOnRender bug in Hosted apps (#9)
Browse files Browse the repository at this point in the history
Co-authored-by: michaelpduda <michael@pulselyre.com>
  • Loading branch information
michaelpduda and michaelpduda authored Oct 24, 2023
1 parent 8fa6feb commit 2b3080e
Show file tree
Hide file tree
Showing 9 changed files with 51 additions and 3 deletions.
2 changes: 1 addition & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,7 @@ UpbeatUI implementations are available as NuGet packages:

Three samples are included: one showing [manual setup](samples/ManualUpbeatUISample) and teardown without dependency injection, one showing manual setup and teardown with [dependency injection](samples/ServiceProvidedUpbeatUISample) using an **IServiceProvider**, and one showing [automatic setup and teardown](samples/HostedUpbeatUISample) using an **IHostBuilder**. All samples demonstrate the following capabilities:

![UpbeatUI Sample](https://user-images.githubusercontent.com/20475952/111044956-6c7e1200-8400-11eb-82f3-1befa64c951b.gif)
![UpbeatUI Sample](https://github.com/Pulselyre/UpbeatUI/assets/20475952/968f2465-43cb-4486-a671-c8a0d898022e)

>Note: The background in the sample is OrangeRed to demonstrate how the effect can be configured. The default value is Gray.
Expand Down
9 changes: 9 additions & 0 deletions samples/HostedUpbeatUISample/View/MenuControl.xaml
Original file line number Diff line number Diff line change
Expand Up @@ -38,6 +38,15 @@
<TextBlock TextAlignment="Center"
Margin="5"
Text="{Binding SecondsElapsed}" />
<Ellipse Margin="5"
HorizontalAlignment="Center"
Height="10"
Width="10">
<Ellipse.Fill>
<SolidColorBrush Color="Red"
Opacity="{Binding Visibility}"/>
</Ellipse.Fill>
</Ellipse>
</StackPanel>
<Button DockPanel.Dock="Bottom"
Margin="5"
Expand Down
6 changes: 6 additions & 0 deletions samples/HostedUpbeatUISample/ViewModel/MenuViewModel.cs
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@
* https://github.com/pulselyre/upbeatui/blob/main/LICENSE.md
*/
using System;
using System.Diagnostics;
using System.Windows;
using System.Windows.Input;
using UpbeatUI.Extensions.Hosting;
Expand All @@ -15,6 +16,7 @@ public class MenuViewModel : BaseViewModel, IDisposable
{
private readonly IUpbeatService _upbeatService;
private readonly SharedTimer _sharedTimer;
private readonly Stopwatch _stopwatch = new();

public MenuViewModel(
IUpbeatService upbeatService, // This will be a unique IUpbeatService created and injected by the IUpbeatStack specifically for this ViewModel.
Expand All @@ -25,6 +27,9 @@ public MenuViewModel(
_ = hostedUpbeatService ?? throw new NullReferenceException(nameof(hostedUpbeatService));
_sharedTimer = sharedTimer ?? throw new NullReferenceException(nameof(sharedTimer));

_stopwatch.Start();
_upbeatService.RegisterUpdateCallback(() => RaisePropertyChanged(nameof(Visibility))); // Registered "UpdateCallbacks" will be called each time the UI thread renders a new frame.

_sharedTimer.Ticked += SharedTimerTicked;

// DelegateCommand is a common convenience ICommand implementation to call methods or lambda expressions when the command is executed. It supports both async and non-async methods/lambdas.
Expand All @@ -49,6 +54,7 @@ public MenuViewModel(
public ICommand OpenRandomDataCommand { get; }
public ICommand OpenSharedListCommand { get; }
public string SecondsElapsed => $"{_sharedTimer.ElapsedSeconds} Seconds";
public double Visibility => Math.Abs(1000.0 - _stopwatch.ElapsedMilliseconds % 2000) / 1000.0; // Will be calculated on each "RaisePropertyChanged(nameof(Visibility))" and used in the View to control visibility of an ellipse. Cycles between full and no visibility every two seconds.

public void Dispose() =>
_sharedTimer.Ticked -= SharedTimerTicked;
Expand Down
9 changes: 9 additions & 0 deletions samples/ManualUpbeatUISample/View/MenuControl.xaml
Original file line number Diff line number Diff line change
Expand Up @@ -38,6 +38,15 @@
<TextBlock TextAlignment="Center"
Margin="5"
Text="{Binding SecondsElapsed}" />
<Ellipse Margin="5"
HorizontalAlignment="Center"
Height="10"
Width="10">
<Ellipse.Fill>
<SolidColorBrush Color="Red"
Opacity="{Binding Visibility}"/>
</Ellipse.Fill>
</Ellipse>
</StackPanel>
<Button DockPanel.Dock="Bottom"
Margin="5"
Expand Down
6 changes: 6 additions & 0 deletions samples/ManualUpbeatUISample/ViewModel/MenuViewModel.cs
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@
* https://github.com/pulselyre/upbeatui/blob/main/LICENSE.md
*/
using System;
using System.Diagnostics;
using System.Threading.Tasks;
using System.Windows;
using System.Windows.Input;
Expand All @@ -15,6 +16,7 @@ public class MenuViewModel : BaseViewModel, IDisposable
{
private readonly IUpbeatService _upbeatService;
private readonly SharedTimer _sharedTimer;
private readonly Stopwatch _stopwatch = new();

public MenuViewModel(
IUpbeatService upbeatService, // This will be a unique IUpbeatService created and injected by the IUpbeatStack specifically for this ViewModel.
Expand All @@ -25,6 +27,9 @@ public MenuViewModel(
_ = closeApplicationCallbackAsync ?? throw new NullReferenceException(nameof(closeApplicationCallbackAsync));
_sharedTimer = sharedTimer ?? throw new NullReferenceException(nameof(sharedTimer));

_stopwatch.Start();
_upbeatService.RegisterUpdateCallback(() => RaisePropertyChanged(nameof(Visibility))); // Registered "UpdateCallbacks" will be called each time the UI thread renders a new frame.

_sharedTimer.Ticked += SharedTimerTicked;

// DelegateCommand is a common convenience ICommand implementation to call methods or lambda expressions when the command is executed. It supports both async and non-async methods/lambdas.
Expand All @@ -49,6 +54,7 @@ public MenuViewModel(
public ICommand OpenRandomDataCommand { get; }
public ICommand OpenSharedListCommand { get; }
public string SecondsElapsed => $"{_sharedTimer.ElapsedSeconds} Seconds";
public double Visibility => Math.Abs(1000.0 - _stopwatch.ElapsedMilliseconds % 2000) / 1000.0; // Will be calculated on each "RaisePropertyChanged(nameof(Visibility))" and used in the View to control visibility of an ellipse. Cycles between full and no visibility every two seconds.

public void Dispose() =>
_sharedTimer.Ticked -= SharedTimerTicked;
Expand Down
9 changes: 9 additions & 0 deletions samples/ServiceProvidedUpbeatUISample/View/MenuControl.xaml
Original file line number Diff line number Diff line change
Expand Up @@ -38,6 +38,15 @@
<TextBlock TextAlignment="Center"
Margin="5"
Text="{Binding SecondsElapsed}" />
<Ellipse Margin="5"
HorizontalAlignment="Center"
Height="10"
Width="10">
<Ellipse.Fill>
<SolidColorBrush Color="Red"
Opacity="{Binding Visibility}"/>
</Ellipse.Fill>
</Ellipse>
</StackPanel>
<Button DockPanel.Dock="Bottom"
Margin="5"
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@
* https://github.com/pulselyre/upbeatui/blob/main/LICENSE.md
*/
using System;
using System.Diagnostics;
using System.Threading.Tasks;
using System.Windows;
using System.Windows.Input;
Expand All @@ -15,6 +16,7 @@ public class MenuViewModel : BaseViewModel, IDisposable
{
private readonly IUpbeatService _upbeatService;
private readonly SharedTimer _sharedTimer;
private readonly Stopwatch _stopwatch = new();

public MenuViewModel(
IUpbeatService upbeatService, // This will be a unique IUpbeatService created and injected by the IUpbeatStack specifically for this ViewModel.
Expand All @@ -25,6 +27,9 @@ public MenuViewModel(
_ = closeApplicationCallbackAsync ?? throw new NullReferenceException(nameof(closeApplicationCallbackAsync));
_sharedTimer = sharedTimer ?? throw new NullReferenceException(nameof(sharedTimer));

_stopwatch.Start();
_upbeatService.RegisterUpdateCallback(() => RaisePropertyChanged(nameof(Visibility))); // Registered "UpdateCallbacks" will be called each time the UI thread renders a new frame.

_sharedTimer.Ticked += SharedTimerTicked;

// DelegateCommand is a common convenience ICommand implementation to call methods or lambda expressions when the command is executed. It supports both async and non-async methods/lambdas.
Expand All @@ -49,6 +54,7 @@ public MenuViewModel(
public ICommand OpenRandomDataCommand { get; }
public ICommand OpenSharedListCommand { get; }
public string SecondsElapsed => $"{_sharedTimer.ElapsedSeconds} Seconds";
public double Visibility => Math.Abs(1000.0 - _stopwatch.ElapsedMilliseconds % 2000) / 1000.0; // Will be calculated on each "RaisePropertyChanged(nameof(Visibility))" and used in the View to control visibility of an ellipse. Cycles between full and no visibility every two seconds.

public void Dispose() =>
_sharedTimer.Ticked -= SharedTimerTicked;
Expand Down
2 changes: 2 additions & 0 deletions source/UpbeatUI.Extensions.Hosting/HostedUpbeatService.cs
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@
using System.Threading.Tasks;
using System.Windows;
using Microsoft.Extensions.Hosting;
using UpbeatUI.Extensions.DependencyInjection;

namespace UpbeatUI.Extensions.Hosting
{
Expand All @@ -26,6 +27,7 @@ public Task StartAsync(CancellationToken cancellationToken)
{
Window mainWindow = null;
Task baseViewModelOpen = null;
_upbeatStack = new ServiceProvidedUpbeatStack(_serviceProvider ?? throw new ArgumentNullException(nameof(_serviceProvider)));
foreach (var registerer in _upbeatHostBuilder.MappingRegisterers ?? throw new InvalidOperationException($"No {nameof(_upbeatHostBuilder.MappingRegisterers)} provided."))
registerer.Invoke(_upbeatStack);
mainWindow = _upbeatHostBuilder.WindowCreator?.Invoke() ?? throw new InvalidOperationException($"No {nameof(_upbeatHostBuilder.WindowCreator)} provided.");
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -12,16 +12,17 @@ namespace UpbeatUI.Extensions.Hosting
internal class UpbeatApplicationService : IUpbeatApplicationService, IDisposable
{
protected readonly HostedUpbeatBuilder _upbeatHostBuilder;
protected readonly ServiceProvidedUpbeatStack _upbeatStack;
protected readonly IHostApplicationLifetime _hostApplicationLifetime;
protected readonly TaskCompletionSource<bool> _applicationTaskSource = new TaskCompletionSource<bool>();
protected readonly TaskCompletionSource<bool> _forcedClosedTaskSource = new TaskCompletionSource<bool>();
protected readonly IServiceProvider _serviceProvider;
protected ServiceProvidedUpbeatStack _upbeatStack;

protected UpbeatApplicationService(HostedUpbeatBuilder upbeatHostBuilder, IServiceProvider serviceProvider, IHostApplicationLifetime hostApplicationLifetime)
{
_upbeatHostBuilder = upbeatHostBuilder ?? throw new ArgumentNullException(nameof(upbeatHostBuilder));
_hostApplicationLifetime = hostApplicationLifetime ?? throw new ArgumentNullException(nameof(hostApplicationLifetime));
_upbeatStack = new ServiceProvidedUpbeatStack(serviceProvider ?? throw new ArgumentNullException(nameof(serviceProvider)));
_serviceProvider = serviceProvider;
}

public async void CloseUpbeatApplication()
Expand Down

0 comments on commit 2b3080e

Please sign in to comment.