0

I am using C#, WPF, .NET Standard, Visual Studio. All the latest or almost latest versions.

This is my datacontext model (which is created in seperated library called ProgrammingManagerAPI):

public class MainModel
{
    public List<Project> Projects { get; set; }
    ...
}

which have list of object of type Project defined like this (also in seperated library ProgrammingManagerAPI, in directory Models), some properties and some methods:

public class Project
{
    public int Id { get; set; }
    ...
    public TimeSpan? TotalWorkedTime(bool subtasksIncluded = true)
    {
        if (Id < 0)
            return null;
        else 
            return new TimeSpan(...);
    }
    ...
}

In mainWindow I have a ListView, which I want to use to list projects with its properties.
I have lots of properties and some methods which are giving back the value depending on boolean parameter.

I read that in this case I should use ObjectDataProvider, so I tried like below:

xmlns:s="clr-namespace:System;assembly=mscorlib" 
xmlns:API.Models="clr-namespace:ProgrammingManagerAPI.Models;assembly=ProgrammingManagerAPI"

<Window.Resources>
    <ObjectDataProvider x:Key="yourStaticData"
            ObjectType="{x:Type API.Models:Project}"
            MethodName="TotalWorkedTime" >
        <ObjectDataProvider.MethodParameters>
            <s:Boolean>false</s:Boolean>
        </ObjectDataProvider.MethodParameters>
    </ObjectDataProvider>
</Window.Resources>

 <Grid Grid.Row="1" Grid.Column="0" Margin="10">
     <ListView Margin="10" ItemsSource="{Binding Projects}" HorizontalAlignment="Center" HorizontalContentAlignment="Center">
         <ListView.View>
             <GridView>
                <GridViewColumn HeaderContainerStyle="{StaticResource ListViewStyle}" Header="Id" DisplayMemberBinding="{Binding Id}" />
                <GridViewColumn HeaderContainerStyle="{StaticResource ListViewStyle}" Header="TotalWorkedTime" DisplayMemberBinding="{Binding Path=., Source={StaticResource yourStaticData}}" />
             </GridView>
         </ListView.View>
     </ListView>
 </Grid>

The call to the function TotalWorkedTime is fired, because breakpoint is hit. But is hit once, while I have created 4 object for test. Moreover it is hit like static function, not for every instance of the object like other properties. In immediate window I am trying to see what are other properties and those are nulls. While the column for Id is hit all the properties are available for each instance of Project. Moreover I have observed that it is hit before Id property getter is called.

I have tried many versions like without Path, in Binding and so many others ways.

Anyone can point me my mistake?

ninidow359
  • 19
  • 6
  • 1
    Why don't you simply have a TotalWorkedTime property and bind to it like `DisplayMemberBinding="{Binding TotalWorkedTime}"`? – Clemens Jan 12 '20 at 13:23
  • Because it is parametrized. I have at least few (for now) methods that return a value depending on some requirements. If I have 3 parameters I would need to make 8 properties with getters. But if I have 5 or 6 parameters it would be completely unmaintainable. – ninidow359 Jan 12 '20 at 15:29
  • And you would not instead need the same amount of ObjectDataProviders? – Clemens Jan 12 '20 at 15:34
  • That is right what you mean. You are correct. However I would not like to mess my API project. May you advise some way of solution? I mean just point me to read about something can be useful for my case? I would not like to end with object that contains hundreads of properties with getters like TotalWorkedTime1,TotalWorkedTime2, TotalWorkedTime3, ..., TotalWorkedTime999. I am sure WPF is so flexible, so it allows to use some smarter way, and surely someone had similar issue to mine. Edit: by the way thanks for you comment :) you are correct. – ninidow359 Jan 12 '20 at 15:54
  • Sorry, but to me it is totally unclear why the items that should be displayed in an ItemsControl should have so many properties. How many columns do you intend to have in that ListView? There should not be more properties than roughly the number of columns. – Clemens Jan 12 '20 at 15:58
  • I have API which would be used maybe in few apps. One of them (first) is WPF app. I would like to make API stable and not make it to depend on any view. The object project (the listview object) would have like 20 properties and like 10 methods(for now), each methods can give few different results depending on what parameters I use. So summarizing it would be like 30-70 columns(20 properties and 10 methods * 4 values). Some of parameters would not be used for this listView but it would be used in other places. – ninidow359 Jan 12 '20 at 16:10
  • Perhaps wrap your model in a view model that exposes the properties that a specifc view should display. – Clemens Jan 12 '20 at 17:35
  • @Clemens, that is also a good idea. Just for your information the answer below is what I was looking for, probably I didn;t address the problem in clear way. Thanks for help :) – ninidow359 Feb 02 '20 at 23:31

1 Answers1

0

ObjectDataProvider is useful when you have one single instance of an object (or a static class) you want to bind to, but you're using an ItemsControl (ListView), which makes things a bit more complicated.

What you need is an IValueConverter. That takes an object and "converts" it by calling a function and returning the result. I honestly expected to be able to find one by Googling, but I wasn't able to. I thought I might end up using something like this sooner or later so I went ahead and built one. This supports any type of object with any function name taking any number of parameters.

    public class FunctionConverter : IValueConverter
    {
        public string FunctionName { get; set; }

        public object Convert(object value, Type targetType, object parameter, CultureInfo culture)
        {
            return value.GetType().GetMethod(FunctionName).Invoke(value, (object[])parameter);
        }

        public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture)
        {
            return Binding.DoNothing;
        }
    }

And here's an example of how you might use it:

MainWindow.xaml.cs:

    public partial class MainWindow : Window
    {
        public MainWindow()
        {
            TestInstance = new Test();
            InitializeComponent();
        }

        public Test TestInstance { get; set; }

    }

    public class Test
    {
        public string Foo(string bar)
        {
            return bar;
        }
    }

MainWindow.xaml:

    <Grid>
        <Grid.Resources>
            <KHS:FunctionConverter x:Key="FuncCon" FunctionName="Foo"/>
        </Grid.Resources>

        <TextBlock>
            <TextBlock.Text>
                <Binding Path="TestInstance" Converter="{StaticResource FuncCon}">
                    <Binding.ConverterParameter>
                        <x:Array Type="sys:Object">
                            <sys:String>Hello World</sys:String>
                        </x:Array>
                    </Binding.ConverterParameter>
                </Binding>
            </TextBlock.Text>
        </TextBlock>
    </Grid>

You declare the converter as a resource, just like you did with the ObjectDataProvider, and set FunctionName to the name of the function you want to call. The converter then uses MethodInfo.Invoke(Object, Object[]) to run that function and returns the result.

You pass parameters for the function via the binding's ConverterParameter property, which would let you potentially pass different values for different items in your list. In the example, I pass the string "Hello World" to the function Foo, which just returns exactly what was passed.

A few final notes: This converter only works one way. The converter as provided doesn't check for null and has no handling in place for when FunctionName is not found. Using a binding like this doesn't allow for update notifications like a dependency property would provide.

Keith Stein
  • 6,235
  • 4
  • 17
  • 36