Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Pass type parameters to attached behaviors

I am implementing an attached behavior in a WPF application. I need to pass a type parameters to the behavior, so I can call a method void NewRow(Table<T> table) on SqliteBoundRow. If I was instantiating an object in XAML, I would pass a type parameters using x:TypeArguments, but I don't see a way to do this when setting an attached behavior, because it uses a static property.

Code for the attached behavior looks like this:

public abstract class SqliteBoundRow<T> where T : SqliteBoundRow<T>
{
    public abstract void NewRow(Table<T> table);
}

public class DataGridBehavior<T> where T:SqliteBoundRow<T>
{
    public static readonly DependencyProperty IsEnabledProperty;
    static DataGridBehavior()
    {
        IsEnabledProperty = DependencyProperty.RegisterAttached("IsEnabled",
            typeof(bool), typeof(DataGridBehavior<T>),
            new FrameworkPropertyMetadata(false, OnBehaviorEnabled));
    }
    public static void SetIsEnabled(DependencyObject obj, bool value)
    {
        obj.SetValue(IsEnabledProperty, value);
    }
    public static bool GetIsEnabled(DependencyObject obj)
    {
        return (bool)obj.GetValue(IsEnabledProperty);
    }
    private static void OnBehaviorEnabled(DependencyObject dependencyObject,
        DependencyPropertyChangedEventArgs args)
    {
        var dg = dependencyObject as DataGrid;
        dg.InitializingNewItem += DataGrid_InitializingNewItem;
    }
    private static void DataGrid_InitializingNewItem(object sender,
        InitializingNewItemEventArgs e)
    {
        var table = (sender as DataGrid).ItemsSource as Table<T>;
        (e.NewItem as T).NewRow(table);
    }

}

XAML looks like this:

<DataGrid DataGridBehavior.IsEnabled="True">
    <!-- DataGridBehavior needs a type parameter -->
</DataGrid>

My current solution is to wrap DataGridBehavior in a a derived class which specifies the type parameters.

like image 423
tdr Avatar asked Jan 11 '23 11:01

tdr


2 Answers

The simplest solution is for you to declare another Attached Property, but of type Type to hold the parameter value for you. In this case, you would set the Type property before your IsEnabled Attached Property:

<DataGrid DataGridBehavior.TypeParameter="{x:Type SomePrefix:SomeType}" 
    DataGridBehavior.IsEnabled="True" ... />

Looking again at your code, it seems as though your IsEnabled property does nothing except adding a new row to your table... in that case, there's no reason why you couldn't replace it with the TypeParameter Attached Property and use that one to add the new row instead.

like image 103
Sheridan Avatar answered Jan 13 '23 01:01

Sheridan


I don't think WPF provides an elegant syntactic way to do what you want. So I was just about to post a similar answer to the one from Sheridan. That is you can provide an additional property of type Type to determine the generic type. However, Sheridan beat me to it. Below is some sample code of how you can do this with reflection:

Xaml

 <DataGrid behaviors:DataGridBehavior.InnerType="namespace:SqliteBoundRow"
      behaviors:DataGridBehavior.IsEnabled="True">
</DataGrid>

Code behind

public abstract class DataGridBehavior
{
    private static readonly ConcurrentDictionary<Type, DataGridBehavior> Behaviors = new ConcurrentDictionary<Type, DataGridBehavior>();
    public static readonly DependencyProperty IsEnabledProperty;
    public static readonly DependencyProperty InnerTypeProperty;
    static DataGridBehavior()
    {
        IsEnabledProperty = DependencyProperty.RegisterAttached("IsEnabled",
            typeof(bool), typeof(DataGridBehavior),
            new FrameworkPropertyMetadata(false, OnBehaviorEnabled));

        InnerTypeProperty = DependencyProperty.RegisterAttached("InnerType",
            typeof(Type), typeof(DataGridBehavior));
    }
    public static void SetIsEnabled(DependencyObject obj, bool value)
    {
        obj.SetValue(IsEnabledProperty, value);
    }
    public static bool GetIsEnabled(DependencyObject obj)
    {
        return (bool)obj.GetValue(IsEnabledProperty);
    }

    public static void SetInnerType(DependencyObject obj, Type value)
    {
        obj.SetValue(InnerTypeProperty, value);
    }
    public static Type GetInnerType(DependencyObject obj)
    {
        return (Type)obj.GetValue(InnerTypeProperty);
    }

    private static void OnBehaviorEnabled(DependencyObject dependencyObject,
          DependencyPropertyChangedEventArgs args)
    {
        var innerType = GetInnerType(dependencyObject);
        if (innerType == null)
            throw new Exception("Missing inner type");

        var behavior = Behaviors.GetOrAdd(innerType, GetBehavior);
        behavior.OnEnabled(dependencyObject);
    }

    private static DataGridBehavior GetBehavior(Type innerType)
    {
        var behaviorType = typeof(DataGridBehavior<>).MakeGenericType(innerType);
        var behavior = (DataGridBehavior)Activator.CreateInstance(behaviorType);
        return behavior;
    }

    protected abstract void OnEnabled(DependencyObject dependencyObject);

}

public class DataGridBehavior<T> : DataGridBehavior
    where T : SqliteBoundRow
{
    protected override void OnEnabled(DependencyObject dependencyObject)
    {
        //dg.InitializingNewItem += DataGrid_InitializingNewItem;
    }

    private static void DataGrid_InitializingNewItem(object sender,
        InitializingNewItemEventArgs e)
    {
        //var table = (sender as DataGrid).ItemsSource as Table<T>;
        //(e.NewItem as T).NewRow(table);
    }

}

public class SqliteBoundRow
{
}
like image 20
Kess Avatar answered Jan 12 '23 23:01

Kess