This example illustrates how to start a complex operation in a background thread and display its progress and status (Loading, Finishing, etc.) in the Splash Screen. This Splash Screen also contains the Close button that allows users to cancel the operation and close the Splash Screen.
Use SplashScreenManagerService to operate with this manager in an MVVM way.
Implementation Details
Add the SplashScreenManagerService to the MainView. Specify the required Splash Screen UI in the SplashScreenView and assign this view to the service's ViewTemplate:
XAML<dxmvvm:Interaction.Behaviors>
<dxmvvm:DispatcherService/>
<dx:SplashScreenManagerService OwnerLockMode="WindowInputOnly"
StartupLocation="CenterOwner">
<dx:SplashScreenManagerService.ViewTemplate>
<DataTemplate>
<Views:SplashScreenView />
</DataTemplate>
</dx:SplashScreenManagerService.ViewTemplate>
<dx:SplashScreenManagerService.SplashScreenWindowStyle>
<Style TargetType="dx:SplashScreenWindow">
<Setter Property="AllowAcrylic" Value="True" />
<Setter Property="AllowsTransparency" Value="True" />
<Setter Property="Background" Value="#B887A685" />
</Style>
</dx:SplashScreenManagerService.SplashScreenWindowStyle>
</dx:SplashScreenManagerService>
</dxmvvm:Interaction.Behaviors>
When the Splash Screen is shown, this SplashScreenView's DataContext contains an instance of the DXSplashScreenViewModel (or its descendant) class. You can bind visual elements of the SplashScreenView to the Logo
, Title
, Progress
, and Status
properties from this class. When you change these settings in the SplashScreenManagerService.ViewModel object, SplashScreenView's elements reflect these changes.
The executed complex operation does not allow you to update the view model that is created in the main thread. To avoid this, create a DispatcherService that can update the Splash Screen's view model properties.
The main view model is a ViewModelBase class descendant. Use the approach from the Services in ViewModelBase descendants article to get access to the view services:
C#public ISplashScreenManagerService SplashScreenManagerService {
get { return this.GetService<ISplashScreenManagerService>(); }
}
public IDispatcherService DispatcherService {
get { return this.GetService<IDispatcherService>(); }
}
NOTE
Refer to the following help topics if you use other view model types:
- Services in Generated View Models
- Services in Custom ViewModels
The BackgroundWorker class allows you to execute a complex operation in a background thread. Set the WorkerSupportsCancellation
property to true
to cancel the operation on demand:
C#BackgroundWorker worker;
void RunBackgroundWorker() {
worker = new BackgroundWorker();
worker.DoWork += Worker_DoWork;
worker.WorkerSupportsCancellation = true;
worker.RunWorkerAsync();
}
private void Worker_DoWork(object sender, DoWorkEventArgs e) {
int i = -1;
while(++i < 100) {
if(worker.CancellationPending) {
e.Cancel = true;
break;
}
UpdateSplashScreenContent(i);
Thread.Sleep(200);
}
this.DispatcherService.Invoke(() => {
this.SplashScreenManagerService.Close();
worker.DoWork -= Worker_DoWork;
worker = null;
});
}
When a user clicks the "Start a complex operation" button, initialize properties in the SplashScreenManagerService.ViewModel and show the Splash Screen:
C#[Command(CanExecuteMethodName = "CanStart")]
public void Start() {
if(this.SplashScreenManagerService != null) {
this.SplashScreenManagerService.ViewModel = new DXSplashScreenViewModel();
this.InitSplashScreenViewModel(this.SplashScreenManagerService.ViewModel);
this.SplashScreenManagerService.Show();
this.RunBackgroundWorker();
}
}
In the InitSplashScreenViewModel define the Title, SubTitle, Progress, and other settings. Set the Tag property in the Splash Screen's view model to DelegateCommand that calls the CancelOperation method from the main view model:
C#void InitSplashScreenViewModel(DXSplashScreenViewModel vm) {
vm.Title = "SOME BACKGROUND WORK";
vm.SubTitle = "This can take some time";
vm.Logo = new Uri("pack://application:,,,/logo.png");
vm.IsIndeterminate = false;
vm.Tag = new DelegateCommand(CancelOperation, CanCancelOperation);
}
public bool CanCancelOperation() { return worker != null && worker.IsBusy; }
public void CancelOperation() {
if(worker != null && worker.IsBusy)
worker.CancelAsync();
}
In the Splash Screen's view, bind the close button's Command
property to the View Model's Tag property:
XAML...
<dx:SimpleButton Margin="20"
HorizontalAlignment="Right"
VerticalAlignment="Top"
Command="{Binding Tag}"
Glyph="{dx:DXImage GrayScaleImages/Edit/Delete_16x16.png}"
ToolTip="Cancel and Close" />
...
To update the Splash Screen during a complex operation, set the Progress and State properties in the SplashScreenManagerService.ViewModel object to the required values. To do this in the main thread, use the DispatcherService's Invoke method:
C#void UpdateSplashScreenContent(int progressValue) {
var state = string.Empty;
state = progressValue < 20 ? "Starting..." : progressValue < 70 ? "Loading data.." : "Finishing";
this.DispatcherService.Invoke(() => {
this.SplashScreenManagerService.ViewModel.Progress = progressValue;
this.SplashScreenManagerService.ViewModel.Status = $"({progressValue} %) - {state}";
});
}
Files to Review
- MainView.xaml (VB: MainView.xaml)
- SplashScreenView.xaml (VB: SplashScreenView.xaml)
- MainViewModel.cs (MainViewModel.vb)
Documentation
More Examples
Does this example address your development requirements/objectives?
(you will be redirected to DevExpress.com to submit your response)
Example Code
XAML<UserControl x:Class="SplashScreenManagerExample.Views.MainView"
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:dx="http://schemas.devexpress.com/winfx/2008/xaml/core"
xmlns:dxmvvm="http://schemas.devexpress.com/winfx/2008/xaml/mvvm"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:ViewModels="clr-namespace:SplashScreenManagerExample.ViewModels"
xmlns:Views="clr-namespace:SplashScreenManagerExample.Views"
mc:Ignorable="d"
d:DesignHeight="300"
d:DesignWidth="300">
<dxmvvm:Interaction.Behaviors>
<dx:SplashScreenManagerService InputBlock="WindowContent"
StartupLocation="CenterOwner">
<dx:SplashScreenManagerService.ViewTemplate>
<DataTemplate>
<Views:SplashScreenView />
</DataTemplate>
</dx:SplashScreenManagerService.ViewTemplate>
<dx:SplashScreenManagerService.SplashScreenWindowStyle>
<Style TargetType="dx:SplashScreenWindow">
<Setter Property="AllowAcrylic" Value="True" />
<Setter Property="AllowsTransparency" Value="True" />
<Setter Property="Background" Value="#B887A685" />
</Style>
</dx:SplashScreenManagerService.SplashScreenWindowStyle>
</dx:SplashScreenManagerService>
<dxmvvm:DispatcherService/>
</dxmvvm:Interaction.Behaviors>
<UserControl.DataContext>
<ViewModels:MainViewModel />
</UserControl.DataContext>
<Grid>
<Button Width="200"
HorizontalAlignment="Center"
VerticalAlignment="Center"
Command="{Binding StartCommand}"
Content="Start a complex operation" />
</Grid>
</UserControl>
XAML<UserControl x:Class="SplashScreenManagerExample.Views.SplashScreenView"
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:dx="http://schemas.devexpress.com/winfx/2008/xaml/core"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
MinWidth="660"
MinHeight="360"
mc:Ignorable="d"
Foreground="White">
<Grid Background="Transparent">
<Grid.RowDefinitions>
<RowDefinition />
<RowDefinition />
<RowDefinition />
<RowDefinition />
</Grid.RowDefinitions>
<dx:DXImage Margin="20"
HorizontalAlignment="Left"
VerticalAlignment="Top"
Source="{Binding Logo}"
Stretch="None" />
<dx:SimpleButton Margin="20"
HorizontalAlignment="Right"
VerticalAlignment="Top"
Command="{Binding Tag}"
Glyph="{dx:DXImage GrayScaleImages/Edit/Delete_16x16.png}"
ToolTip="Cancel and Close" />
<StackPanel Grid.Row="1"
HorizontalAlignment="Stretch"
VerticalAlignment="Bottom">
<TextBlock Margin="2"
HorizontalAlignment="Center"
FontSize="21"
Text="{Binding Title}" />
<TextBlock Margin="2"
HorizontalAlignment="Center"
FontSize="15"
Opacity="0.75"
Text="{Binding Subtitle}" />
</StackPanel>
<ProgressBar Grid.Row="2"
Width="350"
Height="16"
HorizontalAlignment="Center"
VerticalAlignment="Center"
Value="{Binding Progress}"
Background="#FFA3A3A3"
BorderBrush="#FFF3F3F3"
BorderThickness="1"
Foreground="#FFFFFFFF"
IsIndeterminate="{Binding IsIndeterminate}" />
<TextBlock Grid.Row="3"
Margin="20,3,3,20"
HorizontalAlignment="Left"
VerticalAlignment="Bottom"
FontSize="11"
Text="{Binding Copyright}" />
<TextBlock Grid.Row="2"
Margin="0,3,3,6"
HorizontalAlignment="Center"
VerticalAlignment="Bottom"
FontSize="11"
Text="{Binding Status}" />
</Grid>
</UserControl>
C#using System;
using System.ComponentModel;
using System.Threading;
using System.Windows.Threading;
using DevExpress.Mvvm;
using DevExpress.Mvvm.DataAnnotations;
namespace SplashScreenManagerExample.ViewModels {
public class MainViewModel : ViewModelBase {
public ISplashScreenManagerService SplashScreenManagerService
{
get { return this.GetService<ISplashScreenManagerService>(); }
}
public IDispatcherService DispatcherService
{
get { return this.GetService<IDispatcherService>(); }
}
BackgroundWorker worker;
public bool CanStart() { return worker == null || !worker.IsBusy; }
[Command(CanExecuteMethodName = "CanStart")]
public void Start() {
if(this.SplashScreenManagerService != null) {
this.SplashScreenManagerService.ViewModel = new DXSplashScreenViewModel();
this.InitSplashScreenViewModel(this.SplashScreenManagerService.ViewModel);
this.SplashScreenManagerService.Show();
this.RunBackgroundWorker();
}
}
void RunBackgroundWorker() {
worker = new BackgroundWorker();
worker.DoWork += Worker_DoWork;
worker.WorkerSupportsCancellation = true;
worker.RunWorkerAsync();
}
private void Worker_DoWork(object sender, DoWorkEventArgs e) {
int i = -1;
while(++i < 100) {
if(worker.CancellationPending) {
e.Cancel = true;
break;
}
UpdateSplashScreenContent(i);
Thread.Sleep(200);
}
this.DispatcherService.Invoke(() => {
this.SplashScreenManagerService.Close();
worker.DoWork -= Worker_DoWork;
worker = null;
});
}
void UpdateSplashScreenContent(int progressValue) {
var state = string.Empty;
state = progressValue < 20 ? "Starting..." : progressValue < 70 ? "Loading data.." : "Finishing";
this.DispatcherService
.Invoke(() => {
this.SplashScreenManagerService.ViewModel.Progress = progressValue;
this.SplashScreenManagerService.ViewModel.Status = $"({progressValue} %) - {state}";
});
}
void InitSplashScreenViewModel(DXSplashScreenViewModel vm) {
vm.Title = "SOME BACKGROUND WORK";
vm.Subtitle = "This can take some time";
vm.Logo = new Uri("pack://application:,,,/logo.png");
vm.IsIndeterminate = false;
vm.Tag = new DelegateCommand(CancelOperation, CanCancelOperation);
}
public bool CanCancelOperation() { return worker != null && worker.IsBusy; }
public void CancelOperation() {
if(worker != null && worker.IsBusy)
worker.CancelAsync();
}
}
}