views:

621

answers:

5

I'm developing an XBAP and i have a simple requirement.

The DataContext of the whole main page is set to an instance of my UserViewModel. The UserViewModel has a DependencyProperty called AuthenticationState which is an enum with values like 'Authenticated','NotAutheticated' and 'AuthenticationFailed'.

Now, i need to respond to any change in this value by hiding/displaying various elements on the page.

What (and where) is the best way of doing that?

A: 

The best way would be to use a DataTrigger. So something like this:

<Window.Triggers>
    <DataTrigger Binding="{Binding AuthenticationState}" Value="NotAuthenticated">
     <Setter TargetName="nameOfControl" Property="Visibility" Value="Collapsed" />
    </DataTrigger>
    ...
    <TextBox x:Name="nameOfControl" />
</Window.Triggers>

As long as you UserViewModel object is in the DataContext of the Window then this should work!

Steven
I can't do that. Window is a FrameworkElement which only accept EventTriggers in their triggers collection.
Stimul8d
That is a good point, I forgot about that. I rarely run into that issue becuase just about everything I do is done in Control/DataTemplates.
Steven
+2  A: 

As you mentioned you can't use a DataTrigger directly on a control. A work around would be to use a style on each Control that needs to be hidden.

<Grid>
    <Rectangle Fill="Red" />
    <Grid.Style>
     <Style TargetType="Grid">
      <Style.Triggers>
       <DataTrigger Binding="{Binding Test}" Value="true">
        <Setter Property="Visibility" Value="Collapsed" />
       </DataTrigger>
      </Style.Triggers>
     </Style>
    </Grid.Style>
</Grid>
Steven
You can also set the Style on your Window as you have in your other answer and use Setter.TargetName. That way you'll have 1 trigger and many setters.
Robert Macnee
I would be very surprised if that would work because I don't think that the TargetName would resolve because the context that the Trigger is used.
Steven
+2  A: 

A preferable method would be to use a Converter called "AuthenticationStateToVisibilityConverter" that is used in binding the control's Visibility property to the data context's AuthenticationState property.

Steven
lol. Should have spotted that! Can't you just tell it's 5:00 on a Friday.
Stimul8d
A: 

Actually, the best way to do this is to expose the appropriate properties from your view model. This makes your logic more centralized and easier to test. Also, it performs better than converters. It is, after all, a view model. Therefore, it should model the view. If the view needs a property to tell it when to hide / show a panel, add such a property to your view model.

HTH, Kent

Kent Boogaart
I did try to do that but where's the hook into that property? Whose triggers collection can i use to perform the switch?
Stimul8d
Using DependencyObjects (and DependencyPropertys) for view models is a poor practice IMHO. You're much better off using POCOs that implement INotifyPropertyChanged. That said, you can use DependencyPropertyDescriptor.AddValueChanged() to listen for changes.
Kent Boogaart
You'll quickly have an explosion of properties like "ShowViewModel". Then you will have cases where you can't have the same ViewModel instance in more than one spot because there is a difference expected behavior for different scenerios. A converter is maintainable and testing is just as easy.
Steven
There are other ways to factor out the logic. Base view model classes, for one. Utility classes are another (basically what converters amount to). This keeps your XAML cleaner and more performant.
Kent Boogaart
By the way, there are a lot of other good reasons not to use DependencyObjects as VMs. I am planning a blog post on this topic soon and will link to it from here.
Kent Boogaart
My post on DependencyObject-based view models versus POCO view models is here: http://kentb.blogspot.com/2009/03/view-models-pocos-versus.html
Kent Boogaart
A: 

Managed to sort it using styles. It's a pain but it works!

The full source is below.

<Grid x:Name="contentGrid" Grid.Row="1">
        <!--login-->
        <controls:LoginControl>
            <controls:LoginControl.Style>
                <Style>
                    <Setter Property="Control.Opacity" Value="0"/>
                    <Setter Property="Control.IsHitTestVisible" Value="False"/>
                    <Style.Triggers>
                        <DataTrigger Binding="{Binding Source={StaticResource UserViewModel},Path=UserAuthenticationState}"
                                     Value="{x:Static model:AuthenticationState.NotAuthenticated}">
                            <Setter Property="Control.IsHitTestVisible" Value="True"/>
                            <DataTrigger.EnterActions>
                                <BeginStoryboard>
                                    <Storyboard>
                                        <DoubleAnimation To="1" Duration="0:0:2"
                                                         Storyboard.TargetProperty="Opacity"></DoubleAnimation>
                                    </Storyboard>
                                </BeginStoryboard>
                            </DataTrigger.EnterActions>
                            <DataTrigger.ExitActions>
                                <BeginStoryboard>
                                    <Storyboard>
                                        <DoubleAnimation To="0" Duration="0:0:2"
                                                         Storyboard.TargetProperty="Opacity"></DoubleAnimation>
                                    </Storyboard>
                                </BeginStoryboard>
                            </DataTrigger.ExitActions>
                        </DataTrigger>
                    </Style.Triggers>
                </Style>
            </controls:LoginControl.Style>
        </controls:LoginControl>    

        <!--slider-->
        <slider:PageSlider>
            <Button>1</Button>
            <Button>2</Button>
            <Button>3</Button>
            <slider:PageSlider.Style>
                <Style>
                    <Setter Property="Control.Opacity" Value="0"/>
                    <Setter Property="Control.IsHitTestVisible" Value="False"/>
                    <Style.Triggers>
                        <DataTrigger Binding="{Binding Source={StaticResource UserViewModel},Path=UserAuthenticationState}"
                                     Value="{x:Static model:AuthenticationState.Authenticated}">
                            <Setter Property="Control.IsHitTestVisible" Value="True"/>
                            <DataTrigger.EnterActions>
                                <BeginStoryboard>
                                    <Storyboard>
                                        <DoubleAnimation To="1" Duration="0:0:2"
                                                         Storyboard.TargetProperty="Opacity"></DoubleAnimation>
                                    </Storyboard>
                                </BeginStoryboard>
                            </DataTrigger.EnterActions>
                            <DataTrigger.ExitActions>
                                <BeginStoryboard>
                                    <Storyboard>
                                        <DoubleAnimation To="0" Duration="0:0:2"
                                                         Storyboard.TargetProperty="Opacity"></DoubleAnimation>
                                    </Storyboard>
                                </BeginStoryboard>
                            </DataTrigger.ExitActions>
                        </DataTrigger>
                    </Style.Triggers>
                </Style>
            </slider:PageSlider.Style>
        </slider:PageSlider>
    </Grid>
Stimul8d
You'll want a trigger that collapses the control when the opacity is 0.
Steven