6

I have the following hierarchy:

abstract class TicketBase
{
    public DateTime PublishedDate { get; set; }
}

class TicketTypeA:TicketBase
{
     public string PropertyA { get; set; }
}   

class TicketTypeB:TicketBase
{
     public string PropertyB { get; set; }
}

In my VM I have a List<TicketBase> Tickets. When a user clicks a button on my app, they want to see a list of previous values of a certain property, e.g.:

<Button Tag="{x:Type Types:TicketTypeA}" 
        Command="{Binding ListHistoryCommand}"
        CommandParameter="{Binding Tag, RelativeSource={RelativeSource Self}}" />

as you can see, I set my Tag property to TicketTypeA and pass that as parameter to my command:

private void ListHistory(object o)
{
   if (Tickets.Count == 0)
       return;
   Type ty = o as Type;
   ValueHistory = new ObservableCollection<TicketBase>(GetTicketsOfType(ty).Select(t => t)); // <- Need to return t.PropertyA here, but dynamically
}

IEnumerable<TicketBase> GetTicketsOfType(Type type)
{
    if (!typeof(TicketBase).IsAssignableFrom(type))
        throw new ArgumentException("Parameter 'type' is not a TicketBase");
    return Tickets.Where(p => p.GetType() == type);
}

(ValueHistory is another collection that I set as ItemsSource on my grid)

However I need to also pass in the property name too, so that I can display just that property in the grid like so:

Published Time     |  PropertyA
===================================================
09:00              | <value of PropertyA at 09:00>
08:55              | <value of PropertyA at 08:55>

So the question is basically what is the cleanest way to pass in the property name as another parameter into my command?

Cody Gray - on strike
  • 239,200
  • 50
  • 490
  • 574
cjroebuck
  • 2,273
  • 4
  • 30
  • 46

2 Answers2

13

See this question
Passing two command parameters using a WPF binding

Update
If you need to store both the Type and the Property Name on the Button you'll have to use an attached property like you said. To pass the two parameters to the Command, something like this should work

<Button Tag="{x:Type Types:TicketTypeA}"
        local:ParameterNameBehavior.ParameterName="{Binding Source='Parameter A'}"
        Command="{Binding ListHistoryCommand}">
    <Button.CommandParameter>
        <MultiBinding Converter="{StaticResource PassThroughConverter}">
            <Binding Path="Tag" RelativeSource="{RelativeSource Self}"/>
            <Binding Path="(local:ParameterNameBehavior.ParameterName)"
                     RelativeSource="{RelativeSource Self}"/>
        </MultiBinding>
    </Button.CommandParameter>
</Button>

ParameterNameBehavior

public static class ParameterNameBehavior
{
    private static readonly DependencyProperty ParameterNameProperty = 
        DependencyProperty.RegisterAttached("ParameterName",
                                            typeof(string),
                                            typeof(ParameterNameBehavior));
    public static void SetParameterName(DependencyObject element, string value)
    {
        element.SetValue(ParameterNameProperty, value);
    }
    public static string GetParameterName(DependencyObject element)
    {
        return (string)element.GetValue(ParameterNameProperty);
    }
}

PassThroughConverter

public class PassThroughConverter : IMultiValueConverter
{
    public object Convert(object[] values, Type targetType, object parameter, CultureInfo culture)
    {
        return values.ToList();
    }
    public object[] ConvertBack(object value, Type[] targetTypes, object parameter, CultureInfo culture)
    {
        throw new NotSupportedException();
    }
}
Community
  • 1
  • 1
Fredrik Hedblad
  • 83,499
  • 23
  • 264
  • 266
  • but it doesnt really explain where I can store my property name.. as Im already using the Tag to store the type, I need another place to store property name. I will look at adding attached properties to the button. – cjroebuck Feb 24 '11 at 14:57
  • @cjroebuck: Ah ok. so you both want to store it and send it as a CommandParameter? In that case I think you'll have to use an Attach Property like you said – Fredrik Hedblad Feb 24 '11 at 14:59
  • @cjroebuck: See my updated answer. Do you have a special reason to store the properties on the `Button` itself or do you think somethink like that would work? Maybe I'm missing some part of the question here :) – Fredrik Hedblad Feb 24 '11 at 15:21
  • I have an array of buttons, one for each different property over a variety of ticket types. The content of each button is bound to the most recent value of the property. When the user clicks one of these buttons, the historic values of the property are listed in the grid. So yes, I think the button is the obvious place to store the properties. Did you have a better idea? – cjroebuck Feb 24 '11 at 16:10
  • @cjroebuck: No, no better idea :) Just didn't get the full context of your problem. Now I see why you need that – Fredrik Hedblad Feb 24 '11 at 16:12
  • @cjroebuck: Updated my answer with an attached property. I'm not really sure how you want to handle the Property Name but you probably got that part figured out :) – Fredrik Hedblad Feb 24 '11 at 16:20
  • Thanks for your help on this Meleak, much appreciated – cjroebuck Feb 24 '11 at 16:36
  • Actually.. handling the Property Name is harder than I originally thought: http://stackoverflow.com/questions/5116177/how-do-i-create-a-custom-select-lambda-expression-at-runtime-to-work-with-sub-cla – cjroebuck Feb 25 '11 at 10:38
5

I got this working without resorting to Attached Properties by using the x:Name property in the Xaml and then passing this on to my CommandParameter as a MultiBinding along with the Tag. From Front to Back:

In my View:

 <Button Content="{Binding PropertyA}" x:Name="PropertyA" Tag="{x:Type Types:TicketTypeA}" Style="{StaticResource LinkButton}"/>

 <Button Content="{Binding PropertyB}" x:Name="PropertyB" Tag="{x:Type Types:TicketTypeB}" Style="{StaticResource LinkButton}"/>

In the style for each button:

 <Style x:Key="LinkButton" TargetType="Button">
        <Setter Property="Command" Value="{Binding DataContext.ListHistoryCommand, Mode=OneWay, RelativeSource={RelativeSource FindAncestor, AncestorType={x:Type UserControl}}}" />

        <Setter Property="CommandParameter">
            <Setter.Value>
                <MultiBinding Converter="{StaticResource propertyConverter}">
                    <MultiBinding.Bindings>
                        <Binding Path="Tag" RelativeSource="{RelativeSource Mode=Self}"/>
                        <Binding Path="Name" RelativeSource="{RelativeSource Mode=Self}"/>
                    </MultiBinding.Bindings>
                </MultiBinding>
            </Setter.Value>
        </Setter>

In my Converter:

public class PropertyConverter : IMultiValueConverter
{
        public object Convert(object[] values, Type targetType, object parameter, CultureInfo culture)
        {
            //Type t = values[0] as Type;
            //String propName = values[1] as string;

            Type t = values[0] as Type;
            if (t == null)
                return typeof(TicketBase);
            String s = values[1] as String;

            return new Tuple<Type,String>(t,s);
        }
}

In my View Model:

private void ListHistory(object o)
    {
        if (Tickets.Count == 0)
            return;
        var tuple = o as Tuple<Type,String>;

        // Now write some code to dynamically select the propertyName (tuple.Item2) from the type (tuple.Item1)  

    }

I am now receiving the Type and PropertyName in my Command. Now, I just need to compile a lambda expression at runtime to dynamically Select the PropertyName from the Type.

Community
  • 1
  • 1
cjroebuck
  • 2,273
  • 4
  • 30
  • 46
  • How do you create the instance of your converter class so that `Converter="{StaticResource propertyConverter}"` can work? – mins Jun 02 '19 at 18:26