CoreOne.Winforms 1.3.2.1

dotnet add package CoreOne.Winforms --version 1.3.2.1
                    
NuGet\Install-Package CoreOne.Winforms -Version 1.3.2.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.Winforms" Version="1.3.2.1" />
                    
For projects that support PackageReference, copy this XML node into the project file to reference the package.
<PackageVersion Include="CoreOne.Winforms" Version="1.3.2.1" />
                    
Directory.Packages.props
<PackageReference Include="CoreOne.Winforms" />
                    
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.Winforms --version 1.3.2.1
                    
#r "nuget: CoreOne.Winforms, 1.3.2.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.Winforms@1.3.2.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.Winforms&version=1.3.2.1
                    
Install as a Cake Addin
#tool nuget:?package=CoreOne.Winforms&version=1.3.2.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-windows7.0 is compatible.  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

This package is not used by any NuGet packages.

GitHub repositories

This package is not used by any popular GitHub repositories.

Version Downloads Last Updated
1.3.2.1 96 3/4/2026
1.3.2 87 2/25/2026
1.3.1 98 2/4/2026
1.3.0 89 2/4/2026
1.2.0 97 2/2/2026
1.1.0.1 98 1/29/2026
1.1.0 97 1/29/2026
1.0.1.1 178 7/10/2025
1.0.1 180 7/8/2025
1.0.0 187 7/8/2025