Sort out sample apps

This commit is contained in:
Caelan Sayler
2024-02-26 14:39:48 +00:00
parent d40eb92295
commit 4084661851
39 changed files with 13 additions and 2025 deletions

View File

@@ -0,0 +1,10 @@
<Application xmlns="https://github.com/avaloniaui"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
x:Class="AvaloniaCrossPlat.App"
RequestedThemeVariant="Default">
<!-- "Default" ThemeVariant follows system theme variant. "Dark" or "Light" are other available options. -->
<Application.Styles>
<FluentTheme />
</Application.Styles>
</Application>

View File

@@ -0,0 +1,22 @@
using Avalonia;
using Avalonia.Controls.ApplicationLifetimes;
using Avalonia.Markup.Xaml;
namespace AvaloniaCrossPlat;
public partial class App : Application
{
public override void Initialize()
{
AvaloniaXamlLoader.Load(this);
}
public override void OnFrameworkInitializationCompleted()
{
if (ApplicationLifetime is IClassicDesktopStyleApplicationLifetime desktop) {
desktop.MainWindow = new MainWindow();
}
base.OnFrameworkInitializationCompleted();
}
}

View File

@@ -0,0 +1,43 @@
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<OutputType>WinExe</OutputType>
<TargetFramework>net8.0</TargetFramework>
<BuiltInComInteropSupport>true</BuiltInComInteropSupport>
<ApplicationManifest>app.manifest</ApplicationManifest>
<AvaloniaUseCompiledBindingsByDefault>true</AvaloniaUseCompiledBindingsByDefault>
<NoWarn>$(NoWarn);IDE0161</NoWarn>
</PropertyGroup>
<ItemGroup>
<PackageReference Include="Avalonia" Version="11.0.9" />
<PackageReference Include="Avalonia.Desktop" Version="11.0.9" />
<PackageReference Include="Avalonia.Themes.Fluent" Version="11.0.9" />
<PackageReference Include="Avalonia.Fonts.Inter" Version="11.0.9" />
<!--Condition below is needed to remove Avalonia.Diagnostics package from build output in Release configuration.-->
<PackageReference Condition="'$(Configuration)' == 'Debug'" Include="Avalonia.Diagnostics" Version="11.0.9" />
</ItemGroup>
<!-- For test build scripts which target the local Velopack project instead of the NuGet package -->
<Choose>
<When Condition=" $(UseLocalVelopack) != '' ">
<ItemGroup>
<ProjectReference Include="..\..\src\Velopack\Velopack.csproj" />
</ItemGroup>
</When>
<Otherwise>
<ItemGroup>
<PackageReference Include="Velopack" Version="0.*" />
</ItemGroup>
</Otherwise>
</Choose>
<!-- For demonstrating updates, so the installed application can find the Release directory -->
<ItemGroup>
<AssemblyAttribute Include="System.Reflection.AssemblyMetadataAttribute">
<_Parameter1>AvaloniaSampleReleaseDir</_Parameter1>
<_Parameter2>$(MSBuildThisFileDirectory)releases</_Parameter2>
</AssemblyAttribute>
</ItemGroup>
</Project>

View File

@@ -0,0 +1,27 @@
<Window xmlns="https://github.com/avaloniaui"
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"
mc:Ignorable="d" Width="600" Height="600"
x:Class="AvaloniaCrossPlat.MainWindow"
WindowStartupLocation="CenterScreen"
Title="AvaloniaCrossPlat">
<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" />
<Button Margin="10,0" Name="BtnDownloadUpdate" Content="Download" Click="BtnDownloadUpdateClick" IsEnabled="False" />
<Button Name="BtnRestartApply" Content="Restart &amp; Apply" Click="BtnRestartApplyClick" IsEnabled="False" />
</StackPanel>
</StackPanel>
<ScrollViewer Name="ScrollLog" Grid.Row="2" HorizontalAlignment="Stretch" VerticalAlignment="Stretch">
<TextBlock Name="TextLog" Background="BlueViolet" Foreground="White" TextWrapping="Wrap" />
</ScrollViewer>
</Grid>
</Window>

View File

@@ -0,0 +1,103 @@
using System;
using System.Text;
using Avalonia.Controls;
using Avalonia.Interactivity;
using Avalonia.Threading;
using Microsoft.Extensions.Logging;
using Velopack;
namespace AvaloniaCrossPlat;
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(_update);
}
private void LogUpdated(object sender, LogUpdatedEventArgs e)
{
// logs can be sent from other threads
Dispatcher.UIThread.InvokeAsync(() => {
TextLog.Text = e.Text;
ScrollLog.ScrollToEnd();
});
}
private void Progress(int percent)
{
// progress can be sent from other threads
Dispatcher.UIThread.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,44 @@
using System;
using System.Text;
using Microsoft.Extensions.Logging;
namespace AvaloniaCrossPlat;
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,55 @@
using System;
using System.Linq;
using System.Reflection;
using Avalonia;
using Velopack;
namespace AvaloniaCrossPlat;
class Program
{
public static string UpdateUrl { get; private set; }
public static MemoryLogger Log { get; private set; }
// Initialization code. Don't use any Avalonia, third-party APIs or any
// SynchronizationContext-reliant code before AppMain is called: things aren't initialized
// yet and stuff might break.
[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()
.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 == "AvaloniaSampleReleaseDir")
.Single().Value;
// Now it's time to run Avalonia
BuildAvaloniaApp()
.StartWithClassicDesktopLifetime(args);
} catch (Exception ex) {
string message = "Unhandled exception: " + ex.ToString();
Console.WriteLine(message);
throw;
}
}
// Avalonia configuration, don't remove method; also used by visual designer.
public static AppBuilder BuildAvaloniaApp()
{
return AppBuilder.Configure<App>()
.UsePlatformDetect()
.WithInterFont()
.LogToTrace();
}
}

Binary file not shown.

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.4 KiB

View File

@@ -0,0 +1,18 @@
<?xml version="1.0" encoding="utf-8"?>
<assembly manifestVersion="1.0" xmlns="urn:schemas-microsoft-com:asm.v1">
<!-- This manifest is used on Windows only.
Don't remove it as it might cause problems with window transparency and embedded controls.
For more details visit https://learn.microsoft.com/en-us/windows/win32/sbscs/application-manifests -->
<assemblyIdentity version="1.0.0.0" name="AvaloniaCrossPlat.Desktop"/>
<compatibility xmlns="urn:schemas-microsoft-com:compatibility.v1">
<application>
<!-- A list of the Windows versions that this application has been tested on
and is designed to work with. Uncomment the appropriate elements
and Windows will automatically select the most compatible environment. -->
<!-- Windows 10 -->
<supportedOS Id="{8e0f7a12-bfb3-4fe8-b9a5-48fd50a15a9a}" />
</application>
</compatibility>
</assembly>

View File

@@ -0,0 +1,24 @@
#!/bin/bash
# Find the absolute path of the script
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
# Check if version parameter is provided
if [ "$#" -ne 1 ]; then
echo "Version number is required."
echo "Usage: ./build.sh [version]"
exit 1
fi
BUILD_VERSION="$1"
RELEASE_DIR="$SCRIPT_DIR/releases"
PUBLISH_DIR="$SCRIPT_DIR/publish"
ICON_PATH="$SCRIPT_DIR/Velopack.png"
echo ""
echo "Compiling AvaloniaCrossPlat with dotnet..."
dotnet publish -c Release --self-contained -r linux-x64 -o "$PUBLISH_DIR"
echo ""
echo "Building Velopack Release v$BUILD_VERSION"
vpk pack -u AvaloniaCrossPlat -v $BUILD_VERSION -o "$RELEASE_DIR" -p "$PUBLISH_DIR" -i "$ICON_PATH"

View File

@@ -0,0 +1,24 @@
#!/bin/bash
# Find the absolute path of the script
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
# Check if version parameter is provided
if [ "$#" -ne 1 ]; then
echo "Version number is required."
echo "Usage: ./build.sh [version]"
exit 1
fi
BUILD_VERSION="$1"
RELEASE_DIR="$SCRIPT_DIR/releases"
PUBLISH_DIR="$SCRIPT_DIR/publish"
ICON_PATH="$SCRIPT_DIR/Velopack.icns"
echo ""
echo "Compiling AvaloniaCrossPlat with dotnet..."
dotnet publish -c Release --self-contained -r osx-x64 -o "$PUBLISH_DIR"
echo ""
echo "Building Velopack Release v$BUILD_VERSION"
vpk pack -u AvaloniaCrossPlat -v $BUILD_VERSION -o "$RELEASE_DIR" -p "$PUBLISH_DIR" -i "$ICON_PATH"

View File

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

View File

@@ -0,0 +1,36 @@
#!/bin/bash
# Find the absolute path of the script
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
# Check if version parameter is provided
if [ "$#" -ne 1 ]; then
echo "Version number is required."
echo "Usage: ./build.sh [version]"
exit 1
fi
BUILD_VERSION="$1"
RELEASE_DIR="$SCRIPT_DIR/../releases"
PUBLISH_DIR="$SCRIPT_DIR/../publish"
ICON_PATH="$SCRIPT_DIR/../Velopack.png"
echo ""
echo "Building Velopack Rust"
cd "$SCRIPT_DIR/../../../src/Rust"
cargo build --target x86_64-unknown-linux-gnu
cp target/x86_64-unknown-linux-gnu/release/update target/release/update
echo ""
echo "Building Velopack Vpk"
cd "$SCRIPT_DIR/../../.."
dotnet build src/Velopack.Vpk/Velopack.Vpk.csproj
echo ""
cd "$SCRIPT_DIR/.."
echo "Compiling AvaloniaCrossPlat with dotnet..."
dotnet publish -c Release --self-contained -r linux-x64 -o "$PUBLISH_DIR" -p:UseLocalVelopack=true
echo ""
echo "Building Velopack Release v$BUILD_VERSION"
"$SCRIPT_DIR/../../../build/Debug/net8.0/vpk" pack -u AvaloniaCrossPlat -v $BUILD_VERSION -o "$RELEASE_DIR" -p "$PUBLISH_DIR" -i "$ICON_PATH"

View File

@@ -0,0 +1,35 @@
#!/bin/bash
# Find the absolute path of the script
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
# Check if version parameter is provided
if [ "$#" -ne 1 ]; then
echo "Version number is required."
echo "Usage: ./build.sh [version]"
exit 1
fi
BUILD_VERSION="$1"
RELEASE_DIR="$SCRIPT_DIR/../releases"
PUBLISH_DIR="$SCRIPT_DIR/../publish"
ICON_PATH="$SCRIPT_DIR/../Velopack.icns"
echo ""
echo "Building Velopack Rust"
cd "$SCRIPT_DIR/../../../src/Rust"
cargo build
echo ""
echo "Building Velopack Vpk"
cd "$SCRIPT_DIR/../../.."
dotnet build src/Velopack.Vpk/Velopack.Vpk.csproj
echo ""
cd "$SCRIPT_DIR/.."
echo "Compiling AvaloniaCrossPlat with dotnet..."
dotnet publish -c Release --self-contained -r osx-x64 -o "$PUBLISH_DIR" -p:UseLocalVelopack=true
echo ""
echo "Building Velopack Release v$BUILD_VERSION"
"$SCRIPT_DIR/../../../build/Debug/net8.0/vpk" pack -u AvaloniaCrossPlat -v $BUILD_VERSION -o "$RELEASE_DIR" -p "$PUBLISH_DIR" -i "$ICON_PATH"

View File

@@ -0,0 +1,34 @@
@echo off
REM This script requires several tools to be installed for it to work:
REM cargo (rust): winget install Rustlang.Rustup
REM Nerdbank.GitVersioning (nbgv): dotnet tool install --global nbgv
REM C++ Build Tools, typically installed via "Desktop development with C++" workload.
setlocal enabledelayedexpansion
if "%~1"=="" (
echo Version number is required.
echo Usage: build.bat [version] [extra_args...]
exit /b 1
)
echo.
echo Building Velopack Rust
cd %~dp0..\..\..\src\Rust
cargo build --features windows
echo.
echo Building Velopack Vpk
cd %~dp0..\..\..\
dotnet build src/Velopack.Vpk/Velopack.Vpk.csproj
cd %~dp0..
set "version=%~1"
echo.
echo Compiling AvaloniaCrossPlat with dotnet...
dotnet publish -c Release --self-contained -r win-x64 -o publish -p:UseLocalVelopack=true
echo.
echo Building Velopack Release v%version%
%~dp0..\..\..\build\Debug\net8.0\vpk pack -u AvaloniaCrossPlat -o releases -p publish -v %*

View File

@@ -0,0 +1,47 @@
# AvaloniaCrossPlat
_Prerequisites: vpk command line tool installed_
This app demonstrates how to use Avalonia to provide a desktop UI, installer, and updates for Mac, Linux, and Windows.
You can run this sample by executing the build script with a version number (eg. `build-win.bat 1.0.0`).
There are build scripts provided for each platform (`build-win.bat`, `build-linux.sh`, `build-osx.bat`).
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.
On Linux, there is no installer, since the program is shipped as a `.AppImage`, it is only portable - however it can still update itself by replacing it's own `.AppImage` (even if that `.AppImage` is inside priveleged directories)
## Avalonia Implementation Notes
The Avalonia Template will generate a `Program.Main()` for you. You need to be careful when editing this file as to not break the Avalonia designer. You must not delete the `BuildAvaloniaApp()` function, but you must add the `VelopackApp` builder to the `Main()` method. For example:
```cs
class Program
{
// Initialization code. Don't use any Avalonia, third-party APIs or any
// SynchronizationContext-reliant code before AppMain is called.
// things aren't initialized yet and stuff might break.
[STAThread]
public static void Main(string[] args)
{
VelopackApp.Build().Run();
BuildAvaloniaApp().StartWithClassicDesktopLifetime(args);
}
// Avalonia configuration, don't remove method; also used by visual designer.
public static AppBuilder BuildAvaloniaApp()
{
return AppBuilder.Configure<App>()
.UsePlatformDetect()
.WithInterFont()
.LogToTrace();
}
}
```
## Testing changes to Velopack
This project has a folder of development build scripts (e.g. `.\dev-scripts\build-win.bat`) which will create a release in same way as the main scripts, except with a project reference to Velopack, and it will invoke the local vpk tool as well.
If you have made a change to Velopack and would like to test it in the sample app, these are the scripts you should run instead.
Don't forget to review the [compiling guide](../../docs/compiling.md) to make sure you can build Velopack.

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,8 @@
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,101 @@
using System.Text;
using System.Windows;
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(_update);
}
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,44 @@
using System.Text;
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,50 @@
using System.Reflection;
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,30 @@
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<OutputType>WinExe</OutputType>
<TargetFramework>net48</TargetFramework>
<ImplicitUsings>true</ImplicitUsings>
<UseWPF>true</UseWPF>
<EnableWindowsTargeting>true</EnableWindowsTargeting>
<LangVersion>latest</LangVersion>
<NoWarn>$(NoWarn);IDE0161</NoWarn>
</PropertyGroup>
<PropertyGroup>
<!-- This overrides the default Program.Main that WPF creates for you, and allows you to add VelopackApp -->
<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, remove in your app -->
<ItemGroup>
<AssemblyAttribute Include="System.Reflection.AssemblyMetadataAttribute">
<_Parameter1>WpfSampleReleaseDir</_Parameter1>
<_Parameter2>$(MSBuildThisFileDirectory)releases</_Parameter2>
</AssemblyAttribute>
</ItemGroup>
</Project>

View File

@@ -0,0 +1,18 @@
@echo off
setlocal enabledelayedexpansion
if "%~1"=="" (
echo Version number is required.
echo Usage: build.bat [version]
exit /b 1
)
set "version=%~1"
echo.
echo Compiling VeloWpfSample with dotnet...
dotnet publish -c Release -o %~dp0publish
echo.
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,30 @@
# VeloWpfSample
_Prerequisites: vpk command line tool installed_
This app demonstrates how to use WPF to provide a desktop UI, installer, and updates for Windows only.
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();
}
```

10
samples/readme.md Normal file
View File

@@ -0,0 +1,10 @@
# Velopack Examples
- [**AvaloniaCrossPlat**](AvaloniaCrossPlat) - uses Avalonia to provide a desktop UI, installer, and updates for Mac, Linux, and Windows.
- [**VeloWpfSample**](VeloWpfSample) - demonstrates how to use Velopack effectively with WPF.
## Other Languages
Note that only sample apps written in C# using the core reference library are available here.
For other programming languages (Rust, C++, JS, etc) please visit the [Velopack Fusion](https://github.com/velopack/velopack.fusion) repository.