Back to list
christian289

defining-wpf-dependencyproperty

by christian289

ClaudeCode와 함께하는 .NET 개발 튜토리얼

1🍴 0📅 Jan 25, 2026

SKILL.md


name: defining-wpf-dependencyproperty description: Defines WPF DependencyProperty with Register, PropertyMetadata, callbacks, and validation. Use when creating custom controls, attached properties, or properties that support data binding and styling.

WPF DependencyProperty Patterns

Defining dependency properties for data binding, styling, animation, and property value inheritance.

Advanced Patterns: See ADVANCED.md for value inheritance, metadata override, and event integration.

1. DependencyProperty Overview

Standard CLR Property:
    private string _name;
    public string Name { get => _name; set => _name = value; }

DependencyProperty:
    public static readonly DependencyProperty NameProperty = ...
    public string Name { get => (string)GetValue(NameProperty); set => SetValue(NameProperty, value); }

Benefits:
    ✅ Data Binding
    ✅ Styling & Templating
    ✅ Animation
    ✅ Property Value Inheritance
    ✅ Default Values
    ✅ Change Notifications
    ✅ Value Coercion
    ✅ Validation

2. Basic DependencyProperty

2.1 Standard Registration

namespace MyApp.Controls;

using System.Windows;
using System.Windows.Controls;

public class MyControl : Control
{
    // 1. Register DependencyProperty
    public static readonly DependencyProperty TitleProperty = DependencyProperty.Register(
        name: nameof(Title),
        propertyType: typeof(string),
        ownerType: typeof(MyControl),
        typeMetadata: new PropertyMetadata(defaultValue: string.Empty));

    // 2. CLR property wrapper
    public string Title
    {
        get => (string)GetValue(TitleProperty);
        set => SetValue(TitleProperty, value);
    }
}

2.2 With FrameworkPropertyMetadata

public static readonly DependencyProperty ValueProperty = DependencyProperty.Register(
    name: nameof(Value),
    propertyType: typeof(double),
    ownerType: typeof(MyControl),
    typeMetadata: new FrameworkPropertyMetadata(
        defaultValue: 0.0,
        flags: FrameworkPropertyMetadataOptions.AffectsRender |
               FrameworkPropertyMetadataOptions.BindsTwoWayByDefault,
        propertyChangedCallback: OnValueChanged,
        coerceValueCallback: CoerceValue),
    validateValueCallback: ValidateValue);

public double Value
{
    get => (double)GetValue(ValueProperty);
    set => SetValue(ValueProperty, value);
}

3. FrameworkPropertyMetadataOptions

FlagDescription
NoneNo special behavior
AffectsMeasureTriggers Measure pass on change
AffectsArrangeTriggers Arrange pass on change
AffectsRenderTriggers Render on change
InheritsValue inherits to child elements
BindsTwoWayByDefaultDefault binding mode is TwoWay

Common Combinations

// Read-only display property (AffectsRender)
new FrameworkPropertyMetadata(
    defaultValue: null,
    flags: FrameworkPropertyMetadataOptions.AffectsRender);

// Layout-affecting property (AffectsMeasure)
new FrameworkPropertyMetadata(
    defaultValue: 100.0,
    flags: FrameworkPropertyMetadataOptions.AffectsMeasure |
           FrameworkPropertyMetadataOptions.AffectsArrange);

// Two-way bindable property
new FrameworkPropertyMetadata(
    defaultValue: false,
    flags: FrameworkPropertyMetadataOptions.BindsTwoWayByDefault);

4. Callbacks

4.1 PropertyChangedCallback

public static readonly DependencyProperty IsActiveProperty = DependencyProperty.Register(
    nameof(IsActive),
    typeof(bool),
    typeof(MyControl),
    new PropertyMetadata(false, OnIsActiveChanged));

private static void OnIsActiveChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
{
    var control = (MyControl)d;
    var oldValue = (bool)e.OldValue;
    var newValue = (bool)e.NewValue;

    // Handle property change
    control.UpdateVisualState(newValue);
}

4.2 CoerceValueCallback

public static readonly DependencyProperty ProgressProperty = DependencyProperty.Register(
    nameof(Progress),
    typeof(double),
    typeof(ProgressControl),
    new FrameworkPropertyMetadata(
        0.0,
        propertyChangedCallback: null,
        coerceValueCallback: CoerceProgress));

private static object CoerceProgress(DependencyObject d, object baseValue)
{
    var value = (double)baseValue;

    // Clamp value to valid range
    if (value < 0.0) return 0.0;
    if (value > 100.0) return 100.0;

    return value;
}

4.3 ValidateValueCallback

public static readonly DependencyProperty CountProperty = DependencyProperty.Register(
    nameof(Count),
    typeof(int),
    typeof(MyControl),
    new PropertyMetadata(0),
    ValidateCount);  // Note: Not part of PropertyMetadata

private static bool ValidateCount(object value)
{
    var count = (int)value;

    // Return false to reject the value (throws exception)
    return count >= 0;
}

4.4 Callback Execution Order

1. ValidateValueCallback - Can reject value (throw exception)
2. CoerceValueCallback   - Can modify value
3. PropertyChangedCallback - Handle the change

5. Read-Only DependencyProperty

namespace MyApp.Controls;

using System.Windows;

public class StatusControl : Control
{
    // 1. Register read-only property key (private)
    private static readonly DependencyPropertyKey IsConnectedPropertyKey =
        DependencyProperty.RegisterReadOnly(
            nameof(IsConnected),
            typeof(bool),
            typeof(StatusControl),
            new FrameworkPropertyMetadata(false));

    // 2. Expose public DependencyProperty
    public static readonly DependencyProperty IsConnectedProperty =
        IsConnectedPropertyKey.DependencyProperty;

    // 3. Read-only CLR property
    public bool IsConnected => (bool)GetValue(IsConnectedProperty);

    // 4. Internal setter using key
    protected void SetIsConnected(bool value)
    {
        SetValue(IsConnectedPropertyKey, value);
    }
}

6. Attached Property

6.1 Defining Attached Property

namespace MyApp.Attached;

using System.Windows;

public static class GridHelper
{
    // Register attached property
    public static readonly DependencyProperty ColumnSpacingProperty =
        DependencyProperty.RegisterAttached(
            name: "ColumnSpacing",
            propertyType: typeof(double),
            ownerType: typeof(GridHelper),
            defaultMetadata: new FrameworkPropertyMetadata(
                0.0,
                FrameworkPropertyMetadataOptions.AffectsMeasure,
                OnColumnSpacingChanged));

    // Getter
    public static double GetColumnSpacing(DependencyObject obj)
    {
        return (double)obj.GetValue(ColumnSpacingProperty);
    }

    // Setter
    public static void SetColumnSpacing(DependencyObject obj, double value)
    {
        obj.SetValue(ColumnSpacingProperty, value);
    }

    private static void OnColumnSpacingChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
    {
        if (d is Grid grid)
        {
            UpdateGridSpacing(grid, (double)e.NewValue);
        }
    }

    private static void UpdateGridSpacing(Grid grid, double spacing)
    {
        // Implementation
    }
}

6.2 Using Attached Property in XAML

<Grid local:GridHelper.ColumnSpacing="10">
    <Grid.ColumnDefinitions>
        <ColumnDefinition Width="*"/>
        <ColumnDefinition Width="*"/>
        <ColumnDefinition Width="*"/>
    </Grid.ColumnDefinitions>
    <!-- Children -->
</Grid>

7. References

Score

Total Score

65/100

Based on repository quality metrics

SKILL.md

SKILL.mdファイルが含まれている

+20
LICENSE

ライセンスが設定されている

+10
説明文

100文字以上の説明がある

0/10
人気

GitHub Stars 100以上

0/15
最近の活動

1ヶ月以内に更新

+10
フォーク

10回以上フォークされている

0/5
Issue管理

オープンIssueが50未満

+5
言語

プログラミング言語が設定されている

+5
タグ

1つ以上のタグが設定されている

+5

Reviews

💬

Reviews coming soon