440 likes | 923 Views
MVVM Overview. Frank Shoemaker MindCrafted Systems frank@mindcrafted.com. Overview of MVVM. Some background Some examples. MVVM. MVVM stands for M odel V iew V iew- M odel. Simple Case. Model. Typical class that covers a database Could be a WCF Service and its client reference.
E N D
MVVM Overview Frank Shoemaker MindCrafted Systems frank@mindcrafted.com
Overview of MVVM Some background Some examples
MVVM • MVVM stands for • Model • View • View-Model
Model Typical class that covers a database Could be a WCF Service and its client reference
ViewModel Provides data to and from the View Responds to both the View and the Model Informs the View of changes in the data Reusable (at least much more than code behind a form)
ViewModel • Knows nothing about the View • Does not “push” data into the view TextBox1.Text = object.Name()
View Uses Binding to “subscribe” to the ViewModel Interprets business data and state of ViewModel to the human Nothing but Presentation - XAML No or minimal code-behind
More on the ViewModel in MVVM • WPF/Silverlight data binding is what makes things work • ViewModel presents a “published interface” to the View • Binding in the XAML instead of code • More use of Declarative Programming
Model Class Business data Properties Properties to return “Select” lists (AllSelect and StatusSelect) The usual CRUD routines
Model Class • Encapsulates how it communications with the back-end • Uses Events to signal I/O successfully completed or an error occurred • In WPF it’s synchronous, but can be used as if it’s a asynchronous. • In Silverlight it’s async.
Model Class – I/OWPF - Synchronous Public Sub Read(ByVal id As Integer) Try myLibraryObj.MCFetch(id) RaiseEvent IOSuccessful("Read", New EventArgs()) Catch ex As Exception RaiseEvent IOErrorOccurred("Read", ex) End Try End Sub
Model Class – I/OSilverlight - Asynch Public Sub Read(ByVal id As Integer) Try myService.FetchAsync(id, ServiceReference1.myContextPayloadType.Heavy) Catch ex As Exception RaiseEventIOErrorOccurred("Read", ex) End Try End Sub
Model Class – I/OSilverlight - Asynch Private Sub Read_Completed(ByVal sender As System.Object, ByVale As ServiceReference1.FetchCompletedEventArgs) Handles myService.FetchCompleted If IsNothing(e.Error) Then myData = e.Result RaiseEventIOSuccessful("Read", New EventArgs()) Else RaiseEventIOErrorOccurred("Read", e.Error) End If End Sub
ViewModel Properties Properties come in two flavors • Data Properties • Name, StatusID, CreateDate, AllSelect • Form State properties • IsEditing, IsNotEditing, IsOKToBeginEdit
ViewModel Methods Methods come in two flavors • CRUD Methods • Create, Read, Update, Delete • Implements the IEditableObject interface • BeginEdit, CancelEdit, EndEdit
ViewModel Class Events • Implements INotifyPropertyChanged interface • Notify the View that a property has changed its value • Allows the View to respond to reflect the change, if it wants to
ViewModel Class Properties Present some properties in more than one way for the convenience of the View • IsEditing • True if the user is currently editing the business object • IsNotEditing • True if the user is NOT currently editing the business object.
Gluing the Pieces Together DataContext Binding
Setup the DataContext • This example sets up the DataContext in code • Create a new instance of the ViewModel • Bind the View to the ViewModel Instance • All Controls on the View then “inherit” the DataContext from the page.
Set up the DataContext Private Sub Page_Loaded(ByVal sender As Object, ByVale As System.Windows.RoutedEventArgs) myVM = New ViewModel.DepartmentVM() ' Set the DataContext for the ' entire page to the ViewModel Me.DataContext = myVM End Sub
Data Binding - ViewModelINotifyPropertyChanged Public Event PropertyChanged(ByVal sender As Object, ByVal e As PropertyChangedEventArgs) _ Implements INotifyPropertyChanged.PropertyChanged
Data Binding - ViewModelDeparment.CreatedBy Public Property CreatedBy() As String Get Return myModel.CreatedBy End Get Set(ByVal value As String) myModel.CreatedBy = value RaiseEventPropertyChanged(Me, New PropertyChangedEventArgs("CreatedBy")) End Set End Property
Data Binding - ViewDeparment.CreatedBy <TextBoxText="{Binding Path=CreateDate, Mode=OneWay}" . . . Path is within the DataContext Mode=OneWay means the control won’t update the ViewModel Since it’s bound to CreateDate, when the PropertyChanged event is raised the control reloads from the CreateDate property in the ViewModel Controls don’t need to be named
Binding For State <TextBox Text="{Binding Path=Name,Mode=TwoWay, IsEnabled="{Binding Path=IsEditing}" . . . Binding to interpret the ViewModel’s state to the user.
Binding for State <Button Name="Edit" Content="Edit" IsEnabled="{Binding Path=IsOKToBeginEdit}" Click="Edit_Begin" . . . <Button Name="Save" Content="Save" IsEnabled="{Binding Path=IsEditing}" Click="Edit_Save" . . . <Button Name="Cancel" Content="Cancel" IsEnabled="{Binding Path=IsEditing}" Click="Edit_Cancel" . . .
TwoWay Data Bindning <TextBox Text="{Binding Path=Name, Mode=TwoWay, . . . Bi-directional binding.
Validating in the ViewModel <TextBox Text="{Binding Path=Name, Mode=TwoWay, ValidatesOnExceptions=True}" . . . When the ViewModel throws the exception, the View changes
Setting up Styles for Validation Binding <Style x:Key="styleTextBox" TargetType="TextBox"> . . . <Setter Property="Validation.ErrorTemplate" Value="{StaticResource errorEyeCatcher}"/> <Style.Triggers> <Trigger Property="Validation.HasError" Value="true"> <Setter Property="ToolTip" Value="{Binding RelativeSource={RelativeSource Self}, Path=(Validation.Errors)[0].ErrorContent}"/> </Trigger> </Style.Triggers> </Style>
Setting up Styles for Validation Binding <!--Display a Red * next to the control with an error --> <ControlTemplate x:Key="errorEyeCatcher"> <DockPanel> <Border BorderBrush="Red" BorderThickness="1" Padding="2"> <AdornedElementPlaceholder/> </Border> <TextBlock Foreground="Red" FontSize="24" Text="*"/> </DockPanel> </ControlTemplate>
When to Validate? <TextBox Text="{Binding Path=Name, Mode=TwoWay, UpdateSourceTrigger=LostFocus,ValidatesOnExceptions=True}" IsEnabled="{Binding Path=IsEditing}" . . .
ComboBox Binding <ComboBoxItemsSource="{Binding Path=StatusSelect}" DisplayMemberPath="Value" SelectedValuePath="Key" SelectedValue="{Binding Path=StatusID, Mode=TwoWay}" . . .
Data BindingViewModel.FormStateMessage The state of Editing is maintained by the ViewModel ViewModel exposes properties to indicate state View interprets the ViewModel’s state to the user
Displaying the Status Message <TextBlock Text="{Binding Path=FormStateMessage}" . . .
Binding to Change Color of the Message if it’s an Error <TextBlock Text="{Binding Path=FormStateMessage}" Foreground="{Binding Path=FormStateMessageType, Converter={StaticResourceMessageForegroundColor}, ConverterParameter=FormStateMessageType}" . . . Use a converter routine to transform integer from the ViewModel into a color on theTextBox
Converter Routine Public Function Convert(ByVal value As Object, ByVal targetType As System.Type, ByVal parameter As Object, ByVal culture As System.Globalization.CultureInfo) _ As Object Implements System.Windows.Data.IValueConverter.Convert If CInt(value) = 1 Then ' Informational message Return "Black" Else ' Error message Return "Red" End If End Function
Setup the Converter Routine as a Resource in the XAML <Page.Resources> <converter:MessageForegroundColor x:Key="MessageForegroundColor" /> </Page.Resources>
Testing Since ViewModels know nothing about the UI, they can be driven with a programmatic test case.
MVVM Wrap up Loose coupling between the Model, ViewModel, and View Bright lines between areas of concerns At least some chance of reusability of the ViewModel ViewModel is independently testable
MVVM Wrap up View can be worked on by designers without messing up the ViewModel Would benefit from a root ViewModel class for the state management.