Add WPF sample app

This commit is contained in:
Caelan Sayler
2024-01-11 14:40:06 +00:00
parent f21e546f72
commit 72c8c790c0
12 changed files with 343 additions and 1 deletions

View File

@@ -42,6 +42,8 @@ Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "AvaloniaCrossPlat", "exampl
EndProject
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "LegacyTestApp", "test\LegacyTestApp\LegacyTestApp.csproj", "{8B27C4BF-21B8-48B0-80F8-74520227C35F}"
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "VeloWpfSample", "examples\VeloWpfSample\VeloWpfSample.csproj", "{9E0F2B00-1B88-4B75-BEED-6DF8DBCA36B5}"
EndProject
Global
GlobalSection(SolutionConfigurationPlatforms) = preSolution
Debug|Any CPU = Debug|Any CPU
@@ -96,6 +98,10 @@ Global
{8B27C4BF-21B8-48B0-80F8-74520227C35F}.Debug|Any CPU.Build.0 = Debug|Any CPU
{8B27C4BF-21B8-48B0-80F8-74520227C35F}.Release|Any CPU.ActiveCfg = Release|Any CPU
{8B27C4BF-21B8-48B0-80F8-74520227C35F}.Release|Any CPU.Build.0 = Release|Any CPU
{9E0F2B00-1B88-4B75-BEED-6DF8DBCA36B5}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{9E0F2B00-1B88-4B75-BEED-6DF8DBCA36B5}.Debug|Any CPU.Build.0 = Debug|Any CPU
{9E0F2B00-1B88-4B75-BEED-6DF8DBCA36B5}.Release|Any CPU.ActiveCfg = Release|Any CPU
{9E0F2B00-1B88-4B75-BEED-6DF8DBCA36B5}.Release|Any CPU.Build.0 = Release|Any CPU
EndGlobalSection
GlobalSection(SolutionProperties) = preSolution
HideSolutionNode = FALSE
@@ -107,6 +113,7 @@ Global
{784B5987-2E71-4AEE-81B9-E0CC7F1DBEB3} = {7AC3A776-B582-4B65-9D03-BD52332B5CA3}
{1FF6A262-13D0-45DF-B818-77AC84C52C6F} = {3EBFA551-780C-473D-A197-0EE56F2CBA82}
{8B27C4BF-21B8-48B0-80F8-74520227C35F} = {7AC3A776-B582-4B65-9D03-BD52332B5CA3}
{9E0F2B00-1B88-4B75-BEED-6DF8DBCA36B5} = {3EBFA551-780C-473D-A197-0EE56F2CBA82}
EndGlobalSection
GlobalSection(ExtensibilityGlobals) = postSolution
SolutionGuid = {68CA987A-9BAB-4C75-8EEB-4596BA6BBD07}

View File

@@ -0,0 +1,9 @@
<Application x:Class="VeloWpfSample.App"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:local="clr-namespace:VeloWpfSample"
StartupUri="MainWindow.xaml">
<Application.Resources>
</Application.Resources>
</Application>

View File

@@ -0,0 +1,10 @@
using System.Configuration;
using System.Data;
using System.Windows;
namespace VeloWpfSample
{
public partial class App : Application
{
}
}

View File

@@ -0,0 +1,6 @@
using System.Windows;
[assembly: ThemeInfo(
ResourceDictionaryLocation.None,
ResourceDictionaryLocation.SourceAssembly
)]

View File

@@ -0,0 +1,28 @@
<Window x:Class="VeloWpfSample.MainWindow"
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:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:local="clr-namespace:VeloWpfSample"
mc:Ignorable="d"
WindowStartupLocation="CenterScreen"
Title="VeloWpfSample" Height="600" Width="600">
<Grid>
<Grid.RowDefinitions>
<RowDefinition Height="Auto" />
<RowDefinition Height="15" />
<RowDefinition Height="*" />
</Grid.RowDefinitions>
<StackPanel>
<TextBlock Margin="10" Name="TextStatus" />
<StackPanel Orientation="Horizontal" Margin="10">
<Button Name="BtnCheckUpdate" Content="Check for Updates" Click="BtnCheckUpdateClick" Padding="10,5" />
<Button Margin="10,0" Name="BtnDownloadUpdate" Content="Download" Click="BtnDownloadUpdateClick" Padding="10,5" IsEnabled="False" />
<Button Name="BtnRestartApply" Content="Restart &amp; Apply" Click="BtnRestartApplyClick" Padding="10,5" IsEnabled="False" />
</StackPanel>
</StackPanel>
<ScrollViewer Name="ScrollLog" Grid.Row="2" HorizontalAlignment="Stretch" VerticalAlignment="Stretch" VerticalScrollBarVisibility="Auto">
<TextBlock Name="TextLog" Background="DarkGoldenrod" Foreground="White" TextWrapping="Wrap" />
</ScrollViewer>
</Grid>
</Window>

View File

@@ -0,0 +1,109 @@
using System.Text;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Data;
using System.Windows.Documents;
using System.Windows.Input;
using System.Windows.Media;
using System.Windows.Media.Imaging;
using System.Windows.Navigation;
using System.Windows.Shapes;
using Microsoft.Extensions.Logging;
using Velopack;
namespace VeloWpfSample
{
public partial class MainWindow : Window
{
private UpdateManager _um;
private UpdateInfo _update;
public MainWindow()
{
InitializeComponent();
_um = new UpdateManager(Program.UpdateUrl, logger: Program.Log);
TextLog.Text = Program.Log.ToString();
Program.Log.LogUpdated += LogUpdated;
UpdateStatus();
}
private async void BtnCheckUpdateClick(object sender, RoutedEventArgs e)
{
Working();
try {
// ConfigureAwait(true) so that UpdateStatus() is called on the UI thread
_update = await _um.CheckForUpdatesAsync().ConfigureAwait(true);
} catch (Exception ex) {
Program.Log.LogError(ex, "Error checking for updates");
}
UpdateStatus();
}
private async void BtnDownloadUpdateClick(object sender, RoutedEventArgs e)
{
Working();
try {
// ConfigureAwait(true) so that UpdateStatus() is called on the UI thread
await _um.DownloadUpdatesAsync(_update, Progress).ConfigureAwait(true);
} catch (Exception ex) {
Program.Log.LogError(ex, "Error downloading updates");
}
UpdateStatus();
}
private void BtnRestartApplyClick(object sender, RoutedEventArgs e)
{
_um.ApplyUpdatesAndRestart();
}
private void LogUpdated(object sender, LogUpdatedEventArgs e)
{
// logs can be sent from other threads
this.Dispatcher.InvokeAsync(() => {
TextLog.Text = e.Text;
ScrollLog.ScrollToEnd();
});
}
private void Progress(int percent)
{
// progress can be sent from other threads
this.Dispatcher.InvokeAsync(() => {
TextStatus.Text = $"Downloading ({percent}%)...";
});
}
private void Working()
{
Program.Log.LogInformation("");
BtnCheckUpdate.IsEnabled = false;
BtnDownloadUpdate.IsEnabled = false;
BtnRestartApply.IsEnabled = false;
TextStatus.Text = "Working...";
}
private void UpdateStatus()
{
StringBuilder sb = new StringBuilder();
sb.AppendLine($"Velopack version: {VelopackRuntimeInfo.VelopackNugetVersion}");
sb.AppendLine($"This app version: {(_um.IsInstalled ? _um.CurrentVersion : "(n/a - not installed)")}");
if (_update != null) {
sb.AppendLine($"Update available: {_update.TargetFullRelease.Version}");
BtnDownloadUpdate.IsEnabled = true;
} else {
BtnDownloadUpdate.IsEnabled = false;
}
if (_um.IsUpdatePendingRestart) {
sb.AppendLine("Update ready, pending restart to install");
BtnRestartApply.IsEnabled = true;
} else {
BtnRestartApply.IsEnabled = false;
}
TextStatus.Text = sb.ToString();
BtnCheckUpdate.IsEnabled = true;
}
}
}

View File

@@ -0,0 +1,48 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using Microsoft.Extensions.Logging;
namespace VeloWpfSample
{
public class LogUpdatedEventArgs : EventArgs
{
public string Text { get; set; }
}
public class MemoryLogger : ILogger
{
public event EventHandler<LogUpdatedEventArgs> LogUpdated;
private readonly StringBuilder _sb = new StringBuilder();
public IDisposable BeginScope<TState>(TState state)
{
return null;
}
public bool IsEnabled(LogLevel logLevel)
{
return true;
}
public void Log<TState>(LogLevel logLevel, EventId eventId, TState state, Exception exception, Func<TState, Exception, string> formatter)
{
lock (_sb) {
var message = formatter(state, exception);
if (exception != null) message += "\n" + exception.ToString();
Console.WriteLine("log: " + message);
_sb.AppendLine(message);
LogUpdated?.Invoke(this, new LogUpdatedEventArgs { Text = _sb.ToString() });
}
}
public override string ToString()
{
lock (_sb) {
return _sb.ToString();
}
}
}
}

View File

@@ -0,0 +1,56 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Reflection;
using System.Runtime.CompilerServices;
using System.Text;
using System.Threading.Tasks;
using System.Windows;
using Velopack;
namespace VeloWpfSample
{
public class Program
{
public static bool WasFirstRun { get; private set; }
public static bool WasJustUpdated { get; private set; }
public static string UpdateUrl { get; private set; }
public static MemoryLogger Log { get; private set; }
// Since WPF has an "automatic" Program.Main, we need to create our own.
// In order for this to work, you must also add the following to your .csproj:
// <StartupObject>VeloWpfSample.Program</StartupObject>
[STAThread]
public static void Main(string[] args)
{
try {
// Logging is essential for debugging! Ideally you should write it to a file.
Log = new MemoryLogger();
// It's important to Run() the VelopackApp as early as possible in app startup.
VelopackApp.Build()
.WithRestarted((v) => WasJustUpdated = true)
.WithFirstRun((v) => WasFirstRun = true)
.Run(Log);
// This is purely for demonstration purposes, we get the update URL from a
// property defined by MSBuild, so we can locate the local releases directory.
// In your production app, this should point to your update server.
UpdateUrl = Assembly.GetEntryAssembly()
.GetCustomAttributes<AssemblyMetadataAttribute>()
.Where(x => x.Key == "WpfSampleReleaseDir")
.Single().Value;
// We can now launch the WPF application as normal.
var app = new App();
app.InitializeComponent();
app.Run();
} catch (Exception ex) {
MessageBox.Show("Unhandled exception: " + ex.ToString());
}
}
}
}

View File

@@ -0,0 +1,23 @@
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<OutputType>WinExe</OutputType>
<TargetFramework>net8.0-windows</TargetFramework>
<ImplicitUsings>true</ImplicitUsings>
<UseWPF>true</UseWPF>
<StartupObject>VeloWpfSample.Program</StartupObject>
</PropertyGroup>
<ItemGroup>
<PackageReference Include="Velopack" Version="0.*" />
</ItemGroup>
<!-- Used for demonstrating updates, so the installed application can find the Release directory -->
<ItemGroup>
<AssemblyAttribute Include="System.Reflection.AssemblyMetadataAttribute">
<_Parameter1>WpfSampleReleaseDir</_Parameter1>
<_Parameter2>$(MSBuildThisFileDirectory)releases</_Parameter2>
</AssemblyAttribute>
</ItemGroup>
</Project>

View File

@@ -0,0 +1,16 @@
@echo off
setlocal enabledelayedexpansion
if "%~1"=="" (
echo Version number is required.
echo Usage: build.bat [version]
exit /b 1
)
set "version=%~1"
echo Compiling VeloWpfSample with dotnet...
dotnet publish -c Release --no-self-contained -r win-x64 -o %~dp0publish
echo Building Velopack Release v%version%
vpk pack -u VeloWpfSample -v %version% -o %~dp0releases -p %~dp0publish -f net8-x64-desktop

View File

@@ -0,0 +1,28 @@
# VeloWpfSample
_Prerequisites: vpk command line tool installed_
You can run this sample by executing the build script with a version number: `build.bat 1.0.0`. Once built, you can install the app - build more updates, and then test updates and so forth. The sample app will check the local release dir for new update packages.
In your production apps, you should deploy your updates to some kind of update server instead.
## WPF Implementation Notes
WPF generates a `Program.Main(argv[])` method automatically for you, so it requires a couple of extra steps to get Velopack working with WPF.
1. You need to create your own `Program.cs` class, and add a static `Main()` method.
2. In order for dotnet to execute this new Main() method instead of the default WPF one, you need to add the following to your .csproj:
```xml
<PropertyGroup>
<StartupObject>YourNamespace.Program</StartupObject>
</PropertyGroup>
```
3. You should run the `VelopackApp` builder before starting WPF as usual.
```cs
[STAThread]
public static void Main(string[] args)
{
VelopackApp.Build().Run();
var application = new App();
application.InitializeComponent();
application.Run();
}
```

View File

@@ -1,3 +1,5 @@
# Velopack Examples
- [**AvaloniaCrossPlat**](AvaloniaCrossPlat) - this Example app uses Avalonia to provide a desktop UI for Mac and Windows. The provided build scripts (`build-win.bat` and `build-osx.sh`) will compile Velopack dependencies, so check that you've installed the [Velopack compiling prerequisites](../docs/compiling.md) before cloning this repository.
- [**AvaloniaCrossPlat**](AvaloniaCrossPlat) - this Example app uses Avalonia to provide a desktop UI for Mac and Windows. The provided build scripts (`build-win.bat` and `build-osx.sh`) will compile Velopack dependencies, so check that you've installed the [Velopack compiling prerequisites](../docs/compiling.md) before cloning this repository.
- [**VeloWpfSample**](VeloWpfSample) - demonstrates how to use Velopack effectively with WPF.