diff --git a/.editorconfig b/.editorconfig index fa83487e0..ab8bec56d 100644 --- a/.editorconfig +++ b/.editorconfig @@ -8,104 +8,65 @@ root = true # A newline ending every file # Use 4 spaces as indentation [*] -insert_final_newline = true indent_style = space indent_size = 4 -# C# files -[*.cs] -# New line preferences -csharp_new_line_before_open_brace = all -csharp_new_line_before_else = true -csharp_new_line_before_catch = true -csharp_new_line_before_finally = true -csharp_new_line_before_members_in_object_initializers = true -csharp_new_line_before_members_in_anonymous_types = true -csharp_new_line_between_query_expression_clauses = true - -# Indentation preferences -csharp_indent_block_contents = true -csharp_indent_braces = false -csharp_indent_case_contents = true -csharp_indent_switch_labels = true -csharp_indent_labels = one_less_than_current - -# avoid this. unless absolutely necessary -dotnet_style_qualification_for_field = false:suggestion -dotnet_style_qualification_for_property = false:suggestion -dotnet_style_qualification_for_method = false:suggestion -dotnet_style_qualification_for_event = false:suggestion - -# only use var when it's obvious what the variable type is -csharp_style_var_for_built_in_types = false:none -csharp_style_var_when_type_is_apparent = false:none -csharp_style_var_elsewhere = false:suggestion - -# use language keywords instead of BCL types -dotnet_style_predefined_type_for_locals_parameters_members = true:suggestion -dotnet_style_predefined_type_for_member_access = true:suggestion - -# name all constant fields using PascalCase -dotnet_naming_rule.constant_fields_should_be_pascal_case.severity = suggestion -dotnet_naming_rule.constant_fields_should_be_pascal_case.symbols = constant_fields -dotnet_naming_rule.constant_fields_should_be_pascal_case.style = pascal_case_style - -dotnet_naming_symbols.constant_fields.applicable_kinds = field -dotnet_naming_symbols.constant_fields.required_modifiers = const - -dotnet_naming_style.pascal_case_style.capitalization = pascal_case - -# static fields should have s_ prefix -dotnet_naming_rule.static_fields_should_have_prefix.severity = suggestion -dotnet_naming_rule.static_fields_should_have_prefix.symbols = static_fields -dotnet_naming_rule.static_fields_should_have_prefix.style = static_prefix_style - -dotnet_naming_symbols.static_fields.applicable_kinds = field -dotnet_naming_symbols.static_fields.required_modifiers = static +[*.{asm,inc}] +indent_size = 8 -dotnet_naming_style.static_prefix_style.capitalization = pascal_case +# Xml project files +[*.{csproj,vcxproj,vcxproj.filters,proj,nativeproj,locproj}] +tab_width = 2 +indent_size = 2 +end_of_line = crlf +indent_style = space +insert_final_newline = true -# internal and private fields should be _camelCase -dotnet_naming_rule.camel_case_for_private_internal_fields.severity = suggestion -dotnet_naming_rule.camel_case_for_private_internal_fields.symbols = private_internal_fields -dotnet_naming_rule.camel_case_for_private_internal_fields.style = camel_case_underscore_style +# Xml config files +[*.{props,targets,config,nuspec}] +tab_width = 2 +indent_size = 2 +end_of_line = crlf +indent_style = space +insert_final_newline = true -dotnet_naming_symbols.private_internal_fields.applicable_kinds = field -dotnet_naming_symbols.private_internal_fields.applicable_accessibilities = private, internal +[CMakeLists.txt] +indent_size = 2 -dotnet_naming_style.camel_case_underscore_style.required_prefix = _ -dotnet_naming_style.camel_case_underscore_style.capitalization = camel_case +[**.md] +indent_style = tab +trim_trailing_whitespace = false -# Code style defaults -dotnet_sort_system_directives_first = true -csharp_preserve_single_line_blocks = true -csharp_preserve_single_line_statements = false +[{*.yml,*.yaml,package.json}] +indent_size = 2 -# Expression-level preferences -dotnet_style_object_initializer = true:suggestion -dotnet_style_collection_initializer = true:suggestion -dotnet_style_explicit_tuple_names = true:suggestion -dotnet_style_coalesce_expression = true:suggestion -dotnet_style_null_propagation = true:suggestion +[**.json] +indent_size = 2 -# Expression-bodied members -csharp_style_expression_bodied_methods = false:none -csharp_style_expression_bodied_constructors = false:none -csharp_style_expression_bodied_operators = false:none -csharp_style_expression_bodied_properties = true:none -csharp_style_expression_bodied_indexers = true:none -csharp_style_expression_bodied_accessors = true:none +[*.cmd] +indent_size = 2 -# Pattern matching -csharp_style_pattern_matching_over_is_with_cast_check = true:suggestion -csharp_style_pattern_matching_over_as_with_null_check = true:suggestion -csharp_style_inlined_variable_declaration = true:suggestion +[*.ps1] +indent_size = 2 -# Null checking preferences -csharp_style_throw_expression = true:suggestion -csharp_style_conditional_delegate_call = true:suggestion +[*.sh] +indent_size = 2 +end_of_line = lf -# Space preferences +[*.cs] +csharp_indent_block_contents = true +csharp_indent_braces = false +csharp_indent_case_contents = true +csharp_indent_case_contents_when_block = true +csharp_indent_labels = one_less_than_current +csharp_indent_switch_labels = true +csharp_new_line_before_catch = false +csharp_new_line_before_else = false +csharp_new_line_before_finally = false +csharp_new_line_before_members_in_anonymous_types = true +csharp_new_line_before_members_in_object_initializers = true +csharp_new_line_before_open_brace = none +csharp_new_line_between_query_expression_clauses = true csharp_space_after_cast = false csharp_space_after_colon_in_inheritance_clause = true csharp_space_after_comma = true @@ -113,7 +74,7 @@ csharp_space_after_dot = false csharp_space_after_keywords_in_control_flow_statements = true csharp_space_after_semicolon_in_for_statement = true csharp_space_around_binary_operators = before_and_after -csharp_space_around_declaration_statements = do_not_ignore +csharp_space_around_declaration_statements = false csharp_space_before_colon_in_inheritance_clause = true csharp_space_before_comma = false csharp_space_before_dot = false @@ -128,20 +89,188 @@ csharp_space_between_method_declaration_name_and_open_parenthesis = false csharp_space_between_method_declaration_parameter_list_parentheses = false csharp_space_between_parentheses = false csharp_space_between_square_brackets = false +csharp_prefer_braces = true:warning +csharp_prefer_simple_default_expression = true:suggestion +csharp_prefer_simple_using_statement = true:warning +csharp_prefer_static_local_function = true:suggestion +csharp_preserve_single_line_blocks = true +csharp_preserve_single_line_statements = true +csharp_style_allow_blank_line_after_colon_in_constructor_initializer_experimental = true:silent +csharp_style_allow_blank_line_after_token_in_arrow_expression_clause_experimental = true:silent +csharp_style_allow_blank_line_after_token_in_conditional_expression_experimental = true:silent +csharp_style_allow_blank_lines_between_consecutive_braces_experimental = true:silent +csharp_style_allow_embedded_statements_on_same_line_experimental = true:silent +csharp_style_conditional_delegate_call = true:warning +csharp_style_deconstructed_variable_declaration = true:suggestion +csharp_style_expression_bodied_accessors = true:warning +csharp_style_expression_bodied_constructors = true:warning +csharp_style_expression_bodied_indexers = true:warning +csharp_style_expression_bodied_lambdas = true:warning +csharp_style_expression_bodied_local_functions = true:warning +csharp_style_expression_bodied_methods = true:warning +csharp_style_expression_bodied_operators = true:warning +csharp_style_expression_bodied_properties = true:warning +csharp_style_implicit_object_creation_when_type_is_apparent = false:warning +csharp_style_inlined_variable_declaration = true:warning +csharp_style_namespace_declarations = file_scoped:warning +csharp_style_pattern_matching_over_as_with_null_check = true:suggestion +csharp_style_pattern_matching_over_is_with_cast_check = true:suggestion +csharp_style_prefer_extended_property_pattern = true:suggestion +csharp_style_prefer_index_operator = true:suggestion +csharp_style_prefer_local_over_anonymous_function = true:suggestion +csharp_style_prefer_method_group_conversion = true:suggestion +csharp_style_prefer_not_pattern = true:suggestion +csharp_style_prefer_null_check_over_type_check = true:suggestion +csharp_style_prefer_pattern_matching = true:suggestion +csharp_style_prefer_primary_constructors = true:suggestion +csharp_style_prefer_range_operator = true:suggestion +csharp_style_prefer_readonly_struct = true:suggestion +csharp_style_prefer_readonly_struct_member = true:suggestion +csharp_style_prefer_switch_expression = true:suggestion +csharp_style_prefer_top_level_statements = true:suggestion +csharp_style_prefer_tuple_swap = true:suggestion +csharp_style_prefer_utf8_string_literals = true:suggestion +csharp_style_throw_expression = true:suggestion +csharp_style_unused_value_assignment_preference = discard_variable:warning +csharp_style_unused_value_expression_statement_preference = discard_variable:silent +csharp_style_var_elsewhere = false:warning +csharp_style_var_for_built_in_types = false:warning +csharp_style_var_when_type_is_apparent = true:warning +csharp_using_directive_placement = outside_namespace:warning -[*.{asm,inc}] -indent_size = 8 +dotnet_diagnostic.SA1101.severity = none # Prefix Local Calls With This +dotnet_diagnostic.SA1500.severity = none # Braces For MultiLine Statements Must Not Share Line +dotnet_diagnostic.SA1633.severity = none # File Must Have Header +dotnet_diagnostic.SA1309.severity = none # Field Names Must Not Begin With Underscore -# Xml project files -[*.{csproj,vcxproj,vcxproj.filters,proj,nativeproj,locproj}] -indent_size = 2 +dotnet_diagnostic.CA1070.severity = warning # Do not declare event fields as virtual +dotnet_diagnostic.CA1001.severity = warning # Types that own disposable fields should be disposable +dotnet_diagnostic.CA1200.severity = warning # Avoid using cref tags with a prefix +dotnet_diagnostic.CA1311.severity = warning # Specify a culture or use an invariant version +dotnet_diagnostic.CA1309.severity = warning # Use ordinal string comparison +dotnet_diagnostic.CA1507.severity = warning # Use nameof to express symbol names +dotnet_diagnostic.CA2016.severity = warning # Forward the 'CancellationToken' parameter to methods -# Xml config files -[*.{props,targets,config,nuspec}] -indent_size = 2 +[*.{cs,vb}] +tab_width = 4 +indent_size = 4 +end_of_line = crlf +indent_style = space +insert_final_newline = true +trim_trailing_whitespace = true -[CMakeLists.txt] -indent_size = 2 +dotnet_style_namespace_match_folder = false:silent +dotnet_style_operator_placement_when_wrapping = beginning_of_line +dotnet_style_coalesce_expression = true:suggestion +dotnet_style_collection_initializer = true:suggestion +dotnet_style_explicit_tuple_names = true:suggestion +dotnet_style_null_propagation = true:suggestion +dotnet_style_object_initializer = true:suggestion +dotnet_style_prefer_auto_properties = true:suggestion +dotnet_style_prefer_compound_assignment = true:suggestion +dotnet_style_prefer_conditional_expression_over_assignment = true:suggestion +dotnet_style_prefer_conditional_expression_over_return = true:suggestion +dotnet_style_prefer_inferred_anonymous_type_member_names = true:suggestion +dotnet_style_prefer_inferred_tuple_names = true:suggestion +dotnet_style_prefer_is_null_check_over_reference_equality_method = true:suggestion +dotnet_style_prefer_simplified_boolean_expressions = true:suggestion +dotnet_style_prefer_simplified_interpolation = true:suggestion +dotnet_style_readonly_field = true:error +dotnet_style_predefined_type_for_locals_parameters_members = true:error +dotnet_style_predefined_type_for_member_access = true:error +dotnet_style_require_accessibility_modifiers = for_non_interface_members:error +dotnet_style_allow_multiple_blank_lines_experimental = false:error +dotnet_style_allow_statement_immediately_after_block_experimental = true:silent +dotnet_style_parentheses_in_arithmetic_binary_operators = always_for_clarity:error +dotnet_style_parentheses_in_other_binary_operators = always_for_clarity:error +dotnet_style_parentheses_in_relational_binary_operators = always_for_clarity:error +dotnet_style_parentheses_in_other_operators = never_if_unnecessary:suggestion +dotnet_style_parentheses_in_arithmetic_binary_operators = always_for_clarity:warning +dotnet_style_parentheses_in_other_binary_operators = always_for_clarity:warning +dotnet_style_parentheses_in_other_operators = never_if_unnecessary:suggestion +dotnet_style_parentheses_in_relational_binary_operators = always_for_clarity:warning +dotnet_style_qualification_for_field = false:error +dotnet_style_qualification_for_property = false:error +dotnet_style_qualification_for_method = false:error +dotnet_style_qualification_for_event = false:error +dotnet_style_allow_multiple_blank_lines_experimental = false:warning +dotnet_style_allow_statement_immediately_after_block_experimental = true:silent +dotnet_style_predefined_type_for_locals_parameters_members = true:warning +dotnet_style_predefined_type_for_member_access = true:warning +dotnet_style_qualification_for_event = false:warning +dotnet_style_qualification_for_field = false:warning +dotnet_style_qualification_for_method = false:warning +dotnet_style_qualification_for_property = false:warning +dotnet_style_readonly_field = true:warning +dotnet_style_require_accessibility_modifiers = for_non_interface_members:warning +dotnet_code_quality_unused_parameters = all:error +dotnet_code_quality_unused_parameters = all:warning +dotnet_naming_rule.interface_should_be_begins_with_i.severity = warning +dotnet_naming_rule.interface_should_be_begins_with_i.symbols = interface +dotnet_naming_rule.interface_should_be_begins_with_i.style = begins_with_i +dotnet_naming_rule.non_field_members_should_be_pascal_case.severity = suggestion +dotnet_naming_rule.non_field_members_should_be_pascal_case.symbols = non_field_members +dotnet_naming_rule.non_field_members_should_be_pascal_case.style = pascal_case +dotnet_naming_rule.types_should_be_pascal_case.severity = suggestion +dotnet_naming_rule.types_should_be_pascal_case.symbols = types +dotnet_naming_rule.types_should_be_pascal_case.style = pascal_case +dotnet_naming_symbols.interface.applicable_kinds = interface +dotnet_naming_symbols.interface.applicable_accessibilities = public, internal, private, protected, protected_internal, private_protected +dotnet_naming_symbols.interface.required_modifiers = +dotnet_naming_symbols.non_field_members.applicable_kinds = property, event, method +dotnet_naming_symbols.non_field_members.applicable_accessibilities = public, internal, private, protected, protected_internal, private_protected +dotnet_naming_symbols.non_field_members.required_modifiers = +dotnet_naming_symbols.types.applicable_kinds = class, struct, interface, enum +dotnet_naming_symbols.types.applicable_accessibilities = public, internal, private, protected, protected_internal, private_protected +dotnet_naming_symbols.types.required_modifiers = +dotnet_naming_style.begins_with_i.required_prefix = I +dotnet_naming_style.begins_with_i.required_suffix = +dotnet_naming_style.begins_with_i.word_separator = +dotnet_naming_style.begins_with_i.capitalization = pascal_case +dotnet_naming_style.pascal_case.required_prefix = +dotnet_naming_style.pascal_case.required_suffix = +dotnet_naming_style.pascal_case.word_separator = +dotnet_naming_style.pascal_case.capitalization = pascal_case -[*.cmd] -indent_size = 2 \ No newline at end of file +dotnet_diagnostic.CA1014.severity = none # Mark assemblies with CLSCompliantAttribute +dotnet_diagnostic.CA1303.severity = none # Do not pass literals as localized parameters +dotnet_diagnostic.CA1822.severity = none # Make members as static + +dotnet_diagnostic.CA1000.severity = warning # Do not declare static members on generic types +dotnet_diagnostic.CA1010.severity = warning # Generic interface should also be implemented +dotnet_diagnostic.CA1016.severity = warning # Mark assemblies with assembly version +dotnet_diagnostic.CA1018.severity = warning # Mark attributes with AttributeUsageAttribute +dotnet_diagnostic.CA1036.severity = warning # Override methods on comparable types +dotnet_diagnostic.CA1041.severity = warning # Provide ObsoleteAttribute message +dotnet_diagnostic.CA1050.severity = warning # Declare types in namespaces +dotnet_diagnostic.CA1051.severity = warning # Do not declare visible instance fields +dotnet_diagnostic.CA1061.severity = warning # Do not hide base class methods +dotnet_diagnostic.CA1067.severity = warning # Override Object.Equals(object) when implementing IEquatable +dotnet_diagnostic.CA1068.severity = warning # CancellationToken parameters must come last +dotnet_diagnostic.CA1069.severity = warning # Enums values should not be duplicated +dotnet_diagnostic.CA1310.severity = warning # Specify StringComparison for correctness +dotnet_diagnostic.CA1305.severity = warning # Specify IFormatProvider Globalization +dotnet_diagnostic.CA1304.severity = warning # Specify CultureInfo +dotnet_diagnostic.CA2101.severity = warning # Specify marshaling for P/Invoke string arguments +dotnet_diagnostic.CA1816.severity = warning # Dispose methods should call SuppressFinalize +dotnet_diagnostic.CA2211.severity = warning # Non-constant fields should not be visible +dotnet_diagnostic.CA2208.severity = warning # Instantiate argument exceptions correctly +dotnet_diagnostic.CA2219.severity = warning # Do not raise exceptions in finally clauses +dotnet_diagnostic.CA2249.severity = warning # Consider using 'string.Contains' instead of 'string.IndexOf' +dotnet_diagnostic.CA2248.severity = warning # Provide correct 'enum' argument to 'Enum.HasFlag' +dotnet_diagnostic.CA2245.severity = warning # Do not assign a property to itself +dotnet_diagnostic.CA5397.severity = warning # Do not use deprecated SslProtocols values +dotnet_diagnostic.CA5384.severity = warning # Do Not Use Digital Signature Algorithm (DSA) +dotnet_diagnostic.CA5379.severity = warning # Ensure Key Derivation Function algorithm is sufficiently strong +dotnet_diagnostic.CA5350.severity = warning # Do Not Use Weak Cryptographic Algorithms +dotnet_diagnostic.CA5351.severity = warning # Do Not Use Broken Cryptographic Algorithms +dotnet_diagnostic.CA5364.severity = warning # Do Not Use Deprecated Security Protocols +dotnet_diagnostic.CA2019.severity = warning # Improper 'ThreadStatic' field initialization +dotnet_diagnostic.CA2012.severity = warning # Use ValueTasks correctly +dotnet_diagnostic.CA2011.severity = warning # Avoid infinite recursion +dotnet_diagnostic.CA2009.severity = warning # Do not call ToImmutableCollection on an ImmutableCollection value +dotnet_diagnostic.CA1854.severity = warning # Prefer the 'IDictionary.TryGetValue(TKey, out TValue)' method +dotnet_diagnostic.CA1853.severity = warning # Unnecessary call to 'Dictionary.ContainsKey(key)' +dotnet_diagnostic.CA1852.severity = warning # Seal internal types +dotnet_diagnostic.CA1827.severity = warning # Do not use Count() or LongCount() when Any() can be used +dotnet_diagnostic.CA1828.severity = warning # Do not use CountAsync() or LongCountAsync() when AnyAsync() can be used diff --git a/src/Caliburn.Micro.Core.Tests/Caliburn.Micro.Core.Tests.csproj b/src/Caliburn.Micro.Core.Tests/Caliburn.Micro.Core.Tests.csproj index 337286563..3b9dc1be3 100644 --- a/src/Caliburn.Micro.Core.Tests/Caliburn.Micro.Core.Tests.csproj +++ b/src/Caliburn.Micro.Core.Tests/Caliburn.Micro.Core.Tests.csproj @@ -1,26 +1,30 @@  + + net462 false + $(NoWarn);CA1707;SA0001;xUnit1004 + + all runtime; build; native; contentfiles; analyzers; buildtransitive - - - - - \ No newline at end of file diff --git a/src/Caliburn.Micro.Core.Tests/ScreenExtentionTests.cs b/src/Caliburn.Micro.Core.Tests/ConductorTests/ConductWithTests.cs similarity index 72% rename from src/Caliburn.Micro.Core.Tests/ScreenExtentionTests.cs rename to src/Caliburn.Micro.Core.Tests/ConductorTests/ConductWithTests.cs index 04c0f26a9..bc2b78252 100644 --- a/src/Caliburn.Micro.Core.Tests/ScreenExtentionTests.cs +++ b/src/Caliburn.Micro.Core.Tests/ConductorTests/ConductWithTests.cs @@ -4,31 +4,27 @@ using System.Text; using System.Threading; using System.Threading.Tasks; + using Caliburn.Micro.Core; using Xunit; -namespace Caliburn.Micro.Core.Tests -{ - public class ConductWithTests - { +namespace Caliburn.Micro.Core.Tests { + public class ConductWithTests { [Fact] - public async Task Screen_ConductWithTests() - { + public async Task Screen_ConductWithTests() { var root = new Screen(); - var child1 = new StateScreen - { - DisplayName = "screen1" + var child1 = new StateScreen { + DisplayName = "screen1", }; - // simulate a long deactivation process - var child2 = new StateScreen(TimeSpan.FromSeconds(3)) - { - DisplayName = "screen2" + + // simulate a long deactivation process + var child2 = new StateScreen(TimeSpan.FromSeconds(3)) { + DisplayName = "screen2", }; - var child3 = new StateScreen() - { - DisplayName = "screen3" + var child3 = new StateScreen() { + DisplayName = "screen3", }; child1.ConductWith(root); @@ -49,23 +45,19 @@ public async Task Screen_ConductWithTests() } [Fact] - public async Task Conductor_ConductWithTests() - { + public async Task Conductor_ConductWithTests() { var root = new Conductor.Collection.AllActive(); - var child1 = new StateScreen - { - DisplayName = "screen1" + var child1 = new StateScreen { + DisplayName = "screen1", }; - var child2 = new StateScreen(TimeSpan.FromSeconds(3)) - { + var child2 = new StateScreen(TimeSpan.FromSeconds(3)) { DisplayName = "screen2", IsClosable = false, }; - var child3 = new StateScreen() - { - DisplayName = "screen3" + var child3 = new StateScreen() { + DisplayName = "screen3", }; root.Items.Add(child1); @@ -85,30 +77,25 @@ public async Task Conductor_ConductWithTests() Assert.True(child3.IsClosed, "child 3 should be closed"); } - class StateScreen : Screen - { - public StateScreen() - { + private sealed class StateScreen : Screen { + private readonly TimeSpan? deactivationDelay; + + public StateScreen() { } public StateScreen(TimeSpan? deactivationDelay) - { - this.deactivationDelay = deactivationDelay; - } + => this.deactivationDelay = deactivationDelay; public bool WasActivated { get; private set; } + public bool IsClosed { get; private set; } + public bool IsClosable { get; set; } - public override Task CanCloseAsync(CancellationToken cancellationToken = default) - { - return Task.FromResult(IsClosable); - } + public override Task CanCloseAsync(CancellationToken cancellationToken = default) => Task.FromResult(IsClosable); - protected override async Task OnActivateAsync(CancellationToken cancellationToken) - { - if (deactivationDelay.HasValue) - { + protected override async Task OnActivateAsync(CancellationToken cancellationToken) { + if (deactivationDelay.HasValue) { await Task.Delay(deactivationDelay.Value, cancellationToken).ConfigureAwait(false); } @@ -118,19 +105,15 @@ protected override async Task OnActivateAsync(CancellationToken cancellationToke IsClosable = false; } - protected override async Task OnDeactivateAsync(bool close, CancellationToken cancellationToken = default(CancellationToken)) - { + protected override async Task OnDeactivateAsync(bool close, CancellationToken cancellationToken = default(CancellationToken)) { await base.OnDeactivateAsync(close, cancellationToken).ConfigureAwait(false); - if (deactivationDelay.HasValue) - { + if (deactivationDelay.HasValue) { await Task.Delay(deactivationDelay.Value, cancellationToken).ConfigureAwait(false); } IsClosed = close; } - - private readonly TimeSpan? deactivationDelay; } } } diff --git a/src/Caliburn.Micro.Core.Tests/ConductorWithCollectionOneActiveTests.cs b/src/Caliburn.Micro.Core.Tests/ConductorTests/ConductorWithCollectionOneActiveTests.cs similarity index 77% rename from src/Caliburn.Micro.Core.Tests/ConductorWithCollectionOneActiveTests.cs rename to src/Caliburn.Micro.Core.Tests/ConductorTests/ConductorWithCollectionOneActiveTests.cs index 6e3371cd0..163bfa1be 100644 --- a/src/Caliburn.Micro.Core.Tests/ConductorWithCollectionOneActiveTests.cs +++ b/src/Caliburn.Micro.Core.Tests/ConductorTests/ConductorWithCollectionOneActiveTests.cs @@ -1,32 +1,13 @@ using System; using System.Threading; using System.Threading.Tasks; -using Xunit; - -namespace Caliburn.Micro.Core.Tests -{ - public class ConductorWithCollectionOneActiveTests - { - private class StateScreen : Screen - { - public bool IsClosed { get; private set; } - public bool IsClosable { get; set; } - - public override Task CanCloseAsync(CancellationToken cancellationToken) - { - return Task.FromResult(IsClosable); - } - protected override async Task OnDeactivateAsync(bool close, CancellationToken cancellationToken) - { - await base.OnDeactivateAsync(close, cancellationToken); - IsClosed = close; - } - } +using Xunit; +namespace Caliburn.Micro.Core.Tests { + public class ConductorWithCollectionOneActiveTests { [Fact] - public void AddedItemAppearsInChildren() - { + public void AddedItemAppearsInChildren() { var conductor = new Conductor.Collection.OneActive(); var conducted = new Screen(); conductor.Items.Add(conducted); @@ -34,44 +15,38 @@ public void AddedItemAppearsInChildren() } [Fact] - public async Task CanCloseIsTrueWhenItemsAreClosable() - { + public async Task CanCloseIsTrueWhenItemsAreClosable() { var conductor = new Conductor.Collection.OneActive(); - var conducted = new StateScreen - { - IsClosable = true + var conducted = new StateScreen { + IsClosable = true, }; conductor.Items.Add(conducted); await ((IActivate)conductor).ActivateAsync(CancellationToken.None); - var canClose =await conductor.CanCloseAsync(CancellationToken.None); + bool canClose = await conductor.CanCloseAsync(CancellationToken.None); Assert.True(canClose); Assert.False(conducted.IsClosed); } [Fact(Skip = "Investigating close issue. http://caliburnmicro.codeplex.com/discussions/275824")] - public async Task CanCloseIsTrueWhenItemsAreNotClosableAndCloseStrategyCloses() - { - var conductor = new Conductor.Collection.OneActive - { - CloseStrategy = new DefaultCloseStrategy(true) + public async Task CanCloseIsTrueWhenItemsAreNotClosableAndCloseStrategyCloses() { + var conductor = new Conductor.Collection.OneActive { + CloseStrategy = new DefaultCloseStrategy(true), }; - var conducted = new StateScreen - { - IsClosable = true + var conducted = new StateScreen { + IsClosable = true, }; conductor.Items.Add(conducted); - await((IActivate)conductor).ActivateAsync(CancellationToken.None); - var canClose = await conductor.CanCloseAsync(CancellationToken.None); + await ((IActivate)conductor).ActivateAsync(CancellationToken.None); + bool canClose = await conductor.CanCloseAsync(CancellationToken.None); Assert.True(canClose); Assert.True(conducted.IsClosed); } [Fact] - public async Task ChildrenAreActivatedIfConductorIsActive() - { + public async Task ChildrenAreActivatedIfConductorIsActive() { var conductor = new Conductor.Collection.OneActive(); var conducted = new Screen(); conductor.Items.Add(conducted); @@ -82,8 +57,7 @@ public async Task ChildrenAreActivatedIfConductorIsActive() } [Fact(Skip = "ActiveItem currently set regardless of IsActive value. See http://caliburnmicro.codeplex.com/discussions/276375")] - public async Task ChildrenAreNotActivatedIfConductorIsNotActive() - { + public async Task ChildrenAreNotActivatedIfConductorIsNotActive() { var conductor = new Conductor.Collection.OneActive(); var conducted = new Screen(); conductor.Items.Add(conducted); @@ -93,15 +67,13 @@ public async Task ChildrenAreNotActivatedIfConductorIsNotActive() } [Fact(Skip = "Behavior currently allowed; under investigation. See http://caliburnmicro.codeplex.com/discussions/276373")] - public void ConductorCannotConductSelf() - { + public void ConductorCannotConductSelf() { var conductor = new Conductor.Collection.OneActive(); Assert.Throws(() => conductor.Items.Add(conductor)); } [Fact] - public void ParentItemIsSetOnAddedConductedItem() - { + public void ParentItemIsSetOnAddedConductedItem() { var conductor = new Conductor.Collection.OneActive(); var conducted = new Screen(); conductor.Items.Add(conducted); @@ -109,8 +81,7 @@ public void ParentItemIsSetOnAddedConductedItem() } [Fact] - public void ParentItemIsSetOnReplacedConductedItem() - { + public void ParentItemIsSetOnReplacedConductedItem() { var conductor = new Conductor.Collection.OneActive(); var originalConducted = new Screen(); conductor.Items.Add(originalConducted); @@ -120,8 +91,7 @@ public void ParentItemIsSetOnReplacedConductedItem() } [Fact(Skip = "This is not possible as we don't get the removed items in the event handler.")] - public void ParentItemIsUnsetOnClear() - { + public void ParentItemIsUnsetOnClear() { var conductor = new Conductor.Collection.OneActive(); var conducted = new Screen(); conductor.Items.Add(conducted); @@ -130,8 +100,7 @@ public void ParentItemIsUnsetOnClear() } [Fact] - public void ParentItemIsUnsetOnRemovedConductedItem() - { + public void ParentItemIsUnsetOnRemovedConductedItem() { var conductor = new Conductor.Collection.OneActive(); var conducted = new Screen(); conductor.Items.Add(conducted); @@ -140,8 +109,7 @@ public void ParentItemIsUnsetOnRemovedConductedItem() } [Fact] - public void ParentItemIsUnsetOnReplaceConductedItem() - { + public void ParentItemIsUnsetOnReplaceConductedItem() { var conductor = new Conductor.Collection.OneActive(); var conducted = new Screen(); conductor.Items.Add(conducted); @@ -150,5 +118,19 @@ public void ParentItemIsUnsetOnReplaceConductedItem() Assert.NotEqual(conductor, conducted.Parent); Assert.Equal(conductor, conducted2.Parent); } + + private sealed class StateScreen : Screen { + public bool IsClosed { get; private set; } + + public bool IsClosable { get; set; } + + public override Task CanCloseAsync(CancellationToken cancellationToken) + => Task.FromResult(IsClosable); + + protected override async Task OnDeactivateAsync(bool close, CancellationToken cancellationToken) { + await base.OnDeactivateAsync(close, cancellationToken); + IsClosed = close; + } + } } } diff --git a/src/Caliburn.Micro.Core.Tests/EventAggregatorTests.cs b/src/Caliburn.Micro.Core.Tests/EventAggregatorTests.cs deleted file mode 100644 index 02f764447..000000000 --- a/src/Caliburn.Micro.Core.Tests/EventAggregatorTests.cs +++ /dev/null @@ -1,175 +0,0 @@ -using System; -using System.Threading; -using System.Threading.Tasks; -using Moq; -using Xunit; - -namespace Caliburn.Micro.Core.Tests -{ - public class EventAggregatorSubscribing - { - [Fact] - public void A_null_subscriber_causes_an_ArgumentNullException() - { - Assert.Throws(() => { new EventAggregator().SubscribeOnPublishedThread(null); }); - } - - [Fact] - public void A_null_marshall_causes_an_ArgumentNullException() - { - var handlerStub = new Mock>().Object; - Assert.Throws(() => { new EventAggregator().Subscribe(handlerStub, null); }); - } - - [Fact] - public void A_valid_subscriber_is_assigned_as_a_handler_its_message_type() - { - var handlerStub = new Mock>().Object; - var aggregator = new EventAggregator(); - - Assert.False(aggregator.HandlerExistsFor(typeof(object))); - - aggregator.SubscribeOnPublishedThread(handlerStub); - - Assert.True(aggregator.HandlerExistsFor(typeof(object))); - } - } - - public class EventAggregatorUnsubscribing - { - [Fact] - public void A_null_subscriber_throws_an_ArgumentNullException() - { - Assert.Throws(() => { new EventAggregator().Unsubscribe(null); }); - } - - [Fact] - public void A_valid_subscriber_gets_removed_from_the_handler_list() - { - var eventAggregator = new EventAggregator(); - var handlerMock = new Mock>(); - - eventAggregator.SubscribeOnPublishedThread(handlerMock.Object); - Assert.True(eventAggregator.HandlerExistsFor(typeof(object))); - - eventAggregator.Unsubscribe(handlerMock.Object); - Assert.False(eventAggregator.HandlerExistsFor(typeof(object))); - } - } - - public class EventAggregatorPublishing - { - [Fact] - public async Task A_null_marshal_causes_an_ArgumentNullException() - { - await Assert.ThrowsAsync(async () => { await new EventAggregator().PublishAsync(new object(), null, CancellationToken.None); }); - } - - [Fact] - public async Task A_null_message_causes_an_ArgumentNullException() - { - await Assert.ThrowsAsync(async () => { await new EventAggregator().PublishOnCurrentThreadAsync(null, CancellationToken.None); }); - } - - [Fact] - public async Task A_valid_message_is_invoked_on_the_supplied_publication_marshaller() - { - var eventAggregator = new EventAggregator(); - var handlerMock = new Mock>(); - var marshallerCalled = false; - - eventAggregator.SubscribeOnPublishedThread(handlerMock.Object); - - await eventAggregator.PublishAsync(new object(), f => - { - marshallerCalled = true; - - return f(); - - }, CancellationToken.None); - - Assert.True(marshallerCalled); - } - - [Fact] - public async Task A_valid_message_is_invoked_on_the_supplied_subscription_marshaller() - { - var eventAggregator = new EventAggregator(); - var handlerMock = new Mock>(); - var marshallerCalled = false; - - eventAggregator.Subscribe(handlerMock.Object, f => - { - marshallerCalled = true; - - return f(); - - }); - - await eventAggregator.PublishOnCurrentThreadAsync(new object(), CancellationToken.None); - - Assert.True(marshallerCalled); - } - - [Fact] - public async Task A_valid_message_is_published_to_all_handlers() - { - var eventAggregator = new EventAggregator(); - - var handlerMockA = new Mock>(); - var handlerMockB = new Mock>(); - - eventAggregator.SubscribeOnPublishedThread(handlerMockA.Object); - eventAggregator.SubscribeOnPublishedThread(handlerMockB.Object); - - await eventAggregator.PublishOnCurrentThreadAsync(new object(), CancellationToken.None); - - handlerMockA.Verify(handlerStub => handlerStub.HandleAsync(It.IsAny(), It.IsAny()), - Times.AtLeastOnce()); - handlerMockB.Verify(handlerStub => handlerStub.HandleAsync(It.IsAny(), It.IsAny()), - Times.AtLeastOnce()); - } - - [Fact] - public async Task A_valid_message_is_published_to_all_handlers_respecting_inheritance() - { - var eventAggregator = new EventAggregator(); - - var handlerMockA = new Mock>(); - var handlerMockB = new Mock>(); - - eventAggregator.SubscribeOnPublishedThread(handlerMockA.Object); - eventAggregator.SubscribeOnPublishedThread(handlerMockB.Object); - - await eventAggregator.PublishOnCurrentThreadAsync(new Message(), CancellationToken.None); - - handlerMockA.Verify(handlerStub => handlerStub.HandleAsync(It.IsAny(), It.IsAny()), - Times.AtLeastOnce()); - handlerMockB.Verify(handlerStub => handlerStub.HandleAsync(It.IsAny(), It.IsAny()), - Times.AtLeastOnce()); - } - - public class MessageBase { } - public class Message : MessageBase { } - } - - public class EventAggregatorHandlerExistence - { - [Fact] - public void False_returned_when_no_handlers_exist_for_a_given_message() - { - Assert.False(new EventAggregator().HandlerExistsFor(typeof(object))); - } - - [Fact] - public void True_returned_when_a_handler_exists_for_a_given_message() - { - var handlerStub = new Mock>().Object; - var aggregator = new EventAggregator(); - - aggregator.SubscribeOnPublishedThread(handlerStub); - - Assert.True(aggregator.HandlerExistsFor(typeof(object))); - } - } -} diff --git a/src/Caliburn.Micro.Core.Tests/EventAggregatorTests/EventAggregatorHandlerExistenceTests.cs b/src/Caliburn.Micro.Core.Tests/EventAggregatorTests/EventAggregatorHandlerExistenceTests.cs new file mode 100644 index 000000000..a19678d2a --- /dev/null +++ b/src/Caliburn.Micro.Core.Tests/EventAggregatorTests/EventAggregatorHandlerExistenceTests.cs @@ -0,0 +1,20 @@ +using Moq; +using Xunit; + +namespace Caliburn.Micro.Core.Tests { + public class EventAggregatorHandlerExistenceTests { + [Fact] + public void False_returned_when_no_handlers_exist_for_a_given_message() + => Assert.False(new EventAggregator().HandlerExistsFor(typeof(object))); + + [Fact] + public void True_returned_when_a_handler_exists_for_a_given_message() { + IHandle handlerStub = new Mock>().Object; + var aggregator = new EventAggregator(); + + aggregator.SubscribeOnPublishedThread(handlerStub); + + Assert.True(aggregator.HandlerExistsFor(typeof(object))); + } + } +} diff --git a/src/Caliburn.Micro.Core.Tests/EventAggregatorTests/EventAggregatorPublishingTests.cs b/src/Caliburn.Micro.Core.Tests/EventAggregatorTests/EventAggregatorPublishingTests.cs new file mode 100644 index 000000000..eed7d990c --- /dev/null +++ b/src/Caliburn.Micro.Core.Tests/EventAggregatorTests/EventAggregatorPublishingTests.cs @@ -0,0 +1,101 @@ +using System; +using System.Threading; +using System.Threading.Tasks; + +using Moq; +using Xunit; + +namespace Caliburn.Micro.Core.Tests { + public class EventAggregatorPublishingTests { + [Fact] + public async Task A_null_marshal_causes_an_ArgumentNullException() + => await Assert.ThrowsAsync(async () => await new EventAggregator().PublishAsync(new object(), null, CancellationToken.None)); + + [Fact] + public async Task A_null_message_causes_an_ArgumentNullException() + => await Assert.ThrowsAsync(async () => await new EventAggregator().PublishOnCurrentThreadAsync(null, CancellationToken.None)); + + [Fact] + public async Task A_valid_message_is_invoked_on_the_supplied_publication_marshaller() { + var eventAggregator = new EventAggregator(); + var handlerMock = new Mock>(); + bool marshallerCalled = false; + + eventAggregator.SubscribeOnPublishedThread(handlerMock.Object); + + await eventAggregator.PublishAsync( + new object(), + f => { + marshallerCalled = true; + + return f(); + }, + CancellationToken.None); + + Assert.True(marshallerCalled); + } + + [Fact] + public async Task A_valid_message_is_invoked_on_the_supplied_subscription_marshaller() { + var eventAggregator = new EventAggregator(); + var handlerMock = new Mock>(); + bool marshallerCalled = false; + + eventAggregator.Subscribe(handlerMock.Object, f => { + marshallerCalled = true; + + return f(); + }); + + await eventAggregator.PublishOnCurrentThreadAsync(new object(), CancellationToken.None); + + Assert.True(marshallerCalled); + } + + [Fact] + public async Task A_valid_message_is_published_to_all_handlers() { + var eventAggregator = new EventAggregator(); + + var handlerMockA = new Mock>(); + var handlerMockB = new Mock>(); + + eventAggregator.SubscribeOnPublishedThread(handlerMockA.Object); + eventAggregator.SubscribeOnPublishedThread(handlerMockB.Object); + + await eventAggregator.PublishOnCurrentThreadAsync(new object(), CancellationToken.None); + + handlerMockA.Verify( + handlerStub => handlerStub.HandleAsync(It.IsAny(), It.IsAny()), + Times.AtLeastOnce()); + handlerMockB.Verify( + handlerStub => handlerStub.HandleAsync(It.IsAny(), It.IsAny()), + Times.AtLeastOnce()); + } + + [Fact] + public async Task A_valid_message_is_published_to_all_handlers_respecting_inheritance() { + var eventAggregator = new EventAggregator(); + + var handlerMockA = new Mock>(); + var handlerMockB = new Mock>(); + + eventAggregator.SubscribeOnPublishedThread(handlerMockA.Object); + eventAggregator.SubscribeOnPublishedThread(handlerMockB.Object); + + await eventAggregator.PublishOnCurrentThreadAsync(new Message(), CancellationToken.None); + + handlerMockA.Verify( + handlerStub => handlerStub.HandleAsync(It.IsAny(), It.IsAny()), + Times.AtLeastOnce()); + handlerMockB.Verify( + handlerStub => handlerStub.HandleAsync(It.IsAny(), It.IsAny()), + Times.AtLeastOnce()); + } + + public class MessageBase { + } + + public sealed class Message : MessageBase { + } + } +} diff --git a/src/Caliburn.Micro.Core.Tests/EventAggregatorTests/EventAggregatorSubscribingTests.cs b/src/Caliburn.Micro.Core.Tests/EventAggregatorTests/EventAggregatorSubscribingTests.cs new file mode 100644 index 000000000..3a763999a --- /dev/null +++ b/src/Caliburn.Micro.Core.Tests/EventAggregatorTests/EventAggregatorSubscribingTests.cs @@ -0,0 +1,30 @@ +using System; + +using Moq; +using Xunit; + +namespace Caliburn.Micro.Core.Tests { + public class EventAggregatorSubscribingTests { + [Fact] + public void A_null_subscriber_causes_an_ArgumentNullException() + => Assert.Throws(() => { new EventAggregator().SubscribeOnPublishedThread(null); }); + + [Fact] + public void A_null_marshall_causes_an_ArgumentNullException() { + IHandle handlerStub = new Mock>().Object; + Assert.Throws(() => { new EventAggregator().Subscribe(handlerStub, null); }); + } + + [Fact] + public void A_valid_subscriber_is_assigned_as_a_handler_its_message_type() { + IHandle handlerStub = new Mock>().Object; + var aggregator = new EventAggregator(); + + Assert.False(aggregator.HandlerExistsFor(typeof(object))); + + aggregator.SubscribeOnPublishedThread(handlerStub); + + Assert.True(aggregator.HandlerExistsFor(typeof(object))); + } + } +} diff --git a/src/Caliburn.Micro.Core.Tests/EventAggregatorTests/EventAggregatorUnsubscribingTests.cs b/src/Caliburn.Micro.Core.Tests/EventAggregatorTests/EventAggregatorUnsubscribingTests.cs new file mode 100644 index 000000000..229d2ce8d --- /dev/null +++ b/src/Caliburn.Micro.Core.Tests/EventAggregatorTests/EventAggregatorUnsubscribingTests.cs @@ -0,0 +1,24 @@ +using System; + +using Moq; +using Xunit; + +namespace Caliburn.Micro.Core.Tests { + public class EventAggregatorUnsubscribingTests { + [Fact] + public void A_null_subscriber_throws_an_ArgumentNullException() + => Assert.Throws(() => { new EventAggregator().Unsubscribe(null); }); + + [Fact] + public void A_valid_subscriber_gets_removed_from_the_handler_list() { + var eventAggregator = new EventAggregator(); + var handlerMock = new Mock>(); + + eventAggregator.SubscribeOnPublishedThread(handlerMock.Object); + Assert.True(eventAggregator.HandlerExistsFor(typeof(object))); + + eventAggregator.Unsubscribe(handlerMock.Object); + Assert.False(eventAggregator.HandlerExistsFor(typeof(object))); + } + } +} diff --git a/src/Caliburn.Micro.Core.Tests/IoCTests.cs b/src/Caliburn.Micro.Core.Tests/IocTests/IoC_Get_Tests.cs similarity index 64% rename from src/Caliburn.Micro.Core.Tests/IoCTests.cs rename to src/Caliburn.Micro.Core.Tests/IocTests/IoC_Get_Tests.cs index a3c5b1160..f04a41a8f 100644 --- a/src/Caliburn.Micro.Core.Tests/IoCTests.cs +++ b/src/Caliburn.Micro.Core.Tests/IocTests/IoC_Get_Tests.cs @@ -1,93 +1,74 @@ using System; using System.Collections.Generic; -using Xunit; - -namespace Caliburn.Micro.Core.Tests -{ - public class IoCGet - { - private class IoCReset : IDisposable - { - private readonly Action _buildUp; - private readonly Func> _getAllInstances; - private readonly Func _getInstance; - - private IoCReset() - { - _getInstance = IoC.GetInstance; - _getAllInstances = IoC.GetAllInstances; - _buildUp = IoC.BuildUp; - } - public void Dispose() - { - IoC.GetInstance = _getInstance; - IoC.GetAllInstances = _getAllInstances; - IoC.BuildUp = _buildUp; - } - - public static IDisposable Create() - { - return new IoCReset(); - } - } +using Xunit; +namespace Caliburn.Micro.Core.Tests { + public class IoC_Get_Tests { [Fact] - public void A_not_initialized_BuildUp_throws_an_InvalidOperationException() - { - using (IoCReset.Create()) - { + public void A_not_initialized_BuildUp_throws_an_InvalidOperationException() { + using (IoCReset.Create()) { Assert.Throws(() => IoC.BuildUp(new object())); } } [Fact] - public void A_not_initialized_GetAllInstances_throws_an_InvalidOperationException() - { - using (IoCReset.Create()) - { + public void A_not_initialized_GetAllInstances_throws_an_InvalidOperationException() { + using (IoCReset.Create()) { Assert.Throws(() => IoC.GetAll()); } } - [Fact] - public void A_not_initialized_GetInstance_throws_an_InvalidOperationException() - { - using (IoCReset.Create()) - { + public void A_not_initialized_GetInstance_throws_an_InvalidOperationException() { + using (IoCReset.Create()) { Assert.Throws(() => IoC.Get()); } } [Fact] - public void A_null_GetInstance_throws_a_NullRefrenceException() - { - using (IoCReset.Create()) - { + public void A_null_GetInstance_throws_a_NullRefrenceException() { + using (IoCReset.Create()) { IoC.GetInstance = null; Assert.Throws(() => IoC.Get()); } } [Fact] - public void A_valid_GetAll_does_not_throw() - { - using (IoCReset.Create()) - { - IoC.GetAllInstances = type => new object[] {"foo", "bar"}; + public void A_valid_GetAll_does_not_throw() { + using (IoCReset.Create()) { + IoC.GetAllInstances = type => new object[] { "foo", "bar" }; IoC.GetAll(); } } [Fact] - public void A_valid_GetInstance_does_not_throw() - { - using (IoCReset.Create()) - { + public void A_valid_GetInstance_does_not_throw() { + using (IoCReset.Create()) { IoC.GetInstance = (type, s) => new object(); IoC.Get(); } } + + private sealed class IoCReset : IDisposable { + private readonly Action _buildUp; + private readonly Func> _getAllInstances; + private readonly Func _getInstance; + + private IoCReset() { + _getInstance = IoC.GetInstance; + _getAllInstances = IoC.GetAllInstances; + _buildUp = IoC.BuildUp; + } + + public static IDisposable Create() + => new IoCReset(); + + public void Dispose() { + IoC.GetInstance = _getInstance; + IoC.GetAllInstances = _getAllInstances; + IoC.BuildUp = _buildUp; + } + } } } diff --git a/src/Caliburn.Micro.Core.Tests/IocTests/SimpleContainer_Checking_For_Handler_Tests.cs b/src/Caliburn.Micro.Core.Tests/IocTests/SimpleContainer_Checking_For_Handler_Tests.cs new file mode 100644 index 000000000..ccd8f6473 --- /dev/null +++ b/src/Caliburn.Micro.Core.Tests/IocTests/SimpleContainer_Checking_For_Handler_Tests.cs @@ -0,0 +1,20 @@ +using Xunit; + +namespace Caliburn.Micro.Core.Tests { + public class SimpleContainer_Checking_For_Handler_Tests { + [Fact] + public void HasHandler_returns_false_when_handler_does_not_exist() { + Assert.False(new SimpleContainer().HasHandler(typeof(object), null)); + Assert.False(new SimpleContainer().HasHandler(null, "Object")); + } + + [Fact] + public void HasHandler_returns_true_when_handler_exists() { + var container = new SimpleContainer(); + container.RegisterPerRequest(typeof(object), "Object", typeof(object)); + + Assert.True(container.HasHandler(typeof(object), null)); + Assert.True(container.HasHandler(null, "Object")); + } + } +} diff --git a/src/Caliburn.Micro.Core.Tests/IocTests/SimpleContainer_Creating_AChild_Container_Tests.cs b/src/Caliburn.Micro.Core.Tests/IocTests/SimpleContainer_Creating_AChild_Container_Tests.cs new file mode 100644 index 000000000..508826b42 --- /dev/null +++ b/src/Caliburn.Micro.Core.Tests/IocTests/SimpleContainer_Creating_AChild_Container_Tests.cs @@ -0,0 +1,34 @@ +using Xunit; + +namespace Caliburn.Micro.Core.Tests { + public class SimpleContainer_Creating_AChild_Container_Tests { + [Fact] + public void Singleton_instances_are_the_same_across_parent_and_child() { + var container = new SimpleContainer(); + container.Singleton(); + SimpleContainer childContainer = container.CreateChildContainer(); + + object parentInstance = container.GetInstance(typeof(object), null); + object childInstance = childContainer.GetInstance(typeof(object), null); + + Assert.Same(parentInstance, childInstance); + } + + [Fact] + public void The_child_container_returned_contains_parent_entries() { + var container = new SimpleContainer(); + container.PerRequest(); + SimpleContainer childContainer = container.CreateChildContainer(); + + Assert.NotNull(childContainer.GetInstance(typeof(object), null)); + } + + [Fact] + public void The_child_container_returned_is_not_the_same_instance_as_its_parent() { + var container = new SimpleContainer(); + SimpleContainer childContainer = container.CreateChildContainer(); + + Assert.NotSame(container, childContainer); + } + } +} diff --git a/src/Caliburn.Micro.Core.Tests/IocTests/SimpleContainer_Find_Constructor_Tests.cs b/src/Caliburn.Micro.Core.Tests/IocTests/SimpleContainer_Find_Constructor_Tests.cs new file mode 100644 index 000000000..f7d6491c9 --- /dev/null +++ b/src/Caliburn.Micro.Core.Tests/IocTests/SimpleContainer_Find_Constructor_Tests.cs @@ -0,0 +1,72 @@ +using Xunit; + +namespace Caliburn.Micro.Core.Tests { + public class SimpleContainer_Find_Constructor_Tests { + [Fact] + public void Container_Finds_Single_Constructor() { + var container = new SimpleContainer(); + container.Singleton(); + container.GetInstance(typeof(SingleEmptyConstructorType), null); + } + + [Fact] + public void Container_No_EmptyConstructor() { + var container = new SimpleContainer(); + container.Singleton(); + container.GetInstance(typeof(SingleNonEmptyConstructorType), null); + } + + [Fact] + public void Container_SingleIntConstructor() { + var container = new SimpleContainer(); + container.Singleton(); + container.RegisterInstance(typeof(int), "x", 4); + var inst = (SingleIntConstructor)container.GetInstance(typeof(SingleIntConstructor), null); + Assert.Equal(4, inst.Value); + } + + [Fact] + public void Container_ChooseConstructorWithRegisteredParameter() { + var container = new SimpleContainer(); + container.Singleton(); + container.RegisterInstance(typeof(int), null, 23); + var inst = (TwoConstructors)container.GetInstance(typeof(TwoConstructors), null); + Assert.Equal(23, inst.Value); + } + + [Fact] + public void Container_ChooseEmptyConstructorWithoutRegisteredParameter() { + var container = new SimpleContainer(); + container.Singleton(); + var inst = (TwoConstructors)container.GetInstance(typeof(TwoConstructors), null); + Assert.Equal(42, inst.Value); + } + + private sealed class SingleNonEmptyConstructorType { + public SingleNonEmptyConstructorType(SingleEmptyConstructorType type) + => _ = type; + } + + private sealed class SingleEmptyConstructorType { + public SingleEmptyConstructorType() { + } + } + + private sealed class SingleIntConstructor { + public SingleIntConstructor(int x) + => Value = x; + + public int Value { get; private set; } + } + + private sealed class TwoConstructors { + public TwoConstructors() + => Value = 42; + + public TwoConstructors(int value) + => Value = value; + + public int Value { get; set; } + } + } +} diff --git a/src/Caliburn.Micro.Core.Tests/IocTests/SimpleContainer_Getting_ASingle_Instance_Tests.cs b/src/Caliburn.Micro.Core.Tests/IocTests/SimpleContainer_Getting_ASingle_Instance_Tests.cs new file mode 100644 index 000000000..069707dcd --- /dev/null +++ b/src/Caliburn.Micro.Core.Tests/IocTests/SimpleContainer_Getting_ASingle_Instance_Tests.cs @@ -0,0 +1,24 @@ +using Xunit; + +namespace Caliburn.Micro.Core.Tests { + public class SimpleContainer_Getting_ASingle_Instance_Tests { + [Fact] + public void An_instance_is_returned_for_the_type_specified_if_found() { + var container = new SimpleContainer(); + container.PerRequest(); + + Assert.NotNull(container.GetInstance(typeof(object), null)); + } + + [Fact] + public void Instances_can_be_found_by_name_only() { + var container = new SimpleContainer(); + container.RegisterPerRequest(typeof(object), "AnObject", typeof(object)); + + Assert.NotNull(container.GetInstance(null, "AnObject")); + } + + [Fact] + public void Null_is_returned_when_no_instance_is_found() => Assert.Null(new SimpleContainer().GetInstance(typeof(object), null)); + } +} diff --git a/src/Caliburn.Micro.Core.Tests/IocTests/SimpleContainer_Recursive_Tests.cs b/src/Caliburn.Micro.Core.Tests/IocTests/SimpleContainer_Recursive_Tests.cs new file mode 100644 index 000000000..0277e1c8c --- /dev/null +++ b/src/Caliburn.Micro.Core.Tests/IocTests/SimpleContainer_Recursive_Tests.cs @@ -0,0 +1,140 @@ +using System.Collections.Generic; +using System.Linq; + +using Xunit; + +namespace Caliburn.Micro.Core.Tests { + public class SimpleContainer_Recursive_Tests { + private interface IComponent { + } + + private interface IDependency1 { + } + + private interface IDependency2 { + } + + private interface IEnumerableDependency { + } + + [Fact] + public void Instances_Are_Recursively_Property_Injected_When_Enabled() { + var container = new SimpleContainer { + EnablePropertyInjection = true, + }; + + RegisterAllComponents(container); + + var instance = (Component)container.GetInstance(); + + Assert.NotNull(((Dependency1)instance.Dependency1).Dependency2); + } + + [Fact] + public void BuildUp_Injects_All_Registered_Dependencies_Non_Recursively() { + var container = new SimpleContainer(); + RegisterAllComponents(container); + + var instance = (Component)container.GetInstance(); + container.BuildUp(instance); + + Assert.Null(((Dependency1)instance.Dependency1).Dependency2); + } + + [Fact] + public void BuildUp_Injects_Dependencies_Recursively() { + var container = new SimpleContainer { + EnablePropertyInjection = true, + }; + + RegisterAllComponents(container); + + var instance = (Component)container.GetInstance(); + container.BuildUp(instance); + + Assert.NotNull(((Dependency1)instance.Dependency1).Dependency2); + } + + [Fact] + public void BuildUp_Injects_Enumerable_Dependencies() { + var container = new SimpleContainer { + EnablePropertyInjection = true, + }; + + RegisterAllComponents(container); + + var instance = (Component)container.GetInstance(); + container.BuildUp(instance); + + Assert.Equal(2, ((Dependency1)instance.Dependency1).EnumerableDependencies.Count); + } + + [Fact] + public void BuildUp_Injects_Properties_Of_Enumerable_Dependencies() { + var container = new SimpleContainer { + EnablePropertyInjection = true, + }; + + RegisterAllComponents(container); + + var instance = (Component)container.GetInstance(); + container.BuildUp(instance); + + Assert.NotNull(((EnumerableDependency1)((Dependency1)instance.Dependency1).EnumerableDependencies.First()).Dependency2); + } + + [Fact] + public void BuildUp_Throws_When_Multiple_Types_Found_For_Component() { + var container = new SimpleContainer(); + RegisterAllComponents(container); + container.RegisterPerRequest(typeof(IDependency1), null, typeof(SecondDependency1)); + + var instance = (Component)container.GetInstance(); + + try { + container.BuildUp(instance); + } catch { + return; + } + + Assert.NotNull(null); + } + + private static void RegisterAllComponents(SimpleContainer container) { + container.RegisterPerRequest(typeof(IComponent), null, typeof(Component)); + container.RegisterPerRequest(typeof(IDependency1), null, typeof(Dependency1)); + container.RegisterPerRequest(typeof(IDependency2), null, typeof(Dependency2)); + container.RegisterPerRequest(typeof(NonInterfaceDependency), null, typeof(NonInterfaceDependency)); + container.RegisterPerRequest(typeof(IEnumerableDependency), null, typeof(EnumerableDependency1)); + container.RegisterPerRequest(typeof(IEnumerableDependency), null, typeof(EnumerableDependency2)); + } + + private sealed class Component : IComponent { + public IDependency1 Dependency1 { get; set; } + + public NonInterfaceDependency NonInterfaceDependency { get; set; } + } + + private class Dependency1 : IDependency1 { + public IDependency2 Dependency2 { get; set; } + + public IList EnumerableDependencies { get; set; } + } + + private sealed class Dependency2 : IDependency2 { + } + + private sealed class EnumerableDependency1 : IEnumerableDependency { + public IDependency2 Dependency2 { get; set; } + } + + private sealed class EnumerableDependency2 : IEnumerableDependency { + } + + private sealed class NonInterfaceDependency { + } + + private sealed class SecondDependency1 : Dependency1 { + } + } +} diff --git a/src/Caliburn.Micro.Core.Tests/IocTests/SimpleContainer_Registering_Instances_Tests.cs b/src/Caliburn.Micro.Core.Tests/IocTests/SimpleContainer_Registering_Instances_Tests.cs new file mode 100644 index 000000000..444fc0ed2 --- /dev/null +++ b/src/Caliburn.Micro.Core.Tests/IocTests/SimpleContainer_Registering_Instances_Tests.cs @@ -0,0 +1,41 @@ +using System.Collections.Generic; +using System.Linq; + +using Xunit; + +namespace Caliburn.Micro.Core.Tests { + public class SimpleContainer_Registering_Instances_Tests { + [Fact] + public void Instances_registed_Singleton_return_the_same_instance_for_each_call() { + var container = new SimpleContainer(); + container.Singleton(); + + object instanceA = container.GetInstance(typeof(object), null); + object instanceB = container.GetInstance(typeof(object), null); + + Assert.Same(instanceA, instanceB); + } + + [Fact] + public void Instances_registered_PerRequest_returns_a_different_instance_for_each_call() { + var container = new SimpleContainer(); + container.PerRequest(); + + object instanceA = container.GetInstance(typeof(object), null); + object instanceB = container.GetInstance(typeof(object), null); + + Assert.NotSame(instanceA, instanceB); + } + + [Fact] + public void Instances_registered_with_different_keys_get_all_instances_return_all() { + var container = new SimpleContainer(); + container.RegisterInstance(typeof(object), "test", new object()); + container.RegisterInstance(typeof(object), "test", new object()); + + IEnumerable results = container.GetAllInstances("test"); + + Assert.Equal(2, results.Count()); + } + } +} diff --git a/src/Caliburn.Micro.Core.Tests/SimpleContainerTests.cs b/src/Caliburn.Micro.Core.Tests/SimpleContainerTests.cs deleted file mode 100644 index e6c1381a6..000000000 --- a/src/Caliburn.Micro.Core.Tests/SimpleContainerTests.cs +++ /dev/null @@ -1,355 +0,0 @@ -using System.Collections.Generic; -using System.Linq; -using Xunit; - -namespace Caliburn.Micro.Core.Tests -{ - public class SimpleContainerCreatingAChildContainer - { - [Fact] - public void Singleton_instances_are_the_same_across_parent_and_child() - { - var container = new SimpleContainer(); - container.Singleton(); - var childContainer = container.CreateChildContainer(); - - var parentInstance = container.GetInstance(typeof(object), null); - var childInstance = childContainer.GetInstance(typeof(object), null); - - Assert.Same(parentInstance, childInstance); - } - - [Fact] - public void The_child_container_returned_contains_parent_entries() - { - var container = new SimpleContainer(); - container.PerRequest(); - var childContainer = container.CreateChildContainer(); - - Assert.NotNull(childContainer.GetInstance(typeof(object), null)); - } - - [Fact] - public void The_child_container_returned_is_not_the_same_instance_as_its_parent() - { - var container = new SimpleContainer(); - var childContainer = container.CreateChildContainer(); - - Assert.NotSame(container, childContainer); - } - } - - public class SimpleContainerCheckingForHandler - { - [Fact] - public void HasHandler_returns_false_when_handler_does_not_exist() - { - Assert.False(new SimpleContainer().HasHandler(typeof(object), null)); - Assert.False(new SimpleContainer().HasHandler(null, "Object")); - } - - [Fact] - public void HasHandler_returns_true_when_handler_exists() - { - var container = new SimpleContainer(); - container.RegisterPerRequest(typeof(object), "Object", typeof(object)); - - Assert.True(container.HasHandler(typeof(object), null)); - Assert.True(container.HasHandler(null, "Object")); - } - } - - public class SimpleContainerGettingASingleInstance - { - [Fact] - public void An_instance_is_returned_for_the_type_specified_if_found() - { - var container = new SimpleContainer(); - container.PerRequest(); - - Assert.NotNull(container.GetInstance(typeof(object), null)); - } - - [Fact] - public void Instances_can_be_found_by_name_only() - { - var container = new SimpleContainer(); - container.RegisterPerRequest(typeof(object), "AnObject", typeof(object)); - - Assert.NotNull(container.GetInstance(null, "AnObject")); - } - - [Fact] - public void Null_is_returned_when_no_instance_is_found() - { - Assert.Null(new SimpleContainer().GetInstance(typeof(object), null)); - } - } - - public class SimpleContainerRegisteringInstances - { - [Fact] - public void Instances_registed_Singleton_return_the_same_instance_for_each_call() - { - var container = new SimpleContainer(); - container.Singleton(); - - var instanceA = container.GetInstance(typeof(object), null); - var instanceB = container.GetInstance(typeof(object), null); - - Assert.Same(instanceA, instanceB); - } - - [Fact] - public void Instances_registered_PerRequest_returns_a_different_instance_for_each_call() - { - var container = new SimpleContainer(); - container.PerRequest(); - - var instanceA = container.GetInstance(typeof(object), null); - var instanceB = container.GetInstance(typeof(object), null); - - Assert.NotSame(instanceA, instanceB); - } - - [Fact] - public void Instances_registered_with_different_keys_get_all_instances_return_all() - { - var container = new SimpleContainer(); - container.RegisterInstance(typeof(object), "test", new object()); - container.RegisterInstance(typeof(object), "test", new object()); - - var results = container.GetAllInstances("test"); - - Assert.Equal(2, results.Count()); - } - } - - public class SimpleContainer_Find_Constructor - { - - public class SingleEmptyConstructorType - { - public SingleEmptyConstructorType() - { - - } - } - - [Fact] - public void Container_Finds_Single_Constructor() - { - var container = new SimpleContainer(); - container.Singleton(); - container.GetInstance(typeof(SingleEmptyConstructorType), null); - } - - public class SingleNonEmptyConstructorType - { - public SingleNonEmptyConstructorType(SimpleContainer_Find_Constructor.SingleEmptyConstructorType type) - { - } - } - - [Fact] - public void Container_No_EmptyConstructor() - { - var container = new SimpleContainer(); - container.Singleton(); - container.GetInstance(typeof(SingleNonEmptyConstructorType), null); - } - - public class SingleIntConstructor - { - public int Value { get; private set; } - - public SingleIntConstructor(int x) - { - this.Value = x; - } - } - - [Fact] - public void Container_SingleIntConstructor() - { - var container = new SimpleContainer(); - container.Singleton(); - container.RegisterInstance(typeof(int), "x", 4); - var inst = (SingleIntConstructor)container.GetInstance(typeof(SingleIntConstructor), null); - Assert.Equal(4, inst.Value); - } - - public class TwoConstructors - { - public int Value { get; set; } - - public TwoConstructors() - { - this.Value = 42; - } - - public TwoConstructors(int value) - { - Value = value; - } - } - - [Fact] - public void Container_ChooseConstructorWithRegisteredParameter() - { - var container = new SimpleContainer(); - container.Singleton(); - container.RegisterInstance(typeof(int), null, 23); - var inst = (TwoConstructors)container.GetInstance(typeof(TwoConstructors), null); - Assert.Equal(23, inst.Value); - } - - [Fact] - public void Container_ChooseEmptyConstructorWithoutRegisteredParameter() - { - var container = new SimpleContainer(); - container.Singleton(); - var inst = (TwoConstructors)container.GetInstance(typeof(TwoConstructors), null); - Assert.Equal(42, inst.Value); - } - } - - public class SimpleContainer_Recursive - { - private interface IComponent { } - private interface IDependency1 { } - private interface IDependency2 { } - private interface IEnumerableDependency { } - - private class Component : IComponent - { - public IDependency1 Dependency1 { get; set; } - public NonInterfaceDependency NonInterfaceDependency { get; set; } - } - - private class Dependency1 : IDependency1 - { - public IDependency2 Dependency2 { get; set; } - public IList EnumerableDependencies { get; set; } - } - - private class Dependency2 : IDependency2 { } - - private class EnumerableDependency1 : IEnumerableDependency - { - public IDependency2 Dependency2 { get; set; } - } - - private class EnumerableDependency2 : IEnumerableDependency { } - - private class NonInterfaceDependency { } - - private class SecondDependency1 : Dependency1 { } - - private static void RegisterAllComponents(SimpleContainer container) - { - container.RegisterPerRequest(typeof(IComponent), null, typeof(Component)); - container.RegisterPerRequest(typeof(IDependency1), null, typeof(Dependency1)); - container.RegisterPerRequest(typeof(IDependency2), null, typeof(Dependency2)); - container.RegisterPerRequest(typeof(NonInterfaceDependency), null, typeof(NonInterfaceDependency)); - container.RegisterPerRequest(typeof(IEnumerableDependency), null, typeof(EnumerableDependency1)); - container.RegisterPerRequest(typeof(IEnumerableDependency), null, typeof(EnumerableDependency2)); - } - - [Fact] - public void Instances_Are_Recursively_Property_Injected_When_Enabled() - { - var container = new SimpleContainer - { - EnablePropertyInjection = true - }; - - RegisterAllComponents(container); - - var instance = (Component)container.GetInstance(); - - Assert.NotNull(((Dependency1)instance.Dependency1).Dependency2); - } - - [Fact] - public void BuildUp_Injects_All_Registered_Dependencies_Non_Recursively() - { - var container = new SimpleContainer(); - RegisterAllComponents(container); - - var instance = (Component)container.GetInstance(); - container.BuildUp(instance); - - Assert.Null(((Dependency1)instance.Dependency1).Dependency2); - } - - [Fact] - public void BuildUp_Injects_Dependencies_Recursively() - { - var container = new SimpleContainer - { - EnablePropertyInjection = true - }; - - RegisterAllComponents(container); - - var instance = (Component)container.GetInstance(); - container.BuildUp(instance); - - Assert.NotNull(((Dependency1)instance.Dependency1).Dependency2); - } - - [Fact] - public void BuildUp_Injects_Enumerable_Dependencies() - { - var container = new SimpleContainer - { - EnablePropertyInjection = true - }; - - RegisterAllComponents(container); - - var instance = (Component)container.GetInstance(); - container.BuildUp(instance); - - Assert.Equal(2, (((Dependency1)instance.Dependency1).EnumerableDependencies.Count)); - } - - [Fact] - public void BuildUp_Injects_Properties_Of_Enumerable_Dependencies() - { - var container = new SimpleContainer - { - EnablePropertyInjection = true - }; - - RegisterAllComponents(container); - - var instance = (Component)container.GetInstance(); - container.BuildUp(instance); - - Assert.NotNull(((EnumerableDependency1)(((Dependency1)instance.Dependency1).EnumerableDependencies.First())).Dependency2); - } - - [Fact] - public void BuildUp_Throws_When_Multiple_Types_Found_For_Component() - { - var container = new SimpleContainer(); - RegisterAllComponents(container); - container.RegisterPerRequest(typeof(IDependency1), null, typeof(SecondDependency1)); - - var instance = (Component)container.GetInstance(); - - try - { - container.BuildUp(instance); - } - catch - { - return; - } - - Assert.NotNull(null); - } - } -} diff --git a/src/Caliburn.Micro.Core/ActivateExtensions.cs b/src/Caliburn.Micro.Core/ActivateExtensions.cs deleted file mode 100644 index 5f5641f11..000000000 --- a/src/Caliburn.Micro.Core/ActivateExtensions.cs +++ /dev/null @@ -1,17 +0,0 @@ -using System.Threading.Tasks; - -namespace Caliburn.Micro -{ - /// - /// Extension methods for the instance. - /// - public static class ActivateExtensions - { - /// - /// Activates this instance. - /// - /// The instance to activate - /// A task that represents the asynchronous operation. - public static Task ActivateAsync(this IActivate activate) => activate.ActivateAsync(default); - } -} diff --git a/src/Caliburn.Micro.Core/ActivationEventArgs.cs b/src/Caliburn.Micro.Core/ActivationEventArgs.cs deleted file mode 100644 index d05fef6d4..000000000 --- a/src/Caliburn.Micro.Core/ActivationEventArgs.cs +++ /dev/null @@ -1,15 +0,0 @@ -using System; - -namespace Caliburn.Micro -{ - /// - /// EventArgs sent during activation. - /// - public class ActivationEventArgs : EventArgs - { - /// - /// Indicates whether the sender was initialized in addition to being activated. - /// - public bool WasInitialized { get; set; } - } -} diff --git a/src/Caliburn.Micro.Core/ActivationProcessedEventArgs.cs b/src/Caliburn.Micro.Core/ActivationProcessedEventArgs.cs deleted file mode 100644 index 8e9fac7ae..000000000 --- a/src/Caliburn.Micro.Core/ActivationProcessedEventArgs.cs +++ /dev/null @@ -1,21 +0,0 @@ -using System; - -namespace Caliburn.Micro -{ - /// - /// Contains details about the success or failure of an item's activation through an . - /// - public class ActivationProcessedEventArgs : EventArgs - { - /// - /// The item whose activation was processed. - /// - public object Item { get; set; } - - /// - /// Gets or sets a value indicating whether the activation was a success. - /// - /// true if success; otherwise, false. - public bool Success { get; set; } - } -} diff --git a/src/Caliburn.Micro.Core/AsyncEventHandler.cs b/src/Caliburn.Micro.Core/AsyncEventHandler.cs deleted file mode 100644 index f92776d5e..000000000 --- a/src/Caliburn.Micro.Core/AsyncEventHandler.cs +++ /dev/null @@ -1,10 +0,0 @@ -using System; -using System.Threading.Tasks; - -namespace Caliburn.Micro -{ - public delegate Task AsyncEventHandler( - object sender, - TEventArgs e) - where TEventArgs : EventArgs; -} diff --git a/src/Caliburn.Micro.Core/AsyncEventHandlerExtensions.cs b/src/Caliburn.Micro.Core/AsyncEventHandlerExtensions.cs deleted file mode 100644 index c4b2ecaa9..000000000 --- a/src/Caliburn.Micro.Core/AsyncEventHandlerExtensions.cs +++ /dev/null @@ -1,24 +0,0 @@ -using System; -using System.Collections.Generic; -using System.Linq; -using System.Threading.Tasks; - -namespace Caliburn.Micro -{ - public static class AsyncEventHandlerExtensions - { - public static IEnumerable> GetHandlers( - this AsyncEventHandler handler) - where TEventArgs : EventArgs - => handler.GetInvocationList().Cast>(); - - public static Task InvokeAllAsync( - this AsyncEventHandler handler, - object sender, - TEventArgs e) - where TEventArgs : EventArgs - => Task.WhenAll( - handler.GetHandlers() - .Select(handleAsync => handleAsync(sender, e))); - } -} diff --git a/src/Caliburn.Micro.Core/BindableCollection.cs b/src/Caliburn.Micro.Core/BindableCollection.cs deleted file mode 100644 index 0a43719fb..000000000 --- a/src/Caliburn.Micro.Core/BindableCollection.cs +++ /dev/null @@ -1,285 +0,0 @@ -using System; -using System.Collections.Generic; -using System.Collections.ObjectModel; -using System.Collections.Specialized; -using System.ComponentModel; - -namespace Caliburn.Micro -{ - /// - /// A base collection class that supports automatic UI thread marshalling. - /// - /// The type of elements contained in the collection. - public class BindableCollection : ObservableCollection, IObservableCollection - { - /// - /// Initializes a new instance of the class. - /// - public BindableCollection() - { - IsNotifying = true; - } - - /// - /// Initializes a new instance of the class. - /// - /// The collection from which the elements are copied. - public BindableCollection(IEnumerable collection) - : base(collection) - { - IsNotifying = true; - } - - /// - /// Enables/Disables property change notification. - /// - public bool IsNotifying { get; set; } - - /// - /// Notifies subscribers of the property change. - /// - /// Name of the property. - public virtual void NotifyOfPropertyChange(string propertyName) - { - if (IsNotifying) - { - if (PlatformProvider.Current.PropertyChangeNotificationsOnUIThread) - { - OnUIThread(() => OnPropertyChanged(new PropertyChangedEventArgs(propertyName))); - } - else - { - OnPropertyChanged(new PropertyChangedEventArgs(propertyName)); - } - } - } - - /// - /// Raises a change notification indicating that all bindings should be refreshed. - /// - public void Refresh() - { - if (PlatformProvider.Current.PropertyChangeNotificationsOnUIThread) - { - OnUIThread(() => - { - OnPropertyChanged(new PropertyChangedEventArgs("Count")); - OnPropertyChanged(new PropertyChangedEventArgs("Item[]")); - OnCollectionChanged(new NotifyCollectionChangedEventArgs(NotifyCollectionChangedAction.Reset)); - }); - } - else - { - OnPropertyChanged(new PropertyChangedEventArgs("Count")); - OnPropertyChanged(new PropertyChangedEventArgs("Item[]")); - OnCollectionChanged(new NotifyCollectionChangedEventArgs(NotifyCollectionChangedAction.Reset)); - } - } - - /// - /// Inserts the item to the specified position. - /// - /// The index to insert at. - /// The item to be inserted. - protected override sealed void InsertItem(int index, T item) - { - if (PlatformProvider.Current.PropertyChangeNotificationsOnUIThread) - { - OnUIThread(() => InsertItemBase(index, item)); - } - else - { - InsertItemBase(index, item); - } - } - - /// - /// Exposes the base implementation of the function. - /// - /// The index. - /// The item. - /// - /// Used to avoid compiler warning regarding unverifiable code. - /// - protected virtual void InsertItemBase(int index, T item) - { - base.InsertItem(index, item); - } - - /// - /// Sets the item at the specified position. - /// - /// The index to set the item at. - /// The item to set. - protected override sealed void SetItem(int index, T item) - { - if (PlatformProvider.Current.PropertyChangeNotificationsOnUIThread) - { - OnUIThread(() => SetItemBase(index, item)); - } - else - { - SetItemBase(index, item); - } - } - - /// - /// Exposes the base implementation of the function. - /// - /// The index. - /// The item. - /// - /// Used to avoid compiler warning regarding unverifiable code. - /// - protected virtual void SetItemBase(int index, T item) - { - base.SetItem(index, item); - } - - /// - /// Removes the item at the specified position. - /// - /// The position used to identify the item to remove. - protected override sealed void RemoveItem(int index) - { - if (PlatformProvider.Current.PropertyChangeNotificationsOnUIThread) - { - OnUIThread(() => RemoveItemBase(index)); - } - else - { - RemoveItemBase(index); - } - } - - /// - /// Exposes the base implementation of the function. - /// - /// The index. - /// - /// Used to avoid compiler warning regarding unverifiable code. - /// - protected virtual void RemoveItemBase(int index) - { - base.RemoveItem(index); - } - - /// - /// Clears the items contained by the collection. - /// - protected override sealed void ClearItems() - { - OnUIThread(ClearItemsBase); - } - - /// - /// Exposes the base implementation of the function. - /// - /// - /// Used to avoid compiler warning regarding unverifiable code. - /// - protected virtual void ClearItemsBase() - { - base.ClearItems(); - } - - /// - /// Raises the event with the provided arguments. - /// - /// Arguments of the event being raised. - protected override void OnCollectionChanged(NotifyCollectionChangedEventArgs e) - { - if (IsNotifying) - { - base.OnCollectionChanged(e); - } - } - - /// - /// Raises the PropertyChanged event with the provided arguments. - /// - /// The event data to report in the event. - protected override void OnPropertyChanged(PropertyChangedEventArgs e) - { - if (IsNotifying) - { - base.OnPropertyChanged(e); - } - } - - /// - /// Adds the range. - /// - /// The items. - public virtual void AddRange(IEnumerable items) - { - void AddRange() - { - var previousNotificationSetting = IsNotifying; - IsNotifying = false; - var index = Count; - foreach (var item in items) - { - InsertItemBase(index, item); - index++; - } - IsNotifying = previousNotificationSetting; - - OnPropertyChanged(new PropertyChangedEventArgs("Count")); - OnPropertyChanged(new PropertyChangedEventArgs("Item[]")); - OnCollectionChanged(new NotifyCollectionChangedEventArgs(NotifyCollectionChangedAction.Reset)); - } - - if (PlatformProvider.Current.PropertyChangeNotificationsOnUIThread) - { - OnUIThread(AddRange); - } - else - { - AddRange(); - } - } - - /// - /// Removes the range. - /// - /// The items. - public virtual void RemoveRange(IEnumerable items) - { - void RemoveRange() - { - var previousNotificationSetting = IsNotifying; - IsNotifying = false; - foreach (var item in items) - { - var index = IndexOf(item); - if (index >= 0) - { - RemoveItemBase(index); - } - } - IsNotifying = previousNotificationSetting; - - OnPropertyChanged(new PropertyChangedEventArgs("Count")); - OnPropertyChanged(new PropertyChangedEventArgs("Item[]")); - OnCollectionChanged(new NotifyCollectionChangedEventArgs(NotifyCollectionChangedAction.Reset)); - } - - if (PlatformProvider.Current.PropertyChangeNotificationsOnUIThread) - { - OnUIThread(RemoveRange); - } - else - { - RemoveRange(); - } - } - - /// - /// Executes the given action on the UI thread - /// - /// An extension point for subclasses to customise how property change notifications are handled. - /// - protected virtual void OnUIThread(System.Action action) => action.OnUIThread(); - } -} diff --git a/src/Caliburn.Micro.Core/Caliburn.Micro.Core.csproj b/src/Caliburn.Micro.Core/Caliburn.Micro.Core.csproj index e42533612..03d8f849d 100644 --- a/src/Caliburn.Micro.Core/Caliburn.Micro.Core.csproj +++ b/src/Caliburn.Micro.Core/Caliburn.Micro.Core.csproj @@ -10,25 +10,21 @@ latest - - - - - - - - Caliburn.Micro.Core - - - - + + + + + + + + diff --git a/src/Caliburn.Micro.Core/CloseResult.cs b/src/Caliburn.Micro.Core/CloseResult.cs deleted file mode 100644 index 7897860d5..000000000 --- a/src/Caliburn.Micro.Core/CloseResult.cs +++ /dev/null @@ -1,32 +0,0 @@ -using System.Collections.Generic; - -namespace Caliburn.Micro -{ - /// - /// The result of a test whether an instance can be closed. - /// - /// The type of the children of the instance. - public class CloseResult : ICloseResult - { - /// - /// Creates an instance of the - /// - /// Whether of not a close operation should occur. - /// The children of the instance that can be closed. - public CloseResult(bool closeCanOccur, IEnumerable children) - { - CloseCanOccur = closeCanOccur; - Children = children; - } - - /// - /// Whether of not a close operation should occur. - /// - public bool CloseCanOccur { get; } - - /// - /// The children of the instance that can be closed. - /// - public IEnumerable Children { get; } - } -} diff --git a/src/Caliburn.Micro.Core/Conductor.cs b/src/Caliburn.Micro.Core/Conductor.cs deleted file mode 100644 index c95033128..000000000 --- a/src/Caliburn.Micro.Core/Conductor.cs +++ /dev/null @@ -1,100 +0,0 @@ -using System.Collections.Generic; -using System.Threading; -using System.Threading.Tasks; - -namespace Caliburn.Micro -{ - /// - /// An implementation of that holds on to and activates only one item at a time. - /// - public partial class Conductor : ConductorBaseWithActiveItem where T : class - { - /// - public override async Task ActivateItemAsync(T item, CancellationToken cancellationToken = default) - { - if (item != null && item.Equals(ActiveItem)) - { - if (IsActive) - { - await ScreenExtensions.TryActivateAsync(item, cancellationToken); - OnActivationProcessed(item, true); - } - return; - } - - var closeResult = await CloseStrategy.ExecuteAsync(new[] { ActiveItem }, cancellationToken); - - if (closeResult.CloseCanOccur) - { - await ChangeActiveItemAsync(item, true, cancellationToken); - } - else - { - OnActivationProcessed(item, false); - } - } - - /// - /// Deactivates the specified item. - /// - /// The item to close. - /// Indicates whether or not to close the item after deactivating it. - /// The cancellation token to cancel operation. - /// A task that represents the asynchronous operation. - public override async Task DeactivateItemAsync(T item, bool close, CancellationToken cancellationToken = default) - { - if (item == null || !item.Equals(ActiveItem)) - { - return; - } - - var closeResult = await CloseStrategy.ExecuteAsync(new[] { ActiveItem }, CancellationToken.None); - - if (closeResult.CloseCanOccur) - { - await ChangeActiveItemAsync(default(T), close); - } - } - - /// - /// Called to check whether or not this instance can close. - /// - /// The cancellation token to cancel operation. - /// A task that represents the asynchronous operation. - public override async Task CanCloseAsync(CancellationToken cancellationToken = default ) - { - var closeResult = await CloseStrategy.ExecuteAsync(new[] { ActiveItem }, cancellationToken); - - return closeResult.CloseCanOccur; - } - - /// - /// Called when activating. - /// - /// A task that represents the asynchronous operation. - protected override Task OnActivateAsync(CancellationToken cancellationToken) - { - return ScreenExtensions.TryActivateAsync(ActiveItem, cancellationToken); - } - - /// - /// Called when deactivating. - /// - /// Indicates whether this instance will be closed. - /// The cancellation token to cancel operation. - /// A task that represents the asynchronous operation. - protected override Task OnDeactivateAsync(bool close, CancellationToken cancellationToken) - { - return ScreenExtensions.TryDeactivateAsync(ActiveItem, close, cancellationToken); - } - - /// - /// Gets the children. - /// - /// The collection of children. - public override IEnumerable GetChildren() - { - return new[] { ActiveItem }; - } - } -} diff --git a/src/Caliburn.Micro.Core/Conductor/Base/ConductorBase.cs b/src/Caliburn.Micro.Core/Conductor/Base/ConductorBase.cs new file mode 100644 index 000000000..e16fa5ed9 --- /dev/null +++ b/src/Caliburn.Micro.Core/Conductor/Base/ConductorBase.cs @@ -0,0 +1,95 @@ +using System; +using System.Collections; +using System.Collections.Generic; +using System.Threading; +using System.Threading.Tasks; + +namespace Caliburn.Micro; + +/// +/// A base class for various implementations of . +/// +/// The type that is being conducted. +public abstract class ConductorBase : Screen, IConductor, IParent + where T : class { + private ICloseStrategy _closeStrategy; + + /// + /// Occurs when an activation request is processed. + /// + public virtual event EventHandler ActivationProcessed + = (sender, e) => { }; + + /// + /// Gets or sets the close strategy. + /// + /// The close strategy. + public ICloseStrategy CloseStrategy { + get => _closeStrategy ??= new DefaultCloseStrategy(); + set => _closeStrategy = value; + } + + Task IConductor.DeactivateItemAsync(object item, bool close, CancellationToken cancellationToken) + => DeactivateItemAsync((T)item, close, cancellationToken); + + IEnumerable IParent.GetChildren() + => GetChildren(); + + /// + /// Gets the children. + /// + /// The collection of children. + public abstract IEnumerable GetChildren(); + + Task IConductor.ActivateItemAsync(object item, CancellationToken cancellationToken) + => ActivateItemAsync((T)item, cancellationToken); + + /// + /// Activates the specified item. + /// + /// The item to activate. + /// A cancellation token that can be used by other objects or threads to receive notice of cancellation. + /// A task that represents the asynchronous operation. + public abstract Task ActivateItemAsync(T item, CancellationToken cancellationToken = default); + + /// + /// Deactivates the specified item. + /// + /// The item to close. + /// Indicates whether or not to close the item after deactivating it. + /// The cancellation token to cancel operation. + /// A task that represents the asynchronous operation. + public abstract Task DeactivateItemAsync(T item, bool close, CancellationToken cancellationToken = default); + + /// + /// Called by a subclass when an activation needs processing. + /// + /// The item on which activation was attempted. + /// if set to true activation was successful. + protected virtual void OnActivationProcessed(T item, bool success) { + if (item == null) { + return; + } + + ActivationProcessed?.Invoke(this, new ActivationProcessedEventArgs { + Item = item, + Success = success, + }); + } + + /// + /// Ensures that an item is ready to be activated. + /// + /// The item that is about to be activated. + /// The item to be activated. + protected virtual T EnsureItem(T newItem) { + if (newItem is not IChild child || + (child.Parent != null && child.Parent.Equals(this))) { + return newItem; + } + + child.Parent = this; + + return newItem; + } +} diff --git a/src/Caliburn.Micro.Core/Conductor/Base/ConductorBaseWithActiveItem.cs b/src/Caliburn.Micro.Core/Conductor/Base/ConductorBaseWithActiveItem.cs new file mode 100644 index 000000000..780042e80 --- /dev/null +++ b/src/Caliburn.Micro.Core/Conductor/Base/ConductorBaseWithActiveItem.cs @@ -0,0 +1,62 @@ +using System.Threading; +using System.Threading.Tasks; + +namespace Caliburn.Micro; + +/// +/// A base class for various implementations of that maintain an active item. +/// +/// The type that is being conducted. +public abstract class ConductorBaseWithActiveItem : ConductorBase, IConductActiveItem + where T : class { + private T _activeItem; + + /// + /// Gets or sets the currently active item. + /// + public T ActiveItem { + get => _activeItem; + set => ActivateItemAsync(value, CancellationToken.None); + } + + /// + /// Gets or sets the currently active item. + /// + /// + /// The currently active item. + /// + object IHaveActiveItem.ActiveItem { + get => ActiveItem; + set => ActiveItem = (T)value; + } + + /// + /// Changes the active item. + /// + /// The new item to activate. + /// Indicates whether or not to close the previous active item. + /// The cancellation token to cancel operation. + /// A task that represents the asynchronous operation. + protected virtual async Task ChangeActiveItemAsync(T newItem, bool closePrevious, CancellationToken cancellationToken) { + await ScreenExtensions.TryDeactivateAsync(_activeItem, closePrevious, cancellationToken); + newItem = EnsureItem(newItem); + + _activeItem = newItem; + NotifyOfPropertyChange(nameof(ActiveItem)); + + if (IsActive) { + await ScreenExtensions.TryActivateAsync(newItem, cancellationToken); + } + + OnActivationProcessed(_activeItem, true); + } + + /// + /// Changes the active item. + /// + /// The new item to activate. + /// Indicates whether or not to close the previous active item. + /// A task that represents the asynchronous operation. + protected Task ChangeActiveItemAsync(T newItem, bool closePrevious) + => ChangeActiveItemAsync(newItem, closePrevious, default); +} diff --git a/src/Caliburn.Micro.Core/Conductor/Conductor.cs b/src/Caliburn.Micro.Core/Conductor/Conductor.cs new file mode 100644 index 000000000..6ced26e42 --- /dev/null +++ b/src/Caliburn.Micro.Core/Conductor/Conductor.cs @@ -0,0 +1,88 @@ +using System.Collections.Generic; +using System.Threading; +using System.Threading.Tasks; + +namespace Caliburn.Micro; + +/// +/// An implementation of that holds on to and activates only one item at a time. +/// +public partial class Conductor : ConductorBaseWithActiveItem + where T : class { + /// + /// Gets the children. + /// + /// The collection of children. + public override IEnumerable GetChildren() + => new[] { ActiveItem }; + + /// + public override async Task ActivateItemAsync(T item, CancellationToken cancellationToken = default) { + if (item != null && item.Equals(ActiveItem)) { + if (!IsActive) { + return; + } + + await ScreenExtensions.TryActivateAsync(item, cancellationToken); + OnActivationProcessed(item, true); + + return; + } + + ICloseResult closeResult = await CloseStrategy.ExecuteAsync(new[] { ActiveItem }, cancellationToken); + if (!closeResult.CloseCanOccur) { + OnActivationProcessed(item, false); + + return; + } + + await ChangeActiveItemAsync(item, true, cancellationToken); + } + + /// + /// Deactivates the specified item. + /// + /// The item to close. + /// Indicates whether or not to close the item after deactivating it. + /// The cancellation token to cancel operation. + /// A task that represents the asynchronous operation. + public override async Task DeactivateItemAsync(T item, bool close, CancellationToken cancellationToken = default) { + if (item == null || !item.Equals(ActiveItem)) { + return; + } + + ICloseResult closeResult = await CloseStrategy.ExecuteAsync(new[] { ActiveItem }, CancellationToken.None); + if (!closeResult.CloseCanOccur) { + return; + } + + await ChangeActiveItemAsync(default, close, cancellationToken); + } + + /// + /// Called to check whether or not this instance can close. + /// + /// The cancellation token to cancel operation. + /// A task that represents the asynchronous operation. + public override async Task CanCloseAsync(CancellationToken cancellationToken = default) { + ICloseResult closeResult = await CloseStrategy.ExecuteAsync(new[] { ActiveItem }, cancellationToken); + + return closeResult.CloseCanOccur; + } + + /// + /// Called when activating. + /// + /// A task that represents the asynchronous operation. + protected override Task OnActivateAsync(CancellationToken cancellationToken) + => ScreenExtensions.TryActivateAsync(ActiveItem, cancellationToken); + + /// + /// Called when deactivating. + /// + /// Indicates whether this instance will be closed. + /// The cancellation token to cancel operation. + /// A task that represents the asynchronous operation. + protected override Task OnDeactivateAsync(bool close, CancellationToken cancellationToken) + => ScreenExtensions.TryDeactivateAsync(ActiveItem, close, cancellationToken); +} diff --git a/src/Caliburn.Micro.Core/Conductor/ConductorWithCollectionAllActive.cs b/src/Caliburn.Micro.Core/Conductor/ConductorWithCollectionAllActive.cs new file mode 100644 index 000000000..70849c603 --- /dev/null +++ b/src/Caliburn.Micro.Core/Conductor/ConductorWithCollectionAllActive.cs @@ -0,0 +1,192 @@ +using System; +using System.Collections.Generic; +using System.Collections.Specialized; +using System.Linq; +using System.Reflection; +using System.Threading; +using System.Threading.Tasks; + +namespace Caliburn.Micro; + +public partial class Conductor { + /// + /// An implementation of that holds on many items. + /// + public partial class Collection { + /// + /// An implementation of that holds on to many items which are all activated. + /// + public class AllActive : ConductorBase { + private readonly BindableCollection _items = new(); + private readonly bool _openPublicItems; + + /// + /// Initializes a new instance of the class. + /// + /// if set to true opens public items that are properties of this class. + public AllActive(bool openPublicItems) + : this() + => _openPublicItems = openPublicItems; + + /// + /// Initializes a new instance of the class. + /// + public AllActive() + => _items.CollectionChanged += + (s, e) => { + switch (e.Action) { + case NotifyCollectionChangedAction.Add: + e.NewItems.OfType().Apply(x => x.Parent = this); + break; + case NotifyCollectionChangedAction.Remove: + e.OldItems.OfType().Apply(x => x.Parent = null); + break; + case NotifyCollectionChangedAction.Replace: + e.NewItems.OfType().Apply(x => x.Parent = this); + e.OldItems.OfType().Apply(x => x.Parent = null); + break; + case NotifyCollectionChangedAction.Reset: + _items.OfType().Apply(x => x.Parent = this); + break; + } + }; + + /// + /// Gets the items that are currently being conducted. + /// + public IObservableCollection Items + => _items; + + /// + /// Called to check whether or not this instance can close. + /// + /// The cancellation token to cancel operation. + /// A task that represents the asynchronous operation. + public override async Task CanCloseAsync(CancellationToken cancellationToken = default) { + ICloseResult closeResult = await CloseStrategy.ExecuteAsync(_items.ToList(), cancellationToken); + + if (closeResult.CloseCanOccur || !closeResult.Children.Any()) { + return closeResult.CloseCanOccur; + } + + foreach (IDeactivate deactivate in closeResult.Children.OfType()) { + await deactivate.DeactivateAsync(true, cancellationToken); + } + + _items.RemoveRange(closeResult.Children); + + return closeResult.CloseCanOccur; + } + + /// + /// Activates the specified item. + /// + /// The item to activate. + /// The cancellation token to cancel operation. + /// A task that represents the asynchronous operation. + public override async Task ActivateItemAsync(T item, CancellationToken cancellationToken = default) { + if (item == null) { + return; + } + + item = EnsureItem(item); + + if (IsActive) { + await ScreenExtensions.TryActivateAsync(item, cancellationToken); + } + + OnActivationProcessed(item, true); + } + + /// + /// Deactivates the specified item. + /// + /// The item to close. + /// Indicates whether or not to close the item after deactivating it. + /// The cancellation token to cancel operation. + /// A task that represents the asynchronous operation. + public override async Task DeactivateItemAsync(T item, bool close, CancellationToken cancellationToken = default) { + if (item == null) { + return; + } + + if (!close) { + await ScreenExtensions.TryDeactivateAsync(item, false, cancellationToken); + + return; + } + + ICloseResult closeResult = await CloseStrategy.ExecuteAsync(new[] { item }, CancellationToken.None); + if (!closeResult.CloseCanOccur) { + return; + } + + await CloseItemCoreAsync(item, cancellationToken); + } + + /// + /// Gets the children. + /// + /// The collection of children. + public override IEnumerable GetChildren() + => _items; + + /// + /// Called when activating. + /// + protected override Task OnActivateAsync(CancellationToken cancellationToken) + => Task.WhenAll(_items.OfType().Select(x => x.ActivateAsync(cancellationToken))); + + /// + /// Called when deactivating. + /// + /// Indicates whether this instance will be closed. + /// The cancellation token to cancel operation. + /// A task that represents the asynchronous operation. + protected override async Task OnDeactivateAsync(bool close, CancellationToken cancellationToken) { + foreach (IDeactivate deactivate in _items.OfType()) { + await deactivate.DeactivateAsync(close, cancellationToken); + } + + if (close) { + _items.Clear(); + } + } + + /// + /// Called when initializing. + /// + protected override async Task OnInitializeAsync(CancellationToken cancellationToken) { + if (_openPublicItems) { + await Task.WhenAll(GetType().GetRuntimeProperties() + .Where(x => x.Name != "Parent" && typeof(T).GetTypeInfo().IsAssignableFrom(x.PropertyType.GetTypeInfo())) + .Select(x => x.GetValue(this, null)) + .Cast() + .Select(i => ActivateItemAsync(i, cancellationToken))); + } + } + + /// + /// Ensures that an item is ready to be activated. + /// + /// The item that is about to be activated. + /// The item to be activated. + protected override T EnsureItem(T newItem) { + int index = _items.IndexOf(newItem); + + if (index == -1) { + _items.Add(newItem); + } else { + newItem = _items[index]; + } + + return base.EnsureItem(newItem); + } + + private async Task CloseItemCoreAsync(T item, CancellationToken cancellationToken = default) { + await ScreenExtensions.TryDeactivateAsync(item, true, cancellationToken); + _items.Remove(item); + } + } + } +} diff --git a/src/Caliburn.Micro.Core/Conductor/ConductorWithCollectionOneActive.cs b/src/Caliburn.Micro.Core/Conductor/ConductorWithCollectionOneActive.cs new file mode 100644 index 000000000..2441c34d2 --- /dev/null +++ b/src/Caliburn.Micro.Core/Conductor/ConductorWithCollectionOneActive.cs @@ -0,0 +1,223 @@ +using System.Collections.Generic; +using System.Collections.Specialized; +using System.Linq; +using System.Threading; +using System.Threading.Tasks; + +namespace Caliburn.Micro; + +public partial class Conductor { + /// + /// An implementation of that holds on many items. + /// + public partial class Collection { + /// + /// An implementation of that holds on many items but only activates one at a time. + /// + public class OneActive : ConductorBaseWithActiveItem { + private readonly BindableCollection _items = new(); + + /// + /// Initializes a new instance of the class. + /// + public OneActive() + => _items.CollectionChanged += + (s, e) => { + switch (e.Action) { + case NotifyCollectionChangedAction.Add: + e.NewItems.OfType().Apply(x => x.Parent = this); + break; + case NotifyCollectionChangedAction.Remove: + e.OldItems.OfType().Apply(x => x.Parent = null); + break; + case NotifyCollectionChangedAction.Replace: + e.NewItems.OfType().Apply(x => x.Parent = this); + e.OldItems.OfType().Apply(x => x.Parent = null); + break; + case NotifyCollectionChangedAction.Reset: + _items.OfType().Apply(x => x.Parent = this); + break; + } + }; + + /// + /// Gets the items that are currently being conducted. + /// + public IObservableCollection Items + => _items; + + /// + /// Gets the children. + /// + /// The collection of children. + public override IEnumerable GetChildren() + => _items; + + /// + /// Activates the specified item. + /// + /// The item to activate. + /// A cancellation token that can be used by other objects or threads to receive notice of cancellation. + /// A task that represents the asynchronous operation. + public override async Task ActivateItemAsync(T item, CancellationToken cancellationToken = default) { + if (item == null || !item.Equals(ActiveItem)) { + await ChangeActiveItemAsync(item, false, cancellationToken); + + return; + } + + if (!IsActive) { + return; + } + + await ScreenExtensions.TryActivateAsync(item, cancellationToken); + OnActivationProcessed(item, true); + } + + /// + /// Deactivates the specified item. + /// + /// The item to close. + /// Indicates whether or not to close the item after deactivating it. + /// The cancellation token to cancel operation. + /// A task that represents the asynchronous operation. + public override async Task DeactivateItemAsync(T item, bool close, CancellationToken cancellationToken = default) { + if (item == null) { + return; + } + + if (!close) { + await ScreenExtensions.TryDeactivateAsync(item, false, cancellationToken); + + return; + } + + ICloseResult closeResult = await CloseStrategy.ExecuteAsync(new[] { item }, CancellationToken.None); + + if (closeResult.CloseCanOccur) { + await CloseItemCoreAsync(item, cancellationToken); + } + } + + /// + /// Called to check whether or not this instance can close. + /// + /// The cancellation token to cancel operation. + /// A task that represents the asynchronous operation. + public override async Task CanCloseAsync(CancellationToken cancellationToken = default) { + ICloseResult closeResult = await CloseStrategy.ExecuteAsync(_items.ToList(), cancellationToken); + if (closeResult.CloseCanOccur || !closeResult.Children.Any()) { + return closeResult.CloseCanOccur; + } + + IEnumerable closable = closeResult.Children; + + if (closable.Contains(ActiveItem)) { + var list = _items.ToList(); + T next = ActiveItem; + do { + T previous = next; + next = DetermineNextItemToActivate(list, list.IndexOf(previous)); + list.Remove(previous); + } while (closable.Contains(next)); + + T previousActive = ActiveItem; + await ChangeActiveItemAsync(next, true, cancellationToken); + _items.Remove(previousActive); + + var stillToClose = closable.ToList(); + stillToClose.Remove(previousActive); + closable = stillToClose; + } + + foreach (IDeactivate deactivate in closable.OfType()) { + await deactivate.DeactivateAsync(true, cancellationToken); + } + + _items.RemoveRange(closable); + + return closeResult.CloseCanOccur; + } + + /// + /// Called when activating. + /// + /// The cancellation token to cancel operation. + /// A task that represents the asynchronous operation. + protected override Task OnActivateAsync(CancellationToken cancellationToken) + => ScreenExtensions.TryActivateAsync(ActiveItem, cancellationToken); + + /// + /// Called when deactivating. + /// + /// Indicates whether this instance will be closed. + /// The cancellation token to cancel operation. + /// A task that represents the asynchronous operation. + protected override async Task OnDeactivateAsync(bool close, CancellationToken cancellationToken) { + if (!close) { + await ScreenExtensions.TryDeactivateAsync(ActiveItem, false, cancellationToken); + + return; + } + + foreach (IDeactivate deactivate in _items.OfType()) { + await deactivate.DeactivateAsync(true, cancellationToken); + } + + _items.Clear(); + } + + /// + /// Ensures that an item is ready to be activated. + /// + /// The item that is about to be activated. + /// The item to be activated. + protected override T EnsureItem(T newItem) { + if (newItem == null) { + newItem = DetermineNextItemToActivate(_items, ActiveItem != null ? _items.IndexOf(ActiveItem) : 0); + + return base.EnsureItem(newItem); + } + + int index = _items.IndexOf(newItem); + if (index == -1) { + _items.Add(newItem); + } else { + newItem = _items[index]; + } + + return base.EnsureItem(newItem); + } + + /// + /// Determines the next item to activate based on the last active index. + /// + /// The list of possible active items. + /// The index of the last active item. + /// The next item to activate. + /// Called after an active item is closed. + protected virtual T DetermineNextItemToActivate(IList list, int lastIndex) { + int toRemoveAt = lastIndex - 1; + + return toRemoveAt == -1 && list.Count > 1 + ? list[1] + : toRemoveAt > -1 && toRemoveAt < list.Count - 1 + ? list[toRemoveAt] + : default; + } + + private async Task CloseItemCoreAsync(T item, CancellationToken cancellationToken = default) { + if (item.Equals(ActiveItem)) { + int index = _items.IndexOf(item); + T next = DetermineNextItemToActivate(_items, index); + + await ChangeActiveItemAsync(next, true, cancellationToken); + } else { + await ScreenExtensions.TryDeactivateAsync(item, true, cancellationToken); + } + + _items.Remove(item); + } + } + } +} diff --git a/src/Caliburn.Micro.Core/Conductor/Contracts/ICloseResult.cs b/src/Caliburn.Micro.Core/Conductor/Contracts/ICloseResult.cs new file mode 100644 index 000000000..2d43a2cfc --- /dev/null +++ b/src/Caliburn.Micro.Core/Conductor/Contracts/ICloseResult.cs @@ -0,0 +1,18 @@ +using System.Collections.Generic; + +namespace Caliburn.Micro; + +/// +/// Results from the close strategy. +/// +public interface ICloseResult { + /// + /// Gets list of children that should close if the parent cannot. + /// + IEnumerable Children { get; } + + /// + /// Gets a value indicating whether a close can occur. + /// + bool CloseCanOccur { get; } +} diff --git a/src/Caliburn.Micro.Core/Conductor/Contracts/ICloseStrategy.cs b/src/Caliburn.Micro.Core/Conductor/Contracts/ICloseStrategy.cs new file mode 100644 index 000000000..ee29f34da --- /dev/null +++ b/src/Caliburn.Micro.Core/Conductor/Contracts/ICloseStrategy.cs @@ -0,0 +1,20 @@ +using System; +using System.Collections.Generic; +using System.Threading; +using System.Threading.Tasks; + +namespace Caliburn.Micro; + +/// +/// Used to gather the results from multiple child elements which may or may not prevent closing. +/// +/// The type of child element. +public interface ICloseStrategy { + /// + /// Executes the strategy. + /// + /// Items that are requesting close. + /// The cancellation token to cancel operation. + /// A task that represents the asynchronous operation and contains the result of the strategy. + Task> ExecuteAsync(IEnumerable toClose, CancellationToken cancellationToken = default); +} diff --git a/src/Caliburn.Micro.Core/Conductor/Contracts/IConductActiveItem.cs b/src/Caliburn.Micro.Core/Conductor/Contracts/IConductActiveItem.cs new file mode 100644 index 000000000..f672e5ffa --- /dev/null +++ b/src/Caliburn.Micro.Core/Conductor/Contracts/IConductActiveItem.cs @@ -0,0 +1,11 @@ +using System; +using System.Threading; +using System.Threading.Tasks; + +namespace Caliburn.Micro; + +/// +/// An that also implements . +/// +public interface IConductActiveItem : IConductor, IHaveActiveItem { +} diff --git a/src/Caliburn.Micro.Core/Conductor/Contracts/IConductor.cs b/src/Caliburn.Micro.Core/Conductor/Contracts/IConductor.cs new file mode 100644 index 000000000..e2d6867d0 --- /dev/null +++ b/src/Caliburn.Micro.Core/Conductor/Contracts/IConductor.cs @@ -0,0 +1,32 @@ +using System; +using System.Threading; +using System.Threading.Tasks; + +namespace Caliburn.Micro; + +/// +/// Denotes an instance which conducts other objects by managing an ActiveItem and maintaining a strict lifecycle. +/// +/// Conducted instances can optin to the lifecycle by impelenting any of the follosing , , . +public interface IConductor : IParent, INotifyPropertyChangedEx { + /// + /// Occurs when an activation request is processed. + /// + event EventHandler ActivationProcessed; + + /// + /// Activates the specified item. + /// + /// The item to activate. + /// A cancellation token that can be used by other objects or threads to receive notice of cancellation. + Task ActivateItemAsync(object item, CancellationToken cancellationToken = default); + + /// + /// Deactivates the specified item. + /// + /// The item to close. + /// Indicates whether or not to close the item after deactivating it. + /// The cancellation token to cancel operation. + /// A task that represents the asynchronous operation. + Task DeactivateItemAsync(object item, bool close, CancellationToken cancellationToken = default); +} diff --git a/src/Caliburn.Micro.Core/Conductor/Contracts/IHaveActiveItem.cs b/src/Caliburn.Micro.Core/Conductor/Contracts/IHaveActiveItem.cs new file mode 100644 index 000000000..aceb48f30 --- /dev/null +++ b/src/Caliburn.Micro.Core/Conductor/Contracts/IHaveActiveItem.cs @@ -0,0 +1,11 @@ +namespace Caliburn.Micro; + +/// +/// Denotes an instance which maintains an active item. +/// +public interface IHaveActiveItem { + /// + /// Gets or sets the currently active item. + /// + object ActiveItem { get; set; } +} diff --git a/src/Caliburn.Micro.Core/Conductor/Contracts/IParent.cs b/src/Caliburn.Micro.Core/Conductor/Contracts/IParent.cs new file mode 100644 index 000000000..f04815bf9 --- /dev/null +++ b/src/Caliburn.Micro.Core/Conductor/Contracts/IParent.cs @@ -0,0 +1,17 @@ +using System.Collections; +using System.Collections.Generic; + +namespace Caliburn.Micro; + +/// +/// Interface used to define an object associated to a collection of children. +/// +public interface IParent { + /// + /// Gets the children. + /// + /// + /// The collection of children. + /// + IEnumerable GetChildren(); +} diff --git a/src/Caliburn.Micro.Core/Conductor/Contracts/IParent{T}.cs b/src/Caliburn.Micro.Core/Conductor/Contracts/IParent{T}.cs new file mode 100644 index 000000000..200768874 --- /dev/null +++ b/src/Caliburn.Micro.Core/Conductor/Contracts/IParent{T}.cs @@ -0,0 +1,18 @@ +using System.Collections; +using System.Collections.Generic; + +namespace Caliburn.Micro; + +/// +/// Interface used to define a specialized parent. +/// +/// The type of children. +public interface IParent : IParent { + /// + /// Gets the children. + /// + /// + /// The collection of children. + /// + new IEnumerable GetChildren(); +} diff --git a/src/Caliburn.Micro.Core/Conductor/DefaultImpl/CloseResult.cs b/src/Caliburn.Micro.Core/Conductor/DefaultImpl/CloseResult.cs new file mode 100644 index 000000000..7257395f9 --- /dev/null +++ b/src/Caliburn.Micro.Core/Conductor/DefaultImpl/CloseResult.cs @@ -0,0 +1,29 @@ +using System.Collections.Generic; + +namespace Caliburn.Micro; + +/// +/// The result of a test whether an instance can be closed. +/// +/// The type of the children of the instance. +public class CloseResult : ICloseResult { + /// + /// Initializes a new instance of the class. + /// + /// Whether of not a close operation should occur. + /// The children of the instance that can be closed. + public CloseResult(bool closeCanOccur, IEnumerable children) { + CloseCanOccur = closeCanOccur; + Children = children; + } + + /// + /// Gets a value indicating whether or not a close operation should occur. + /// + public bool CloseCanOccur { get; } + + /// + /// Gets the children of the instance that can be closed. + /// + public IEnumerable Children { get; } +} diff --git a/src/Caliburn.Micro.Core/Conductor/DefaultImpl/DefaultCloseStrategy.cs b/src/Caliburn.Micro.Core/Conductor/DefaultImpl/DefaultCloseStrategy.cs new file mode 100644 index 000000000..aacd84f22 --- /dev/null +++ b/src/Caliburn.Micro.Core/Conductor/DefaultImpl/DefaultCloseStrategy.cs @@ -0,0 +1,45 @@ +using System.Collections.Generic; +using System.Threading; +using System.Threading.Tasks; + +namespace Caliburn.Micro; + +/// +/// Used to gather the results from multiple child elements which may or may not prevent closing. +/// +/// The type of child element. +public class DefaultCloseStrategy : ICloseStrategy { + private readonly bool _closeConductedItemsWhenConductorCannotClose; + + /// + /// Initializes a new instance of the class. + /// + /// Indicates that even if all conducted items are not closable, those that are should be closed. The default is FALSE. + public DefaultCloseStrategy(bool closeConductedItemsWhenConductorCannotClose = false) + => _closeConductedItemsWhenConductorCannotClose = closeConductedItemsWhenConductorCannotClose; + + /// + public async Task> ExecuteAsync(IEnumerable toClose, CancellationToken cancellationToken = default) { + var closeable = new List(); + bool closeCanOccur = true; + foreach (T child in toClose) { + if (child is not IGuardClose guard) { + closeable.Add(child); + continue; + } + + bool canClose = await guard.CanCloseAsync(cancellationToken); + if (canClose) { + closeable.Add(child); + } + + closeCanOccur = closeCanOccur && canClose; + } + + if (!_closeConductedItemsWhenConductorCannotClose && !closeCanOccur) { + closeable.Clear(); + } + + return new CloseResult(closeCanOccur, closeable); + } +} diff --git a/src/Caliburn.Micro.Core/Conductor/EventArgs/ActivationProcessedEventArgs.cs b/src/Caliburn.Micro.Core/Conductor/EventArgs/ActivationProcessedEventArgs.cs new file mode 100644 index 000000000..69557471f --- /dev/null +++ b/src/Caliburn.Micro.Core/Conductor/EventArgs/ActivationProcessedEventArgs.cs @@ -0,0 +1,19 @@ +using System; + +namespace Caliburn.Micro; + +/// +/// Contains details about the success or failure of an item's activation through an . +/// +public class ActivationProcessedEventArgs : EventArgs { + /// + /// Gets or sets the item whose activation was processed. + /// + public object Item { get; set; } + + /// + /// Gets or sets a value indicating whether the activation was a success. + /// + /// true if success; otherwise, false. + public bool Success { get; set; } +} diff --git a/src/Caliburn.Micro.Core/Conductor/Extensions/ConductorExtensions.cs b/src/Caliburn.Micro.Core/Conductor/Extensions/ConductorExtensions.cs new file mode 100644 index 000000000..3cc9b0dae --- /dev/null +++ b/src/Caliburn.Micro.Core/Conductor/Extensions/ConductorExtensions.cs @@ -0,0 +1,18 @@ +using System.Threading; +using System.Threading.Tasks; + +namespace Caliburn.Micro; + +/// +/// Extension methods for the instance. +/// +public static class ConductorExtensions { + /// + /// Activates the specified item. + /// + /// The conductor to activate the item with. + /// The item to activate. + /// A task that represents the asynchronous operation. + public static Task ActivateItemAsync(this IConductor conductor, object item) + => conductor.ActivateItemAsync(item, default); +} diff --git a/src/Caliburn.Micro.Core/ConductorBase.cs b/src/Caliburn.Micro.Core/ConductorBase.cs deleted file mode 100644 index aa7443907..000000000 --- a/src/Caliburn.Micro.Core/ConductorBase.cs +++ /dev/null @@ -1,100 +0,0 @@ -using System; -using System.Collections; -using System.Collections.Generic; -using System.Threading; -using System.Threading.Tasks; - -namespace Caliburn.Micro -{ - /// - /// A base class for various implementations of . - /// - /// The type that is being conducted. - public abstract class ConductorBase : Screen, IConductor, IParent where T : class - { - private ICloseStrategy _closeStrategy; - - /// - /// Gets or sets the close strategy. - /// - /// The close strategy. - public ICloseStrategy CloseStrategy - { - get => _closeStrategy ?? (_closeStrategy = new DefaultCloseStrategy()); - set => _closeStrategy = value; - } - - Task IConductor.DeactivateItemAsync(object item, bool close, CancellationToken cancellationToken) - { - return DeactivateItemAsync((T)item, close, cancellationToken); - } - - IEnumerable IParent.GetChildren() - { - return GetChildren(); - } - - /// - /// Occurs when an activation request is processed. - /// - public virtual event EventHandler ActivationProcessed = delegate { }; - - /// - /// Gets the children. - /// - /// The collection of children. - public abstract IEnumerable GetChildren(); - - Task IConductor.ActivateItemAsync(object item, CancellationToken cancellationToken) - { - return ActivateItemAsync((T)item, cancellationToken); - } - - /// - /// Activates the specified item. - /// - /// The item to activate. - /// A cancellation token that can be used by other objects or threads to receive notice of cancellation. - /// A task that represents the asynchronous operation. - public abstract Task ActivateItemAsync(T item, CancellationToken cancellationToken = default); - - /// - /// Deactivates the specified item. - /// - /// The item to close. - /// Indicates whether or not to close the item after deactivating it. - /// The cancellation token to cancel operation. - /// A task that represents the asynchronous operation. - public abstract Task DeactivateItemAsync(T item, bool close, CancellationToken cancellationToken = default); - - /// - /// Called by a subclass when an activation needs processing. - /// - /// The item on which activation was attempted. - /// if set to true activation was successful. - protected virtual void OnActivationProcessed(T item, bool success) - { - if (item == null) - return; - - ActivationProcessed?.Invoke(this, new ActivationProcessedEventArgs - { - Item = item, - Success = success - }); - } - - /// - /// Ensures that an item is ready to be activated. - /// - /// The item that is about to be activated. - /// The item to be activated. - protected virtual T EnsureItem(T newItem) - { - if (newItem is IChild node && node.Parent != this) - node.Parent = this; - - return newItem; - } - } -} diff --git a/src/Caliburn.Micro.Core/ConductorBaseWithActiveItem.cs b/src/Caliburn.Micro.Core/ConductorBaseWithActiveItem.cs deleted file mode 100644 index 1249eac40..000000000 --- a/src/Caliburn.Micro.Core/ConductorBaseWithActiveItem.cs +++ /dev/null @@ -1,62 +0,0 @@ -using System.Threading; -using System.Threading.Tasks; - -namespace Caliburn.Micro -{ - /// - /// A base class for various implementations of that maintain an active item. - /// - /// The type that is being conducted. - public abstract class ConductorBaseWithActiveItem : ConductorBase, IConductActiveItem where T : class - { - private T _activeItem; - - /// - /// The currently active item. - /// - public T ActiveItem - { - get => _activeItem; - set => ActivateItemAsync(value, CancellationToken.None); - } - - /// - /// The currently active item. - /// - /// - object IHaveActiveItem.ActiveItem - { - get => ActiveItem; - set => ActiveItem = (T)value; - } - - /// - /// Changes the active item. - /// - /// The new item to activate. - /// Indicates whether or not to close the previous active item. - /// The cancellation token to cancel operation. - /// A task that represents the asynchronous operation. - protected virtual async Task ChangeActiveItemAsync(T newItem, bool closePrevious, CancellationToken cancellationToken) - { - await ScreenExtensions.TryDeactivateAsync(_activeItem, closePrevious, cancellationToken); - newItem = EnsureItem(newItem); - - _activeItem = newItem; - NotifyOfPropertyChange(nameof(ActiveItem)); - - if (IsActive) - await ScreenExtensions.TryActivateAsync(newItem, cancellationToken); - - OnActivationProcessed(_activeItem, true); - } - - /// - /// Changes the active item. - /// - /// The new item to activate. - /// Indicates whether or not to close the previous active item. - /// A task that represents the asynchronous operation. - protected Task ChangeActiveItemAsync(T newItem, bool closePrevious) => ChangeActiveItemAsync(newItem, closePrevious, default); - } -} diff --git a/src/Caliburn.Micro.Core/ConductorExtensions.cs b/src/Caliburn.Micro.Core/ConductorExtensions.cs deleted file mode 100644 index 283df14a8..000000000 --- a/src/Caliburn.Micro.Core/ConductorExtensions.cs +++ /dev/null @@ -1,19 +0,0 @@ -using System.Threading; -using System.Threading.Tasks; - -namespace Caliburn.Micro -{ - /// - /// Extension methods for the instance. - /// - public static class ConductorExtensions - { - /// - /// Activates the specified item. - /// - /// The conductor to activate the item with. - /// The item to activate. - /// A task that represents the asynchronous operation. - public static Task ActivateItemAsync(this IConductor conductor, object item) => conductor.ActivateItemAsync(item, default); - } -} diff --git a/src/Caliburn.Micro.Core/ConductorWithCollectionAllActive.cs b/src/Caliburn.Micro.Core/ConductorWithCollectionAllActive.cs deleted file mode 100644 index 610dcbea9..000000000 --- a/src/Caliburn.Micro.Core/ConductorWithCollectionAllActive.cs +++ /dev/null @@ -1,203 +0,0 @@ -using System; -using System.Collections.Generic; -using System.Collections.Specialized; -using System.Linq; -using System.Reflection; -using System.Threading; -using System.Threading.Tasks; - -namespace Caliburn.Micro -{ - public partial class Conductor - { - /// - /// An implementation of that holds on many items. - /// - public partial class Collection - { - /// - /// An implementation of that holds on to many items which are all activated. - /// - public class AllActive : ConductorBase - { - private readonly BindableCollection _items = new BindableCollection(); - private readonly bool _openPublicItems; - - /// - /// Initializes a new instance of the class. - /// - /// if set to true opens public items that are properties of this class. - public AllActive(bool openPublicItems) - : this() - { - _openPublicItems = openPublicItems; - } - - /// - /// Initializes a new instance of the class. - /// - public AllActive() - { - _items.CollectionChanged += (s, e) => - { - switch (e.Action) - { - case NotifyCollectionChangedAction.Add: - e.NewItems.OfType().Apply(x => x.Parent = this); - break; - case NotifyCollectionChangedAction.Remove: - e.OldItems.OfType().Apply(x => x.Parent = null); - break; - case NotifyCollectionChangedAction.Replace: - e.NewItems.OfType().Apply(x => x.Parent = this); - e.OldItems.OfType().Apply(x => x.Parent = null); - break; - case NotifyCollectionChangedAction.Reset: - _items.OfType().Apply(x => x.Parent = this); - break; - } - }; - } - - /// - /// Gets the items that are currently being conducted. - /// - public IObservableCollection Items => _items; - - /// - /// Called when activating. - /// - protected override Task OnActivateAsync(CancellationToken cancellationToken) - { - return Task.WhenAll(_items.OfType().Select(x => x.ActivateAsync(cancellationToken))); - } - - /// - /// Called when deactivating. - /// - /// Indicates whether this instance will be closed. - /// The cancellation token to cancel operation. - /// A task that represents the asynchronous operation. - protected override async Task OnDeactivateAsync(bool close, CancellationToken cancellationToken) - { - foreach(var deactivate in _items.OfType()) - { - await deactivate.DeactivateAsync(close, cancellationToken); - } - - if (close) - _items.Clear(); - } - - /// - /// Called to check whether or not this instance can close. - /// - /// The cancellation token to cancel operation. - /// A task that represents the asynchronous operation. - public override async Task CanCloseAsync(CancellationToken cancellationToken = default) - { - var closeResult = await CloseStrategy.ExecuteAsync(_items.ToList(), cancellationToken); - - if (!closeResult.CloseCanOccur && closeResult.Children.Any()) - { - foreach (var deactivate in closeResult.Children.OfType()) - { - await deactivate.DeactivateAsync(true, cancellationToken); - } - - _items.RemoveRange(closeResult.Children); - } - - return closeResult.CloseCanOccur; - } - - /// - /// Called when initializing. - /// - protected override async Task OnInitializeAsync(CancellationToken cancellationToken) - { - if (_openPublicItems) - await Task.WhenAll(GetType().GetRuntimeProperties() - .Where(x => x.Name != "Parent" && typeof(T).GetTypeInfo().IsAssignableFrom(x.PropertyType.GetTypeInfo())) - .Select(x => x.GetValue(this, null)) - .Cast() - .Select(i => ActivateItemAsync(i, cancellationToken))); - } - - /// - /// Activates the specified item. - /// - /// The item to activate. - /// The cancellation token to cancel operation. - /// A task that represents the asynchronous operation. - public override async Task ActivateItemAsync(T item, CancellationToken cancellationToken = default) - { - if (item == null) - return; - - item = EnsureItem(item); - - if (IsActive) - await ScreenExtensions.TryActivateAsync(item, cancellationToken); - - OnActivationProcessed(item, true); - } - - /// - /// Deactivates the specified item. - /// - /// The item to close. - /// Indicates whether or not to close the item after deactivating it. - /// The cancellation token to cancel operation. - /// A task that represents the asynchronous operation. - public override async Task DeactivateItemAsync(T item, bool close, CancellationToken cancellationToken = default) - { - if (item == null) - return; - - if (close) - { - var closeResult = await CloseStrategy.ExecuteAsync(new[] { item }, CancellationToken.None); - - if (closeResult.CloseCanOccur) - await CloseItemCoreAsync(item, cancellationToken); - } - else - await ScreenExtensions.TryDeactivateAsync(item, false, cancellationToken); - } - - /// - /// Gets the children. - /// - /// The collection of children. - public override IEnumerable GetChildren() - { - return _items; - } - - private async Task CloseItemCoreAsync(T item, CancellationToken cancellationToken = default) - { - await ScreenExtensions.TryDeactivateAsync(item, true, cancellationToken); - _items.Remove(item); - } - - /// - /// Ensures that an item is ready to be activated. - /// - /// The item that is about to be activated. - /// The item to be activated. - protected override T EnsureItem(T newItem) - { - var index = _items.IndexOf(newItem); - - if (index == -1) - _items.Add(newItem); - else - newItem = _items[index]; - - return base.EnsureItem(newItem); - } - } - } - } -} diff --git a/src/Caliburn.Micro.Core/ConductorWithCollectionOneActive.cs b/src/Caliburn.Micro.Core/ConductorWithCollectionOneActive.cs deleted file mode 100644 index e871e216b..000000000 --- a/src/Caliburn.Micro.Core/ConductorWithCollectionOneActive.cs +++ /dev/null @@ -1,245 +0,0 @@ -using System.Collections.Generic; -using System.Collections.Specialized; -using System.Linq; -using System.Threading; -using System.Threading.Tasks; - -namespace Caliburn.Micro -{ - public partial class Conductor - { - /// - /// An implementation of that holds on many items. - /// - public partial class Collection - { - /// - /// An implementation of that holds on many items but only activates one at a time. - /// - public class OneActive : ConductorBaseWithActiveItem - { - private readonly BindableCollection _items = new BindableCollection(); - - /// - /// Initializes a new instance of the class. - /// - public OneActive() - { - _items.CollectionChanged += (s, e) => - { - switch (e.Action) - { - case NotifyCollectionChangedAction.Add: - e.NewItems.OfType().Apply(x => x.Parent = this); - break; - case NotifyCollectionChangedAction.Remove: - e.OldItems.OfType().Apply(x => x.Parent = null); - break; - case NotifyCollectionChangedAction.Replace: - e.NewItems.OfType().Apply(x => x.Parent = this); - e.OldItems.OfType().Apply(x => x.Parent = null); - break; - case NotifyCollectionChangedAction.Reset: - _items.OfType().Apply(x => x.Parent = this); - break; - } - }; - } - - /// - /// Gets the items that are currently being conducted. - /// - public IObservableCollection Items => _items; - - /// - /// Gets the children. - /// - /// The collection of children. - public override IEnumerable GetChildren() => _items; - - /// - /// Activates the specified item. - /// - /// The item to activate. - /// A cancellation token that can be used by other objects or threads to receive notice of cancellation. - /// A task that represents the asynchronous operation. - public override async Task ActivateItemAsync(T item, CancellationToken cancellationToken = default) - { - if (item != null && item.Equals(ActiveItem)) - { - if (IsActive) - { - await ScreenExtensions.TryActivateAsync(item, cancellationToken); - OnActivationProcessed(item, true); - } - - return; - } - - await ChangeActiveItemAsync(item, false, cancellationToken); - } - - /// - /// Deactivates the specified item. - /// - /// The item to close. - /// Indicates whether or not to close the item after deactivating it. - /// The cancellation token to cancel operation. - /// A task that represents the asynchronous operation. - public override async Task DeactivateItemAsync(T item, bool close, CancellationToken cancellationToken = default) - { - if (item == null) - return; - - if (!close) - await ScreenExtensions.TryDeactivateAsync(item, false, cancellationToken); - else - { - var closeResult = await CloseStrategy.ExecuteAsync(new[] { item }, CancellationToken.None); - - if (closeResult.CloseCanOccur) - await CloseItemCoreAsync(item, cancellationToken); - } - } - - private async Task CloseItemCoreAsync(T item, CancellationToken cancellationToken = default) - { - if (item.Equals(ActiveItem)) - { - var index = _items.IndexOf(item); - var next = DetermineNextItemToActivate(_items, index); - - await ChangeActiveItemAsync(next, true); - } - else - { - await ScreenExtensions.TryDeactivateAsync(item, true, cancellationToken); - } - - _items.Remove(item); - } - - /// - /// Determines the next item to activate based on the last active index. - /// - /// The list of possible active items. - /// The index of the last active item. - /// The next item to activate. - /// Called after an active item is closed. - protected virtual T DetermineNextItemToActivate(IList list, int lastIndex) - { - var toRemoveAt = lastIndex - 1; - - if (toRemoveAt == -1 && list.Count > 1) - return list[1]; - - if (toRemoveAt > -1 && toRemoveAt < list.Count - 1) - return list[toRemoveAt]; - - return default(T); - } - - /// - /// Called to check whether or not this instance can close. - /// - /// The cancellation token to cancel operation. - /// A task that represents the asynchronous operation. - public override async Task CanCloseAsync(CancellationToken cancellationToken = default) - { - var closeResult = await CloseStrategy.ExecuteAsync(_items.ToList(), cancellationToken); - - if (!closeResult.CloseCanOccur && closeResult.Children.Any()) - { - var closable = closeResult.Children; - - if (closable.Contains(ActiveItem)) - { - var list = _items.ToList(); - var next = ActiveItem; - do - { - var previous = next; - next = DetermineNextItemToActivate(list, list.IndexOf(previous)); - list.Remove(previous); - } while (closable.Contains(next)); - - var previousActive = ActiveItem; - await ChangeActiveItemAsync(next, true); - _items.Remove(previousActive); - - var stillToClose = closable.ToList(); - stillToClose.Remove(previousActive); - closable = stillToClose; - } - - foreach(var deactivate in closable.OfType()) - { - await deactivate.DeactivateAsync(true, cancellationToken); - } - - _items.RemoveRange(closable); - } - - return closeResult.CloseCanOccur; - } - - /// - /// Called when activating. - /// - /// The cancellation token to cancel operation. - /// A task that represents the asynchronous operation. - protected override Task OnActivateAsync(CancellationToken cancellationToken) - { - return ScreenExtensions.TryActivateAsync(ActiveItem, cancellationToken); - } - - /// - /// Called when deactivating. - /// - /// Indicates whether this instance will be closed. - /// The cancellation token to cancel operation. - /// A task that represents the asynchronous operation. - protected override async Task OnDeactivateAsync(bool close, CancellationToken cancellationToken) - { - if (close) - { - foreach (var deactivate in _items.OfType()) - { - await deactivate.DeactivateAsync(true, cancellationToken); - } - - _items.Clear(); - } - else - { - await ScreenExtensions.TryDeactivateAsync(ActiveItem, false, cancellationToken); - } - } - - /// - /// Ensures that an item is ready to be activated. - /// - /// The item that is about to be activated. - /// The item to be activated. - protected override T EnsureItem(T newItem) - { - if (newItem == null) - { - newItem = DetermineNextItemToActivate(_items, ActiveItem != null ? _items.IndexOf(ActiveItem) : 0); - } - else - { - var index = _items.IndexOf(newItem); - - if (index == -1) - _items.Add(newItem); - else - newItem = _items[index]; - } - - return base.EnsureItem(newItem); - } - } - } - } -} diff --git a/src/Caliburn.Micro.Core/ContainerExtensions.cs b/src/Caliburn.Micro.Core/ContainerExtensions.cs deleted file mode 100644 index bd8140d15..000000000 --- a/src/Caliburn.Micro.Core/ContainerExtensions.cs +++ /dev/null @@ -1,173 +0,0 @@ -using System; -using System.Collections.Generic; -using System.Linq; -using System.Reflection; - -namespace Caliburn.Micro -{ - /// - /// Extension methods for the . - /// - public static class ContainerExtensions - { - /// - /// Registers a singleton. - /// - /// The type of the implementation. - /// The container. - /// The key. - /// The container. - public static SimpleContainer Singleton(this SimpleContainer container, string key = null) - { - return Singleton(container, key); - } - - /// - /// Registers a singleton. - /// - /// The type of the service. - /// The type of the implementation. - /// The container. - /// The key. - /// The container. - public static SimpleContainer Singleton(this SimpleContainer container, string key = null) - where TImplementation : TService - { - container.RegisterSingleton(typeof(TService), key, typeof(TImplementation)); - return container; - } - - /// - /// Registers an service to be created on each request. - /// - /// The type of the implementation. - /// The container. - /// The key. - /// The container. - public static SimpleContainer PerRequest(this SimpleContainer container, string key = null) - { - return PerRequest(container, key); - } - - /// - /// Registers an service to be created on each request. - /// - /// The type of the service. - /// The type of the implementation. - /// The container. - /// The key. - /// The container. - public static SimpleContainer PerRequest(this SimpleContainer container, string key = null) - where TImplementation : TService - { - container.RegisterPerRequest(typeof(TService), key, typeof(TImplementation)); - return container; - } - - /// - /// Registers an instance with the container. - /// - /// The type of the service. - /// The container. - /// The instance. - /// The container. - public static SimpleContainer Instance(this SimpleContainer container, TService instance) - { - container.RegisterInstance(typeof(TService), null, instance); - return container; - } - - /// - /// Registers a custom service handler with the container. - /// - /// The type of the service. - /// The container. - /// The handler. - /// The container. - public static SimpleContainer Handler(this SimpleContainer container, - Func handler) - { - container.RegisterHandler(typeof(TService), null, handler); - return container; - } - - /// - /// Registers all specified types in an assembly as singleton in the container. - /// - /// The type of the service. - /// The container. - /// The assembly. - /// The type filter. - /// The container. - public static SimpleContainer AllTypesOf(this SimpleContainer container, Assembly assembly, - Func filter = null) - { - if (filter == null) - { - filter = type => true; - } - - var serviceType = typeof(TService); - var types = from type in assembly.DefinedTypes - where serviceType.GetTypeInfo().IsAssignableFrom(type) - && !type.IsAbstract - && !type.IsInterface - && filter(type.AsType()) - select type; - - foreach (var type in types) - { - container.RegisterSingleton(typeof(TService), null, type.AsType()); - } - - return container; - } - - /// - /// Requests an instance. - /// - /// The type of the service. - /// The container. - /// The key. - /// The instance. - public static TService GetInstance(this SimpleContainer container, string key = null) - { - return (TService)container.GetInstance(typeof(TService), key); - } - - /// - /// Gets all instances of a particular type and the given key (default null). - /// - /// The type to resolve. - /// The container. - /// The key shared by those instances - /// The resolved instances. - public static IEnumerable GetAllInstances(this SimpleContainer container, string key = null) - { - return container.GetAllInstances(typeof(TService), key).Cast(); - } - - /// - /// Determines if a handler for the service/key has previously been registered. - /// - /// The service type. - /// The container. - /// The key. - /// True if a handler is registere; false otherwise. - public static bool HasHandler(this SimpleContainer container, string key = null) - { - return container.HasHandler(typeof(TService), key); - } - - /// - /// Unregisters any handlers for the service/key that have previously been registered. - /// - /// The service type. - /// The container. - /// The key. - public static void UnregisterHandler(this SimpleContainer container, string key = null) - { - container.UnregisterHandler(typeof(TService), key); - } - } -} diff --git a/src/Caliburn.Micro.Core/ContinueResultDecorator.cs b/src/Caliburn.Micro.Core/ContinueResultDecorator.cs deleted file mode 100644 index 815b58a9e..000000000 --- a/src/Caliburn.Micro.Core/ContinueResultDecorator.cs +++ /dev/null @@ -1,79 +0,0 @@ -using System; - -namespace Caliburn.Micro -{ - /// - /// A result decorator which executes a coroutine when the wrapped result was cancelled. - /// - public class ContinueResultDecorator : ResultDecoratorBase - { - private static readonly ILog Log = LogManager.GetLog(typeof(ContinueResultDecorator)); - private readonly Func coroutine; - - /// - /// Initializes a new instance of the class. - /// - /// The result to decorate. - /// The coroutine to execute when was canceled. - public ContinueResultDecorator(IResult result, Func coroutine) - : base(result) - { - if (coroutine == null) - { - throw new ArgumentNullException("coroutine"); - } - - this.coroutine = coroutine; - } - - /// - /// Called when the execution of the decorated result has completed. - /// - /// The context. - /// The decorated result. - /// The instance containing the event data. - protected override void OnInnerResultCompleted(CoroutineExecutionContext context, IResult innerResult, ResultCompletionEventArgs args) - { - if (args.Error != null || !args.WasCancelled) - { - OnCompleted(new ResultCompletionEventArgs { Error = args.Error }); - } - else - { - Log.Info(string.Format("Executing coroutine because {0} was cancelled.", innerResult.GetType().Name)); - Continue(context); - } - } - - private void Continue(CoroutineExecutionContext context) - { - IResult continueResult; - try - { - continueResult = coroutine(); - } - catch (Exception ex) - { - OnCompleted(new ResultCompletionEventArgs { Error = ex }); - return; - } - - try - { - continueResult.Completed += ContinueCompleted; - IoC.BuildUp(continueResult); - continueResult.Execute(context); - } - catch (Exception ex) - { - ContinueCompleted(continueResult, new ResultCompletionEventArgs { Error = ex }); - } - } - - private void ContinueCompleted(object sender, ResultCompletionEventArgs args) - { - ((IResult)sender).Completed -= ContinueCompleted; - OnCompleted(new ResultCompletionEventArgs { Error = args.Error, WasCancelled = (args.Error == null) }); - } - } -} diff --git a/src/Caliburn.Micro.Core/Coroutine.cs b/src/Caliburn.Micro.Core/Coroutine.cs deleted file mode 100644 index 2b357960c..000000000 --- a/src/Caliburn.Micro.Core/Coroutine.cs +++ /dev/null @@ -1,100 +0,0 @@ -using System; -using System.Collections.Generic; -using System.Threading.Tasks; - -namespace Caliburn.Micro -{ - /// - /// Manages coroutine execution. - /// - public static class Coroutine - { - private static readonly ILog Log = LogManager.GetLog(typeof(Coroutine)); - - /// - /// Creates the parent enumerator. - /// - public static Func, IResult> CreateParentEnumerator = inner => new SequentialResult(inner); - - /// - /// Executes a coroutine. - /// - /// The coroutine to execute. - /// The context to execute the coroutine within. - /// /// The completion callback for the coroutine. - public static void BeginExecute(IEnumerator coroutine, CoroutineExecutionContext context = null, EventHandler callback = null) - { - Log.Info("Executing coroutine."); - - var enumerator = CreateParentEnumerator(coroutine); - IoC.BuildUp(enumerator); - - if (callback != null) - { - ExecuteOnCompleted(enumerator, callback); - } - - ExecuteOnCompleted(enumerator, Completed); - enumerator.Execute(context ?? new CoroutineExecutionContext()); - } - - /// - /// Executes a coroutine asynchronous. - /// - /// The coroutine to execute. - /// The context to execute the coroutine within. - /// A task that represents the asynchronous coroutine. - public static Task ExecuteAsync(IEnumerator coroutine, CoroutineExecutionContext context = null) - { - var taskSource = new TaskCompletionSource(); - - BeginExecute(coroutine, context, (s, e) => - { - if (e.Error != null) - { - taskSource.SetException(e.Error); - } - else if (e.WasCancelled) - { - taskSource.SetCanceled(); - } - else - { - taskSource.SetResult(null); - } - }); - - return taskSource.Task; - } - - private static void ExecuteOnCompleted(IResult result, EventHandler handler) - { - EventHandler onCompledted = null; - onCompledted = (s, e) => - { - result.Completed -= onCompledted; - handler(s, e); - }; - result.Completed += onCompledted; - } - - /// - /// Called upon completion of a coroutine. - /// - public static event EventHandler Completed = (s, e) => - { - if (e.Error != null) - { - Log.Error(e.Error); - } - else if (e.WasCancelled) - { - Log.Info("Coroutine execution cancelled."); - } - else - { - Log.Info("Coroutine execution completed."); - } - }; - } -} diff --git a/src/Caliburn.Micro.Core/Coroutine/ExtensionPoints/Coroutine.cs b/src/Caliburn.Micro.Core/Coroutine/ExtensionPoints/Coroutine.cs new file mode 100644 index 000000000..3ae5c1191 --- /dev/null +++ b/src/Caliburn.Micro.Core/Coroutine/ExtensionPoints/Coroutine.cs @@ -0,0 +1,106 @@ +using System; +using System.Collections.Generic; +using System.Threading.Tasks; + +namespace Caliburn.Micro; + +/// +/// Manages coroutine execution. +/// +public static class Coroutine { + private static readonly ILog Log + = LogManager.GetLog(typeof(Coroutine)); + + /// + /// Called upon completion of a coroutine. + /// + public static event EventHandler Completed + = (s, e) => { + if (e.Error != null) { + Log.Error(e.Error); + + return; + } + + if (e.WasCancelled) { + Log.Info("Coroutine execution cancelled."); + + return; + } + + Log.Info("Coroutine execution completed."); + }; + + /// + /// Gets or sets func to create the parent enumerator. + /// + public static Func, IResult> CreateParentEnumerator { get; set; } + = inner => new SequentialResult(inner); + + /// + /// Executes a coroutine. + /// + /// The coroutine to execute. + /// The context to execute the coroutine within. + /// /// The completion callback for the coroutine. + public static void BeginExecute( + IEnumerator coroutine, + CoroutineExecutionContext context = null, + EventHandler callback = null) { + Log.Info("Executing coroutine."); + + IResult enumerator = CreateParentEnumerator(coroutine); + IoC.BuildUp(enumerator); + + if (callback != null) { + ExecuteOnCompleted(enumerator, callback); + } + + ExecuteOnCompleted(enumerator, Completed); + enumerator.Execute(context ?? new CoroutineExecutionContext()); + } + + /// + /// Executes a coroutine asynchronous. + /// + /// The coroutine to execute. + /// The context to execute the coroutine within. + /// A task that represents the asynchronous coroutine. + public static Task ExecuteAsync( + IEnumerator coroutine, + CoroutineExecutionContext context = null) { + var taskSource = new TaskCompletionSource(); + + BeginExecute( + coroutine, + context, + (s, e) => { + if (e.Error != null) { + taskSource.SetException(e.Error); + + return; + } + + if (e.WasCancelled) { + taskSource.SetCanceled(); + + return; + } + + taskSource.SetResult(null); + }); + + return taskSource.Task; + } + + private static void ExecuteOnCompleted( + IResult result, + EventHandler handler) { + void OnCompledted(object s, ResultCompletionEventArgs e) { + result.Completed -= OnCompledted; + handler(s, e); + } + + result.Completed += OnCompledted; + } +} diff --git a/src/Caliburn.Micro.Core/Coroutine/Models/CoroutineExecutionContext.cs b/src/Caliburn.Micro.Core/Coroutine/Models/CoroutineExecutionContext.cs new file mode 100644 index 000000000..d7bae0a77 --- /dev/null +++ b/src/Caliburn.Micro.Core/Coroutine/Models/CoroutineExecutionContext.cs @@ -0,0 +1,21 @@ +namespace Caliburn.Micro; + +/// +/// The context used during the execution of a Coroutine. +/// +public class CoroutineExecutionContext { + /// + /// Gets or sets the source from which the message originates. + /// + public object Source { get; set; } + + /// + /// Gets or sets the view associated with the target. + /// + public object View { get; set; } + + /// + /// Gets or sets the instance on which the action is invoked. + /// + public object Target { get; set; } +} diff --git a/src/Caliburn.Micro.Core/CoroutineExecutionContext.cs b/src/Caliburn.Micro.Core/CoroutineExecutionContext.cs deleted file mode 100644 index 9d3ae3af5..000000000 --- a/src/Caliburn.Micro.Core/CoroutineExecutionContext.cs +++ /dev/null @@ -1,23 +0,0 @@ -namespace Caliburn.Micro -{ - /// - /// The context used during the execution of a Coroutine. - /// - public class CoroutineExecutionContext - { - /// - /// The source from which the message originates. - /// - public object Source { get; set; } - - /// - /// The view associated with the target. - /// - public object View { get; set; } - - /// - /// The instance on which the action is invoked. - /// - public object Target { get; set; } - } -} diff --git a/src/Caliburn.Micro.Core/DeactivateExtensions.cs b/src/Caliburn.Micro.Core/DeactivateExtensions.cs deleted file mode 100644 index 54ee73c3e..000000000 --- a/src/Caliburn.Micro.Core/DeactivateExtensions.cs +++ /dev/null @@ -1,18 +0,0 @@ -using System.Threading.Tasks; - -namespace Caliburn.Micro -{ - /// - /// Extension methods for the instance. - /// - public static class DeactivateExtensions - { - /// - /// Deactivates this instance. - /// - /// The instance to deactivate - /// Indicates whether or not this instance is being closed. - /// A task that represents the asynchronous operation. - public static Task DeactivateAsync(this IDeactivate deactivate, bool close) => deactivate.DeactivateAsync(close, default); - } -} diff --git a/src/Caliburn.Micro.Core/DeactivationEventArgs.cs b/src/Caliburn.Micro.Core/DeactivationEventArgs.cs deleted file mode 100644 index 52089bbda..000000000 --- a/src/Caliburn.Micro.Core/DeactivationEventArgs.cs +++ /dev/null @@ -1,15 +0,0 @@ -using System; - -namespace Caliburn.Micro -{ - /// - /// EventArgs sent during deactivation. - /// - public class DeactivationEventArgs : EventArgs - { - /// - /// Indicates whether the sender was closed in addition to being deactivated. - /// - public bool WasClosed { get; set; } - } -} diff --git a/src/Caliburn.Micro.Core/DebugLog.cs b/src/Caliburn.Micro.Core/DebugLog.cs deleted file mode 100644 index a7f8867f6..000000000 --- a/src/Caliburn.Micro.Core/DebugLog.cs +++ /dev/null @@ -1,53 +0,0 @@ -#define DEBUG - -using System; -using System.Diagnostics; - -namespace Caliburn.Micro -{ - /// - /// A simple logger thats logs everything to the debugger. - /// - public class DebugLog : ILog - { - private readonly string typeName; - - /// - /// Initializes a new instance of the class. - /// - /// The type. - public DebugLog(Type type) - { - typeName = type.FullName; - } - - /// - /// Logs the message as info. - /// - /// A formatted message. - /// Parameters to be injected into the formatted message. - public void Info(string format, params object[] args) - { - Debug.WriteLine("[{1}] INFO: {0}", string.Format(format, args), typeName); - } - - /// - /// Logs the message as a warning. - /// - /// A formatted message. - /// Parameters to be injected into the formatted message. - public void Warn(string format, params object[] args) - { - Debug.WriteLine("[{1}] WARN: {0}", string.Format(format, args), typeName); - } - - /// - /// Logs the exception. - /// - /// The exception. - public void Error(Exception exception) - { - Debug.WriteLine("[{1}] ERROR: {0}", exception, typeName); - } - } -} diff --git a/src/Caliburn.Micro.Core/DefaultCloseStrategy.cs b/src/Caliburn.Micro.Core/DefaultCloseStrategy.cs deleted file mode 100644 index e5d7263f9..000000000 --- a/src/Caliburn.Micro.Core/DefaultCloseStrategy.cs +++ /dev/null @@ -1,57 +0,0 @@ -using System.Collections.Generic; -using System.Threading; -using System.Threading.Tasks; - -namespace Caliburn.Micro -{ - /// - /// Used to gather the results from multiple child elements which may or may not prevent closing. - /// - /// The type of child element. - public class DefaultCloseStrategy : ICloseStrategy - { - readonly bool closeConductedItemsWhenConductorCannotClose; - - /// - /// Creates an instance of the class. - /// - /// Indicates that even if all conducted items are not closable, those that are should be closed. The default is FALSE. - public DefaultCloseStrategy(bool closeConductedItemsWhenConductorCannotClose = false) - { - this.closeConductedItemsWhenConductorCannotClose = closeConductedItemsWhenConductorCannotClose; - } - - /// - public async Task> ExecuteAsync(IEnumerable toClose, CancellationToken cancellationToken = default) - { - var closeable = new List(); - var closeCanOccur = true; - - foreach(var child in toClose) - { - if (child is IGuardClose guard) - { - var canClose = await guard.CanCloseAsync(cancellationToken); - - if (canClose) - { - closeable.Add(child); - } - - closeCanOccur = closeCanOccur && canClose; - } - else - { - closeable.Add(child); - } - } - - if (!this.closeConductedItemsWhenConductorCannotClose && !closeCanOccur) - { - closeable.Clear(); - } - - return new CloseResult(closeCanOccur, closeable); - } - } -} diff --git a/src/Caliburn.Micro.Core/DefaultPlatformProvider.cs b/src/Caliburn.Micro.Core/DefaultPlatformProvider.cs deleted file mode 100644 index de6d215a8..000000000 --- a/src/Caliburn.Micro.Core/DefaultPlatformProvider.cs +++ /dev/null @@ -1,107 +0,0 @@ -using System; -using System.Collections.Generic; -using System.Threading; -using System.Threading.Tasks; - -namespace Caliburn.Micro -{ - /// - /// Default implementation for that does no platform enlightenment. - /// - public class DefaultPlatformProvider : IPlatformProvider - { - /// - /// Indicates whether or not the framework is in design-time mode. - /// - public virtual bool InDesignMode - { - get { return true; } - } - - /// - /// Executes the action on the UI thread asynchronously. - /// - /// The action to execute. - public virtual void BeginOnUIThread(Action action) - { - action(); - } - - /// - /// Executes the action on the UI thread asynchronously. - /// - /// The action to execute. - /// - public virtual Task OnUIThreadAsync(Func action) - { - return Task.Factory.StartNew(action); - } - - /// - /// Executes the action on the UI thread. - /// - /// The action to execute. - public virtual void OnUIThread(Action action) - { - action(); - } - - /// - /// Whether or not classes should execute property change notications on the UI thread. - /// - public virtual bool PropertyChangeNotificationsOnUIThread => true; - - /// - /// Used to retrieve the root, non-framework-created view. - /// - /// The view to search. - /// - /// The root element that was not created by the framework. - /// - /// - /// In certain instances the services create UI elements. - /// For example, if you ask the window manager to show a UserControl as a dialog, it creates a window to host the UserControl in. - /// The WindowManager marks that element as a framework-created element so that it can determine what it created vs. what was intended by the developer. - /// Calling GetFirstNonGeneratedView allows the framework to discover what the original element was. - /// - public virtual object GetFirstNonGeneratedView(object view) - { - return view; - } - - /// - /// Executes the handler the fist time the view is loaded. - /// - /// The view. - /// The handler. - /// true if the handler was executed immediately; false otherwise - public virtual void ExecuteOnFirstLoad(object view, Action handler) - { - handler(view); - } - - /// - /// Executes the handler the next time the view's LayoutUpdated event fires. - /// - /// The view. - /// The handler. - public virtual void ExecuteOnLayoutUpdated(object view, Action handler) - { - handler(view); - } - - /// - /// Get the close action for the specified view model. - /// - /// The view model to close. - /// The associated views. - /// The dialog result. - /// - /// An to close the view model. - /// - public virtual Func GetViewCloseAction(object viewModel, ICollection views, bool? dialogResult) - { - return ct => Task.FromResult(true); - } - } -} diff --git a/src/Caliburn.Micro.Core/DelegateResult.cs b/src/Caliburn.Micro.Core/DelegateResult.cs deleted file mode 100644 index cc7781995..000000000 --- a/src/Caliburn.Micro.Core/DelegateResult.cs +++ /dev/null @@ -1,94 +0,0 @@ -using System; - -namespace Caliburn.Micro -{ - /// - /// A result that executes an . - /// - public class DelegateResult : IResult - { - private readonly Action toExecute; - - /// - /// Initializes a new instance of the class. - /// - /// The action. - public DelegateResult(Action action) - { - toExecute = action; - } - - /// - /// Executes the result using the specified context. - /// - /// The context. - public void Execute(CoroutineExecutionContext context) - { - var eventArgs = new ResultCompletionEventArgs(); - - try - { - toExecute(); - } - catch (Exception ex) - { - eventArgs.Error = ex; - } - - Completed(this, eventArgs); - } - - /// - /// Occurs when execution has completed. - /// - public event EventHandler Completed = delegate { }; - } - - /// - /// A result that executes a - /// - /// The type of the result. - public class DelegateResult : IResult - { - private readonly Func toExecute; - - /// - /// Initializes a new instance of the class. - /// - /// The action. - public DelegateResult(Func action) - { - toExecute = action; - } - - /// - /// Executes the result using the specified context. - /// - /// The context. - public void Execute(CoroutineExecutionContext context) - { - var eventArgs = new ResultCompletionEventArgs(); - - try - { - Result = toExecute(); - } - catch (Exception ex) - { - eventArgs.Error = ex; - } - - Completed(this, eventArgs); - } - - /// - /// Gets the result. - /// - public TResult Result { get; private set; } - - /// - /// Occurs when execution has completed. - /// - public event EventHandler Completed = delegate { }; - } -} diff --git a/src/Caliburn.Micro.Core/EnumerableExtensions.cs b/src/Caliburn.Micro.Core/EnumerableExtensions.cs deleted file mode 100644 index da03243ce..000000000 --- a/src/Caliburn.Micro.Core/EnumerableExtensions.cs +++ /dev/null @@ -1,25 +0,0 @@ -using System; -using System.Collections.Generic; - -namespace Caliburn.Micro -{ - /// - /// Extension methods for - /// - public static class EnumerableExtensions - { - /// - /// Applies the action to each element in the list. - /// - /// The enumerable item's type. - /// The elements to enumerate. - /// The action to apply to each item in the list. - public static void Apply(this IEnumerable enumerable, Action action) - { - foreach (var item in enumerable) - { - action(item); - } - } - } -} diff --git a/src/Caliburn.Micro.Core/EventAggregator.cs b/src/Caliburn.Micro.Core/EventAggregator.cs deleted file mode 100644 index 2143c074a..000000000 --- a/src/Caliburn.Micro.Core/EventAggregator.cs +++ /dev/null @@ -1,170 +0,0 @@ -using System; -using System.Collections.Generic; -using System.Linq; -using System.Reflection; -using System.Threading; -using System.Threading.Tasks; - -namespace Caliburn.Micro -{ - /// - public class EventAggregator : IEventAggregator - { - private readonly List _handlers = new List(); - - /// - public virtual bool HandlerExistsFor(Type messageType) - { - lock (_handlers) - { - return _handlers.Any(handler => handler.Handles(messageType) && !handler.IsDead); - } - } - - /// - public virtual void Subscribe(object subscriber, Func, Task> marshal) - { - if (subscriber == null) - { - throw new ArgumentNullException(nameof(subscriber)); - } - - if (marshal == null) - { - throw new ArgumentNullException(nameof(marshal)); - } - - lock (_handlers) - { - if (_handlers.Any(x => x.Matches(subscriber))) - { - return; - } - - _handlers.Add(new Handler(subscriber, marshal)); - } - } - - /// - public virtual void Unsubscribe(object subscriber) - { - if (subscriber == null) - { - throw new ArgumentNullException(nameof(subscriber)); - } - - lock (_handlers) - { - var found = _handlers.FirstOrDefault(x => x.Matches(subscriber)); - - if (found != null) - { - _handlers.Remove(found); - } - } - } - - /// - public virtual Task PublishAsync(object message, Func, Task> marshal, CancellationToken cancellationToken = default) - { - if (message == null) - { - throw new ArgumentNullException(nameof(message)); - } - - if (marshal == null) - { - throw new ArgumentNullException(nameof(marshal)); - } - - Handler[] toNotify; - - lock (_handlers) - { - toNotify = _handlers.ToArray(); - } - - return marshal(async () => - { - var messageType = message.GetType(); - - var tasks = toNotify.Select(h => h.Handle(messageType, message, cancellationToken)); - - await Task.WhenAll(tasks); - - var dead = toNotify.Where(h => h.IsDead).ToList(); - - if (dead.Any()) - { - lock (_handlers) - { - dead.Apply(x => _handlers.Remove(x)); - } - } - }); - } - - private class Handler - { - private readonly Func, Task> _marshal; - private readonly WeakReference _reference; - private readonly Dictionary _supportedHandlers = new Dictionary(); - - public Handler(object handler, Func, Task> marshal) - { - _marshal = marshal; - _reference = new WeakReference(handler); - - //var interfaces = handler.GetType().GetTypeInfo().ImplementedInterfaces - // .Where(x => typeof(IHandle).GetTypeInfo().IsAssignableFrom(x.GetTypeInfo()) && x.GetTypeInfo().IsGenericType); - - var interfaces = handler.GetType().GetTypeInfo().ImplementedInterfaces - .Where(x => x.GetTypeInfo().IsGenericType && x.GetGenericTypeDefinition() == typeof(IHandle<>)); - - foreach (var @interface in interfaces) - { - var type = @interface.GetTypeInfo().GenericTypeArguments[0]; - var method = @interface.GetRuntimeMethod("HandleAsync", new[] { type, typeof(CancellationToken) }); - - if (method != null) - { - _supportedHandlers[type] = method; - } - } - } - - public bool IsDead => _reference.Target == null; - - public bool Matches(object instance) - { - return _reference.Target == instance; - } - - public Task Handle(Type messageType, object message, CancellationToken cancellationToken) - { - var target = _reference.Target; - - if (target == null) - { - return Task.FromResult(false); - } - - return _marshal(() => - { - var tasks = _supportedHandlers - .Where(handler => handler.Key.GetTypeInfo().IsAssignableFrom(messageType.GetTypeInfo())) - .Select(pair => pair.Value.Invoke(target, new[] { message, cancellationToken })) - .Select(result => (Task)result) - .ToList(); - - return Task.WhenAll(tasks); - }); - } - - public bool Handles(Type messageType) - { - return _supportedHandlers.Any(pair => pair.Key.GetTypeInfo().IsAssignableFrom(messageType.GetTypeInfo())); - } - } - } -} diff --git a/src/Caliburn.Micro.Core/EventAggregator/Contracts/IEventAggregator.cs b/src/Caliburn.Micro.Core/EventAggregator/Contracts/IEventAggregator.cs new file mode 100644 index 000000000..564a7757d --- /dev/null +++ b/src/Caliburn.Micro.Core/EventAggregator/Contracts/IEventAggregator.cs @@ -0,0 +1,40 @@ +using System; +using System.Threading; +using System.Threading.Tasks; + +namespace Caliburn.Micro; + +/// +/// Enables loosely-coupled publication of and subscription to events. +/// +public interface IEventAggregator { + /// + /// Searches the subscribed handlers to check if we have a handler for + /// the message type supplied. + /// + /// The message type to check with. + /// True if any handler is found, false if not. + bool HandlerExistsFor(Type messageType); + + /// + /// Subscribes an instance to all events declared through implementations of . + /// + /// The instance to subscribe for event publication. + /// Allows the subscriber to provide a custom thread marshaller for the message subscription. + void Subscribe(object subscriber, Func, Task> marshal); + + /// + /// Unsubscribes the instance from all events. + /// + /// The instance to unsubscribe. + void Unsubscribe(object subscriber); + + /// + /// Publishes a message. + /// + /// The message instance. + /// Allows the publisher to provide a custom thread marshaller for the message publication. + /// The cancellation token to cancel operation. + /// A task that represents the asynchronous operation. + Task PublishAsync(object message, Func, Task> marshal, CancellationToken cancellationToken = default); +} diff --git a/src/Caliburn.Micro.Core/EventAggregator/Contracts/IHandle.cs b/src/Caliburn.Micro.Core/EventAggregator/Contracts/IHandle.cs new file mode 100644 index 000000000..311dccf5b --- /dev/null +++ b/src/Caliburn.Micro.Core/EventAggregator/Contracts/IHandle.cs @@ -0,0 +1,19 @@ +using System.Threading; +using System.Threading.Tasks; + +namespace Caliburn.Micro; + +/// +/// Denotes a class which can handle a particular type of message. +/// +/// The type of message to handle. +// ReSharper disable once TypeParameterCanBeVariant +public interface IHandle { + /// + /// Handles the message. + /// + /// The message. + /// A cancellation token that can be used by other objects or threads to receive notice of cancellation. + /// A task that represents the asynchronous coroutine. + Task HandleAsync(TMessage message, CancellationToken cancellationToken); +} diff --git a/src/Caliburn.Micro.Core/EventAggregator/EventAggregator.cs b/src/Caliburn.Micro.Core/EventAggregator/EventAggregator.cs new file mode 100644 index 000000000..be33684db --- /dev/null +++ b/src/Caliburn.Micro.Core/EventAggregator/EventAggregator.cs @@ -0,0 +1,138 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Reflection; +using System.Threading; +using System.Threading.Tasks; + +namespace Caliburn.Micro; + +/// +public class EventAggregator : IEventAggregator { + private readonly List _handlers = new(); + + /// + public virtual bool HandlerExistsFor(Type messageType) { + lock (_handlers) { + return _handlers.Any(handler => handler.Handles(messageType) && !handler.IsDead); + } + } + + /// + public virtual void Subscribe(object subscriber, Func, Task> marshal) { + if (subscriber == null) { + throw new ArgumentNullException(nameof(subscriber)); + } + + if (marshal == null) { + throw new ArgumentNullException(nameof(marshal)); + } + + lock (_handlers) { + if (_handlers.Any(x => x.Matches(subscriber))) { + return; + } + + _handlers.Add(new Handler(subscriber, marshal)); + } + } + + /// + public virtual void Unsubscribe(object subscriber) { + if (subscriber == null) { + throw new ArgumentNullException(nameof(subscriber)); + } + + lock (_handlers) { + Handler found = _handlers.FirstOrDefault(x => x.Matches(subscriber)); + if (found != null) { + _handlers.Remove(found); + } + } + } + + /// + public virtual Task PublishAsync(object message, Func, Task> marshal, CancellationToken cancellationToken = default) { + if (message == null) { + throw new ArgumentNullException(nameof(message)); + } + + if (marshal == null) { + throw new ArgumentNullException(nameof(marshal)); + } + + Handler[] toNotify; + + lock (_handlers) { + toNotify = _handlers.ToArray(); + } + + return marshal(async () => { + Type messageType = message.GetType(); + + IEnumerable tasks = toNotify.Select(h => h.Handle(messageType, message, cancellationToken)); + + await Task.WhenAll(tasks); + + var dead = toNotify.Where(h => h.IsDead).ToList(); + + if (dead.Any()) { + lock (_handlers) { + dead.Apply(x => _handlers.Remove(x)); + } + } + }); + } + + private sealed class Handler { + private readonly Func, Task> _marshal; + private readonly WeakReference _reference; + private readonly Dictionary _supportedHandlers = new(); + + public Handler(object handler, Func, Task> marshal) { + _marshal = marshal; + _reference = new WeakReference(handler); + + IEnumerable interfaces + = handler + .GetType() + .GetTypeInfo().ImplementedInterfaces + .Where(x => x.GetTypeInfo().IsGenericType && + x.GetGenericTypeDefinition() == typeof(IHandle<>)); + foreach (Type @interface in interfaces) { + Type type = @interface.GetTypeInfo().GenericTypeArguments[0]; + MethodInfo method = @interface.GetRuntimeMethod("HandleAsync", new[] { type, typeof(CancellationToken) }); + if (method == null) { + continue; + } + + _supportedHandlers[type] = method; + } + } + + public bool IsDead + => _reference.Target == null; + + public bool Matches(object instance) + => _reference.Target.Equals(instance); + + public bool Handles(Type messageType) + => _supportedHandlers.Any(pair => pair.Key.GetTypeInfo().IsAssignableFrom(messageType.GetTypeInfo())); + + public Task Handle(Type messageType, object message, CancellationToken cancellationToken) { + object target = _reference.Target; + + return target == null + ? Task.FromResult(false) + : _marshal(() => { + var tasks = _supportedHandlers + .Where(handler => handler.Key.GetTypeInfo().IsAssignableFrom(messageType.GetTypeInfo())) + .Select(pair => pair.Value.Invoke(target, new[] { message, cancellationToken })) + .Select(result => (Task)result) + .ToList(); + + return Task.WhenAll(tasks); + }); + } + } +} diff --git a/src/Caliburn.Micro.Core/EventAggregator/Extensions/EventAggregatorExtensions.cs b/src/Caliburn.Micro.Core/EventAggregator/Extensions/EventAggregatorExtensions.cs new file mode 100644 index 000000000..e78c548fa --- /dev/null +++ b/src/Caliburn.Micro.Core/EventAggregator/Extensions/EventAggregatorExtensions.cs @@ -0,0 +1,139 @@ +using System; +using System.Threading; +using System.Threading.Tasks; + +namespace Caliburn.Micro; + +/// +/// Extensions for . +/// +public static class EventAggregatorExtensions { + /// + /// Subscribes an instance to all events declared through implementations of . + /// + /// The subscription is invoked on the thread chosen by the publisher. + /// The EventAggregator. + /// The instance to subscribe for event publication. + public static void SubscribeOnPublishedThread(this IEventAggregator eventAggregator, object subscriber) + => eventAggregator.Subscribe(subscriber, f => f()); + + /// + /// Subscribes an instance to all events declared through implementations of . + /// + /// The subscription is invoked on the thread chosen by the publisher. + /// The event aggregator. + /// The instance to subscribe for event publication. + [Obsolete("Use SubscribeOnPublishedThread")] + public static void Subscribe(this IEventAggregator eventAggregator, object subscriber) + => eventAggregator.SubscribeOnPublishedThread(subscriber); + + /// + /// Subscribes an instance to all events declared through implementations of . + /// + /// The subscription is invoked on a new background thread. + /// The event aggregator. + /// The instance to subscribe for event publication. + public static void SubscribeOnBackgroundThread(this IEventAggregator eventAggregator, object subscriber) + => eventAggregator.Subscribe(subscriber, f => Task.Factory.StartNew(f, default, TaskCreationOptions.None, TaskScheduler.Default)); + + /// + /// Subscribes an instance to all events declared through implementations of . + /// + /// The subscription is invoked on the UI thread. + /// The event aggregator. + /// The instance to subscribe for event publication. + public static void SubscribeOnUIThread(this IEventAggregator eventAggregator, object subscriber) + => eventAggregator.Subscribe( + subscriber, + f => { + var taskCompletionSource = new TaskCompletionSource(); + Execute.BeginOnUIThread(async () => { + try { + await f(); + + taskCompletionSource.SetResult(true); + } catch (OperationCanceledException) { + taskCompletionSource.SetCanceled(); + } catch (Exception ex) { + taskCompletionSource.SetException(ex); + } + }); + + return taskCompletionSource.Task; + }); + + /// + /// Publishes a message on the current thread (synchrone). + /// + /// The event aggregator. + /// The message instance. + /// A cancellation token that can be used by other objects or threads to receive notice of cancellation. + /// A task that represents the asynchronous operation. + public static Task PublishOnCurrentThreadAsync(this IEventAggregator eventAggregator, object message, CancellationToken cancellationToken) + => eventAggregator.PublishAsync(message, f => f(), cancellationToken); + + /// + /// Publishes a message on the current thread (synchrone). + /// + /// The event aggregator. + /// The message instance. + /// A task that represents the asynchronous operation. + public static Task PublishOnCurrentThreadAsync(this IEventAggregator eventAggregator, object message) + => eventAggregator.PublishOnCurrentThreadAsync(message, default); + + /// + /// Publishes a message on a background thread (async). + /// + /// The event aggregator. + /// The message instance. + /// A cancellation token that can be used by other objects or threads to receive notice of cancellation. + /// A task that represents the asynchronous operation. + public static Task PublishOnBackgroundThreadAsync(this IEventAggregator eventAggregator, object message, CancellationToken cancellationToken) + => eventAggregator.PublishAsync(message, f => Task.Factory.StartNew(f, default, TaskCreationOptions.None, TaskScheduler.Default), cancellationToken); + + /// + /// Publishes a message on a background thread (async). + /// + /// The event aggregator. + /// The message instance. + /// A task that represents the asynchronous operation. + public static Task PublishOnBackgroundThreadAsync(this IEventAggregator eventAggregator, object message) + => eventAggregator.PublishOnBackgroundThreadAsync(message, default); + + /// + /// Publishes a message on the UI thread. + /// + /// The event aggregator. + /// The message instance. + /// A task that represents the asynchronous operation. + public static Task PublishOnUIThreadAsync(this IEventAggregator eventAggregator, object message) + => eventAggregator.PublishOnUIThreadAsync(message, default); + + /// + /// Publishes a message on the UI thread. + /// + /// The event aggregator. + /// The message instance. + /// A cancellation token that can be used by other objects or threads to receive notice of cancellation. + /// A task that represents the asynchronous operation. + public static Task PublishOnUIThreadAsync(this IEventAggregator eventAggregator, object message, CancellationToken cancellationToken) + => eventAggregator.PublishAsync( + message, + f => { + var taskCompletionSource = new TaskCompletionSource(); + Execute.BeginOnUIThread(async () => { + try { + await f(); + + taskCompletionSource.SetResult(true); + } catch (OperationCanceledException) { + taskCompletionSource.SetCanceled(); + } catch (Exception ex) { + taskCompletionSource.SetException(ex); + } + }); + + return taskCompletionSource.Task; + }, + cancellationToken); +} diff --git a/src/Caliburn.Micro.Core/EventAggregatorExtensions.cs b/src/Caliburn.Micro.Core/EventAggregatorExtensions.cs deleted file mode 100644 index f920275ed..000000000 --- a/src/Caliburn.Micro.Core/EventAggregatorExtensions.cs +++ /dev/null @@ -1,174 +0,0 @@ -using System; -using System.Threading; -using System.Threading.Tasks; - -namespace Caliburn.Micro -{ - /// - /// Extensions for . - /// - public static class EventAggregatorExtensions - { - /// - /// Subscribes an instance to all events declared through implementations of . - /// - /// The subscription is invoked on the thread chosen by the publisher. - /// - /// The instance to subscribe for event publication. - public static void SubscribeOnPublishedThread(this IEventAggregator eventAggregator, object subscriber) - { - eventAggregator.Subscribe(subscriber, f => f()); - } - - /// - /// Subscribes an instance to all events declared through implementations of . - /// - /// The subscription is invoked on the thread chosen by the publisher. - /// - /// The instance to subscribe for event publication. - [Obsolete("Use SubscribeOnPublishedThread")] - public static void Subscribe(this IEventAggregator eventAggregator, object subscriber) - { - eventAggregator.SubscribeOnPublishedThread(subscriber); - } - - /// - /// Subscribes an instance to all events declared through implementations of . - /// - /// The subscription is invoked on a new background thread. - /// - /// The instance to subscribe for event publication. - public static void SubscribeOnBackgroundThread(this IEventAggregator eventAggregator, object subscriber) - { - eventAggregator.Subscribe(subscriber, f => Task.Factory.StartNew(f, default, TaskCreationOptions.None, TaskScheduler.Default)); - } - - /// - /// Subscribes an instance to all events declared through implementations of . - /// - /// The subscription is invoked on the UI thread. - /// - /// The instance to subscribe for event publication. - public static void SubscribeOnUIThread(this IEventAggregator eventAggregator, object subscriber) - { - eventAggregator.Subscribe(subscriber, f => - { - var taskCompletionSource = new TaskCompletionSource(); - - Execute.BeginOnUIThread(async () => - { - try - { - await f(); - - taskCompletionSource.SetResult(true); - } - catch (OperationCanceledException) - { - taskCompletionSource.SetCanceled(); - } - catch (Exception ex) - { - taskCompletionSource.SetException(ex); - } - }); - - return taskCompletionSource.Task; - - }); - } - - /// - /// Publishes a message on the current thread (synchrone). - /// - /// The event aggregator. - /// The message instance. - /// A cancellation token that can be used by other objects or threads to receive notice of cancellation. - /// A task that represents the asynchronous operation. - public static Task PublishOnCurrentThreadAsync(this IEventAggregator eventAggregator, object message, CancellationToken cancellationToken) - { - return eventAggregator.PublishAsync(message, f => f(), cancellationToken); - } - - /// - /// Publishes a message on the current thread (synchrone). - /// - /// The event aggregator. - /// The message instance. - /// A task that represents the asynchronous operation. - public static Task PublishOnCurrentThreadAsync(this IEventAggregator eventAggregator, object message) - { - return eventAggregator.PublishOnCurrentThreadAsync(message, default); - } - - /// - /// Publishes a message on a background thread (async). - /// - /// The event aggregator. - /// The message instance. - /// A cancellation token that can be used by other objects or threads to receive notice of cancellation. - /// A task that represents the asynchronous operation. - public static Task PublishOnBackgroundThreadAsync(this IEventAggregator eventAggregator, object message, CancellationToken cancellationToken) - { - return eventAggregator.PublishAsync(message, f => Task.Factory.StartNew(f, default, TaskCreationOptions.None, TaskScheduler.Default), cancellationToken); - } - - /// - /// Publishes a message on a background thread (async). - /// - /// The event aggregator. - /// The message instance. - /// A task that represents the asynchronous operation. - public static Task PublishOnBackgroundThreadAsync(this IEventAggregator eventAggregator, object message) - { - return eventAggregator.PublishOnBackgroundThreadAsync(message, default); - } - - /// - /// Publishes a message on the UI thread. - /// - /// The event aggregator. - /// The message instance. - /// A cancellation token that can be used by other objects or threads to receive notice of cancellation. - /// A task that represents the asynchronous operation. - public static Task PublishOnUIThreadAsync(this IEventAggregator eventAggregator, object message, CancellationToken cancellationToken) - { - return eventAggregator.PublishAsync(message, f => - { - var taskCompletionSource = new TaskCompletionSource(); - - Execute.BeginOnUIThread(async () => - { - try - { - await f(); - - taskCompletionSource.SetResult(true); - } - catch (OperationCanceledException) - { - taskCompletionSource.SetCanceled(); - } - catch (Exception ex) - { - taskCompletionSource.SetException(ex); - } - }); - - return taskCompletionSource.Task; - - }, cancellationToken); - } - - /// - /// Publishes a message on the UI thread. - /// - /// The event aggregator. - /// The message instance. - /// A task that represents the asynchronous operation. - public static Task PublishOnUIThreadAsync(this IEventAggregator eventAggregator, object message) - { - return eventAggregator.PublishOnUIThreadAsync(message, default); - } - } -} diff --git a/src/Caliburn.Micro.Core/Execute.cs b/src/Caliburn.Micro.Core/Execute.cs deleted file mode 100644 index 8aec3a143..000000000 --- a/src/Caliburn.Micro.Core/Execute.cs +++ /dev/null @@ -1,49 +0,0 @@ -using System; -using System.Threading.Tasks; - -namespace Caliburn.Micro -{ - /// - /// Enables easy marshalling of code to the UI thread. - /// - public static class Execute - { - /// - /// Indicates whether or not the framework is in design-time mode. - /// - public static bool InDesignMode - { - get - { - return PlatformProvider.Current.InDesignMode; - } - } - - /// - /// Executes the action on the UI thread asynchronously. - /// - /// The action to execute. - public static void BeginOnUIThread(this Action action) - { - PlatformProvider.Current.BeginOnUIThread(action); - } - - /// - /// Executes the action on the UI thread asynchronously. - /// - /// The action to execute. - public static Task OnUIThreadAsync(this Func action) - { - return PlatformProvider.Current.OnUIThreadAsync(action); - } - - /// - /// Executes the action on the UI thread. - /// - /// The action to execute. - public static void OnUIThread(this Action action) - { - PlatformProvider.Current.OnUIThread(action); - } - } -} diff --git a/src/Caliburn.Micro.Core/ExpressionExtensions.cs b/src/Caliburn.Micro.Core/ExpressionExtensions.cs deleted file mode 100644 index 047202d9e..000000000 --- a/src/Caliburn.Micro.Core/ExpressionExtensions.cs +++ /dev/null @@ -1,34 +0,0 @@ -using System.Linq.Expressions; -using System.Reflection; - -namespace Caliburn.Micro -{ - /// - /// Extension for . - /// - public static class ExpressionExtensions - { - /// - /// Converts an expression into a . - /// - /// The expression to convert. - /// The member info. - public static MemberInfo GetMemberInfo(this Expression expression) - { - var lambda = (LambdaExpression)expression; - - MemberExpression memberExpression; - if (lambda.Body is UnaryExpression) - { - var unaryExpression = (UnaryExpression)lambda.Body; - memberExpression = (MemberExpression)unaryExpression.Operand; - } - else - { - memberExpression = (MemberExpression)lambda.Body; - } - - return memberExpression.Member; - } - } -} diff --git a/src/Caliburn.Micro.Core/Extensions/EnumerableExtensions.cs b/src/Caliburn.Micro.Core/Extensions/EnumerableExtensions.cs new file mode 100644 index 000000000..d718092f4 --- /dev/null +++ b/src/Caliburn.Micro.Core/Extensions/EnumerableExtensions.cs @@ -0,0 +1,21 @@ +using System; +using System.Collections.Generic; + +namespace Caliburn.Micro; + +/// +/// Extension methods for . +/// +public static class EnumerableExtensions { + /// + /// Applies the action to each element in the list. + /// + /// The enumerable item's type. + /// The elements to enumerate. + /// The action to apply to each item in the list. + public static void Apply(this IEnumerable enumerable, Action action) { + foreach (T item in enumerable) { + action(item); + } + } +} diff --git a/src/Caliburn.Micro.Core/Extensions/Execute.cs b/src/Caliburn.Micro.Core/Extensions/Execute.cs new file mode 100644 index 000000000..2e87f35db --- /dev/null +++ b/src/Caliburn.Micro.Core/Extensions/Execute.cs @@ -0,0 +1,36 @@ +using System; +using System.Threading.Tasks; + +namespace Caliburn.Micro; + +/// +/// Enables easy marshalling of code to the UI thread. +/// +public static class Execute { + /// + /// Gets a value indicating whether or not the framework is in design-time mode. + /// + public static bool InDesignMode + => PlatformProvider.Current.InDesignMode; + + /// + /// Executes the action on the UI thread asynchronously. + /// + /// The action to execute. + public static void BeginOnUIThread(this Action action) + => PlatformProvider.Current.BeginOnUIThread(action); + + /// + /// Executes the action on the UI thread asynchronously. + /// + /// The action to execute. + public static Task OnUIThreadAsync(this Func action) + => PlatformProvider.Current.OnUIThreadAsync(action); + + /// + /// Executes the action on the UI thread. + /// + /// The action to execute. + public static void OnUIThread(this Action action) + => PlatformProvider.Current.OnUIThread(action); +} diff --git a/src/Caliburn.Micro.Core/Extensions/ExpressionExtensions.cs b/src/Caliburn.Micro.Core/Extensions/ExpressionExtensions.cs new file mode 100644 index 000000000..d49b3f4b3 --- /dev/null +++ b/src/Caliburn.Micro.Core/Extensions/ExpressionExtensions.cs @@ -0,0 +1,24 @@ +using System.Linq.Expressions; +using System.Reflection; + +namespace Caliburn.Micro; + +/// +/// Extension for . +/// +public static class ExpressionExtensions { + /// + /// Converts an expression into a . + /// + /// The expression to convert. + /// The member info. + public static MemberInfo GetMemberInfo(this Expression expression) { + var lambda = (LambdaExpression)expression; + MemberExpression memberExpression + = lambda.Body is UnaryExpression unaryExpression + ? (MemberExpression)unaryExpression.Operand + : (MemberExpression)lambda.Body; + + return memberExpression.Member; + } +} diff --git a/src/Caliburn.Micro.Core/IActivate.cs b/src/Caliburn.Micro.Core/IActivate.cs deleted file mode 100644 index d59318d97..000000000 --- a/src/Caliburn.Micro.Core/IActivate.cs +++ /dev/null @@ -1,29 +0,0 @@ -using System; -using System.Threading; -using System.Threading.Tasks; - -namespace Caliburn.Micro -{ - /// - /// Denotes an instance which requires activation. - /// - public interface IActivate - { - /// - /// Indicates whether or not this instance is active. - /// - bool IsActive { get; } - - /// - /// Activates this instance. - /// - /// The cancellation token to cancel operation. - /// A task that represents the asynchronous operation. - Task ActivateAsync(CancellationToken cancellationToken = default); - - /// - /// Raised after activation occurs. - /// - event AsyncEventHandler Activated; - } -} diff --git a/src/Caliburn.Micro.Core/IChild.cs b/src/Caliburn.Micro.Core/IChild.cs deleted file mode 100644 index 8081dab0b..000000000 --- a/src/Caliburn.Micro.Core/IChild.cs +++ /dev/null @@ -1,25 +0,0 @@ -namespace Caliburn.Micro -{ - /// - /// Denotes a node within a parent/child hierarchy. - /// - public interface IChild - { - /// - /// Gets or Sets the Parent - /// - object Parent { get; set; } - } - - /// - /// Denotes a node within a parent/child hierarchy. - /// - /// The type of parent. - public interface IChild : IChild - { - /// - /// Gets or Sets the Parent - /// - new TParent Parent { get; set; } - } -} diff --git a/src/Caliburn.Micro.Core/IClose.cs b/src/Caliburn.Micro.Core/IClose.cs deleted file mode 100644 index e7fef0801..000000000 --- a/src/Caliburn.Micro.Core/IClose.cs +++ /dev/null @@ -1,17 +0,0 @@ -using System.Threading.Tasks; - -namespace Caliburn.Micro -{ - /// - /// Denotes an object that can be closed. - /// - public interface IClose - { - /// - /// Tries to close this instance. - /// Also provides an opportunity to pass a dialog result to it's corresponding view. - /// - /// The dialog result. - Task TryCloseAsync(bool? dialogResult = null); - } -} diff --git a/src/Caliburn.Micro.Core/ICloseResult.cs b/src/Caliburn.Micro.Core/ICloseResult.cs deleted file mode 100644 index c165b4a81..000000000 --- a/src/Caliburn.Micro.Core/ICloseResult.cs +++ /dev/null @@ -1,20 +0,0 @@ -using System.Collections.Generic; - -namespace Caliburn.Micro -{ - /// - /// Results from the close strategy. - /// - public interface ICloseResult - { - /// - /// Indicates which children shbould close if the parent cannot. - /// - IEnumerable Children { get; } - - /// - /// Indicates whether a close can occur - /// - bool CloseCanOccur { get; } - } -} diff --git a/src/Caliburn.Micro.Core/ICloseStrategy.cs b/src/Caliburn.Micro.Core/ICloseStrategy.cs deleted file mode 100644 index 4d365d0ad..000000000 --- a/src/Caliburn.Micro.Core/ICloseStrategy.cs +++ /dev/null @@ -1,22 +0,0 @@ -using System; -using System.Collections.Generic; -using System.Threading; -using System.Threading.Tasks; - -namespace Caliburn.Micro -{ - /// - /// Used to gather the results from multiple child elements which may or may not prevent closing. - /// - /// The type of child element. - public interface ICloseStrategy - { - /// - /// Executes the strategy. - /// - /// Items that are requesting close. - /// The cancellation token to cancel operation. - /// A task that represents the asynchronous operation and contains the result of the strategy. - Task> ExecuteAsync(IEnumerable toClose, CancellationToken cancellationToken = default); - } -} diff --git a/src/Caliburn.Micro.Core/IConductor.cs b/src/Caliburn.Micro.Core/IConductor.cs deleted file mode 100644 index ce0c7666e..000000000 --- a/src/Caliburn.Micro.Core/IConductor.cs +++ /dev/null @@ -1,41 +0,0 @@ -using System; -using System.Threading; -using System.Threading.Tasks; - -namespace Caliburn.Micro -{ - /// - /// Denotes an instance which conducts other objects by managing an ActiveItem and maintaining a strict lifecycle. - /// - /// Conducted instances can optin to the lifecycle by impelenting any of the follosing , , . - public interface IConductor : IParent, INotifyPropertyChangedEx - { - /// - /// Activates the specified item. - /// - /// The item to activate. - /// A cancellation token that can be used by other objects or threads to receive notice of cancellation. - Task ActivateItemAsync(object item, CancellationToken cancellationToken = default); - - /// - /// Deactivates the specified item. - /// - /// The item to close. - /// Indicates whether or not to close the item after deactivating it. - /// The cancellation token to cancel operation. - /// A task that represents the asynchronous operation. - Task DeactivateItemAsync(object item, bool close, CancellationToken cancellationToken = default); - - /// - /// Occurs when an activation request is processed. - /// - event EventHandler ActivationProcessed; - } - - /// - /// An that also implements . - /// - public interface IConductActiveItem : IConductor, IHaveActiveItem - { - } -} diff --git a/src/Caliburn.Micro.Core/IDeactivate.cs b/src/Caliburn.Micro.Core/IDeactivate.cs deleted file mode 100644 index 3c99f4b79..000000000 --- a/src/Caliburn.Micro.Core/IDeactivate.cs +++ /dev/null @@ -1,30 +0,0 @@ -using System; -using System.Threading; -using System.Threading.Tasks; - -namespace Caliburn.Micro -{ - /// - /// Denotes an instance which requires deactivation. - /// - public interface IDeactivate - { - /// - /// Raised before deactivation. - /// - event EventHandler AttemptingDeactivation; - - /// - /// Deactivates this instance. - /// - /// Indicates whether or not this instance is being closed. - /// The cancellation token to cancel operation. - /// A task that represents the asynchronous operation. - Task DeactivateAsync(bool close, CancellationToken cancellationToken = default); - - /// - /// Raised after deactivation. - /// - event AsyncEventHandler Deactivated; - } -} diff --git a/src/Caliburn.Micro.Core/IEventAggregator.cs b/src/Caliburn.Micro.Core/IEventAggregator.cs deleted file mode 100644 index 832cbda92..000000000 --- a/src/Caliburn.Micro.Core/IEventAggregator.cs +++ /dev/null @@ -1,42 +0,0 @@ -using System; -using System.Threading; -using System.Threading.Tasks; - -namespace Caliburn.Micro -{ - /// - /// Enables loosely-coupled publication of and subscription to events. - /// - public interface IEventAggregator - { - /// - /// Searches the subscribed handlers to check if we have a handler for - /// the message type supplied. - /// - /// The message type to check with - /// True if any handler is found, false if not. - bool HandlerExistsFor(Type messageType); - - /// - /// Subscribes an instance to all events declared through implementations of - /// - /// The instance to subscribe for event publication. - /// Allows the subscriber to provide a custom thread marshaller for the message subscription. - void Subscribe(object subscriber, Func, Task> marshal); - - /// - /// Unsubscribes the instance from all events. - /// - /// The instance to unsubscribe. - void Unsubscribe(object subscriber); - - /// - /// Publishes a message. - /// - /// The message instance. - /// Allows the publisher to provide a custom thread marshaller for the message publication. - /// The cancellation token to cancel operation. - /// A task that represents the asynchronous operation. - Task PublishAsync(object message, Func, Task> marshal, CancellationToken cancellationToken = default); - } -} diff --git a/src/Caliburn.Micro.Core/IGuardClose.cs b/src/Caliburn.Micro.Core/IGuardClose.cs deleted file mode 100644 index 286651c6a..000000000 --- a/src/Caliburn.Micro.Core/IGuardClose.cs +++ /dev/null @@ -1,18 +0,0 @@ -using System.Threading; -using System.Threading.Tasks; - -namespace Caliburn.Micro -{ - /// - /// Denotes an instance which may prevent closing. - /// - public interface IGuardClose : IClose - { - /// - /// Called to check whether or not this instance can close. - /// - /// The cancellation token to cancel operation. - /// A task that represents the asynchronous operation and contains the result of the close. - Task CanCloseAsync(CancellationToken cancellationToken = default); - } -} diff --git a/src/Caliburn.Micro.Core/IHandle.cs b/src/Caliburn.Micro.Core/IHandle.cs deleted file mode 100644 index 5c3f32399..000000000 --- a/src/Caliburn.Micro.Core/IHandle.cs +++ /dev/null @@ -1,22 +0,0 @@ -using System.Threading; -using System.Threading.Tasks; - -namespace Caliburn.Micro -{ - /// - /// Denotes a class which can handle a particular type of message. - /// - /// The type of message to handle. - // ReSharper disable once TypeParameterCanBeVariant - public interface IHandle - { - /// - /// Handles the message. - /// - /// The message. - /// A cancellation token that can be used by other objects or threads to receive notice of cancellation. - /// A task that represents the asynchronous coroutine. - Task HandleAsync(TMessage message, CancellationToken cancellationToken); - - } -} diff --git a/src/Caliburn.Micro.Core/IHaveActiveItem.cs b/src/Caliburn.Micro.Core/IHaveActiveItem.cs deleted file mode 100644 index 468811b82..000000000 --- a/src/Caliburn.Micro.Core/IHaveActiveItem.cs +++ /dev/null @@ -1,13 +0,0 @@ -namespace Caliburn.Micro -{ - /// - /// Denotes an instance which maintains an active item. - /// - public interface IHaveActiveItem - { - /// - /// The currently active item. - /// - object ActiveItem { get; set; } - } -} diff --git a/src/Caliburn.Micro.Core/IHaveDisplayName.cs b/src/Caliburn.Micro.Core/IHaveDisplayName.cs deleted file mode 100644 index 563ba9241..000000000 --- a/src/Caliburn.Micro.Core/IHaveDisplayName.cs +++ /dev/null @@ -1,13 +0,0 @@ -namespace Caliburn.Micro -{ - /// - /// Denotes an instance which has a display name. - /// - public interface IHaveDisplayName - { - /// - /// Gets or Sets the Display Name - /// - string DisplayName { get; set; } - } -} diff --git a/src/Caliburn.Micro.Core/ILog.cs b/src/Caliburn.Micro.Core/ILog.cs deleted file mode 100644 index d23b5719d..000000000 --- a/src/Caliburn.Micro.Core/ILog.cs +++ /dev/null @@ -1,30 +0,0 @@ -using System; - -namespace Caliburn.Micro -{ - /// - /// A logger. - /// - public interface ILog - { - /// - /// Logs the message as info. - /// - /// A formatted message. - /// Parameters to be injected into the formatted message. - void Info(string format, params object[] args); - - /// - /// Logs the message as a warning. - /// - /// A formatted message. - /// Parameters to be injected into the formatted message. - void Warn(string format, params object[] args); - - /// - /// Logs the exception. - /// - /// The exception. - void Error(Exception exception); - } -} diff --git a/src/Caliburn.Micro.Core/INotifyPropertyChangedEx.cs b/src/Caliburn.Micro.Core/INotifyPropertyChangedEx.cs deleted file mode 100644 index 93c5960ed..000000000 --- a/src/Caliburn.Micro.Core/INotifyPropertyChangedEx.cs +++ /dev/null @@ -1,26 +0,0 @@ -using System.ComponentModel; - -namespace Caliburn.Micro -{ - /// - /// Extends such that the change event can be raised by external parties. - /// - public interface INotifyPropertyChangedEx : INotifyPropertyChanged - { - /// - /// Enables/Disables property change notification. - /// - bool IsNotifying { get; set; } - - /// - /// Notifies subscribers of the property change. - /// - /// Name of the property. - void NotifyOfPropertyChange(string propertyName); - - /// - /// Raises a change notification indicating that all bindings should be refreshed. - /// - void Refresh(); - } -} diff --git a/src/Caliburn.Micro.Core/IObservableCollection.cs b/src/Caliburn.Micro.Core/IObservableCollection.cs deleted file mode 100644 index 1f1af4870..000000000 --- a/src/Caliburn.Micro.Core/IObservableCollection.cs +++ /dev/null @@ -1,24 +0,0 @@ -using System.Collections.Generic; -using System.Collections.Specialized; - -namespace Caliburn.Micro -{ - /// - /// Represents a collection that is observable. - /// - /// The type of elements contained in the collection. - public interface IObservableCollection : IList, INotifyPropertyChangedEx, INotifyCollectionChanged - { - /// - /// Adds the range. - /// - /// The items. - void AddRange(IEnumerable items); - - /// - /// Removes the range. - /// - /// The items. - void RemoveRange(IEnumerable items); - } -} diff --git a/src/Caliburn.Micro.Core/IParent.cs b/src/Caliburn.Micro.Core/IParent.cs deleted file mode 100644 index a4f997ad5..000000000 --- a/src/Caliburn.Micro.Core/IParent.cs +++ /dev/null @@ -1,34 +0,0 @@ -using System.Collections; -using System.Collections.Generic; - -namespace Caliburn.Micro -{ - /// - /// Interface used to define an object associated to a collection of children. - /// - public interface IParent - { - /// - /// Gets the children. - /// - /// - /// The collection of children. - /// - IEnumerable GetChildren(); - } - - /// - /// Interface used to define a specialized parent. - /// - /// The type of children. - public interface IParent : IParent - { - /// - /// Gets the children. - /// - /// - /// The collection of children. - /// - new IEnumerable GetChildren(); - } -} diff --git a/src/Caliburn.Micro.Core/IPlatformProvider.cs b/src/Caliburn.Micro.Core/IPlatformProvider.cs deleted file mode 100644 index e314efc9e..000000000 --- a/src/Caliburn.Micro.Core/IPlatformProvider.cs +++ /dev/null @@ -1,76 +0,0 @@ -using System; -using System.Collections.Generic; -using System.Threading; -using System.Threading.Tasks; - -namespace Caliburn.Micro -{ - /// - /// Interface for platform specific operations that need enlightenment. - /// - public interface IPlatformProvider - { - /// - /// Indicates whether or not the framework is in design-time mode. - /// - bool InDesignMode { get; } - - /// - /// Whether or not classes should execute property change notications on the UI thread. - /// - bool PropertyChangeNotificationsOnUIThread { get; } - - /// - /// Executes the action on the UI thread asynchronously. - /// - /// The action to execute. - void BeginOnUIThread(Action action); - - /// - /// Executes the action on the UI thread asynchronously. - /// - /// The action to execute. - Task OnUIThreadAsync(Func action); - - /// - /// Executes the action on the UI thread. - /// - /// The action to execute. - void OnUIThread(Action action); - - /// - /// Used to retrieve the root, non-framework-created view. - /// - /// The view to search. - /// The root element that was not created by the framework. - /// In certain instances the services create UI elements. - /// For example, if you ask the window manager to show a UserControl as a dialog, it creates a window to host the UserControl in. - /// The WindowManager marks that element as a framework-created element so that it can determine what it created vs. what was intended by the developer. - /// Calling GetFirstNonGeneratedView allows the framework to discover what the original element was. - /// - object GetFirstNonGeneratedView(object view); - - /// - /// Executes the handler the fist time the view is loaded. - /// - /// The view. - /// The handler. - void ExecuteOnFirstLoad(object view, Action handler); - - /// - /// Executes the handler the next time the view's LayoutUpdated event fires. - /// - /// The view. - /// The handler. - void ExecuteOnLayoutUpdated(object view, Action handler); - - /// - /// Get the close action for the specified view model. - /// - /// The view model to close. - /// The associated views. - /// The dialog result. - /// An to close the view model. - Func GetViewCloseAction(object viewModel, ICollection views, bool? dialogResult); - } -} diff --git a/src/Caliburn.Micro.Core/IResult.cs b/src/Caliburn.Micro.Core/IResult.cs deleted file mode 100644 index 421aefa0f..000000000 --- a/src/Caliburn.Micro.Core/IResult.cs +++ /dev/null @@ -1,33 +0,0 @@ -using System; - -namespace Caliburn.Micro -{ - /// - /// Allows custom code to execute after the return of a action. - /// - public interface IResult - { - /// - /// Executes the result using the specified context. - /// - /// The context. - void Execute(CoroutineExecutionContext context); - - /// - /// Occurs when execution has completed. - /// - event EventHandler Completed; - } - - /// - /// Allows custom code to execute after the return of a action. - /// - /// The type of the result. - public interface IResult : IResult - { - /// - /// Gets the result of the asynchronous operation. - /// - TResult Result { get; } - } -} diff --git a/src/Caliburn.Micro.Core/IScreen.cs b/src/Caliburn.Micro.Core/IScreen.cs deleted file mode 100644 index 1999bbece..000000000 --- a/src/Caliburn.Micro.Core/IScreen.cs +++ /dev/null @@ -1,10 +0,0 @@ -namespace Caliburn.Micro -{ - /// - /// Denotes an instance which implements , , - /// , and - /// - public interface IScreen : IHaveDisplayName, IActivate, IDeactivate, IGuardClose, INotifyPropertyChangedEx - { - } -} diff --git a/src/Caliburn.Micro.Core/IViewAware.cs b/src/Caliburn.Micro.Core/IViewAware.cs deleted file mode 100644 index 5b82017a3..000000000 --- a/src/Caliburn.Micro.Core/IViewAware.cs +++ /dev/null @@ -1,29 +0,0 @@ -using System; - -namespace Caliburn.Micro -{ - /// - /// Denotes a class which is aware of its view(s). - /// - public interface IViewAware - { - /// - /// Attaches a view to this instance. - /// - /// The view. - /// The context in which the view appears. - void AttachView(object view, object context = null); - - /// - /// Gets a view previously attached to this instance. - /// - /// The context denoting which view to retrieve. - /// The view. - object GetView(object context = null); - - /// - /// Raised when a view is attached. - /// - event EventHandler ViewAttached; - } -} diff --git a/src/Caliburn.Micro.Core/IoC.cs b/src/Caliburn.Micro.Core/IoC.cs deleted file mode 100644 index a060db170..000000000 --- a/src/Caliburn.Micro.Core/IoC.cs +++ /dev/null @@ -1,48 +0,0 @@ -using System; -using System.Collections.Generic; -using System.Linq; - -namespace Caliburn.Micro -{ - /// - /// Used by the framework to pull instances from an IoC container and to inject dependencies into certain existing classes. - /// - public static class IoC - { - /// - /// Gets an instance by type and key. - /// - public static Func GetInstance = (service, key) => { throw new InvalidOperationException("IoC is not initialized."); }; - - /// - /// Gets all instances of a particular type. - /// - public static Func> GetAllInstances = service => { throw new InvalidOperationException("IoC is not initialized."); }; - - /// - /// Passes an existing instance to the IoC container to enable dependencies to be injected. - /// - public static Action BuildUp = instance => { throw new InvalidOperationException("IoC is not initialized."); }; - - /// - /// Gets an instance from the container. - /// - /// The type to resolve. - /// The key to look up. - /// The resolved instance. - public static T Get(string key = null) - { - return (T)GetInstance(typeof(T), key); - } - - /// - /// Gets all instances of a particular type. - /// - /// The type to resolve. - /// The resolved instances. - public static IEnumerable GetAll() - { - return GetAllInstances(typeof(T)).Cast(); - } - } -} diff --git a/src/Caliburn.Micro.Core/IoC/ExtensionPoints/IoC.cs b/src/Caliburn.Micro.Core/IoC/ExtensionPoints/IoC.cs new file mode 100644 index 000000000..ed189eabf --- /dev/null +++ b/src/Caliburn.Micro.Core/IoC/ExtensionPoints/IoC.cs @@ -0,0 +1,48 @@ +using System; +using System.Collections.Generic; +using System.Linq; + +namespace Caliburn.Micro; + +/// +/// Used by the framework to pull instances from an IoC container and to inject dependencies into certain existing classes. +/// +public static class IoC { + /// + /// Gets or sets func to get an instance by type and key. + /// + public static Func GetInstance { get; set; } + = (service, key) + => throw new InvalidOperationException("IoC is not initialized."); + + /// + /// Gets or sets func to get all instances of a particular type. + /// + public static Func> GetAllInstances { get; set; } + = service + => throw new InvalidOperationException("IoC is not initialized."); + + /// + /// Gets or sets action to be passes an existing instance to the IoC container to enable dependencies to be injected. + /// + public static Action BuildUp { get; set; } + = instance + => throw new InvalidOperationException("IoC is not initialized."); + + /// + /// Gets an instance from the container. + /// + /// The type to resolve. + /// The key to look up. + /// The resolved instance. + public static T Get(string key = null) + => (T)GetInstance(typeof(T), key); + + /// + /// Gets all instances of a particular type. + /// + /// The type to resolve. + /// The resolved instances. + public static IEnumerable GetAll() + => GetAllInstances(typeof(T)).Cast(); +} diff --git a/src/Caliburn.Micro.Core/IoC/Extensions/ContainerExtensions.cs b/src/Caliburn.Micro.Core/IoC/Extensions/ContainerExtensions.cs new file mode 100644 index 000000000..0e9963351 --- /dev/null +++ b/src/Caliburn.Micro.Core/IoC/Extensions/ContainerExtensions.cs @@ -0,0 +1,159 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Reflection; + +namespace Caliburn.Micro; + +/// +/// Extension methods for the . +/// +public static class ContainerExtensions { + /// + /// Registers a singleton. + /// + /// The type of the implementation. + /// The container. + /// The key. + /// The . + public static SimpleContainer Singleton(this SimpleContainer container, string key = null) + => Singleton(container, key); + + /// + /// Registers a singleton. + /// + /// The type of the service. + /// The type of the implementation. + /// The container. + /// The key. + /// The . + public static SimpleContainer Singleton(this SimpleContainer container, string key = null) + where TImplementation : TService { + container.RegisterSingleton(typeof(TService), key, typeof(TImplementation)); + + return container; + } + + /// + /// Registers an service to be created on each request. + /// + /// The type of the implementation. + /// The container. + /// The key. + /// The . + public static SimpleContainer PerRequest(this SimpleContainer container, string key = null) + => PerRequest(container, key); + + /// + /// Registers an service to be created on each request. + /// + /// The type of the service. + /// The type of the implementation. + /// The container. + /// The key. + /// The . + public static SimpleContainer PerRequest(this SimpleContainer container, string key = null) + where TImplementation : TService { + container.RegisterPerRequest(typeof(TService), key, typeof(TImplementation)); + + return container; + } + + /// + /// Registers an instance with the container. + /// + /// The type of the service. + /// The container. + /// The instance. + /// The . + public static SimpleContainer Instance(this SimpleContainer container, TService instance) { + container.RegisterInstance(typeof(TService), null, instance); + + return container; + } + + /// + /// Registers a custom service handler with the container. + /// + /// The type of the service. + /// The container. + /// The handler. + /// The . + public static SimpleContainer Handler( + this SimpleContainer container, + Func handler) { + container.RegisterHandler(typeof(TService), null, handler); + + return container; + } + + /// + /// Registers all specified types in an assembly as singleton in the container. + /// + /// The type of the service. + /// The container. + /// The assembly. + /// The type filter. + /// The . + public static SimpleContainer AllTypesOf( + this SimpleContainer container, + Assembly assembly, + Func filter = null) { + filter ??= type => true; + Type serviceType = typeof(TService); + IEnumerable types + = from type in assembly.DefinedTypes + where serviceType.GetTypeInfo().IsAssignableFrom(type) + && !type.IsAbstract + && !type.IsInterface + && filter(type.AsType()) + select type; + foreach (TypeInfo type in types) { + container.RegisterSingleton(typeof(TService), null, type.AsType()); + } + + return container; + } + + /// + /// Requests an instance. + /// + /// The type of the service. + /// The container. + /// The key. + /// The instance. + public static TService GetInstance(this SimpleContainer container, string key = null) + => (TService)container + .GetInstance(typeof(TService), key); + + /// + /// Gets all instances of a particular type and the given key (default null). + /// + /// The type to resolve. + /// The container. + /// The key shared by those instances. + /// The resolved instances. + public static IEnumerable GetAllInstances(this SimpleContainer container, string key = null) + => container + .GetAllInstances(typeof(TService), key) + .Cast(); + + /// + /// Determines if a handler for the service/key has previously been registered. + /// + /// The service type. + /// The container. + /// The key. + /// True if a handler is registere; false otherwise. + public static bool HasHandler(this SimpleContainer container, string key = null) + => container.HasHandler(typeof(TService), key); + + /// + /// Unregisters any handlers for the service/key that have previously been registered. + /// + /// The service type. + /// The container. + /// The key. + public static void UnregisterHandler(this SimpleContainer container, string key = null) + => container.UnregisterHandler(typeof(TService), key); +} diff --git a/src/Caliburn.Micro.Core/IoC/SimpleContainer.cs b/src/Caliburn.Micro.Core/IoC/SimpleContainer.cs new file mode 100644 index 000000000..ea3ed3a9c --- /dev/null +++ b/src/Caliburn.Micro.Core/IoC/SimpleContainer.cs @@ -0,0 +1,282 @@ +using System; +using System.Collections; +using System.Collections.Generic; +using System.Linq; +using System.Reflection; + +namespace Caliburn.Micro; + +/// +/// A simple IoC container. +/// +public class SimpleContainer { + private static readonly Type DelegateType = typeof(Delegate); + private static readonly Type EnumerableType = typeof(IEnumerable); + private static readonly TypeInfo EnumerableTypeInfo = EnumerableType.GetTypeInfo(); + private static readonly TypeInfo DelegateTypeInfo = DelegateType.GetTypeInfo(); + private readonly Type _simpleContainerType = typeof(SimpleContainer); + private readonly List _entries; + + /// + /// Initializes a new instance of the class. + /// + public SimpleContainer() + => _entries = new List(); + + private SimpleContainer(IEnumerable entries) + => _entries = new List(entries); + + /// + /// Occurs when a new instance is created. + /// + public event Action Activated + = obj => { }; + + /// + /// Gets or sets a value indicating whether to enable recursive property injection for all resolutions. + /// + public bool EnablePropertyInjection { get; set; } + + /// + /// Registers the instance. + /// + /// The service. + /// The key. + /// The implementation. + public void RegisterInstance(Type service, string key, object implementation) + => RegisterHandler(service, key, container => implementation); + + /// + /// Registers the class so that a new instance is created on every request. + /// + /// The service. + /// The key. + /// The implementation. + public void RegisterPerRequest(Type service, string key, Type implementation) + => RegisterHandler(service, key, container => container.BuildInstance(implementation)); + + /// + /// Registers the class so that it is created once, on first request, and the same instance is returned to all requestors thereafter. + /// + /// The service. + /// The key. + /// The implementation. + public void RegisterSingleton(Type service, string key, Type implementation) { + object singleton = null; + RegisterHandler(service, key, container => (singleton ??= container.BuildInstance(implementation))); + } + + /// + /// Registers a custom handler for serving requests from the container. + /// + /// The service. + /// The key. + /// The handler. + public void RegisterHandler(Type service, string key, Func handler) + => GetOrCreateEntry(service, key).Add(handler); + + /// + /// Unregisters any handlers for the service/key that have previously been registered. + /// + /// The service. + /// The key. + public void UnregisterHandler(Type service, string key) { + ContainerEntry entry = GetEntry(service, key); + if (entry == null) { + return; + } + + _entries.Remove(entry); + } + + /// + /// Requests an instance. + /// + /// The service. + /// The key. + /// The instance, or null if a handler is not found. + public object GetInstance(Type service, string key) { + ContainerEntry entry = GetEntry(service, key); + if (entry != null) { + object instance = entry.Single()(this); + if (!EnablePropertyInjection || instance == null) { + return instance; + } + + BuildUp(instance); + + return instance; + } + + if (service == null) { + return null; + } + + TypeInfo serviceTypeInfo = service.GetTypeInfo(); + if (DelegateTypeInfo.IsAssignableFrom(serviceTypeInfo)) { + Type typeToCreate = serviceTypeInfo.GenericTypeArguments[0]; + Type factoryFactoryType = typeof(FactoryFactory<>).MakeGenericType(typeToCreate); + object factoryFactoryHost = Activator.CreateInstance(factoryFactoryType); + MethodInfo factoryFactoryMethod = factoryFactoryType.GetRuntimeMethod("Create", new Type[] { _simpleContainerType }); + + return factoryFactoryMethod.Invoke(factoryFactoryHost, new object[] { this }); + } + + if (!EnumerableTypeInfo.IsAssignableFrom(serviceTypeInfo) || !serviceTypeInfo.IsGenericType) { + return null; + } + + Type listType = serviceTypeInfo.GenericTypeArguments[0]; + var instances = GetAllInstances(listType).ToList(); + var array = Array.CreateInstance(listType, instances.Count); + + for (int i = 0; i < array.Length; i++) { + if (EnablePropertyInjection) { + BuildUp(instances[i]); + } + + array.SetValue(instances[i], i); + } + + return array; + } + + /// + /// Determines if a handler for the service/key has previously been registered. + /// + /// The service. + /// The key. + /// True if a handler is registere; false otherwise. + public bool HasHandler(Type service, string key) + => GetEntry(service, key) != null; + + /// + /// Requests all instances of a given type and the given key (default null). + /// + /// The service. + /// The key shared by those instances. + /// All the instances or an empty enumerable if none are found. + public IEnumerable GetAllInstances(Type service, string key = null) { + ContainerEntry entries = GetEntry(service, key); + if (entries == null) { + return Array.Empty(); + } + + IEnumerable instances = entries.Select(e => e(this)); + if (!EnablePropertyInjection) { + return instances; + } + + foreach (object instance in instances.Where(instance => instance != null)) { + BuildUp(instance); + } + + return instances; + } + + /// + /// Pushes dependencies into an existing instance based on interface properties with setters. + /// + /// The instance. + public void BuildUp(object instance) { + IEnumerable properties = instance + .GetType() + .GetRuntimeProperties() + .Where(p => p.CanRead && p.CanWrite && p.PropertyType.GetTypeInfo().IsInterface); + + foreach (PropertyInfo property in properties) { + object value = GetInstance(property.PropertyType, null); + if (value == null) { + continue; + } + + property.SetValue(instance, value, null); + } + } + + /// + /// Creates a child container. + /// + /// A new container. + public SimpleContainer CreateChildContainer() + => new(_entries); + + /// + /// Actually does the work of creating the instance and satisfying it's constructor dependencies. + /// + /// The type. + protected object BuildInstance(Type type) { + object[] args = DetermineConstructorArgs(type); + + return ActivateInstance(type, args); + } + + /// + /// Creates an instance of the type with the specified constructor arguments. + /// + /// The type. + /// The constructor args. + /// The created instance. + protected virtual object ActivateInstance(Type type, object[] args) { + object instance = args.Length > 0 ? System.Activator.CreateInstance(type, args) : System.Activator.CreateInstance(type); + Activated(instance); + + return instance; + } + + private ContainerEntry GetOrCreateEntry(Type service, string key) { + ContainerEntry entry = GetEntry(service, key); + if (entry != null) { + return entry; + } + + entry = new ContainerEntry { Service = service, Key = key }; + _entries.Add(entry); + + return entry; + } + + private ContainerEntry GetEntry(Type service, string key) + => service == null + ? _entries.FirstOrDefault(x => x.Key == key) + : key == null + ? _entries.FirstOrDefault(x => x.Service == service && + string.IsNullOrEmpty(x.Key)) + ?? _entries.FirstOrDefault(x => x.Service == service) + : _entries.FirstOrDefault(x => x.Service == service && + x.Key == key); + + private object[] DetermineConstructorArgs(Type implementation) { + var args = new List(); + ConstructorInfo constructor = SelectEligibleConstructor(implementation); + if (constructor == null) { + return args.ToArray(); + } + + args.AddRange(constructor.GetParameters().Select(info => GetInstance(info.ParameterType, null))); + + return args.ToArray(); + } + + private ConstructorInfo SelectEligibleConstructor(Type type) + => type.GetTypeInfo().DeclaredConstructors + .Where(c => c.IsPublic) + .Select(c => new { + Constructor = c, + HandledParamters = c.GetParameters().Count(p => HasHandler(p.ParameterType, null)), + }) + .OrderByDescending(c => c.HandledParamters) + .Select(c => c.Constructor) + .FirstOrDefault(); + + private sealed class ContainerEntry : List> { + public string Key { get; set; } + + public Type Service { get; set; } + } + + private sealed class FactoryFactory { + public Func Create(SimpleContainer container) + => () => (T)container.GetInstance(typeof(T), null); + } +} diff --git a/src/Caliburn.Micro.Core/LogManager.cs b/src/Caliburn.Micro.Core/LogManager.cs deleted file mode 100644 index 6d1dab146..000000000 --- a/src/Caliburn.Micro.Core/LogManager.cs +++ /dev/null @@ -1,24 +0,0 @@ -using System; - -namespace Caliburn.Micro -{ - /// - /// Used to manage logging. - /// - public static class LogManager - { - private static readonly ILog NullLogInstance = new NullLog(); - - /// - /// Creates an for the provided type. - /// - public static Func GetLog = type => NullLogInstance; - - private class NullLog : ILog - { - public void Info(string format, params object[] args) { } - public void Warn(string format, params object[] args) { } - public void Error(Exception exception) { } - } - } -} diff --git a/src/Caliburn.Micro.Core/Logging/Contracts/ILog.cs b/src/Caliburn.Micro.Core/Logging/Contracts/ILog.cs new file mode 100644 index 000000000..9ba1fa333 --- /dev/null +++ b/src/Caliburn.Micro.Core/Logging/Contracts/ILog.cs @@ -0,0 +1,28 @@ +using System; + +namespace Caliburn.Micro; + +/// +/// A logger. +/// +public interface ILog { + /// + /// Logs the message as info. + /// + /// A formatted message. + /// Parameters to be injected into the formatted message. + void Info(string format, params object[] args); + + /// + /// Logs the message as a warning. + /// + /// A formatted message. + /// Parameters to be injected into the formatted message. + void Warn(string format, params object[] args); + + /// + /// Logs the exception. + /// + /// The exception. + void Error(Exception exception); +} diff --git a/src/Caliburn.Micro.Core/Logging/ExtensionPoints/LogManager.cs b/src/Caliburn.Micro.Core/Logging/ExtensionPoints/LogManager.cs new file mode 100644 index 000000000..3c5084e4a --- /dev/null +++ b/src/Caliburn.Micro.Core/Logging/ExtensionPoints/LogManager.cs @@ -0,0 +1,28 @@ +using System; + +namespace Caliburn.Micro; + +/// +/// Used to manage logging. +/// +public static class LogManager { + private static readonly ILog NullLogInstance = new NullLog(); + + /// + /// Gets or sets func to create an for the provided type. + /// + public static Func GetLog { get; set; } + = type + => NullLogInstance; + + private sealed class NullLog : ILog { + public void Info(string format, params object[] args) { + } + + public void Warn(string format, params object[] args) { + } + + public void Error(Exception exception) { + } + } +} diff --git a/src/Caliburn.Micro.Core/Logging/Impl/DebugLog.cs b/src/Caliburn.Micro.Core/Logging/Impl/DebugLog.cs new file mode 100644 index 000000000..09e15db93 --- /dev/null +++ b/src/Caliburn.Micro.Core/Logging/Impl/DebugLog.cs @@ -0,0 +1,44 @@ +#define DEBUG + +using System; +using System.Diagnostics; +using System.Globalization; + +namespace Caliburn.Micro; + +/// +/// A simple logger thats logs everything to the debugger. +/// +public class DebugLog : ILog { + private readonly string _typeName; + + /// + /// Initializes a new instance of the class. + /// + /// The type. + public DebugLog(Type type) + => _typeName = type.FullName; + + /// + /// Logs the message as info. + /// + /// A formatted message. + /// Parameters to be injected into the formatted message. + public void Info(string format, params object[] args) + => Debug.WriteLine("[{1}] INFO: {0}", string.Format(CultureInfo.InvariantCulture, format, args), _typeName); + + /// + /// Logs the message as a warning. + /// + /// A formatted message. + /// Parameters to be injected into the formatted message. + public void Warn(string format, params object[] args) + => Debug.WriteLine("[{1}] WARN: {0}", string.Format(CultureInfo.InvariantCulture, format, args), _typeName); + + /// + /// Logs the exception. + /// + /// The exception. + public void Error(Exception exception) + => Debug.WriteLine("[{1}] ERROR: {0}", exception, _typeName); +} diff --git a/src/Caliburn.Micro.Core/OverrideCancelResultDecorator.cs b/src/Caliburn.Micro.Core/OverrideCancelResultDecorator.cs deleted file mode 100644 index 46b576aba..000000000 --- a/src/Caliburn.Micro.Core/OverrideCancelResultDecorator.cs +++ /dev/null @@ -1,33 +0,0 @@ -namespace Caliburn.Micro -{ - /// - /// A result decorator that overrides of the decorated instance. - /// - public class OverrideCancelResultDecorator : ResultDecoratorBase - { - private static readonly ILog Log = LogManager.GetLog(typeof(OverrideCancelResultDecorator)); - - /// - /// Initializes a new instance of the class. - /// - /// The result to decorate. - public OverrideCancelResultDecorator(IResult result) - : base(result) { } - - /// - /// Called when the execution of the decorated result has completed. - /// - /// The context. - /// The decorated result. - /// The instance containing the event data. - protected override void OnInnerResultCompleted(CoroutineExecutionContext context, IResult innerResult, ResultCompletionEventArgs args) - { - if (args.WasCancelled) - { - Log.Info(string.Format("Overriding WasCancelled from {0}.", innerResult.GetType().Name)); - } - - OnCompleted(new ResultCompletionEventArgs { Error = args.Error }); - } - } -} diff --git a/src/Caliburn.Micro.Core/PlatformProvider.cs b/src/Caliburn.Micro.Core/PlatformProvider.cs deleted file mode 100644 index 76014ef49..000000000 --- a/src/Caliburn.Micro.Core/PlatformProvider.cs +++ /dev/null @@ -1,13 +0,0 @@ -namespace Caliburn.Micro -{ - /// - /// Access the current . - /// - public static class PlatformProvider - { - /// - /// Gets or sets the current . - /// - public static IPlatformProvider Current { get; set; } = new DefaultPlatformProvider(); - } -} diff --git a/src/Caliburn.Micro.Core/PlatformProvider/Contracts/IPlatformProvider.cs b/src/Caliburn.Micro.Core/PlatformProvider/Contracts/IPlatformProvider.cs new file mode 100644 index 000000000..1e97647d5 --- /dev/null +++ b/src/Caliburn.Micro.Core/PlatformProvider/Contracts/IPlatformProvider.cs @@ -0,0 +1,74 @@ +using System; +using System.Collections.Generic; +using System.Threading; +using System.Threading.Tasks; + +namespace Caliburn.Micro; + +/// +/// Interface for platform specific operations that need enlightenment. +/// +public interface IPlatformProvider { + /// + /// Gets a value indicating whether or not the framework is in design-time mode. + /// + bool InDesignMode { get; } + + /// + /// Gets a value indicating whether or not classes should execute property change notications on the UI thread. + /// + bool PropertyChangeNotificationsOnUIThread { get; } + + /// + /// Executes the action on the UI thread asynchronously. + /// + /// The action to execute. + void BeginOnUIThread(Action action); + + /// + /// Executes the action on the UI thread asynchronously. + /// + /// The action to execute. + Task OnUIThreadAsync(Func action); + + /// + /// Executes the action on the UI thread. + /// + /// The action to execute. + void OnUIThread(Action action); + + /// + /// Used to retrieve the root, non-framework-created view. + /// + /// The view to search. + /// The root element that was not created by the framework. + /// In certain instances the services create UI elements. + /// For example, if you ask the window manager to show a UserControl as a dialog, it creates a window to host the UserControl in. + /// The WindowManager marks that element as a framework-created element so that it can determine what it created vs. what was intended by the developer. + /// Calling GetFirstNonGeneratedView allows the framework to discover what the original element was. + /// + object GetFirstNonGeneratedView(object view); + + /// + /// Executes the handler the fist time the view is loaded. + /// + /// The view. + /// The handler. + void ExecuteOnFirstLoad(object view, Action handler); + + /// + /// Executes the handler the next time the view's LayoutUpdated event fires. + /// + /// The view. + /// The handler. + void ExecuteOnLayoutUpdated(object view, Action handler); + + /// + /// Get the close action for the specified view model. + /// + /// The view model to close. + /// The associated views. + /// The dialog result. + /// An to close the view model. + Func GetViewCloseAction(object viewModel, ICollection views, bool? dialogResult); +} diff --git a/src/Caliburn.Micro.Core/PlatformProvider/DefaultImpl/DefaultPlatformProvider.cs b/src/Caliburn.Micro.Core/PlatformProvider/DefaultImpl/DefaultPlatformProvider.cs new file mode 100644 index 000000000..712c4feed --- /dev/null +++ b/src/Caliburn.Micro.Core/PlatformProvider/DefaultImpl/DefaultPlatformProvider.cs @@ -0,0 +1,88 @@ +using System; +using System.Collections.Generic; +using System.Threading; +using System.Threading.Tasks; + +namespace Caliburn.Micro; + +/// +/// Default implementation for that does no platform enlightenment. +/// +public class DefaultPlatformProvider : IPlatformProvider { + /// + /// Gets a value indicating whether or not the framework is in design-time mode. + /// + public virtual bool InDesignMode + => true; + + /// + /// Gets a value indicating whether or not classes should execute property change notications on the UI thread. + /// + public virtual bool PropertyChangeNotificationsOnUIThread + => true; + + /// + /// Executes the action on the UI thread asynchronously. + /// + /// The action to execute. + public virtual void BeginOnUIThread(Action action) + => action(); + + /// + /// Executes the action on the UI thread asynchronously. + /// + /// The action to execute. + public virtual Task OnUIThreadAsync(Func action) + => Task.Factory.StartNew(action); + + /// + /// Executes the action on the UI thread. + /// + /// The action to execute. + public virtual void OnUIThread(Action action) + => action(); + + /// + /// Used to retrieve the root, non-framework-created view. + /// + /// The view to search. + /// + /// The root element that was not created by the framework. + /// + /// + /// In certain instances the services create UI elements. + /// For example, if you ask the window manager to show a UserControl as a dialog, it creates a window to host the UserControl in. + /// The WindowManager marks that element as a framework-created element so that it can determine what it created vs. what was intended by the developer. + /// Calling GetFirstNonGeneratedView allows the framework to discover what the original element was. + /// + public virtual object GetFirstNonGeneratedView(object view) + => view; + + /// + /// Executes the handler the fist time the view is loaded. + /// + /// The view. + /// The handler. + public virtual void ExecuteOnFirstLoad(object view, Action handler) + => handler(view); + + /// + /// Executes the handler the next time the view's LayoutUpdated event fires. + /// + /// The view. + /// The handler. + public virtual void ExecuteOnLayoutUpdated(object view, Action handler) + => handler(view); + + /// + /// Get the close action for the specified view model. + /// + /// The view model to close. + /// The associated views. + /// The dialog result. + /// + /// An to close the view model. + /// + public virtual Func GetViewCloseAction(object viewModel, ICollection views, bool? dialogResult) + => ct => Task.FromResult(true); +} diff --git a/src/Caliburn.Micro.Core/PlatformProvider/ExtensionPoints/PlatformProvider.cs b/src/Caliburn.Micro.Core/PlatformProvider/ExtensionPoints/PlatformProvider.cs new file mode 100644 index 000000000..a6f1b5363 --- /dev/null +++ b/src/Caliburn.Micro.Core/PlatformProvider/ExtensionPoints/PlatformProvider.cs @@ -0,0 +1,12 @@ +namespace Caliburn.Micro; + +/// +/// Access the current . +/// +public static class PlatformProvider { + /// + /// Gets or sets the current . + /// + public static IPlatformProvider Current { get; set; } + = new DefaultPlatformProvider(); +} diff --git a/src/Caliburn.Micro.Core/PropertyChangedBase.cs b/src/Caliburn.Micro.Core/PropertyChangedBase.cs deleted file mode 100644 index 78d4c530c..000000000 --- a/src/Caliburn.Micro.Core/PropertyChangedBase.cs +++ /dev/null @@ -1,110 +0,0 @@ -using System; -using System.Collections.Generic; -using System.ComponentModel; -using System.Linq.Expressions; -using System.Runtime.Serialization; - -namespace Caliburn.Micro -{ - /// - /// A base class that implements the infrastructure for property change notification and automatically performs UI thread marshalling. - /// - [DataContract] - public class PropertyChangedBase : INotifyPropertyChangedEx - { - /// - /// Creates an instance of . - /// - public PropertyChangedBase() - { - IsNotifying = true; - } - - /// - /// Occurs when a property value changes. - /// - public virtual event PropertyChangedEventHandler PropertyChanged; - - /// - /// Enables/Disables property change notification. - /// Virtualized in order to help with document oriented view models. - /// - public virtual bool IsNotifying { get; set; } - - /// - /// Raises a change notification indicating that all bindings should be refreshed. - /// - public virtual void Refresh() - { - NotifyOfPropertyChange(string.Empty); - } - - /// - /// Notifies subscribers of the property change. - /// - /// Name of the property. - public virtual void NotifyOfPropertyChange([System.Runtime.CompilerServices.CallerMemberName] string propertyName = null) - { - if (IsNotifying && PropertyChanged != null) - { - if (PlatformProvider.Current.PropertyChangeNotificationsOnUIThread) - { - OnUIThread(() => OnPropertyChanged(new PropertyChangedEventArgs(propertyName))); - } - else - { - OnPropertyChanged(new PropertyChangedEventArgs(propertyName)); - } - } - } - - /// - /// Notifies subscribers of the property change. - /// - /// The type of the property. - /// The property expression. - public void NotifyOfPropertyChange(Expression> property) - { - NotifyOfPropertyChange(property.GetMemberInfo().Name); - } - - /// - /// Raises the event directly. - /// - /// The instance containing the event data. - [EditorBrowsable(EditorBrowsableState.Never)] - protected void OnPropertyChanged(PropertyChangedEventArgs e) - { - PropertyChanged?.Invoke(this, e); - } - - /// - /// Executes the given action on the UI thread - /// - /// An extension point for subclasses to customise how property change notifications are handled. - /// - protected virtual void OnUIThread(System.Action action) => action.OnUIThread(); - - /// - /// Sets a backing field value and if it's changed raise a notification. - /// - /// The type of the value being set. - /// A reference to the field to update. - /// The new value. - /// The name of the property for change notifications. - /// - public virtual bool Set(ref T oldValue, T newValue, [System.Runtime.CompilerServices.CallerMemberName] string propertyName = null) - { - if (EqualityComparer.Default.Equals(oldValue, newValue)) - { - return false; - } - - oldValue = newValue; - - NotifyOfPropertyChange(propertyName ?? string.Empty); - - return true; - } - } -} diff --git a/src/Caliburn.Micro.Core/RescueResultDecorator.cs b/src/Caliburn.Micro.Core/RescueResultDecorator.cs deleted file mode 100644 index 6314dc4dd..000000000 --- a/src/Caliburn.Micro.Core/RescueResultDecorator.cs +++ /dev/null @@ -1,83 +0,0 @@ -using System; - -namespace Caliburn.Micro -{ - /// - /// A result decorator which rescues errors from the decorated result by executing a rescue coroutine. - /// - /// The type of the exception we want to perform the rescue on - public class RescueResultDecorator : ResultDecoratorBase where TException : Exception - { - private static readonly ILog Log = LogManager.GetLog(typeof(RescueResultDecorator<>)); - private readonly bool cancelResult; - private readonly Func coroutine; - - /// - /// Initializes a new instance of the class. - /// - /// The result to decorate. - /// The rescue coroutine. - /// Set to true to cancel the result after executing rescue. - public RescueResultDecorator(IResult result, Func coroutine, bool cancelResult = true) : base(result) - { - this.coroutine = coroutine ?? throw new ArgumentNullException("coroutine"); - this.cancelResult = cancelResult; - } - - /// - /// Called when the execution of the decorated result has completed. - /// - /// The context. - /// The decorated result. - /// The instance containing the event data. - protected override void OnInnerResultCompleted(CoroutineExecutionContext context, IResult innerResult, ResultCompletionEventArgs args) - { - var error = args.Error as TException; - if (error == null) - { - OnCompleted(args); - } - else - { - Log.Error(error); - Log.Info(string.Format("Executing coroutine because {0} threw an exception.", innerResult.GetType().Name)); - Rescue(context, error); - } - } - - private void Rescue(CoroutineExecutionContext context, TException exception) - { - IResult rescueResult; - try - { - rescueResult = coroutine(exception); - } - catch (Exception ex) - { - OnCompleted(new ResultCompletionEventArgs { Error = ex }); - return; - } - - try - { - rescueResult.Completed += RescueCompleted; - IoC.BuildUp(rescueResult); - rescueResult.Execute(context); - } - catch (Exception ex) - { - RescueCompleted(rescueResult, new ResultCompletionEventArgs { Error = ex }); - } - } - - private void RescueCompleted(object sender, ResultCompletionEventArgs args) - { - ((IResult)sender).Completed -= RescueCompleted; - OnCompleted(new ResultCompletionEventArgs - { - Error = args.Error, - WasCancelled = (args.Error == null && (args.WasCancelled || cancelResult)) - }); - } - } -} diff --git a/src/Caliburn.Micro.Core/Result/Contracts/IResult.cs b/src/Caliburn.Micro.Core/Result/Contracts/IResult.cs new file mode 100644 index 000000000..2dadaed03 --- /dev/null +++ b/src/Caliburn.Micro.Core/Result/Contracts/IResult.cs @@ -0,0 +1,19 @@ +using System; + +namespace Caliburn.Micro; + +/// +/// Allows custom code to execute after the return of a action. +/// +public interface IResult { + /// + /// Occurs when execution has completed. + /// + event EventHandler Completed; + + /// + /// Executes the result using the specified context. + /// + /// The context. + void Execute(CoroutineExecutionContext context); +} diff --git a/src/Caliburn.Micro.Core/Result/Contracts/IResult{TResult}.cs b/src/Caliburn.Micro.Core/Result/Contracts/IResult{TResult}.cs new file mode 100644 index 000000000..bf5b344a1 --- /dev/null +++ b/src/Caliburn.Micro.Core/Result/Contracts/IResult{TResult}.cs @@ -0,0 +1,14 @@ +using System; + +namespace Caliburn.Micro; + +/// +/// Allows custom code to execute after the return of a action. +/// +/// The type of the result. +public interface IResult : IResult { + /// + /// Gets the result of the asynchronous operation. + /// + TResult Result { get; } +} diff --git a/src/Caliburn.Micro.Core/Result/EventArgs/ResultCompletionEventArgs.cs b/src/Caliburn.Micro.Core/Result/EventArgs/ResultCompletionEventArgs.cs new file mode 100644 index 000000000..3b00ebffd --- /dev/null +++ b/src/Caliburn.Micro.Core/Result/EventArgs/ResultCompletionEventArgs.cs @@ -0,0 +1,20 @@ +using System; + +namespace Caliburn.Micro; + +/// +/// The event args for the Completed event of an . +/// +public class ResultCompletionEventArgs : EventArgs { + /// + /// Gets or sets the error if one occurred. + /// + /// The error. + public Exception Error { get; set; } + + /// + /// Gets or sets a value indicating whether the result was cancelled. + /// + /// true if cancelled; otherwise, false. + public bool WasCancelled { get; set; } +} diff --git a/src/Caliburn.Micro.Core/Result/Extensions/ResultExtensions.cs b/src/Caliburn.Micro.Core/Result/Extensions/ResultExtensions.cs new file mode 100644 index 000000000..938ade94e --- /dev/null +++ b/src/Caliburn.Micro.Core/Result/Extensions/ResultExtensions.cs @@ -0,0 +1,113 @@ +using System; +using System.Threading.Tasks; + +namespace Caliburn.Micro; + +/// +/// Extension methods for instances. +/// +public static class ResultExtensions { + /// + /// Adds behavior to the result which is executed when the was cancelled. + /// + /// The result to decorate. + /// The coroutine to execute when was canceled. + public static IResult WhenCancelled(this IResult result, Func coroutine) + => new ContinueResultDecorator(result, coroutine); + + /// + /// Overrides of the decorated instance. + /// + /// The result to decorate. + public static IResult OverrideCancel(this IResult result) + => new OverrideCancelResultDecorator(result); + + /// + /// Rescues from the decorated by executing a coroutine. + /// + /// The type of the exception we want to perform the rescue on. + /// The result to decorate. + /// The rescue coroutine. + /// Set to true to cancel the result after executing rescue. + public static IResult Rescue(this IResult result, Func rescue, bool cancelResult = true) + where TException : Exception + => new RescueResultDecorator(result, rescue, cancelResult); + + /// + /// Rescues any exception from the decorated by executing a coroutine. + /// + /// The result to decorate. + /// The rescue coroutine. + /// Set to true to cancel the result after executing rescue. + public static IResult Rescue(this IResult result, Func rescue, bool cancelResult = true) + => Rescue(result, rescue, cancelResult); + + /// + /// Executes an asynchronous. + /// + /// The coroutine to execute. + /// The context to execute the coroutine within. + /// A task that represents the asynchronous coroutine. + public static Task ExecuteAsync(this IResult result, CoroutineExecutionContext context = null) + => InternalExecuteAsync(result, context); + + /// + /// Executes an asynchronous. + /// + /// The type of the result. + /// The coroutine to execute. + /// The context to execute the coroutine within. + /// A task that represents the asynchronous coroutine. + public static Task ExecuteAsync(this IResult result, CoroutineExecutionContext context = null) + => InternalExecuteAsync(result, context); + + /// + /// Encapsulates a task inside a couroutine. + /// + /// The task. + /// The coroutine that encapsulates the task. + public static TaskResult AsResult(this Task task) + => new(task); + + /// + /// Encapsulates a task inside a couroutine. + /// + /// The type of the result. + /// The task. + /// The coroutine that encapsulates the task. + public static TaskResult AsResult(this Task task) + => new(task); + + private static Task InternalExecuteAsync(IResult result, CoroutineExecutionContext context) { + var taskSource = new TaskCompletionSource(); + + void OnCompleted(object s, ResultCompletionEventArgs e) { + result.Completed -= OnCompleted; + + if (e.Error != null) { + taskSource.SetException(e.Error); + + return; + } + + if (e.WasCancelled) { + taskSource.SetCanceled(); + + return; + } + + taskSource.SetResult(result is IResult rr ? rr.Result : default); + } + + try { + IoC.BuildUp(result); + result.Completed += OnCompleted; + result.Execute(context ?? new CoroutineExecutionContext()); + } catch (Exception ex) { + result.Completed -= OnCompleted; + taskSource.SetException(ex); + } + + return taskSource.Task; + } +} diff --git a/src/Caliburn.Micro.Core/Result/Impl/ContinueResultDecorator.cs b/src/Caliburn.Micro.Core/Result/Impl/ContinueResultDecorator.cs new file mode 100644 index 000000000..658852816 --- /dev/null +++ b/src/Caliburn.Micro.Core/Result/Impl/ContinueResultDecorator.cs @@ -0,0 +1,64 @@ +using System; +using System.Globalization; + +namespace Caliburn.Micro; + +/// +/// A result decorator which executes a coroutine when the wrapped result was cancelled. +/// +public class ContinueResultDecorator : ResultDecoratorBase { + private static readonly ILog Log = LogManager.GetLog(typeof(ContinueResultDecorator)); + private readonly Func _coroutine; + + /// + /// Initializes a new instance of the class. + /// + /// The result to decorate. + /// The coroutine to execute when was canceled. + public ContinueResultDecorator(IResult result, Func coroutine) + : base(result) + => _coroutine = coroutine ?? throw new ArgumentNullException(nameof(coroutine)); + + /// + /// Called when the execution of the decorated result has completed. + /// + /// The context. + /// The decorated result. + /// The instance containing the event data. + protected override void OnInnerResultCompleted(CoroutineExecutionContext context, IResult innerResult, ResultCompletionEventArgs args) { + if (args.Error != null || !args.WasCancelled) { + OnCompleted(new ResultCompletionEventArgs { Error = args.Error }); + + return; + } + + Log.Info(string.Format(CultureInfo.InvariantCulture, "Executing coroutine because {0} was cancelled.", innerResult.GetType().Name)); + Continue(context); + } + + private void Continue(CoroutineExecutionContext context) { + IResult continueResult; + try { + continueResult = _coroutine(); + } catch (Exception ex) { + OnCompleted(new ResultCompletionEventArgs { Error = ex }); + return; + } + + try { + continueResult.Completed += ContinueCompleted; + IoC.BuildUp(continueResult); + continueResult.Execute(context); + } catch (Exception ex) { + ContinueCompleted(continueResult, new ResultCompletionEventArgs { Error = ex }); + } + } + + private void ContinueCompleted(object sender, ResultCompletionEventArgs args) { + ((IResult)sender).Completed -= ContinueCompleted; + OnCompleted(new ResultCompletionEventArgs { + Error = args.Error, + WasCancelled = args.Error == null, + }); + } +} diff --git a/src/Caliburn.Micro.Core/Result/Impl/DelegateResult.cs b/src/Caliburn.Micro.Core/Result/Impl/DelegateResult.cs new file mode 100644 index 000000000..7730609c8 --- /dev/null +++ b/src/Caliburn.Micro.Core/Result/Impl/DelegateResult.cs @@ -0,0 +1,38 @@ +using System; + +namespace Caliburn.Micro; + +/// +/// A result that executes an . +/// +public class DelegateResult : IResult { + private readonly Action _toExecute; + + /// + /// Initializes a new instance of the class. + /// + /// The action. + public DelegateResult(Action action) + => _toExecute = action; + + /// + /// Occurs when execution has completed. + /// + public event EventHandler Completed + = (sender, e) => { }; + + /// + /// Executes the result using the specified context. + /// + /// The context. + public void Execute(CoroutineExecutionContext context) { + var eventArgs = new ResultCompletionEventArgs(); + try { + _toExecute(); + } catch (Exception ex) { + eventArgs.Error = ex; + } + + Completed(this, eventArgs); + } +} diff --git a/src/Caliburn.Micro.Core/Result/Impl/DelegateResult{TResult}.cs b/src/Caliburn.Micro.Core/Result/Impl/DelegateResult{TResult}.cs new file mode 100644 index 000000000..150994621 --- /dev/null +++ b/src/Caliburn.Micro.Core/Result/Impl/DelegateResult{TResult}.cs @@ -0,0 +1,44 @@ +using System; + +namespace Caliburn.Micro; + +/// +/// A result that executes a . +/// +/// The type of the result. +public class DelegateResult : IResult { + private readonly Func _toExecute; + + /// + /// Initializes a new instance of the class. + /// + /// The action. + public DelegateResult(Func action) + => _toExecute = action; + + /// + /// Occurs when execution has completed. + /// + public event EventHandler Completed + = (sender, e) => { }; + + /// + /// Gets the result. + /// + public TResult Result { get; private set; } + + /// + /// Executes the result using the specified context. + /// + /// The context. + public void Execute(CoroutineExecutionContext context) { + var eventArgs = new ResultCompletionEventArgs(); + try { + Result = _toExecute(); + } catch (Exception ex) { + eventArgs.Error = ex; + } + + Completed(this, eventArgs); + } +} diff --git a/src/Caliburn.Micro.Core/Result/Impl/OverrideCancelResultDecorator.cs b/src/Caliburn.Micro.Core/Result/Impl/OverrideCancelResultDecorator.cs new file mode 100644 index 000000000..00544890f --- /dev/null +++ b/src/Caliburn.Micro.Core/Result/Impl/OverrideCancelResultDecorator.cs @@ -0,0 +1,35 @@ +using System.Globalization; + +namespace Caliburn.Micro; + +/// +/// A result decorator that overrides of the decorated instance. +/// +public class OverrideCancelResultDecorator : ResultDecoratorBase { + private static readonly ILog Log + = LogManager.GetLog(typeof(OverrideCancelResultDecorator)); + + /// + /// Initializes a new instance of the class. + /// + /// The result to decorate. + public OverrideCancelResultDecorator(IResult result) + : base(result) { + } + + /// + /// Called when the execution of the decorated result has completed. + /// + /// The context. + /// The decorated result. + /// The instance containing the event data. + protected override void OnInnerResultCompleted(CoroutineExecutionContext context, IResult innerResult, ResultCompletionEventArgs args) { + if (args.WasCancelled) { + Log.Info(string.Format(CultureInfo.InvariantCulture, "Overriding WasCancelled from {0}.", innerResult.GetType().Name)); + } + + OnCompleted(new ResultCompletionEventArgs { + Error = args.Error, + }); + } +} diff --git a/src/Caliburn.Micro.Core/Result/Impl/RescueResultDecorator.cs b/src/Caliburn.Micro.Core/Result/Impl/RescueResultDecorator.cs new file mode 100644 index 000000000..281f5cfd8 --- /dev/null +++ b/src/Caliburn.Micro.Core/Result/Impl/RescueResultDecorator.cs @@ -0,0 +1,72 @@ +using System; +using System.Globalization; + +namespace Caliburn.Micro; + +/// +/// A result decorator which rescues errors from the decorated result by executing a rescue coroutine. +/// +/// The type of the exception we want to perform the rescue on. +public class RescueResultDecorator : ResultDecoratorBase + where TException : Exception { + private static readonly ILog Log = LogManager.GetLog(typeof(RescueResultDecorator<>)); + private readonly bool _cancelResult; + private readonly Func _coroutine; + + /// + /// Initializes a new instance of the class. + /// + /// The result to decorate. + /// The rescue coroutine. + /// Set to true to cancel the result after executing rescue. + public RescueResultDecorator(IResult result, Func coroutine, bool cancelResult = true) + : base(result) { + _coroutine = coroutine ?? throw new ArgumentNullException(nameof(coroutine)); + _cancelResult = cancelResult; + } + + /// + /// Called when the execution of the decorated result has completed. + /// + /// The context. + /// The decorated result. + /// The instance containing the event data. + protected override void OnInnerResultCompleted(CoroutineExecutionContext context, IResult innerResult, ResultCompletionEventArgs args) { + if (args.Error is not TException error) { + OnCompleted(args); + + return; + } + + Log.Error(error); + Log.Info(string.Format(CultureInfo.InvariantCulture, "Executing coroutine because {0} threw an exception.", innerResult.GetType().Name)); + Rescue(context, error); + } + + private void Rescue(CoroutineExecutionContext context, TException exception) { + IResult rescueResult; + try { + rescueResult = _coroutine(exception); + } catch (Exception ex) { + OnCompleted(new ResultCompletionEventArgs { Error = ex }); + return; + } + + try { + rescueResult.Completed += RescueCompleted; + IoC.BuildUp(rescueResult); + rescueResult.Execute(context); + } catch (Exception ex) { + RescueCompleted(rescueResult, new ResultCompletionEventArgs { Error = ex }); + } + } + + private void RescueCompleted(object sender, ResultCompletionEventArgs args) { + ((IResult)sender).Completed -= RescueCompleted; + OnCompleted(new ResultCompletionEventArgs { + Error = args.Error, + WasCancelled = args.Error == null && + (args.WasCancelled || _cancelResult), + }); + } +} diff --git a/src/Caliburn.Micro.Core/Result/Impl/ResultDecoratorBase.cs b/src/Caliburn.Micro.Core/Result/Impl/ResultDecoratorBase.cs new file mode 100644 index 000000000..3b26cc91e --- /dev/null +++ b/src/Caliburn.Micro.Core/Result/Impl/ResultDecoratorBase.cs @@ -0,0 +1,61 @@ +using System; + +namespace Caliburn.Micro; + +/// +/// Base class for all decorators. +/// +public abstract class ResultDecoratorBase : IResult { + private readonly IResult _innerResult; + private CoroutineExecutionContext _context; + + /// + /// Initializes a new instance of the class. + /// + /// The result to decorate. + protected ResultDecoratorBase(IResult result) + => _innerResult = result ?? throw new ArgumentNullException(nameof(result)); + + /// + /// Occurs when execution has completed. + /// + public event EventHandler Completed + = (sender, e) => { }; + + /// + /// Executes the result using the specified context. + /// + /// The context. + public void Execute(CoroutineExecutionContext context) { + _context = context; + + try { + _innerResult.Completed += InnerResultCompleted; + IoC.BuildUp(_innerResult); + _innerResult.Execute(_context); + } catch (Exception ex) { + InnerResultCompleted(_innerResult, new ResultCompletionEventArgs { Error = ex }); + } + } + + /// + /// Called when the execution of the decorated result has completed. + /// + /// The context. + /// The decorated result. + /// The instance containing the event data. + protected abstract void OnInnerResultCompleted(CoroutineExecutionContext context, IResult innerResult, ResultCompletionEventArgs args); + + /// + /// Raises the event. + /// + /// The instance containing the event data. + protected void OnCompleted(ResultCompletionEventArgs args) + => Completed(this, args); + + private void InnerResultCompleted(object sender, ResultCompletionEventArgs args) { + _innerResult.Completed -= InnerResultCompleted; + OnInnerResultCompleted(_context, _innerResult, args); + _context = null; + } +} diff --git a/src/Caliburn.Micro.Core/Result/Impl/SequentialResult.cs b/src/Caliburn.Micro.Core/Result/Impl/SequentialResult.cs new file mode 100644 index 000000000..12acbf6a3 --- /dev/null +++ b/src/Caliburn.Micro.Core/Result/Impl/SequentialResult.cs @@ -0,0 +1,81 @@ +using System; +using System.Collections.Generic; + +namespace Caliburn.Micro; + +/// +/// An implementation of that enables sequential execution of multiple results. +/// +public class SequentialResult : IResult { + private readonly IEnumerator _enumerator; + private CoroutineExecutionContext _context; + + /// + /// Initializes a new instance of the class. + /// + /// The enumerator. + public SequentialResult(IEnumerator enumerator) + => _enumerator = enumerator; + + /// + /// Occurs when execution has completed. + /// + public event EventHandler Completed + = (sender, e) => { }; + + /// + /// Executes the result using the specified context. + /// + /// The context. + public void Execute(CoroutineExecutionContext context) { + _context = context; + ChildCompleted(null, new ResultCompletionEventArgs()); + } + + private void ChildCompleted(object sender, ResultCompletionEventArgs args) { + if (sender is IResult previous) { + previous.Completed -= ChildCompleted; + } + + if (args.Error != null || args.WasCancelled) { + OnComplete(args.Error, args.WasCancelled); + + return; + } + + bool moveNextSucceeded = false; + try { + moveNextSucceeded = _enumerator.MoveNext(); + } catch (Exception ex) { + OnComplete(ex, false); + return; + } + + if (!moveNextSucceeded) { + OnComplete(null, false); + + return; + } + + try { + IResult next = _enumerator.Current; + IoC.BuildUp(next); + next.Completed += ChildCompleted; + next.Execute(_context); + } catch (Exception ex) { + OnComplete(ex, false); + + return; + } + } + + private void OnComplete(Exception error, bool wasCancelled) { + _enumerator.Dispose(); + Completed( + this, + new ResultCompletionEventArgs { + Error = error, + WasCancelled = wasCancelled, + }); + } +} diff --git a/src/Caliburn.Micro.Core/Result/Impl/SimpleResult.cs b/src/Caliburn.Micro.Core/Result/Impl/SimpleResult.cs new file mode 100644 index 000000000..55e85a466 --- /dev/null +++ b/src/Caliburn.Micro.Core/Result/Impl/SimpleResult.cs @@ -0,0 +1,53 @@ +using System; + +namespace Caliburn.Micro; + +/// +/// A simple result. +/// +public sealed class SimpleResult : IResult { + private readonly bool _wasCancelled; + private readonly Exception _error; + + private SimpleResult(bool wasCancelled, Exception error) { + _wasCancelled = wasCancelled; + _error = error; + } + + /// + /// Occurs when execution has completed. + /// + public event EventHandler Completed + = (sender, e) => { }; + + /// + /// A result that is always succeeded. + /// + public static IResult Succeeded() + => new SimpleResult(false, null); + + /// + /// A result that is always canceled. + /// + /// The result. + public static IResult Cancelled() + => new SimpleResult(true, null); + + /// + /// A result that is always failed. + /// + public static IResult Failed(Exception error) + => new SimpleResult(false, error); + + /// + /// Executes the result using the specified context. + /// + /// The context. + public void Execute(CoroutineExecutionContext context) + => Completed( + this, + new ResultCompletionEventArgs { + WasCancelled = _wasCancelled, + Error = _error, + }); +} diff --git a/src/Caliburn.Micro.Core/Result/Impl/TaskResult.cs b/src/Caliburn.Micro.Core/Result/Impl/TaskResult.cs new file mode 100644 index 000000000..27e43f507 --- /dev/null +++ b/src/Caliburn.Micro.Core/Result/Impl/TaskResult.cs @@ -0,0 +1,52 @@ +using System; +using System.Threading.Tasks; + +namespace Caliburn.Micro; + +/// +/// A couroutine that encapsulates an . +/// +public class TaskResult : IResult { + private readonly Task _innerTask; + + /// + /// Initializes a new instance of the class. + /// + /// The task. + public TaskResult(Task task) + => _innerTask = task; + + /// + /// Occurs when execution has completed. + /// + public event EventHandler Completed + = (sender, e) => { }; + + /// + /// Executes the result using the specified context. + /// + /// The context. + public void Execute(CoroutineExecutionContext context) { + if (_innerTask.IsCompleted) { + OnCompleted(_innerTask); + + return; + } + + _innerTask.ContinueWith( + OnCompleted, + System.Threading.SynchronizationContext.Current != null ? TaskScheduler.FromCurrentSynchronizationContext() : TaskScheduler.Current); + } + + /// + /// Called when the asynchronous task has completed. + /// + /// The completed task. + protected virtual void OnCompleted(Task task) + => Completed( + this, + new ResultCompletionEventArgs { + WasCancelled = task.IsCanceled, + Error = task.Exception, + }); +} diff --git a/src/Caliburn.Micro.Core/Result/Impl/TaskResult{TResult}.cs b/src/Caliburn.Micro.Core/Result/Impl/TaskResult{TResult}.cs new file mode 100644 index 000000000..588f70cb2 --- /dev/null +++ b/src/Caliburn.Micro.Core/Result/Impl/TaskResult{TResult}.cs @@ -0,0 +1,39 @@ +using System; +using System.Threading.Tasks; + +namespace Caliburn.Micro; + +/// +/// A couroutine that encapsulates an . +/// +/// The type of the result. +public class TaskResult : TaskResult, IResult { + /// + /// Initializes a new instance of the class. + /// + /// The task. + public TaskResult(Task task) + : base(task) { + } + + /// + /// Gets the result of the asynchronous operation. + /// + public TResult Result { get; private set; } + + /// + /// Called when the asynchronous task has completed. + /// + /// The completed task. + protected override void OnCompleted(Task task) { + if (task.IsFaulted || task.IsCanceled) { + base.OnCompleted(task); + + return; + } + + Result = ((Task)task).Result; + + base.OnCompleted(task); + } +} diff --git a/src/Caliburn.Micro.Core/ResultCompletionEventArgs.cs b/src/Caliburn.Micro.Core/ResultCompletionEventArgs.cs deleted file mode 100644 index e6e86b196..000000000 --- a/src/Caliburn.Micro.Core/ResultCompletionEventArgs.cs +++ /dev/null @@ -1,22 +0,0 @@ -using System; - -namespace Caliburn.Micro -{ - /// - /// The event args for the Completed event of an . - /// - public class ResultCompletionEventArgs : EventArgs - { - /// - /// Gets or sets the error if one occurred. - /// - /// The error. - public Exception Error; - - /// - /// Gets or sets a value indicating whether the result was cancelled. - /// - /// true if cancelled; otherwise, false. - public bool WasCancelled; - } -} diff --git a/src/Caliburn.Micro.Core/ResultDecoratorBase.cs b/src/Caliburn.Micro.Core/ResultDecoratorBase.cs deleted file mode 100644 index 00b17e79a..000000000 --- a/src/Caliburn.Micro.Core/ResultDecoratorBase.cs +++ /dev/null @@ -1,71 +0,0 @@ -using System; - -namespace Caliburn.Micro -{ - /// - /// Base class for all decorators. - /// - public abstract class ResultDecoratorBase : IResult - { - private readonly IResult innerResult; - private CoroutineExecutionContext context; - - /// - /// Initializes a new instance of the class. - /// - /// The result to decorate. - protected ResultDecoratorBase(IResult result) - { - innerResult = result ?? throw new ArgumentNullException("result"); - } - - /// - /// Executes the result using the specified context. - /// - /// The context. - public void Execute(CoroutineExecutionContext context) - { - this.context = context; - - try - { - innerResult.Completed += InnerResultCompleted; - IoC.BuildUp(innerResult); - innerResult.Execute(this.context); - } - catch (Exception ex) - { - InnerResultCompleted(innerResult, new ResultCompletionEventArgs { Error = ex }); - } - } - - private void InnerResultCompleted(object sender, ResultCompletionEventArgs args) - { - innerResult.Completed -= InnerResultCompleted; - OnInnerResultCompleted(context, innerResult, args); - context = null; - } - - /// - /// Called when the execution of the decorated result has completed. - /// - /// The context. - /// The decorated result. - /// The instance containing the event data. - protected abstract void OnInnerResultCompleted(CoroutineExecutionContext context, IResult innerResult, ResultCompletionEventArgs args); - - /// - /// Occurs when execution has completed. - /// - public event EventHandler Completed = delegate { }; - - /// - /// Raises the event. - /// - /// The instance containing the event data. - protected void OnCompleted(ResultCompletionEventArgs args) - { - Completed(this, args); - } - } -} diff --git a/src/Caliburn.Micro.Core/ResultExtensions.cs b/src/Caliburn.Micro.Core/ResultExtensions.cs deleted file mode 100644 index 4c1c05f57..000000000 --- a/src/Caliburn.Micro.Core/ResultExtensions.cs +++ /dev/null @@ -1,59 +0,0 @@ -using System; - -namespace Caliburn.Micro -{ - /// - /// Extension methods for instances. - /// - public static class ResultExtensions - { - /// - /// Adds behavior to the result which is executed when the was cancelled. - /// - /// The result to decorate. - /// The coroutine to execute when was canceled. - /// - public static IResult WhenCancelled(this IResult result, Func coroutine) - { - return new ContinueResultDecorator(result, coroutine); - } - - /// - /// Overrides of the decorated instance. - /// - /// The result to decorate. - /// - public static IResult OverrideCancel(this IResult result) - { - return new OverrideCancelResultDecorator(result); - } - - /// - /// Rescues from the decorated by executing a coroutine. - /// - /// The type of the exception we want to perform the rescue on. - /// The result to decorate. - /// The rescue coroutine. - /// Set to true to cancel the result after executing rescue. - /// - public static IResult Rescue(this IResult result, Func rescue, - bool cancelResult = true) - where TException : Exception - { - return new RescueResultDecorator(result, rescue, cancelResult); - } - - /// - /// Rescues any exception from the decorated by executing a coroutine. - /// - /// The result to decorate. - /// The rescue coroutine. - /// Set to true to cancel the result after executing rescue. - /// - public static IResult Rescue(this IResult result, Func rescue, - bool cancelResult = true) - { - return Rescue(result, rescue, cancelResult); - } - } -} diff --git a/src/Caliburn.Micro.Core/Screen.cs b/src/Caliburn.Micro.Core/Screen.cs deleted file mode 100644 index 5175e64f3..000000000 --- a/src/Caliburn.Micro.Core/Screen.cs +++ /dev/null @@ -1,209 +0,0 @@ -using System; -using System.Threading; -using System.Threading.Tasks; - -namespace Caliburn.Micro -{ - /// - /// A base implementation of . - /// - public class Screen : ViewAware, IScreen, IChild - { - private static readonly ILog Log = LogManager.GetLog(typeof(Screen)); - private string _displayName; - - private bool _isActive; - private bool _isInitialized; - private object _parent; - - /// - /// Creates an instance of the screen. - /// - public Screen() - { - _displayName = GetType().FullName; - } - - /// - /// Indicates whether or not this instance is currently initialized. - /// Virtualized in order to help with document oriented view models. - /// - public virtual bool IsInitialized - { - get => _isInitialized; - private set - { - _isInitialized = value; - NotifyOfPropertyChange(); - } - } - - /// - /// Gets or Sets the Parent - /// - public virtual object Parent - { - get => _parent; - set - { - _parent = value; - NotifyOfPropertyChange(); - } - } - - /// - /// Gets or Sets the Display Name - /// - public virtual string DisplayName - { - get => _displayName; - set - { - _displayName = value; - NotifyOfPropertyChange(); - } - } - - /// - /// Indicates whether or not this instance is currently active. - /// Virtualized in order to help with document oriented view models. - /// - public virtual bool IsActive - { - get => _isActive; - private set - { - _isActive = value; - NotifyOfPropertyChange(); - } - } - - /// - /// Raised after activation occurs. - /// - public virtual event AsyncEventHandler Activated = delegate { return Task.FromResult(true); }; - - /// - /// Raised before deactivation. - /// - public virtual event EventHandler AttemptingDeactivation = delegate { }; - - /// - /// Raised after deactivation. - /// - public virtual event AsyncEventHandler Deactivated = delegate { return Task.FromResult(true); }; - - async Task IActivate.ActivateAsync(CancellationToken cancellationToken) - { - if (IsActive) - return; - - var initialized = false; - - if (!IsInitialized) - { - await OnInitializeAsync(cancellationToken); - IsInitialized = initialized = true; - } - - Log.Info("Activating {0}.", this); - await OnActivateAsync(cancellationToken); - IsActive = true; - await OnActivatedAsync(cancellationToken); - - await (Activated?.InvokeAllAsync(this, new ActivationEventArgs - { - WasInitialized = initialized - }) ?? Task.FromResult(true)); - } - - async Task IDeactivate.DeactivateAsync(bool close, CancellationToken cancellationToken) - { - if (IsActive || IsInitialized && close) - { - AttemptingDeactivation?.Invoke(this, new DeactivationEventArgs - { - WasClosed = close - }); - - Log.Info("Deactivating {0}.", this); - await OnDeactivateAsync(close, cancellationToken); - IsActive = false; - - await (Deactivated?.InvokeAllAsync(this, new DeactivationEventArgs - { - WasClosed = close - }) ?? Task.FromResult(true)); - - if (close) - { - Views.Clear(); - Log.Info("Closed {0}.", this); - } - } - } - - /// - /// Called to check whether or not this instance can close. - /// - /// The cancellation token to cancel operation. - /// A task that represents the asynchronous operation and holds the value of the close check.. - public virtual Task CanCloseAsync(CancellationToken cancellationToken = default) - { - return Task.FromResult(true); - } - - /// - /// Tries to close this instance by asking its Parent to initiate shutdown or by asking its corresponding view to close. - /// Also provides an opportunity to pass a dialog result to it's corresponding view. - /// - /// The dialog result. - public virtual async Task TryCloseAsync(bool? dialogResult = null) - { - if (Parent is IConductor conductor) - { - await conductor.CloseItemAsync(this, CancellationToken.None); - } - - var closeAction = PlatformProvider.Current.GetViewCloseAction(this, Views.Values, dialogResult); - - await Execute.OnUIThreadAsync(async () => await closeAction(CancellationToken.None)); - } - - /// - /// Called when initializing. - /// - protected virtual Task OnInitializeAsync(CancellationToken cancellationToken) - { - return Task.FromResult(true); - } - - /// - /// Called when activating. - /// - protected virtual Task OnActivateAsync(CancellationToken cancellationToken) - { - return Task.FromResult(true); - } - - - /// - /// Called when view has been activated. - /// - protected virtual Task OnActivatedAsync(CancellationToken cancellationToken) - { - return Task.FromResult(true); - } - - /// - /// Called when deactivating. - /// - /// Indicates whether this instance will be closed. - /// The cancellation token to cancel operation. - /// A task that represents the asynchronous operation. - protected virtual Task OnDeactivateAsync(bool close, CancellationToken cancellationToken) - { - return Task.FromResult(true); - } - } -} diff --git a/src/Caliburn.Micro.Core/Screen/Contracts/IActivate.cs b/src/Caliburn.Micro.Core/Screen/Contracts/IActivate.cs new file mode 100644 index 000000000..53411f516 --- /dev/null +++ b/src/Caliburn.Micro.Core/Screen/Contracts/IActivate.cs @@ -0,0 +1,27 @@ +using System; +using System.Threading; +using System.Threading.Tasks; + +namespace Caliburn.Micro; + +/// +/// Denotes an instance which requires activation. +/// +public interface IActivate { + /// + /// Raised after activation occurs. + /// + event AsyncEventHandler Activated; + + /// + /// Gets a value indicating whether or not this instance is active. + /// + bool IsActive { get; } + + /// + /// Activates this instance. + /// + /// The cancellation token to cancel operation. + /// A task that represents the asynchronous operation. + Task ActivateAsync(CancellationToken cancellationToken = default); +} diff --git a/src/Caliburn.Micro.Core/Screen/Contracts/IChild.cs b/src/Caliburn.Micro.Core/Screen/Contracts/IChild.cs new file mode 100644 index 000000000..57c2c23e7 --- /dev/null +++ b/src/Caliburn.Micro.Core/Screen/Contracts/IChild.cs @@ -0,0 +1,11 @@ +namespace Caliburn.Micro; + +/// +/// Denotes a node within a parent/child hierarchy. +/// +public interface IChild { + /// + /// Gets or Sets the Parent. + /// + object Parent { get; set; } +} diff --git a/src/Caliburn.Micro.Core/Screen/Contracts/IChild{TParent}.cs b/src/Caliburn.Micro.Core/Screen/Contracts/IChild{TParent}.cs new file mode 100644 index 000000000..5a5c963c9 --- /dev/null +++ b/src/Caliburn.Micro.Core/Screen/Contracts/IChild{TParent}.cs @@ -0,0 +1,12 @@ +namespace Caliburn.Micro; + +/// +/// Denotes a node within a parent/child hierarchy. +/// +/// The type of parent. +public interface IChild : IChild { + /// + /// Gets or Sets the Parent. + /// + new TParent Parent { get; set; } +} diff --git a/src/Caliburn.Micro.Core/Screen/Contracts/IClose.cs b/src/Caliburn.Micro.Core/Screen/Contracts/IClose.cs new file mode 100644 index 000000000..b321ad7e4 --- /dev/null +++ b/src/Caliburn.Micro.Core/Screen/Contracts/IClose.cs @@ -0,0 +1,15 @@ +using System.Threading.Tasks; + +namespace Caliburn.Micro; + +/// +/// Denotes an object that can be closed. +/// +public interface IClose { + /// + /// Tries to close this instance. + /// Also provides an opportunity to pass a dialog result to it's corresponding view. + /// + /// The dialog result. + Task TryCloseAsync(bool? dialogResult = null); +} diff --git a/src/Caliburn.Micro.Core/Screen/Contracts/IDeactivate.cs b/src/Caliburn.Micro.Core/Screen/Contracts/IDeactivate.cs new file mode 100644 index 000000000..f5f48ae83 --- /dev/null +++ b/src/Caliburn.Micro.Core/Screen/Contracts/IDeactivate.cs @@ -0,0 +1,28 @@ +using System; +using System.Threading; +using System.Threading.Tasks; + +namespace Caliburn.Micro; + +/// +/// Denotes an instance which requires deactivation. +/// +public interface IDeactivate { + /// + /// Raised before deactivation. + /// + event EventHandler AttemptingDeactivation; + + /// + /// Raised after deactivation. + /// + event AsyncEventHandler Deactivated; + + /// + /// Deactivates this instance. + /// + /// Indicates whether or not this instance is being closed. + /// The cancellation token to cancel operation. + /// A task that represents the asynchronous operation. + Task DeactivateAsync(bool close, CancellationToken cancellationToken = default); +} diff --git a/src/Caliburn.Micro.Core/Screen/Contracts/IGuardClose.cs b/src/Caliburn.Micro.Core/Screen/Contracts/IGuardClose.cs new file mode 100644 index 000000000..183b3b0c5 --- /dev/null +++ b/src/Caliburn.Micro.Core/Screen/Contracts/IGuardClose.cs @@ -0,0 +1,16 @@ +using System.Threading; +using System.Threading.Tasks; + +namespace Caliburn.Micro; + +/// +/// Denotes an instance which may prevent closing. +/// +public interface IGuardClose : IClose { + /// + /// Called to check whether or not this instance can close. + /// + /// The cancellation token to cancel operation. + /// A task that represents the asynchronous operation and contains the result of the close. + Task CanCloseAsync(CancellationToken cancellationToken = default); +} diff --git a/src/Caliburn.Micro.Core/Screen/Contracts/IHaveDisplayName.cs b/src/Caliburn.Micro.Core/Screen/Contracts/IHaveDisplayName.cs new file mode 100644 index 000000000..7365a4188 --- /dev/null +++ b/src/Caliburn.Micro.Core/Screen/Contracts/IHaveDisplayName.cs @@ -0,0 +1,11 @@ +namespace Caliburn.Micro; + +/// +/// Denotes an instance which has a display name. +/// +public interface IHaveDisplayName { + /// + /// Gets or Sets the Display Name. + /// + string DisplayName { get; set; } +} diff --git a/src/Caliburn.Micro.Core/Screen/Contracts/IScreen.cs b/src/Caliburn.Micro.Core/Screen/Contracts/IScreen.cs new file mode 100644 index 000000000..8b21c184f --- /dev/null +++ b/src/Caliburn.Micro.Core/Screen/Contracts/IScreen.cs @@ -0,0 +1,8 @@ +namespace Caliburn.Micro; + +/// +/// Denotes an instance which implements , , +/// , and . +/// +public interface IScreen : IHaveDisplayName, IActivate, IDeactivate, IGuardClose, INotifyPropertyChangedEx { +} diff --git a/src/Caliburn.Micro.Core/Screen/EventArgs/ActivationEventArgs.cs b/src/Caliburn.Micro.Core/Screen/EventArgs/ActivationEventArgs.cs new file mode 100644 index 000000000..ac6c2cc36 --- /dev/null +++ b/src/Caliburn.Micro.Core/Screen/EventArgs/ActivationEventArgs.cs @@ -0,0 +1,13 @@ +using System; + +namespace Caliburn.Micro; + +/// +/// EventArgs sent during activation. +/// +public class ActivationEventArgs : EventArgs { + /// + /// Gets or sets a value indicating whether the sender was initialized in addition to being activated. + /// + public bool WasInitialized { get; set; } +} diff --git a/src/Caliburn.Micro.Core/Screen/EventArgs/DeactivationEventArgs.cs b/src/Caliburn.Micro.Core/Screen/EventArgs/DeactivationEventArgs.cs new file mode 100644 index 000000000..dac45989b --- /dev/null +++ b/src/Caliburn.Micro.Core/Screen/EventArgs/DeactivationEventArgs.cs @@ -0,0 +1,13 @@ +using System; + +namespace Caliburn.Micro; + +/// +/// EventArgs sent during deactivation. +/// +public class DeactivationEventArgs : EventArgs { + /// + /// Gets or sets a value indicating whether the sender was closed in addition to being deactivated. + /// + public bool WasClosed { get; set; } +} diff --git a/src/Caliburn.Micro.Core/Screen/EventHandlers/AsyncEventHandler.cs b/src/Caliburn.Micro.Core/Screen/EventHandlers/AsyncEventHandler.cs new file mode 100644 index 000000000..5c869983f --- /dev/null +++ b/src/Caliburn.Micro.Core/Screen/EventHandlers/AsyncEventHandler.cs @@ -0,0 +1,14 @@ +using System; +using System.Threading.Tasks; + +namespace Caliburn.Micro; + +/// +/// Async Event Handler. +/// +/// The Event Args type. +/// Event source. +/// Event argument. +/// Task. +public delegate Task AsyncEventHandler(object sender, TEventArgs e) + where TEventArgs : EventArgs; diff --git a/src/Caliburn.Micro.Core/Screen/Extensions/ActivateExtensions.cs b/src/Caliburn.Micro.Core/Screen/Extensions/ActivateExtensions.cs new file mode 100644 index 000000000..2ecc15ace --- /dev/null +++ b/src/Caliburn.Micro.Core/Screen/Extensions/ActivateExtensions.cs @@ -0,0 +1,16 @@ +using System.Threading.Tasks; + +namespace Caliburn.Micro; + +/// +/// Extension methods for the instance. +/// +public static class ActivateExtensions { + /// + /// Activates this instance. + /// + /// The instance to activate. + /// A task that represents the asynchronous operation. + public static Task ActivateAsync(this IActivate activate) + => activate.ActivateAsync(default); +} diff --git a/src/Caliburn.Micro.Core/Screen/Extensions/AsyncEventHandlerExtensions.cs b/src/Caliburn.Micro.Core/Screen/Extensions/AsyncEventHandlerExtensions.cs new file mode 100644 index 000000000..37c908b41 --- /dev/null +++ b/src/Caliburn.Micro.Core/Screen/Extensions/AsyncEventHandlerExtensions.cs @@ -0,0 +1,35 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Threading.Tasks; + +namespace Caliburn.Micro; + +/// +/// Async EventHandler Extensions. +/// +public static class AsyncEventHandlerExtensions { + /// + /// Get Invocation List of AsyncEventHandler. + /// + /// The Event args type. + /// Async EventHandler. + /// List of AsyncEventHandler. + public static IEnumerable> GetHandlers(this AsyncEventHandler handler) + where TEventArgs : EventArgs + => handler.GetInvocationList().Cast>(); + + /// + /// Invoke all handlers of AsyncEventHandler. + /// + /// The Event args type. + /// Async EventHandler. + /// The event source. + /// The Event args. + /// Task. + public static Task InvokeAllAsync(this AsyncEventHandler handler, object sender, TEventArgs e) + where TEventArgs : EventArgs + => Task.WhenAll( + handler.GetHandlers() + .Select(handleAsync => handleAsync(sender, e))); +} diff --git a/src/Caliburn.Micro.Core/Screen/Extensions/DeactivateExtensions.cs b/src/Caliburn.Micro.Core/Screen/Extensions/DeactivateExtensions.cs new file mode 100644 index 000000000..280da723f --- /dev/null +++ b/src/Caliburn.Micro.Core/Screen/Extensions/DeactivateExtensions.cs @@ -0,0 +1,17 @@ +using System.Threading.Tasks; + +namespace Caliburn.Micro; + +/// +/// Extension methods for the instance. +/// +public static class DeactivateExtensions { + /// + /// Deactivates this instance. + /// + /// The instance to deactivate. + /// Indicates whether or not this instance is being closed. + /// A task that represents the asynchronous operation. + public static Task DeactivateAsync(this IDeactivate deactivate, bool close) + => deactivate.DeactivateAsync(close, default); +} diff --git a/src/Caliburn.Micro.Core/Screen/Extensions/ScreenExtensions.cs b/src/Caliburn.Micro.Core/Screen/Extensions/ScreenExtensions.cs new file mode 100644 index 000000000..a71455385 --- /dev/null +++ b/src/Caliburn.Micro.Core/Screen/Extensions/ScreenExtensions.cs @@ -0,0 +1,137 @@ +using System; +using System.Threading; +using System.Threading.Tasks; + +namespace Caliburn.Micro; + +/// +/// Hosts extension methods for classes. +/// +public static class ScreenExtensions { + /// + /// Activates the item if it implements , otherwise does nothing. + /// + /// The potential activatable. + public static Task TryActivateAsync(object potentialActivatable) + => potentialActivatable is IActivate activator ? activator.ActivateAsync(CancellationToken.None) : Task.FromResult(true); + + /// + /// Activates the item if it implements , otherwise does nothing. + /// + /// The potential activatable. + /// The cancellation token to cancel operation. + /// A task that represents the asynchronous operation. + public static Task TryActivateAsync(object potentialActivatable, CancellationToken cancellationToken) + => potentialActivatable is IActivate activator ? activator.ActivateAsync(cancellationToken) : Task.FromResult(true); + + /// + /// Deactivates the item if it implements , otherwise does nothing. + /// + /// The potential deactivatable. + /// Indicates whether or not to close the item after deactivating it. + /// A task that represents the asynchronous operation. + public static Task TryDeactivateAsync(object potentialDeactivatable, bool close) + => potentialDeactivatable is IDeactivate deactivator ? deactivator.DeactivateAsync(close, CancellationToken.None) : Task.FromResult(true); + + /// + /// Deactivates the item if it implements , otherwise does nothing. + /// + /// The potential deactivatable. + /// Indicates whether or not to close the item after deactivating it. + /// The cancellation token to cancel operation. + /// A task that represents the asynchronous operation. + public static Task TryDeactivateAsync(object potentialDeactivatable, bool close, CancellationToken cancellationToken) + => potentialDeactivatable is IDeactivate deactivator ? deactivator.DeactivateAsync(close, cancellationToken) : Task.FromResult(true); + + /// + /// Closes the specified item. + /// + /// The conductor. + /// The item to close. + /// A task that represents the asynchronous operation. + public static Task CloseItemAsync(this IConductor conductor, object item) + => conductor.DeactivateItemAsync(item, true, CancellationToken.None); + + /// + /// Closes the specified item. + /// + /// The conductor. + /// The item to close. + /// The cancellation token to cancel operation. + /// A task that represents the asynchronous operation. + public static Task CloseItemAsync(this IConductor conductor, object item, CancellationToken cancellationToken) + => conductor.DeactivateItemAsync(item, true, cancellationToken); + + /// + /// Closes the specified item. + /// + /// The conductor. + /// The item to close. + /// A task that represents the asynchronous operation. + public static Task CloseItemAsync(this ConductorBase conductor, T item) + where T : class + => conductor.DeactivateItemAsync(item, true, CancellationToken.None); + + /// + /// Closes the specified item. + /// + /// The conductor. + /// The item to close. + /// The cancellation token to cancel operation. + /// A task that represents the asynchronous operation. + public static Task CloseItemAsync(this ConductorBase conductor, T item, CancellationToken cancellationToken) + where T : class + => conductor.DeactivateItemAsync(item, true, cancellationToken); + + /// + /// Activates a child whenever the specified parent is activated. + /// + /// The child to activate. + /// The parent whose activation triggers the child's activation. + public static void ActivateWith(this IActivate child, IActivate parent) { + var childReference = new WeakReference(child); + + async Task OnParentActivated(object s, ActivationEventArgs e) { + var activatable = (IActivate)childReference.Target; + if (activatable == null) { + ((IActivate)s).Activated -= OnParentActivated; + } else { + await activatable.ActivateAsync(CancellationToken.None); + } + } + + parent.Activated += OnParentActivated; + } + + /// + /// Deactivates a child whenever the specified parent is deactivated. + /// + /// The child to deactivate. + /// The parent whose deactivation triggers the child's deactivation. + public static void DeactivateWith(this IDeactivate child, IDeactivate parent) { + var childReference = new WeakReference(child); + + async Task OnParentDeactivated(object s, DeactivationEventArgs e) { + var deactivatable = (IDeactivate)childReference.Target; + if (deactivatable == null) { + ((IDeactivate)s).Deactivated -= OnParentDeactivated; + } else { + await deactivatable.DeactivateAsync(e.WasClosed, CancellationToken.None); + } + } + + parent.Deactivated += OnParentDeactivated; + } + + /// + /// Activates and Deactivates a child whenever the specified parent is Activated or Deactivated. + /// + /// The child to activate/deactivate. + /// The parent whose activation/deactivation triggers the child's activation/deactivation. + public static void ConductWith(this TChild child, TParent parent) + where TChild : IActivate, IDeactivate + where TParent : IActivate, IDeactivate { + child.ActivateWith(parent); + child.DeactivateWith(parent); + } +} diff --git a/src/Caliburn.Micro.Core/Screen/Screen.cs b/src/Caliburn.Micro.Core/Screen/Screen.cs new file mode 100644 index 000000000..99806ca52 --- /dev/null +++ b/src/Caliburn.Micro.Core/Screen/Screen.cs @@ -0,0 +1,184 @@ +using System; +using System.Threading; +using System.Threading.Tasks; + +namespace Caliburn.Micro; + +/// +/// A base implementation of . +/// +public class Screen : ViewAware, IScreen, IChild { + private static readonly ILog Log = LogManager.GetLog(typeof(Screen)); + private string _displayName; + + private bool _isActive; + private bool _isInitialized; + private object _parent; + + /// + /// Initializes a new instance of the class. + /// + public Screen() + => _displayName = GetType().FullName; + + /// + /// Raised after activation occurs. + /// + public virtual event AsyncEventHandler Activated + = (sender, e) => Task.FromResult(true); + + /// + /// Raised before deactivation. + /// + public virtual event EventHandler AttemptingDeactivation + = (sender, e) => { }; + + /// + /// Raised after deactivation. + /// + public virtual event AsyncEventHandler Deactivated + = (sender, e) => Task.FromResult(true); + + /// + /// Gets a value indicating whether or not this instance is currently initialized. + /// Virtualized in order to help with document oriented view models. + /// + public virtual bool IsInitialized { + get => _isInitialized; + private set { + _isInitialized = value; + NotifyOfPropertyChange(); + } + } + + /// + /// Gets or Sets the Parent . + /// + public virtual object Parent { + get => _parent; + set { + _parent = value; + NotifyOfPropertyChange(); + } + } + + /// + /// Gets or Sets the Display Name. + /// + public virtual string DisplayName { + get => _displayName; + set { + _displayName = value; + NotifyOfPropertyChange(); + } + } + + /// + /// Gets a value indicating whether or not this instance is currently active. + /// Virtualized in order to help with document oriented view models. + /// + public virtual bool IsActive { + get => _isActive; + private set { + _isActive = value; + NotifyOfPropertyChange(); + } + } + + async Task IActivate.ActivateAsync(CancellationToken cancellationToken) { + if (IsActive) { + return; + } + + bool initialized = false; + + if (!IsInitialized) { + await OnInitializeAsync(cancellationToken); + IsInitialized = initialized = true; + } + + Log.Info("Activating {0}.", this); + await OnActivateAsync(cancellationToken); + IsActive = true; + await OnActivatedAsync(cancellationToken); + + await (Activated?.InvokeAllAsync(this, new ActivationEventArgs { + WasInitialized = initialized, + }) ?? Task.FromResult(true)); + } + + async Task IDeactivate.DeactivateAsync(bool close, CancellationToken cancellationToken) { + if (!IsActive && (!IsInitialized || !close)) { + return; + } + + AttemptingDeactivation?.Invoke(this, new DeactivationEventArgs { + WasClosed = close, + }); + + Log.Info("Deactivating {0}.", this); + await OnDeactivateAsync(close, cancellationToken); + IsActive = false; + + await (Deactivated?.InvokeAllAsync(this, new DeactivationEventArgs { + WasClosed = close, + }) ?? Task.FromResult(true)); + + if (!close) { + return; + } + + Views.Clear(); + Log.Info("Closed {0}.", this); + } + + /// + /// Called to check whether or not this instance can close. + /// + /// The cancellation token to cancel operation. + /// A task that represents the asynchronous operation and holds the value of the close check.. + public virtual Task CanCloseAsync(CancellationToken cancellationToken = default) + => Task.FromResult(true); + + /// + /// Tries to close this instance by asking its Parent to initiate shutdown or by asking its corresponding view to close. + /// Also provides an opportunity to pass a dialog result to it's corresponding view. + /// + /// The dialog result. + public virtual async Task TryCloseAsync(bool? dialogResult = null) { + if (Parent is IConductor conductor) { + await conductor.CloseItemAsync(this, CancellationToken.None); + } + + Func closeAction = PlatformProvider.Current.GetViewCloseAction(this, Views.Values, dialogResult); + + await Execute.OnUIThreadAsync(async () => await closeAction(CancellationToken.None)); + } + + /// + /// Called when initializing. + /// + protected virtual Task OnInitializeAsync(CancellationToken cancellationToken) + => Task.FromResult(true); + + /// + /// Called when activating. + /// + protected virtual Task OnActivateAsync(CancellationToken cancellationToken) + => Task.FromResult(true); + + /// + /// Called when view has been activated. + /// + protected virtual Task OnActivatedAsync(CancellationToken cancellationToken) + => Task.FromResult(true); + + /// + /// Called when deactivating. + /// + /// Indicates whether this instance will be closed. + /// The cancellation token to cancel operation. + /// A task that represents the asynchronous operation. + protected virtual Task OnDeactivateAsync(bool close, CancellationToken cancellationToken) + => Task.FromResult(true); +} diff --git a/src/Caliburn.Micro.Core/ScreenExtensions.cs b/src/Caliburn.Micro.Core/ScreenExtensions.cs deleted file mode 100644 index f0956f78f..000000000 --- a/src/Caliburn.Micro.Core/ScreenExtensions.cs +++ /dev/null @@ -1,156 +0,0 @@ -using System; -using System.Threading; -using System.Threading.Tasks; - -namespace Caliburn.Micro -{ - /// - /// Hosts extension methods for classes. - /// - public static class ScreenExtensions - { - /// - /// Activates the item if it implements , otherwise does nothing. - /// - /// The potential activatable. - public static Task TryActivateAsync(object potentialActivatable) - { - return potentialActivatable is IActivate activator ? activator.ActivateAsync(CancellationToken.None) : Task.FromResult(true); - } - - /// - /// Activates the item if it implements , otherwise does nothing. - /// - /// The potential activatable. - /// The cancellation token to cancel operation. - /// A task that represents the asynchronous operation. - public static Task TryActivateAsync(object potentialActivatable, CancellationToken cancellationToken) - { - return potentialActivatable is IActivate activator ? activator.ActivateAsync(cancellationToken) : Task.FromResult(true); - } - - /// - /// Deactivates the item if it implements , otherwise does nothing. - /// - /// The potential deactivatable. - /// Indicates whether or not to close the item after deactivating it. - /// A task that represents the asynchronous operation. - public static Task TryDeactivateAsync(object potentialDeactivatable, bool close) - { - return potentialDeactivatable is IDeactivate deactivator ? deactivator.DeactivateAsync(close, CancellationToken.None) : Task.FromResult(true); - } - - /// - /// Deactivates the item if it implements , otherwise does nothing. - /// - /// The potential deactivatable. - /// Indicates whether or not to close the item after deactivating it. - /// The cancellation token to cancel operation. - /// A task that represents the asynchronous operation. - public static Task TryDeactivateAsync(object potentialDeactivatable, bool close, CancellationToken cancellationToken) - { - return potentialDeactivatable is IDeactivate deactivator ? deactivator.DeactivateAsync(close, cancellationToken): Task.FromResult(true); - } - - /// - /// Closes the specified item. - /// - /// The conductor. - /// The item to close. - /// A task that represents the asynchronous operation. - public static Task CloseItemAsync(this IConductor conductor, object item) - { - return conductor.DeactivateItemAsync(item, true, CancellationToken.None); - } - - /// - /// Closes the specified item. - /// - /// The conductor. - /// The item to close. - /// The cancellation token to cancel operation. - /// A task that represents the asynchronous operation. - public static Task CloseItemAsync(this IConductor conductor, object item, CancellationToken cancellationToken) - { - return conductor.DeactivateItemAsync(item, true, cancellationToken); - } - - /// - /// Closes the specified item. - /// - /// The conductor. - /// The item to close. - /// A task that represents the asynchronous operation. - public static Task CloseItemAsync(this ConductorBase conductor, T item) where T : class - { - return conductor.DeactivateItemAsync(item, true, CancellationToken.None); - } - - /// - /// Closes the specified item. - /// - /// The conductor. - /// The item to close. - /// The cancellation token to cancel operation. - /// A task that represents the asynchronous operation. - public static Task CloseItemAsync(this ConductorBase conductor, T item, CancellationToken cancellationToken) where T : class - { - return conductor.DeactivateItemAsync(item, true, cancellationToken); - } - - /// - /// Activates a child whenever the specified parent is activated. - /// - ///The child to activate. - ///The parent whose activation triggers the child's activation. - public static void ActivateWith(this IActivate child, IActivate parent) - { - var childReference = new WeakReference(child); - - async Task OnParentActivated(object s, ActivationEventArgs e) - { - var activatable = (IActivate)childReference.Target; - if (activatable == null) - ((IActivate)s).Activated -= OnParentActivated; - else - await activatable.ActivateAsync(CancellationToken.None); - } - - parent.Activated += OnParentActivated; - } - - /// - /// Deactivates a child whenever the specified parent is deactivated. - /// - ///The child to deactivate. - ///The parent whose deactivation triggers the child's deactivation. - public static void DeactivateWith(this IDeactivate child, IDeactivate parent) - { - var childReference = new WeakReference(child); - - async Task OnParentDeactivated(object s, DeactivationEventArgs e) - { - var deactivatable = (IDeactivate)childReference.Target; - if (deactivatable == null) - ((IDeactivate)s).Deactivated -= OnParentDeactivated; - else - await deactivatable.DeactivateAsync(e.WasClosed, CancellationToken.None); - } - - parent.Deactivated += OnParentDeactivated; - } - - /// - /// Activates and Deactivates a child whenever the specified parent is Activated or Deactivated. - /// - ///The child to activate/deactivate. - ///The parent whose activation/deactivation triggers the child's activation/deactivation. - public static void ConductWith(this TChild child, TParent parent) - where TChild : IActivate, IDeactivate - where TParent : IActivate, IDeactivate - { - child.ActivateWith(parent); - child.DeactivateWith(parent); - } - } -} diff --git a/src/Caliburn.Micro.Core/SequentialResult.cs b/src/Caliburn.Micro.Core/SequentialResult.cs deleted file mode 100644 index e6858a087..000000000 --- a/src/Caliburn.Micro.Core/SequentialResult.cs +++ /dev/null @@ -1,90 +0,0 @@ -using System; -using System.Collections.Generic; - -namespace Caliburn.Micro -{ - /// - /// An implementation of that enables sequential execution of multiple results. - /// - public class SequentialResult : IResult - { - private readonly IEnumerator enumerator; - private CoroutineExecutionContext context; - - /// - /// Initializes a new instance of the class. - /// - /// The enumerator. - public SequentialResult(IEnumerator enumerator) - { - this.enumerator = enumerator; - } - - /// - /// Occurs when execution has completed. - /// - public event EventHandler Completed = delegate { }; - - /// - /// Executes the result using the specified context. - /// - /// The context. - public void Execute(CoroutineExecutionContext context) - { - this.context = context; - ChildCompleted(null, new ResultCompletionEventArgs()); - } - - private void ChildCompleted(object sender, ResultCompletionEventArgs args) - { - var previous = sender as IResult; - if (previous != null) - { - previous.Completed -= ChildCompleted; - } - - if (args.Error != null || args.WasCancelled) - { - OnComplete(args.Error, args.WasCancelled); - return; - } - - var moveNextSucceeded = false; - try - { - moveNextSucceeded = enumerator.MoveNext(); - } - catch (Exception ex) - { - OnComplete(ex, false); - return; - } - - if (moveNextSucceeded) - { - try - { - var next = enumerator.Current; - IoC.BuildUp(next); - next.Completed += ChildCompleted; - next.Execute(context); - } - catch (Exception ex) - { - OnComplete(ex, false); - return; - } - } - else - { - OnComplete(null, false); - } - } - - private void OnComplete(Exception error, bool wasCancelled) - { - enumerator.Dispose(); - Completed(this, new ResultCompletionEventArgs { Error = error, WasCancelled = wasCancelled }); - } - } -} diff --git a/src/Caliburn.Micro.Core/SimpleContainer.cs b/src/Caliburn.Micro.Core/SimpleContainer.cs deleted file mode 100644 index f9a948a8b..000000000 --- a/src/Caliburn.Micro.Core/SimpleContainer.cs +++ /dev/null @@ -1,324 +0,0 @@ -using System; -using System.Collections; -using System.Collections.Generic; -using System.Linq; -using System.Reflection; - -namespace Caliburn.Micro -{ - /// - /// A simple IoC container. - /// - public class SimpleContainer - { - private static readonly Type delegateType = typeof(Delegate); - private static readonly Type enumerableType = typeof(IEnumerable); - private static readonly TypeInfo enumerableTypeInfo = enumerableType.GetTypeInfo(); - private static readonly TypeInfo delegateTypeInfo = delegateType.GetTypeInfo(); - private Type simpleContainerType = typeof(SimpleContainer); - private readonly List entries; - - /// - /// Initializes a new instance of the class. - /// - public SimpleContainer() - { - entries = new List(); - } - - private SimpleContainer(IEnumerable entries) - { - this.entries = new List(entries); - } - - /// - /// Whether to enable recursive property injection for all resolutions. - /// - public bool EnablePropertyInjection { get; set; } - - /// - /// Registers the instance. - /// - /// The service. - /// The key. - /// The implementation. - public void RegisterInstance(Type service, string key, object implementation) - { - RegisterHandler(service, key, container => implementation); - } - - /// - /// Registers the class so that a new instance is created on every request. - /// - /// The service. - /// The key. - /// The implementation. - public void RegisterPerRequest(Type service, string key, Type implementation) - { - RegisterHandler(service, key, container => container.BuildInstance(implementation)); - } - - /// - /// Registers the class so that it is created once, on first request, and the same instance is returned to all requestors thereafter. - /// - /// The service. - /// The key. - /// The implementation. - public void RegisterSingleton(Type service, string key, Type implementation) - { - object singleton = null; - RegisterHandler(service, key, container => singleton ?? (singleton = container.BuildInstance(implementation))); - } - - /// - /// Registers a custom handler for serving requests from the container. - /// - /// The service. - /// The key. - /// The handler. - public void RegisterHandler(Type service, string key, Func handler) - { - GetOrCreateEntry(service, key).Add(handler); - } - - /// - /// Unregisters any handlers for the service/key that have previously been registered. - /// - /// The service. - /// The key. - public void UnregisterHandler(Type service, string key) - { - var entry = GetEntry(service, key); - if (entry != null) - { - entries.Remove(entry); - } - } - - /// - /// Requests an instance. - /// - /// The service. - /// The key. - /// The instance, or null if a handler is not found. - public object GetInstance(Type service, string key) - { - var entry = GetEntry(service, key); - if (entry != null) - { - var instance = entry.Single()(this); - - if (EnablePropertyInjection && instance != null) - { - BuildUp(instance); - } - - return instance; - } - - if (service == null) - { - return null; - } - TypeInfo serviceTypeInfo = service.GetTypeInfo(); - - if (delegateTypeInfo.IsAssignableFrom(serviceTypeInfo)) - { - var typeToCreate = serviceTypeInfo.GenericTypeArguments[0]; - var factoryFactoryType = typeof(FactoryFactory<>).MakeGenericType(typeToCreate); - var factoryFactoryHost = Activator.CreateInstance(factoryFactoryType); - var factoryFactoryMethod = factoryFactoryType.GetRuntimeMethod("Create", new Type[] { simpleContainerType }); - return factoryFactoryMethod.Invoke(factoryFactoryHost, new object[] { this }); - } - - if (enumerableTypeInfo.IsAssignableFrom(serviceTypeInfo) && serviceTypeInfo.IsGenericType) - { - var listType = serviceTypeInfo.GenericTypeArguments[0]; - var instances = GetAllInstances(listType).ToList(); - var array = Array.CreateInstance(listType, instances.Count); - - for (var i = 0; i < array.Length; i++) - { - if (EnablePropertyInjection) - { - BuildUp(instances[i]); - } - - array.SetValue(instances[i], i); - } - - return array; - } - - return null; - } - - /// - /// Determines if a handler for the service/key has previously been registered. - /// - /// The service. - /// The key. - /// True if a handler is registere; false otherwise. - public bool HasHandler(Type service, string key) - { - return GetEntry(service, key) != null; - } - - /// - /// Requests all instances of a given type and the given key (default null). - /// - /// The service. - /// The key shared by those instances - /// All the instances or an empty enumerable if none are found. - public IEnumerable GetAllInstances(Type service, string key = null) - { - var entries = GetEntry(service, key); - - if (entries == null) - { - return new object[0]; - } - - var instances = entries.Select(e => e(this)); - - foreach (var instance in instances) - { - if (EnablePropertyInjection && instance != null) - { - BuildUp(instance); - } - } - - return instances; - } - - /// - /// Pushes dependencies into an existing instance based on interface properties with setters. - /// - /// The instance. - public void BuildUp(object instance) - { - var properties = instance - .GetType() - .GetRuntimeProperties() - .Where(p => p.CanRead && p.CanWrite && p.PropertyType.GetTypeInfo().IsInterface); - - foreach (var property in properties) - { - var value = GetInstance(property.PropertyType, null); - - if (value != null) - { - property.SetValue(instance, value, null); - } - } - } - - /// - /// Creates a child container. - /// - /// A new container. - public SimpleContainer CreateChildContainer() - { - return new SimpleContainer(entries); - } - - private ContainerEntry GetOrCreateEntry(Type service, string key) - { - var entry = GetEntry(service, key); - if (entry == null) - { - entry = new ContainerEntry { Service = service, Key = key }; - entries.Add(entry); - } - - return entry; - } - - private ContainerEntry GetEntry(Type service, string key) - { - if (service == null) - { - return entries.FirstOrDefault(x => x.Key == key); - } - - if (key == null) - { - return entries.FirstOrDefault(x => x.Service == service && string.IsNullOrEmpty(x.Key)) - ?? entries.FirstOrDefault(x => x.Service == service); - } - - return entries.FirstOrDefault(x => x.Service == service && x.Key == key); - } - - /// - /// Actually does the work of creating the instance and satisfying it's constructor dependencies. - /// - /// The type. - /// - protected object BuildInstance(Type type) - { - var args = DetermineConstructorArgs(type); - return ActivateInstance(type, args); - } - - /// - /// Creates an instance of the type with the specified constructor arguments. - /// - /// The type. - /// The constructor args. - /// The created instance. - protected virtual object ActivateInstance(Type type, object[] args) - { - var instance = args.Length > 0 ? System.Activator.CreateInstance(type, args) : System.Activator.CreateInstance(type); - Activated(instance); - return instance; - } - - /// - /// Occurs when a new instance is created. - /// - public event Action Activated = delegate { }; - - private object[] DetermineConstructorArgs(Type implementation) - { - var args = new List(); - var constructor = SelectEligibleConstructor(implementation); - - if (constructor != null) - { - args.AddRange(constructor.GetParameters().Select(info => GetInstance(info.ParameterType, null))); - } - - return args.ToArray(); - } - - private ConstructorInfo SelectEligibleConstructor(Type type) - { - return type.GetTypeInfo().DeclaredConstructors - .Where(c => c.IsPublic) - .Select(c => new - { - Constructor = c, - HandledParamters = c.GetParameters().Count(p => HasHandler(p.ParameterType, null)) - }) - .OrderByDescending(c => c.HandledParamters) - .Select(c => c.Constructor) - .FirstOrDefault(); - } - - private class ContainerEntry : List> - { - public string Key; - public Type Service; - } - - private class FactoryFactory - { - public Func Create(SimpleContainer container) - { - return () => (T)container.GetInstance(typeof(T), null); - } - } - } -} diff --git a/src/Caliburn.Micro.Core/SimpleResult.cs b/src/Caliburn.Micro.Core/SimpleResult.cs deleted file mode 100644 index fcaace455..000000000 --- a/src/Caliburn.Micro.Core/SimpleResult.cs +++ /dev/null @@ -1,58 +0,0 @@ -using System; - -namespace Caliburn.Micro -{ - /// - /// A simple result. - /// - public sealed class SimpleResult : IResult - { - private readonly bool wasCancelled; - private readonly Exception error; - - private SimpleResult(bool wasCancelled, Exception error) - { - this.wasCancelled = wasCancelled; - this.error = error; - } - - /// - /// A result that is always succeeded. - /// - public static IResult Succeeded() - { - return new SimpleResult(false, null); - } - - /// - /// A result that is always canceled. - /// - /// The result. - public static IResult Cancelled() - { - return new SimpleResult(true, null); - } - - /// - /// A result that is always failed. - /// - public static IResult Failed(Exception error) - { - return new SimpleResult(false, error); - } - - /// - /// Executes the result using the specified context. - /// - /// The context. - public void Execute(CoroutineExecutionContext context) - { - Completed(this, new ResultCompletionEventArgs { WasCancelled = wasCancelled, Error = error }); - } - - /// - /// Occurs when execution has completed. - /// - public event EventHandler Completed = delegate { }; - } -} diff --git a/src/Caliburn.Micro.Core/TaskExtensions.cs b/src/Caliburn.Micro.Core/TaskExtensions.cs deleted file mode 100644 index 0e3bf182f..000000000 --- a/src/Caliburn.Micro.Core/TaskExtensions.cs +++ /dev/null @@ -1,95 +0,0 @@ -namespace Caliburn.Micro -{ - using System; - using System.Threading.Tasks; - - /// - /// Extension methods to bring and together. - /// - public static class TaskExtensions - { - /// - /// Executes an asynchronous. - /// - /// The coroutine to execute. - /// The context to execute the coroutine within. - /// A task that represents the asynchronous coroutine. - public static Task ExecuteAsync(this IResult result, CoroutineExecutionContext context = null) - { - return InternalExecuteAsync(result, context); - } - - /// - /// Executes an asynchronous. - /// - /// The type of the result. - /// The coroutine to execute. - /// The context to execute the coroutine within. - /// A task that represents the asynchronous coroutine. - public static Task ExecuteAsync(this IResult result, - CoroutineExecutionContext context = null) - { - return InternalExecuteAsync(result, context); - } - - private static Task InternalExecuteAsync(IResult result, CoroutineExecutionContext context) - { - var taskSource = new TaskCompletionSource(); - - EventHandler completed = null; - completed = (s, e) => - { - result.Completed -= completed; - - if (e.Error != null) - { - taskSource.SetException(e.Error); - } - else if (e.WasCancelled) - { - taskSource.SetCanceled(); - } - else - { - var rr = result as IResult; - taskSource.SetResult(rr != null ? rr.Result : default(TResult)); - } - }; - - try - { - IoC.BuildUp(result); - result.Completed += completed; - result.Execute(context ?? new CoroutineExecutionContext()); - } - catch (Exception ex) - { - result.Completed -= completed; - taskSource.SetException(ex); - } - - return taskSource.Task; - } - - /// - /// Encapsulates a task inside a couroutine. - /// - /// The task. - /// The coroutine that encapsulates the task. - public static TaskResult AsResult(this Task task) - { - return new TaskResult(task); - } - - /// - /// Encapsulates a task inside a couroutine. - /// - /// The type of the result. - /// The task. - /// The coroutine that encapsulates the task. - public static TaskResult AsResult(this Task task) - { - return new TaskResult(task); - } - } -} diff --git a/src/Caliburn.Micro.Core/TaskResult.cs b/src/Caliburn.Micro.Core/TaskResult.cs deleted file mode 100644 index 8266bafe5..000000000 --- a/src/Caliburn.Micro.Core/TaskResult.cs +++ /dev/null @@ -1,90 +0,0 @@ -using System; -using System.Threading.Tasks; - -namespace Caliburn.Micro -{ - /// - /// A couroutine that encapsulates an . - /// - public class TaskResult : IResult - { - private readonly Task innerTask; - - /// - /// Initializes a new instance of the class. - /// - /// The task. - public TaskResult(Task task) - { - innerTask = task; - } - - /// - /// Executes the result using the specified context. - /// - /// The context. - public void Execute(CoroutineExecutionContext context) - { - if (innerTask.IsCompleted) - { - OnCompleted(innerTask); - } - else - { - innerTask.ContinueWith(OnCompleted, - System.Threading.SynchronizationContext.Current != null - ? TaskScheduler.FromCurrentSynchronizationContext() - : TaskScheduler.Current); - } - } - - /// - /// Called when the asynchronous task has completed. - /// - /// The completed task. - protected virtual void OnCompleted(Task task) - { - Completed(this, new ResultCompletionEventArgs { WasCancelled = task.IsCanceled, Error = task.Exception }); - } - - /// - /// Occurs when execution has completed. - /// - public event EventHandler Completed = delegate { }; - } - - /// - /// A couroutine that encapsulates an . - /// - /// The type of the result. - public class TaskResult : TaskResult, IResult - { - /// - /// Initializes a new instance of the class. - /// - /// The task. - public TaskResult(Task task) - : base(task) - { - } - - /// - /// Gets the result of the asynchronous operation. - /// - public TResult Result { get; private set; } - - /// - /// Called when the asynchronous task has completed. - /// - /// The completed task. - protected override void OnCompleted(Task task) - { - if (!task.IsFaulted && !task.IsCanceled) - { - Result = ((Task)task).Result; - } - - base.OnCompleted(task); - } - } -} diff --git a/src/Caliburn.Micro.Core/Types/BindableCollection.cs b/src/Caliburn.Micro.Core/Types/BindableCollection.cs new file mode 100644 index 000000000..be878b0b0 --- /dev/null +++ b/src/Caliburn.Micro.Core/Types/BindableCollection.cs @@ -0,0 +1,251 @@ +using System; +using System.Collections.Generic; +using System.Collections.ObjectModel; +using System.Collections.Specialized; +using System.ComponentModel; +using System.Linq; + +namespace Caliburn.Micro; + +/// +/// A base collection class that supports automatic UI thread marshalling. +/// +/// The type of elements contained in the collection. +public class BindableCollection : ObservableCollection, IObservableCollection { + /// + /// Initializes a new instance of the class. + /// + public BindableCollection() + => IsNotifying = true; + + /// + /// Initializes a new instance of the class. + /// + /// The collection from which the elements are copied. + public BindableCollection(IEnumerable collection) + : base(collection) + => IsNotifying = true; + + /// + /// Gets or sets a value indicating whether to Enable/Disable property change notification. + /// + public bool IsNotifying { get; set; } + + /// + /// Notifies subscribers of the property change. + /// + /// Name of the property. + public virtual void NotifyOfPropertyChange(string propertyName) { + if (!IsNotifying) { + return; + } + + if (!PlatformProvider.Current.PropertyChangeNotificationsOnUIThread) { + OnPropertyChanged(new PropertyChangedEventArgs(propertyName)); + + return; + } + + OnUIThread(() => OnPropertyChanged(new PropertyChangedEventArgs(propertyName))); + } + + /// + /// Raises a change notification indicating that all bindings should be refreshed. + /// + public void Refresh() { + if (!PlatformProvider.Current.PropertyChangeNotificationsOnUIThread) { + OnPropertyChanged(new PropertyChangedEventArgs("Count")); + OnPropertyChanged(new PropertyChangedEventArgs("Item[]")); + OnCollectionChanged(new NotifyCollectionChangedEventArgs(NotifyCollectionChangedAction.Reset)); + + return; + } + + OnUIThread(() => { + OnPropertyChanged(new PropertyChangedEventArgs("Count")); + OnPropertyChanged(new PropertyChangedEventArgs("Item[]")); + OnCollectionChanged(new NotifyCollectionChangedEventArgs(NotifyCollectionChangedAction.Reset)); + }); + } + + /// + /// Adds the range. + /// + /// The items. + public virtual void AddRange(IEnumerable items) { + void AddRange() { + bool previousNotificationSetting = IsNotifying; + IsNotifying = false; + int index = Count; + foreach (T item in items) { + InsertItemBase(index, item); + index++; + } + + IsNotifying = previousNotificationSetting; + + OnPropertyChanged(new PropertyChangedEventArgs("Count")); + OnPropertyChanged(new PropertyChangedEventArgs("Item[]")); + OnCollectionChanged(new NotifyCollectionChangedEventArgs(NotifyCollectionChangedAction.Reset)); + } + + if (!PlatformProvider.Current.PropertyChangeNotificationsOnUIThread) { + AddRange(); + + return; + } + + OnUIThread(AddRange); + } + + /// + /// Removes the range. + /// + /// The items. + public virtual void RemoveRange(IEnumerable items) { + void RemoveRange() { + bool previousNotificationSetting = IsNotifying; + IsNotifying = false; + + items.Select(IndexOf) + .Where(index => index >= 0) + .Apply(RemoveItemBase); + + IsNotifying = previousNotificationSetting; + + OnPropertyChanged(new PropertyChangedEventArgs("Count")); + OnPropertyChanged(new PropertyChangedEventArgs("Item[]")); + OnCollectionChanged(new NotifyCollectionChangedEventArgs(NotifyCollectionChangedAction.Reset)); + } + + if (!PlatformProvider.Current.PropertyChangeNotificationsOnUIThread) { + RemoveRange(); + + return; + } + + OnUIThread(RemoveRange); + } + + /// + /// Executes the given action on the UI thread. + /// + /// An extension point for subclasses to customise how property change notifications are handled. + protected virtual void OnUIThread(System.Action action) + => action.OnUIThread(); + + /// + /// Inserts the item to the specified position. + /// + /// The index to insert at. + /// The item to be inserted. + protected sealed override void InsertItem(int index, T item) { + if (!PlatformProvider.Current.PropertyChangeNotificationsOnUIThread) { + InsertItemBase(index, item); + + return; + } + + OnUIThread(() => InsertItemBase(index, item)); + } + + /// + /// Exposes the base implementation of the function. + /// + /// The index. + /// The item. + /// + /// Used to avoid compiler warning regarding unverifiable code. + /// + protected virtual void InsertItemBase(int index, T item) + => base.InsertItem(index, item); + + /// + /// Sets the item at the specified position. + /// + /// The index to set the item at. + /// The item to set. + protected sealed override void SetItem(int index, T item) { + if (!PlatformProvider.Current.PropertyChangeNotificationsOnUIThread) { + SetItemBase(index, item); + + return; + } + + OnUIThread(() => SetItemBase(index, item)); + } + + /// + /// Exposes the base implementation of the function. + /// + /// The index. + /// The item. + /// + /// Used to avoid compiler warning regarding unverifiable code. + /// + protected virtual void SetItemBase(int index, T item) + => base.SetItem(index, item); + + /// + /// Removes the item at the specified position. + /// + /// The position used to identify the item to remove. + protected sealed override void RemoveItem(int index) { + if (!PlatformProvider.Current.PropertyChangeNotificationsOnUIThread) { + RemoveItemBase(index); + + return; + } + + OnUIThread(() => RemoveItemBase(index)); + } + + /// + /// Exposes the base implementation of the function. + /// + /// The index. + /// + /// Used to avoid compiler warning regarding unverifiable code. + /// + protected virtual void RemoveItemBase(int index) + => base.RemoveItem(index); + + /// + /// Clears the items contained by the collection. + /// + protected sealed override void ClearItems() + => OnUIThread(ClearItemsBase); + + /// + /// Exposes the base implementation of the function. + /// + /// + /// Used to avoid compiler warning regarding unverifiable code. + /// + protected virtual void ClearItemsBase() + => base.ClearItems(); + + /// + /// Raises the event with the provided arguments. + /// + /// Arguments of the event being raised. + protected override void OnCollectionChanged(NotifyCollectionChangedEventArgs e) { + if (!IsNotifying) { + return; + } + + base.OnCollectionChanged(e); + } + + /// + /// Raises the PropertyChanged event with the provided arguments. + /// + /// The event data to report in the event. + protected override void OnPropertyChanged(PropertyChangedEventArgs e) { + if (!IsNotifying) { + return; + } + + base.OnPropertyChanged(e); + } +} diff --git a/src/Caliburn.Micro.Core/Types/INotifyPropertyChangedEx.cs b/src/Caliburn.Micro.Core/Types/INotifyPropertyChangedEx.cs new file mode 100644 index 000000000..b0ad76980 --- /dev/null +++ b/src/Caliburn.Micro.Core/Types/INotifyPropertyChangedEx.cs @@ -0,0 +1,24 @@ +using System.ComponentModel; + +namespace Caliburn.Micro; + +/// +/// Extends such that the change event can be raised by external parties. +/// +public interface INotifyPropertyChangedEx : INotifyPropertyChanged { + /// + /// Gets or sets a value indicating whether to enable/Disable property change notification. + /// + bool IsNotifying { get; set; } + + /// + /// Notifies subscribers of the property change. + /// + /// Name of the property. + void NotifyOfPropertyChange(string propertyName); + + /// + /// Raises a change notification indicating that all bindings should be refreshed. + /// + void Refresh(); +} diff --git a/src/Caliburn.Micro.Core/Types/IObservableCollection.cs b/src/Caliburn.Micro.Core/Types/IObservableCollection.cs new file mode 100644 index 000000000..ae8148c6f --- /dev/null +++ b/src/Caliburn.Micro.Core/Types/IObservableCollection.cs @@ -0,0 +1,22 @@ +using System.Collections.Generic; +using System.Collections.Specialized; + +namespace Caliburn.Micro; + +/// +/// Represents a collection that is observable. +/// +/// The type of elements contained in the collection. +public interface IObservableCollection : IList, INotifyPropertyChangedEx, INotifyCollectionChanged { + /// + /// Adds the range. + /// + /// The items. + void AddRange(IEnumerable items); + + /// + /// Removes the range. + /// + /// The items. + void RemoveRange(IEnumerable items); +} diff --git a/src/Caliburn.Micro.Core/Types/PropertyChangedBase.cs b/src/Caliburn.Micro.Core/Types/PropertyChangedBase.cs new file mode 100644 index 000000000..a8b49ab03 --- /dev/null +++ b/src/Caliburn.Micro.Core/Types/PropertyChangedBase.cs @@ -0,0 +1,91 @@ +using System; +using System.Collections.Generic; +using System.ComponentModel; +using System.Linq.Expressions; +using System.Runtime.Serialization; + +namespace Caliburn.Micro; + +/// +/// A base class that implements the infrastructure for property change notification and automatically performs UI thread marshalling. +/// +[DataContract] +public class PropertyChangedBase : INotifyPropertyChangedEx { + /// + /// Occurs when a property value changes. + /// + public virtual event PropertyChangedEventHandler PropertyChanged; + + /// + /// Gets or sets a value indicating whether to Enable/Disable property change notification. + /// Virtualized in order to help with document oriented view models. + /// + public virtual bool IsNotifying { get; set; } + = true; + + /// + /// Raises a change notification indicating that all bindings should be refreshed. + /// + public virtual void Refresh() + => NotifyOfPropertyChange(string.Empty); + + /// + /// Notifies subscribers of the property change. + /// + /// Name of the property. + public virtual void NotifyOfPropertyChange([System.Runtime.CompilerServices.CallerMemberName] string propertyName = null) { + if (!IsNotifying || PropertyChanged == null) { + return; + } + + if (!PlatformProvider.Current.PropertyChangeNotificationsOnUIThread) { + OnPropertyChanged(new PropertyChangedEventArgs(propertyName)); + + return; + } + + OnUIThread(() => OnPropertyChanged(new PropertyChangedEventArgs(propertyName))); + } + + /// + /// Notifies subscribers of the property change. + /// + /// The type of the property. + /// The property expression. + public void NotifyOfPropertyChange(Expression> property) + => NotifyOfPropertyChange(property.GetMemberInfo().Name); + + /// + /// Sets a backing field value and if it's changed raise a notification. + /// + /// The type of the value being set. + /// A reference to the field to update. + /// The new value. + /// The name of the property for change notifications. + public virtual bool Set(ref T oldValue, T newValue, [System.Runtime.CompilerServices.CallerMemberName] string propertyName = null) { + if (EqualityComparer.Default.Equals(oldValue, newValue)) { + return false; + } + + oldValue = newValue; + + NotifyOfPropertyChange(propertyName ?? string.Empty); + + return true; + } + + /// + /// Raises the event directly. + /// + /// The instance containing the event data. + [EditorBrowsable(EditorBrowsableState.Never)] + protected void OnPropertyChanged(PropertyChangedEventArgs e) + => PropertyChanged?.Invoke(this, e); + + /// + /// Executes the given action on the UI thread. + /// + /// An extension point for subclasses to customise how property change notifications are handled. + protected virtual void OnUIThread(System.Action action) + => action.OnUIThread(); +} diff --git a/src/Caliburn.Micro.Core/Types/WeakValueDictionary.cs b/src/Caliburn.Micro.Core/Types/WeakValueDictionary.cs new file mode 100644 index 000000000..d611a2704 --- /dev/null +++ b/src/Caliburn.Micro.Core/Types/WeakValueDictionary.cs @@ -0,0 +1,294 @@ +using System; +using System.Collections; +using System.Collections.Generic; +using System.Linq; + +namespace Caliburn.Micro; + +/// +/// A dictionary in which the values are weak references. +/// +/// The type of keys in the dictionary. +/// The type of values in the dictionary. +internal sealed class WeakValueDictionary : IDictionary + where TValue : class { + private readonly Dictionary _inner; + private readonly WeakReference _gcSentinel = new(new object()); + + /// + /// Initializes a new instance of the class that is empty, has the default initial capacity, and uses the default equality comparer for the key type. + /// + public WeakValueDictionary() + => _inner = new Dictionary(); + + /// + /// Initializes a new instance of the class that contains elements copied from the specified and uses the default equality comparer for the key type. + /// + /// The whose elements are copied to the new . + public WeakValueDictionary(IDictionary dictionary) { + _inner = new Dictionary(); + dictionary.Apply(item => _inner.Add(item.Key, new WeakReference(item.Value))); + } + + /// + /// Initializes a new instance of the class that contains elements copied from the specified and uses the specified . + /// + /// The whose elements are copied to the new . + /// The implementation to use when comparing keys, or null to use the default for the type of the key. + public WeakValueDictionary(IDictionary dictionary, IEqualityComparer comparer) { + _inner = new Dictionary(comparer); + dictionary.Apply(item => _inner.Add(item.Key, new WeakReference(item.Value))); + } + + /// + /// Initializes a new instance of the class that is empty, has the default initial capacity, and uses the specified . + /// + /// The implementation to use when comparing keys, or null to use the default for the type of the key. + public WeakValueDictionary(IEqualityComparer comparer) + => _inner = new Dictionary(comparer); + + /// + /// Initializes a new instance of the class that is empty, has the specified initial capacity, and uses the default equality comparer for the key type. + /// + /// The initial number of elements that the can contain. + public WeakValueDictionary(int capacity) + => _inner = new Dictionary(capacity); + + /// + /// Initializes a new instance of the class that is empty, has the specified initial capacity, and uses the specified . + /// + /// The initial number of elements that the can contain. + /// The implementation to use when comparing keys, or null to use the default for the type of the key. + public WeakValueDictionary(int capacity, IEqualityComparer comparer) + => _inner = new Dictionary(capacity, comparer); + + /// + /// Gets the number of key/value pairs contained in the . + /// + /// + /// Since the items in the dictionary are held by weak reference, the count value + /// cannot be relied upon to guarantee the number of objects that would be discovered via + /// enumeration. Treat the Count as an estimate only. + /// + public int Count { + get { + CleanIfNeeded(); + + return _inner.Count; + } + } + + /// + /// Gets a collection containing the keys in the . + /// + public ICollection Keys + => _inner.Keys; + + /// + /// Gets a collection containing the values in the . + /// + public ICollection Values + => new ValueCollection(this); + + bool ICollection>.IsReadOnly + => false; + + /// + /// Gets or sets the value associated with the specified key. + /// + /// The key of the value to get or set. + /// + /// The value associated with the specified key. If the specified key is not found, a get operation throws a , + /// and a set operation creates a new element with the specified key. + /// + public TValue this[TKey key] { + get => TryGetValue(key, out TValue result) + ? result + : throw new KeyNotFoundException(); + set { + CleanIfNeeded(); + _inner[key] = new WeakReference(value); + } + } + + /// + /// Returns an enumerator that iterates through the . + /// + /// The enumerator. + public IEnumerator> GetEnumerator() { + CleanIfNeeded(); + IEnumerable> enumerable + = _inner + .Select(pair => new KeyValuePair(pair.Key, (TValue)pair.Value.Target)) + .Where(pair => pair.Value != null); + return enumerable.GetEnumerator(); + } + + IEnumerator IEnumerable.GetEnumerator() + => GetEnumerator(); + + void ICollection>.Add(KeyValuePair item) + => Add(item.Key, item.Value); + + /// + /// Removes all keys and values from the . + /// + public void Clear() + => _inner.Clear(); + + bool ICollection>.Contains(KeyValuePair item) + => TryGetValue(item.Key, out TValue value) && value == item.Value; + + void ICollection>.CopyTo(KeyValuePair[] array, int arrayIndex) { + if (array == null) { + throw new ArgumentNullException(nameof(array)); + } + + if (arrayIndex < 0 || arrayIndex >= array.Length) { + throw new ArgumentOutOfRangeException(nameof(arrayIndex)); + } + + if ((arrayIndex + Count) > array.Length) { + throw new ArgumentException("The number of elements in the source collection is greater than the available space from arrayIndex to the end of the destination array."); + } + + this.ToArray().CopyTo(array, arrayIndex); + } + + bool ICollection>.Remove(KeyValuePair item) + => TryGetValue(item.Key, out TValue value) && + value == item.Value && + _inner.Remove(item.Key); + + /// + /// Adds the specified key and value to the dictionary. + /// + /// The key of the element to add. + /// The value of the element to add. The value can be null for reference types. + public void Add(TKey key, TValue value) { + CleanIfNeeded(); + _inner.Add(key, new WeakReference(value)); + } + + /// + /// Determines whether the contains the specified key. + /// + /// The key to locate in the . + public bool ContainsKey(TKey key) + => TryGetValue(key, out _); + + /// + /// Removes the value with the specified key from the . + /// + /// The key of the element to remove. + /// true if the element is successfully found and removed; otherwise, false. This method returns false if key is not found in the . + public bool Remove(TKey key) { + CleanIfNeeded(); + + return _inner.Remove(key); + } + + /// + /// Gets the value associated with the specified key. + /// + /// The key of the value to get. + /// + /// When this method returns, contains the value associated with the specified key, + /// if the key is found; otherwise, the default value for the type of the value parameter. + /// This parameter is passed uninitialized. + /// true if the contains an element with the specified key; otherwise, false. + public bool TryGetValue(TKey key, out TValue value) { + CleanIfNeeded(); + if (!_inner.TryGetValue(key, out WeakReference wr)) { + value = null; + + return false; + } + + var result = (TValue)wr.Target; + if (result != null) { + value = result; + + return true; + } + + _inner.Remove(key); + value = null; + + return false; + } + + private bool IsCleanupNeeded() { + if (_gcSentinel.Target != null) { + return false; + } + + _gcSentinel.Target = new object(); + + return true; + } + + private void CleanAbandonedItems() { + var keysToRemove = _inner.Where(pair => !pair.Value.IsAlive) + .Select(pair => pair.Key) + .ToList(); + + keysToRemove.Apply(key => _inner.Remove(key)); + } + + private void CleanIfNeeded() { + if (!IsCleanupNeeded()) { + return; + } + + CleanAbandonedItems(); + } + + private sealed class ValueCollection : ICollection { + private readonly WeakValueDictionary _inner; + + public ValueCollection(WeakValueDictionary dictionary) + => _inner = dictionary; + + public int Count + => _inner.Count; + + bool ICollection.IsReadOnly + => true; + + public IEnumerator GetEnumerator() + => _inner.Select(pair => pair.Value).GetEnumerator(); + + IEnumerator IEnumerable.GetEnumerator() + => GetEnumerator(); + + void ICollection.Add(TValue item) + => throw new NotSupportedException(); + + void ICollection.Clear() + => throw new NotSupportedException(); + + bool ICollection.Contains(TValue item) + => _inner.Any(pair => pair.Value == item); + + public void CopyTo(TValue[] array, int arrayIndex) { + if (array == null) { + throw new ArgumentNullException(nameof(array)); + } + + if (arrayIndex < 0 || arrayIndex >= array.Length) { + throw new ArgumentOutOfRangeException(nameof(arrayIndex)); + } + + if ((arrayIndex + Count) > array.Length) { + throw new ArgumentException( + "The number of elements in the source collection is greater than the available space from arrayIndex to the end of the destination array."); + } + + this.ToArray().CopyTo(array, arrayIndex); + } + + bool ICollection.Remove(TValue item) + => throw new NotSupportedException(); + } +} diff --git a/src/Caliburn.Micro.Core/ViewAttachedEventArgs.cs b/src/Caliburn.Micro.Core/ViewAttachedEventArgs.cs deleted file mode 100644 index 6a4e7cf42..000000000 --- a/src/Caliburn.Micro.Core/ViewAttachedEventArgs.cs +++ /dev/null @@ -1,20 +0,0 @@ -using System; - -namespace Caliburn.Micro -{ - /// - /// The event args for the event. - /// - public class ViewAttachedEventArgs : EventArgs - { - /// - /// The view. - /// - public object View { get; set; } - - /// - /// The context. - /// - public object Context { get; set; } - } -} diff --git a/src/Caliburn.Micro.Core/ViewAware.cs b/src/Caliburn.Micro.Core/ViewAware.cs deleted file mode 100644 index ef6623dcd..000000000 --- a/src/Caliburn.Micro.Core/ViewAware.cs +++ /dev/null @@ -1,115 +0,0 @@ -using System; -using System.Collections.Generic; -using System.Threading.Tasks; - -namespace Caliburn.Micro -{ - /// - /// A base implementation of which is capable of caching views by context. - /// - public class ViewAware : PropertyChangedBase, IViewAware - { - private readonly IDictionary views; - - /// - /// The default view context. - /// - public static readonly object DefaultContext = new object(); - - /// - /// The view chache for this instance. - /// - protected IDictionary Views - { - get { return views; } - } - - /// - /// Creates an instance of . - /// - public ViewAware() - { - views = new WeakValueDictionary(); - } - - /// - /// Raised when a view is attached. - /// - public event EventHandler ViewAttached = delegate { }; - - void IViewAware.AttachView(object view, object context) - { - Views[context ?? DefaultContext] = view; - - var nonGeneratedView = PlatformProvider.Current.GetFirstNonGeneratedView(view); - PlatformProvider.Current.ExecuteOnFirstLoad(nonGeneratedView, OnViewLoaded); - OnViewAttached(nonGeneratedView, context); - ViewAttached(this, new ViewAttachedEventArgs { View = nonGeneratedView, Context = context }); - - var activatable = this as IActivate; - if (activatable == null || activatable.IsActive) - { - PlatformProvider.Current.ExecuteOnLayoutUpdated(nonGeneratedView, OnViewReady); - } - else - { - AttachViewReadyOnActivated(activatable, nonGeneratedView); - } - } - - private static void AttachViewReadyOnActivated(IActivate activatable, object nonGeneratedView) - { - var viewReference = new WeakReference(nonGeneratedView); - AsyncEventHandler handler = null; - handler = (s, e) => - { - ((IActivate)s).Activated -= handler; - var view = viewReference.Target; - if (view != null) - { - PlatformProvider.Current.ExecuteOnLayoutUpdated(view, ((ViewAware)s).OnViewReady); - } - - return Task.CompletedTask; - }; - activatable.Activated += handler; - } - - /// - /// Called when a view is attached. - /// - /// The view. - /// The context in which the view appears. - protected virtual void OnViewAttached(object view, object context) - { - } - - /// - /// Called when an attached view's Loaded event fires. - /// - /// - protected virtual void OnViewLoaded(object view) - { - } - - /// - /// Called the first time the page's LayoutUpdated event fires after it is navigated to. - /// - /// - protected virtual void OnViewReady(object view) - { - } - - /// - /// Gets a view previously attached to this instance. - /// - /// The context denoting which view to retrieve. - /// The view. - public virtual object GetView(object context = null) - { - object view; - Views.TryGetValue(context ?? DefaultContext, out view); - return view; - } - } -} diff --git a/src/Caliburn.Micro.Core/ViewAware/Contracts/IViewAware.cs b/src/Caliburn.Micro.Core/ViewAware/Contracts/IViewAware.cs new file mode 100644 index 000000000..e1a7b5a37 --- /dev/null +++ b/src/Caliburn.Micro.Core/ViewAware/Contracts/IViewAware.cs @@ -0,0 +1,27 @@ +using System; + +namespace Caliburn.Micro; + +/// +/// Denotes a class which is aware of its view(s). +/// +public interface IViewAware { + /// + /// Raised when a view is attached. + /// + event EventHandler ViewAttached; + + /// + /// Attaches a view to this instance. + /// + /// The view. + /// The context in which the view appears. + void AttachView(object view, object context = null); + + /// + /// Gets a view previously attached to this instance. + /// + /// The context denoting which view to retrieve. + /// The view. + object GetView(object context = null); +} diff --git a/src/Caliburn.Micro.Core/ViewAware/EventArgs/ViewAttachedEventArgs.cs b/src/Caliburn.Micro.Core/ViewAware/EventArgs/ViewAttachedEventArgs.cs new file mode 100644 index 000000000..bf2f1c867 --- /dev/null +++ b/src/Caliburn.Micro.Core/ViewAware/EventArgs/ViewAttachedEventArgs.cs @@ -0,0 +1,18 @@ +using System; + +namespace Caliburn.Micro; + +/// +/// The event args for the event. +/// +public class ViewAttachedEventArgs : EventArgs { + /// + /// Gets or sets the view. + /// + public object View { get; set; } + + /// + /// Gets or sets the context. + /// + public object Context { get; set; } +} diff --git a/src/Caliburn.Micro.Core/ViewAware/ViewAware.cs b/src/Caliburn.Micro.Core/ViewAware/ViewAware.cs new file mode 100644 index 000000000..6631bd3d5 --- /dev/null +++ b/src/Caliburn.Micro.Core/ViewAware/ViewAware.cs @@ -0,0 +1,99 @@ +using System; +using System.Collections.Generic; +using System.Threading.Tasks; + +namespace Caliburn.Micro; + +/// +/// A base implementation of which is capable of caching views by context. +/// +public class ViewAware : PropertyChangedBase, IViewAware { + /// + /// The default view context. + /// + public static readonly object DefaultContext + = new(); + + /// + /// Initializes a new instance of the class. + /// + public ViewAware() + => Views = new WeakValueDictionary(); + + /// + /// Raised when a view is attached. + /// + public event EventHandler ViewAttached + = (sender, e) => { }; + + /// + /// Gets the view chache for this instance. + /// + protected IDictionary Views { get; } + + /// + /// Gets a view previously attached to this instance. + /// + /// The context denoting which view to retrieve. + /// The view. + public virtual object GetView(object context = null) { + Views.TryGetValue(context ?? DefaultContext, out object view); + + return view; + } + + void IViewAware.AttachView(object view, object context) { + Views[context ?? DefaultContext] = view; + + object nonGeneratedView = PlatformProvider.Current.GetFirstNonGeneratedView(view); + PlatformProvider.Current.ExecuteOnFirstLoad(nonGeneratedView, OnViewLoaded); + OnViewAttached(nonGeneratedView, context); + ViewAttached(this, new ViewAttachedEventArgs { View = nonGeneratedView, Context = context }); + + if (this is IActivate activatable && !activatable.IsActive) { + AttachViewReadyOnActivated(activatable, nonGeneratedView); + + return; + } + + PlatformProvider.Current.ExecuteOnLayoutUpdated(nonGeneratedView, OnViewReady); + } + + /// + /// Called when a view is attached. + /// + /// The view. + /// The context in which the view appears. + protected virtual void OnViewAttached(object view, object context) { + } + + /// + /// Called when an attached view's Loaded event fires. + /// + /// The loaded view. + protected virtual void OnViewLoaded(object view) { + } + + /// + /// Called the first time the page's LayoutUpdated event fires after it is navigated to. + /// + protected virtual void OnViewReady(object view) { + } + + private static void AttachViewReadyOnActivated(IActivate activatable, object nonGeneratedView) { + var viewReference = new WeakReference(nonGeneratedView); + Task OnActivated(object s, ActivationEventArgs e) { + ((IActivate)s).Activated -= OnActivated; + object view = viewReference.Target; + if (view == null) { + return Task.CompletedTask; + } + + PlatformProvider.Current.ExecuteOnLayoutUpdated(view, ((ViewAware)s).OnViewReady); + + return Task.CompletedTask; + } + + activatable.Activated += OnActivated; + } +} diff --git a/src/Caliburn.Micro.Core/WeakValueDictionary.cs b/src/Caliburn.Micro.Core/WeakValueDictionary.cs deleted file mode 100644 index 2b550c366..000000000 --- a/src/Caliburn.Micro.Core/WeakValueDictionary.cs +++ /dev/null @@ -1,395 +0,0 @@ -using System; -using System.Collections; -using System.Collections.Generic; -using System.Linq; - -namespace Caliburn.Micro -{ - /// - /// A dictionary in which the values are weak references. - /// - /// The type of keys in the dictionary. - /// The type of values in the dictionary. - internal class WeakValueDictionary : IDictionary - where TValue : class - { - private readonly Dictionary inner; - private readonly WeakReference gcSentinel = new WeakReference(new object()); - - #region Cleanup handling - - private bool IsCleanupNeeded() - { - if (gcSentinel.Target == null) - { - gcSentinel.Target = new object(); - return true; - } - - return false; - } - - private void CleanAbandonedItems() - { - var keysToRemove = inner.Where(pair => !pair.Value.IsAlive) - .Select(pair => pair.Key) - .ToList(); - - keysToRemove.Apply(key => inner.Remove(key)); - } - - private void CleanIfNeeded() - { - if (IsCleanupNeeded()) - { - CleanAbandonedItems(); - } - } - - #endregion - - #region Constructors - - /// - /// Initializes a new instance of the class that is empty, has the default initial capacity, and uses the default equality comparer for the key type. - /// - public WeakValueDictionary() - { - inner = new Dictionary(); - } - - /// - /// Initializes a new instance of the class that contains elements copied from the specified and uses the default equality comparer for the key type. - /// - /// The whose elements are copied to the new . - public WeakValueDictionary(IDictionary dictionary) - { - inner = new Dictionary(); - dictionary.Apply(item => inner.Add(item.Key, new WeakReference(item.Value))); - } - - /// - /// Initializes a new instance of the class that contains elements copied from the specified and uses the specified . - /// - /// The whose elements are copied to the new . - /// The implementation to use when comparing keys, or null to use the default for the type of the key. - public WeakValueDictionary(IDictionary dictionary, IEqualityComparer comparer) - { - inner = new Dictionary(comparer); - dictionary.Apply(item => inner.Add(item.Key, new WeakReference(item.Value))); - } - - /// - /// Initializes a new instance of the class that is empty, has the default initial capacity, and uses the specified . - /// - /// The implementation to use when comparing keys, or null to use the default for the type of the key. - public WeakValueDictionary(IEqualityComparer comparer) - { - inner = new Dictionary(comparer); - } - - /// - /// Initializes a new instance of the class that is empty, has the specified initial capacity, and uses the default equality comparer for the key type. - /// - /// The initial number of elements that the can contain. - public WeakValueDictionary(int capacity) - { - inner = new Dictionary(capacity); - } - - /// - /// Initializes a new instance of the class that is empty, has the specified initial capacity, and uses the specified . - /// - /// The initial number of elements that the can contain. - /// The implementation to use when comparing keys, or null to use the default for the type of the key. - public WeakValueDictionary(int capacity, IEqualityComparer comparer) - { - inner = new Dictionary(capacity, comparer); - } - - #endregion - - /// - /// Returns an enumerator that iterates through the . - /// - /// The enumerator. - public IEnumerator> GetEnumerator() - { - CleanIfNeeded(); - var enumerable = inner.Select(pair => new KeyValuePair(pair.Key, (TValue)pair.Value.Target)) - .Where(pair => pair.Value != null); - return enumerable.GetEnumerator(); - } - - IEnumerator IEnumerable.GetEnumerator() - { - return GetEnumerator(); - } - - void ICollection>.Add(KeyValuePair item) - { - Add(item.Key, item.Value); - } - - /// - /// Removes all keys and values from the . - /// - public void Clear() - { - inner.Clear(); - } - - bool ICollection>.Contains(KeyValuePair item) - { - TValue value; - if (!TryGetValue(item.Key, out value)) - { - return false; - } - - return value == item.Value; - } - - void ICollection>.CopyTo(KeyValuePair[] array, int arrayIndex) - { - if (array == null) - { - throw new ArgumentNullException("array"); - } - - if (arrayIndex < 0 || arrayIndex >= array.Length) - { - throw new ArgumentOutOfRangeException("arrayIndex"); - } - - if ((arrayIndex + Count) > array.Length) - { - throw new ArgumentException( - "The number of elements in the source collection is greater than the available space from arrayIndex to the end of the destination array."); - } - - this.ToArray().CopyTo(array, arrayIndex); - } - - bool ICollection>.Remove(KeyValuePair item) - { - TValue value; - if (!TryGetValue(item.Key, out value)) - { - return false; - } - - if (value != item.Value) - { - return false; - } - - return inner.Remove(item.Key); - } - - /// - /// Gets the number of key/value pairs contained in the . - /// - /// - /// Since the items in the dictionary are held by weak reference, the count value - /// cannot be relied upon to guarantee the number of objects that would be discovered via - /// enumeration. Treat the Count as an estimate only. - /// - public int Count - { - get - { - CleanIfNeeded(); - return inner.Count; - } - } - - bool ICollection>.IsReadOnly - { - get { return false; } - } - - /// - /// Adds the specified key and value to the dictionary. - /// - /// The key of the element to add. - /// The value of the element to add. The value can be null for reference types. - public void Add(TKey key, TValue value) - { - CleanIfNeeded(); - inner.Add(key, new WeakReference(value)); - } - - /// - /// Determines whether the contains the specified key. - /// - /// The key to locate in the . - /// - public bool ContainsKey(TKey key) - { - TValue dummy; - return TryGetValue(key, out dummy); - } - - /// - /// Removes the value with the specified key from the . - /// - /// The key of the element to remove. - /// true if the element is successfully found and removed; otherwise, false. This method returns false if key is not found in the . - public bool Remove(TKey key) - { - CleanIfNeeded(); - return inner.Remove(key); - } - - /// - /// Gets the value associated with the specified key. - /// - /// The key of the value to get. - /// - /// When this method returns, contains the value associated with the specified key, - /// if the key is found; otherwise, the default value for the type of the value parameter. - /// This parameter is passed uninitialized. - /// true if the contains an element with the specified key; otherwise, false. - public bool TryGetValue(TKey key, out TValue value) - { - CleanIfNeeded(); - - WeakReference wr; - if (!inner.TryGetValue(key, out wr)) - { - value = null; - return false; - } - - var result = (TValue)wr.Target; - if (result == null) - { - inner.Remove(key); - value = null; - return false; - } - - value = result; - return true; - } - - /// - /// Gets or sets the value associated with the specified key. - /// - /// The key of the value to get or set. - /// - /// The value associated with the specified key. If the specified key is not found, a get operation throws a , - /// and a set operation creates a new element with the specified key. - /// - public TValue this[TKey key] - { - get - { - TValue result; - if (!TryGetValue(key, out result)) - { - throw new KeyNotFoundException(); - } - - return result; - } - set - { - CleanIfNeeded(); - inner[key] = new WeakReference(value); - } - } - - /// - /// Gets a collection containing the keys in the . - /// - public ICollection Keys - { - get { return inner.Keys; } - } - - /// - /// Gets a collection containing the values in the . - /// - public ICollection Values - { - get { return new ValueCollection(this); } - } - - #region Inner Types - - private sealed class ValueCollection : ICollection - { - private readonly WeakValueDictionary inner; - - public ValueCollection(WeakValueDictionary dictionary) - { - inner = dictionary; - } - - public IEnumerator GetEnumerator() - { - return inner.Select(pair => pair.Value).GetEnumerator(); - } - - IEnumerator IEnumerable.GetEnumerator() - { - return GetEnumerator(); - } - - void ICollection.Add(TValue item) - { - throw new NotSupportedException(); - } - - void ICollection.Clear() - { - throw new NotSupportedException(); - } - - bool ICollection.Contains(TValue item) - { - return inner.Any(pair => pair.Value == item); - } - - public void CopyTo(TValue[] array, int arrayIndex) - { - if (array == null) - { - throw new ArgumentNullException("array"); - } - - if (arrayIndex < 0 || arrayIndex >= array.Length) - { - throw new ArgumentOutOfRangeException("arrayIndex"); - } - - if ((arrayIndex + Count) > array.Length) - { - throw new ArgumentException( - "The number of elements in the source collection is greater than the available space from arrayIndex to the end of the destination array."); - } - - this.ToArray().CopyTo(array, arrayIndex); - } - - bool ICollection.Remove(TValue item) - { - throw new NotSupportedException(); - } - - public int Count - { - get { return inner.Count; } - } - - bool ICollection.IsReadOnly - { - get { return true; } - } - } - - #endregion - } -} diff --git a/src/Caliburn.Micro.Maui/Caliburn.Micro.Maui.csproj b/src/Caliburn.Micro.Maui/Caliburn.Micro.Maui.csproj index 59cc27d9b..9cfa1c13f 100644 --- a/src/Caliburn.Micro.Maui/Caliburn.Micro.Maui.csproj +++ b/src/Caliburn.Micro.Maui/Caliburn.Micro.Maui.csproj @@ -1,35 +1,35 @@  - - net6.0;net6.0-android;net6.0-ios;net6.0-maccatalyst - $(TargetFrameworks);net6.0-windows10.0.19041 - $(TargetsForTfmSpecificBuildOutput);CopyProjectReferencesToPackage - - true - true - + + + net6.0; + net6.0-android; + net6.0-ios; + net6.0-maccatalyst + + + + MAUI - 14.2 - 14.0 - 21.0 - 10.0.17763.0 - 10.0.17763.0 - + true + true + disable + 8.0 Caliburn.Micro.Maui Caliburn.Micro.Maui - - - enable - 8.0 - + $(TargetList) + $(TargetFrameworks);net6.0-windows10.0.19041 + $(TargetsForTfmSpecificBuildOutput);CopyProjectReferencesToPackage - - - - - + 14.2 + 14.0 + 21.0 + 10.0.17763.0 + + 10.0.17763.0 + @@ -52,23 +52,9 @@ - - - - - - - - - - - - - - - - - + + Platforms\Maui\%(RecursiveDir)%(Filename)%(Extension) + @@ -77,22 +63,28 @@ - + + Platforms\Maui\Android\%(RecursiveDir)%(Filename)%(Extension) + - + - - - - - - + + Platforms\Maui\ios\%(RecursiveDir)%(Filename)%(Extension) + - - + + Platforms\Maui\Windows\%(RecursiveDir)%(Filename)%(Extension) + + + + + + + diff --git a/src/Caliburn.Micro.Platform.Core/AssemblySource.cs b/src/Caliburn.Micro.Platform.Core/AssemblySource.cs index 1d90fb78c..400b2b1d8 100644 --- a/src/Caliburn.Micro.Platform.Core/AssemblySource.cs +++ b/src/Caliburn.Micro.Platform.Core/AssemblySource.cs @@ -5,123 +5,45 @@ using System.Linq; using System.Reflection; -namespace Caliburn.Micro -{ +namespace Caliburn.Micro { /// /// A source of assemblies that are inspectable by the framework. /// - public static class AssemblySource - { + public static class AssemblySource { /// /// The singleton instance of the AssemblySource used by the framework. /// - public static readonly IObservableCollection Instance = new BindableCollection(); - - - /// - /// Adds a collection of assemblies to AssemblySource - /// - /// The assemblies to add - public static void AddRange(IEnumerable assemblies) - { - foreach(var assembly in assemblies) - { - try - { - if (!Instance.Contains(assembly)) - Instance.Add(assembly); - } - catch (ArgumentException) - { - // ignore - } - } - } - - /// - /// Finds a type which matches one of the elements in the sequence of names. - /// - public static Func, Type> FindTypeByNames = names => - { - if (names == null) - { - return null; - } - - var type = names - .Join(Instance.SelectMany(a => a.ExportedTypes), n => n, t => t.FullName, (n, t) => t) - .FirstOrDefault(); - return type; - }; - } - - /// - /// A caching subsystem for . - /// - public static class AssemblySourceCache - { - private static bool isInstalled; - private static readonly IDictionary TypeNameCache = new Dictionary(); - - /// - /// Extracts the types from the spezified assembly for storing in the cache. - /// - public static Func> ExtractTypes = assembly => - assembly.ExportedTypes - .Where(t => - typeof(INotifyPropertyChanged).GetTypeInfo().IsAssignableFrom(t.GetTypeInfo())); + public static readonly IObservableCollection Instance + = new BindableCollection(); /// - /// Installs the caching subsystem. + /// Gets or sets func to finds a type which matches one of the elements in the sequence of names. /// - public static System.Action Install = () => - { - if (isInstalled) - { - return; - } - - isInstalled = true; - - AssemblySource.Instance.CollectionChanged += (s, e) => - { - switch (e.Action) - { - case NotifyCollectionChangedAction.Add: - e.NewItems.OfType() - .SelectMany(a => ExtractTypes(a)) - .Apply(AddTypeAssembly); - break; - case NotifyCollectionChangedAction.Remove: - case NotifyCollectionChangedAction.Replace: - case NotifyCollectionChangedAction.Reset: - TypeNameCache.Clear(); - AssemblySource.Instance - .SelectMany(a => ExtractTypes(a)) - .Apply(AddTypeAssembly); - break; - } - }; - - AssemblySource.Instance.Refresh(); - - AssemblySource.FindTypeByNames = names => - { - if (names == null) - { + public static Func, Type> FindTypeByNames { get; set; } + = names => { + if (names == null) { return null; } - var type = names.Select(n => TypeNameCache.GetValueOrDefault(n)).FirstOrDefault(t => t != null); + Type type = names + .Join(Instance.SelectMany(a => a.ExportedTypes), n => n, t => t.FullName, (n, t) => t) + .FirstOrDefault(); return type; }; - }; - private static void AddTypeAssembly(Type type) - { - if (!TypeNameCache.ContainsKey(type.FullName)) - { - TypeNameCache.Add(type.FullName, type); + /// + /// Adds a collection of assemblies to AssemblySource. + /// + /// The assemblies to add. + public static void AddRange(IEnumerable assemblies) { + foreach (Assembly assembly in assemblies) { + try { + if (!Instance.Contains(assembly)) { + Instance.Add(assembly); + } + } catch (ArgumentException) { + // ignore + } } } } diff --git a/src/Caliburn.Micro.Platform.Core/AssemblySourceCache.cs b/src/Caliburn.Micro.Platform.Core/AssemblySourceCache.cs new file mode 100644 index 000000000..cd3f88c9f --- /dev/null +++ b/src/Caliburn.Micro.Platform.Core/AssemblySourceCache.cs @@ -0,0 +1,72 @@ +using System; +using System.Collections.Generic; +using System.Collections.Specialized; +using System.ComponentModel; +using System.Linq; +using System.Reflection; + +namespace Caliburn.Micro { + /// + /// A caching subsystem for . + /// + public static class AssemblySourceCache { + private static readonly IDictionary TypeNameCache = new Dictionary(); + + private static bool isInstalled; + + /// + /// Gets or sets func to extracts the types from the spezified assembly for storing in the cache. + /// + public static Func> ExtractTypes { get; set; } = assembly => + assembly.ExportedTypes + .Where(t => + typeof(INotifyPropertyChanged).GetTypeInfo().IsAssignableFrom(t.GetTypeInfo())); + + /// + /// Gets or sets func to installs the caching subsystem. + /// + public static System.Action Install { get; set; } + = () => { + if (isInstalled) { + return; + } + + isInstalled = true; + + AssemblySource.Instance.CollectionChanged += (s, e) => { + switch (e.Action) { + case NotifyCollectionChangedAction.Add: + e.NewItems.OfType() + .SelectMany(a => ExtractTypes(a)) + .Apply(AddTypeAssembly); + break; + case NotifyCollectionChangedAction.Remove: + case NotifyCollectionChangedAction.Replace: + case NotifyCollectionChangedAction.Reset: + TypeNameCache.Clear(); + AssemblySource.Instance + .SelectMany(a => ExtractTypes(a)) + .Apply(AddTypeAssembly); + break; + } + }; + + AssemblySource.Instance.Refresh(); + + AssemblySource.FindTypeByNames = names => { + if (names == null) { + return null; + } + + Type type = names.Select(n => TypeNameCache.GetValueOrDefault(n)).FirstOrDefault(t => t != null); + return type; + }; + }; + + private static void AddTypeAssembly(Type type) { + if (!TypeNameCache.ContainsKey(type.FullName)) { + TypeNameCache.Add(type.FullName, type); + } + } + } +} diff --git a/src/Caliburn.Micro.Platform.Core/Caliburn.Micro.Platform.Core.csproj b/src/Caliburn.Micro.Platform.Core/Caliburn.Micro.Platform.Core.csproj index 23ff8eab7..94a48b147 100644 --- a/src/Caliburn.Micro.Platform.Core/Caliburn.Micro.Platform.Core.csproj +++ b/src/Caliburn.Micro.Platform.Core/Caliburn.Micro.Platform.Core.csproj @@ -1,4 +1,4 @@ - + netstandard2.0 @@ -7,9 +7,7 @@ .\..\Caliburn.Micro.snk true - - - + diff --git a/src/Caliburn.Micro.Platform.Core/ExtensionMethods.cs b/src/Caliburn.Micro.Platform.Core/ExtensionMethods.cs index c7b0851c9..e3fae9a19 100644 --- a/src/Caliburn.Micro.Platform.Core/ExtensionMethods.cs +++ b/src/Caliburn.Micro.Platform.Core/ExtensionMethods.cs @@ -1,25 +1,21 @@ using System.Collections.Generic; using System.Reflection; -namespace Caliburn.Micro -{ +namespace Caliburn.Micro { /// /// Generic extension methods used by the framework. /// - public static class ExtensionMethods - { + public static class ExtensionMethods { /// /// Get's the name of the assembly. /// /// The assembly. /// The assembly's name. public static string GetAssemblyName(this Assembly assembly) - { - return assembly.FullName.Remove(assembly.FullName.IndexOf(',')); - } + => assembly.FullName.Remove(assembly.FullName.IndexOf(',')); /// - /// Gets the value for a key. If the key does not exist, return default(TValue); + /// Gets the value for a key. If the key does not exist, return default(TValue). /// /// The type of the keys in the dictionary. /// The type of the values in the dictionary. @@ -27,8 +23,6 @@ public static string GetAssemblyName(this Assembly assembly) /// The key to look up. /// The key value. default(TValue) if this key is not in the dictionary. public static TValue GetValueOrDefault(this IDictionary dictionary, TKey key) - { - return dictionary.TryGetValue(key, out TValue result) ? result : default; - } + => dictionary.TryGetValue(key, out TValue result) ? result : default; } } diff --git a/src/Caliburn.Micro.Platform.Core/NameTransformer.cs b/src/Caliburn.Micro.Platform.Core/NameTransformer.cs index 4a28a9fbe..28ddc863b 100644 --- a/src/Caliburn.Micro.Platform.Core/NameTransformer.cs +++ b/src/Caliburn.Micro.Platform.Core/NameTransformer.cs @@ -3,94 +3,71 @@ using System.Linq; using System.Text.RegularExpressions; -namespace Caliburn.Micro -{ +namespace Caliburn.Micro { /// /// Class for managing the list of rules for doing name transformation. /// - public class NameTransformer : BindableCollection - { - - private const RegexOptions options = RegexOptions.None; - private bool useEagerRuleSelection = true; + public class NameTransformer : BindableCollection { + private const RegexOptions Options = RegexOptions.None; /// - /// Flag to indicate if transformations from all matched rules are returned. Otherwise, transformations from only the first matched rule are returned. + /// Gets or sets a value indicating whether if transformations from all matched rules are returned. Otherwise, transformations from only the first matched rule are returned. /// - public bool UseEagerRuleSelection - { - get { return useEagerRuleSelection; } - set { useEagerRuleSelection = value; } - } + public bool UseEagerRuleSelection { get; set; } = true; /// /// Adds a transform using a single replacement value and a global filter pattern. /// - /// Regular expression pattern for replacing text + /// Regular expression pattern for replacing text. /// The replacement value. - /// Regular expression pattern for global filtering + /// Regular expression pattern for global filtering. public void AddRule(string replacePattern, string replaceValue, string globalFilterPattern = null) - { - AddRule(replacePattern, new[] { replaceValue }, globalFilterPattern); - } + => AddRule(replacePattern, new[] { replaceValue }, globalFilterPattern); /// /// Adds a transform using a list of replacement values and a global filter pattern. /// - /// Regular expression pattern for replacing text - /// The list of replacement values - /// Regular expression pattern for global filtering - public void AddRule(string replacePattern, IEnumerable replaceValueList, string globalFilterPattern = null) - { - Add(new Rule - { - ReplacePattern = replacePattern, - ReplacementValues = replaceValueList, - GlobalFilterPattern = globalFilterPattern - }); - } + /// Regular expression pattern for replacing text. + /// The list of replacement values. + /// Regular expression pattern for global filtering. + public void AddRule(string replacePattern, IEnumerable replaceValueList, string globalFilterPattern = null) => Add(new Rule { + ReplacePattern = replacePattern, + ReplacementValues = replaceValueList, + GlobalFilterPattern = globalFilterPattern, + }); /// /// Gets the list of transformations for a given name. /// - /// The name to transform into the resolved name list + /// The name to transform into the resolved name list. /// The transformed names. - public IEnumerable Transform(string source) - { - return Transform(source, r => r); - } + public IEnumerable Transform(string source) => Transform(source, r => r); /// /// Gets the list of transformations for a given name. /// - /// The name to transform into the resolved name list - /// A function to do a transform on each item in the ReplaceValueList prior to applying the regular expression transform + /// The name to transform into the resolved name list. + /// A function to do a transform on each item in the ReplaceValueList prior to applying the regular expression transform. /// The transformed names. - public IEnumerable Transform(string source, Func getReplaceString) - { + public IEnumerable Transform(string source, Func getReplaceString) { var nameList = new List(); - var rules = this.Reverse(); + IEnumerable rules = this.Reverse(); - foreach (var rule in rules) - { - if (!string.IsNullOrEmpty(rule.GlobalFilterPattern) && !rule.GlobalFilterPatternRegex.IsMatch(source)) - { + foreach (Rule rule in rules) { + if (!string.IsNullOrEmpty(rule.GlobalFilterPattern) && !rule.GlobalFilterPatternRegex.IsMatch(source)) { continue; } - if (!rule.ReplacePatternRegex.IsMatch(source)) - { + if (!rule.ReplacePatternRegex.IsMatch(source)) { continue; } nameList.AddRange( rule.ReplacementValues .Select(getReplaceString) - .Select(repString => rule.ReplacePatternRegex.Replace(source, repString)) - ); + .Select(repString => rule.ReplacePatternRegex.Replace(source, repString))); - if (!useEagerRuleSelection) - { + if (!UseEagerRuleSelection) { break; } } @@ -98,50 +75,39 @@ public IEnumerable Transform(string source, Func getRepl return nameList; } - /// + /// /// A rule that describes a name transform. - /// - public class Rule - { + /// + public class Rule { private Regex replacePatternRegex; private Regex globalFilterPatternRegex; /// - /// Regular expression pattern for global filtering + /// Gets or sets regular expression pattern for global filtering. /// - public string GlobalFilterPattern; + public string GlobalFilterPattern { get; set; } /// - /// Regular expression pattern for replacing text + /// Gets or sets regular expression pattern for replacing text. /// - public string ReplacePattern; + public string ReplacePattern { get; set; } /// - /// The list of replacement values + /// Gets or sets the list of replacement values. /// - public IEnumerable ReplacementValues; + public IEnumerable ReplacementValues { get; set; } /// - /// Regular expression for global filtering + /// Gets regular expression for global filtering. /// public Regex GlobalFilterPatternRegex - { - get - { - return globalFilterPatternRegex ?? (globalFilterPatternRegex = new Regex(GlobalFilterPattern, options)); - } - } + => globalFilterPatternRegex ?? (globalFilterPatternRegex = new Regex(GlobalFilterPattern, Options)); /// - /// Regular expression for replacing text + /// Gets regular expression for replacing text. /// public Regex ReplacePatternRegex - { - get - { - return replacePatternRegex ?? (replacePatternRegex = new Regex(ReplacePattern, options)); - } - } + => replacePatternRegex ?? (replacePatternRegex = new Regex(ReplacePattern, Options)); } } -} \ No newline at end of file +} diff --git a/src/Caliburn.Micro.Platform.Core/RegExHelper.cs b/src/Caliburn.Micro.Platform.Core/RegExHelper.cs index 678e8a34f..b01344cc2 100644 --- a/src/Caliburn.Micro.Platform.Core/RegExHelper.cs +++ b/src/Caliburn.Micro.Platform.Core/RegExHelper.cs @@ -1,71 +1,63 @@ using System; -namespace Caliburn.Micro -{ +namespace Caliburn.Micro { /// - /// Helper class for encoding strings to regular expression patterns + /// Helper class for encoding strings to regular expression patterns. /// - public static class RegExHelper - { + public static class RegExHelper { /// - /// Regular expression pattern for valid name + /// Regular expression pattern for valid name. /// public const string NameRegEx = @"[\p{Lu}\p{Ll}\p{Lt}\p{Lm}\p{Lo}\p{Nl}_][\p{Lu}\p{Ll}\p{Lt}\p{Lm}\p{Lo}\p{Nl}\p{Mn}\p{Mc}\p{Nd}\p{Pc}\p{Cf}_]*"; /// - /// Regular expression pattern for subnamespace (including dot) + /// Regular expression pattern for subnamespace (including dot). /// public const string SubNamespaceRegEx = NameRegEx + @"\."; /// - /// Regular expression pattern for namespace or namespace fragment + /// Regular expression pattern for namespace or namespace fragment. /// public const string NamespaceRegEx = "(" + SubNamespaceRegEx + ")*"; /// - /// Creates a named capture group with the specified regular expression + /// Creates a named capture group with the specified regular expression. /// - /// Name of capture group to create - /// Regular expression pattern to capture - /// Regular expression capture group with the specified group name + /// Name of capture group to create. + /// Regular expression pattern to capture. + /// Regular expression capture group with the specified group name. public static string GetCaptureGroup(string groupName, string regEx) - { - return String.Concat(@"(?<", groupName, ">", regEx, ")"); - } + => string.Concat(@"(?<", groupName, ">", regEx, ")"); /// - /// Converts a namespace (including wildcards) to a regular expression string + /// Converts a namespace (including wildcards) to a regular expression string. /// - /// Source namespace to convert to regular expression - /// Namespace converted to a regular expression - public static string NamespaceToRegEx(string srcNamespace) - { - //Need to escape the "." as it's a special character in regular expression syntax - var nsencoded = srcNamespace.Replace(".", @"\."); + /// Source namespace to convert to regular expression. + /// Namespace converted to a regular expression. + public static string NamespaceToRegEx(string srcNamespace) { + // Need to escape the "." as it's a special character in regular expression syntax + string nsencoded = srcNamespace.Replace(".", @"\."); - //Replace "*" wildcard with regular expression syntax + // Replace "*" wildcard with regular expression syntax nsencoded = nsencoded.Replace(@"*\.", NamespaceRegEx); + return nsencoded; } /// - /// Creates a capture group for a valid name regular expression pattern + /// Creates a capture group for a valid name regular expression pattern. /// - /// Name of capture group to create - /// Regular expression capture group with the specified group name + /// Name of capture group to create. + /// Regular expression capture group with the specified group name. public static string GetNameCaptureGroup(string groupName) - { - return GetCaptureGroup(groupName, NameRegEx); - } + => GetCaptureGroup(groupName, NameRegEx); /// - /// Creates a capture group for a namespace regular expression pattern + /// Creates a capture group for a namespace regular expression pattern. /// - /// Name of capture group to create - /// Regular expression capture group with the specified group name + /// Name of capture group to create. + /// Regular expression capture group with the specified group name. public static string GetNamespaceCaptureGroup(string groupName) - { - return GetCaptureGroup(groupName, NamespaceRegEx); - } + => GetCaptureGroup(groupName, NamespaceRegEx); } -} \ No newline at end of file +} diff --git a/src/Caliburn.Micro.Platform.Core/StringSplitter.cs b/src/Caliburn.Micro.Platform.Core/StringSplitter.cs index a9d8f2240..14e4d5475 100644 --- a/src/Caliburn.Micro.Platform.Core/StringSplitter.cs +++ b/src/Caliburn.Micro.Platform.Core/StringSplitter.cs @@ -2,64 +2,47 @@ using System.Linq; using System.Text; -namespace Caliburn.Micro -{ - +namespace Caliburn.Micro { /// - /// Helper class when splitting strings + /// Helper class when splitting strings. /// - public static class StringSplitter - { - + public static class StringSplitter { /// - /// Splits a string with a chosen separator. + /// Splits a string with a chosen separator. /// If a substring is contained in [...] it will not be splitted. /// - /// The message to split - /// The separator to use when splitting - /// - public static string[] Split(string message, char separator) - { - //Splits a string using the specified separator, if it is found outside of relevant places - //delimited by [ and ] + /// The message to split. + /// The separator to use when splitting. + public static string[] Split(string message, char separator) { + // Splits a string using the specified separator, if it is found outside of relevant places + // delimited by [ and ] string str; var list = new List(); var builder = new StringBuilder(); int squareBrackets = 0; - foreach (var current in message) - { - //Square brackets are used as delimiters, so only separators outside them count... - if (current == '[') - { + foreach (char current in message) { + // Square brackets are used as delimiters, so only separators outside them count... + if (current == '[') { squareBrackets++; - } - else if (current == ']') - { + } else if (current == ']') { squareBrackets--; - } - else if (current == separator) - { - if (squareBrackets == 0) - { - str = builder.ToString(); - if (!string.IsNullOrEmpty(str)) - { - list.Add(builder.ToString().Trim()); - } - - builder.Length = 0; - continue; + } else if (current == separator && squareBrackets == 0) { + str = builder.ToString(); + if (!string.IsNullOrEmpty(str)) { + list.Add(builder.ToString().Trim()); } + + builder.Length = 0; + continue; } builder.Append(current); } str = builder.ToString(); - if (!string.IsNullOrEmpty(str)) - { + if (!string.IsNullOrEmpty(str)) { list.Add(builder.ToString().Trim()); } @@ -69,14 +52,12 @@ public static string[] Split(string message, char separator) } /// - /// Splits a string with , as separator. - /// Does not split within {},[],() + /// Splits a string with , as separator. + /// Does not split within {},[],(). /// - /// The string to split - /// - public static string[] SplitParameters(string parameters) - { - //Splits parameter string taking into account brackets... + /// The string to split. + public static string[] SplitParameters(string parameters) { + // Splits parameter string taking into account brackets... var list = new List(); var builder = new StringBuilder(); @@ -85,22 +66,15 @@ public static string[] SplitParameters(string parameters) int curlyBrackets = 0; int squareBrackets = 0; int roundBrackets = 0; - for (int i = 0; i < parameters.Length; i++) - { - var current = parameters[i]; - - if (current == '"' || current == '\'') - { - if (i == 0 || parameters[i - 1] != '\\') - { - isInString = !isInString; - } + for (int i = 0; i < parameters.Length; i++) { + char current = parameters[i]; + if ((current == '"' || current == '\'') && + (i == 0 || parameters[i - 1] != '\\')) { + isInString = !isInString; } - if (!isInString) - { - switch (current) - { + if (!isInString) { + switch (current) { case '{': curlyBrackets++; break; @@ -120,18 +94,18 @@ public static string[] SplitParameters(string parameters) roundBrackets--; break; default: - if (current == ',' && roundBrackets == 0 && squareBrackets == 0 && curlyBrackets == 0) - { - //The only commas to be considered as parameter separators are outside: - //- Strings - //- Square brackets (to ignore indexers) - //- Parantheses (to ignore method invocations) - //- Curly brackets (to ignore initializers and Bindings) - list.Add(builder.ToString()); - builder.Length = 0; - continue; + if (current != ',' || roundBrackets != 0 || squareBrackets != 0 || curlyBrackets != 0) { + break; } - break; + + // The only commas to be considered as parameter separators are outside: + // - Strings + // - Square brackets (to ignore indexers) + // - Parantheses (to ignore method invocations) + // - Curly brackets (to ignore initializers and Bindings) + list.Add(builder.ToString()); + builder.Length = 0; + continue; } } diff --git a/src/Caliburn.Micro.Platform.Core/TypeMappingConfiguration.cs b/src/Caliburn.Micro.Platform.Core/TypeMappingConfiguration.cs index 58589a56d..7fde0bda3 100644 --- a/src/Caliburn.Micro.Platform.Core/TypeMappingConfiguration.cs +++ b/src/Caliburn.Micro.Platform.Core/TypeMappingConfiguration.cs @@ -1,46 +1,57 @@ -namespace Caliburn.Micro -{ - using System.Collections.Generic; +using System.Collections.Generic; +namespace Caliburn.Micro { /// - /// Class to specify settings for configuring type mappings by the ViewLocator or ViewModelLocator + /// Class to specify settings for configuring type mappings by the ViewLocator or ViewModelLocator. /// - public class TypeMappingConfiguration - { + public class TypeMappingConfiguration { /// - /// The default subnamespace for Views. Used for creating default subnamespace mappings. Defaults to "Views". + /// Initializes a new instance of the class. /// - public string DefaultSubNamespaceForViews { get; set; } = "Views"; + public TypeMappingConfiguration() { + DefaultSubNamespaceForViews = "Views"; + DefaultSubNamespaceForViewModels = "ViewModels"; + UseNameSuffixesInMappings = true; + NameFormat = @"{0}{1}"; + IncludeViewSuffixInViewModelNames = true; + ViewSuffixList = new List(new[] { "View", "Page" }); + ViewModelSuffix = "ViewModel"; + } /// - /// The default subnamespace for ViewModels. Used for creating default subnamespace mappings. Defaults to "ViewModels". + /// Gets or sets the default subnamespace for Views. Used for creating default subnamespace mappings. Defaults to "Views". /// - public string DefaultSubNamespaceForViewModels { get; set; } = "ViewModels"; + public string DefaultSubNamespaceForViews { get; set; } /// - /// Flag to indicate whether or not the name of the Type should be transformed when adding a type mapping. Defaults to true. + /// Gets or sets the default subnamespace for ViewModels. Used for creating default subnamespace mappings. Defaults to "ViewModels". /// - public bool UseNameSuffixesInMappings { get; set; } = true; + public string DefaultSubNamespaceForViewModels { get; set; } /// - /// The format string used to compose the name of a type from base name and name suffix + /// Gets or sets a value indicating whether flag to indicate whether or not the name of the Type should be transformed when adding a type mapping. Defaults to true. /// - public string NameFormat { get; set; } = @"{0}{1}"; + public bool UseNameSuffixesInMappings { get; set; } /// - /// Flag to indicate if ViewModel names should include View suffixes (i.e. CustomerPageViewModel vs. CustomerViewModel) + /// Gets or sets the format string used to compose the name of a type from base name and name suffix. /// - public bool IncludeViewSuffixInViewModelNames { get; set; } = true; + public string NameFormat { get; set; } /// - /// List of View suffixes for which default type mappings should be created. Applies only when UseNameSuffixesInMappings = true. - /// Default values are "View", "Page" + /// Gets or sets a value indicating whether flag to indicate if ViewModel names should include View suffixes (i.e. CustomerPageViewModel vs. CustomerViewModel). /// - public List ViewSuffixList { get; set; } = new List(new[] { "View", "Page" }); + public bool IncludeViewSuffixInViewModelNames { get; set; } /// - /// The name suffix for ViewModels. Applies only when UseNameSuffixesInMappings = true. The default is "ViewModel". + /// Gets or sets list of View suffixes for which default type mappings should be created. Applies only when UseNameSuffixesInMappings = true. + /// Default values are "View", "Page". /// - public string ViewModelSuffix { get; set; } = "ViewModel"; + public List ViewSuffixList { get; set; } + + /// + /// Gets or sets the name suffix for ViewModels. Applies only when UseNameSuffixesInMappings = true. The default is "ViewModel". + /// + public string ViewModelSuffix { get; set; } } -} \ No newline at end of file +} diff --git a/src/Caliburn.Micro.Platform.Tests/AssemblyCacheTests.cs b/src/Caliburn.Micro.Platform.Tests/AssemblyCacheTests.cs index 72fa67271..39b2e9c0f 100644 --- a/src/Caliburn.Micro.Platform.Tests/AssemblyCacheTests.cs +++ b/src/Caliburn.Micro.Platform.Tests/AssemblyCacheTests.cs @@ -1,5 +1,5 @@ using System; -using System.Globalization; + using Xunit; namespace Caliburn.Micro.Platform.Tests @@ -11,11 +11,11 @@ public void AddingTheSameAssemblyMoreThanOneShouldNotThrow() { AssemblySourceCache.Install(); - var testAssembly = typeof(AssemblyCacheTests).Assembly; + System.Reflection.Assembly testAssembly = typeof(AssemblyCacheTests).Assembly; AssemblySource.Instance.Add(testAssembly); - //Re-add the same assembly - var exception = Record.Exception(() => AssemblySource.Instance.Add(testAssembly)); + /* Re-add the same assembly */ + Exception exception = Record.Exception(() => AssemblySource.Instance.Add(testAssembly)); Assert.Null(exception); } @@ -24,17 +24,13 @@ public void ResettingTheCacheWithMoreThanOneAssemblyShouldNotThrow() { AssemblySourceCache.Install(); - var testAssembly = typeof(AssemblyCacheTests).Assembly; + System.Reflection.Assembly testAssembly = typeof(AssemblyCacheTests).Assembly; AssemblySource.Instance.AddRange(new[] { testAssembly, testAssembly }); - //Refresh clears and re-creates the cache - var exception = Record.Exception(() => AssemblySource.Instance.Refresh()); + /* Refresh clears and re-creates the cache */ + Exception exception = Record.Exception(() => AssemblySource.Instance.Refresh()); Assert.Null(exception); } } - - public class TestScreen : Screen - { - } } diff --git a/src/Caliburn.Micro.Platform.Tests/BindingScopeTests.cs b/src/Caliburn.Micro.Platform.Tests/BindingScopeTests.cs deleted file mode 100644 index 433e51e25..000000000 --- a/src/Caliburn.Micro.Platform.Tests/BindingScopeTests.cs +++ /dev/null @@ -1,98 +0,0 @@ -using System.Collections.Generic; -using System.Linq; -using System.Windows; -using System.Windows.Controls; -using Xunit; - -namespace Caliburn.Micro.Platform.Tests -{ - public class BindingScopeFindName - { - [WpfFact] - public void A_given_match_is_always_the_first_instance_found() - { - var elements = new List - { - new FrameworkElement - { - Name = "Foo" - }, - new FrameworkElement - { - Name = "Foo" - } - }; - - var found = BindingScope.FindName(elements, "Foo"); - Assert.NotSame(elements.Last(), found); - } - - [WpfFact] - public void A_given_name_is_found_regardless_of_case_sensitivity() - { - var elements = new List - { - new FrameworkElement - { - Name = "FOO" - } - }; - - var found = BindingScope.FindName(elements, "foo"); - Assert.NotNull(found); - } - - [WpfFact] - public void A_given_name_is_matched_correctly() - { - var elements = new List - { - new FrameworkElement - { - Name = "Bar" - }, - new FrameworkElement - { - Name = "Foo" - } - }; - - var found = BindingScope.FindName(elements, "Foo"); - Assert.NotNull(found); - } - } - - public class BindingScope_FindScopeNamingRoute - { - [WpfFact] - public void A_given_Pages_Content_is_ScopeRoute_if_it_is_a_dependency_object() - { - var page = new Page - { - Content = new Control() - }; - var route = BindingScope.FindScopeNamingRoute(page); - - Assert.Same(page.Content, route.Root); - } - - [WpfFact] - public void A_given_UserControl_is_ScopeRoute() - { - var userControl = new UserControl(); - var route = BindingScope.FindScopeNamingRoute(userControl); - - Assert.Same(userControl, route.Root); - } - - [WpfFact] - public void Any_DependencyObject_is_ScopeRoot_if_IsScopeRoot_is_true() - { - var dependencyObject = new DependencyObject(); - dependencyObject.SetValue(View.IsScopeRootProperty, true); - var route = BindingScope.FindScopeNamingRoute(dependencyObject); - - Assert.Same(dependencyObject, route.Root); - } - } -} diff --git a/src/Caliburn.Micro.Platform.Tests/BindingScope_FindName_Tests.cs b/src/Caliburn.Micro.Platform.Tests/BindingScope_FindName_Tests.cs new file mode 100644 index 000000000..81f6e8bc9 --- /dev/null +++ b/src/Caliburn.Micro.Platform.Tests/BindingScope_FindName_Tests.cs @@ -0,0 +1,64 @@ +using System.Collections.Generic; +using System.Linq; +using System.Windows; + +using Xunit; + +namespace Caliburn.Micro.Platform.Tests +{ + public class BindingScope_FindName_Tests + { + [WpfFact] + public void A_given_match_is_always_the_first_instance_found() + { + var elements = new List + { + new FrameworkElement + { + Name = "Foo", + }, + new FrameworkElement + { + Name = "Foo", + }, + }; + + FrameworkElement found = BindingScope.FindName(elements, "Foo"); + Assert.NotSame(elements.Last(), found); + } + + [WpfFact] + public void A_given_name_is_found_regardless_of_case_sensitivity() + { + var elements = new List + { + new FrameworkElement + { + Name = "FOO", + }, + }; + + FrameworkElement found = BindingScope.FindName(elements, "foo"); + Assert.NotNull(found); + } + + [WpfFact] + public void A_given_name_is_matched_correctly() + { + var elements = new List + { + new FrameworkElement + { + Name = "Bar", + }, + new FrameworkElement + { + Name = "Foo", + }, + }; + + FrameworkElement found = BindingScope.FindName(elements, "Foo"); + Assert.NotNull(found); + } + } +} diff --git a/src/Caliburn.Micro.Platform.Tests/BindingScope_FindScopeNamingRoute_Tests.cs b/src/Caliburn.Micro.Platform.Tests/BindingScope_FindScopeNamingRoute_Tests.cs new file mode 100644 index 000000000..63af90951 --- /dev/null +++ b/src/Caliburn.Micro.Platform.Tests/BindingScope_FindScopeNamingRoute_Tests.cs @@ -0,0 +1,35 @@ +using System.Windows; +using System.Windows.Controls; + +using Xunit; + +namespace Caliburn.Micro.Platform.Tests { + public class BindingScope_FindScopeNamingRoute_Tests { + [WpfFact] + public void A_given_Pages_Content_is_ScopeRoute_if_it_is_a_dependency_object() { + var page = new Page { + Content = new Control(), + }; + BindingScope.ScopeNamingRoute route = BindingScope.FindScopeNamingRoute(page); + + Assert.Same(page.Content, route.Root); + } + + [WpfFact] + public void A_given_UserControl_is_ScopeRoute() { + var userControl = new UserControl(); + BindingScope.ScopeNamingRoute route = BindingScope.FindScopeNamingRoute(userControl); + + Assert.Same(userControl, route.Root); + } + + [WpfFact] + public void Any_DependencyObject_is_ScopeRoot_if_IsScopeRoot_is_true() { + var dependencyObject = new DependencyObject(); + dependencyObject.SetValue(View.IsScopeRootProperty, true); + BindingScope.ScopeNamingRoute route = BindingScope.FindScopeNamingRoute(dependencyObject); + + Assert.Same(dependencyObject, route.Root); + } + } +} diff --git a/src/Caliburn.Micro.Platform.Tests/Caliburn.Micro.Platform.Tests.csproj b/src/Caliburn.Micro.Platform.Tests/Caliburn.Micro.Platform.Tests.csproj index 04ad2ae69..982f37874 100644 --- a/src/Caliburn.Micro.Platform.Tests/Caliburn.Micro.Platform.Tests.csproj +++ b/src/Caliburn.Micro.Platform.Tests/Caliburn.Micro.Platform.Tests.csproj @@ -1,13 +1,20 @@  + + net462 false + $(NoWarn);CA1707;SA0001 + all @@ -17,20 +24,16 @@ - - - - - - - + - + + + \ No newline at end of file diff --git a/src/Caliburn.Micro.Platform.Tests/MessageBinderTests.cs b/src/Caliburn.Micro.Platform.Tests/MessageBinderTests.cs index 2337e473c..cf70c2fea 100644 --- a/src/Caliburn.Micro.Platform.Tests/MessageBinderTests.cs +++ b/src/Caliburn.Micro.Platform.Tests/MessageBinderTests.cs @@ -8,11 +8,11 @@ public class MessageBinderTests public void EvaluateParameterCaseInsensitive() { MessageBinder.SpecialValues.Add("$sampleParameter", context => 42); - var caseSensitiveValue = MessageBinder.EvaluateParameter("$sampleParameter", typeof(int), new ActionExecutionContext()); + object caseSensitiveValue = MessageBinder.EvaluateParameter("$sampleParameter", typeof(int), new ActionExecutionContext()); Assert.NotEqual("$sampleParameter", caseSensitiveValue); - var caseInsensitiveValue = MessageBinder.EvaluateParameter("$sampleparameter", typeof(int), new ActionExecutionContext()); + object caseInsensitiveValue = MessageBinder.EvaluateParameter("$sampleparameter", typeof(int), new ActionExecutionContext()); Assert.NotEqual("$sampleparameter", caseInsensitiveValue); } } diff --git a/src/Caliburn.Micro.Platform.Tests/ParserTests.cs b/src/Caliburn.Micro.Platform.Tests/ParserTests.cs index f20324a76..e046c2dc1 100644 --- a/src/Caliburn.Micro.Platform.Tests/ParserTests.cs +++ b/src/Caliburn.Micro.Platform.Tests/ParserTests.cs @@ -1,9 +1,5 @@ -using System; -using System.Collections.Generic; -using System.Globalization; -using System.Linq; -using System.Text; -using System.Threading.Tasks; +using System.Globalization; + using Xunit; namespace Caliburn.Micro.Platform.Tests @@ -13,9 +9,9 @@ public class ParserTests [Fact] public void CreateParametersWithOddNumbers() { - var evenResult = Parser.CreateParameter(null, "0.1"); - var oddResult = Parser.CreateParameter(null, "-0.1"); - var nanResult = Parser.CreateParameter(null, "-0.1abc"); + Parameter evenResult = Parser.CreateParameter(null, "0.1"); + Parameter oddResult = Parser.CreateParameter(null, "-0.1"); + Parameter nanResult = Parser.CreateParameter(null, "-0.1abc"); Assert.Equal(0.1, double.Parse((string)evenResult.Value, CultureInfo.InvariantCulture)); Assert.Equal(-0.1, double.Parse((string)oddResult.Value, CultureInfo.InvariantCulture)); diff --git a/src/Caliburn.Micro.Platform.Tests/ScopeNamingRouteTests.cs b/src/Caliburn.Micro.Platform.Tests/ScopeNamingRouteTests.cs index 1403eed06..ca588ab12 100644 --- a/src/Caliburn.Micro.Platform.Tests/ScopeNamingRouteTests.cs +++ b/src/Caliburn.Micro.Platform.Tests/ScopeNamingRouteTests.cs @@ -1,6 +1,7 @@ using System; using System.Collections.Generic; using System.Windows; + using Xunit; namespace Caliburn.Micro.Platform.Tests @@ -16,7 +17,7 @@ public void CannotAddDisjointHops() var d3 = new DependencyObject(); var d4 = new DependencyObject(); - // d1 -> d2 and d3 -> d4, but d2 doesn't lead to d3, so d3 -> d4 is rejected + /* d1 -> d2 and d3 -> d4, but d2 doesn't lead to d3, so d3 -> d4 is rejected */ route.AddHop(d1, d2); Assert.Throws(() => route.AddHop(d3, d4)); } @@ -29,7 +30,7 @@ public void CorrectlyGetsAddedHop() var d2 = new DependencyObject(); route.AddHop(d1, d2); DependencyObject target; - var result = route.TryGetHop(d1, out target); + bool result = route.TryGetHop(d1, out target); Assert.True(result); Assert.Same(d2, target); } @@ -51,10 +52,10 @@ public void GetsAllHopsAdded() { d2, d3, - d4 + d4, }; - var source = d1; + DependencyObject source = d1; DependencyObject target; while (route.TryGetHop(source, out target)) diff --git a/src/Caliburn.Micro.Platform.Tests/StringSplitterTests.cs b/src/Caliburn.Micro.Platform.Tests/StringSplitterTests.cs index 21ce578fa..cd902b815 100644 --- a/src/Caliburn.Micro.Platform.Tests/StringSplitterTests.cs +++ b/src/Caliburn.Micro.Platform.Tests/StringSplitterTests.cs @@ -1,54 +1,52 @@ using Xunit; -namespace Caliburn.Micro.Platform.Tests -{ - public class StringSplitterTests - { +namespace Caliburn.Micro.Platform.Tests { + public class StringSplitterTests { [Fact] - public void SplitSimpleString() - { - var output = StringSplitter.Split("MyMethodName", ';'); + public void SplitSimpleString() { + string[] output = StringSplitter.Split("MyMethodName", ';'); Assert.Collection(output, o => Assert.Equal("MyMethodName", o)); } [Fact] - public void SplitSeparatedString() - { - var output = StringSplitter.Split("First;Second", ';'); + public void SplitSeparatedString() { + string[] output = StringSplitter.Split("First;Second", ';'); - Assert.Collection(output, + Assert.Collection( + output, o => Assert.Equal("First", o), o => Assert.Equal("Second", o)); } [Fact] - public void TrimsSplitSimpleString() - { - var output = StringSplitter.Split("MyMethodName ", ';'); + public void TrimsSplitSimpleString() { + string[] output = StringSplitter.Split("MyMethodName ", ';'); Assert.Collection(output, o => Assert.Equal("MyMethodName", o)); } [Fact] - public void RemovesEmptySplitsSeparatedString() - { - var output = StringSplitter.Split("First;Second;", ';'); + public void RemovesEmptySplitsSeparatedString() { + string[] output = StringSplitter.Split("First;Second;", ';'); - Assert.Collection(output, + Assert.Collection( + output, o => Assert.Equal("First", o), o => Assert.Equal("Second", o)); } [Fact] - public void HandlesNewLinesInSeparatedString() - { - var output = StringSplitter.Split(@" + public void HandlesNewLinesInSeparatedString() { + string[] output = StringSplitter.Split( + @" First; Second; - ", ';'); + ", + ';'); - Assert.Collection(output, + Assert.Collection( + output, o => Assert.Equal("First", o), o => Assert.Equal("Second", o)); } diff --git a/src/Caliburn.Micro.Platform.Tests/ViewLocatorTests.cs b/src/Caliburn.Micro.Platform.Tests/ViewLocatorTests.cs index f2f4de0ef..6de8cce91 100644 --- a/src/Caliburn.Micro.Platform.Tests/ViewLocatorTests.cs +++ b/src/Caliburn.Micro.Platform.Tests/ViewLocatorTests.cs @@ -1,4 +1,5 @@ using System; + using Xunit; namespace Caliburn.Micro.Platform.Tests @@ -12,7 +13,7 @@ public void ConfigureTypeMappingsShouldThrowWhenDefaultSubNamespaceForViewModels { DefaultSubNamespaceForViews = "not empty", DefaultSubNamespaceForViewModels = string.Empty, - NameFormat = "not Empty" + NameFormat = "{0}{1}", }; Assert.Throws(() => ViewLocator.ConfigureTypeMappings(config)); @@ -25,7 +26,7 @@ public void ConfigureTypeMappingsShouldThrowWhenDefaultSubNamespaceForViewModels { DefaultSubNamespaceForViews = "not null", DefaultSubNamespaceForViewModels = null, - NameFormat = "not null" + NameFormat = "{0}{1}", }; Assert.Throws(() => ViewLocator.ConfigureTypeMappings(config)); @@ -38,7 +39,7 @@ public void ConfigureTypeMappingsShouldThrowWhenDefaultSubNamespaceForViewsIsEmp { DefaultSubNamespaceForViews = string.Empty, DefaultSubNamespaceForViewModels = "not Empty", - NameFormat = "not Empty" + NameFormat = "{0}{1}", }; Assert.Throws(() => ViewLocator.ConfigureTypeMappings(config)); @@ -51,7 +52,7 @@ public void ConfigureTypeMappingsShouldThrowWhenDefaultSubNamespaceForViewsIsNul { DefaultSubNamespaceForViews = null, DefaultSubNamespaceForViewModels = "not null", - NameFormat = "not null" + NameFormat = "{0}{1}", }; Assert.Throws(() => ViewLocator.ConfigureTypeMappings(config)); @@ -64,7 +65,7 @@ public void ConfigureTypeMappingsShouldThrowWhenNameFormatIsEmpty() { DefaultSubNamespaceForViews = "not Empty", DefaultSubNamespaceForViewModels = "not Empty", - NameFormat = string.Empty + NameFormat = string.Empty, }; Assert.Throws(() => ViewLocator.ConfigureTypeMappings(config)); @@ -77,7 +78,7 @@ public void ConfigureTypeMappingsShouldThrowWhenNameFormatIsNull() { DefaultSubNamespaceForViews = "not null", DefaultSubNamespaceForViewModels = "not null", - NameFormat = null + NameFormat = null, }; Assert.Throws(() => ViewLocator.ConfigureTypeMappings(config)); diff --git a/src/Caliburn.Micro.Platform.Tests/ViewModelLocatorTests.cs b/src/Caliburn.Micro.Platform.Tests/ViewModelLocatorTests.cs index abb890884..6649a0410 100644 --- a/src/Caliburn.Micro.Platform.Tests/ViewModelLocatorTests.cs +++ b/src/Caliburn.Micro.Platform.Tests/ViewModelLocatorTests.cs @@ -1,91 +1,77 @@ using System; + using Xunit; -namespace Caliburn.Micro.Platform.Tests -{ - public class ViewModelLocatorTests - { +namespace Caliburn.Micro.Platform.Tests { + public class ViewModelLocatorTests { [Fact] - public void ConfigureTypeMappingsShouldThrowWhenDefaultSubNamespaceForViewModelsIsEmpty() - { - var config = new TypeMappingConfiguration - { + public void ConfigureTypeMappingsShouldThrowWhenDefaultSubNamespaceForViewModelsIsEmpty() { + var config = new TypeMappingConfiguration { DefaultSubNamespaceForViews = "not empty", DefaultSubNamespaceForViewModels = string.Empty, - NameFormat = "not Empty" + NameFormat = "{0}{1}", }; Assert.Throws(() => ViewModelLocator.ConfigureTypeMappings(config)); } [Fact] - public void ConfigureTypeMappingsShouldThrowWhenDefaultSubNamespaceForViewModelsIsNull() - { - var config = new TypeMappingConfiguration - { + public void ConfigureTypeMappingsShouldThrowWhenDefaultSubNamespaceForViewModelsIsNull() { + var config = new TypeMappingConfiguration { DefaultSubNamespaceForViews = "not null", DefaultSubNamespaceForViewModels = null, - NameFormat = "not null" + NameFormat = "{0}{1}", }; Assert.Throws(() => ViewModelLocator.ConfigureTypeMappings(config)); } [Fact] - public void ConfigureTypeMappingsShouldThrowWhenDefaultSubNamespaceForViewsIsEmpty() - { - var config = new TypeMappingConfiguration - { + public void ConfigureTypeMappingsShouldThrowWhenDefaultSubNamespaceForViewsIsEmpty() { + var config = new TypeMappingConfiguration { DefaultSubNamespaceForViews = string.Empty, DefaultSubNamespaceForViewModels = "not Empty", - NameFormat = "not Empty" + NameFormat = "{0}{1}", }; Assert.Throws(() => ViewModelLocator.ConfigureTypeMappings(config)); } [Fact] - public void ConfigureTypeMappingsShouldThrowWhenDefaultSubNamespaceForViewsIsNull() - { - var config = new TypeMappingConfiguration - { + public void ConfigureTypeMappingsShouldThrowWhenDefaultSubNamespaceForViewsIsNull() { + var config = new TypeMappingConfiguration { DefaultSubNamespaceForViews = null, DefaultSubNamespaceForViewModels = "not null", - NameFormat = "not null" + NameFormat = "{0}{1}", }; Assert.Throws(() => ViewModelLocator.ConfigureTypeMappings(config)); } [Fact] - public void ConfigureTypeMappingsShouldThrowWhenNameFormatIsEmpty() - { - var config = new TypeMappingConfiguration - { + public void ConfigureTypeMappingsShouldThrowWhenNameFormatIsEmpty() { + var config = new TypeMappingConfiguration { DefaultSubNamespaceForViews = "not Empty", DefaultSubNamespaceForViewModels = "not Empty", - NameFormat = string.Empty + NameFormat = string.Empty, }; Assert.Throws(() => ViewModelLocator.ConfigureTypeMappings(config)); } [Fact] - public void ConfigureTypeMappingsShouldThrowWhenNameFormatIsNull() - { - var config = new TypeMappingConfiguration - { + public void ConfigureTypeMappingsShouldThrowWhenNameFormatIsNull() { + var config = new TypeMappingConfiguration { DefaultSubNamespaceForViews = "not null", DefaultSubNamespaceForViewModels = "not null", - NameFormat = null + NameFormat = null, }; Assert.Throws(() => ViewModelLocator.ConfigureTypeMappings(config)); } [Fact] - public void COnfigureTypeMappingsWithDefaultValuesShouldNotThrow() - { + public void COnfigureTypeMappingsWithDefaultValuesShouldNotThrow() { var typeMappingConfiguration = new TypeMappingConfiguration(); ViewModelLocator.ConfigureTypeMappings(typeMappingConfiguration); diff --git a/src/Caliburn.Micro.Platform/Action.cs b/src/Caliburn.Micro.Platform/Action.cs index a1dffe372..290ff3400 100644 --- a/src/Caliburn.Micro.Platform/Action.cs +++ b/src/Caliburn.Micro.Platform/Action.cs @@ -1,4 +1,22 @@ -#if XFORMS +#if WINDOWS_UWP +using System.Linq; +using System.Reflection; + +using Windows.UI.Xaml; +#elif XFORMS +using DependencyObject = Xamarin.Forms.BindableObject; +using DependencyProperty = Xamarin.Forms.BindableProperty; +using FrameworkElement = Xamarin.Forms.VisualElement; +using UIElement = Xamarin.Forms.Element; +#elif MAUI +using DependencyObject = Microsoft.Maui.Controls.BindableObject; +using DependencyProperty = Microsoft.Maui.Controls.BindableProperty; +using FrameworkElement = Microsoft.Maui.Controls.VisualElement; +#else +using System.Windows; +#endif + +#if XFORMS namespace Caliburn.Micro.Xamarin.Forms #elif MAUI namespace Caliburn.Micro.Maui @@ -6,30 +24,10 @@ namespace Caliburn.Micro.Maui namespace Caliburn.Micro #endif { -#if WINDOWS_UWP - using System.Linq; - using Windows.UI.Xaml; - using System.Reflection; -#elif XFORMS - using UIElement = global::Xamarin.Forms.Element; - using FrameworkElement = global::Xamarin.Forms.VisualElement; - using DependencyProperty = global::Xamarin.Forms.BindableProperty; - using DependencyObject = global::Xamarin.Forms.BindableObject; -#elif MAUI - using UIElement = global::Microsoft.Maui.Controls.Element; - using FrameworkElement = global::Microsoft.Maui.Controls.VisualElement; - using DependencyProperty = global::Microsoft.Maui.Controls.BindableProperty; - using DependencyObject = global::Microsoft.Maui.Controls.BindableObject; -#else - using System.Windows; -#endif - /// /// A host for action related attached properties. /// public static class Action { - static readonly ILog Log = LogManager.GetLog(typeof(Action)); - /// /// A property definition representing the target of an . The DataContext of the element will be set to this instance. /// @@ -38,9 +36,8 @@ public static class Action { "Target", typeof(object), typeof(Action), - null, - OnTargetChanged - ); + null, + OnTargetChanged); /// /// A property definition representing the target of an . The DataContext of the element is not set to this instance. @@ -50,27 +47,26 @@ public static class Action { "TargetWithoutContext", typeof(object), typeof(Action), - null, - OnTargetWithoutContextChanged - ); + null, + OnTargetWithoutContextChanged); + + private static readonly ILog Log = LogManager.GetLog(typeof(Action)); /// /// Sets the target of the . /// /// The element to attach the target to. /// The target for instances of . - public static void SetTarget(DependencyObject d, object target) { - d.SetValue(TargetProperty, target); - } + public static void SetTarget(DependencyObject d, object target) + => d.SetValue(TargetProperty, target); /// /// Gets the target for instances of . /// /// The element to which the target is attached. - /// The target for instances of - public static object GetTarget(DependencyObject d) { - return d.GetValue(TargetProperty); - } + /// The target for instances of . + public static object GetTarget(DependencyObject d) + => d.GetValue(TargetProperty); /// /// Sets the target of the . @@ -80,53 +76,47 @@ public static object GetTarget(DependencyObject d) { /// /// The DataContext will not be set. /// - public static void SetTargetWithoutContext(DependencyObject d, object target) { - d.SetValue(TargetWithoutContextProperty, target); - } + public static void SetTargetWithoutContext(DependencyObject d, object target) + => d.SetValue(TargetWithoutContextProperty, target); /// /// Gets the target for instances of . /// /// The element to which the target is attached. - /// The target for instances of - public static object GetTargetWithoutContext(DependencyObject d) { - return d.GetValue(TargetWithoutContextProperty); - } + /// The target for instances of . + public static object GetTargetWithoutContext(DependencyObject d) + => d.GetValue(TargetWithoutContextProperty); - /// + /// /// Checks if the -Target was set. - /// - /// DependencyObject to check - /// True if Target or TargetWithoutContext was set on + /// + /// DependencyObject to check. + /// True if Target or TargetWithoutContext was set on . public static bool HasTargetSet(DependencyObject element) { - if (GetTarget(element) != null || GetTargetWithoutContext(element) != null) + if (GetTarget(element) != null || GetTargetWithoutContext(element) != null) { return true; + } #if XFORMS return false; #else - var frameworkElement = element as FrameworkElement; - if (frameworkElement == null) - return false; - - return ConventionManager.HasBinding(frameworkElement, TargetProperty) - || ConventionManager.HasBinding(frameworkElement, TargetWithoutContextProperty); + return element is FrameworkElement frameworkElement && + (ConventionManager.HasBinding(frameworkElement, TargetProperty) || + ConventionManager.HasBinding(frameworkElement, TargetWithoutContextProperty)); #endif } #if !XFORMS - /// + /// /// Uses the action pipeline to invoke the method. - /// - /// The object instance to invoke the method on. - /// The name of the method to invoke. - /// The view. - /// The source of the invocation. - /// The event args. - /// The method parameters. + /// + /// The object instance to invoke the method on. + /// The name of the method to invoke. + /// The view. + /// The source of the invocation. + /// The event args. + /// The method parameters. public static void Invoke(object target, string methodName, DependencyObject view = null, FrameworkElement source = null, object eventArgs = null, object[] parameters = null) { - - var message = new ActionMessage {MethodName = methodName}; - + var message = new ActionMessage { MethodName = methodName }; var context = new ActionExecutionContext { Target = target, #if WINDOWS_UWP @@ -137,12 +127,10 @@ public static void Invoke(object target, string methodName, DependencyObject vie Message = message, View = view, Source = source, - EventArgs = eventArgs + EventArgs = eventArgs, }; - if (parameters != null) { - parameters.Apply(x => context.Message.Parameters.Add(x as Parameter ?? new Parameter { Value = x })); - } + parameters?.Apply(x => context.Message.Parameters.Add(x as Parameter ?? new Parameter { Value = x })); ActionMessage.InvokeAction(context); @@ -151,38 +139,33 @@ public static void Invoke(object target, string methodName, DependencyObject vie } #endif - static void OnTargetWithoutContextChanged(DependencyObject d, DependencyPropertyChangedEventArgs e) { - SetTargetCore(e, d, false); - } + private static void OnTargetWithoutContextChanged(DependencyObject d, DependencyPropertyChangedEventArgs e) => SetTargetCore(e, d, false); - static void OnTargetChanged(DependencyObject d, DependencyPropertyChangedEventArgs e) { - SetTargetCore(e, d, true); - } + private static void OnTargetChanged(DependencyObject d, DependencyPropertyChangedEventArgs e) => SetTargetCore(e, d, true); - static void SetTargetCore(DependencyPropertyChangedEventArgs e, DependencyObject d, bool setContext) { + private static void SetTargetCore(DependencyPropertyChangedEventArgs e, DependencyObject d, bool setContext) { if (e.NewValue == e.OldValue || (Execute.InDesignMode && e.NewValue is string)) { return; } - var target = e.NewValue; + object target = e.NewValue; #if XFORMS || MAUI Log.Info("Attaching message handler {0} to {1}.", target, d); Message.SetHandler(d, target); - if (setContext && d is FrameworkElement) { + if (setContext && d is FrameworkElement element) { Log.Info("Setting DC of {0} to {1}.", d, target); - ((FrameworkElement)d).BindingContext = target; + element.BindingContext = target; } #else - if (setContext && d is FrameworkElement) { + if (setContext && d is FrameworkElement element) { Log.Info("Setting DC of {0} to {1}.", d, target); - ((FrameworkElement)d).DataContext = target; + element.DataContext = target; } - Log.Info("Attaching message handler {0} to {1}.", target, d); - Message.SetHandler(d, target); + Log.Info("Attaching message handler {0} to {1}.", target, d); + Message.SetHandler(d, target); #endif - } } diff --git a/src/Caliburn.Micro.Platform/ActionExecutionContext.cs b/src/Caliburn.Micro.Platform/ActionExecutionContext.cs index 895791432..a47e7d7d9 100644 --- a/src/Caliburn.Micro.Platform/ActionExecutionContext.cs +++ b/src/Caliburn.Micro.Platform/ActionExecutionContext.cs @@ -1,4 +1,23 @@ -#if XFORMS +using System; +using System.Collections.Generic; +using System.Reflection; + +#if WINDOWS_UWP +using Windows.UI.Xaml; +#elif XFORMS +using Xamarin.Forms; + +using DependencyObject = Xamarin.Forms.BindableObject; +using DependencyProperty = Xamarin.Forms.BindableProperty; +using FrameworkElement = Xamarin.Forms.VisualElement; +#elif MAUI +using DependencyObject = Microsoft.Maui.Controls.BindableObject; +using FrameworkElement = Microsoft.Maui.Controls.VisualElement; +#else +using System.Windows; +#endif + +#if XFORMS namespace Caliburn.Micro.Xamarin.Forms #elif MAUI namespace Caliburn.Micro.Maui @@ -6,25 +25,6 @@ namespace Caliburn.Micro.Maui namespace Caliburn.Micro #endif { - using System; - using System.Collections.Generic; - using System.Reflection; -#if WINDOWS_UWP - using Windows.UI.Xaml; -#elif XFORMS - using global::Xamarin.Forms; - using DependencyObject = global::Xamarin.Forms.BindableObject; - using DependencyProperty = global::Xamarin.Forms.BindableProperty; - using FrameworkElement = global::Xamarin.Forms.VisualElement; -#elif MAUI - using global::Microsoft.Maui; - using DependencyObject = global::Microsoft.Maui.Controls.BindableObject; - using DependencyProperty = global::Microsoft.Maui.Controls.BindableProperty; - using FrameworkElement = global::Microsoft.Maui.Controls.VisualElement; -#else - using System.Windows; -#endif - /// /// The context used during the execution of an Action or its guard. /// @@ -34,53 +34,59 @@ public class ActionExecutionContext : IDisposable { private WeakReference target; private WeakReference view; private Dictionary values; + private bool _isDisposed; /// - /// Determines whether the action can execute. + /// Called when the execution context is disposed + /// + public event EventHandler Disposing = (sender, e) => { }; + + /// + /// Gets or sets func to determines whether the action can execute. /// /// Returns true if the action can execute, false otherwise. - public Func CanExecute; + public Func CanExecute { get; set; } /// - /// Any event arguments associated with the action's invocation. + /// Gets or sets any event arguments associated with the action's invocation. /// - public object EventArgs; + public object EventArgs { get; set; } /// - /// The actual method info to be invoked. + /// Gets or sets the actual method info to be invoked. /// - public MethodInfo Method; + public MethodInfo Method { get; set; } /// - /// The message being executed. + /// Gets or sets the message being executed. /// public ActionMessage Message { - get { return message == null ? null : message.Target as ActionMessage; } - set { message = new WeakReference(value); } + get => message == null ? null : message.Target as ActionMessage; + set => message = new WeakReference(value); } /// - /// The source from which the message originates. + /// Gets or sets the source from which the message originates. /// public FrameworkElement Source { - get { return source == null ? null : source.Target as FrameworkElement; } - set { source = new WeakReference(value); } + get => source == null ? null : source.Target as FrameworkElement; + set => source = new WeakReference(value); } /// - /// The instance on which the action is invoked. + /// Gets or sets the instance on which the action is invoked. /// public object Target { - get { return target == null ? null : target.Target; } - set { target = new WeakReference(value); } + get => target?.Target; + set => target = new WeakReference(value); } /// - /// The view associated with the target. + /// Gets or sets the view associated with the target. /// public DependencyObject View { - get { return view == null ? null : view.Target as DependencyObject; } - set { view = new WeakReference(value); } + get => view == null ? null : view.Target as DependencyObject; + set => view = new WeakReference(value); } /// @@ -90,17 +96,19 @@ public DependencyObject View { /// Custom data associated with the context. public object this[string key] { get { - if (values == null) + if (values == null) { values = new Dictionary(); + } - object result; - values.TryGetValue(key, out result); + values.TryGetValue(key, out object result); return result; } + set { - if (values == null) + if (values == null) { values = new Dictionary(); + } values[key] = value; } @@ -110,12 +118,30 @@ public object this[string key] { /// Performs application-defined tasks associated with freeing, releasing, or resetting unmanaged resources. /// public void Dispose() { - Disposing(this, System.EventArgs.Empty); + Dispose(disposing: true); + GC.SuppressFinalize(this); } /// - /// Called when the execution context is disposed + /// Perform Dispose. /// - public event EventHandler Disposing = delegate { }; + /// Dispose managed resources. + protected virtual void Dispose(bool disposing) { + if (_isDisposed) { + return; + } + + if (disposing) { + Disposing(this, System.EventArgs.Empty); + } + + message = null; + source = null; + target = null; + view = null; + values = null; + + _isDisposed = true; + } } } diff --git a/src/Caliburn.Micro.Platform/ActionMessage.cs b/src/Caliburn.Micro.Platform/ActionMessage.cs index faaa9cc87..105e3824b 100644 --- a/src/Caliburn.Micro.Platform/ActionMessage.cs +++ b/src/Caliburn.Micro.Platform/ActionMessage.cs @@ -1,29 +1,33 @@ -namespace Caliburn.Micro -{ - using System; - using System.Collections.Generic; - using System.ComponentModel; - using System.Linq; - using System.Reflection; +using System; +using System.Collections.Generic; +using System.ComponentModel; +using System.Globalization; +using System.Linq; +using System.Reflection; + #if WINDOWS_UWP - using Windows.UI.Xaml; - using Windows.UI.Xaml.Data; - using Windows.UI.Xaml.Markup; - using Windows.UI.Xaml.Media; - using Windows.UI.Xaml.Controls; - using Microsoft.Xaml.Interactivity; - using TriggerBase = Microsoft.Xaml.Interactivity.IBehavior; - using EventTrigger = Microsoft.Xaml.Interactions.Core.EventTriggerBehavior; +using Microsoft.Xaml.Interactivity; +using Windows.UI.Xaml; +using Windows.UI.Xaml.Controls; +using Windows.UI.Xaml.Data; +using Windows.UI.Xaml.Markup; +using Windows.UI.Xaml.Media; + +using EventTrigger = Microsoft.Xaml.Interactions.Core.EventTriggerBehavior; +using TriggerBase = Microsoft.Xaml.Interactivity.IBehavior; + #else - using System.Windows; - using System.Windows.Controls.Primitives; - using System.Windows.Data; - using System.Windows.Markup; - using Microsoft.Xaml.Behaviors; - using EventTrigger = Microsoft.Xaml.Behaviors.EventTrigger; -#endif +using System.Windows; +using System.Windows.Controls.Primitives; +using System.Windows.Data; +using System.Windows.Markup; + +using Microsoft.Xaml.Behaviors; +using EventTrigger = Microsoft.Xaml.Behaviors.EventTrigger; +#endif +namespace Caliburn.Micro { /// /// Used to send a message from the UI to a presentation model class, indicating that a particular Action should be invoked. /// @@ -36,516 +40,552 @@ [TypeConstraint(typeof(FrameworkElement))] #endif public class ActionMessage : TriggerAction, IHaveParameters { - static readonly ILog Log = LogManager.GetLog(typeof(ActionMessage)); - ActionExecutionContext context; - - internal static readonly DependencyProperty HandlerProperty = DependencyProperty.RegisterAttached( - "Handler", - typeof(object), - typeof(ActionMessage), - new PropertyMetadata(null, HandlerPropertyChanged) - ); - - /// - /// Causes the action invocation to "double check" if the action should be invoked by executing the guard immediately before hand. - /// - /// This is disabled by default. If multiple actions are attached to the same element, you may want to enable this so that each individaul action checks its guard regardless of how the UI state appears. - public static bool EnforceGuardsDuringInvocation = false; - - /// - /// Causes the action to throw if it cannot locate the target or the method at invocation time. - /// - /// True by default. - public static bool ThrowsExceptions = true; - /// /// Represents the method name of an action message. /// - public static readonly DependencyProperty MethodNameProperty = - DependencyProperty.Register( + public static readonly DependencyProperty MethodNameProperty + = DependencyProperty.Register( "MethodName", typeof(string), typeof(ActionMessage), - null - ); + null); /// /// Represents the parameters of an action message. /// - public static readonly DependencyProperty ParametersProperty = - DependencyProperty.Register( - "Parameters", - typeof(AttachedCollection), - typeof(ActionMessage), - null - ); + public static readonly DependencyProperty ParametersProperty + = DependencyProperty.Register( + "Parameters", + typeof(AttachedCollection), + typeof(ActionMessage), + null); + + internal static readonly DependencyProperty HandlerProperty + = DependencyProperty.RegisterAttached( + "Handler", + typeof(object), + typeof(ActionMessage), + new PropertyMetadata(null, HandlerPropertyChanged)); + + private static readonly ILog Log = LogManager.GetLog(typeof(ActionMessage)); + + private ActionExecutionContext context; /// - /// Creates an instance of . + /// Initializes a new instance of the class. /// - public ActionMessage() { - SetValue(ParametersProperty, new AttachedCollection()); - } + public ActionMessage() + => SetValue(ParametersProperty, new AttachedCollection()); /// - /// Gets or sets the name of the method to be invoked on the presentation model class. + /// Occurs before the message detaches from the associated object. /// - /// The name of the method. -#if !WINDOWS_UWP - [Category("Common Properties")] -#endif - public string MethodName { - get { return (string)GetValue(MethodNameProperty); } - set { SetValue(MethodNameProperty, value); } - } + public event EventHandler Detaching + = (sender, e) => { }; /// - /// Gets the parameters to pass as part of the method invocation. + /// Gets or sets a value indicating whether the action invocation to "double check" if the action should be invoked by executing the guard immediately before hand. /// - /// The parameters. -#if !WINDOWS_UWP - [Category("Common Properties")] -#endif - public AttachedCollection Parameters { - get { return (AttachedCollection)GetValue(ParametersProperty); } - } + /// This is disabled by default. If multiple actions are attached to the same element, you may want to enable this so that each individaul action checks its guard regardless of how the UI state appears. + public static bool EnforceGuardsDuringInvocation { get; set; } /// - /// Occurs before the message detaches from the associated object. + /// Gets or sets a value indicating whether the action throw if it cannot locate the target or the method at invocation time. /// - public event EventHandler Detaching = delegate { }; + /// True by default. + public static bool ThrowsExceptions { get; set; } + = true; /// - /// Called after the action is attached to an AssociatedObject. + /// Gets or sets func to return the list of possible names of guard methods / properties for the given method. /// -#if WINDOWS_UWP - protected override void OnAttached() { - if (!View.InDesignMode) { - Parameters.Attach(AssociatedObject); - Parameters.OfType().Apply(x => x.MakeAwareOf(this)); - - - if (View.ExecuteOnLoad(AssociatedObject, ElementLoaded)) { - // Not yet sure if this will be needed - //var trigger = Interaction.GetTriggers(AssociatedObject) - // .FirstOrDefault(t => t.Actions.Contains(this)) as EventTrigger; - //if (trigger != null && trigger.EventName == "Loaded") - // Invoke(new RoutedEventArgs()); - } + public static Func> BuildPossibleGuardNames { get; set; } + = method + => { + var guardNames = new List(); - View.ExecuteOnUnload(AssociatedObject, ElementUnloaded); - } + const string GuardPrefix = "Can"; - base.OnAttached(); - } + string methodName = method.Name; - void ElementUnloaded(object sender, RoutedEventArgs e) - { - OnDetaching(); - } -#else - protected override void OnAttached() { - if (!View.InDesignMode) { - Parameters.Attach(AssociatedObject); - Parameters.Apply(x => x.MakeAwareOf(this)); - - if (View.ExecuteOnLoad(AssociatedObject, ElementLoaded)) { - var trigger = Interaction.GetTriggers(AssociatedObject) - .FirstOrDefault(t => t.Actions.Contains(this)) as EventTrigger; - if (trigger != null && trigger.EventName == "Loaded") - Invoke(new RoutedEventArgs()); + guardNames.Add(GuardPrefix + methodName); + + const string AsyncMethodSuffix = "Async"; + + if (!methodName.EndsWith(AsyncMethodSuffix, StringComparison.OrdinalIgnoreCase)) { + return guardNames; } - } - base.OnAttached(); - } -#endif + guardNames.Add(GuardPrefix + methodName.Substring(0, methodName.Length - AsyncMethodSuffix.Length)); - static void HandlerPropertyChanged(DependencyObject d, DependencyPropertyChangedEventArgs e) { - ((ActionMessage)d).UpdateContext(); - } + return guardNames; + }; /// - /// Called when the action is being detached from its AssociatedObject, but before it has actually occurred. + /// Gets or sets action to invoke the action using the specified . /// - protected override void OnDetaching() { - if (!View.InDesignMode) { - Detaching(this, EventArgs.Empty); - AssociatedObject.Loaded -= ElementLoaded; - Parameters.Detach(); - } + public static Action InvokeAction { get; set; } + = context + => { + object[] values = MessageBinder.DetermineParameters(context, context.Method.GetParameters()); + object returnValue = context.Method.Invoke(context.Target, values); + + if (returnValue is System.Threading.Tasks.Task task) { + returnValue = task.AsResult(); + } - base.OnDetaching(); - } + if (returnValue is IResult result) { + returnValue = new[] { result }; + } - void ElementLoaded(object sender, RoutedEventArgs e) { - UpdateContext(); + if (returnValue is IEnumerable enumerable) { + returnValue = enumerable.GetEnumerator(); + } - DependencyObject currentElement; - if (context.View == null) { - currentElement = AssociatedObject; - while (currentElement != null) { - if (Action.HasTargetSet(currentElement)) - break; + if (returnValue is IEnumerator enumerator) { + Coroutine.BeginExecute( + enumerator, + new CoroutineExecutionContext { + Source = context.Source, + View = context.View, + Target = context.Target, + }); + } + }; - currentElement = BindingScope.GetVisualParent(currentElement); - } - } - else currentElement = context.View; + /// + /// Gets or sets func to apply an availability effect, such as IsEnabled, to an element. + /// + /// Returns a value indicating whether or not the action is available. + public static Func ApplyAvailabilityEffect { get; set; } + = context + => { +#if WINDOWS_UWP + if (context.Source is not Control source) { + return true; + } +#else + FrameworkElement source = context.Source; + if (source == null) { + return true; + } +#endif -#if NET || CAL_NETCORE - var binding = new Binding { - Path = new PropertyPath(Message.HandlerProperty), - Source = currentElement - }; -#elif WINDOWS_UWP - var binding = new Binding { - Source = currentElement - }; +#if WINDOWS_UWP + bool hasBinding = ConventionManager.HasBinding(source, Control.IsEnabledProperty); #else - const string bindingText = ""; + bool hasBinding = ConventionManager.HasBinding(source, UIElement.IsEnabledProperty); +#endif + if (!hasBinding && context.CanExecute != null) { + source.IsEnabled = context.CanExecute(); + } - var binding = (Binding)XamlReader.Load(bindingText); - binding.Source = currentElement; + return source.IsEnabled; + }; + + /// + /// Gets or sets func to find the method on the target matching the specified message. + /// + /// The matching method, if available. + public static Func GetTargetMethod { get; set; } + = (message, target) + => +#if WINDOWS_UWP + (from method in target.GetType().GetRuntimeMethods() + where method.Name == message.MethodName + let methodParameters = method.GetParameters() + where message.Parameters.Count == methodParameters.Length + select method).FirstOrDefault(); +#else + (from method in target.GetType().GetMethods() + where method.Name == message.MethodName + let methodParameters = method.GetParameters() + where message.Parameters.Count == methodParameters.Length + select method).FirstOrDefault(); #endif - BindingOperations.SetBinding(this, HandlerProperty, binding); - } - void UpdateContext() { - if (context != null) - context.Dispose(); + /// + /// Gets or sets action to Set the target, method and view on the context. Uses a bubbling strategy by default. + /// + public static Action SetMethodBinding { get; set; } + = context + => { + FrameworkElement source = context.Source; + + DependencyObject currentElement = source; + while (currentElement != null) { + if (Action.HasTargetSet(currentElement)) { + object target = Message.GetHandler(currentElement); + if (target != null) { + MethodInfo method = GetTargetMethod(context.Message, target); + if (method != null) { + context.Method = method; + context.Target = target; + context.View = currentElement; + return; + } + } else { + context.View = currentElement; + return; + } + } - context = new ActionExecutionContext { - Message = this, - Source = AssociatedObject - }; + currentElement = BindingScope.GetVisualParent(currentElement); + } - PrepareContext(context); - UpdateAvailabilityCore(); - } + if (source != null && source.DataContext != null) { + object target = source.DataContext; + MethodInfo method = GetTargetMethod(context.Message, target); + + if (method != null) { + context.Target = target; + context.Method = method; + context.View = source; + } + } + }; /// - /// Invokes the action. + /// Gets or sets action to prepare the action execution context for use. /// - /// The parameter to the action. If the action does not require a parameter, the parameter may be set to a null reference. - protected override void Invoke(object eventArgs) { - Log.Info("Invoking {0}.", this); + public static Action PrepareContext { get; set; } + = context + => { + SetMethodBinding(context); + if (context.Target == null || context.Method == null) { + return; + } - if (context == null) { - UpdateContext(); - } + var possibleGuardNames = BuildPossibleGuardNames(context.Method).ToList(); - if (context.Target == null || context.View == null) { - PrepareContext(context); - if (context.Target == null) { - var ex = new Exception(string.Format("No target found for method {0}.", context.Message.MethodName)); - Log.Error(ex); + MethodInfo guard = TryFindGuardMethod(context, possibleGuardNames); + + if (guard != null) { + context.CanExecute = () => (bool)guard.Invoke(context.Target, MessageBinder.DetermineParameters(context, guard.GetParameters())); - if (!ThrowsExceptions) return; - throw ex; - } + } - if (!UpdateAvailabilityCore()) { - return; - } - } + if (context.Target is not INotifyPropertyChanged inpc) { + return; + } - if (context.Method == null) { - var ex = new Exception(string.Format("Method {0} not found on target of type {1}.", context.Message.MethodName, context.Target.GetType())); - Log.Error(ex); + Type targetType = context.Target.GetType(); + string matchingGuardName = null; + foreach (string possibleGuardName in possibleGuardNames) { + matchingGuardName = possibleGuardName; + guard = GetMethodInfo(targetType, "get_" + matchingGuardName); + if (guard != null) { + break; + } + } - if (!ThrowsExceptions) - return; - throw ex; - } + if (guard == null) { + return; + } - context.EventArgs = eventArgs; + void OnPropertyChanged(object s, PropertyChangedEventArgs e) { + if (string.IsNullOrEmpty(e.PropertyName) || e.PropertyName == matchingGuardName) { + Caliburn.Micro.Execute.OnUIThread(() => { + ActionMessage message = context.Message; + if (message == null) { + inpc.PropertyChanged -= OnPropertyChanged; + return; + } + + message.UpdateAvailability(); + }); + } + } - if (EnforceGuardsDuringInvocation && context.CanExecute != null && !context.CanExecute()) { - return; - } + inpc.PropertyChanged += OnPropertyChanged; + context.Disposing += (sender, e) => inpc.PropertyChanged -= OnPropertyChanged; + context.Message.Detaching += (sender, e) => inpc.PropertyChanged -= OnPropertyChanged; + context.CanExecute = () => (bool)guard.Invoke(context.Target, MessageBinder.DetermineParameters(context, guard.GetParameters())); + }; - InvokeAction(context); - context.EventArgs = null; + /// + /// Gets or sets the name of the method to be invoked on the presentation model class. + /// + /// The name of the method. +#if !WINDOWS_UWP + [Category("Common Properties")] +#endif + public string MethodName { + get => (string)GetValue(MethodNameProperty); + set => SetValue(MethodNameProperty, value); } + /// + /// Gets the parameters to pass as part of the method invocation. + /// + /// The parameters. +#if !WINDOWS_UWP + [Category("Common Properties")] +#endif + public AttachedCollection Parameters + => (AttachedCollection)GetValue(ParametersProperty); + /// /// Forces an update of the UI's Enabled/Disabled state based on the the preconditions associated with the method. /// public virtual void UpdateAvailability() { - if (context == null) + if (context == null) { return; + } - if (context.Target == null || context.View == null) + if (context.Target == null || context.View == null) { PrepareContext(context); + } UpdateAvailabilityCore(); } - bool UpdateAvailabilityCore() { - Log.Info("{0} availability update.", this); - return ApplyAvailabilityEffect(context); - } - /// - /// Returns a that represents the current . + /// Returns a that represents the current . /// /// - /// A that represents the current . + /// A that represents the current . /// - public override string ToString() { - return "Action: " + MethodName; - } + public override string ToString() + => "Action: " + MethodName; /// - /// Invokes the action using the specified + /// Called when the action is being detached from its AssociatedObject, but before it has actually occurred. /// - public static Action InvokeAction = context => { - var values = MessageBinder.DetermineParameters(context, context.Method.GetParameters()); - var returnValue = context.Method.Invoke(context.Target, values); - - var task = returnValue as System.Threading.Tasks.Task; - if (task != null) { - returnValue = task.AsResult(); - } - - var result = returnValue as IResult; - if (result != null) { - returnValue = new[] { result }; - } - - var enumerable = returnValue as IEnumerable; - if (enumerable != null) { - returnValue = enumerable.GetEnumerator(); - } - - var enumerator = returnValue as IEnumerator; - if (enumerator != null) { - Coroutine.BeginExecute(enumerator, - new CoroutineExecutionContext - { - Source = context.Source, - View = context.View, - Target = context.Target - }); + protected override void OnDetaching() { + if (!View.InDesignMode) { + Detaching(this, EventArgs.Empty); + AssociatedObject.Loaded -= ElementLoaded; + Parameters.Detach(); } - }; - /// - /// Applies an availability effect, such as IsEnabled, to an element. - /// - /// Returns a value indicating whether or not the action is available. - public static Func ApplyAvailabilityEffect = context => { + base.OnDetaching(); + } -#if WINDOWS_UWP - var source = context.Source as Control; + /* + * Change in the public API of Microsoft.Xaml.Behaviors causing CA1725 issue. + * The parameter name changed from 'parameter' to 'parmeter' (without second 'a') + * This if statement to reslove + * CA1725: Parameter names should match base declaration. + */ +#if UAP10_0_19041 + /// Invokes the action. + /// The parameter to the action. If the action does not require a parameter, the parameter may be set to a null reference. + protected override void Invoke(object parmeter) { #else - var source = context.Source; + /// Invokes the action. + /// The parameter to the action. If the action does not require a parameter, the parameter may be set to a null reference. + protected override void Invoke(object parameter) { #endif - if (source == null) { - return true; - } + Log.Info("Invoking {0}.", this); -#if WINDOWS_UWP - var hasBinding = ConventionManager.HasBinding(source, Control.IsEnabledProperty); -#else - var hasBinding = ConventionManager.HasBinding(source, UIElement.IsEnabledProperty); -#endif - if (!hasBinding && context.CanExecute != null) { - source.IsEnabled = context.CanExecute(); + if (context == null) { + UpdateContext(); } - return source.IsEnabled; - }; - - /// - /// Finds the method on the target matching the specified message. - /// - /// The matching method, if available. - public static Func GetTargetMethod = (message, target) => { -#if WINDOWS_UWP - return (from method in target.GetType().GetRuntimeMethods() - where method.Name == message.MethodName - let methodParameters = method.GetParameters() - where message.Parameters.Count == methodParameters.Length - select method).FirstOrDefault(); -#else - return (from method in target.GetType().GetMethods() - where method.Name == message.MethodName - let methodParameters = method.GetParameters() - where message.Parameters.Count == methodParameters.Length - select method).FirstOrDefault(); -#endif - }; + if (context.Target == null || context.View == null) { + PrepareContext(context); + if (context.Target == null) { + var ex = new Exception(string.Format(CultureInfo.InvariantCulture, "No target found for method {0}.", context.Message.MethodName)); + Log.Error(ex); - /// - /// Sets the target, method and view on the context. Uses a bubbling strategy by default. - /// - public static Action SetMethodBinding = context => { - var source = context.Source; - - DependencyObject currentElement = source; - while (currentElement != null) { - if (Action.HasTargetSet(currentElement)) { - var target = Message.GetHandler(currentElement); - if (target != null) { - var method = GetTargetMethod(context.Message, target); - if (method != null) { - context.Method = method; - context.Target = target; - context.View = currentElement; - return; - } - } - else { - context.View = currentElement; + if (!ThrowsExceptions) { return; } + + throw ex; } - currentElement = BindingScope.GetVisualParent(currentElement); + if (!UpdateAvailabilityCore()) { + return; + } } - if (source != null && source.DataContext != null) { - var target = source.DataContext; - var method = GetTargetMethod(context.Message, target); + if (context.Method == null) { + var ex = new Exception(string.Format(CultureInfo.InvariantCulture, "Method {0} not found on target of type {1}.", context.Message.MethodName, context.Target.GetType())); + Log.Error(ex); - if (method != null) { - context.Target = target; - context.Method = method; - context.View = source; + if (!ThrowsExceptions) { + return; } + + throw ex; } - }; - /// - /// Prepares the action execution context for use. - /// - public static Action PrepareContext = context => { - SetMethodBinding(context); - if (context.Target == null || context.Method == null) - { +#if UAP10_0_19041 + context.EventArgs = parmeter; +#else + context.EventArgs = parameter; +#endif + + if (EnforceGuardsDuringInvocation && context.CanExecute != null && !context.CanExecute()) { return; } - var possibleGuardNames = BuildPossibleGuardNames(context.Method).ToList(); - var guard = TryFindGuardMethod(context, possibleGuardNames); + InvokeAction(context); + context.EventArgs = null; + } - if (guard == null) - { - var inpc = context.Target as INotifyPropertyChanged; - if (inpc == null) - return; + /// + /// Called after the action is attached to an AssociatedObject. + /// +#if WINDOWS_UWP + protected override void OnAttached() { + if (View.InDesignMode) { + base.OnAttached(); - var targetType = context.Target.GetType(); - string matchingGuardName = null; - foreach (string possibleGuardName in possibleGuardNames) - { - matchingGuardName = possibleGuardName; - guard = GetMethodInfo(targetType, "get_" + matchingGuardName); - if (guard != null) break; - } + return; + } - if (guard == null) - return; + Parameters.Attach(AssociatedObject); + Parameters.OfType().Apply(x => x.MakeAwareOf(this)); + + _ = View.ExecuteOnLoad(AssociatedObject, ElementLoaded); + /* + if (View.ExecuteOnLoad(AssociatedObject, ElementLoaded)) { + // Not yet sure if this will be needed + // var trigger = Interaction.GetTriggers(AssociatedObject) + // .FirstOrDefault(t => t.Actions.Contains(this)) as EventTrigger; + // if (trigger != null && trigger.EventName == "Loaded") + // Invoke(new RoutedEventArgs()); + } + */ - PropertyChangedEventHandler handler = null; - handler = (s, e) => { - if (string.IsNullOrEmpty(e.PropertyName) || e.PropertyName == matchingGuardName) - { - Caliburn.Micro.Execute.OnUIThread(() => { - var message = context.Message; - if (message == null) - { - inpc.PropertyChanged -= handler; - return; - } - message.UpdateAvailability(); - }); - } - }; + View.ExecuteOnUnload(AssociatedObject, (s, e) => OnDetaching()); + base.OnAttached(); + } +#else + protected override void OnAttached() { + if (View.InDesignMode) { + base.OnAttached(); + + return; + } - inpc.PropertyChanged += handler; - context.Disposing += delegate { inpc.PropertyChanged -= handler; }; - context.Message.Detaching += delegate { inpc.PropertyChanged -= handler; }; + Parameters.Attach(AssociatedObject); + Parameters.Apply(x => x.MakeAwareOf(this)); + if (View.ExecuteOnLoad(AssociatedObject, ElementLoaded) && + Interaction.GetTriggers(AssociatedObject) + .FirstOrDefault(t => t.Actions.Contains(this)) is EventTrigger trigger && + trigger.EventName == "Loaded") { + Invoke(new RoutedEventArgs()); } - context.CanExecute = () => (bool)guard.Invoke( - context.Target, - MessageBinder.DetermineParameters(context, guard.GetParameters())); - }; + base.OnAttached(); + } +#endif /// - /// Try to find a candidate for guard function, having: + /// Try to find a candidate for guard function, having: /// - a name matching any of /// - no generic parameters /// - a bool return type - /// - no parameters or a set of parameters corresponding to the action method + /// - no parameters or a set of parameters corresponding to the action method. /// - /// The execution context + /// The execution context. /// Method names to look for. - ///A MethodInfo, if found; null otherwise - static MethodInfo TryFindGuardMethod(ActionExecutionContext context, IEnumerable possibleGuardNames) { - var targetType = context.Target.GetType(); + /// A MethodInfo, if found; null otherwise. + private static MethodInfo TryFindGuardMethod(ActionExecutionContext context, IEnumerable possibleGuardNames) { + Type targetType = context.Target.GetType(); MethodInfo guard = null; - foreach (string possibleGuardName in possibleGuardNames) - { + foreach (string possibleGuardName in possibleGuardNames) { guard = GetMethodInfo(targetType, possibleGuardName); - if (guard != null) break; + if (guard != null) { + break; + } + } + + if (guard == null) { + return null; } - if (guard == null) return null; - if (guard.ContainsGenericParameters) return null; - if (!typeof(bool).Equals(guard.ReturnType)) return null; + if (guard.ContainsGenericParameters) { + return null; + } - var guardPars = guard.GetParameters(); - var actionPars = context.Method.GetParameters(); - if (guardPars.Length == 0) return guard; - if (guardPars.Length != actionPars.Length) return null; + if (!typeof(bool).Equals(guard.ReturnType)) { + return null; + } - var comparisons = guardPars.Zip( - context.Method.GetParameters(), - (x, y) => x.ParameterType == y.ParameterType - ); + ParameterInfo[] guardPars = guard.GetParameters(); + ParameterInfo[] actionPars = context.Method.GetParameters(); + if (guardPars.Length == 0) { + return guard; + } - if (comparisons.Any(x => !x)) - { + if (guardPars.Length != actionPars.Length) { return null; } - return guard; + IEnumerable comparisons = guardPars.Zip( + context.Method.GetParameters(), + (x, y) => x.ParameterType == y.ParameterType); + + return comparisons.Any(x => !x) ? null : guard; } - /// - /// Returns the list of possible names of guard methods / properties for the given method. - /// - public static Func> BuildPossibleGuardNames = method => { + private static void HandlerPropertyChanged(DependencyObject d, DependencyPropertyChangedEventArgs e) + => ((ActionMessage)d).UpdateContext(); - var guardNames = new List(); + private static MethodInfo GetMethodInfo(Type t, string methodName) => +#if WINDOWS_UWP + t.GetRuntimeMethods().SingleOrDefault(m => m.Name == methodName); +#else + t.GetMethod(methodName); +#endif - const string GuardPrefix = "Can"; + private bool UpdateAvailabilityCore() { + Log.Info("{0} availability update.", this); - var methodName = method.Name; + return ApplyAvailabilityEffect(context); + } - guardNames.Add(GuardPrefix + methodName); + private void UpdateContext() { + context?.Dispose(); - const string AsyncMethodSuffix = "Async"; + context = new ActionExecutionContext { + Message = this, + Source = AssociatedObject, + }; - if (methodName.EndsWith(AsyncMethodSuffix, StringComparison.OrdinalIgnoreCase)) { - guardNames.Add(GuardPrefix + methodName.Substring(0, methodName.Length - AsyncMethodSuffix.Length)); - } + PrepareContext(context); + UpdateAvailabilityCore(); + } - return guardNames; - }; + private void ElementLoaded(object sender, RoutedEventArgs e) { + UpdateContext(); - static MethodInfo GetMethodInfo(Type t, string methodName) - { -#if WINDOWS_UWP - return t.GetRuntimeMethods().SingleOrDefault(m => m.Name == methodName); + DependencyObject currentElement; + if (context.View == null) { + currentElement = AssociatedObject; + while (currentElement != null) { + if (Action.HasTargetSet(currentElement)) { + break; + } + + currentElement = BindingScope.GetVisualParent(currentElement); + } + } else { + currentElement = context.View; + } + +#if NET || CAL_NETCORE + var binding = new Binding { + Source = currentElement, + Path = new PropertyPath(Message.HandlerProperty), + }; +#elif WINDOWS_UWP + var binding = new Binding { + Source = currentElement, + }; #else - return t.GetMethod(methodName); + const string bindingText = ""; + + var binding = (Binding)XamlReader.Load(bindingText); + binding.Source = currentElement; #endif + BindingOperations.SetBinding(this, HandlerProperty, binding); } } } diff --git a/src/Caliburn.Micro.Platform/Bind.cs b/src/Caliburn.Micro.Platform/Bind.cs index d5dab8175..1c1a973de 100644 --- a/src/Caliburn.Micro.Platform/Bind.cs +++ b/src/Caliburn.Micro.Platform/Bind.cs @@ -1,4 +1,28 @@ -#if XFORMS +#if WINDOWS_UWP +using System.Globalization; + +using Windows.UI.Xaml; +using Windows.UI.Xaml.Data; +#elif XFORMS +using System; + +using Xamarin.Forms; + +using DependencyObject =Xamarin.Forms.BindableObject; +using DependencyProperty = Xamarin.Forms.BindableProperty; +using FrameworkElement = Xamarin.Forms.VisualElement; + +#elif MAUI +using DependencyObject = Microsoft.Maui.Controls.BindableObject; +using DependencyProperty = Microsoft.Maui.Controls.BindableProperty; +using FrameworkElement = Microsoft.Maui.Controls.VisualElement; +#else +using System.Globalization; +using System.Windows; +using System.Windows.Data; +#endif + +#if XFORMS namespace Caliburn.Micro.Xamarin.Forms #elif MAUI namespace Caliburn.Micro.Maui @@ -6,27 +30,6 @@ namespace Caliburn.Micro.Maui namespace Caliburn.Micro #endif { - using System; -#if WINDOWS_UWP - using Windows.UI.Xaml; - using Windows.UI.Xaml.Data; -#elif XFORMS - using global::Xamarin.Forms; - using UIElement = global::Xamarin.Forms.Element; - using FrameworkElement = global::Xamarin.Forms.VisualElement; - using DependencyProperty = global::Xamarin.Forms.BindableProperty; - using DependencyObject =global::Xamarin.Forms.BindableObject; -#elif MAUI - using global::Microsoft.Maui; - using UIElement = global::Microsoft.Maui.Controls.Element; - using FrameworkElement = global::Microsoft.Maui.Controls.VisualElement; - using DependencyProperty = global::Microsoft.Maui.Controls.BindableProperty; - using DependencyObject =global::Microsoft.Maui.Controls.BindableObject; -#else - using System.Windows; - using System.Windows.Data; -#endif - /// /// Hosts dependency properties for binding. /// @@ -34,88 +37,120 @@ public static class Bind { /// /// Allows binding on an existing view. Use this on root UserControls, Pages and Windows; not in a DataTemplate. /// - public static DependencyProperty ModelProperty = - DependencyPropertyHelper.RegisterAttached( + public static readonly DependencyProperty ModelProperty + = DependencyPropertyHelper.RegisterAttached( "Model", typeof(object), typeof(Bind), - null, + null, ModelChanged); /// /// Allows binding on an existing view without setting the data context. Use this from within a DataTemplate. /// - public static DependencyProperty ModelWithoutContextProperty = - DependencyPropertyHelper.RegisterAttached( + public static readonly DependencyProperty ModelWithoutContextProperty + = DependencyPropertyHelper.RegisterAttached( "ModelWithoutContext", typeof(object), typeof(Bind), - null, + null, ModelWithoutContextChanged); - internal static DependencyProperty NoContextProperty = - DependencyPropertyHelper.RegisterAttached( + /// + /// Allows application of conventions at design-time. + /// + public static readonly DependencyProperty AtDesignTimeProperty + = DependencyPropertyHelper.RegisterAttached( + "AtDesignTime", + typeof(bool), + typeof(Bind), + false, + AtDesignTimeChanged); + + internal static readonly DependencyProperty NoContextProperty + = DependencyPropertyHelper.RegisterAttached( "NoContext", typeof(bool), typeof(Bind), false); + private static readonly DependencyProperty DataContextProperty + = DependencyPropertyHelper.RegisterAttached( + "DataContext", + typeof(object), + typeof(Bind), + null, + DataContextChanged); + /// /// Gets the model to bind to. /// /// The dependency object to bind to. /// The model. - public static object GetModelWithoutContext(DependencyObject dependencyObject) { - return dependencyObject.GetValue(ModelWithoutContextProperty); - } + public static object GetModelWithoutContext(DependencyObject dependencyObject) + => dependencyObject.GetValue(ModelWithoutContextProperty); /// /// Sets the model to bind to. /// /// The dependency object to bind to. /// The model. - public static void SetModelWithoutContext(DependencyObject dependencyObject, object value) { - dependencyObject.SetValue(ModelWithoutContextProperty, value); - } + public static void SetModelWithoutContext(DependencyObject dependencyObject, object value) + => dependencyObject.SetValue(ModelWithoutContextProperty, value); /// /// Gets the model to bind to. /// /// The dependency object to bind to. /// The model. - public static object GetModel(DependencyObject dependencyObject) { - return dependencyObject.GetValue(ModelProperty); - } + public static object GetModel(DependencyObject dependencyObject) => dependencyObject.GetValue(ModelProperty); /// /// Sets the model to bind to. /// /// The dependency object to bind to. /// The model. - public static void SetModel(DependencyObject dependencyObject, object value) { - dependencyObject.SetValue(ModelProperty, value); - } + public static void SetModel(DependencyObject dependencyObject, object value) + => dependencyObject.SetValue(ModelProperty, value); + + /// + /// Gets whether or not conventions are being applied at design-time. + /// + /// The ui to apply conventions to. + /// Whether or not conventions are applied. +#if !MAUI && (NET || CAL_NETCORE) + [AttachedPropertyBrowsableForTypeAttribute(typeof(DependencyObject))] +#endif + public static bool GetAtDesignTime(DependencyObject dependencyObject) + => (bool)dependencyObject.GetValue(AtDesignTimeProperty); - static void ModelChanged(DependencyObject d, DependencyPropertyChangedEventArgs e) { + /// + /// Sets whether or not do bind conventions at design-time. + /// + /// The ui to apply conventions to. + /// Whether or not to apply conventions. + public static void SetAtDesignTime(DependencyObject dependencyObject, bool value) + => dependencyObject.SetValue(AtDesignTimeProperty, value); + + private static void ModelChanged(DependencyObject d, DependencyPropertyChangedEventArgs e) { if (View.InDesignMode || e.NewValue == null || e.NewValue == e.OldValue) { return; } - var fe = d as FrameworkElement; - if (fe == null) { + if (!(d is FrameworkElement fe)) { return; } View.ExecuteOnLoad(fe, delegate { - var target = e.NewValue; + object target = e.NewValue; d.SetValue(View.IsScopeRootProperty, true); #if XFORMS || MAUI - var context = fe.Id.ToString("N"); + string context = fe.Id.ToString("N"); #else - var context = string.IsNullOrEmpty(fe.Name) - ? fe.GetHashCode().ToString() + string context = string.IsNullOrEmpty(fe.Name) + ? fe.GetHashCode().ToString(CultureInfo.InvariantCulture) : fe.Name; #endif @@ -123,25 +158,24 @@ static void ModelChanged(DependencyObject d, DependencyPropertyChangedEventArgs }); } - static void ModelWithoutContextChanged(DependencyObject d, DependencyPropertyChangedEventArgs e) { + private static void ModelWithoutContextChanged(DependencyObject d, DependencyPropertyChangedEventArgs e) { if (View.InDesignMode || e.NewValue == null || e.NewValue == e.OldValue) { return; } - var fe = d as FrameworkElement; - if (fe == null) { + if (!(d is FrameworkElement fe)) { return; } View.ExecuteOnLoad(fe, delegate { - var target = e.NewValue; + object target = e.NewValue; d.SetValue(View.IsScopeRootProperty, true); #if XFORMS || MAUI - var context = fe.Id.ToString("N"); + string context = fe.Id.ToString("N"); #else - var context = string.IsNullOrEmpty(fe.Name) - ? fe.GetHashCode().ToString() + string context = string.IsNullOrEmpty(fe.Name) + ? fe.GetHashCode().ToString(CultureInfo.InvariantCulture) : fe.Name; #endif @@ -150,47 +184,17 @@ static void ModelWithoutContextChanged(DependencyObject d, DependencyPropertyCha }); } - /// - /// Allows application of conventions at design-time. - /// - public static DependencyProperty AtDesignTimeProperty = - DependencyPropertyHelper.RegisterAttached( - "AtDesignTime", - typeof(bool), - typeof(Bind), - false, - AtDesignTimeChanged); - - /// - /// Gets whether or not conventions are being applied at design-time. - /// - /// The ui to apply conventions to. - /// Whether or not conventions are applied. -#if !MAUI && (NET || CAL_NETCORE) - [AttachedPropertyBrowsableForTypeAttribute(typeof(DependencyObject))] -#endif - public static bool GetAtDesignTime(DependencyObject dependencyObject) { - return (bool)dependencyObject.GetValue(AtDesignTimeProperty); - } - - /// - /// Sets whether or not do bind conventions at design-time. - /// - /// The ui to apply conventions to. - /// Whether or not to apply conventions. - public static void SetAtDesignTime(DependencyObject dependencyObject, bool value) { - dependencyObject.SetValue(AtDesignTimeProperty, value); - } - - static void AtDesignTimeChanged(DependencyObject d, DependencyPropertyChangedEventArgs e) { - if (!View.InDesignMode) + private static void AtDesignTimeChanged(DependencyObject d, DependencyPropertyChangedEventArgs e) { + if (!View.InDesignMode) { return; + } - var atDesignTime = (bool) e.NewValue; - if (!atDesignTime) + bool atDesignTime = (bool)e.NewValue; + if (!atDesignTime) { return; + } #if XFORMS - d.SetBinding(DataContextProperty, String.Empty); + d.SetBinding(DataContextProperty, string.Empty); #elif MAUI d.SetBinding(DataContextProperty, null); #else @@ -198,28 +202,25 @@ static void AtDesignTimeChanged(DependencyObject d, DependencyPropertyChangedEve #endif } - static readonly DependencyProperty DataContextProperty = - DependencyPropertyHelper.RegisterAttached( - "DataContext", - typeof(object), - typeof(Bind), - null, DataContextChanged); - - static void DataContextChanged(DependencyObject d, DependencyPropertyChangedEventArgs e) { - if (!View.InDesignMode) + private static void DataContextChanged(DependencyObject d, DependencyPropertyChangedEventArgs e) { + if (!View.InDesignMode) { return; + } - var enable = d.GetValue(AtDesignTimeProperty); - if (enable == null || ((bool)enable) == false || e.NewValue == null) + object enable = d.GetValue(AtDesignTimeProperty); + if (enable == null || + (enable is bool isEnabled && !isEnabled) || + e.NewValue == null) { return; + } - var fe = d as FrameworkElement; - if (fe == null) + if (!(d is FrameworkElement fe)) { return; + } #if XFORMS || MAUI ViewModelBinder.Bind(e.NewValue, d, fe.Id.ToString("N")); #else - ViewModelBinder.Bind(e.NewValue, d, string.IsNullOrEmpty(fe.Name) ? fe.GetHashCode().ToString() : fe.Name); + ViewModelBinder.Bind(e.NewValue, d, string.IsNullOrEmpty(fe.Name) ? fe.GetHashCode().ToString(CultureInfo.InvariantCulture) : fe.Name); #endif } } diff --git a/src/Caliburn.Micro.Platform/BindingScope.cs b/src/Caliburn.Micro.Platform/BindingScope.cs index 12a31fe57..4efbd8808 100644 --- a/src/Caliburn.Micro.Platform/BindingScope.cs +++ b/src/Caliburn.Micro.Platform/BindingScope.cs @@ -1,51 +1,214 @@ -namespace Caliburn.Micro { - using System; - using System.Collections.Generic; - using System.Linq; +using System; +using System.Collections.Generic; +using System.Globalization; +using System.Linq; + #if WINDOWS_UWP - using System.ServiceModel; - using Windows.UI.Xaml; - using Windows.UI.Xaml.Controls; - using Windows.UI.Xaml.Controls.Primitives; - using Windows.UI.Xaml.Media; +using Windows.UI.Xaml; +using Windows.UI.Xaml.Controls; +using Windows.UI.Xaml.Controls.Primitives; +using Windows.UI.Xaml.Media; #else - using System.Windows; - using System.Windows.Controls; - using System.Windows.Media; - using System.Windows.Media.Media3D; +using System.Windows; +using System.Windows.Controls; +using System.Windows.Media; +using System.Windows.Media.Media3D; #endif +namespace Caliburn.Micro { /// /// Provides methods for searching a given scope for named elements. /// public static class BindingScope { - static readonly List ChildResolvers = new List(); - static readonly Dictionary NonResolvableChildTypes = new Dictionary(); + private static readonly List ChildResolvers = new List(); + private static readonly Dictionary NonResolvableChildTypes = new Dictionary(); - static BindingScope() - { + static BindingScope() { AddChildResolver(e => new[] { e.Content as DependencyObject }); - AddChildResolver(e => e.Items.OfType().ToArray() ); -#if !WINDOWS_UWP - AddChildResolver(e => new[] { e.Header as DependencyObject }); - AddChildResolver(e => new[] { e.Header as DependencyObject }); -#endif + AddChildResolver(e => e.Items.OfType().ToArray()); #if WINDOWS_UWP AddChildResolver(e => new[] { e.ZoomedInView as DependencyObject, e.ZoomedOutView as DependencyObject }); AddChildResolver(e => new[] { e.Header as DependencyObject }); -#endif -#if WINDOWS_UWP AddChildResolver(e => new[] { e.Footer as DependencyObject }); AddChildResolver(ResolveHub); AddChildResolver(e => new[] { e.Header as DependencyObject }); AddChildResolver(ResolveCommandBar); AddChildResolver