M463
M463

Reputation: 2183

Disabling UserControls in a TabControl

I've got an application consisting of a TabControl with TabItems that are hosting custom UserControls like this:

<TabControl>
    <TabItem Header="UC_1 and UC_2">
        <StackPanel>
            <local:UC_1/>
            <Separator/>
            <local:UC_2/>
        </StackPanel>
    </TabItem>
    <TabItem Header="UC_3">
        <local:UC_3/>
    </TabItem>
    <TabItem Header="UC_4">
        <local:UC_3/>
    </TabItem>
</TabControl>

The UserControls have a bunch of different controls on them, like this:

<ScrollViewer>
    <StackPanel>
        <DockPanel>
            <TextBlock Text="Surname"/>
            <Border>
                <TextBox Text="{Binding Model.Surname}" />
            </Border>
        </DockPanel>
        <DockPanel>
            <TextBlock Text="Firstname" />
            <Border>
                <TextBox Text="{Binding Model.Firstname}" />
            </Border>
        </DockPanel>
    </StackPanel>
</ScrollViewer>

Now I want to set the whole TabControl to ReadOnly, depending on a property in the Model, so the user can still read but not edit the content of the controls.
Unfortunately, IsReadOnly is not a property of a TabControl, nor a TabItem nor a UserControl. So I decided to go with the IsEnabled-property instead.

But if I disable the whole TabControl, the user would be unable to switch between the TabItems anymore to read the data, as those cannot be clicked on if they are disabled (which seems legit, given the meaning of the IsEnabled-property).

So, to achieve my "diabled but still clickable"-TabItems, I've tried to setup a Style with a DataTrigger inside the Resources of the TabControl, so disable the UserControls inside the TabItems instead of the whole TabControl or the TabItems:

<TabControl.Resources>
    <Style TargetType="{x:Type UserControl}">
        <Style.Triggers>
            <DataTrigger Binding="{Binding Model.TCEnabledProperty}" Value="9">
                <Setter Property="IsEnabled" Value="False"/>
            </DataTrigger>
        </Style.Triggers>
    </Style>
</TabControl.Resources>

But this is not working.

So I changed

<Style TargetType="{x:Type UserControl}"> 

to

<Style TargetType="{x:Type StackPanel}">

and suddenly I got the expected behavior but (of course) only if the given UserControl has a StackPanel hosting all the other controls, what is not the case for all of those UserControls.


The main question here is now:

Why is the Style working for the TargetType = StackPanel but not for the TargetType = UserControl? I don't get the point that I can access the Controls inside the UserControl but not the UserControl itself.

If there is no other option, I'll just add a wrapping StackPanel to the UserControls not having one already, but for the sake of curiosity I would like to understand the whole behavior I'm experiencing here...

Upvotes: 0

Views: 1718

Answers (3)

Liero
Liero

Reputation: 27338

Why dont just bind each TabItem's content IsEnabled property separatelly?

<TabItem Header="UC_1 and UC_2">
    <StackPanel IsEnabled="{Binding ...}"/> <!-- use converter if you can't create property called 'IsTCEnabled' in viewmodel -->
        <local:UC_1 />
        <Separator />
        <local:UC_2 />
    </StackPanel>
</TabItem>
<TabItem Header="UC_3">
    <local:UC_3 IsEnabled="{Binding ...}"/>
</TabItem>
<TabItem Header="UC_4">
    <local:UC_3 IsEnabled="{Binding ...}"/>
</TabItem>

you may also create style to avoid repeating the binding:

<Style x:Key="TabContent" TargetType="FrameworkElement">
   <Setter Property="IsEnabled" Value="{Binding }" />
</Style>

<TabItem Header="UC_1 and UC_2">
    <StackPanel Style="{StaticResource TabContent}" />                      
        <local:UC_1 />
        <Separator />
        <local:UC_2 />
    </StackPanel>
</TabItem>
<TabItem Header="UC_3">
    <local:UC_3 Style="{StaticResource TabContent}" />
</TabItem>
<TabItem Header="UC_4">
    <local:UC_3 Style="{StaticResource TabContent}" />
</TabItem>

EDIT: Answer to your question

Why is the Style working for the TargetType = StackPanel but not for the TargetType = UserControl?

it's simple. StackPanel style works, because you have added StackPanel to the first tab. UserControl does not work, because you have added UC_1 and UC_2 to the second tab. Implicit styles does not work for inherited controls. TargetType must exactly match the type of the element.

Upvotes: 2

M463
M463

Reputation: 2183

Edit:
Liero provided an even better solution to the problem. I'm still leaving my answer up, as I was confusing base classes with concrete objects by the time I was wondering why I was unable to apply a Style to TargetType=UserControl in my View and the text below might help others to understand the difference as well.

The discussion in the comments to Sinatrs answer lead me to another post here on stackoverflow, where I found the solution to the question.

Why isn't the setter working for TargetType=UserControl?

The short answer is:

Styles can only be applied to concrete objects.

UserControl is just a class, not a concrete object. The concrete object would be <local:UC_1/>.

The StackPanels on the other hand, surely are concrete objects, even though they are hosted further down the visual tree, inside the UserControl, and therefore a Style can be applied.

Also, it seems like Styles can only be set from top down if there is not already a Style set for the object in the UserControl.
Any Style I'm trying to set from top down would just be overridden again by the Style nested deeper inside the visual tree (last Style wins).
This applies to any Styles set by a ResourceDirectory for objects in a UserControl as well.

Upvotes: 0

Sinatr
Sinatr

Reputation: 21979

Add IsReadOnly dependency property to UserControl:

<ScrollViewer>
    <StackPanel>
        <DockPanel>
            <TextBlock Text="Surname"/>
            <Border>
                <TextBox Text="{Binding Model.Surname}" />
            </Border>
        </DockPanel>
        <DockPanel>
            <TextBlock Text="Firstname" />
            <Border>
                <TextBlock x:Name="textBlock" Text="{Binding Model.Firstname}" />
                <TextBox x:Name="textBox" Text="{Binding Model.Firstname}"     />
            </Border>
        </DockPanel>
    </StackPanel>
</ScrollViewer>

and code (could be done using bindings and converters, x:Name-way is just quicker way to demonstrate):

public bool IsReadOnly
{
    get { return (bool)GetValue(IsReadOnlyProperty); }
    set { SetValue(IsReadOnlyProperty, value); }
}
public static readonly DependencyProperty IsReadOnlyProperty =
    DependencyProperty.Register("IsReadOnly", typeof(bool), typeof(MyUserControl), new PropertyMetadata(false, (d, e) =>
    {
        var userControl = (MyUserControl)s;
        var value = (bool)e.NewValue;
        userControl.textBlock.Visible = value ? Visibility.Visible : Visibility.Hidden;
        userControl.textBox.Visible = !value ? Visibility.Visible : Visibility.Hidden;
    }));

Then you should be able to set it in tab page:

<TabItem Header="MyUserControl">
    <local:MyUserControl IsReadOnly="{Binding SomeBinding}"/>
</TabItem>

Upvotes: 0

Related Questions