diff --git a/Atc.Wpf.sln b/Atc.Wpf.sln
index d5aed653..0216002f 100644
--- a/Atc.Wpf.sln
+++ b/Atc.Wpf.sln
@@ -40,6 +40,18 @@ Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Atc.Wpf.SourceGenerators",
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Atc.Wpf.SourceGenerators.Tests", "test\Atc.Wpf.SourceGenerators.Tests\Atc.Wpf.SourceGenerators.Tests.csproj", "{6B9C0EC5-1D71-4B95-96C8-E5A7FCDF8276}"
EndProject
+Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "SourceGenerators", "SourceGenerators", "{02EA681E-C7D8-13C7-8484-4AC65E1B71E8}"
+ ProjectSection(SolutionItems) = preProject
+ docs\SourceGenerators\AttachedProperty.md = docs\SourceGenerators\AttachedProperty.md
+ docs\SourceGenerators\DependencyProperty.md = docs\SourceGenerators\DependencyProperty.md
+ docs\SourceGenerators\ViewModel.md = docs\SourceGenerators\ViewModel.md
+ EndProjectSection
+EndProject
+Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Mvvm", "Mvvm", "{02E4711C-F1F1-4682-8111-47957F772DDD}"
+ ProjectSection(SolutionItems) = preProject
+ docs\Mvvm\@Readme.md = docs\Mvvm\@Readme.md
+ EndProjectSection
+EndProject
Global
GlobalSection(SolutionConfigurationPlatforms) = preSolution
Debug|Any CPU = Debug|Any CPU
@@ -111,9 +123,11 @@ Global
{F4BE59B5-26CB-46DB-B290-2049EF656693} = {0FA723AA-8BA7-42AD-9C08-6491F364A258}
{104F4D6B-3991-4FBA-9234-0B37DF6BB292} = {0FA723AA-8BA7-42AD-9C08-6491F364A258}
{6B9C0EC5-1D71-4B95-96C8-E5A7FCDF8276} = {2C893B25-2401-44B7-9FDA-7DC496C00D2B}
+ {02EA681E-C7D8-13C7-8484-4AC65E1B71E8} = {133AFD8A-A870-41D7-8A97-763A36D2502A}
+ {02E4711C-F1F1-4682-8111-47957F772DDD} = {133AFD8A-A870-41D7-8A97-763A36D2502A}
EndGlobalSection
GlobalSection(ExtensibilityGlobals) = postSolution
- SolutionGuid = {7A92D7EC-F444-456B-A86C-D6683E016C35}
RESX_Rules = {"EnabledRules":["StringFormat","WhiteSpaceLead","WhiteSpaceTail","PunctuationTail"]}
+ SolutionGuid = {7A92D7EC-F444-456B-A86C-D6683E016C35}
EndGlobalSection
EndGlobal
diff --git a/README.md b/README.md
index 42eed778..ab6fa269 100644
--- a/README.md
+++ b/README.md
@@ -10,21 +10,23 @@ This is a base libraries for building WPF application with the MVVM design patte
- [Table of contents](#table-of-contents)
- [Requirements](#requirements)
- [NuGet Packages Provided in this Repository](#nuget-packages-provided-in-this-repository)
- - [Demonstration Application](#demonstration-application)
- - [Playground and Viewer for a Given Control or Functionality](#playground-and-viewer-for-a-given-control-or-functionality)
+- [π Demonstration Application](#-demonstration-application)
+ - [Playground and Viewer for a Given Control or Functionality](#playground-and-viewer-for-a-given-control-or-functionality)
- [Initial glimpse at the demonstration application](#initial-glimpse-at-the-demonstration-application)
- - [How to get started with atc-wpf](#how-to-get-started-with-atc-wpf)
- - [Readme's for each NuGet Package area](#readmes-for-each-nuget-package-area)
- - [Atc.Wpf](#atcwpf)
- - [Controls](#controls)
- - [Misc](#misc)
- - [Atc.Wpf.Controls](#atcwpfcontrols)
- - [Controls](#controls-1)
- - [Misc](#misc-1)
- - [Atc.Wpf.FontIcons](#atcwpffonticons)
- - [Misc](#misc-2)
- - [Atc.Wpf.Theming](#atcwpftheming)
- - [How to contribute](#how-to-contribute)
+- [π How to get started with atc-wpf](#-how-to-get-started-with-atc-wpf)
+ - [WPF with MVVM Easily Separate UI and Business Logic](#wpf-with-mvvm-easily-separate-ui-and-business-logic)
+- [π Readme's for each NuGet Package area](#-readmes-for-each-nuget-package-area)
+ - [π Atc.Wpf](#-atcwpf)
+ - [Controls](#controls)
+ - [Misc](#misc)
+ - [π Atc.Wpf.Controls](#-atcwpfcontrols)
+ - [Controls](#controls-1)
+ - [Misc](#misc-1)
+ - [π Atc.Wpf.FontIcons](#-atcwpffonticons)
+ - [Misc](#misc-2)
+ - [π Atc.Wpf.Theming](#-atcwpftheming)
+- [βοΈ Source Generators](#οΈ-source-generators)
+- [How to contribute](#how-to-contribute)
## Requirements
@@ -34,19 +36,19 @@ This is a base libraries for building WPF application with the MVVM design patte
| Nuget package | Description | Dependencies |
|-------------------------|-----------------------------------------------------|--------------------------------|
-| Atc.Wpf | Base Controls, ValueConverters, Extensions etc. | Atc & Atc.Wpf.SourceGenerators |
-| Atc.Wpf.Controls | Miscellaneous UI Controls | Atc.Wpf & Atc.Wpf.Theming |
-| Atc.Wpf.Controls.Sample | Controls for creating WPF sample apps | Atc.Wpf & Atc.Wpf.Theming |
-| Atc.Wpf.FontIcons | Render Svg and Img resources based on fonts | Atc.Wpf |
-| Atc.Wpf.Theming | Theming for Light & Dark mode for WPF base controls | Atc.Wpf |
+| π Atc.Wpf | Base Controls, ValueConverters, Extensions etc. | Atc & Atc.Wpf.SourceGenerators |
+| π Atc.Wpf.Controls | Miscellaneous UI Controls | Atc.Wpf & Atc.Wpf.Theming |
+| π Atc.Wpf.Controls.Sample | Controls for creating WPF sample apps | Atc.Wpf & Atc.Wpf.Theming |
+| π Atc.Wpf.FontIcons | Render Svg and Img resources based on fonts | Atc.Wpf |
+| π Atc.Wpf.Theming | Theming for Light & Dark mode for WPF base controls | Atc.Wpf |
-## Demonstration Application
+# π Demonstration Application
The demonstration application, `Atc.Wpf.Sample`, functions as a control explorer.
It provides quick visualization of a given control, along with options for
copying and pasting the XAML markup and/or the C# code for how to use it.
-### Playground and Viewer for a Given Control or Functionality
+## Playground and Viewer for a Given Control or Functionality
The following example is taken from the ReplayCommandAsync which illustrates its usage:
@@ -70,16 +72,16 @@ The following example is taken from the ReplayCommandAsync which illustrates its
| Wpf.Theming - ImageButton  | Wpf.Theming - ImageButton  |
| Wpf.FontIcons - Viewer  | Wpf.FontIcons - Viewer  |
-## How to get started with atc-wpf
+# π How to get started with atc-wpf
First of all, include Nuget packages in the `.csproj` file like this:
```xml
-
-
-
-
+
+
+
+
```
@@ -105,126 +107,78 @@ Then update `App.xaml` like this:
Now it is possible to use controls with theming and default WPF controls like TextBox, Button etc. with theme style.
-## Readme's for each NuGet Package area
+## WPF with MVVM Easily Separate UI and Business Logic
+
+With the `Atc.Wpf`, package, it is very easy to get startet with the nice `MVVM pattern`
+
+Please read more here:
+
+- [MVVM framework](docs/Mvvm/@Readme.md)
+ - [Observerble properties](docs/Mvvm/@Readme.md)
+ - [RelayCommands](docs/Mvvm/@Readme.md)
+
+# π Readme's for each NuGet Package area
***Note: Right now, it is a limit amount of controls and components there is documented with a `Readme.md` file.
-Therefore run the `Atc.Wpf.Sample` application to explore all the controls and components.*** :-)
+Therefore run the `Atc.Wpf.Sample` application to explore all the controls and components.*** π
-### Atc.Wpf
+## π Atc.Wpf
-#### Controls
+### Controls
-- [GridEx](src/Atc.Wpf/Controls/Layouts/GridEx_Readme.md)
-- [StaggeredPanel](src/Atc.Wpf/Controls/Layouts/StaggeredPanel_Readme.md)
-- [UniformSpacingPanel](src/Atc.Wpf/Controls/Layouts/UniformSpacingPanel_Readme.md)
-- [SvgImage](src/Atc.Wpf/Controls/Media/SvgImage_Readme.md)
-- Control Helpers
+- Layouts
+ - [GridEx](src/Atc.Wpf/Controls/Layouts/GridEx_Readme.md)
+ - [StaggeredPanel](src/Atc.Wpf/Controls/Layouts/StaggeredPanel_Readme.md)
+ - [UniformSpacingPanel](src/Atc.Wpf/Controls/Layouts/UniformSpacingPanel_Readme.md)
+- Media
+ - [SvgImage](src/Atc.Wpf/Controls/Media/SvgImage_Readme.md)
+- Helpers
- [PanelHelper](src/Atc.Wpf/Helpers/PanelHelper_Readme.md)
-#### Misc
+### Misc
-- [MVVM framework](src/Atc.Wpf/Mvvm/@Readme.md)
- - [RelayCommand's](src/Atc.Wpf/Command/@Readme.md)
- [ShaderEffects](src/Atc.Wpf/Media/ShaderEffects/@Readme.md)
- [How to use HLSL Shader Compiler](src/Atc.Wpf/Media/ShaderEffects/Shaders/@Readme.md)
- [Tranlation & localizaion](src/Atc.Wpf/Translation/@Readme.md)
- [ValueConverters](src/Atc.Wpf/ValueConverters/@Readme.md)
-### Atc.Wpf.Controls
+## π Atc.Wpf.Controls
-#### Controls
+### Controls
- [WellKnownColorPicker](src/Atc.Wpf.Controls/ColorControls/WellKnownColorPicker_Readme.md)
-#### Misc
+### Misc
- [ValueConverters](src/Atc.Wpf.Controls/ValueConverters/@Readme.md)
-### Atc.Wpf.FontIcons
+## π Atc.Wpf.FontIcons
-#### Misc
+### Misc
- [ValueConverters](src/Atc.Wpf.FontIcons/ValueConverters/@Readme.md)
-### Atc.Wpf.Theming
+## π Atc.Wpf.Theming
- [ValueConverters](src/Atc.Wpf.Theming/ValueConverters/@Readme.md)
-### Source Generators
+# βοΈ Source Generators
-In MVVM, certain attributes help automate boilerplate code using source generators.
+In for WPF, certain attributes help automate boilerplate code using source generators.
-Example for ViewModel classes
+Read more about here:
-
-
-For more details, see the [MVVM](src/Atc.Wpf/Mvvm/@Readme.md) section.
+- [SourceGenerators for AttachedProperties](docs/SourceGenerators/AttachedProperty.md)
+- [SourceGenerators for DependencyProperties](docs/SourceGenerators/DependencyProperty.md)
+- [SourceGenerators for ViewModel](docs/SourceGenerators/ViewModel.md)
-### DependencyProperty
+Example for ViewModel classes
-### Human made Code for complex property
-
-```csharp
-[DependencyProperty("IsRunning")]
-public partial class MyControl : UserControl
-{
-}
-```
-
-### Human made Code for simple property
-
-```csharp
-[DependencyProperty("IsRunning"]
-public partial class MyControl : UserControl
-{
-}
-```
-
-### Generated Code for simple property
-
-```csharp
-public partial class MyControl
-{
- public static readonly DependencyProperty IsRunningProperty = DependencyProperty.Register(
- nameof(IsRunning),
- typeof(bool),
- typeof(MyControl),
- new FrameworkPropertyMetadata(defaultValue: BooleanBoxes.TrueBox);
-
- public bool IsRunning
- {
- get => (bool)GetValue(IsRunningProperty);
- set => SetValue(IsRunningProperty, value);
- }
-}
-```
+
-### Generated Code for complex property
-
-```csharp
-public partial class MyControl
-{
- public static readonly DependencyProperty IsRunningProperty = DependencyProperty.Register(
- nameof(IsRunning),
- typeof(bool),
- typeof(MyControl),
- new FrameworkPropertyMetadata(
- defaultValue: BooleanBoxes.TrueBox,
- propertyChangedCallback: PropertyChangedCallback,
- coerceValueCallback: CoerceValueCallback,
- flags: FrameworkPropertyMetadataOptions.AffectsMeasure | FrameworkPropertyMetadataOptions.AffectsRender,
- defaultUpdateSourceTrigger: UpdateSourceTrigger.Default,
- isAnimationProhibited: true));
-
- public bool IsRunning
- {
- get => (bool)GetValue(IsRunningProperty);
- set => SetValue(IsRunningProperty, value);
- }
-}
-```
+For more details, see the [MVVM](docs/Mvvm/@Readme.md) section.
-## How to contribute
+# How to contribute
[Contribution Guidelines](https://atc-net.github.io/introduction/about-atc#how-to-contribute)
diff --git a/docs/Mvvm/@Readme.md b/docs/Mvvm/@Readme.md
new file mode 100644
index 00000000..6ae0fe9d
--- /dev/null
+++ b/docs/Mvvm/@Readme.md
@@ -0,0 +1,50 @@
+# MVVM in WPF
+
+Windows Presentation Foundation (WPF) fully supports the **Model-View-ViewModel (MVVM)** pattern, which promotes a clear separation of concerns between the UI and business logic.
+
+The **Atc.Wpf** library provides a robust foundation for implementing MVVM effectively, reducing boilerplate code and simplifying development.
+
+## Features
+
+The `Atc.Wpf` library offers a variety of base classes and utilities to streamline MVVM implementation:
+
+| Component | Description |
+|---------------------------|--------------------------------------------------------------------------------|
+| `ViewModelBase` | A base class for ViewModels. |
+| `MainWindowViewModelBase` | A base class for the main window ViewModel. |
+| `ViewModelDialogBase` | A base class for dialog ViewModels. |
+| `ObservableObject` | A base class for observable objects implementing `INotifyPropertyChanged`. |
+| `RelayCommand` | A command supporting `CanExecute`. |
+| `RelayCommand` | A command with a generic parameter and `CanExecute`. |
+| `RelayCommandAsync` | An asynchronous command supporting `CanExecute`. |
+| `RelayCommandAsync` | An asynchronous command with a generic parameter and `CanExecute`. |
+
+For detailed information about commands, refer to the [RelayCommand documentation](../SourceGenerators/ViewModel.md).
+
+---
+
+### Getting started using `ViewModelBase`
+
+Below is a simple example demonstrating how to create a ViewModel using `ViewModelBase`:
+
+```csharp
+public class MyViewModel : ViewModelBase
+{
+ private string myProperty;
+
+ public string MyProperty
+ {
+ get => myProperty;
+ set
+ {
+ if (myProperty == value)
+ {
+ return;
+ }
+
+ myProperty = value;
+ RaisePropertyChanged();
+ }
+ }
+}
+```
diff --git a/docs/SourceGenerators/AttachedProperty.md b/docs/SourceGenerators/AttachedProperty.md
new file mode 100644
index 00000000..fba6c3f2
--- /dev/null
+++ b/docs/SourceGenerators/AttachedProperty.md
@@ -0,0 +1,207 @@
+# βοΈ AttachedProperty with SourceGeneration
+
+In WPF, **attached properties** are a type of dependency property that allows properties to be defined in one class but used in another. They are widely used in scenarios like behaviors, layout configurations, and interactions where a property needs to be applied to multiple elements without modifying their class definitions. Traditionally, defining attached properties requires boilerplate code, but source generators can automate this process, reducing errors and improving maintainability.
+
+---
+
+## π Defining an Attached Property
+
+### β¨ Creating a Simple Attached Property
+
+Let's define an attached property using source generators.
+
+```csharp
+[AttachedProperty("IsDraggable")]
+public static partial class DragBehavior
+{
+}
+```
+
+### π What's Happening Here?
+
+- The `AttachedPropertyAttribute` automatically generates the `IsDraggable` property with `Get` and `Set` methods.
+
+### π₯οΈ XAML Example
+
+```xml
+
+
+
+
+
+```
+
+This allows the `IsDraggable` property to be applied to any UI element dynamically.
+
+---
+
+## π Summary
+
+This example showcases `advanced metadata` for attached properties, allowing:
+
+- βοΈ **Automatic property registration**
+- βοΈ **Flexible application to various UI elements**
+- βοΈ **Custom property value coercion and validation**
+- βοΈ **Efficient UI updates**
+- βοΈ **Simplified code structure**
+
+### π Why Use Atc.Wpf Source Generators?
+
+- β **Eliminates boilerplate** β Just declare the property, and the generator handles the rest.
+- β **Ensures consistency** β Less room for human error.
+
+---
+
+## π Behind the scenes
+
+### π Human-Written Code - for simple example
+
+```csharp
+[AttachedProperty("IsDraggable"]
+public static partial class DragBehavior
+{
+}
+```
+
+In this example:
+
+- The `[AttachedProperty("IsDraggable")]` attribute declares an attached property named `IsDraggable` of type `bool` for the `DragBehavior` class.
+- The source generator will automatically create the necessary methods and property registration.
+
+### βοΈ Auto-Generated Code - for simple example
+
+The source generator will produce code equivalent to:
+
+```csharp
+public static partial class DragBehavior
+{
+ public static readonly DependencyProperty IsDraggableProperty = DependencyProperty.RegisterAttached(
+ "IsDraggable",
+ typeof(bool),
+ typeof(DragBehavior),
+ new PropertyMetadata(defaultValue: BooleanBoxes.FalseBox));
+
+ public static bool GetIsDraggable(UIElement element)
+ => element is not null && (bool)element.GetValue(IsDraggableProperty);
+
+ public static void SetIsDraggable(UIElement element, bool value)
+ => element?.SetValue(IsDraggableProperty, value);
+}
+```
+
+### π Human-Written Code - for complex example
+
+```csharp
+[AttachedProperty(
+ "IsDraggable",
+ DefaultValue = false,
+ PropertyChangedCallback = nameof(PropertyChangedCallback),
+ CoerceValueCallback = nameof(CoerceValueCallback),
+ Flags = FrameworkPropertyMetadataOptions.AffectsMeasure | FrameworkPropertyMetadataOptions.AffectsRender,
+ DefaultUpdateSourceTrigger = UpdateSourceTrigger.Default,
+ IsAnimationProhibited = true,
+ ValidateValueCallback = nameof(ValidateValueCallback))]
+public partial class DragBehavior
+{
+ private static void PropertyChangedCallback(DependencyObject d, DependencyPropertyChangedEventArgs e)
+ {
+ throw new NotImplementedException();
+ }
+
+ private static object CoerceValueCallback(DependencyObject d, object baseValue)
+ {
+ throw new NotImplementedException();
+ }
+
+ private static bool ValidateValueCallback(object value)
+ {
+ throw new NotImplementedException();
+ }
+}
+```
+
+**In this example:**
+
+- `[AttachedProperty("IsDraggable")]`
+ - Declares a **dependency property** named `IsDraggable` of type `bool` for the `DragBehavior` class.
+ - Unlike a regular dependency property, an **attached property** is **not tied to a single class** but can be applied to any **UI element**.
+ - The source generator will automatically create:
+ - A `DependencyProperty` field for `IsDraggableProperty`.
+ - **Static** `GetIsDraggable` **and** `SetIsDraggable` **methods**, allowing other controls to use this property dynamically.
+
+- `DefaultValue = false`
+ - Specifies the default value of `IsDraggable` as `false`, meaning that **elements are not draggable unless explicitly enabled**.
+
+- `PropertyChangedCallback = nameof(PropertyChangedCallback)`
+ - Assigns a **property changed callback method**, that is triggered **whenever the** `IsDraggable` **value changes**.
+ - This allows dynamic behavior updatesβe.g., adding or removing event handlers for drag operations.
+
+- `CoerceValueCallback = nameof(CoerceValueCallback)`
+ - Called before the property value is assigned.
+ - This method can **modify the value before applying it**.
+ - Example: If an element **must not be draggable** under certain conditions, the `CoerceValueCallback` can force the value back to `false`.
+
+- `Flags = FrameworkPropertyMetadataOptions.AffectsMeasure | FrameworkPropertyMetadataOptions.AffectsRender`
+ - Specifies that changes to `IsDraggable` affect the UI layout and rendering.
+ - `AffectsMeasure`: Triggers a re-measure of the UI element if this property changes.
+ - `AffectsRender`: Causes a re-render of the control when this property is modified.
+ - Example: If `IsDraggable` changes, **drag indicators or visual cues might need updating**.
+
+- `FrameworkPropertyMetadataOptions`
+ - A flag-based enumeration used to specify additional **property behavior** in WPF.
+ - Other possible values include:
+ - `AffectsParentMeasure`: Causes a layout pass on the parent when the property changes.
+ - `AffectsArrange`: Forces an arrange pass when the property changes.
+ - `Inherits`: Allows the property value to propagate down the visual tree.
+ - `BindsTwoWayByDefault`: Sets the default binding mode to **TwoWay**.
+ - These flags **optimize performance** by ensuring layout changes only occur when necessary.
+
+- `DefaultUpdateSourceTrigger = UpdateSourceTrigger.Default`
+ - Specifies how **data binding** updates the property's source.
+ - The `Default` value means that the **default behavior of the property type** is used.
+ - Other possible values:
+ - `PropertyChanged`: Updates the source immediately when the property changes.
+ - `LostFocus`: Updates the source when the control loses focus (e.g., leaving a text box).
+ - `Explicit`: Requires manual invocation of `BindingExpression.UpdateSource()`.
+
+- `IsAnimationProhibited = true`
+ - Prevents animations from affecting this property.
+ - Some dependency properties allow animations to change their values smoothly over time.
+ - By setting `IsAnimationProhibited = true`, you ensure that **no animations** can modify `IsDraggable`.
+ - This is useful for properties where **instant updates are required**, such as boolean state changes.
+
+- `ValidateValueCallback = nameof(ValidateValueCallback)`
+ - Assigns a **validation callback method**, which ensures that only valid values are assigned to the dependency property.
+ - This function **executes before** the property value is set, allowing you to **reject invalid values** before they are applied.
+ - The `ValidateValueCallback` method should return a `bool`:
+ - `true`: The value is accepted and applied to the property.
+ - `false`: The value is considered invalid, and an exception is thrown.
+
+### βοΈ Auto-Generated Code - for complex example
+
+The source generator will produce equivalent code:
+
+```csharp
+public partial class DragBehavior
+{
+ public static readonly DependencyProperty IsDraggableProperty = DependencyProperty.Register(
+ nameof(IsDraggable),
+ typeof(bool),
+ typeof(DragBehavior),
+ new FrameworkPropertyMetadata(
+ defaultValue: BooleanBoxes.FalseBox,
+ propertyChangedCallback: PropertyChangedCallback,
+ coerceValueCallback: CoerceValueCallback,
+ flags: FrameworkPropertyMetadataOptions.AffectsMeasure | FrameworkPropertyMetadataOptions.AffectsRender,
+ defaultUpdateSourceTrigger: UpdateSourceTrigger.Default,
+ isAnimationProhibited: true,
+ validateValueCallback = ValidateValueCallback));
+
+ public bool IsDraggable
+ {
+ get => (bool)GetValue(IsDraggableProperty);
+ set => SetValue(IsDraggableProperty, value);
+ }
+}
+```
diff --git a/docs/SourceGenerators/DependencyProperty.md b/docs/SourceGenerators/DependencyProperty.md
new file mode 100644
index 00000000..e2a90db9
--- /dev/null
+++ b/docs/SourceGenerators/DependencyProperty.md
@@ -0,0 +1,219 @@
+# βοΈ DependencyProperties with SourceGeneration
+
+In WPF, **dependency properties** are a specialized type of property that extends the functionality of standard CLR properties. They support features such as data binding, animation, and property value inheritance, which are integral to the WPF property system. However, defining dependency properties traditionally involves verbose boilerplate code. To streamline this process, source generators can automatically generate the necessary code, reducing errors and improving maintainability.
+
+---
+
+## π Setting Up Your First UserControl
+
+### β¨ Creating a Simple UserControl
+
+Let's start by defining a UserControl using source generators.
+
+```csharp
+[DependencyProperty("IsRunning")]
+public partial class TestView
+{
+ public TestView()
+ {
+ InitializeComponent();
+ }
+}
+```
+
+### π What's Happening Here?
+
+- The `DependencyPropertyAttribute` automatically generates the `IsRunning` property, including `INotifyPropertyChanged` support.
+
+### π₯οΈ XAML Binding Example
+
+```xml
+
+
+
+
+
+
+
+
+
+
+
+```
+
+This setup allows the UI to dynamically update when the IsRunning property changes.
+
+---
+
+## π Summary
+
+This example showcases **advanced metadata** for dependency properties, allowing:
+
+- βοΈ **Automatic property change notifications**
+- βοΈ **Value coercion and validation**
+- βοΈ **Optimized UI performance with layout invalidation**
+- βοΈ **Flexible data binding behavior**
+- βοΈ **Control over animation support**
+
+### π Why Use Atc.Wpf Source Generators?
+
+- β **Eliminates boilerplate** β Just declare the property, and the generator handles the rest.
+
+---
+
+## π Behind the scenes
+
+### π Human-Written Code - for simple example
+
+```csharp
+[DependencyProperty("IsRunning"]
+public partial class MyControl : UserControl
+{
+}
+```
+
+**In this example:**
+
+- The `[DependencyProperty("IsRunning")]` attribute indicates that a dependency property named `IsRunning` of type `bool` should be generated for the `MyControl` class.
+
+### βοΈ Auto-Generated Code - for simple example
+
+The source generator will produce code equivalent to:
+
+```csharp
+public partial class MyControl
+{
+ public static readonly DependencyProperty IsRunningProperty = DependencyProperty.Register(
+ nameof(IsRunning),
+ typeof(bool),
+ typeof(MyControl),
+ new FrameworkPropertyMetadata(defaultValue: BooleanBoxes.TrueBox);
+
+ public bool IsRunning
+ {
+ get => (bool)GetValue(IsRunningProperty);
+ set => SetValue(IsRunningProperty, value);
+ }
+}
+```
+
+### π Human-Written Code - for complex example
+
+```csharp
+
+[DependencyProperty(
+ "IsRunning",
+ DefaultValue = false,
+ PropertyChangedCallback = nameof(PropertyChangedCallback),
+ CoerceValueCallback = nameof(CoerceValueCallback),
+ Flags = FrameworkPropertyMetadataOptions.AffectsMeasure | FrameworkPropertyMetadataOptions.AffectsRender,
+ DefaultUpdateSourceTrigger = UpdateSourceTrigger.Default,
+ IsAnimationProhibited = true,
+ ValidateValueCallback = nameof(ValidateValueCallback))]
+public partial class MyControl : UserControl
+{
+ private static void PropertyChangedCallback(DependencyObject d, DependencyPropertyChangedEventArgs e)
+ {
+ throw new NotImplementedException();
+ }
+
+ private static object CoerceValueCallback(DependencyObject d, object baseValue)
+ {
+ throw new NotImplementedException();
+ }
+
+ private static bool ValidateValueCallback(object value)
+ {
+ throw new NotImplementedException();
+ }
+}
+```
+
+**In this example:**
+
+- `[DependencyProperty("IsRunning")]`
+ - Declares a **dependency property** named `IsRunning` of type `bool` for `MyControl`.
+ - The source generator will automatically create a `DependencyProperty` field and a CLR property wrapper.
+
+- `DefaultValue = false`
+ - Specifies the default value of `IsRunning` as `false`.
+ - This means that when an instance of `MyControl` is created, `IsRunning` will be `false` unless explicitly set.
+
+- `PropertyChangedCallback = nameof(PropertyChangedCallback)`
+ - Assigns a **property changed callback method**, which is invoked whenever the property's value changes.
+ - In this example, `PropertyChangedCallback(DependencyObject d, DependencyPropertyChangedEventArgs e)` is defined as a static method.
+ - This method allows you to **respond to property changes**, such as triggering UI updates or executing business logic.
+
+- `CoerceValueCallback = nameof(CoerceValueCallback)`
+ - Assigns a **coerce value callback method**, which is called before setting the propertyβs value.
+ - This function allows validation, restricting the range of acceptable values, or adjusting the value based on other conditions.
+ - For instance, if `IsRunning` should never be `true` under specific circumstances, the `CoerceValueCallback` could enforce that rule.
+
+- `Flags = FrameworkPropertyMetadataOptions.AffectsMeasure | FrameworkPropertyMetadataOptions.AffectsRender`
+ - Configures how the property affects the **WPF layout system** when it changes.
+ - `AffectsMeasure`: Triggers a re-measure of the UI element if this property changes.
+ - `AffectsRender`: Causes a re-render of the control when this property is modified.
+ - These options ensure that any UI elements depending on `IsRunning` will **recalculate their size and appearance** accordingly.
+
+- `FrameworkPropertyMetadataOptions`
+ - A flag-based enumeration used to specify additional **property behavior** in WPF.
+ - Other possible values include:
+ - `AffectsParentMeasure`: Causes a layout pass on the parent when the property changes.
+ - `AffectsArrange`: Forces an arrange pass when the property changes.
+ - `Inherits`: Allows the property value to propagate down the visual tree.
+ - `BindsTwoWayByDefault`: Sets the default binding mode to **TwoWay**.
+ - These flags **optimize performance** by ensuring layout changes only occur when necessary.
+
+- `DefaultUpdateSourceTrigger = UpdateSourceTrigger.Default`
+ - Specifies how **data binding** updates the property's source.
+ - The `Default` value means that the **default behavior of the property type** is used.
+ - Other possible values:
+ - `PropertyChanged`: Updates the source immediately when the property changes.
+ - `LostFocus`: Updates the source when the control loses focus (e.g., leaving a text box).
+ - `Explicit`: Requires manual invocation of `BindingExpression.UpdateSource()`.
+
+- `IsAnimationProhibited = true`
+ - Prevents animations from affecting this property.
+ - Some dependency properties allow animations to change their values smoothly over time.
+ - By setting `IsAnimationProhibited = true`, you ensure that **no animations** can modify `IsRunning`.
+ - This is useful for properties where **instant updates are required**, such as boolean state changes.
+
+- `ValidateValueCallback = nameof(ValidateValueCallback)`
+ - Assigns a **validation callback method**, which ensures that only valid values are assigned to the dependency property.
+ - This function **executes before** the property value is set, allowing you to **reject invalid values** before they are applied.
+ - The `ValidateValueCallback` method should return a `bool`:
+ - `true`: The value is accepted and applied to the property.
+ - `false`: The value is considered invalid, and an exception is thrown.
+
+---
+
+### βοΈ Auto-Generated Code - for complex example
+
+The source generator will produce code equivalent to:
+
+```csharp
+public partial class MyControl
+{
+ public static readonly DependencyProperty IsRunningProperty = DependencyProperty.Register(
+ nameof(IsRunning),
+ typeof(bool),
+ typeof(MyControl),
+ new FrameworkPropertyMetadata(
+ defaultValue: BooleanBoxes.FalseBox,
+ propertyChangedCallback: PropertyChangedCallback,
+ coerceValueCallback: CoerceValueCallback,
+ flags: FrameworkPropertyMetadataOptions.AffectsMeasure | FrameworkPropertyMetadataOptions.AffectsRender,
+ defaultUpdateSourceTrigger: UpdateSourceTrigger.Default,
+ isAnimationProhibited: true,
+ validateValueCallback = ValidateValueCallback));
+
+ public bool IsRunning
+ {
+ get => (bool)GetValue(IsRunningProperty);
+ set => SetValue(IsRunningProperty, value);
+ }
+}
+```
diff --git a/src/Atc.Wpf/Mvvm/@Readme.md b/docs/SourceGenerators/ViewModel.md
similarity index 54%
rename from src/Atc.Wpf/Mvvm/@Readme.md
rename to docs/SourceGenerators/ViewModel.md
index 35794bdb..31667ded 100644
--- a/src/Atc.Wpf/Mvvm/@Readme.md
+++ b/docs/SourceGenerators/ViewModel.md
@@ -1,55 +1,55 @@
-# MVVM
+# βοΈ ViewModel with Source Generation
-The Windows Presentation Foundation (WPF) fully supports the Model-View-ViewModel (MVVM) pattern.
+The **Atc.Wpf Source Generators** simplify ViewModel development by reducing boilerplate code for properties and commands. With attributes like `ObservableProperty` and `RelayCommand`, you can focus on business logic while automatically handling property change notifications and command implementations.
-The `Atc.Wpf` library provides a solid foundation for implementing MVVM effectively.
-
-## Features
-
-| Component | Description |
-|---------------------------|--------------------------------------------------------------------------------|
-| `ViewModelBase` | A base class for ViewModels. |
-| `MainWindowViewModelBase` | A base class for the main window ViewModel. |
-| `ViewModelDialogBase` | A base class for dialog ViewModels. |
-| `ObservableObject` | A base class for observable objects implementing `INotifyPropertyChanged`. |
-| `RelayCommand` | A command supporting `CanExecute`. |
-| `RelayCommand` | A command with a generic parameter and `CanExecute`. |
-| `RelayCommandAsync` | An asynchronous command supporting `CanExecute`. |
-| `RelayCommandAsync` | An asynchronous command with a generic parameter and `CanExecute`. |
+---
-For more details on commands, see the [RelayCommand documentation](../Command/@Readme.md).
+## π Setting Up Your First ViewModel
----
+### β¨ Creating a Simple ViewModel
-## Example: Using `ViewModelBase`
+Let's start by defining a ViewModel using source generators.
```csharp
-public class MyViewModel : ViewModelBase
+public partial class TestViewModel : ViewModelBase
{
- private string myProperty;
-
- public string MyProperty
- {
- get => myProperty;
- set
- {
- if (myProperty == value)
- {
- return;
- }
-
- myProperty = value;
- RaisePropertyChanged();
- }
- }
+ [ObservableProperty]
+ private string name;
}
```
+### π What's Happening Here?
+
+- `ObservablePropertyAttribute` automatically generates the `Name` property, including `INotifyPropertyChanged` support.
+- `RelayCommand` generates a `SayHelloCommand`, which can be bound to a button in the UI.
+
+### π₯οΈ XAML Binding Example
+
+```xml
+
+
+
+
+
+
+
+
+
+
+
+
+
+```
+
+This setup allows the UI to dynamically update when the Name property changes.
+
---
-## Attributes for Property Source-Generation
+## π Attributes for Property Source Generation
+
+The `ObservableProperty` attribute automatically generates properties from private fields, including `INotifyPropertyChanged` support.
-### Quick Start Tips
+### π Quick Start: Using `ObservableProperty`
```csharp
// Generates a property named "Name"
@@ -63,7 +63,11 @@ public class MyViewModel : ViewModelBase
// Generates a property named "MyName" and notifies FullName and Age
[ObservableProperty(nameof(MyName), DependentProperties = [nameof(FullName), nameof(Age)])] private string name;
+```
+
+### π Notifying Other Properties
+```csharp
// Generates a property named "Name" and notifies FullName and Age
[ObservableProperty(DependentProperties = [nameof(FullName), nameof(Age)])]
@@ -74,13 +78,17 @@ public class MyViewModel : ViewModelBase
[NotifyPropertyChangedFor(nameof(FullName), nameof(Age))]
```
-> Note: The `ObservableProperty` attribute automatically generates a property for a private field in a ViewModel class.
-> The `NotifyPropertyChangedFor` attribute triggers change notifications for other properties when the annotated property changes.
-> Multiple properties can be notified, but an ObservableProperty must be defined for it to work.
+**Note:**
-## Attributes for RelayCommand Source-Generation
+- `ObservableProperty` creates a public property from a private field and implements change notification.
-### Quick Start Tips
+- `NotifyPropertyChangedFor` ensures that when the annotated property changes, specified dependent properties also get notified.
+
+## β‘ Attributes for `RelayCommand` Source-Generation
+
+The `RelayCommand` attribute generates `IRelayCommand` properties, eliminating manual command setup.
+
+### π Quick Start Tips for RelayCommands
```csharp
// Generates a RelayCommand named "SaveCommand"
@@ -88,13 +96,21 @@ public class MyViewModel : ViewModelBase
// Generates a RelayCommand named "MySaveCommand"
[RelayCommand("MySave")] public void Save();
+```
+### π·οΈ Commands with CanExecute Logic
+
+```csharp
// Generates a RelayCommand that takes a string parameter
[RelayCommand()] public void Save(string text);
// Generates a RelayCommand with CanExecute function
[RelayCommand(CanExecute = nameof(CanSave))] public void Save();
+```
+
+### π Asynchronous Commands
+```csharp
// Generates an asynchronous RelayCommand
[RelayCommand()] public Task Save();
@@ -118,7 +134,11 @@ public class MyViewModel : ViewModelBase
// Generates an asynchronous RelayCommand with async keyword and CanExecute function
[RelayCommand(CanExecute = nameof(CanSave))] public async Task Save();
+```
+### π Multi-Parameter Commands
+
+```csharp
// Generates multi asynchronous RelayCommand with async keyword with multiple parameters
[RelayCommand("MyTestLeft", ParameterValues = [LeftTopRightBottomType.Left, 1])]
[RelayCommand("MyTestTop", ParameterValues = [LeftTopRightBottomType.Top, 1])]
@@ -134,19 +154,133 @@ public Task TestDirection(LeftTopRightBottomType leftTopRightBottomType, int ste
public Task TestDirection(LeftTopRightBottomType leftTopRightBottomType, int steps)
```
-> Note: The `RelayCommand` attribute is used to generate a `RelayCommand` property for a method in a ViewModel class.
+**Note:**
+
+- The `RelayCommand` attribute generates an `IRelayCommand` pproperty linked to the annotated method.
+- `CanExecute` logic can be specified to control when the command is executable.
---
-## Example: TestViewModel
+## π― Real-World Use Cases
+
+### π Scenario 1: A User Profile Form
-This is a simple example of a `TestViewModel` class with a single `ObservableProperty`.
+```csharp
+public partial class UserProfileViewModel : ViewModelBase
+{
+ [ObservableProperty]
+ [NotifyPropertyChangedFor(nameof(FullName))]
+ private string firstName;
+
+ [ObservableProperty]
+ [NotifyPropertyChangedFor(nameof(FullName))]
+ private string lastName;
+
+ public string FullName => $"{FirstName} {LastName}";
+
+ [RelayCommand]
+ private void SaveProfile()
+ {
+ MessageBox.Show($"Profile Saved: {FullName}");
+ }
+}
+```
-### Human made Code
+#### π XAML Binding where Context is UserProfileViewModel
+
+```xml
+
+
+
+
+```
+
+#### π₯ Result for UserProfileViewModel binding
+
+The FullName property updates automatically when FirstName or LastName changes
+
+### π Scenario 2: Fetching Data from an API
+
+A ViewModel that fetches data asynchronously and enables/disables a button based on loading state.
```csharp
-namespace TestNamespace;
+public partial class DataViewModel : ViewModelBase
+{
+ [ObservableProperty]
+ private string? data;
+
+ [ObservableProperty]
+ private bool isLoading;
+
+ [RelayCommand(CanExecute = nameof(CanFetchData))]
+ private async Task FetchData(CancellationToken cancellationToken)
+ {
+ IsLoading = true;
+ await Task.Delay(2000, cancellationToken).ConfigureAwait(false); // Simulate API call
+ Data = "Fetched Data from API";
+ IsLoading = false;
+ }
+
+ private bool CanFetchData() => !IsLoading;
+}
+```
+
+#### π XAML Binding where Context is DataViewModel
+
+```xml
+
+
+
+```
+
+#### π₯ Result for DataViewModel binding
+
+The button is disabled while data is being fetched, preventing multiple API calls.
+
+## π Troubleshooting
+
+### π§ Properties Are Not Updating in UI?
+
+β Ensure your ViewModel inherits from ViewModelBase, which includes INotifyPropertyChanged.
+
+```csharp
+public partial class MyViewModel : ViewModelBase { }
+```
+
+### π§ Commands Are Not Executing?
+β Check if your command has a valid CanExecute method.
+
+```csharp
+[RelayCommand(CanExecute = nameof(CanSave))]
+private void Save() { /* ... */ }
+
+private bool CanSave() => !string.IsNullOrEmpty(Name);
+```
+
+---
+
+## π Summary
+
+- βοΈ **Use** `ObservableProperty` to eliminate manual property creation.
+- βοΈ **Use** `NotifyPropertyChangedFor` to notify dependent properties.
+- βοΈ **Use** `RelayCommand` for automatic command generation.
+- βοΈ **Use async commands** for better UI responsiveness.
+- βοΈ **Improve performance** by leveraging `CanExecute` for commands.
+
+### π Why Use Atc.Wpf Source Generators?
+
+- β **Reduces boilerplate** β Write less code, get more done.
+- β **Improves maintainability** β Focus on business logic instead of plumbing.
+- β **Enhances MVVM architecture** β Ensures best practices in WPF development.
+
+---
+
+## π Behind the scenes
+
+### π Human-Written Code - for simple example
+
+```csharp
public partial class TestViewModel : ViewModelBase
{
[ObservableProperty]
@@ -154,13 +288,9 @@ public partial class TestViewModel : ViewModelBase
}
```
-### Generated Code
+### βοΈ Auto-Generated Code - for simple example
```csharp
-#nullable enable
-
-namespace TestNamespace;
-
public partial class TestViewModel
{
public string Name
@@ -172,7 +302,7 @@ public partial class TestViewModel
{
return;
}
-
+
name = value;
RaisePropertyChanged(nameof(Name));
}
@@ -180,17 +310,9 @@ public partial class TestViewModel
}
```
----
-
-## Example: PersonViewModel
-
-This is a more complex example of a `PersonViewModel` class with multiple properties and commands.
-
-### Human made Code
+### π Human-Written Code - for advanced example
```csharp
-namespace TestNamespace;
-
public partial class PersonViewModel : ViewModelBase
{
[ObservableProperty]
@@ -211,7 +333,7 @@ public partial class PersonViewModel : ViewModelBase
[ObservableProperty]
private string? email;
- [ObservableProperty("TheProperty", nameof(FullName), nameof(Age))]
+ [ObservableProperty(nameof(TheProperty), nameof(FullName), nameof(Age))]
private string? myTestProperty;
public string FullName => $"{FirstName} {LastName}";
@@ -238,15 +360,12 @@ public partial class PersonViewModel : ViewModelBase
// TODO: Implement validation
return true;
}
+}
```
-### Generated Code
+### βοΈ Auto-Generated Code - for advanced example
```csharp
-#nullable enable
-
-namespace TestNamespace;
-
public partial class PersonViewModel
{
public IRelayCommand ShowDataCommand => new RelayCommand(ShowData);
@@ -335,4 +454,4 @@ public partial class PersonViewModel
}
}
}
-```
\ No newline at end of file
+```
diff --git a/sample/.editorconfig b/sample/.editorconfig
index f252e0e6..e3700d7c 100644
--- a/sample/.editorconfig
+++ b/sample/.editorconfig
@@ -57,4 +57,6 @@ dotnet_diagnostic.CA2227.severity = none # Collection properties should be rea
dotnet_diagnostic.SA1649.severity = none # File name should match - Recores
-dotnet_diagnostic.MA0048.severity = none # File name should match - Recores
\ No newline at end of file
+dotnet_diagnostic.MA0048.severity = none # File name should match - Recores
+
+dotnet_diagnostic.S3400.severity = none #
\ No newline at end of file
diff --git a/sample/Atc.Wpf.Sample/Atc.Wpf.Sample.csproj b/sample/Atc.Wpf.Sample/Atc.Wpf.Sample.csproj
index 7e2819f4..4f725ff3 100644
--- a/sample/Atc.Wpf.Sample/Atc.Wpf.Sample.csproj
+++ b/sample/Atc.Wpf.Sample/Atc.Wpf.Sample.csproj
@@ -6,6 +6,7 @@
truefalsetrue
+
@@ -60,9 +61,7 @@
-
+
diff --git a/sample/Atc.Wpf.Sample/GlobalUsings.cs b/sample/Atc.Wpf.Sample/GlobalUsings.cs
index ddb8bec1..9749a553 100644
--- a/sample/Atc.Wpf.Sample/GlobalUsings.cs
+++ b/sample/Atc.Wpf.Sample/GlobalUsings.cs
@@ -12,6 +12,7 @@
global using System.Text.Json;
global using System.Windows;
global using System.Windows.Controls;
+global using System.Windows.Data;
global using System.Windows.Input;
global using System.Windows.Media;
global using System.Windows.Media.Imaging;
@@ -42,7 +43,7 @@
global using Atc.Wpf.Serialization.JsonConverters;
global using Atc.Wpf.Theming.Helpers;
global using Atc.Wpf.Translation;
-
+global using Atc.Wpf.ValueConverters;
global using ControlzEx.Theming;
global using Microsoft.Extensions.Configuration;
global using Microsoft.Extensions.DependencyInjection;
diff --git a/sample/Atc.Wpf.Sample/MainWindow.xaml b/sample/Atc.Wpf.Sample/MainWindow.xaml
index f577b0b7..4570ad8a 100644
--- a/sample/Atc.Wpf.Sample/MainWindow.xaml
+++ b/sample/Atc.Wpf.Sample/MainWindow.xaml
@@ -3,6 +3,7 @@
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:atc="https://github.com/atc-net/atc-wpf/tree/main/schemas"
+ xmlns:atcTranslation="https://github.com/atc-net/atc-wpf/tree/main/schemas/translations"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:sample="clr-namespace:Atc.Wpf.Sample"
@@ -20,19 +21,43 @@
-
+
+
+
+
+
+
+
@@ -56,6 +81,13 @@
ScrollViewer.CanContentScroll="True"
SelectedItemChanged="TreeViewOnSelectionChanged" />
+
+
+
public partial class MainWindow
{
+ private IMainWindowViewModel GetViewModel() => (IMainWindowViewModel)DataContext!;
+
+ private readonly TreeView[] sampleTreeViews;
+
public MainWindow(IMainWindowViewModel viewModel)
{
InitializeComponent();
+
DataContext = viewModel;
Loaded += OnLoaded;
Closing += OnClosing;
KeyDown += OnKeyDown;
KeyUp += OnKeyUp;
+
+ sampleTreeViews =
+ [
+ StvSampleWpf,
+ StvSampleWpfControls,
+ StvSamplesWpfSourceGeneratorsTreeView,
+ StvSampleWpfFontIcons,
+ StvSampleWpfTheming,
+ ];
}
private void OnLoaded(
object sender,
RoutedEventArgs e)
{
- var vm = DataContext as IMainWindowViewModel;
- vm!.OnLoaded(this, e);
-
+ GetViewModel().OnLoaded(this, e);
Keyboard.Focus(TbSampleFilter);
}
private void OnClosing(
object? sender,
CancelEventArgs e)
- {
- var vm = DataContext as IMainWindowViewModel;
- vm!.OnClosing(this, e);
- }
+ => GetViewModel().OnClosing(this, e);
private void OnKeyDown(
object sender,
KeyEventArgs e)
- {
- var vm = DataContext as IMainWindowViewModel;
- vm!.OnKeyDown(this, e);
- }
+ => GetViewModel().OnKeyDown(this, e);
private void OnKeyUp(
object sender,
KeyEventArgs e)
- {
- var vm = DataContext as IMainWindowViewModel;
- vm!.OnKeyUp(this, e);
- }
+ => GetViewModel().OnKeyUp(this, e);
private void TreeViewOnSelectionChanged(
object sender,
RoutedPropertyChangedEventArgs
-
+
diff --git a/src/Atc.Wpf.Controls.Sample/SampleViewerViewModel.cs b/src/Atc.Wpf.Controls.Sample/SampleViewerViewModel.cs
index 1a757b19..efd41b62 100644
--- a/src/Atc.Wpf.Controls.Sample/SampleViewerViewModel.cs
+++ b/src/Atc.Wpf.Controls.Sample/SampleViewerViewModel.cs
@@ -8,20 +8,26 @@ public SampleViewerViewModel()
Messenger.Default.Register(this, SampleItemMessageHandler);
}
- private FileInfo[]? readmeMarkdownFiles;
+ private FileInfo[]? markdownDocumentsFiles;
private int tabSelectedIndex;
private string? header;
private UserControl? sampleContent;
private string? xamlCode;
private string? codeBehindCode;
private string? viewModelCode;
- private string? readmeMarkdown;
+ private string? markdownDocument;
+ private bool startOnMarkdownDocument;
public int TabSelectedIndex
{
get => tabSelectedIndex;
set
{
+ if (tabSelectedIndex == value)
+ {
+ return;
+ }
+
tabSelectedIndex = value;
RaisePropertyChanged();
}
@@ -35,13 +41,18 @@ public int TabSelectedIndex
public bool HasViewModelCode => ViewModelCode is not null;
- public bool HasReadmeMarkdown => ReadmeMarkdown is not null;
+ public bool HasMarkdownDocument => MarkdownDocument is not null;
public string? Header
{
get => header;
set
{
+ if (header == value)
+ {
+ return;
+ }
+
header = value;
RaisePropertyChanged();
}
@@ -52,6 +63,11 @@ public UserControl? SampleContent
get => sampleContent;
set
{
+ if (sampleContent == value)
+ {
+ return;
+ }
+
sampleContent = value;
RaisePropertyChanged();
RaisePropertyChanged(nameof(HasSampleContent));
@@ -63,6 +79,11 @@ public string? XamlCode
get => xamlCode;
set
{
+ if (xamlCode == value)
+ {
+ return;
+ }
+
xamlCode = value;
RaisePropertyChanged();
RaisePropertyChanged(nameof(HasXamlCode));
@@ -74,6 +95,11 @@ public string? CodeBehindCode
get => codeBehindCode;
set
{
+ if (codeBehindCode == value)
+ {
+ return;
+ }
+
codeBehindCode = value;
RaisePropertyChanged();
RaisePropertyChanged(nameof(HasCodeBehindCode));
@@ -85,20 +111,45 @@ public string? ViewModelCode
get => viewModelCode;
set
{
+ if (viewModelCode == value)
+ {
+ return;
+ }
+
viewModelCode = value;
RaisePropertyChanged();
RaisePropertyChanged(nameof(HasViewModelCode));
}
}
- public string? ReadmeMarkdown
+ public string? MarkdownDocument
+ {
+ get => markdownDocument;
+ set
+ {
+ if (markdownDocument == value)
+ {
+ return;
+ }
+
+ markdownDocument = value;
+ RaisePropertyChanged();
+ RaisePropertyChanged(nameof(HasMarkdownDocument));
+ }
+ }
+
+ public bool StartOnMarkdownDocument
{
- get => readmeMarkdown;
+ get => startOnMarkdownDocument;
set
{
- readmeMarkdown = value;
+ if (startOnMarkdownDocument == value)
+ {
+ return;
+ }
+
+ startOnMarkdownDocument = value;
RaisePropertyChanged();
- RaisePropertyChanged(nameof(HasReadmeMarkdown));
}
}
@@ -180,59 +231,83 @@ private void SetSelectedViewData(
ViewModelCode = ReadFileText(Path.Combine(sampleLocation.FullName, classViewModelName + ".cs"));
}
- LoadAndRenderReadmeMarkdownIfPossible(classViewName);
+ LoadAndRenderMarkdownDocumentIfPossible(sampleLocation, classViewName);
}
- private void LoadAndRenderReadmeMarkdownIfPossible(
+ [SuppressMessage("Design", "MA0051:Method is too long", Justification = "OK.")]
+ private void LoadAndRenderMarkdownDocumentIfPossible(
+ DirectoryInfo sampleLocation,
string classViewName)
{
- ReadmeMarkdown = null;
- if (readmeMarkdownFiles is null)
+ MarkdownDocument = null;
+ StartOnMarkdownDocument = false;
+ if (markdownDocumentsFiles is null)
{
PrepareReadmeReferences();
}
- if (readmeMarkdownFiles is null)
+ if (markdownDocumentsFiles is null)
{
return;
}
- var readmeMarkdownFile = readmeMarkdownFiles.SingleOrDefault(x => x.Name.StartsWith(classViewName + "_Readme", StringComparison.OrdinalIgnoreCase));
- if (readmeMarkdownFile is null &&
- classViewName.EndsWith("View", StringComparison.Ordinal))
+ var className = classViewName.EndsWith(classViewName, StringComparison.Ordinal)
+ ? classViewName[..^4]
+ : classViewName;
+
+ var docSection = sampleLocation.Name.Replace("SamplesWpf", string.Empty, StringComparison.Ordinal);
+
+ var markdownFile = FindMarkdownFile(Path.Combine("docs", docSection, className)) ??
+ FindMarkdownFile(className + "_Readme");
+
+ if (markdownFile is null)
{
- var className = classViewName.Replace("View", string.Empty, StringComparison.Ordinal);
- readmeMarkdownFile = readmeMarkdownFiles.SingleOrDefault(x => x.Name.StartsWith(className + "_Readme", StringComparison.OrdinalIgnoreCase));
+ var type = FindCustomTypeByName(classViewName) ??
+ FindCustomTypeByName(className);
- if (readmeMarkdownFile is null)
+ if (type?.FullName is not null)
{
- var type = FindCustomTypeByName(classViewName) ??
- FindCustomTypeByName(className);
-
- if (type?.FullName is not null)
+ var sa = type.FullName.Split('.', StringSplitOptions.RemoveEmptyEntries);
+ if (sa.Length > 2)
{
- var sa = type.FullName.Split('.', StringSplitOptions.RemoveEmptyEntries);
- if (sa.Length > 2)
+ var classFolder = sa[^2];
+ markdownFile = FindMarkdownFile(Path.Combine(classFolder, "@Readme"));
+ if (markdownFile is null && classFolder.EndsWith('s'))
+ {
+ classFolder = classFolder[..^1];
+ markdownFile = FindMarkdownFile(Path.Combine(classFolder, "@Readme"));
+ }
+
+ if (markdownFile is not null && classFolder == "ValueConverters")
{
- var classFolder = sa[^2];
- readmeMarkdownFile = readmeMarkdownFiles.SingleOrDefault(x => x.FullName.EndsWith($"\\{classFolder}\\@Readme.md", StringComparison.OrdinalIgnoreCase));
- if (readmeMarkdownFile is null && classFolder.EndsWith('s'))
- {
- classFolder = classFolder[..^1];
- readmeMarkdownFile = readmeMarkdownFiles.SingleOrDefault(x => x.FullName.EndsWith($"\\{classFolder}\\@Readme.md", StringComparison.OrdinalIgnoreCase));
- }
+ StartOnMarkdownDocument = true;
}
}
}
+
+ markdownFile ??= FindMarkdownFile(classViewName + "_Readme");
+ }
+ else
+ {
+ StartOnMarkdownDocument = true;
}
- if (readmeMarkdownFile is null)
+ if (markdownFile is null)
{
return;
}
- var readmeMarkdownTxt = FileHelper.ReadAllText(readmeMarkdownFile);
- ReadmeMarkdown = readmeMarkdownTxt;
+ MarkdownDocument = FileHelper.ReadAllText(markdownFile);
+ }
+
+ private FileInfo? FindMarkdownFile(string endPath)
+ {
+ if (!endPath.EndsWith(".md", StringComparison.Ordinal))
+ {
+ endPath += ".md";
+ }
+
+ return markdownDocumentsFiles!.FirstOrDefault(x => x.FullName.EndsWith(endPath, StringComparison.OrdinalIgnoreCase));
}
private static Type? FindCustomTypeByName(string className)
@@ -282,9 +357,7 @@ public void PrepareReadmeReferences()
{
var basePath = GetBasePath();
- readmeMarkdownFiles = FileHelper.GetFiles(basePath, "*.md")
- .Where(x => x.Name.Contains("readme", StringComparison.OrdinalIgnoreCase))
- .ToArray();
+ markdownDocumentsFiles = FileHelper.GetFiles(basePath, "*.md").ToArray();
}
private static Type? GetTypeBySamplePath(
diff --git a/src/Atc.Wpf.Controls.Sample/Styles/ReadmeMarkdownStyle.xaml b/src/Atc.Wpf.Controls.Sample/Styles/MarkdownDocumentStyle.xaml
similarity index 99%
rename from src/Atc.Wpf.Controls.Sample/Styles/ReadmeMarkdownStyle.xaml
rename to src/Atc.Wpf.Controls.Sample/Styles/MarkdownDocumentStyle.xaml
index dd98bdcb..90189a2d 100644
--- a/src/Atc.Wpf.Controls.Sample/Styles/ReadmeMarkdownStyle.xaml
+++ b/src/Atc.Wpf.Controls.Sample/Styles/MarkdownDocumentStyle.xaml
@@ -1,6 +1,6 @@
-