13

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?

NZJames
  • 4,963
  • 15
  • 50
  • 100
  • 3
    you're doing it all wrong. Create an `ObservableCollection` and bind your `TabControl.ItemsSource` to that. then create a `bool IsVisible` value in the ViewModel and bind the `Visibility` of the Tab Items to that using a `BoolToVisibilityConverter`. – Federico Berasategui Nov 14 '13 at 14:49
  • 1
    Visibility is purely UI (WPF), and has no mean for ViewModel. Change it for Boolean, and use BooleanToVisibilityConverter in binding. – Xaruth Nov 14 '13 at 14:51
  • HighCore, good idea to do it like that, Ive created a BaseResultViewModel class that I have an OBservableCollection of and binding that to the ItemsSource property of the TabControl. Problem is, the TabItem needs Header, HeaderStyle, Content properties etc. How do I get the ViewModel to provide all the dependency properties eg Header, Style, Content to the TabControl? – NZJames Nov 14 '13 at 16:13

3 Answers3

12

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.

Community
  • 1
  • 1
Sheridan
  • 68,826
  • 24
  • 143
  • 183
  • Is there a way to do two way data binding with a parameter similar to this? This looks like it would only work for setting the value in the element from the method but I'd like two way binding. I wonder if I have a design problem since I'm in this situation... – Skystrider Jun 21 '16 at 22:58
  • Oh I guess the way to do it is with a converter that can convert back too. I think... – Skystrider Jun 21 '16 at 23:01
  • @Skychan, it depends what you mean by parameter. You could probably just wrap it in a property and simply data bind to it. – Sheridan Jun 22 '16 at 07:54
  • Thanks. Whatever it was I was trying to do that was similar to this, I got it to work. I think it ended up being a simple data bind to a property. – Skystrider Jul 14 '16 at 21:19
3

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>
Zack
  • 2,789
  • 33
  • 60
Enzojz
  • 863
  • 2
  • 9
  • 15
2

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.

XLars
  • 177
  • 1
  • 5
  • This is indeed a hack and WPF may complain "MyClass is not a collection", but I've found it useful. It should only be used in very isolated, specific use-cases, though. – mike Jan 07 '22 at 23:54
  • There is one extra "}" in the code: `` – LEyahoo Mar 24 '22 at 12:30