Using WPF and MVVM to edit F # records

In a previous question, I asked how to use WPF and MVVM to create a closing dialog for adding new Person entries to F #. Now my next step is to make another dialog to edit these entries. But I have not developed how to transfer an existing entry to the ViewModel and use it to populate the dialog fields. I get exceptions because F # entries are immutable, while the ViewModel seems to expect a mutable object.

I will show you the code for my existing Add dialog - suppose the Edit dialog looks the same.

This is the Person entry:

type Person = { Name: string; Email: string }

Here is the XAML for the Add dialog box:

<Window 
    xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
    xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
    xmlns:fsxaml="http://github.com/fsprojects/FsXaml"
    xmlns:local="clr-namespace:ViewModels;assembly=Test3"
    local:DialogCloser.DialogResult="{Binding DialogResult}"
    Title="Add Person" Height="150" Width="210" ResizeMode="NoResize" >
    <Window.DataContext>
        <local:PersonAddVM />
    </Window.DataContext>
    <StackPanel>
        <Grid FocusManager.FocusedElement="{Binding ElementName=_name}">
            <Grid.RowDefinitions>
                <RowDefinition Height="Auto" />
                <RowDefinition Height="Auto" />
            </Grid.RowDefinitions>
            <Grid.ColumnDefinitions>
                <ColumnDefinition Width="50" />
                <ColumnDefinition Width="140" />
            </Grid.ColumnDefinitions>
            <Label Content="_Name" Target="_name" Grid.Row="0" Grid.Column="0" Margin="2" />
            <TextBox Text="{Binding Name, UpdateSourceTrigger=PropertyChanged}" x:Name="_name" 
                     Grid.Row="0" Grid.Column="1" Margin="4" />
            <Label Content="_Email" Target="_email" Grid.Row="2" Grid.Column="0" Margin="2" HorizontalAlignment="Left" />
            <TextBox Text="{Binding Email, UpdateSourceTrigger=PropertyChanged}" x:Name="_email" 
                     Grid.Row="1" Grid.Column="1" Margin="4" />
        </Grid>
        <UniformGrid Rows="1" Columns="2" VerticalAlignment="Center" Margin="2,20,2,2" >
            <Button Content="OK" IsDefault="True" IsEnabled="{Binding IsValid}" Command="{Binding OkCmd}"
                    HorizontalAlignment="Right" Margin="6,0" Width="50" />
            <Button Content="Cancel" IsCancel="True" HorizontalAlignment="Left" Margin="6,0" Width="50" />
        </UniformGrid>
    </StackPanel>
</Window>

This is his (simplified) ViewModel:

type PersonAddVM() as self =
    inherit DialogVMBase()  // ViewModelBase with a DialogResult property

    let name = self.Factory.Backing( <@ self.Name @>, "", hasLengthAtLeast 4 )
    let email = self.Factory.Backing( <@ self.Email @>, "", hasLengthAtLeast 5 )

    let makePerson () = { Name = name.Value; Email = email.Value }

    member self.Name with get() = name.Value and set value = name.Value <- value
    member self.Email with get() = email.Value and set value = email.Value <- value

    member self.OkCmd = self.Factory.CommandSync( fun () ->
                            PersonCache.Add (makePerson())  // PersonCache is based on an Observable Dictionary
                            self.DialogResult <- true )

PersonList, ViewModel:

    type PersonListView = XAML<"PersonListView.xaml">
    type PersonAddView = XAML<"PersonAddView.xaml">

    module PersonViewHandling =
        let OpenList() = DialogHelper.OpenDialog (PersonListView())
        let OpenAdd() = DialogHelper.OpenDialog (PersonAddView())  // Calls ShowDialog on the view and handles the result

    type PersonListVM() as self =
        inherit DialogVMBase()  

        // I want to use the next 3 lines to access the Person selected in the list, 
        // to pass it to the Edit dialog
        let emptyPerson = { Name = ""; Email = "" }
        let selectedPerson = self.Factory.Backing( <@ self.SelectedPerson @>, emptyPerson )    
        member self.SelectedPerson with get() = selectedPerson.Value and set value = selectedPerson.Value <- value

        member self.AddCmd = self.Factory.CommandSync (fun _ -> PersonViewHandling.OpenAdd() |> ignore)

, ( ), Edit SelectedPerson ?

+4
1

, .

, , , . , :

type PersonAddVM (initial: Person) as self =
    // Then "fill in" based off the selection here...
    let name = self.Factory.Backing( <@ self.Name @>, initial.Name, hasLengthAtLeast 4 )
    // ...

    // Add an easy way to fetch the person:
    member this.EditedPerson with get () = makePerson ()

XAML :

  member self.AddCmd = 
      self.Factory.CommandSync 
         (fun _ -> 
              // Build this out manually:
              let dlg = PersonAddView()
              dlg.DataContext <- PersonAddVM(self.SelectedPerson)
              if  dlg.ShowDialog () = true then
                  let newPerson = dlg.Person
                  // Do something with newPerson here
         )

, ( ).

"", , , Person option , :

module PersonViewHandling =
    let OpenAdd initial = 
        let vm = PersonAddVM(initial)
        let win = PersonAddView(DataContext = vm)
        if DialogHelper.OpenDialog (win) then
             Some win.Person
        else 
             None

Person option, .

+5

Source: https://habr.com/ru/post/1663716/


All Articles