{"id":13629455,"url":"https://github.com/IGood/boilerplatezero","last_synced_at":"2025-04-17T09:33:55.746Z","repository":{"id":38368112,"uuid":"315205113","full_name":"IGood/boilerplatezero","owner":"IGood","description":"boilerplatezero is a collection of C# source generators","archived":false,"fork":false,"pushed_at":"2023-11-19T21:53:42.000Z","size":132,"stargazers_count":37,"open_issues_count":0,"forks_count":4,"subscribers_count":2,"default_branch":"main","last_synced_at":"2024-10-13T03:40:15.566Z","etag":null,"topics":["csharp","csharp-sourcegenerator","dependency-property","routed-event","source-generators","sourcegenerator","wpf"],"latest_commit_sha":null,"homepage":"","language":"C#","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"mit","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/IGood.png","metadata":{"files":{"readme":"README.md","changelog":null,"contributing":null,"funding":".github/FUNDING.yml","license":"LICENSE","code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null,"governance":null,"roadmap":null,"authors":null,"dei":null,"publiccode":null,"codemeta":null},"funding":{"custom":["paypal.me/IanGoodies"]}},"created_at":"2020-11-23T05:00:50.000Z","updated_at":"2024-08-16T15:45:23.000Z","dependencies_parsed_at":"2023-11-19T22:40:04.507Z","dependency_job_id":null,"html_url":"https://github.com/IGood/boilerplatezero","commit_stats":{"total_commits":51,"total_committers":1,"mean_commits":51.0,"dds":0.0,"last_synced_commit":"a7d17c8419821b98b4f3779bfa1999b129277b9d"},"previous_names":[],"tags_count":18,"template":false,"template_full_name":null,"repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/IGood%2Fboilerplatezero","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/IGood%2Fboilerplatezero/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/IGood%2Fboilerplatezero/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/IGood%2Fboilerplatezero/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/IGood","download_url":"https://codeload.github.com/IGood/boilerplatezero/tar.gz/refs/heads/main","host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":223751133,"owners_count":17196577,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2022-07-04T15:15:14.044Z","host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":["csharp","csharp-sourcegenerator","dependency-property","routed-event","source-generators","sourcegenerator","wpf"],"created_at":"2024-08-01T22:01:11.195Z","updated_at":"2024-11-08T20:30:58.428Z","avatar_url":"https://github.com/IGood.png","language":"C#","readme":"# ![Logo](product/bpz%20logo%20dark.png) boilerplatezero (BPZ)\n\n[![NuGet version (boilerplatezero)](https://img.shields.io/nuget/v/boilerplatezero.svg?style=flat-square)](https://www.nuget.org/packages/boilerplatezero/)\n[![MIT License](https://img.shields.io/badge/license-MIT-green.svg?style=flat-square)](/LICENSE)\n\nboilerplatezero (BPZ) is a collection of C# source generators that simplify the code required for common C# patterns.\n\n🔗 Jump to...\n- [WPF Dependency Property Generator](#wpf-dependency-property-generator)\n- [WPF Routed Event Generator](#wpf-routed-event-generator)\n- [🐛 Known Issues List](#-known-issues)\n\n----\n\n## WPF Dependency Property Generator\n\nDependency properties in WPF are great! However, they do require quite a bit of ceremony in order to define one.\u003cbr\u003e\nLuckily, dependency properties (and attached properties) always follow the same pattern when implemented.\u003cbr\u003e\nAs such, this kind of boilerplate code is the perfect candidate for a source generator.\n\nThe dependency property generator in BPZ works by identifying `DependencyProperty` and `DependencyPropertyKey` fields that are initialized with calls to appropriately-named `Gen` or `GenAttached` methods.\u003cbr\u003e\nWhen this happens, the source generator adds private static classes as nested types inside your class \u0026amp; implements the dependency property for you.\u003cbr\u003e\nAdditionally...\n- If an appropriate property-changed handler method is found, then it will be used during registration.\n- If an appropriate coercion method is found, then it will be used during registration.\n- If an appropriate validation method is found, then it will be used during registration.\n\n🔗 Jump to...\n- [👩‍💻 Write This, Not That](#-write-this-not-that-property-examples)\n- [🤖 Generated Code Example](#-generated-code)\n- [✨ Features List](#-features)\n\n----\n\n### 👩‍💻 Write This, Not That: Property Examples\n\n#### 🔧 Dependency Property\n\n```csharp\n// Write this (using BPZ):\nprivate static readonly DependencyPropertyKey FooPropertyKey = Gen.Foo\u003cstring\u003e();\n\n// Not that (idiomatic implementation):\nprivate static readonly DependencyPropertyKey FooPropertyKey = DependencyProperty.RegisterReadOnly(nameof(Foo), typeof(string), typeof(MyClass), null);\npublic static readonly DependencyProperty FooProperty = FooPropertyKey.DependencyProperty;\npublic string Foo\n{\n    get =\u003e (string)this.GetValue(FooProperty);\n    private set =\u003e this.SetValue(FooPropertyKey, value);\n}\n```\n\n\u003cdetails\u003e\u003csummary\u003eA more complex example...\u003c/summary\u003e\n\n```csharp\n// Write this (using BPZ):\npublic static readonly DependencyProperty TextProperty = Gen.Text(\"\", FrameworkPropertyMetadataOptions.BindsTwoWayByDefault);\nprotected virtual void OnTextChanged(string oldText, string newText) { ... }\n\n// Not that (idiomatic implementation):\npublic static readonly DependencyProperty TextProperty = DependencyProperty.Register(\n    nameof(Text), typeof(string), typeof(MyClass),\n    new FrameworkPropertyMetadata(\"\", FrameworkPropertyMetadataOptions.BindsTwoWayByDefault, TextPropertyChanged));\npublic string Text\n{\n    get =\u003e (string)this.GetValue(TextProperty);\n    set =\u003e this.SetValue(TextProperty, value);\n}\nprivate static void TextPropertyChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)\n{\n    ((MyClass)d).OnTextChanged((string)e.OldValue, (string)e.NewValue);\n}\nprotected virtual void OnTextChanged(string oldText, string newText) { ... }\n```\n\u003c/details\u003e\n\n#### 🔧 Attached Property\n\n```csharp\n// Write this (using BPZ):\nprivate static readonly DependencyPropertyKey BarPropertyKey = GenAttached.Bar\u003cstring\u003e();\n\n// Not that (idiomatic implementation):\nprivate static readonly DependencyPropertyKey BarPropertyKey = DependencyProperty.RegisterAttachedReadOnly(\"Bar\", typeof(string), typeof(MyClass), null);\npublic static readonly DependencyProperty BarProperty = BarPropertyKey.DependencyProperty;\npublic static string GetBar(DependencyObject d) =\u003e (string)d.GetValue(BarProperty);\nprivate static void SetBar(DependencyObject d, string value) =\u003e d.SetValue(BarPropertyKey, value);\n```\n\n----\n\n### 🤖 Generated Code\n\nHere's an example of hand-written code \u0026amp; the corresponding generated code.\u003cbr\u003e\nNote that the default value for the dependency property is specified in the user's code \u0026amp; included in the property metadata along with the appropriate property-changed handler.\u003cbr\u003e\nThe documentation comment on `IdPropertyKey` is copied to the generated `Id` property.\n\n```csharp\n// 👩‍💻 Widget.cs\nnamespace Goodies\n{\n    public partial class Widget : DependencyObject\n    {\n        /// \u003cSummary\u003eGets the ID of this instance.\u003c/Summary\u003e\n        protected static readonly DependencyPropertyKey IdPropertyKey = Gen.Id(\"\u003cunset\u003e\");\n\n        private static void IdPropertyChanged(Widget self, DependencyPropertyChangedEventArgs e)\n        {\n            // This method will be used as the property-changed callback during registration!\n            // It was selected because...\n            // - its name contains the property name, \"Id\", \u0026 ends with \"Changed\"\n            // - it is `static` with return type `void`\n            // - the type of parameter 0 is compatible with the owner type\n            // - the type of parameter 1 is `DependencyPropertyChangedEventArgs`\n        }\n    }\n}\n\n// 🤖 Widget.g.cs (actual name may vary)\nnamespace Goodies\n{\n    partial class Widget\n    {\n        public static readonly DependencyProperty IdProperty = IdPropertyKey.DependencyProperty;\n\n        /// \u003cSummary\u003eGets the ID of this instance.\u003c/Summary\u003e\n        public string Id\n        {\n            get =\u003e (string)this.GetValue(IdProperty);\n            protected set =\u003e this.SetValue(IdPropertyKey, value);\n        }\n\n        private static partial class Gen\n        {\n            public static DependencyPropertyKey Id\u003c__T\u003e(__T defaultValue)\n            {\n                PropertyMetadata metadata = new PropertyMetadata(\n                    defaultValue,\n                    (d, e) =\u003e IdPropertyChanged((Goodies.Widget)d, e),\n                    null);\n                return DependencyProperty.RegisterReadOnly(\"Id\", typeof(__T), typeof(Widget), metadata);\n            }\n        }\n    }\n}\n```\n\n----\n\n### ✨ Features\n\n- generates instance properties for dependency properties\n- generates static methods for attached properties\n- optional default values\n- \u003cdetails\u003e\u003csummary\u003eoptional \u003ccode\u003eFrameworkPropertyMetadataOptions\u003c/code\u003e flags\u003c/summary\u003e\n  A \u003ccode\u003eflags\u003c/code\u003e argument may be specified for the property's \u003ccode\u003eFrameworkPropertyMetadata\u003c/code\u003e.\n\n  ```csharp\n  // 👩‍💻 user\n  public static readonly DependencyProperty TextProperty = Gen.Text\u003cstring?\u003e(FrameworkPropertyMetadataOptions.BindsTwoWayByDefault);\n  public static readonly DependencyProperty ErrorBrushProperty = GenAttached.ErrorBrush(Brushes.Red, FrameworkPropertyMetadataOptions.Inherits);\n  ```\n  \u003c/details\u003e\n- \u003cdetails\u003e\u003csummary\u003edetects suitable property-changed handlers\u003c/summary\u003e\n  There are 4 options for property-changed handlers.\n  If multiple candidates are found, then they are prioritized (from highest to lowest) as shown below (static methods, instance methods, routed events).\n\n  ```csharp\n  // 👩‍💻 user\n  public static readonly DependencyProperty SeasonProperty = Gen.Season(\"autumn\");\n\n  // Option 1 - static method, named \"*Season*Changed\"\n  private static void SeasonPropertyChanged(Widget self, DependencyPropertyChangedEventArgs e)\n  {\n      // This method can be used as the property-changed callback during registration!\n      // It is a candidate because...\n      // - its name contains the property name, \"Season\", \u0026 ends with \"Changed\"\n      // - it is `static`\n      // - return type is `void`\n      // - type of parameter 0 is compatible with the owner type\n      // - type of parameter 1 is `DependencyPropertyChangedEventArgs`\n  }\n\n  // Option 2 - instance method, named \"[On]SeasonChanged\", 2 parameters\n  protected virtual void OnSeasonChanged(string oldSeason, string newSeason)\n  {\n      // This method can be used as the property-changed callback during registration!\n      // It is a candidate because...\n      // - its name is \"OnSeasonChanged\" (\"SeasonChanged\" is also acceptable)\n      // - it is not `static`\n      // - return type is `void`\n      // - types of parameter 0 \u0026 1 match the property type\n      // - names of parameter 0 \u0026 1 start with \"old\" \u0026 \"new\" (respectively)\n  }\n\n  // Option 3 - instance method, named \"[On]SeasonChanged\", 1 parameter\n  protected virtual void OnSeasonChanged(DependencyPropertyChangedEventArgs e)\n  {\n      // This method can be used as the property-changed callback during registration!\n      // It is a candidate because...\n      // - its name is \"OnSeasonChanged\" (\"SeasonChanged\" is also acceptable)\n      // - it is not `static`\n      // - return type is `void`\n      // - type of parameter 0 is `DependencyPropertyChangedEventArgs`\n  }\n\n  // Option 4 - routed event, named \"SeasonChangedEvent\"\n  public static readonly RoutedEvent SeasonChangedEvent = Gen.SeasonChanged\u003cstring\u003e();\n      // Invoking this event can be used as the property-changed callback during registration!\n      // It is a candidate because...\n      // - it is a `static readonly RoutedEvent`\n      // - it's name is \"SeasonChangedEvent\"\n  ```\n  \u003c/details\u003e\n- \u003cdetails\u003e\u003csummary\u003edetects suitable value coercion handlers\u003c/summary\u003e\n\n  ```csharp\n  // 👩‍💻 user\n  public static readonly DependencyProperty AgeProperty = Gen.Age(0);\n  private static int CoerceAge(Widget self, int baseValue)\n  {\n      // This method will be used as the value coercion method during registration!\n      // It was selected because...\n      // - its name is \"CoerceAge\" (i.e. \"Coerce\" + the property name)\n      // - it is `static`\n      // - return type is `object` or matches the property type\n      // - type of parameter 0 is compatible with the owner type\n      // - type of parameter 1 is `object` or matches the property type\n      return (baseValue \u003e= 0) ? baseValue : 0;\n  }\n  ```\n  \u003c/details\u003e\n- \u003cdetails\u003e\u003csummary\u003edetects suitable value validation handlers\u003c/summary\u003e\n\n  ```csharp\n  // 👩‍💻 user\n  public static readonly DependencyProperty WrapModeProperty = Gen.WrapMode(TextWrapping.NoWrap);\n  private static bool IsValidWrapMode(TextWrapping value)\n  {\n      // This method will be used as the value validation method during registration!\n      // It was selected because...\n      // - its name is \"IsValidWrapMode\" (i.e. \"IsValid\" + the property name)\n      // - it is `static`\n      // - return type is `bool`\n      // - type of parameter 0 is `object` or matches the property type\n      return Enum.IsDefined(value);\n  }\n  ```\n  \u003c/details\u003e\n- supports generic owner types\n- \u003cdetails\u003e\u003csummary\u003esupports nullable types\u003c/summary\u003e\n\n  ```csharp\n  public static readonly DependencyProperty IsCheckedProperty = Gen.IsChecked\u003cbool?\u003e(false);\n  public static readonly DependencyProperty NameProperty = Gen.Name\u003cstring?\u003e();\n  ```\n  \u003c/details\u003e\n- access modifiers are preserved on generated members\n- documentation is preserved (for dependency properties) on generated members\n- \u003cdetails\u003e\u003csummary\u003egenerates dependency property fields for read-only properties (if necessary)\u003c/summary\u003e\n\n  ```csharp\n  // 👩‍💻 user\n  // Instance field `FooProperty` is defined, so it will not be generated.\n  // Access modifiers for generated get/set of the `Foo` instance property will match the property \u0026 key.\n  private static readonly DependencyPropertyKey FooPropertyKey = GenAttached.Foo(3.14f);\n  protected static readonly DependencyProperty FooProperty = FooPropertyKey.DependencyProperty;\n\n  // Instance field `BarProperty` is not defined, so it will be generated.\n  private static readonly DependencyPropertyKey BarPropertyKey = Gen.Bar\u003cGuid\u003e();\n\n  // 🤖 generated\n  protected float Foo\n  {\n      get =\u003e (float)this.GetValue(FooProperty);\n      private set =\u003e this.SetValue(FooPropertyKey, value);\n  }\n\n  public static readonly DependencyProperty BarProperty = BarPropertyKey.DependencyProperty;\n  public System.Guid Bar\n  {\n      get =\u003e (System.Guid)this.GetValue(BarProperty);\n      private set =\u003e this.SetValue(BarPropertyKey, value);\n  }\n  ```\n  \u003c/details\u003e\n- \u003cdetails\u003e\u003csummary\u003eattached properties may constrain their target type by specifying a generic type argument on \u003ccode\u003eGenAttached\u003c/code\u003e\u003c/summary\u003e\n\n  ```csharp\n  // 👩‍💻 user\n  // Attached property `Standard` may be used with any dependency object.\n  public static readonly DependencyProperty StandardProperty = GenAttached.Standard(\"🍕\");\n\n  // Attached property `IsFancy` may only be used with objects of type \u003csee cref=\"Widget\"/\u003e.\n  public static readonly DependencyProperty IsFancyProperty = GenAttached\u003cGoodies.Widget\u003e.IsFancy(true);\n\n  // 🤖 generated\n  public static string GetStandard(DependencyObject d) =\u003e (string)d.GetValue(StandardProperty);\n  public static void SetStandard(DependencyObject d, string value) =\u003e d.SetValue(StandardProperty, value);\n\n  public static bool GetIsFancy(Goodies.Widget d) =\u003e (bool)d.GetValue(IsFancyProperty);\n  public static void SetIsFancy(Goodies.Widget d, bool value) =\u003e d.SetValue(IsFancyProperty, value);\n  ```\n  \u003c/details\u003e\n\n----\n\n## WPF Routed Event Generator\n\nSimilar to dependency properties, routed events always use the same pattern when implemented correctly.\n\nThe routed event generator in BPZ works by identifying `RoutedEvent` fields that are initialized with calls to appropriately-named `Gen` or `GenAttached` methods.\u003cbr\u003e\nWhen this happens, the source generator adds private static classes as nested types inside your class \u0026amp; implements the routed event for you.\n\n🔗 Jump to...\n- [👩‍💻 Write This, Not That](#-write-this-not-that-event-examples)\n- [✨ Features List](#-features-1)\n\n----\n\n### 👩‍💻 Write This, Not That: Event Examples\n\n#### ⚡ Routed Event\n\n```csharp\n// Write this (using BPZ):\npublic static readonly RoutedEvent FooChangedEvent = Gen.FooChanged\u003cstring\u003e();\n\n// Not that (idiomatic implementation):\npublic static readonly RoutedEvent FooChangedEvent = EventManager.RegisterRoutedEvent(nameof(FooChanged), RoutingStrategy.Direct, typeof(RoutedPropertyChangedEventHandler\u003cstring\u003e), typeof(MyClass));\npublic event RoutedPropertyChangedEventHandler\u003cstring\u003e FooChanged\n{\n    add =\u003e this.AddHandler(FooChangedEvent, value);\n    remove =\u003e this.RemoveHandler(FooChangedEvent, value);\n}\n```\n\n#### ⚡ Attached Event\n\n```csharp\n// Write this (using BPZ):\npublic static readonly RoutedEvent ThingUpdatedEvent = GenAttached.ThingUpdatedChanged(RoutingStrategy.Bubble);\n\n// Not that (idiomatic implementation):\npublic static readonly RoutedEvent ThingUpdatedEvent = EventManager.RegisterRoutedEvent(nameof(BarChanged), RoutingStrategy.Bubble, typeof(RoutedEventHandler), typeof(MyClass));\npublic static void AddThingUpdatedHandler(DependencyObject d, RoutedEventHandler handler) =\u003e (d as UIElement)?.AddHandler(BarChangedEvent, handler);\npublic static void RemoveThingUpdatedHandler(DependencyObject d, RoutedEventHandler handler) =\u003e (d as UIElement)?.RemoveHandler(BarChangedEvent, handler);\n```\n\n----\n\n### ✨ Features\n\n- generates instance events for routed events\n- generates static methods for attached events\n- handler type may be specified via generic type argument on the generator method (e.g. `Gen.FooChanged\u003cRoutedPropertyChangedEventHandler\u003cstring\u003e()`)\n- handler type is optional (default is `RoutedEventHandler`)\n- handler types that are **not** delegates (e.g. `Gen.FooChanged\u003cstring\u003e()`) are treated as `RoutedPropertyChangedEventHandler\u003cT\u003e` with the specified type\n- routing strategy may be specified via generator method argument (e.g. `Gen.ThingUpdated(RoutingStrategy.Bubble)`)\n- routing strategy is optional (default is `RoutingStrategy.Direct`)\n- supports generic owner types\n- access modifiers are preserved on generated members\n- documentation is preserved (for routed events) on generated members\n- attached events may constrain their target type by specifying a generic type argument on `GenAttached`\n\n----\n\n### 🐛 Known Issues\n\nrelated: Source Generator support for WPF project blocked? [#3404](https://github.com/dotnet/wpf/issues/3404)\n- fix / workaround: use this in project file\n  ```xml\n  \u003cIncludePackageReferencesDuringMarkupCompilation\u003etrue\u003c/IncludePackageReferencesDuringMarkupCompilation\u003e\n  ```\n","funding_links":["paypal.me/IanGoodies"],"categories":["Contributors Welcome for those","Source Generators"],"sub_categories":["1. [ThisAssembly](https://ignatandrei.github.io/RSCG_Examples/v2/docs/ThisAssembly) , in the [EnhancementProject](https://ignatandrei.github.io/RSCG_Examples/v2/docs/rscg-examples#enhancementproject) category","XAML / WPF / Avalonia"],"project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2FIGood%2Fboilerplatezero","html_url":"https://awesome.ecosyste.ms/projects/github.com%2FIGood%2Fboilerplatezero","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2FIGood%2Fboilerplatezero/lists"}