Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Binding to a method with parameter in XAML using polymorphism in ViewModel

Tags:

mvvm

binding

wpf

I have a TabControl with six tabs in my ResultView. The ViewModel that sits behind this View can be either a ResultTypeOneViewModel or ResultTypeTwoViewModel, each of which derives from ResultViewModel but you can interchangeably use the result viewer with either result type.

The difference is that in ResultTypeOneViewModel, tabs 1 & 3 need to be visible and the rest hidden. In ResultTypeTwoViewModel, tabs 2, 3, 4, 5, 6 need to be visible and tab 1 hidden.

I wanted to do this via something like

<TabItem Name="1" Visibility={Binding IsTabVisible(0)}>
<TabItem Name="2" Visibility={Binding IsTabVisible(1)}>
<TabItem Name="3" Visibility={Binding IsTabVisible(2)}>
etc...

And have an abstract method declaration in ResultsViewModel such as

public abstract Visibility IsTabVisible(int index);

And in ResultsOneViewModel have

public override Visibility IsTabVisible(int index)
{
    if (index == 0 || index == 2) return Visibility.Visible;
    return Visibility.Hidden;
}

And in ResultsTwoViewModel have

public override Visibility IsTabVisible(int index)
{
    if (index == 0) return Visibility.Hidden;
    return Visibility.Visible;
}

But I cannot figure out how to call a method like this with a parameter through bindings iN WPF XAML.

Can anyone suggest how I can do this or if it's not possible via this method, another way I could solve this problem?

like image 214
NZJames Avatar asked Nov 14 '13 14:11

NZJames


3 Answers

You can use an indexer property to be able to pass a single parameter to a property. Although it's probably not very intuitive to return a boolean value from an indexer property it works fine for me. Also keep in mind the indexer property looses it's expected functionality.

    class MyClass
    {
        public bool this[int tabNumber]
        {
            get
            {
                // Do logic to determine visibility here or in a separate method
                return IsTabVisible(tabNumber);
            }
        }

        bool IsTabVisible(int tabNumber)
        {
            bool result;

            // Method logic...

            return result;
        }
    }

So in XAML you can use the class name and provide a parameter between square brackets.

<TabItem Visibility="{Binding MyClass[0]}}"/>

If you need to raise a NotifyPropertyChanged for the indexer property use:

    NotifyPropertyChanged("Item[]");

I don't know if this fits into the MVVM pattern but it could be useful for anyone who wants to bind to a method with a single parameter.

like image 53
XLars Avatar answered Nov 09 '22 23:11

XLars


To answer your question directly, you can use the ObjectDataProvider to call a method for you so you can work with the results:

xmlns:System="clr-namespace:System;assembly=mscorlib"
xmlns:Windows="clr-namespace:System.Windows;assembly=PresentationCore"

...

<Window.Resources>
    <ObjectDataProvider x:Key="IsTab1VisibleMethod" 
        ObjectType="{x:Type Windows:Visibility}" IsAsynchronous="True" 
        MethodName="IsTabVisible">
        <ObjectDataProvider.MethodParameters>
            <System:Int32>0</System:Int32>
        </ObjectDataProvider.MethodParameters>
    </ObjectDataProvider>
</Window.Resources>

You should then be able to access the result like this (but you'd need one of these for each TabItem):

<TabItem Visibility="{Binding Source={StaticResource IsTab1VisibleMethod}}" />

However, it's generally not good practice to manipulate UI properties such as Visibility in code, so a better idea would be to use a BooleanToVisibilityConverter to Bind the Visibility properties to bool values as @Highcore suggested. You can see an example of this in the Binding a Button Visibility to bool value in ViewModel post here on StackOverflow.

In my opinion, an even better solution would be to simply provide one view for every view model.

like image 20
Sheridan Avatar answered Nov 09 '22 23:11

Sheridan


A better suggestion to this question is to use style.DataTrigger in style of TabItem like this:

<TabItem>
    <TabItem.Style>
        <Style target="{x:Type TabItem}">
            <Style.DataTriggers>
                <DataTrigger Binding="{Binding IsTabVisible}" Value="False">
                    <Setter Property="Visibility" Value = "Collapsed"/>
                </DataTrigger>
            </Style.DataTrigers>
        </Style>
    <TabItem.Style>
</TabItem>
like image 3
Enzojz Avatar answered Nov 09 '22 22:11

Enzojz