CoreOne.Drawing 1.1.0.1

dotnet add package CoreOne.Drawing --version 1.1.0.1
                    
NuGet\Install-Package CoreOne.Drawing -Version 1.1.0.1
                    
This command is intended to be used within the Package Manager Console in Visual Studio, as it uses the NuGet module's version of Install-Package.
<PackageReference Include="CoreOne.Drawing" Version="1.1.0.1" />
                    
For projects that support PackageReference, copy this XML node into the project file to reference the package.
<PackageVersion Include="CoreOne.Drawing" Version="1.1.0.1" />
                    
Directory.Packages.props
<PackageReference Include="CoreOne.Drawing" />
                    
Project file
For projects that support Central Package Management (CPM), copy this XML node into the solution Directory.Packages.props file to version the package.
paket add CoreOne.Drawing --version 1.1.0.1
                    
#r "nuget: CoreOne.Drawing, 1.1.0.1"
                    
#r directive can be used in F# Interactive and Polyglot Notebooks. Copy this into the interactive tool or source code of the script to reference the package.
#:package CoreOne.Drawing@1.1.0.1
                    
#:package directive can be used in C# file-based apps starting in .NET 10 preview 4. Copy this into a .cs file before any lines of code to reference the package.
#addin nuget:?package=CoreOne.Drawing&version=1.1.0.1
                    
Install as a Cake Addin
#tool nuget:?package=CoreOne.Drawing&version=1.1.0.1
                    
Install as a Cake Tool

CoreOne.Winforms

A modern, feature-rich WinForms library that provides dynamic model-based form generation with automatic two-way data binding, extensible handler pipeline, themed controls, and smooth animations. Built for .NET 9.0 with dependency injection support.

NuGet License: MIT

🚀 Features

Dynamic Form Generation

  • ModelControl - Automatically generates form fields from your model properties
  • Two-way data binding - Changes in controls update the model, and vice versa
  • Reactive updates - Built on reactive patterns with Subject<T> for change notifications
  • Dirty tracking - Know when data has been modified with IsDirty flag

Smart Grid Layout

  • 6-column Bootstrap-style grid - Responsive layout system
  • [GridColumn] attribute - Control field width (1-6 columns)
  • [Group] attribute - Group related properties in labeled GroupBox containers
  • Auto-sizing - Calculates ideal form dimensions
  • Labels above controls - Clean, modern layout
  • Attribute-based providers - [DropdownSource(typeof(Provider))] attribute
  • Dependency tracking - [WatchProperties] for cascading dropdowns
  • Sync & Async providers - Support both IDropdownSourceProviderSync and IDropdownSourceProviderAsync
  • Auto-refresh - Dependent dropdowns update automatically when watched properties change

Extensible Handler Pipeline

  • Plug-and-play architecture - Register custom handlers for reactive behaviors
  • Built-in handlers - EnabledWhen, Compute, and Dropdown handlers included
  • IWatchFactory interface - Create custom handlers that automatically integrate
  • Priority-based execution - Control handler execution order
  • Attribute-driven - Handlers activate based on property attributes

Themed Controls

  • OButton - Modern button with hover/press states
  • AnimatedPanel - Smooth transitions and animations
  • LoadingCircle - Loading indicator control
  • RatingControl - Star rating input
  • BaseView - Base control for creating custom views
  • OneForm - Enhanced form with async support and loading indication

Advanced Features

  • Computed Properties - [Compute("MethodName")] executes methods to calculate values
  • Conditional Enabling - [EnabledWhen] for dynamic control state
  • Conditional Visibility - [VisibleWhen] for dynamic control visibility
  • Conditional Clearing - [ClearWhen] to clear values based on conditions
  • Property Watching - [WatchProperty] and [WatchProperties] for reactive monitoring
  • File Selection - [File(filter, multiselect)] for file/folder pickers
  • Validation Support - Built-in validation with error providers
  • Change Management - RejectChanges() and AcceptChanges() for undo/redo
  • Dependency Injection - First-class DI support with IServiceProvider
  • SOLID Architecture - Clean codebase following SOLID & DRY principles

📦 Installation

Install via NuGet Package Manager:

dotnet add package CoreOne.Winforms

Or via Package Manager Console:

Install-Package CoreOne.Winforms

🎯 Quick Start

1. Define Your Model

using CoreOne.Winforms.Attributes;

public class Customer
{
    [Group("Personal Information")]
    [GridColumn(GridColumnSpan.Half)]  // Takes 3 columns (half of 6)
    public string FirstName { get; set; }
    
    [Group("Personal Information")]
    [GridColumn(GridColumnSpan.Half)]
    public string LastName { get; set; }
    
    [Group("Personal Information")]
    public DateTime BirthDate { get; set; }
    
    [Group("Contact Details")]
    [GridColumn(GridColumnSpan.Full)]  // Takes all 6 columns
    public string Email { get; set; }
    
    [Group("Location")]
    [DropdownSource(typeof(CountryProvider))]
    [GridColumn(GridColumnSpan.Half)]
    public string Country { get; set; }
    
    [Group("Location")]
    [DropdownSource(typeof(StateProvider))]
    [WatchProperty(nameof(Country))]  // Refreshes when Country changes
    [GridColumn(GridColumnSpan.Half)]
    public string State { get; set; }
    
    [Group("Status")]
    public bool IsActive { get; set; }
    
    [Group("Status")]
    [EnabledWhen(nameof(IsActive), true)]  // Only enabled when IsActive is true
    [ClearWhen(nameof(IsActive), false)]  // Clears value when IsActive becomes false
    public string ActiveNotes { get; set; }
    
    [Group("Ratings")]
    [Rating(5)]  // Display as 5-star rating control
    public int CustomerRating { get; set; }
    
    [Group("Ratings")]
    [Compute("CalculateTotalScore")]
    [WatchProperties(nameof(CustomerRating), nameof(IsActive))]
    public decimal TotalScore { get; set; }
    
    [Group("Attachments")]
    [File("All Files (*.*)|*.*")]  // File picker control
    [VisibleWhen(nameof(IsActive), true)]  // Only visible when IsActive is true
    public string? AttachmentPath { get; set; }
    
    [Ignore]  // Won't generate a control
    public int InternalId { get; set; }
    
    // Computed property method
    public decimal CalculateTotalScore(int customerRating, bool isActive)
    {
        return isActive ? customerRating * 10 : 0;
    }
}

2. Setup Dependency Injection

using CoreOne.Winforms.Extensions;
using Microsoft.Extensions.DependencyInjection;

var services = new ServiceCollection();

// Register CoreOne.Winforms services
services.AddFormServices();

// Register your dropdown providers
services.AddSingleton<CountryProvider>();
services.AddSingleton<StateProvider>();

var serviceProvider = services.BuildServiceProvider();

3. Create and Use ModelControl

using CoreOne.Winforms.Controls;

public class MyForm : Form
{
    private readonly ModelControl _modelControl;
    
    public MyForm(IServiceProvider services)
    {
        var modelBinder = services.GetRequiredService<IModelBinder>();
        _modelControl = new ModelControl(services, modelBinder);
        _modelControl.Dock = DockStyle.Fill;
        
        // Handle save button click
        _modelControl.SaveClicked += OnSaveClicked;
        
        Controls.Add(_modelControl);
        
        // Bind your model
        var customer = new Customer 
        { 
            FirstName = "John", 
            LastName = "Doe" 
        };
        _modelControl.SetModel(customer);
    }
    
    private void OnSaveClicked(object? sender, ModelSavedEventArgs e)
    {
        if (e.IsModified)
        {
            // Validation is automatically performed
            if (!e.IsValid)
            {
                MessageBox.Show("Please fix validation errors");
                return;
            }
            
            // Get the updated model
            var customer = _modelControl.GetModel<Customer>();
            
            // Save to database...
            MessageBox.Show($"Saved: {customer?.FirstName} {customer?.LastName}");
            
            _modelControl.AcceptChanges();  // Commit changes and clear dirty flag
        }
    }
    
    // Optional: Reject changes and revert to original values
    private void OnCancelClicked(object? sender, EventArgs e)
    {
        _modelControl.RejectChanges();  // Rollback all changes
    }
}

📚 Advanced Usage

Creating Custom Dropdown Providers

Synchronous Provider:

using CoreOne.Winforms;
using CoreOne.Winforms.Models;

public class CountryProvider : IDropdownSourceProviderSync
{
    public void Initialize(IWatchHandler context) { }
    
    public IEnumerable<DropdownItem> GetItems(object model)
    {
        return
        [
            new DropdownItem("US", "United States"),
            new DropdownItem("CA", "Canada"),
            new DropdownItem("MX", "Mexico")
        ];
    }
    
    public void Dispose() { }
}

Dependent (Cascading) Provider:

public class StateProvider : IDropdownSourceProviderSync
{
    public void Initialize(IWatchHandler context) { }
    
    public IEnumerable<DropdownItem> GetItems(object model)
    {
        var customer = (Customer)model;
        
        return customer.Country switch
        {
            "US" => 
            [
                new DropdownItem("CA", "California"),
                new DropdownItem("TX", "Texas"),
                new DropdownItem("NY", "New York")
            ],
            "CA" => 
            [
                new DropdownItem("ON", "Ontario"),
                new DropdownItem("BC", "British Columbia")
            ],
            _ => []
        };
    }
    
    public void Dispose() { }
}

Async Provider (for API calls):

public class ProductCategoriesProvider(HttpClient httpClient) : IDropdownSourceProviderAsync
{
    public void Initialize(IWatchHandler context) { }
    
    public async Task<IEnumerable<DropdownItem>> GetItemsAsync(object model)
    {
        var categories = await httpClient.GetFromJsonAsync<Category[]>("/api/categories");
        return categories?.Select(c => new DropdownItem(c.Id.ToString(), c.Name)) ?? [];
    }
    
    public void Dispose() => httpClient?.Dispose();
}

Creating Custom Watch Handlers

Extend the library with custom reactive handlers using the WatchFactoryFromAttribute<TAttribute> base class:

using CoreOne.Winforms;
using CoreOne.Winforms.Services.WatchHandlers;

// 1. Create your custom attribute
[AttributeUsage(AttributeTargets.Property, AllowMultiple = true)]
public sealed class CustomWhenAttribute(string propertyName, object? expectedValue) 
    : WhenAttribute(propertyName, expectedValue)
{
}

// 2. Create the handler factory
public class CustomWhenHandler : WatchFactoryFromAttribute<CustomWhenAttribute>
{
    // Private nested implementation class
    private class CustomWhenHandlerInstance(PropertyGridItem gridItem, CustomWhenAttribute[] attributes)
        : WhenHandler(gridItem, attributes)
    {
        protected override void OnCondition(object model, IReadOnlyList<bool> flags)
        {
            // Custom logic when condition changes
            var shouldApply = flags.All(f => f);
            PropertyGridItem.InputControl.CrossThread(() => 
            {
                PropertyGridItem.InputControl.BackColor = shouldApply 
                    ? Color.LightGreen 
                    : Color.White;
            });
        }
    }
    
    protected override IWatchHandler? OnCreateInstance(
        PropertyGridItem gridItem, 
        CustomWhenAttribute[] attributes)
    {
        return new CustomWhenHandlerInstance(gridItem, attributes);
    }
}

// 3. Register (auto-registered via AddFormServices if in same assembly)
// Manual: services.AddSingleton<IWatchFactory, CustomWhenHandler>();

// 4. Use it
public class MyModel
{
    public bool IsActive { get; set; }
    
    [CustomWhen(nameof(IsActive), true)]
    public string ActiveField { get; set; }
}

Monitoring Property Changes

// Subscribe to property changes
var subscription = _modelControl.PropertyChanged?.Subscribe(change =>
{
    Console.WriteLine($"Property '{change.PropertyName}' changed " +
                     $"from '{change.OldValue}' to '{change.NewValue}'");
});

// Later, dispose to unsubscribe
subscription?.Dispose();

Available Attributes

Attribute Purpose Example
[GridColumn(GridColumnSpan)] Control column span (1-6) [GridColumn(GridColumnSpan.Full)]
[Group(title)] Group related properties in a GroupBox [Group("Personal Information")]
[DropdownSource(typeof(Provider))] Specify dropdown provider [DropdownSource(typeof(CountryProvider))]
[WatchProperty("prop")] Watch single property [WatchProperty(nameof(Country))]
[WatchProperties("prop1", "prop2")] Watch multiple properties [WatchProperties(nameof(FirstName), nameof(LastName))]
[EnabledWhen("prop", value, comparison)] Conditionally enable control [EnabledWhen(nameof(IsActive), true)]
[VisibleWhen("prop", value, comparison)] Conditionally show/hide control [VisibleWhen(nameof(ShowAdvanced), true)]
[ClearWhen("prop", value, comparison)] Clear value when condition met [ClearWhen(nameof(IsActive), false)]
[Compute("MethodName")] Execute method to compute value [Compute("CalculateTotal")]
[File(filter, multiselect)] File/folder picker control [File("Text files (*.txt)|*.txt", false)]
[Rating(maxRating)] Star rating control [Rating(5)]
[Ignore] Skip property in form generation [Ignore]
[Visible(bool)] Control visibility [Visible(false)]

Grid Column Spans

public enum GridColumnSpan
{
    None = 0,
    One = 1,         // 1/6 width
    Two = 2,         // 1/3 width
    Three = 3,       // 1/2 width (Half width)
    Four = 4,        // 2/3 width
    Five = 5,        // 5/6 width
    Six = 6,         // Full width
    Default = Six    // Default is full width (6 columns)
}

Grouping Controls

The [Group] attribute allows you to visually organize related properties into labeled sections using GroupBox containers:

public class Employee
{
    [Group("Personal Information")]
    public string FirstName { get; set; }
    
    [Group("Personal Information")]
    public string LastName { get; set; }
    
    [Group("Personal Information")]
    public DateTime DateOfBirth { get; set; }
    
    [Group("Employment Details")]
    public string Department { get; set; }
    
    [Group("Employment Details")]
    [Rating(5)]
    public int PerformanceRating { get; set; }
    
    // No group - will be rendered at the top
    public string EmployeeId { get; set; }
}

Key Features:

  • Properties with the same group title are grouped together
  • Groups are rendered in alphabetical order
  • Properties without [Group] appear first (ungrouped section)
  • Each group maintains the 6-column grid layout internally
  • Groups can be combined with all other attributes ([GridColumn], [EnabledWhen], etc.)

🎨 Themed Controls

OButton - Modern Button Control

var button = new OButton
{
    Text = "Click Me",
    BackColor = Color.FromArgb(0, 120, 215),
    ForeColor = Color.White,
    Width = 120,
    Height = 35
};

AnimatedPanel - Transitions & Animations

var panel = new AnimatedPanel();
// Built-in transition support for smooth animations

LoadingCircle

var loading = new LoadingCircle
{
    Active = true,
    Color = Color.Blue
};

RatingControl - Star Rating

var rating = new RatingControl
{
    MaxRating = 5,
    Value = 3
};

rating.ValueChanged += (s, e) => 
{
    Console.WriteLine($"Rating changed to: {rating.Value}");
};

🏗️ Architecture

CoreOne.Winforms follows SOLID & DRY principles with a clean, extensible architecture:

Design Patterns

  • Factory Pattern - Priority-based chain of responsibility for control/handler creation
  • Template Method Pattern - WatchHandler base class with OnInitialize/OnRefresh hooks
  • Observer Pattern - Reactive programming with Subject<T> for property changes
  • Strategy Pattern - Interchangeable animation algorithms (TransitionLinear, TransitionBounce)

Core Services

  • IModelBinder - Two-way binding between models and controls
  • IPropertyGridItemFactory - Property grid item creation with labels
  • IGridLayoutManager - 6-column Bootstrap-style responsive layout
  • IRefreshManager - Property change notification coordination
  • IControlStateManager - Control state management (Normal/Hover/Pressed)
  • FormContainerHostService - Hosted form services lifecycle management

Extensibility Points

  • IControlFactory - Control creation based on types/attributes
    • Priority 100+: Attribute-based ([Rating], [DropdownSource], [File])
    • Priority 0: Type-based (String, Numeric, Boolean, DateTime, Enum)
  • IWatchFactory - Reactive property watch handlers
    • EnabledWhenHandler - Conditional enabling
    • VisibleWhenHandler - Conditional visibility
    • ClearWhenHandler - Conditional value clearing
    • ComputeHandler - Computed properties
    • DropdownHandler - Cascading dropdown refresh
  • Dependency Injection - Constructor injection with IServiceProvider
  • Reactive Programming - Observable changes with Subject<T>

Factory Priority System

Factories use a priority-based chain of responsibility to determine property handling:

  • Priority 100+: Attribute-based factories (e.g., [Rating], [File], [DropdownSource])
  • Priority 0: Type-based factories (e.g., String, Numeric, Boolean, DateTime, Enum)

Custom Factory Example:

public class MyCustomFactory : IControlFactory
{
    public int Priority => 50;  // Medium priority (checked before type-based)
    
    public bool CanHandle(Metadata property) 
    {
        // Check if this factory can handle the property
        return property.PropertyType == typeof(MyCustomType);
    }
    
    public ControlContext? CreateControl(
        Metadata property, 
        object model, 
        Action<object?> onValueChanged) 
    {
        // Create and return custom control context
        var control = new MyCustomControl();
        return new MyCustomControlContext(control, onValueChanged);
    }
}

Handler Pipeline

The watch handler pipeline enables plug-and-play reactive behaviors:

  1. Auto-Discovery: Handlers register via AddFormServices() (scans assembly for IWatchFactory)
  2. Property Matching: Each factory checks CanHandle() during binding
  3. Handler Creation: Matching factories create handler instances via OnCreateInstance()
  4. Initialization: OnInitialize() sets up dependencies and metadata
  5. Reactive Execution: OnRefresh() executes when watched properties change

Template Method Pattern:

public abstract class WatchHandler : IWatchHandler
{
    protected virtual void OnInitialize(object model) { }  // Setup
    protected virtual void OnRefresh(object model, bool isFirst) { }  // React to changes
    protected override void OnDispose() { }  // Cleanup
}

🤝 Contributing

Contributions are welcome! Please feel free to submit a Pull Request.

📄 License

This project is licensed under the MIT License - see the LICENSE file for details.

📝 Requirements

  • .NET 9.0 or later
  • Windows OS (WinForms)
  • Visual Studio 2022 or later (recommended)

Made with ❤️ by Juan Lopez

Product Compatible and additional computed target framework versions.
.NET net9.0 is compatible.  net9.0-android was computed.  net9.0-browser was computed.  net9.0-ios was computed.  net9.0-maccatalyst was computed.  net9.0-macos was computed.  net9.0-tvos was computed.  net9.0-windows was computed.  net10.0 was computed.  net10.0-android was computed.  net10.0-browser was computed.  net10.0-ios was computed.  net10.0-maccatalyst was computed.  net10.0-macos was computed.  net10.0-tvos was computed.  net10.0-windows was computed. 
Compatible target framework(s)
Included target framework(s) (in package)
Learn more about Target Frameworks and .NET Standard.

NuGet packages (1)

Showing the top 1 NuGet packages that depend on CoreOne.Drawing:

Package Downloads
CoreOne.Winforms

Package Description

GitHub repositories

This package is not used by any popular GitHub repositories.

Version Downloads Last Updated
1.1.0.1 95 3/4/2026
1.1.0 121 1/29/2026
1.0.1 191 7/10/2025
1.0.0 178 7/10/2025