views:

20

answers:

1

I'm trying to learn how to separate a view from its associated viewmodel, while making the view have as little or no code-behind as possible.

my control has a textblock when the object is in a display mode, and a textbox when the user wants to edit that field. In both cases, these controls must bind to the same string in the modelview, but only the appropriate one should be displayed depending on the state of the viewmodel. Previously, I'd just modify the panels child to a new element in the codebehind... but as I understand it, I should be trying to make all my changes in XAML for the view.

My viewmodel has a bool denoting if its in display or edit mode. Is there a way to specify use of a different template depending on the value of that bool, but keep it all in XAML?

+1  A: 

There is a way to do what you say you're working on, by using DataTriggers.

First, define a Style which contains the DataTriggers you want to use. For example, note here two identical styles for a ContentControl, each with a DataTrigger pair that performs the opposite of the other:

<Window.Resources>
     <Style TargetType="{x:Type ContentControl}" x:Key="HiddenWhenFalse" >
        <Setter Property="Visibility" Value="Collapsed"/>
        <Style.Triggers>
            <DataTrigger  Value="False" Binding="{Binding MyBooleanValue}">
                <Setter Property="Visibility" Value="Collapsed" />
            </DataTrigger>
            <DataTrigger  Value="True" Binding="{Binding MyBooleanValue}">
                <Setter Property="Visibility" Value="Visible" />
            </DataTrigger>
        </Style.Triggers>
    </Style>


    <Style TargetType="{x:Type ContentControl}" x:Key="HiddenWhenTrue" >
        <Setter Property="Visibility" Value="Visible"/>
        <Style.Triggers>
            <DataTrigger  Value="True" Binding="{Binding MyBooleanValue}">
                <Setter Property="Visibility" Value="Collapsed" />
            </DataTrigger>
            <DataTrigger  Value="False" Binding="{Binding MyBooleanValue}">
                <Setter Property="Visibility" Value="Visible" />
            </DataTrigger>
        </Style.Triggers>
    </Style>
</Window.Resources>

Then, in your main visual tree you would define ContentControls which use those Styles, and assign the DataContext of the Window or UserControl or whatever to your ViewModel. Something like this:

<Grid>
    <StackPanel >
        <Button Content="False" Name="Button2"></Button>
        <Button Content="True" Name="Button1"></Button>
        <ContentControl Style="{StaticResource HiddenWhenFalse}">
            <ContentControl.Content>
                <TextBlock Text="ITS ALL TRUE"/>
            </ContentControl.Content>
        </ContentControl>
        <ContentControl Style="{StaticResource HiddenWhenTrue}">
            <ContentControl.Content>
                <TextBlock Text="ITS ALL FALSE"/>
            </ContentControl.Content>
        </ContentControl>
    </StackPanel>
</Grid>

Here is the ViewModel I'm using, note the implementation of INotifyPropertyChanged:

Imports System.ComponentModel

Public Class MainWindowViewModel
    Implements INotifyPropertyChanged
    Private _MyBooleanValue = False
    Public Property MyBooleanValue
        Get
            Return _MyBooleanValue
        End Get
        Set(ByVal value)
            _MyBooleanValue = value
            RaiseEvent PropertyChanged(Me, New PropertyChangedEventArgs(Nothing))
        End Set
    End Property

    Public Event PropertyChanged(ByVal sender As Object, ByVal e As System.ComponentModel.PropertyChangedEventArgs) Implements System.ComponentModel.INotifyPropertyChanged.PropertyChanged
End Class

Now, for the purposes of this sample, I simply wired up the two buttons to set the ViewModel value. If you want to use Commanding to wire up buttons, that's an entirely different topic. It's worth discussing, but for the sake of simplicity:

Class MainWindow 
    Private vm As New MainWindowViewModel
    Private Sub Button1_Click(ByVal sender As System.Object, ByVal e As System.Windows.RoutedEventArgs) Handles Button1.Click
        vm.MyBooleanValue = True
    End Sub

    Private Sub Button2_Click(ByVal sender As System.Object, ByVal e As System.Windows.RoutedEventArgs) Handles Button2.Click
        vm.MyBooleanValue = False
    End Sub

    Private Sub MainWindow_Loaded(ByVal sender As Object, ByVal e As System.Windows.RoutedEventArgs) Handles Me.Loaded
        Me.DataContext = vm
    End Sub
End Class

Bear in mind that this sample explicitly styles a ContentControl, and that you'll have to change the TargetType of your style if you're working with a type that isn't descended from that class.

Rob Perkins