Skip to content

Commit

Permalink
fix: exception in multi-threaded applications (#266)
Browse files Browse the repository at this point in the history
Fixes #263

---------

Co-authored-by: i28423 <[email protected]>
Co-authored-by: FantasticFiasco <[email protected]>
  • Loading branch information
3 people authored Oct 7, 2023
1 parent 2e3b8ca commit ad75753
Show file tree
Hide file tree
Showing 20 changed files with 544 additions and 20 deletions.
4 changes: 2 additions & 2 deletions .editorconfig
Original file line number Diff line number Diff line change
Expand Up @@ -33,9 +33,9 @@ dotnet_style_coalesce_expression = true:suggestion
dotnet_style_null_propagation = true:suggestion
dotnet_style_explicit_tuple_names = true:suggestion
# Prefer "var" everywhere
csharp_style_var_for_built_in_types = true:suggestion
csharp_style_var_for_built_in_types = false:suggestion
csharp_style_var_when_type_is_apparent = true:suggestion
csharp_style_var_elsewhere = true:suggestion
csharp_style_var_elsewhere = false:suggestion
# Prefer method-like constructs to have a expression body
csharp_style_expression_bodied_methods = true:suggestion
csharp_style_expression_bodied_constructors = true:suggestion
Expand Down
4 changes: 4 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,10 @@ This project adheres to [Semantic Versioning](http://semver.org/) and is followi

## Unreleased

### :syringe: Fixed

- [#263](https://github.com/FantasticFiasco/mvvm-dialogs/issues/263) Exception is thrown when used in a multiple Single Threaded Apartment (STA) application (contributed by [@HeedfulCrayon](https://github.com/HeedfulCrayon))

## 9.1.0 - 2022-09-26

### :zap: Added
Expand Down
14 changes: 14 additions & 0 deletions MvvmDialogs.sln
Original file line number Diff line number Diff line change
Expand Up @@ -75,6 +75,10 @@ Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "TestBaseClasses", "samples\
EndProject
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Demo.CustomDialogTypeLocator.Test", "samples\Demo.CustomDialogTypeLocator.Test\Demo.CustomDialogTypeLocator.Test.csproj", "{2F0419C7-BCF0-42B2-B95B-25A1BC960697}"
EndProject
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Demo.StaThreads", "samples\Demo.StaThreads\Demo.StaThreads.csproj", "{8F535A7F-BCC5-41D5-B5F9-D8CAAFD7D583}"
EndProject
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Demo.StaThreads.Test", "samples\Demo.StaThreads.Test\Demo.StaThreads.Test.csproj", "{F0215BE6-48A3-4F4D-85D2-E6EE57CB0793}"
EndProject
Global
GlobalSection(SolutionConfigurationPlatforms) = preSolution
Debug|Any CPU = Debug|Any CPU
Expand Down Expand Up @@ -213,6 +217,14 @@ Global
{2F0419C7-BCF0-42B2-B95B-25A1BC960697}.Debug|Any CPU.Build.0 = Debug|Any CPU
{2F0419C7-BCF0-42B2-B95B-25A1BC960697}.Release|Any CPU.ActiveCfg = Release|Any CPU
{2F0419C7-BCF0-42B2-B95B-25A1BC960697}.Release|Any CPU.Build.0 = Release|Any CPU
{8F535A7F-BCC5-41D5-B5F9-D8CAAFD7D583}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{8F535A7F-BCC5-41D5-B5F9-D8CAAFD7D583}.Debug|Any CPU.Build.0 = Debug|Any CPU
{8F535A7F-BCC5-41D5-B5F9-D8CAAFD7D583}.Release|Any CPU.ActiveCfg = Release|Any CPU
{8F535A7F-BCC5-41D5-B5F9-D8CAAFD7D583}.Release|Any CPU.Build.0 = Release|Any CPU
{F0215BE6-48A3-4F4D-85D2-E6EE57CB0793}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{F0215BE6-48A3-4F4D-85D2-E6EE57CB0793}.Debug|Any CPU.Build.0 = Debug|Any CPU
{F0215BE6-48A3-4F4D-85D2-E6EE57CB0793}.Release|Any CPU.ActiveCfg = Release|Any CPU
{F0215BE6-48A3-4F4D-85D2-E6EE57CB0793}.Release|Any CPU.Build.0 = Release|Any CPU
EndGlobalSection
GlobalSection(SolutionProperties) = preSolution
HideSolutionNode = FALSE
Expand Down Expand Up @@ -251,6 +263,8 @@ Global
{6760EB09-F75D-44DE-BEB3-5C3DF1D57DCE} = {62592C78-61AA-44B4-A7C3-EAC678A568A2}
{B6AD17E0-892C-460E-97F3-58A0EFFC3C33} = {62592C78-61AA-44B4-A7C3-EAC678A568A2}
{2F0419C7-BCF0-42B2-B95B-25A1BC960697} = {62592C78-61AA-44B4-A7C3-EAC678A568A2}
{8F535A7F-BCC5-41D5-B5F9-D8CAAFD7D583} = {62592C78-61AA-44B4-A7C3-EAC678A568A2}
{F0215BE6-48A3-4F4D-85D2-E6EE57CB0793} = {62592C78-61AA-44B4-A7C3-EAC678A568A2}
EndGlobalSection
GlobalSection(ExtensibilityGlobals) = postSolution
SolutionGuid = {983A22B3-AE5B-4B67-A798-F6A6697B82A8}
Expand Down
41 changes: 41 additions & 0 deletions samples/Demo.StaThreads.Test/Demo.StaThreads.Test.csproj
Original file line number Diff line number Diff line change
@@ -0,0 +1,41 @@
<Project Sdk="Microsoft.NET.Sdk">

<PropertyGroup>
<TargetFramework>net472</TargetFramework>

<RootNamespace>Demo.StaThreads</RootNamespace>

<UseWpf>true</UseWpf>

<ImplicitUsings>enable</ImplicitUsings>
<Nullable>enable</Nullable>

<IsPackable>false</IsPackable>
<IsTestProject>true</IsTestProject>
</PropertyGroup>

<ItemGroup>
<Content Include="xunit.runner.json" CopyToOutputDirectory="PreserveNewest" />
</ItemGroup>

<ItemGroup>
<ProjectReference Include="..\..\src\MvvmDialogs.csproj" />
<ProjectReference Include="..\Demo.StaThreads\Demo.StaThreads.csproj" />
<ProjectReference Include="..\TestBaseClasses\TestBaseClasses.csproj" />
</ItemGroup>

<ItemGroup>
<PackageReference Include="Microsoft.NET.Test.Sdk" Version="17.7.2" />
<PackageReference Include="Moq" Version="4.20.69" />
<PackageReference Include="xunit" Version="2.5.1" />
<PackageReference Include="xunit.runner.visualstudio" Version="2.5.1">
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
<PrivateAssets>all</PrivateAssets>
</PackageReference>
<PackageReference Include="coverlet.collector" Version="6.0.0">
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
<PrivateAssets>all</PrivateAssets>
</PackageReference>
</ItemGroup>

</Project>
34 changes: 34 additions & 0 deletions samples/Demo.StaThreads.Test/MainWindowViewModelTest.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
using System.Windows;
using Moq;
using MvvmDialogs;
using Xunit;

namespace Demo.StaThreads;

public class MainWindowViewModelTest
{
[Fact]
public void ShowMessageBox()
{
// Arrange
var dialogService = new Mock<IDialogService>();
var viewModel = new MainWindowViewModel(dialogService.Object);

dialogService
.Setup(mock =>
mock.ShowMessageBox(
viewModel,
It.IsAny<string>(),
"",
MessageBoxButton.OK,
MessageBoxImage.None,
MessageBoxResult.None))
.Returns(MessageBoxResult.OK);

// Act
viewModel.ShowMessageBoxCommand.Execute(null);

// Assert
dialogService.VerifyAll();
}
}
26 changes: 26 additions & 0 deletions samples/Demo.StaThreads.Test/ScreenObjects/MainScreen.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
using FlaUI.Core.AutomationElements;
using FlaUI.Core.Input;
using TestBaseClasses;

namespace Demo.StaThreads.ScreenObjects;

public class MainScreen : Screen
{
public MainScreen(Window window)
: base(window)
{
}

private Button MessageBoxButton => ElementByAutomationId<Button>("1k7d1Nm8MkOYK5qGrdVX4Q");
private Label ConfirmationLabel => ElementByAutomationId<Label>("kT3_ZUZfsEK1QdZ2jBfuIQ");

public string Confirmation => ConfirmationLabel.Text;

public MessageBoxScreen ClickMessageBox()
{
MessageBoxButton.Click();
Wait.UntilInputIsProcessed();

return new MessageBoxScreen(GetModalWindow(string.Empty));
}
}
29 changes: 29 additions & 0 deletions samples/Demo.StaThreads.Test/ScreenObjects/MessageBoxScreen.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
using FlaUI.Core.AutomationElements;
using TestBaseClasses;

namespace Demo.StaThreads.ScreenObjects;

public class MessageBoxScreen : Screen
{
public MessageBoxScreen(Window window)
: base(window)
{
}

private Label MessageLabel => ElementByAutomationId<Label>("65535");

public string Caption => Window.Title;

public string Message => MessageLabel.Text;

public bool IsIconVisible => ElementWithAutomationIdExists("20");

public bool IsOKButtonVisible => ElementWithTextExists(OK);

public bool IsCancelButtonVisible => ElementWithTextExists(Cancel);

public void ClickOK()
{
DefaultOKButton.Click();
}
}
38 changes: 38 additions & 0 deletions samples/Demo.StaThreads.Test/UITests.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
using Demo.StaThreads.ScreenObjects;
using TestBaseClasses;
using Xunit;

namespace Demo.StaThreads;

public class UITests : IDisposable
{
private readonly Application app;
private readonly MainScreen mainScreen;

public UITests()
{
app = Application.Launch("Demo.StaThreads.exe");
mainScreen = new MainScreen(app.GetMainWindowThatStartsWith("Demo - STA Threads"));
}

[Fact]
[Trait("Category", "Manual")]
public void ConfirmationWithText()
{
var messageBoxScreen = mainScreen.ClickMessageBox();
Assert.Equal(string.Empty, messageBoxScreen.Caption);
Assert.Equal("This is the text.", messageBoxScreen.Message);
Assert.False(messageBoxScreen.IsIconVisible);
Assert.True(messageBoxScreen.IsOKButtonVisible);
Assert.False(messageBoxScreen.IsCancelButtonVisible);

messageBoxScreen.ClickOK();

Assert.Equal("We got confirmation to continue!", mainScreen.Confirmation);
}

public void Dispose()
{
app.Dispose();
}
}
4 changes: 4 additions & 0 deletions samples/Demo.StaThreads.Test/xunit.runner.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
{
"$schema": "https://xunit.net/schema/current/xunit.runner.schema.json",
"shadowCopy": false
}
8 changes: 8 additions & 0 deletions samples/Demo.StaThreads/App.xaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
<Application
x:Class="Demo.StaThreads.App"
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:Demo.StaThreads"
mc:Ignorable="d" />
50 changes: 50 additions & 0 deletions samples/Demo.StaThreads/App.xaml.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,50 @@
using System.Threading;
using System.Windows;
using System.Windows.Threading;
using CommunityToolkit.Mvvm.DependencyInjection;
using Microsoft.Extensions.DependencyInjection;
using MvvmDialogs;

namespace Demo.StaThreads;

public partial class App : Application
{
protected override void OnStartup(StartupEventArgs e)
{
Ioc.Default.ConfigureServices(
new ServiceCollection()
.AddSingleton<IDialogService, DialogService>()
.AddTransient<MainWindowViewModel>()
.BuildServiceProvider());

for (var id = 0; id < 2; id++)
{
var windowThread = new WindowThread(id);

var thread = new Thread(windowThread.Run);
thread.SetApartmentState(ApartmentState.STA);
thread.Start();
}
}

private class WindowThread
{
private readonly int id;

public WindowThread(int id)
{
this.id = id;
}

public void Run()
{
var mainWindow = new MainWindow();
mainWindow.Title += $" (id {id})";
mainWindow.DataContext = Ioc.Default.GetRequiredService<MainWindowViewModel>();

mainWindow.Show();

Dispatcher.Run();
}
}
}
26 changes: 26 additions & 0 deletions samples/Demo.StaThreads/ApplicationResources.xaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
<ResourceDictionary
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml">

<!--
Named Resources
-->

<Style x:Key="BaseStyle" TargetType="{x:Type FrameworkElement}">
<Setter Property="Margin" Value="3"/>
</Style>

<!--
Generic Resources
-->

<Style TargetType="{x:Type Button}" BasedOn="{StaticResource BaseStyle}">
<Setter Property="MinWidth" Value="75" />
</Style>

<Style TargetType="{x:Type TextBlock}" BasedOn="{StaticResource BaseStyle}">
<Setter Property="VerticalAlignment" Value="Center"/>
<Setter Property="TextWrapping" Value="Wrap" />
</Style>

</ResourceDictionary>
21 changes: 21 additions & 0 deletions samples/Demo.StaThreads/Demo.StaThreads.csproj
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
<Project Sdk="Microsoft.NET.Sdk.WindowsDesktop">

<PropertyGroup>
<OutputType>WinExe</OutputType>
<TargetFrameworks>net462;net6.0-windows</TargetFrameworks>
<UseWPF>true</UseWPF>
<RootNamespace>Demo.StaThreads</RootNamespace>
<AssemblyName>Demo.StaThreads</AssemblyName>
<Nullable>enable</Nullable>
</PropertyGroup>

<ItemGroup>
<ProjectReference Include="..\..\src\MvvmDialogs.csproj" />
</ItemGroup>

<ItemGroup>
<PackageReference Include="Microsoft.Extensions.DependencyInjection" Version="7.0.0" />
<PackageReference Include="CommunityToolkit.Mvvm" Version="8.2.1" />
</ItemGroup>

</Project>
29 changes: 29 additions & 0 deletions samples/Demo.StaThreads/MainWindow.xaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
<Window
x:Class="Demo.StaThreads.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:md="https://github.com/fantasticfiasco/mvvm-dialogs"
mc:Ignorable="d"
md:DialogServiceViews.IsRegistered="True"
WindowStartupLocation="CenterScreen"
Title="Demo - STA Threads"
Width="300"
Height="100">

<Window.Resources>
<ResourceDictionary Source="ApplicationResources.xaml" />
</Window.Resources>

<UniformGrid Columns="2" Rows="1">
<Button
AutomationProperties.AutomationId="1k7d1Nm8MkOYK5qGrdVX4Q"
Command="{Binding ShowMessageBoxCommand}">
<TextBlock Text="Show message box" />
</Button>
<TextBlock
AutomationProperties.AutomationId="kT3_ZUZfsEK1QdZ2jBfuIQ"
Text="{Binding Confirmation}" />
</UniformGrid>
</Window>
9 changes: 9 additions & 0 deletions samples/Demo.StaThreads/MainWindow.xaml.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
namespace Demo.StaThreads;

public partial class MainWindow
{
public MainWindow()
{
InitializeComponent();
}
}
Loading

0 comments on commit ad75753

Please sign in to comment.